diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7895119f..0ca212fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: - name: Read VERSION id: extract - run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT + run: echo "version=$(cat VERSION.pz)" >> $GITHUB_OUTPUT build: needs: version @@ -42,13 +42,44 @@ jobs: - os: windows-2025 arch: x64 cmake_arch: x64 + build_type: Release + generator: "Visual Studio 17 2022" + - os: windows-2025 + arch: x64 + cmake_arch: x64 + build_type: Debug + generator: "Visual Studio 17 2022" - os: windows-2025 arch: x86 cmake_arch: Win32 + build_type: Release + generator: "Visual Studio 17 2022" + - os: windows-2025 + arch: x86 + cmake_arch: Win32 + build_type: Debug + generator: "Visual Studio 17 2022" + - os: ubuntu-latest + arch: x64 + cmake_arch: native + build_type: Release + generator: "Ninja" - os: ubuntu-latest arch: x64 cmake_arch: native - name: Build ${{ matrix.arch }} on ${{ matrix.os }} + build_type: Debug + generator: "Ninja" + - os: macos-latest + arch: arm64 + cmake_arch: native + build_type: Release + generator: "Ninja" + - os: macos-latest + arch: arm64 + cmake_arch: native + build_type: Debug + generator: "Ninja" + name: Build ${{ matrix.build_type }} ${{ matrix.arch }} on ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v4.2.2 @@ -60,6 +91,17 @@ jobs: with: cmakeVersion: latest + - name: Install Ninja (non-Windows) + if: matrix.generator == 'Ninja' + run: | + if [[ "$RUNNER_OS" == "Linux" ]]; then + sudo apt-get update + sudo apt-get install -y ninja-build + elif [[ "$RUNNER_OS" == "macOS" ]]; then + brew install ninja + fi + shell: bash + - name: Install dependencies (Linux) if: matrix.os == 'ubuntu-latest' run: | @@ -67,43 +109,59 @@ jobs: sudo apt-get install -y \ build-essential libx11-dev libxcursor-dev libxi-dev \ libxrandr-dev libxext-dev libdrm-dev libgbm-dev \ - libwayland-dev libegl1-mesa-dev libfreetype6-dev libharfbuzz-dev + libwayland-dev libegl1-mesa-dev libfreetype6-dev libharfbuzz-dev \ + libopenmpt-dev + shell: bash + + - name: Install dependencies (macOS) + if: matrix.os == 'macos-latest' + run: | + brew install freetype harfbuzz openal-soft libopenmpt + shell: bash - name: Configure run: | - if [[ "$RUNNER_OS" == "Windows" ]]; then - cmake -S . -B build_${{ matrix.arch }} -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=Release -A ${{ matrix.cmake_arch }} - else - cmake -S . -B build_${{ matrix.arch }} -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release - fi + cmake -S . -B build_${{ matrix.arch }}_${{ matrix.build_type }} \ + -G "${{ matrix.generator }}" \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + ${{ matrix.os == 'windows-2025' && format('-A {0}', matrix.cmake_arch) || '' }} \ + -DCMAKE_INSTALL_PREFIX=$PWD/dist/install shell: bash - name: Build - run: cmake --build build_${{ matrix.arch }} --config Release + run: cmake --build build_${{ matrix.arch }}_${{ matrix.build_type }} --config ${{ matrix.build_type }} + shell: bash - name: Install - run: cmake --install build_${{ matrix.arch }} --config Release + run: cmake --install build_${{ matrix.arch }}_${{ matrix.build_type }} --config ${{ matrix.build_type }} + shell: bash - name: Organize output - shell: bash run: | mkdir -p dist/examples dist/tools - cp VERSION dist/ + cp VERSION.pz dist/ if [[ -d examples/bin ]]; then - cp -r examples/bin/. dist/examples/ + cp -r examples/bin/. dist/examples/ else - echo "warning: examples/bin does not exist." + echo "warning: examples/bin does not exist." fi if [[ "$RUNNER_OS" == "Windows" && -d tools ]]; then cp -r tools/*.exe dist/tools/ || echo "tools not found" + elif [[ "$RUNNER_OS" == "macOS" && -d tools ]]; then + cp -r tools/* dist/tools/ || echo "tools not found" fi + shell: bash + + - name: Remove install folder + run: rm -rf dist/install + shell: bash - name: Upload dist directory uses: actions/upload-artifact@v4 with: - name: PopLib-${{ needs.version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }} + name: PopLib-${{ needs.version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.build_type }} path: dist/ release: diff --git a/.gitignore b/.gitignore index f706c08c..40466bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,10 @@ examples/bin/* !examples/bin/*.* !examples/bin/**/ +crash.txt +mem_leaks.txt +temp/ + examples/bin/*.exe examples/bin/*.dll examples/bin/*.pdb @@ -39,6 +43,19 @@ examples/bin/screenshots/* docs/doxygen/ imgui.ini +# IMPORTANT +private/steam/sdk +libsteam_api.so +libsteam_api.dylib +steam_api.dll +steam_api64.dll +steam_appid.txt +examples/bin/libopenmpt.dll +examples/bin/openmpt-mpg123.dll +examples/bin/openmpt-ogg.dll +examples/bin/openmpt-vorbis.dll +examples/bin/openmpt-zlib.dll + # tools PopPak.exe PopPakPWE.exe \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 3e82e92a..00000000 --- a/.gitmodules +++ /dev/null @@ -1,30 +0,0 @@ -[submodule "external/vorbis"] - path = external/vorbis - url = https://github.com/teampopwork/vorbis -[submodule "external/SDL"] - path = external/SDL - url = https://github.com/teampopwork/SDL -[submodule "external/SDL_ttf"] - path = external/SDL_ttf - url = https://github.com/teampopwork/SDL_ttf -[submodule "external/openal"] - path = external/openal - url = https://github.com/teampopwork/openal-soft -[submodule "external/ogg"] - path = external/ogg - url = https://github.com/teampopwork/ogg -[submodule "external/miniaudio"] - path = external/miniaudio - url = https://github.com/teampopwork/miniaudio -[submodule "external/curl"] - path = external/curl - url = https://github.com/teampopwork/curl.git -[submodule "external/zlib"] - path = external/zlib - url = https://github.com/madler/zlib -[submodule "external/discordrpc"] - path = external/discordrpc - url = https://github.com/EclipseMenu/discord-presence -[submodule "external/tinyxml2"] - path = external/tinyxml2 - url = https://github.com/leethomason/tinyxml2 diff --git a/.vscode/launch.json b/.vscode/launch.json index 4205b9c3..3a81cfbb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,76 +2,43 @@ "version": "0.2.0", "configurations": [ { - "name": "Debug Demo1 (MSVC)", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/examples/bin/Demo1.exe", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}/examples/bin/", - "environment": [], - "console": "internalConsole" - }, - { - "name": "Debug Demo2 (MSVC)", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/examples/bin/Demo2.exe", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}/examples/bin/", - "environment": [], - "console": "internalConsole" - }, - { - "name": "Debug Demo3 (MSVC)", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/examples/bin/Demo3.exe", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}/examples/bin/", - "environment": [], - "console": "internalConsole" - }, - { - "name": "Debug Demo4 (MSVC)", + "name": "Debug V14Demo (MSVC)", "type": "cppvsdbg", "request": "launch", - "program": "${workspaceFolder}/examples/bin/Demo4.exe", + "program": "${workspaceFolder}/examples/bin/V14Demo.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/examples/bin/", "environment": [], "console": "internalConsole" }, - { - "name": "Debug Demo5 (MSVC)", + { + "name": "Debug PhysicsDemo (MSVC)", "type": "cppvsdbg", "request": "launch", - "program": "${workspaceFolder}/examples/bin/Demo5.exe", + "program": "${workspaceFolder}/examples/bin/PhysicsDemo.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/examples/bin/", "environment": [], "console": "internalConsole" }, - { - "name": "Debug V12Demo (MSVC)", + { + "name": "Debug Barebones (MSVC)", "type": "cppvsdbg", "request": "launch", - "program": "${workspaceFolder}/examples/bin/V12Demo.exe", + "program": "${workspaceFolder}/examples/bin/Barebones.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/examples/bin/", "environment": [], "console": "internalConsole" }, - { - "name": "Debug V14Demo (MSVC)", + { + "name": "Debug Tetris (MSVC)", "type": "cppvsdbg", "request": "launch", - "program": "${workspaceFolder}/examples/bin/V14Demo.exe", + "program": "${workspaceFolder}/examples/bin/Tetris.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/examples/bin/", @@ -79,132 +46,10 @@ "console": "internalConsole" }, { - "name": "Debug Hun-garr (MSVC)", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/examples/bin/Hun-garr.exe", - "args": [], - "cwd": "${workspaceFolder}/examples/bin/", - "stopAtEntry": false, - "environment": [], - "console": "internalConsole" - }, - { - "name": "Debug XMLDemo (MSVC)", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/examples/bin/XMLDemo.exe", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}/examples/bin/", - "environment": [], - "console": "internalConsole" - }, - { - "name": "Debug Demo1 (GDB)", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/examples/bin/Demo1", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}/examples/bin/", - "externalConsole": false, - "MIMode": "gdb", - "miDebuggerPath": "gdb", - "environment": [ - { - "name": "LD_LIBRARY_PATH", - "value": "${workspaceFolder}/examples/bin:${env:LD_LIBRARY_PATH}" - } - ], - "setupCommands": [ - { - "description": "Enable pretty-printing for GDB", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ] - }, - { - "name": "Debug Demo2 (GDB)", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/examples/bin/Demo2", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}/examples/bin/", - "externalConsole": false, - "MIMode": "gdb", - "miDebuggerPath": "gdb", - "environment": [ - { - "name": "LD_LIBRARY_PATH", - "value": "${workspaceFolder}/examples/bin:${env:LD_LIBRARY_PATH}" - } - ], - "setupCommands": [ - { - "description": "Enable pretty-printing for GDB", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ] - }, - { - "name": "Debug Demo3 (GDB)", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/examples/bin/Demo3", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}/examples/bin/", - "externalConsole": false, - "MIMode": "gdb", - "miDebuggerPath": "gdb", - "environment": [ - { - "name": "LD_LIBRARY_PATH", - "value": "${workspaceFolder}/examples/bin:${env:LD_LIBRARY_PATH}" - } - ], - "setupCommands": [ - { - "description": "Enable pretty-printing for GDB", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ] - }, - { - "name": "Debug Demo4 (GDB)", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/examples/bin/Demo4", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}/examples/bin/", - "externalConsole": false, - "MIMode": "gdb", - "miDebuggerPath": "gdb", - "environment": [ - { - "name": "LD_LIBRARY_PATH", - "value": "${workspaceFolder}/examples/bin:${env:LD_LIBRARY_PATH}" - } - ], - "setupCommands": [ - { - "description": "Enable pretty-printing for GDB", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ] - }, - { - "name": "Debug Demo5 (GDB)", + "name": "Debug V14Demo (GDB)", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/examples/bin/Demo5", + "program": "${workspaceFolder}/examples/bin/V14Demo", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/examples/bin/", @@ -225,11 +70,11 @@ } ] }, - { - "name": "Debug V12Demo (GDB)", + { + "name": "Debug PhysicsDemo (GDB)", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/examples/bin/V12Demo", + "program": "${workspaceFolder}/examples/bin/PhysicsDemo", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/examples/bin/", @@ -250,11 +95,11 @@ } ] }, - { - "name": "Debug V14Demo (GDB)", + { + "name": "Debug Barebones (GDB)", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/examples/bin/V14Demo", + "program": "${workspaceFolder}/examples/bin/Barebones", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/examples/bin/", @@ -275,11 +120,11 @@ } ] }, - { - "name": "Debug Hun-garr (GDB)", + { + "name": "Debug Tetris (GDB)", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/examples/bin/Hun-garr", + "program": "${workspaceFolder}/examples/bin/Tetris", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/examples/bin/", @@ -300,30 +145,5 @@ } ] }, - { - "name": "Debug XMLDemo (GDB)", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/examples/bin/XMLDemo", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}/examples/bin/", - "externalConsole": false, - "MIMode": "gdb", - "miDebuggerPath": "gdb", - "environment": [ - { - "name": "LD_LIBRARY_PATH", - "value": "${workspaceFolder}/examples/bin:${env:LD_LIBRARY_PATH}" - } - ], - "setupCommands": [ - { - "description": "Enable pretty-printing for GDB", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ] - } ] } diff --git a/CHANGELOG b/CHANGELOG index 2d15b57d..a174ed43 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,11 +4,21 @@ General ======= * Added the V14 demo, showcases: new blend modes (usage of SDL3), TTF text. * Added x64 support. -* Fixed WideString. +* Removed WideString +* Added macOS (partial) + Linux support. +* Added optional Discord RPC support +* Added Chipmunk physics +* Added Dear ImGui support for debug UI's +* Replaced the normal .pak archives with .gpak (NEW!!!!!!!!!) + +Scripting +========= +* Added Lua scripting support AppBase =========== * Replaced the Windows API input parsing with SDL3 events. +* The demo projects now use PopApp instead of AppBase (you can still use AppBase, but it's gonna miss out on new additions PopApp has) Graphics ================================ @@ -20,8 +30,7 @@ Audio BASS related: ============= -* Upgraded BASS to 2.4 -* Gave access to all functions in the library. +* Replaced BASS with libopenmpt ImageLib ============= diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e220c2f..dfacd7af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,23 @@ # CMakeLists.txt -cmake_minimum_required(VERSION 3.26 FATAL_ERROR) +cmake_minimum_required(VERSION 4.0.2 FATAL_ERROR) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) -project(PopLib LANGUAGES C CXX ASM VERSION 1.4.0) +project(PopLib LANGUAGES C CXX ASM) set(POPLIB_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}) -set(BUILD_SHARED_LIBS OFF) +include(${CMAKE_CURRENT_LIST_DIR}/cmake/CopyDLLPost.cmake) +include(FetchContent) # temp workaround for me set(_Python3_EXECUTABLE_DEBUG "" CACHE FILEPATH "Python debug executable") set_property(GLOBAL PROPERTY USE_FOLDERS ON) -option(FEATURE_DISCORD_RPC "Discord RPC" OFF) +option(FEATURE_DISCORD_RPC "Feature Discord RPC" OFF) +option(FEATURE_STEAM_API "Feature Steam API" OFF) option(BUILD_EXAMPLES "Build Examples" ON) option(CONSOLE "Show the console on Windows" ON) option(BUILD_TOOLS "Build Tools" ON) @@ -29,54 +31,195 @@ else () endif() if(FEATURE_DISCORD_RPC) - add_compile_definitions(_FEATURE_DISCORD_RPC) + add_compile_definitions(POP_FEATURE_DISCORD_RPC) endif() -if(CONSOLE AND WIN32) - add_compile_definitions(_CONSOLE_ON) +if(FEATURE_STEAM_API) + add_compile_definitions(POP_FEATURE_STEAM_API) endif() -if (IS_64BIT) - message (STATUS "Getting the x64 BASS path") - set(BASS_LIB_PATH ${POPLIB_ROOT_DIR}/external/bass/x64) -else () - message (STATUS "Getting the x86 BASS path") - set(BASS_LIB_PATH ${POPLIB_ROOT_DIR}/external/bass/x86) +if(CONSOLE AND WIN32) + add_compile_definitions(POP_CONSOLE_ON) endif() -if (WIN32) - set(BASS_PATH "${BASS_LIB_PATH}/bass.dll" CACHE FILEPATH "Path to BASS DLL" FORCE) -elseif (UNIX) - set(BASS_PATH "${BASS_LIB_PATH}/libbass.so" CACHE FILEPATH "Path to BASS SO" FORCE) +if(FEATURE_STEAM_API) + set(STEAM_SDK_DIR "${CMAKE_SOURCE_DIR}/private/steam/sdk" CACHE FILEPATH "Path to Steam API dir" FORCE) + + if (WIN32) + if (IS_64BIT) + message(STATUS "Using Steam API x64 for Windows") + set(STEAMAPI_PATH "${STEAM_SDK_DIR}/redistributable_bin/win64/steam_api64.lib" CACHE FILEPATH "Path to Steam API lib" FORCE) + else() + message(STATUS "Using Steam API x86 for Windows") + set(STEAMAPI_PATH "${STEAM_SDK_DIR}/redistributable_bin/steam_api.lib" CACHE FILEPATH "Path to Steam API lib" FORCE) + endif() + elseif (APPLE) + message(STATUS "Using Steam API for macOS") + set(STEAMAPI_PATH "${STEAM_SDK_DIR}/redistributable_bin/osx/libsteam_api.dylib" CACHE FILEPATH "Path to Steam API lib" FORCE) + elseif (UNIX) + if (IS_64BIT) + message(STATUS "Using Steam API x64 for Linux") + set(STEAMAPI_PATH "${STEAM_SDK_DIR}/redistributable_bin/linux64/libsteam_api.so" CACHE FILEPATH "Path to Steam API lib" FORCE) + else() + message(STATUS "Using Steam API x86 for Linux") + set(STEAMAPI_PATH "${STEAM_SDK_DIR}/redistributable_bin/linux32/libsteam_api.so" CACHE FILEPATH "Path to Steam API lib" FORCE) + endif() + endif() endif() # external libraries set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build as DLL" FORCE) set(SDL_STATIC ON CACHE BOOL "" FORCE) set(SDL_SHARED OFF CACHE BOOL "" FORCE) -set(LIBTYPE "STATIC" CACHE BOOL "" FORCE) # OpenAL -add_subdirectory(external/ogg EXCLUDE_FROM_ALL) -add_subdirectory(external/vorbis EXCLUDE_FROM_ALL) -add_subdirectory(external/SDL EXCLUDE_FROM_ALL) -add_subdirectory(external/SDL_ttf EXCLUDE_FROM_ALL) -add_subdirectory(external/openal EXCLUDE_FROM_ALL) -add_subdirectory(external/miniaudio EXCLUDE_FROM_ALL) -add_subdirectory(external/zlib EXCLUDE_FROM_ALL) -add_subdirectory(external/misc EXCLUDE_FROM_ALL) -add_subdirectory(external/discordrpc EXCLUDE_FROM_ALL) -add_subdirectory(external/tinyxml2 EXCLUDE_FROM_ALL) +set(MINIAUDIO_NO_LIBVORBIS ON CACHE BOOL "Disable miniaudio_libvorbis" FORCE) +set(MINIAUDIO_NO_LIBOPUS ON CACHE BOOL "Disable miniaudio_libopus" FORCE) + +#-- openal +set(LIBTYPE "STATIC" CACHE BOOL "" FORCE) +set(ALSOFT_EXAMPLES OFF CACHE BOOL "" FORCE) +#-- end openal +#-- lua +set(LUA_ENABLE_TESTING OFF CACHE BOOL "Disable Lua tests") +set(LUA_ENABLE_SHARED OFF CACHE BOOL "Build static Lua library") +#-- end lua -# what the hell am i doing with my life +#-- curl set(BUILD_CURL_EXE OFF CACHE BOOL "Build curl executable" FORCE) -# i am asking the same thing buddy. set(CURL_ZLIB OFF CACHE BOOL "Disable curl's use of system zlib" FORCE) set(HTTP_ONLY ON CACHE BOOL "" FORCE) set(CURL_USE_LIBPSL OFF CACHE BOOL "Use libpsl" FORCE) set(CURL_USE_OPENSSL OFF CACHE BOOL "" FORCE) set(CURL_USE_MBEDTLS OFF CACHE BOOL "" FORCE) -add_subdirectory(external/curl EXCLUDE_FROM_ALL) +#-- end curl + +#-- libopenmpt +if(WIN32) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + # x64 + if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64") + set(LIBOPENMPT_ARCH "amd64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64") + set(LIBOPENMPT_ARCH "arm64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64EC") + set(LIBOPENMPT_ARCH "arm64ec") + else() + message(FATAL_ERROR "Unsupported Windows architecture: ${CMAKE_SYSTEM_PROCESSOR}") + endif() + else() + set(LIBOPENMPT_ARCH "x86") + endif() + + set(OPENMPT_LIB_DIR "${CMAKE_SOURCE_DIR}/external/libopenmpt/lib/${LIBOPENMPT_ARCH}") + set(OPENMPT_BIN_DIR "${CMAKE_SOURCE_DIR}/external/libopenmpt/bin/${LIBOPENMPT_ARCH}") + + include_directories("${CMAKE_SOURCE_DIR}/external/libopenmpt/include") + link_directories("${OPENMPT_LIB_DIR}") + set(OPENMPT_LIB_NAME "libopenmpt") + set(OPENMPT_LIBRARIES "${OPENMPT_LIB_DIR}/${OPENMPT_LIB_NAME}.lib") + + set(OPENMPT_FOUND TRUE) +else() + find_package(PkgConfig REQUIRED) + pkg_check_modules(OPENMPT libopenmpt) + + link_directories(${OPENMPT_LIBRARY_DIRS}) +endif() + +if(NOT OPENMPT_FOUND) + message(FATAL_ERROR "libopenmpt not found") +endif() + +message(STATUS "libopenmpt found: includes=${OPENMPT_INCLUDE_DIRS} libs=${OPENMPT_LIBRARIES}") +#-- end libopenmpt + +FetchContent_Declare( + SDL3 + GIT_REPOSITORY https://github.com/libsdl-org/SDL + GIT_TAG main +) + +FetchContent_Declare( + SDL3_TTF + GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf + GIT_TAG main +) + +FetchContent_Declare( + Lua + GIT_REPOSITORY https://github.com/walterschell/Lua + GIT_TAG master +) + +# the most hackiest hacks i've ever done +if(APPLE) + find_library(OPENAL_LIBRARY OpenAL REQUIRED) + + if(NOT OPENAL_LIBRARY) + message(FATAL_ERROR "System OpenAL framework not found") + endif() + + add_library(OpenAL::OpenAL INTERFACE IMPORTED) + set_target_properties(OpenAL::OpenAL PROPERTIES + INTERFACE_LINK_LIBRARIES "${OPENAL_LIBRARY}" + ) + + message(STATUS "Using system OpenAL framework: ${OPENAL_LIBRARY}") +else() + FetchContent_Declare( + OpenAL + GIT_REPOSITORY https://github.com/teampopwork/openal-soft + GIT_TAG master + ) + FetchContent_MakeAvailable(OpenAL) +endif() + +FetchContent_Declare( + miniaudio + GIT_REPOSITORY https://github.com/mackron/miniaudio + GIT_TAG master +) + +FetchContent_Declare( + ogg + GIT_REPOSITORY https://github.com/teampopwork/ogg + GIT_TAG master +) + +FetchContent_Declare( + vorbis + GIT_REPOSITORY https://github.com/teampopwork/vorbis + GIT_TAG master +) + +FetchContent_Declare( + curl + GIT_REPOSITORY https://github.com/curl/curl + GIT_TAG master +) + +FetchContent_Declare( + zlib + GIT_REPOSITORY https://github.com/madler/zlib + GIT_TAG master +) + +FetchContent_Declare( + discordrpc + GIT_REPOSITORY https://github.com/EclipseMenu/discord-presence + GIT_TAG main +) + +FetchContent_Declare( + glm + GIT_REPOSITORY https://github.com/g-truc/glm + GIT_TAG master +) + +FetchContent_MakeAvailable(SDL3 SDL3_TTF miniaudio ogg vorbis curl zlib discordrpc glm Lua) + +add_subdirectory(external/misc EXCLUDE_FROM_ALL) add_subdirectory(PopLib) if(BUILD_EXAMPLES) @@ -89,16 +232,10 @@ if(BUILD_EXAMPLES OR BUILD_TOOLS) if(BUILD_EXAMPLES) list(APPEND demo_deps - Demo1 Demo2 Demo3 Demo4 Demo5 - Hun-garr V12Demo V14Demo XMLDemo + V14Demo PhysicsDemo Barebones + Tetris ) endif() add_custom_target(alldemos ALL DEPENDS ${demo_deps}) endif() - -if(NOT (DEFINED ENV{GITHUB_ACTIONS} AND "$ENV{GITHUB_ACTIONS}" STREQUAL "true")) - add_custom_command(TARGET alldemos POST_BUILD - COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR} --config $ - ) -endif() \ No newline at end of file diff --git a/CMakeSettings.json b/CMakeSettings.json index 302a55b1..0308ea01 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -11,11 +11,6 @@ "buildCommandArgs": "", "ctestCommandArgs": "", "variables": [ - { - "name": "WideStrings", - "value": "False", - "type": "BOOL" - }, { "name": "BuildDemos", "value": "True", diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c2c610de..c4b371e3 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -3,6 +3,6 @@ We expect **everyone** to be respectful and considerate in all interactions. Unacceptable behavior includes harassment, hate speech, and personal attacks. -If you experience or witness violations, please contact [electr0gunner] on Discord. +If you experience or witness violations, please contact *electr0gunner* on Discord. Let’s keep PopLib welcoming for everyone! diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 32fd1605..00000000 --- a/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Dockerfile -FROM ubuntu:22.04 - -RUN apt-get update && apt-get install -y \ - build-essential \ - cmake \ - libfreetype-dev \ - libgl1-mesa-dev \ - libx11-dev \ - libxcursor-dev \ - libxrandr-dev \ - libxi-dev \ - libxext-dev \ - libxinerama-dev \ - libasound2-dev \ - git \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app -COPY . . - -RUN mkdir -p build && cd build && \ - cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=ON -DBUILD_TOOLS=OFF && \ - make -j$(nproc) && \ - cmake --install . - -# CMD ["./examples/bin/Demo1"] diff --git a/NOTICE b/NOTICE index db4f2bba..3b16552d 100644 --- a/NOTICE +++ b/NOTICE @@ -27,6 +27,23 @@ PopLib includes the following third-party libraries: - License: BSD-style License - Source: https://xiph.org/vorbis/ +7. Chipmunk2D + - License: MIT License + - Source: https://github.com/slembcke/Chipmunk2D + - Implementation: https://github.com/keestux/tuxcap + +8. Discord RPC (EclipseMenu) + - License: MIT License + - Source: https://github.com/EclipseMenu/discord-presence + +9. ImGui + - License: MIT License + - Source: https://github.com/ocornut/imgui + +10. libopenmpt + - License: BSD 2-Clause License + - Source: https://lib.openmpt.org/libopenmpt/ + --- For full license texts, please refer to the respective projects or the LICENSE files included in their distributions. diff --git a/PopLib/CMakeLists.txt b/PopLib/CMakeLists.txt index 7d960f78..6f473f11 100644 --- a/PopLib/CMakeLists.txt +++ b/PopLib/CMakeLists.txt @@ -6,6 +6,21 @@ file(GLOB_RECURSE ALL_FILES CONFIGURE_DEPENDS "*.h" "*.hpp" ) +# dirty stupid hack. probably enough to not do #ifdef at the start of the files +set(API_ALLOWED_FILES "") +if(FEATURE_DISCORD_RPC) + list(APPEND API_ALLOWED_FILES + "api/discord.cpp" + "api/discord.hpp" + ) +endif() +if(FEATURE_STEAM_API) + list(APPEND API_ALLOWED_FILES + "api/steam.cpp" + "api/steam.hpp" + ) +endif() + set(FILTERED_FILES "") foreach(FILE_PATH ${ALL_FILES}) file(RELATIVE_PATH REL_PATH "${CMAKE_CURRENT_SOURCE_DIR}" "${FILE_PATH}") @@ -15,6 +30,12 @@ foreach(FILE_PATH ${ALL_FILES}) list(APPEND FILTERED_FILES "${FILE_PATH}") elseif(REL_PATH MATCHES "^graphics/SWTri/") continue() + elseif(REL_PATH MATCHES "^api/") + list(FIND API_ALLOWED_FILES "${REL_PATH}" FILE_INDEX) + if(NOT FILE_INDEX EQUAL -1) + list(APPEND FILTERED_FILES "${FILE_PATH}") + endif() + continue() else() list(APPEND FILTERED_FILES "${FILE_PATH}") endif() @@ -51,6 +72,10 @@ function(capitalize_path INPUT OUTPUT) set(PART "Dear ImGui") elseif(LOWER_PART STREQUAL "readwrite") set(PART "ReadWrite") + elseif(LOWER_PART STREQUAL "api") + set(PART "APIs") + elseif(LOWER_PART STREQUAL "chipmunk") + set(PART "Chipmunk Physics") else() string(SUBSTRING "${PART}" 0 1 FIRST_CHAR) string(SUBSTRING "${PART}" 1 -1 REST_CHARS) @@ -86,20 +111,26 @@ add_library(${PROJECT_NAME} STATIC ${ALL_GROUPED_FILES}) # hacks!! target_include_directories(${PROJECT_NAME} PUBLIC - ${POPLIB_ROOT_DIR} - ${POPLIB_ROOT_DIR}/external/vorbis/include - ${POPLIB_ROOT_DIR}/external/openal - ${POPLIB_ROOT_DIR}/external/ogg/include ${CMAKE_CURRENT_SOURCE_DIR} - ${POPLIB_ROOT_DIR}/external/misc - ${POPLIB_ROOT_DIR}/external/discordrpc ${POPLIB_ROOT_DIR}/external/stb_image + ${OPENMPT_INCLUDE_DIRS} ) -if (FEATURE_DISCORD_RPC) - target_include_directories(${PROJECT_NAME} PUBLIC - ${POPLIB_ROOT_DIR}/external/discordrpc +if (FEATURE_STEAM_API) + if (NOT EXISTS "${STEAM_SDK_DIR}/public/steam/steam_api.h") + message(FATAL_ERROR + "Steamworks SDK not found in ${STEAM_SDK_DIR}.\n" + "Please download the SDK from https://partner.steamgames.com/downloads/ " + "and copy the 'sdk' folder into ${CMAKE_SOURCE_DIR}/private/steam/" + ) + endif() + + target_include_directories(${PROJECT_NAME} + PUBLIC + "${STEAM_SDK_DIR}/public" ) + + target_link_libraries(${PROJECT_NAME} PUBLIC "${STEAMAPI_PATH}") endif() target_compile_definitions(${PROJECT_NAME} PRIVATE @@ -108,10 +139,10 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE STB_IMAGE_WRITE_IMPLEMENTATION ) -if (WIN32) - set(BASS_LIB_PATH "${BASS_LIB_PATH}/bass.lib") -elseif (UNIX) - set(BASS_LIB_PATH "${BASS_LIB_PATH}/libbass.so") +if (NOT WIN32) + target_compile_definitions(${PROJECT_NAME} PRIVATE + $<$:_DEBUG> + ) endif() target_link_libraries(${PROJECT_NAME} PUBLIC @@ -124,9 +155,10 @@ target_link_libraries(${PROJECT_NAME} PUBLIC Vorbis::vorbis CURL::libcurl zlibstatic - tinyxml2 + glm misc - ${BASS_LIB_PATH} + lua_static + ${OPENMPT_LIBRARIES} ) if (FEATURE_DISCORD_RPC) diff --git a/PopLib/api/discord.cpp b/PopLib/api/discord.cpp index 9072cb98..ec7b54db 100644 --- a/PopLib/api/discord.cpp +++ b/PopLib/api/discord.cpp @@ -1,75 +1,72 @@ -#ifdef _FEATURE_DISCORD_RPC - +#define POPLIB_SKIP_INT64_TYPEDEF #include #include "discord.hpp" -#include "../Debug/debug.hpp" +#include "debug/log.hpp" using namespace PopLib; - -DiscordRPC::DiscordRPC(const char* theAppID) + +DiscordRPC::DiscordRPC(const char *theAppID) { - mStartTime = SDL_GetTicks(); - mRPCData = {"test", "test", "icon", "icon"}; - mSendRPC = true; - mAppID = theAppID; - mHasInitialized = false; - InitRPC(); + mStartTime = SDL_GetTicks(); + mRPCData = {"test", "test", "icon", "icon"}; + mSendRPC = true; + mAppID = theAppID; + mHasInitialized = false; + InitRPC(); } - + DiscordRPC::~DiscordRPC() { - discord::RPCManager::get().shutdown(); + discord::RPCManager::get().shutdown(); } - + bool DiscordRPC::InitRPC() { - discord::RPCManager::get() - .setClientID(mAppID) - .onReady([](discord::User const& user) { - SDL_Log("Discord: connected to user %s#%s - %s", user.username.c_str(), user.discriminator.c_str(), user.id.c_str()); - }) - .onDisconnected([](int errcode, std::string_view message) { - SDL_Log("Discord: disconnected with error code %i - %s", errcode, std::string(message)); - }) - .onErrored([](int errcode, std::string_view message) { - SDL_Log("Discord: error with code %i - %s", errcode, std::string(message)); - }) - .onJoinGame([](std::string_view joinSecret) { - SDL_Log("Discord: join game - %s", std::string(joinSecret)); - }) - .onSpectateGame([](std::string_view spectateSecret) { - SDL_Log("Discord: spectate game - %s", std::string(spectateSecret)); - }) - .onJoinRequest([](discord::User const& user) { - SDL_Log("Discord: join request from %s#%s - %s", user.username.c_str(), user.discriminator.c_str(), user.id.c_str()); - }); - discord::RPCManager::get().initialize(); - mHasInitialized = true; - return true; + discord::RPCManager::get() + .setClientID(mAppID) + .onReady([](discord::User const &user) { + LOG_INFO("Discord: connected to user %s#%s - %s", user.username.c_str(), user.discriminator.c_str(), + user.id.c_str()); + }) + .onDisconnected([](int errcode, std::string_view message) { + LOG_INFO("Discord: disconnected with error code %i - %s", errcode, std::string(message)); + }) + .onErrored([](int errcode, std::string_view message) { + LOG_INFO("Discord: error with code %i - %s", errcode, std::string(message)); + }) + .onJoinGame([](std::string_view joinSecret) { LOG_INFO("Discord: join game - %s", std::string(joinSecret)); }) + .onSpectateGame([](std::string_view spectateSecret) { + LOG_INFO("Discord: spectate game - %s", std::string(spectateSecret)); + }) + .onJoinRequest([](discord::User const &user) { + LOG_INFO("Discord: join request from %s#%s - %s", user.username.c_str(), user.discriminator.c_str(), + user.id.c_str()); + }); + discord::RPCManager::get().initialize(); + mHasInitialized = true; + return true; } void DiscordRPC::UpdateRPC() { - if (!mSendRPC) { - discord::RPCManager::get().clearPresence(); - return; - } - auto& rpc = discord::RPCManager::get(); - - - std::time_t current_time = std::time(nullptr); - Uint32 elapsed_ms = SDL_GetTicks() - mStartTime; - std::time_t startTimestamp = current_time - (elapsed_ms / 1000); + if (!mSendRPC) + { + discord::RPCManager::get().clearPresence(); + return; + } + auto &rpc = discord::RPCManager::get(); - rpc.getPresence() - .setState(mRPCData.mState) - .setDetails(mRPCData.mDetails) - .setStartTimestamp(startTimestamp) - .setEndTimestamp(current_time) - .setLargeImageKey(mRPCData.mLargeImageName) - .setSmallImageKey(mRPCData.mSmallImageName) - .setInstance(false) - .refresh(); -} + std::time_t current_time = std::time(nullptr); + Uint32 elapsed_ms = SDL_GetTicks() - mStartTime; + std::time_t startTimestamp = current_time - (elapsed_ms / 1000); -#endif \ No newline at end of file + rpc.getPresence() + .setState(mRPCData.mState) + .setDetails(mRPCData.mDetails) + .setStartTimestamp(startTimestamp) + .setEndTimestamp(current_time) + .setLargeImageKey(mRPCData.mLargeImageName) + .setSmallImageKey(mRPCData.mSmallImageName) + .setInstance(false) + .refresh(); +} \ No newline at end of file diff --git a/PopLib/api/discord.hpp b/PopLib/api/discord.hpp index 0b4bbdf3..3df2b1ed 100644 --- a/PopLib/api/discord.hpp +++ b/PopLib/api/discord.hpp @@ -1,44 +1,37 @@ #ifndef __DISCORD_HPP__ #define __DISCORD_HPP__ -#ifdef _WIN32 -#pragma once -#endif -#ifdef _FEATURE_DISCORD_RPC +#pragma once #include - namespace PopLib -{ - struct RPCData - { - std::string mState; - std::string mDetails; - std::string mSmallImageName; - std::string mLargeImageName; - }; - - class DiscordRPC - { - private: - - std::string mAppID; - RPCData mRPCData; - - bool InitRPC(); - public: - DiscordRPC(const char* theAppID = "1369297870456488057"); - ~DiscordRPC(); - void UpdateRPC(); - int64_t mStartTime; - bool mSendRPC; - bool mHasInitialized; - }; +{ +struct RPCData +{ + std::string mState; + std::string mDetails; + std::string mSmallImageName; + std::string mLargeImageName; +}; + +class DiscordRPC +{ + private: + std::string mAppID; + RPCData mRPCData; + + bool InitRPC(); + + public: + DiscordRPC(const char *theAppID = "1369297870456488057"); + ~DiscordRPC(); + void UpdateRPC(); + int64_t mStartTime; + bool mSendRPC; + bool mHasInitialized; +}; } // namespace PopLib - -#endif - #endif \ No newline at end of file diff --git a/PopLib/api/steam.cpp b/PopLib/api/steam.cpp new file mode 100644 index 00000000..c2b279c9 --- /dev/null +++ b/PopLib/api/steam.cpp @@ -0,0 +1,117 @@ +#include "steam.hpp" +#include "debug/log.hpp" +#include +#include + +using namespace PopLib; + +SteamAPI::SteamAPI() +{ +} + +SteamAPI::~SteamAPI() +{ + Shutdown(); +} + +bool SteamAPI::Init(const std::string &appId) +{ + if (!appId.empty()) + { + LOG_INFO("SteamAPI: Writing appid.txt with App ID %s", appId.c_str()); + + std::ofstream appidFile("steam_appid.txt", std::ios::out | std::ios::trunc); + if (appidFile.is_open()) + { + appidFile << appId; + appidFile.close(); + LOG_INFO("SteamAPI: steam_appid.txt written successfully"); + } + else + { + LOG_ERROR("SteamAPI: Failed to write steam_appid.txt"); + return false; + } + } + + mInitialized = SteamAPI_Init(); + if (!mInitialized) + { + LOG_INFO("SteamAPI: Initialization failed"); + return false; + } + + LOG_INFO("SteamAPI: Initialized successfully. User: %s", GetPersonaName().c_str()); + return true; +} + +void SteamAPI::Shutdown() +{ + if (mInitialized) + { + SteamAPI_Shutdown(); + LOG_INFO("SteamAPI: Shutdown complete"); + mInitialized = false; + } +} + +void SteamAPI::RunCallbacks() +{ + if (mInitialized) + SteamAPI_RunCallbacks(); +} + +bool SteamAPI::IsLoggedIn() const +{ + return mInitialized && SteamUser() && SteamUser()->BLoggedOn(); +} + +std::string SteamAPI::GetPersonaName() const +{ + if (SteamFriends()) + return SteamFriends()->GetPersonaName(); + return {}; +} + +CSteamID SteamAPI::GetSteamID() const +{ + if (SteamUser()) + return SteamUser()->GetSteamID(); + return {}; +} + +bool SteamAPI::SetAchievement(const char *name) +{ + if (!SteamUserStats()) + return false; + bool res = SteamUserStats()->SetAchievement(name); + if (res) + SteamUserStats()->StoreStats(); + return res; +} + +bool SteamAPI::ClearAchievement(const char *name) +{ + if (!SteamUserStats()) + return false; + bool res = SteamUserStats()->ClearAchievement(name); + if (res) + SteamUserStats()->StoreStats(); + return res; +} + +bool SteamAPI::StoreStats() +{ + return SteamUserStats() && SteamUserStats()->StoreStats(); +} + +bool SteamAPI::SetRichPresence(const char *key, const char *value) +{ + return SteamFriends() && SteamFriends()->SetRichPresence(key, value); +} + +void SteamAPI::ClearRichPresence() +{ + if (SteamFriends()) + SteamFriends()->ClearRichPresence(); +} diff --git a/PopLib/api/steam.hpp b/PopLib/api/steam.hpp new file mode 100644 index 00000000..3809a459 --- /dev/null +++ b/PopLib/api/steam.hpp @@ -0,0 +1,43 @@ +#ifndef __STEAM_HPP__ +#define __STEAM_HPP__ + +#pragma once + +#include +#include + +namespace PopLib +{ + +class SteamAPI +{ + public: + SteamAPI(); + ~SteamAPI(); + + bool Init(const std::string &appId = ""); + void Shutdown(); + void RunCallbacks(); + + bool IsInitialized() const + { + return mInitialized; + } + bool IsLoggedIn() const; + std::string GetPersonaName() const; + CSteamID GetSteamID() const; + + bool SetAchievement(const char *name); + bool ClearAchievement(const char *name); + bool StoreStats(); + + bool SetRichPresence(const char *key, const char *value); + void ClearRichPresence(); + + private: + bool mInitialized = false; +}; + +} // namespace PopLib + +#endif diff --git a/PopLib/appbase.cpp b/PopLib/appbase.cpp index 40cd167e..08a17814 100644 --- a/PopLib/appbase.cpp +++ b/PopLib/appbase.cpp @@ -2,9 +2,8 @@ #include "widget/widgetmanager.hpp" #include "widget/widget.hpp" #include "misc/keycodes.hpp" -#include "graphics/sdlinterface.hpp" -#include "graphics/sdlimage.hpp" -#include "graphics/memoryimage.hpp" +#include "graphics/renderer.hpp" +#include "graphics/gpuimage.hpp" #include "widget/dialog.hpp" #include "imagelib/imagelib.hpp" #include "audio/openalsoundmanager.hpp" @@ -14,22 +13,23 @@ #include "debug/perftimer.hpp" #include "math/mtrand.hpp" #include "readwrite/modval.hpp" +#include "debug/sehcatcher.hpp" +#include "scripting/scripting_base.hpp" #ifdef _WIN32 #include #else #include #include +#include #define _strtime strftime #endif #include #include "graphics/sysfont.hpp" #include "resources/resourcemanager.hpp" -#include "audio/bassmusicinterface.hpp" -#include "audio/bass.h" +#include "audio/openmptmusicinterface.hpp" #include "misc/autocrit.hpp" -#include "debug/debug.hpp" -#include "debug/errorhandler.hpp" +#include "debug/log.hpp" #include "paklib/pakinterface.hpp" #include "imgui/imguimanager.hpp" @@ -41,15 +41,19 @@ #include #include +// renderer apis +#include "graphics/renderer/sdlrenderer.hpp" +#include "graphics/renderer/glrenderer.hpp" + +#include "graphics/renderer/apitester.hpp" + #include "debug/memmgr.hpp" -// H521 #undef STB_IMAGE_IMPLEMENTATION #undef STB_IMAGE_WRITE_IMPLEMENTATION #include #include "stb_image_write.h" -// H522 #include using namespace PopLib; @@ -58,13 +62,19 @@ namespace fs = std::filesystem; AppBase *PopLib::gAppBase = nullptr; +SEHCatcher PopLib::gSEHCatcher; + +#ifdef _WIN32 +HMODULE gVersionDLL = NULL; +#endif + static bool gScreenSaverActive = false; #ifndef SPI_GETSCREENSAVERRUNNING #define SPI_GETSCREENSAVERRUNNING 114 #endif -static SDLImage *gFPSImage = nullptr; +static GPUImage *gFPSImage = nullptr; AppBase::AppBase() { @@ -84,11 +94,13 @@ AppBase::AppBase() mOnlyAllowOneCopyToRun = true; #endif +#ifdef _WIN32 // Extract product version - // char aPath[_MAX_PATH]; - // GetModuleFileNameA(nullptr, aPath, 256); - // mProductVersion = GetProductVersion(aPath); - // mChangeDirTo = GetFileDir(aPath); + char aPath[_MAX_PATH]; + GetModuleFileNameA(nullptr, aPath, 256); + mProductVersion = GetProductVersion(aPath); + mChangeDirTo = GetFileDir(aPath); +#endif mNoDefer = false; mFullScreenPageFlip = true; // should we page flip in fullscreen? @@ -108,9 +120,8 @@ AppBase::AppBase() mPreferredY = -1; mIsScreenSaver = false; mAllowMonitorPowersave = true; - mSDLInterface = nullptr; + mRenderer = nullptr; mMusicInterface = nullptr; - mErrorHandler = nullptr; mIGUIManager = nullptr; mFrameTime = 10; mNonDrawCount = 0; @@ -131,7 +142,7 @@ AppBase::AppBase() mFastForwardToMarker = false; mFastForwardStep = false; mSoundManager = nullptr; - mCursorNum = CURSOR_POINTER; + mCursorNum = CursorType::Pointer; mMouseIn = false; mRunning = false; mActive = true; @@ -145,7 +156,6 @@ AppBase::AppBase() mLoadingThreadStarted = false; mAutoStartLoadingThread = true; mLoadingThreadCompleted = false; - mCursorThreadRunning = false; mNumLoadingThreadTasks = 0; mCompletedLoadingThreadTasks = 0; mLastDrawTick = SDL_GetTicks(); @@ -190,6 +200,7 @@ AppBase::AppBase() mDebugKeysEnabled = false; mNoSoundNeeded = false; + mRendererAPI = Renderers::OpenGL; mSyncRefreshRate = 100; mVSyncUpdates = false; mVSyncBroken = false; @@ -209,9 +220,12 @@ AppBase::AppBase() mWindowAspect.Set(4, 3); mIsWideWindow = false; + m_pScriptingBase = nullptr; + int i; - for (i = 0; i < NUM_CURSORS; i++) + using underlying = std::underlying_type_t; + for (underlying i = 0; i < static_cast(CursorType::Last); i++) mCursorImages[i] = nullptr; for (i = 0; i < 256; i++) @@ -247,12 +261,14 @@ AppBase::AppBase() else*/ mTabletPC = false; + gSEHCatcher.mApp = this; + // std::wifstream stringsFile(_wfopen(L".\\properties\\fstrings", L"rb")); // // if(!stringsFile) //{ // MessageBox(nullptr, "file missing: 'install-folder\\properties\\fstrings' Please re-install", "FATAL ERROR", - //MB_OK); DoExit(1); + // MB_OK); DoExit(1); // } // std::getline(stringsFile, mString_HardwareAccelSwitchedOn); // std::getline(stringsFile, mString_HardwareAccelConfirm); @@ -272,88 +288,15 @@ AppBase::AppBase() // stringsFile.close(); } -namespace PopLib { - extern bool gSDLInterfacePreDrawError; +namespace PopLib +{ +extern bool gInterfacePreDrawError; } AppBase::~AppBase() { Shutdown(); - // Check if we should write the current 3d setting - bool showedMsgBox = false; - if (mUserChanged3DSetting) - { - bool writeToRegistry = true; - bool is3D = false; - bool is3DOptionSet = RegistryReadBoolean("Is3D", &is3D); - if (!is3DOptionSet) // should we write the option? - { - if (!Is3DAccelerationRecommended()) // may need to prompt user if he wants to keep 3d acceleration on - { - if (Is3DAccelerated()) - { - showedMsgBox = true; - SDL_MessageBoxButtonData buttons[] = {{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "Yes"}, - {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, "No"}}; - - std::string aTitle = mCompanyName + " " + GetString("HARDWARE_ACCEL_CONFIRMATION", "Hardware Acceleration Confirmation"); - - std::string aMessage = GetString("HARDWARE_ACCEL_SWITCHED_ON", - "Hardware Acceleration was switched on during this session.\n" - "If this resulted in slower performance, it should be switched off.\n" - "Would you like to keep Hardware Acceleration switched on?"); - - SDL_MessageBoxData aMessageboxData = {}; - aMessageboxData.flags = SDL_MESSAGEBOX_INFORMATION; - aMessageboxData.title = aTitle.c_str(); - aMessageboxData.message = aMessage.c_str(); - aMessageboxData.numbuttons = SDL_arraysize(buttons); - aMessageboxData.buttons = buttons; - aMessageboxData.window = mSDLInterface->mWindow; - - int aResult; - if (!SDL_ShowMessageBox(&aMessageboxData, &aResult)) - { - SDL_Log("Error displaying message box: %s", SDL_GetError()); - } - mSDLInterface->mIs3D = aResult == 1 ? true : false; - - if (aResult != 1) - writeToRegistry = false; - } - else - writeToRegistry = false; - } - } - - if (writeToRegistry) - RegistryWriteBoolean("Is3D", mSDLInterface->mIs3D); - } - - if (!showedMsgBox && PopLib::gSDLInterfacePreDrawError && !IsScreenSaver()) - { - const SDL_MessageBoxButtonData buttons[] = { - {0, 0, "No"}, - {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "Yes"}, - }; - - std::string aMessage = GetString("HARDWARE_ACCEL_NOT_WORKING", - "Hardware Acceleration may not have been working correctly during this session.\r\n" - "If you noticed graphics problems, you may want to turn off Hardware Acceleration.\r\n" - "Would you like to keep Hardware Acceleration switched on?"); - - std::string aTitle = mCompanyName + " " + - GetString("HARDWARE_ACCEL_CONFIRMATION", "Hardware Acceleration Confirmation"); - - SDL_MessageBoxData messageBoxData = { - SDL_MESSAGEBOX_INFORMATION, nullptr, aTitle.c_str(), aMessage.c_str(), SDL_arraysize(buttons), buttons, nullptr}; - - int aResult = mSDLInterface->MakeResultMessageBox(messageBoxData); - if (aResult == 0) - RegistryWriteBoolean("Is3D", false); - } - DialogMap::iterator aDialogItr = mDialogMap.begin(); while (aDialogItr != mDialogMap.end()) { @@ -373,25 +316,23 @@ AppBase::~AppBase() while (aSharedImageItr != mSharedImageMap.end()) { SharedImage *aSharedImage = &aSharedImageItr->second; - DBG_ASSERTE(aSharedImage->mRefCount == 0); + ASSERT(aSharedImage->mRefCount == 0); delete aSharedImage->mImage; mSharedImageMap.erase(aSharedImageItr++); } - delete mSDLInterface; + delete mRenderer; delete mMusicInterface; delete mSoundManager; - delete mErrorHandler; delete mIGUIManager; - BASS_Stop(); - WaitForLoadingThread(); - SDL_DestroyCursor(mHandCursor); - SDL_DestroyCursor(mDraggingCursor); - gAppBase = nullptr; + +#ifdef _WIN32 + FreeLibrary(gVersionDLL); +#endif } void AppBase::ClearUpdateBacklog(bool relaxForASecond) @@ -416,8 +357,8 @@ bool AppBase::AppCanRestore() Dialog *AppBase::NewDialog(int theDialogId, bool isModal, const PopString &theDialogHeader, const PopString &theDialogLines, const PopString &theDialogFooter, int theButtonMode) { - Dialog *aDialog = - new Dialog(nullptr, nullptr, theDialogId, isModal, theDialogHeader, theDialogLines, theDialogFooter, theButtonMode); + Dialog *aDialog = new Dialog(nullptr, nullptr, theDialogId, isModal, theDialogHeader, theDialogLines, + theDialogFooter, theButtonMode); return aDialog; } @@ -592,51 +533,56 @@ bool AppBase::OpenURL(const std::string &theURL, bool shutdownOnOpen) std::string AppBase::GetProductVersion(const std::string &thePath) { - /* +#ifdef _WIN32 + if (gVersionDLL == NULL) + gVersionDLL = LoadLibraryA("version.dll"); + // Dynamically Load Version.dll - typedef uint32_t (APIENTRY *GetFileVersionInfoSizeFunc)(LPSTR lptstrFilename, LPuint32_t lpdwHandle); - typedef BOOL (APIENTRY *GetFileVersionInfoFunc)(LPSTR lptstrFilename, uint32_t dwHandle, uint32_t dwLen, LPVOID lpData); - typedef BOOL (APIENTRY *VerQueryValueFunc)(const LPVOID pBlock, LPSTR lpSubBlock, LPVOID * lplpBuffer, PUINT puLen); + typedef uint32_t(APIENTRY * GetFileVersionInfoSizeFunc)(LPSTR lptstrFilename, LPDWORD lpdwHandle); + typedef BOOL(APIENTRY * GetFileVersionInfoFunc)(LPSTR lptstrFilename, uint32_t dwHandle, uint32_t dwLen, + LPVOID lpData); + typedef BOOL(APIENTRY * VerQueryValueFunc)(const LPVOID pBlock, LPSTR lpSubBlock, LPVOID *lplpBuffer, PUINT puLen); static GetFileVersionInfoSizeFunc aGetFileVersionInfoSizeFunc = nullptr; static GetFileVersionInfoFunc aGetFileVersionInfoFunc = nullptr; static VerQueryValueFunc aVerQueryValueFunc = nullptr; - if (aGetFileVersionInfoSizeFunc==nullptr) + if (aGetFileVersionInfoSizeFunc == nullptr) { - aGetFileVersionInfoSizeFunc = (GetFileVersionInfoSizeFunc)GetProcAddress(gVersionDLL,"GetFileVersionInfoSizeA"); - aGetFileVersionInfoFunc = (GetFileVersionInfoFunc)GetProcAddress(gVersionDLL,"GetFileVersionInfoA"); - aVerQueryValueFunc = (VerQueryValueFunc)GetProcAddress(gVersionDLL,"VerQueryValueA"); + aGetFileVersionInfoSizeFunc = + (GetFileVersionInfoSizeFunc)GetProcAddress(gVersionDLL, "GetFileVersionInfoSizeA"); + aGetFileVersionInfoFunc = (GetFileVersionInfoFunc)GetProcAddress(gVersionDLL, "GetFileVersionInfoA"); + aVerQueryValueFunc = (VerQueryValueFunc)GetProcAddress(gVersionDLL, "VerQueryValueA"); } // Get Product Version std::string aProductVersion; - uint aSize = aGetFileVersionInfoSizeFunc((char*) thePath.c_str(), 0); + uint aSize = aGetFileVersionInfoSizeFunc((char *)thePath.c_str(), 0); if (aSize > 0) { - uchar* aVersionBuffer = new uchar[aSize]; - aGetFileVersionInfoFunc((char*) thePath.c_str(), 0, aSize, aVersionBuffer); - char* aBuffer; - if (aVerQueryValueFunc(aVersionBuffer, - "\\StringFileInfo\\040904B0\\ProductVersion", - (void**) &aBuffer, - &aSize)) + uchar *aVersionBuffer = new uchar[aSize]; + aGetFileVersionInfoFunc((char *)thePath.c_str(), 0, aSize, aVersionBuffer); + char *aBuffer; + if (aVerQueryValueFunc(aVersionBuffer, (char *)"\\StringFileInfo\\040904B0\\ProductVersion", (void **)&aBuffer, + &aSize)) { aProductVersion = aBuffer; } - else if (aVerQueryValueFunc(aVersionBuffer, - "\\StringFileInfo\\040904E4\\ProductVersion", - (void**) &aBuffer, - &aSize)) + else if (aVerQueryValueFunc(aVersionBuffer, (char *)"\\StringFileInfo\\040904E4\\ProductVersion", + (void **)&aBuffer, &aSize)) { aProductVersion = aBuffer; } delete aVersionBuffer; } - */ - return "0"; + + return aProductVersion; +#else + // @ThePixelMoon: a good substitute + return POPLIB_VERSION; +#endif } void AppBase::WaitForLoadingThread() @@ -645,13 +591,16 @@ void AppBase::WaitForLoadingThread() SDL_Delay(20); } -void AppBase::SetCursorImage(int theCursorNum, Image *theImage) +void AppBase::SetCursorImage(CursorType theCursorNum, Image *theImage) { - if ((theCursorNum >= 0) && (theCursorNum < NUM_CURSORS)) - { - mCursorImages[theCursorNum] = theImage; - EnforceCursor(); - } + using underlying = std::underlying_type_t; + + if (static_cast(theCursorNum) >= 0 && + static_cast(theCursorNum) < static_cast(CursorType::Last)) + { + mCursorImages[static_cast(theCursorNum)] = theImage; + EnforceCursor(); + } } int WriteScreenShotThread(void *theArg) @@ -700,24 +649,11 @@ void AppBase::TakeScreenshot() filenameStream << std::put_time(&tm, "%Y%m%d_%H%M%S") << ".png"; std::filesystem::path filePath = screenshotDir / filenameStream.str(); - SDL_Surface *surface = SDL_RenderReadPixels(mSDLInterface->mRenderer, nullptr); - if (!surface) + std::unique_ptr image = mRenderer->CaptureFrameBuffer(); + if (!image) return; - uint8_t *bgra = static_cast(surface->pixels); - int size = surface->w * surface->h * 4; - std::vector rgba(size); - - for (int i = 0; i < size; i += 4) - { - rgba[i + 0] = bgra[i + 2]; // R - rgba[i + 1] = bgra[i + 1]; // G - rgba[i + 2] = bgra[i + 0]; // B - rgba[i + 3] = bgra[i + 3]; // A - } - - stbi_write_png(filePath.string().c_str(), surface->w, surface->h, 4, rgba.data(), surface->w * 4); - SDL_DestroySurface(surface); + stbi_write_png(filePath.string().c_str(), image->width, image->height, 4, image->pixels.data(), image->width * 4); } void AppBase::DumpProgramInfo() @@ -750,19 +686,17 @@ void AppBase::DumpProgramInfo() anImageLibImage.mHeight = aThumbHeight; anImageLibImage.mBits = new ulong[aThumbWidth * aThumbHeight]; - typedef std::multimap> SortedImageMap; + typedef std::multimap> SortedImageMap; int aTotalMemory = 0; SortedImageMap aSortedImageMap; - MemoryImageSet::iterator anItr = mMemoryImageSet.begin(); - while (anItr != mMemoryImageSet.end()) + GPUImageSet::iterator anItr = mGPUImageSet.begin(); + while (anItr != mGPUImageSet.end()) { - MemoryImage *aMemoryImage = *anItr; + GPUImage *aGPUImage = *anItr; - int aNumPixels = aMemoryImage->mWidth * aMemoryImage->mHeight; - - SDLImage *aSDLImage = dynamic_cast(aMemoryImage); + int aNumPixels = aGPUImage->mWidth * aGPUImage->mHeight; int aBitsMemory = 0; int aSurfaceMemory = 0; @@ -773,37 +707,36 @@ void AppBase::DumpProgramInfo() int aTextureMemory = 0; int aMemorySize = 0; - if (aMemoryImage->mBits != nullptr) + if (aGPUImage->mBits != nullptr) aBitsMemory = aNumPixels * 4; - if ((aSDLImage != nullptr) && (aSDLImage->mD3DData != nullptr)) + if ((aGPUImage != nullptr) && (aGPUImage->mGPUData != nullptr)) aSurfaceMemory = aNumPixels * 4; // Assume 32bit screen... - if (aMemoryImage->mColorTable != nullptr) + if (aGPUImage->mColorTable != nullptr) aPalletizedMemory = aNumPixels + 256 * 4; - if (aMemoryImage->mNativeAlphaData != nullptr) + if (aGPUImage->mNativeAlphaData != nullptr) { - if (aMemoryImage->mColorTable != nullptr) + if (aGPUImage->mColorTable != nullptr) aNativeAlphaMemory = 256 * 4; else aNativeAlphaMemory = aNumPixels * 4; } - if (aMemoryImage->mRLAlphaData != nullptr) + if (aGPUImage->mRLAlphaData != nullptr) aRLAlphaMemory = aNumPixels; - if (aMemoryImage->mRLAdditiveData != nullptr) + if (aGPUImage->mRLAdditiveData != nullptr) aRLAdditiveMemory = aNumPixels; - if (aMemoryImage->mD3DData != nullptr) - aTextureMemory += ((SDLTextureData *)aMemoryImage->mD3DData)->GetMemSize(); + // if (aGPUImage->mGPUData != nullptr) + // aTextureMemory += ((SDLTextureData *)aGPUImage->mGPUData)->GetMemSize(); aMemorySize = aBitsMemory + aSurfaceMemory + aPalletizedMemory + aNativeAlphaMemory + aRLAlphaMemory + aRLAdditiveMemory + aTextureMemory; aTotalMemory += aMemorySize; - aSortedImageMap.insert(SortedImageMap::value_type(aMemorySize, aMemoryImage)); + aSortedImageMap.insert(SortedImageMap::value_type(aMemorySize, aGPUImage)); ++anItr; } - aDumpStream << "Total Image Allocation: " << CommaSeperate(aTotalMemory).c_str() - << " bytes
"; + aDumpStream << "Total Image Allocation: " << CommaSeperate(aTotalMemory).c_str() << " bytes
"; aDumpStream << ""; int aTotalMemorySize = 0; @@ -818,7 +751,7 @@ void AppBase::DumpProgramInfo() SortedImageMap::iterator aSortedItr = aSortedImageMap.begin(); while (aSortedItr != aSortedImageMap.end()) { - MemoryImage *aMemoryImage = aSortedItr->second; + GPUImage *aGPUImage = aSortedItr->second; char anImageName[256]; sprintf(anImageName, "img%04d", anImgNum); @@ -831,9 +764,7 @@ void AppBase::DumpProgramInfo() aDumpStream << "" << std::endl; - int aNumPixels = aMemoryImage->mWidth * aMemoryImage->mHeight; - - SDLImage *aSDLImage = dynamic_cast(aMemoryImage); + int aNumPixels = aGPUImage->mWidth * aGPUImage->mHeight; int aMemorySize = aSortedItr->first; @@ -846,26 +777,26 @@ void AppBase::DumpProgramInfo() int aTextureMemory = 0; std::string aTextureFormatName; - if (aMemoryImage->mBits != nullptr) + if (aGPUImage->mBits != nullptr) aBitsMemory = aNumPixels * 4; - if ((aSDLImage != nullptr) && (aSDLImage->mD3DData != nullptr)) + if ((aGPUImage != nullptr) && (aGPUImage->mGPUData != nullptr)) aSurfaceMemory = aNumPixels * 4; // Assume 32bit screen... - if (aMemoryImage->mColorTable != nullptr) + if (aGPUImage->mColorTable != nullptr) aPalletizedMemory = aNumPixels + 256 * 4; - if (aMemoryImage->mNativeAlphaData != nullptr) + if (aGPUImage->mNativeAlphaData != nullptr) { - if (aMemoryImage->mColorTable != nullptr) + if (aGPUImage->mColorTable != nullptr) aNativeAlphaMemory = 256 * 4; else aNativeAlphaMemory = aNumPixels * 4; } - if (aMemoryImage->mRLAlphaData != nullptr) + if (aGPUImage->mRLAlphaData != nullptr) aRLAlphaMemory = aNumPixels; - if (aMemoryImage->mRLAdditiveData != nullptr) + if (aGPUImage->mRLAdditiveData != nullptr) aRLAdditiveMemory = aNumPixels; - if (aMemoryImage->mD3DData != nullptr) + if (aGPUImage->mGPUData != nullptr) { - aTextureMemory += ((SDLTextureData *)aMemoryImage->mD3DData)->GetMemSize(); + // aTextureMemory += ((SDLTextureData *)aGPUImage->mGPUData)->GetMemSize(); aTextureFormatName = "ARGB8888"; // They are always like this } @@ -880,76 +811,59 @@ void AppBase::DumpProgramInfo() aTotalRLAdditiveMemory += aRLAdditiveMemory; char aStr[256]; - sprintf(aStr, "%d x %d
%s bytes", aMemoryImage->mWidth, aMemoryImage->mHeight, - CommaSeperate(aMemorySize).c_str()); + sprintf(aStr, "%d x %d
%s bytes", aGPUImage->mWidth, aGPUImage->mHeight, CommaSeperate(aMemorySize).c_str()); aDumpStream << "
" << std::endl; + aDumpStream << "" + << std::endl; aDumpStream << "" << std::endl; - aDumpStream << "" << std::endl; - aDumpStream << "" << std::endl; aDumpStream << "" << std::endl; - aDumpStream << "" << std::endl; + aDumpStream << "" << std::endl; - aDumpStream << "" << std::endl; + aDumpStream << "" << std::endl; - aDumpStream << "" << std::endl; + aDumpStream << "" << std::endl; + aDumpStream << "" << std::endl; aDumpStream << "" << std::endl; - aDumpStream << "" << std::endl; aDumpStream << "" << std::endl; - aDumpStream << "" << std::endl; - aDumpStream << "" + aDumpStream << "" << std::endl; aDumpStream << "" << std::endl; // Write thumb - MemoryImage aCopiedImage(*aMemoryImage); + // Use the existing GPUImage pointer directly instead of copying + GPUImage *aCopiedImage = aGPUImage; - ulong *aBits = aCopiedImage.GetBits(); + ulong *aBits = aCopiedImage->GetBits(); uint32_t *aThumbBitsPtr = (uint32_t *)anImageLibImage.mBits; for (int aThumbY = 0; aThumbY < aThumbHeight; aThumbY++) for (int aThumbX = 0; aThumbX < aThumbWidth; aThumbX++) { - int aSrcX = (int)(aCopiedImage.mWidth * (aThumbX + 0.5)) / aThumbWidth; - int aSrcY = (int)(aCopiedImage.mHeight * (aThumbY + 0.5)) / aThumbHeight; + int aSrcX = (int)(aCopiedImage->mWidth * (aThumbX + 0.5)) / aThumbWidth; + int aSrcY = (int)(aCopiedImage->mHeight * (aThumbY + 0.5)) / aThumbHeight; - *(aThumbBitsPtr++) = aBits[aSrcX + (aSrcY * aCopiedImage.mWidth)]; + *(aThumbBitsPtr++) = aBits[aSrcX + (aSrcY * aCopiedImage->mWidth)]; } ImageLib::WriteImage((GetAppDataFolder() + std::string("_dump/") + aThumbName).c_str(), ".jpeg", @@ -958,9 +872,9 @@ void AppBase::DumpProgramInfo() // Write high resolution image ImageLib::Image anFullImage; - anFullImage.mBits = aCopiedImage.GetBits(); - anFullImage.mWidth = aCopiedImage.GetWidth(); - anFullImage.mHeight = aCopiedImage.GetHeight(); + anFullImage.mBits = aCopiedImage->GetBits(); + anFullImage.mWidth = aCopiedImage->GetWidth(); + anFullImage.mHeight = aCopiedImage->GetHeight(); ImageLib::WriteImage((GetAppDataFolder() + std::string("_dump/") + anImageName).c_str(), ".png", &anFullImage); @@ -1000,12 +914,12 @@ double AppBase::GetLoadingThreadProgress() return std::min(mCompletedLoadingThreadTasks / (double)mNumLoadingThreadTasks, 1.0); } -bool AppBase::RegistryWrite(const std::string &theValueName, JSON_RTYPE theType, const uchar *theValue, ulong theLength) +bool AppBase::RegistryWrite(const std::string &theValueName, JSONRegistryType theType, const uchar *theValue, ulong theLength) { - // H522 + // the Windows(TM) registry isn't available in linux, we use json!!! std::filesystem::path config = GetAppDataFolder() + mRegKey + "/registry.json"; // always registry.json std::filesystem::create_directories(config.parent_path()); - //SDL_Log("%s", config.string().c_str()); + // LOG_INFO("%s", config.string().c_str()); nlohmann::json j; std::ifstream inFile(config); @@ -1022,18 +936,19 @@ bool AppBase::RegistryWrite(const std::string &theValueName, JSON_RTYPE theType, switch (theType) { - case JSON_STRING: + case JSONRegistryType::String: j[theValueName] = std::string(reinterpret_cast(theValue), theLength); break; - case JSON_INTEGER: + case JSONRegistryType::Integer: if (theLength == sizeof(int)) j[theValueName] = *reinterpret_cast(theValue); break; - case JSON_BOOLEAN: + case JSONRegistryType::Bool: if (theLength == sizeof(int)) j[theValueName] = (*reinterpret_cast(theValue)) != 0; break; - case JSON_DATA: { + case JSONRegistryType::Data: + { std::vector bin(theValue, theValue + theLength); j[theValueName] = bin; break; @@ -1051,23 +966,23 @@ bool AppBase::RegistryWrite(const std::string &theValueName, JSON_RTYPE theType, bool AppBase::RegistryWriteString(const std::string &theValueName, const std::string &theString) { - return RegistryWrite(theValueName, JSON_STRING, (uchar *)theString.c_str(), theString.length()); + return RegistryWrite(theValueName, JSONRegistryType::String, (uchar *)theString.c_str(), theString.length()); } bool AppBase::RegistryWriteInteger(const std::string &theValueName, int theValue) { - return RegistryWrite(theValueName, JSON_INTEGER, (uchar *)&theValue, sizeof(int)); + return RegistryWrite(theValueName, JSONRegistryType::Integer, (uchar *)&theValue, sizeof(int)); } bool AppBase::RegistryWriteBoolean(const std::string &theValueName, bool theValue) { int aValue = theValue ? 1 : 0; - return RegistryWrite(theValueName, JSON_BOOLEAN, (uchar *)&aValue, sizeof(int)); + return RegistryWrite(theValueName, JSONRegistryType::Bool, (uchar *)&aValue, sizeof(int)); } bool AppBase::RegistryWriteData(const std::string &theValueName, const uchar *theValue, ulong theLength) { - return RegistryWrite(theValueName, JSON_DATA, (uchar *)theValue, theLength); + return RegistryWrite(theValueName, JSONRegistryType::Data, (uchar *)theValue, theLength); } void AppBase::WriteToRegistry() @@ -1100,7 +1015,7 @@ bool AppBase::RegistryEraseKey(const PopString &_theKeyName) void AppBase::RegistryEraseValue(const PopString &_theValueName) { - // H522 + // the Windows(TM) registry isn't available in linux, we use json!!! std::filesystem::path configPath = GetAppDataFolder() + mRegKey + "/registry.json"; std::string keyName = _theValueName; @@ -1138,15 +1053,15 @@ bool AppBase::RegistryGetSubKeys(const std::string &theKeyName, StringVector *th return false; } -bool AppBase::RegistryRead(const std::string &theValueName, JSON_RTYPE *theType, uchar *theValue, ulong *theLength) +bool AppBase::RegistryRead(const std::string &theValueName, JSONRegistryType *theType, uchar *theValue, ulong *theLength) { return RegistryReadKey(theValueName, theType, theValue, theLength, 0); } -bool AppBase::RegistryReadKey(const std::string &theValueName, JSON_RTYPE *theType, uchar *theValue, ulong *theLength, +bool AppBase::RegistryReadKey(const std::string &theValueName, JSONRegistryType *theType, uchar *theValue, ulong *theLength, ulong theKey) { - // H522 + // the Windows(TM) registry isn't available in linux, we use json!!! std::filesystem::path configPath = GetAppDataFolder() + mRegKey + "/registry.json"; if (!std::filesystem::exists(configPath) || !theType || !theValue || !theLength) return false; @@ -1168,7 +1083,7 @@ bool AppBase::RegistryReadKey(const std::string &theValueName, JSON_RTYPE *theTy return false; auto &entry = j[theValueName]; - // JSON_STRING + // JSONRegistryType::String if (entry.is_string()) { std::string s = entry.get(); @@ -1176,10 +1091,10 @@ bool AppBase::RegistryReadKey(const std::string &theValueName, JSON_RTYPE *theTy return false; std::memcpy(theValue, s.data(), s.size()); *theLength = static_cast(s.size()); - *theType = JSON_STRING; + *theType = JSONRegistryType::String; return true; } - // JSON_INTEGER + // JSONRegistryType::Integer else if (entry.is_number_integer()) { if (*theLength < sizeof(int)) @@ -1187,10 +1102,10 @@ bool AppBase::RegistryReadKey(const std::string &theValueName, JSON_RTYPE *theTy int v = entry.get(); std::memcpy(theValue, &v, sizeof(int)); *theLength = sizeof(int); - *theType = JSON_INTEGER; + *theType = JSONRegistryType::Integer; return true; } - // JSON_BOOLEAN + // JSONRegistryType::Bool else if (entry.is_boolean()) { if (*theLength < sizeof(int)) @@ -1198,10 +1113,10 @@ bool AppBase::RegistryReadKey(const std::string &theValueName, JSON_RTYPE *theTy int b = entry.get() ? 1 : 0; std::memcpy(theValue, &b, sizeof(int)); *theLength = sizeof(int); - *theType = JSON_BOOLEAN; + *theType = JSONRegistryType::Bool; return true; } - // JSON_DATA + // JSONRegistryType::Data else if (entry.is_array()) { size_t size = entry.size(); @@ -1210,7 +1125,7 @@ bool AppBase::RegistryReadKey(const std::string &theValueName, JSON_RTYPE *theTy for (size_t i = 0; i < size; ++i) theValue[i] = static_cast(entry[i].get()); *theLength = static_cast(size); - *theType = JSON_DATA; + *theType = JSONRegistryType::Data; return true; } @@ -1219,7 +1134,7 @@ bool AppBase::RegistryReadKey(const std::string &theValueName, JSON_RTYPE *theTy bool AppBase::RegistryReadString(const std::string &theKey, std::string *theString) { - // H522 + // the Windows(TM) registry isn't available in linux, we use json!!! std::filesystem::path configPath = GetAppDataFolder() + mRegKey + "/registry.json"; if (!std::filesystem::exists(configPath) || !theString) return false; @@ -1247,7 +1162,7 @@ bool AppBase::RegistryReadString(const std::string &theKey, std::string *theStri bool AppBase::RegistryReadInteger(const std::string &theKey, int *theValue) { - // H522 + // the Windows(TM) registry isn't available in linux, we use json!!! if (!theValue) return false; @@ -1277,7 +1192,7 @@ bool AppBase::RegistryReadInteger(const std::string &theKey, int *theValue) bool AppBase::RegistryReadBoolean(const std::string &theKey, bool *theValue) { - // H522 + // the Windows(TM) registry isn't available in linux, we use json!!! if (!theValue) return false; @@ -1305,7 +1220,7 @@ bool AppBase::RegistryReadBoolean(const std::string &theKey, bool *theValue) bool AppBase::RegistryReadData(const std::string &theKey, uchar *theValue, ulong *theLength) { - // H522 + // the Windows(TM) registry isn't available in linux, we use json!!! if (!theValue || !theLength) return false; @@ -1448,7 +1363,7 @@ std::string AppBase::GetGameSEHInfo() sprintf(aTimeStr, "%02d:%02d:%02d", (aSecLoaded / 60 / 60), (aSecLoaded / 60) % 60, aSecLoaded % 60); char aThreadIdStr[16]; - sprintf(aThreadIdStr, "%X", mPrimaryThreadId); + sprintf(aThreadIdStr, "%lX", mPrimaryThreadId); std::string anInfoString = "Product: " + mProdName + "\r\n" + "Version: " + mProductVersion + "\r\n"; @@ -1473,6 +1388,9 @@ void AppBase::ShutdownHook() void AppBase::Shutdown() { + if (m_pScriptingBase) + m_pScriptingBase->Shutdown(); + if ((mPrimaryThreadId != 0) && (SDL_GetCurrentThreadID() != mPrimaryThreadId)) { mLoadingFailed = true; @@ -1483,11 +1401,8 @@ void AppBase::Shutdown() mShutdown = true; ShutdownHook(); - // Blah - while (mCursorThreadRunning) - { - SDL_Delay(10); - } + if (mWindow) + SDL_DestroyWindow(mWindow); if (mMusicInterface != nullptr) mMusicInterface->StopAllMusic(); @@ -1569,7 +1484,7 @@ void AppBase::Redraw(Rect *theClipRect) if (gScreenSaverActive) return; - mSDLInterface->Redraw(theClipRect); + mRenderer->Redraw(theClipRect); mFPSFlipCount++; } @@ -1589,7 +1504,7 @@ static void CalculateFPS() static SysFont aFont(gAppBase, LiberationSans_Regular, LiberationSans_Regular_Size, 8); if (gFPSImage == nullptr) { - gFPSImage = new SDLImage(gAppBase->mSDLInterface); + gFPSImage = gAppBase->mRenderer->NewGPUImage(); gFPSImage->Create(50, aFont.GetHeight() + 4); gFPSImage->SetImageMode(false, false); gFPSImage->SetVolatile(true); @@ -1631,7 +1546,7 @@ static void FPSDrawCoords(int theX, int theY) static SysFont aFont(gAppBase, LiberationSans_Regular, LiberationSans_Regular_Size, 8); if (gFPSImage == nullptr) { - gFPSImage = new SDLImage(gAppBase->mSDLInterface); + gFPSImage = gAppBase->mRenderer->NewGPUImage(); gFPSImage->Create(50, aFont.GetHeight() + 4); gFPSImage->SetImageMode(false, false); gFPSImage->SetVolatile(true); @@ -1720,15 +1635,15 @@ bool AppBase::DrawDirtyStuff() if (mShowFPS) { - Graphics g(mSDLInterface->GetScreenImage()); + Graphics g(mRenderer->GetScreenImage()); g.DrawImage(gFPSImage, mWidth - gFPSImage->GetWidth() - 10, mHeight - gFPSImage->GetHeight() - 10); } if (mWaitForVSync && mIsPhysWindowed && mSoftVSyncWait) { uint32_t aTick = SDL_GetTicks(); - if (aTick - mLastDrawTick < mSDLInterface->mMillisecondsPerFrame) - SDL_Delay(mSDLInterface->mMillisecondsPerFrame - (aTick - mLastDrawTick)); + if (aTick - mLastDrawTick < mRenderer->mMillisecondsPerFrame) + SDL_Delay(mRenderer->mMillisecondsPerFrame - (aTick - mLastDrawTick)); } uint32_t aPreScreenBltTime = SDL_GetTicks(); @@ -1804,11 +1719,11 @@ void AppBase::LogScreenSaverError(const std::string &theError) #ifdef _WIN32 fprintf(aFile, "%s %s %u\n", theError.c_str(), _strtime(aBuf), SDL_GetTicks()); #else - char aBuf[9]; // HH:MM:SS + char aBuf[9]; // HH:MM:SS time_t now = time(nullptr); strftime(aBuf, sizeof(aBuf), "%H:%M:%S", localtime(&now)); - fprintf(aFile, "%s %s %u\n", theError.c_str(), aBuf, SDL_GetTicks()); + fprintf(aFile, "%s %s %lu\n", theError.c_str(), aBuf, SDL_GetTicks()); #endif fclose(aFile); } @@ -1816,14 +1731,6 @@ void AppBase::LogScreenSaverError(const std::string &theError) void AppBase::BeginPopup() { - // if (!mIsPhysWindowed) - //{ - // if (mSDLInterface && mDDInterface->mDD) - //{ - // mDDInterface->mDD->FlipToGDISurface(); - // mNoDefer = true; - //} - //} } void AppBase::EndPopup() @@ -1837,7 +1744,7 @@ void AppBase::EndPopup() if (mWidgetManager->mDownButtons) { mWidgetManager->DoMouseUps(); -// ReleaseCapture(); + // ReleaseCapture(); } } @@ -1849,68 +1756,14 @@ int AppBase::MsgBox(const std::string &theText, const std::string &theTitle, int return 0; } - SDL_MessageBoxData messageBoxData = { - SDL_MESSAGEBOX_INFORMATION, NULL, theTitle.c_str(), theText.c_str(), NULL, NULL, NULL}; - - if (theFlags & MsgBoxFlags::MsgBox_OK) - { - SDL_MessageBoxButtonData buttons[] = { - {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "OK"}, - }; - - messageBoxData.numbuttons = SDL_arraysize(buttons); - messageBoxData.buttons = buttons; - } - else if (theFlags & MsgBoxFlags::MsgBox_OKCANCEL) - { - SDL_MessageBoxButtonData buttons[] = { - {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "OK"}, - {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 1, "Cancel"}, - }; - messageBoxData.numbuttons = SDL_arraysize(buttons); - messageBoxData.buttons = buttons; - } - else if (theFlags & MsgBoxFlags::MsgBox_ABORTRETRYIGNORE) - { - SDL_MessageBoxButtonData buttons[] = { - {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Abort"}, - {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 1, "Retry"}, - {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 2, "Ignore"}, - }; - messageBoxData.numbuttons = SDL_arraysize(buttons); - messageBoxData.buttons = buttons; - } - else if (theFlags & MsgBoxFlags::MsgBox_YESNOCANCEL) - { - SDL_MessageBoxButtonData buttons[] = { - {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Yes"}, - {0, 1, "No"}, - {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 2, "Cancel"}, - }; - messageBoxData.numbuttons = SDL_arraysize(buttons); - messageBoxData.buttons = buttons; - } - else if (theFlags & MsgBoxFlags::MsgBox_YESNO) - { - SDL_MessageBoxButtonData buttons[] = { - {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Yes"}, - {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 1, "No"}, - }; - messageBoxData.numbuttons = SDL_arraysize(buttons); - messageBoxData.buttons = buttons; - } - else if (theFlags & MsgBoxFlags::MsgBox_RETRYCANCEL) - { - SDL_MessageBoxButtonData buttons[] = { - {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Retry"}, - {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 1, "Cancel"}, - }; - messageBoxData.numbuttons = SDL_arraysize(buttons); - messageBoxData.buttons = buttons; - } + MsgBoxData msgBoxData; + msgBoxData.mFlags = static_cast(theFlags); + msgBoxData.mTitle = theTitle.c_str(); + msgBoxData.mMessage = theText.c_str(); BeginPopup(); - int aResult = mSDLInterface->MakeResultMessageBox(messageBoxData); + int aResult; + SDL_ShowMessageBox(reinterpret_cast(&msgBoxData), &aResult); EndPopup(); return aResult; @@ -1926,8 +1779,9 @@ void AppBase::Popup(const std::string &theString) BeginPopup(); if (!mShutdown) - mSDLInterface->MakeSimpleMessageBox(GetString("FATAL_ERROR", "FATAL ERROR").c_str(), - theString.c_str(), SDL_MESSAGEBOX_ERROR); + SDL_ShowSimpleMessageBox(static_cast(MsgBox_OK), + GetString("FATAL_ERROR", "FATAL ERROR").c_str(), theString.c_str(), mWindow); + EndPopup(); } @@ -1967,7 +1821,7 @@ void AppBase::RehupFocus() mWidgetManager->LostFocus(); LostFocus(); -// ReleaseCapture(); + // ReleaseCapture(); mWidgetManager->DoMouseUps(); } } @@ -1998,17 +1852,17 @@ void AppBase::ShowMemoryUsage() mDDInterface->mDD7->GetAvailableVidMem(&aCaps,&aTotal,&aFree); } - MemoryImageSet::iterator anItr = mMemoryImageSet.begin(); + GPUImageSet::iterator anItr = mGPUImageSet.begin(); typedef std::pair FormatUsage; typedef std::map FormatMap; FormatMap aFormatMap; int aTextureMemory = 0; - while (anItr != mMemoryImageSet.end()) + while (anItr != mGPUImageSet.end()) { - MemoryImage* aMemoryImage = *anItr; - if (aMemoryImage->mD3DData != nullptr) + GPUImage* aGPUImage = *anItr; + if (aGPUImage->mGPUData != nullptr) { - TextureData *aData = (TextureData*)aMemoryImage->mD3DData; + TextureData *aData = (TextureData*)aGPUImage->mGPUData; aTextureMemory += aData->mTexMemSize; FormatUsage &aUsage = aFormatMap[aData->mPixelFormat]; @@ -2031,7 +1885,7 @@ void AppBase::ShowMemoryUsage() aStr += StrFormat("3D-Mode is %s (3D is %s on this system)\r\n\r\n",Is3DAccelerated()?"On":"Off",aDesc); - aStr += StrFormat("Num Images: %d\r\n",(int)mMemoryImageSet.size()); + aStr += StrFormat("Num Images: %d\r\n",(int)mGPUImageSet.size()); aStr += StrFormat("Num Sounds: %d\r\n",mSoundManager->GetNumSounds()); aStr += StrFormat("Video Memory: %s/%s KB\r\n", PopStringToString(CommaSeperate((aTotal-aFree)/1024)).c_str(), PopStringToString(CommaSeperate(aTotal/1024)).c_str()); aStr += StrFormat("Texture Memory: %s @@ -2205,12 +2059,15 @@ bool AppBase::ProcessDeferredMessages(bool singleMessage) Shutdown(); break; case SDL_EVENT_WINDOW_FOCUS_GAINED: - mActive = true; - RehupFocus(); - if (!mIsWindowed) - mWidgetManager->MarkAllDirty(); - if (mIsOpeningURL && !mActive) - URLOpenSucceeded(mOpeningURL); + if ((!gInAssert) && (!mSEHOccured) && (!mShutdown)) + { + mActive = true; + RehupFocus(); + if (!mIsWindowed) + mWidgetManager->MarkAllDirty(); + if (mIsOpeningURL && !mActive) + URLOpenSucceeded(mOpeningURL); + } break; case SDL_EVENT_WINDOW_FOCUS_LOST: mActive = false; @@ -2268,7 +2125,7 @@ bool AppBase::ProcessDeferredMessages(bool singleMessage) int y = event.button.y; int renderWidth, renderHeight; - SDL_GetCurrentRenderOutputSize(mSDLInterface->mRenderer, &renderWidth, &renderHeight); + mRenderer->GetOutputSize(&renderWidth, &renderHeight); int scaledX = static_cast(event.button.x * ((float)mWidth / renderWidth)); int scaledY = static_cast(event.button.y * ((float)mHeight / renderHeight)); @@ -2325,9 +2182,54 @@ std::string AppBase::NotifyCrashHook() void AppBase::MakeWindow() { - if (mSDLInterface == nullptr) + int aWindowFlags = mIsWindowed ? 0 : SDL_WINDOW_FULLSCREEN; + + std::string fullTitle = mTitle.c_str(); + switch (mRendererAPI) { - mSDLInterface = new SDLInterface(this); + case Renderers::SDL: + fullTitle += " | SDL"; + break; + case Renderers::OpenGL: + fullTitle += " | OpenGL"; + break; + default: + break; + } + + mWindow = SDL_CreateWindow(fullTitle.c_str(), mWidth, mHeight, aWindowFlags | SDL_WINDOW_OPENGL); + if (mWindow == nullptr) + { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Window Creation Failed", SDL_GetError(), nullptr); + Shutdown(); + return; + } + + if (!mIsWindowed) + SDL_SetWindowFullscreen(mWindow, true); + + SDL_StartTextInput(mWindow); + + if (!mRenderer) + { + //-- rendering + switch (mRendererAPI) + { + case Renderers::SDL: { + mRenderer = new SDLRenderer(this); + break; + } + case Renderers::OpenGL: { + mRenderer = new GLRenderer(this); + break; + } + default: { + // well, if we don't have a specified renderer then do SDL + mRenderer = new SDLRenderer(this); + break; + } + } + //-- end rendering // Enable 3d setting bool is3D = false; @@ -2343,11 +2245,17 @@ void AppBase::MakeWindow() if (is3D) mTest3D = true; - mSDLInterface->mIs3D = is3D; + mRenderer->mIs3D = is3D; } } - int aResult = InitSDLInterface(); + if (!m_pScriptingBase) + { + m_pScriptingBase = new ScriptingBase(); + m_pScriptingBase->Initialize(); + } + + int aResult = InitInterface(); bool isActive = mActive; // mActive = GetActiveWindow() == mHWnd; @@ -2368,42 +2276,58 @@ void AppBase::MakeWindow() ReInitImages(); - mWidgetManager->mImage = mSDLInterface->GetScreenImage(); + mWidgetManager->mImage = mRenderer->GetScreenImage(); mWidgetManager->MarkAllDirty(); // SetTimer(mHWnd, 100, mFrameTime, nullptr); } +bool AppBase::UpdateWindowIcon(Image *theImage) +{ + if (theImage != nullptr) + { + SDL_Surface *aSurface = + SDL_CreateSurfaceFrom(theImage->mWidth, theImage->mHeight, SDL_PIXELFORMAT_ARGB8888, + ((MemoryImage *)theImage)->GetBits(), theImage->mWidth * sizeof(ulong)); + + SDL_SetWindowIcon(mWindow, aSurface); + + SDL_DestroySurface(aSurface); + return true; + } + return false; +} + void AppBase::DeleteNativeImageData() { - MemoryImageSet::iterator anItr = mMemoryImageSet.begin(); - while (anItr != mMemoryImageSet.end()) + GPUImageSet::iterator anItr = mGPUImageSet.begin(); + while (anItr != mGPUImageSet.end()) { - MemoryImage *aMemoryImage = *anItr; - aMemoryImage->DeleteNativeData(); + GPUImage *aGPUImage = *anItr; + aGPUImage->DeleteNativeData(); ++anItr; } } void AppBase::DeleteExtraImageData() { - AutoCrit anAutoCrit(mSDLInterface->mCritSect); - MemoryImageSet::iterator anItr = mMemoryImageSet.begin(); - while (anItr != mMemoryImageSet.end()) + AutoCrit anAutoCrit(mRenderer->mCritSect); + GPUImageSet::iterator anItr = mGPUImageSet.begin(); + while (anItr != mGPUImageSet.end()) { - MemoryImage *aMemoryImage = *anItr; - aMemoryImage->DeleteExtraBuffers(); + GPUImage *aGPUImage = *anItr; + aGPUImage->DeleteExtraBuffers(); ++anItr; } } void AppBase::ReInitImages() { - MemoryImageSet::iterator anItr = mMemoryImageSet.begin(); - while (anItr != mMemoryImageSet.end()) + GPUImageSet::iterator anItr = mGPUImageSet.begin(); + while (anItr != mGPUImageSet.end()) { - MemoryImage *aMemoryImage = *anItr; - aMemoryImage->ReInit(); + GPUImage *aGPUImage = *anItr; + aGPUImage->ReInit(); ++anItr; } } @@ -2423,8 +2347,8 @@ int AppBase::LoadingThreadProcStub(void *theArg) aPopLibApp->LoadingThreadProc(); char aStr[256]; - sprintf(aStr, "Resource Loading Time: %d\r\n", (SDL_GetTicks() - aPopLibApp->mTimeLoaded)); - SDL_Log(aStr); + sprintf(aStr, "Resource Loading Time: %lu\n", (SDL_GetTicks() - aPopLibApp->mTimeLoaded)); + LOG_INFO("%s", aStr); aPopLibApp->mLoadingThreadCompleted = true; @@ -2442,71 +2366,6 @@ void AppBase::StartLoadingThread() SDL_DetachThread(aThread); } } -void AppBase::CursorThreadProc() -{ - SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH); - SDL_Point aLastCursorPos = {0, 0}; - int aLastDrawCount = 0; - - while (!mShutdown) - { - - SDL_Point aCursorPos; - - float x, y; - - SDL_GetMouseState(&x, &y); - - aCursorPos.x = (int)x; - aCursorPos.y = (int)y; - - if (aLastDrawCount != mDrawCount) - { - // We did a draw so we may have committed a pending mNextCursorX/Y - aLastCursorPos.x = mSDLInterface->mCursorX; - aLastCursorPos.y = mSDLInterface->mCursorY; - } - - if ((aCursorPos.x != aLastCursorPos.x) || (aCursorPos.y != aLastCursorPos.y)) - { - Uint32 aTimeNow = SDL_GetTicks(); - if (aTimeNow - mNextDrawTick > mSDLInterface->mMillisecondsPerFrame + 5) - { - // Do the special drawing if we are rendering at less than full framerate - mSDLInterface->SetCursorPos(aCursorPos.x, aCursorPos.y); - aLastCursorPos = aCursorPos; - } - else - { - // Set them up to get assigned in the next screen redraw - mSDLInterface->mNextCursorX = aCursorPos.x; - mSDLInterface->mNextCursorY = aCursorPos.y; - } - } - - SDL_Delay(10); - } - - mCursorThreadRunning = false; -} - -int AppBase::CursorThreadProcStub(void *theArg) -{ - AppBase *aPopLibApp = (AppBase *)theArg; - aPopLibApp->CursorThreadProc(); - return 0; -} - -void AppBase::StartCursorThread() -{ - if (!mCursorThreadRunning) - { - mCursorThreadRunning = true; - SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH); - SDL_Thread *aThread = SDL_CreateThread(CursorThreadProcStub, "CursorThread", (void *)this); - SDL_DetachThread(aThread); - } -} void AppBase::SwitchScreenMode(bool wantWindowed, bool is3d, bool force) { @@ -2555,8 +2414,8 @@ void AppBase::SetAlphaDisabled(bool isDisabled) if (mAlphaDisabled != isDisabled) { mAlphaDisabled = isDisabled; - mSDLInterface->SetVideoOnlyDraw(mAlphaDisabled); - mWidgetManager->mImage = mSDLInterface->GetScreenImage(); + mRenderer->SetVideoOnlyDraw(mAlphaDisabled); + mWidgetManager->mImage = mRenderer->GetScreenImage(); mWidgetManager->MarkAllDirty(); } } @@ -2565,67 +2424,81 @@ void AppBase::EnforceCursor() { bool wantSysCursor = true; - if (mSDLInterface == nullptr) + SDL_SystemCursor sdlCursorType; + + switch (mCursorNum) + { + case CursorType::Hand: + sdlCursorType = SDL_SYSTEM_CURSOR_POINTER; + break; + case CursorType::Dragging: + sdlCursorType = SDL_SYSTEM_CURSOR_MOVE; + break; + case CursorType::Text: + sdlCursorType = SDL_SYSTEM_CURSOR_TEXT; + break; + case CursorType::CircleSlash: + sdlCursorType = SDL_SYSTEM_CURSOR_NOT_ALLOWED; + break; + case CursorType::SizeAll: + sdlCursorType = SDL_SYSTEM_CURSOR_MOVE; + break; + case CursorType::SizeNESW: + sdlCursorType = SDL_SYSTEM_CURSOR_NESW_RESIZE; + break; + case CursorType::SizeNS: + sdlCursorType = SDL_SYSTEM_CURSOR_NS_RESIZE; + break; + case CursorType::SizeNWSE: + sdlCursorType = SDL_SYSTEM_CURSOR_NWSE_RESIZE; + break; + case CursorType::SizeWE: + sdlCursorType = SDL_SYSTEM_CURSOR_EW_RESIZE; + break; + case CursorType::Wait: + sdlCursorType = SDL_SYSTEM_CURSOR_WAIT; + break; + case CursorType::Unknown: + SDL_HideCursor(); return; + case CursorType::Pointer: + default: + sdlCursorType = SDL_SYSTEM_CURSOR_DEFAULT; + break; + } if ((mSEHOccured) || (!mMouseIn)) { - mSDLInterface->SetCursor(SDL_SYSTEM_CURSOR_DEFAULT); - if (mSDLInterface->SetCursorImage(nullptr)) - mCustomCursorDirty = true; + SDL_Cursor *aCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER); + SDL_SetCursor(aCursor); } else { - if ((mCursorImages[mCursorNum] == nullptr) || ((!mCustomCursorsEnabled) && (mCursorNum != CURSOR_CUSTOM))) + if ((mCursorImages[static_cast(mCursorNum)] == nullptr) || + ((!mCustomCursorsEnabled) && (mCursorNum != CursorType::Custom))) { - switch (mCursorNum) - { - case CURSOR_POINTER: - mSDLInterface->SetCursor(SDL_SYSTEM_CURSOR_DEFAULT); - break; - case CURSOR_HAND: - mSDLInterface->SetCursor(SDL_SYSTEM_CURSOR_POINTER); - break; - case CURSOR_TEXT: - mSDLInterface->SetCursor(SDL_SYSTEM_CURSOR_TEXT); - break; - case CURSOR_DRAGGING: - SDL_SetCursor(mDraggingCursor); - mSDLInterface->SetCursor(SDL_SYSTEM_CURSOR_TEXT); - break; - case CURSOR_CIRCLE_SLASH: - mSDLInterface->SetCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED); - break; - case CURSOR_SIZEALL: - mSDLInterface->SetCursor(SDL_SYSTEM_CURSOR_MOVE); - break; - case CURSOR_SIZENESW: - case CURSOR_SIZENS: - case CURSOR_SIZENWSE: - case CURSOR_SIZEWE: - mSDLInterface->SetCursor(SDL_SYSTEM_CURSOR_NWSE_RESIZE); - break; - case CURSOR_WAIT: - mSDLInterface->SetCursor(SDL_SYSTEM_CURSOR_WAIT); - break; - case CURSOR_CUSTOM: - case CURSOR_NONE: - SDL_SetCursor(nullptr); - break; - default: - mSDLInterface->SetCursor(SDL_SYSTEM_CURSOR_DEFAULT); - break; - } - - if (mSDLInterface->SetCursorImage(nullptr)) - mCustomCursorDirty = true; + SDL_Cursor *aCursor = SDL_CreateSystemCursor(sdlCursorType); + SDL_SetCursor(aCursor); } else { - if (mSDLInterface->SetCursorImage(mCursorImages[mCursorNum])) - mCustomCursorDirty = true; - - wantSysCursor = false; + SDL_Surface *aSurface = SDL_CreateSurfaceFrom( + mCursorImages[static_cast(mCursorNum)]->mWidth, + mCursorImages[static_cast(mCursorNum)]->mHeight, + SDL_PIXELFORMAT_ARGB8888, + static_cast(mCursorImages[static_cast(mCursorNum)])->GetBits(), + mCursorImages[static_cast(mCursorNum)]->mWidth * sizeof(ulong) + ); + + SDL_Cursor *aCursor = SDL_CreateColorCursor( + aSurface, + mCursorImages[static_cast(mCursorNum)]->mWidth / 2, + mCursorImages[static_cast(mCursorNum)]->mHeight / 2 + ); + + SDL_SetCursor(aCursor); + + SDL_DestroySurface(aSurface); } } @@ -2633,9 +2506,7 @@ void AppBase::EnforceCursor() { mSysCursor = wantSysCursor; - // Don't hide the hardware cursor when playing back a demo buffer - // if (!mPlayingDemoBuffer) - // ::ShowCursor(mSysCursor); + // Optionally show/hide cursor if needed here } } @@ -2790,7 +2661,7 @@ bool AppBase::Process(bool allowSleep) ++mNonDrawCount; bool hasRealUpdate = DoUpdateFrames(); - DBG_ASSERTE(hasRealUpdate); + ASSERT(hasRealUpdate); if (!hasRealUpdate) break; @@ -2876,7 +2747,7 @@ bool AppBase::Process(bool allowSleep) if (theModalDialogId != -1) { aDialog = GetDialog(theModalDialogId); - DBG_ASSERTE(aDialog != nullptr); + ASSERT(aDialog != nullptr); if (aDialog == nullptr) return; } @@ -2980,26 +2851,27 @@ bool AppBase::UpdateApp() } } -int AppBase::InitSDLInterface() +int AppBase::InitInterface() { - PreSDLInterfaceInitHook(); + PreInterfaceInitHook(); DeleteNativeImageData(); - int aResult = mSDLInterface->Init(mIsWindowed); - if (SDLInterface::RESULT_OK == aResult) + + int aResult = mRenderer->Init(); + if (aResult == Renderer::RESULT_OK) { - mScreenBounds.mX = (mWidth - mSDLInterface->mWidth) / 2; - mScreenBounds.mY = (mHeight - mSDLInterface->mHeight) / 2; - mScreenBounds.mWidth = mSDLInterface->mWidth; - mScreenBounds.mHeight = mSDLInterface->mHeight; - mSDLInterface->UpdateViewport(); - mWidgetManager->Resize(mScreenBounds, mSDLInterface->mPresentationRect); - PostSDLInterfaceInitHook(); + mScreenBounds.mX = (mWidth - mRenderer->mWidth) / 2; + mScreenBounds.mY = (mHeight - mRenderer->mHeight) / 2; + mScreenBounds.mWidth = mRenderer->mWidth; + mScreenBounds.mHeight = mRenderer->mHeight; + mRenderer->UpdateViewport(); + mWidgetManager->Resize(mScreenBounds, mRenderer->mPresentationRect); + PostInterfaceInitHook(); if (mIGUIManager) delete mIGUIManager; - mIGUIManager = new ImGuiManager(mSDLInterface); - RegisterImGuiWindows(); + mIGUIManager = new ImGuiManager(mRenderer); + RegisterImGuiWindows(); } return aResult; } @@ -3013,12 +2885,10 @@ void AppBase::Start() if (mShutdown) return; - StartCursorThread(); - if (mAutoStartLoadingThread) StartLoadingThread(); - SDL_RaiseWindow(mSDLInterface->mWindow); + SDL_RaiseWindow(mWindow); int aCount = 0; int aSleepCount = 0; @@ -3037,16 +2907,16 @@ void AppBase::Start() WaitForLoadingThread(); - SDL_Log("Seconds = %g\r\n", (SDL_GetTicks() - aStartTime) / 1000.0); + LOG_INFO("Seconds = %g\r\n", (SDL_GetTicks() - aStartTime) / 1000.0); // sprintf(aString, "Count = %d\r\n", aCount); // OutputDebugString(aString); - SDL_Log("Sleep Count = %d\r\n", mSleepCount); - SDL_Log("Update Count = %d\r\n", mUpdateCount); - SDL_Log("Draw Count = %d\r\n", mDrawCount); - SDL_Log("Screen Blt = %d\r\n", mScreenBltTime); + LOG_INFO("Sleep Count = %d\r\n", mSleepCount); + LOG_INFO("Update Count = %d\r\n", mUpdateCount); + LOG_INFO("Draw Count = %d\r\n", mDrawCount); + LOG_INFO("Screen Blt = %d\r\n", mScreenBltTime); if (mDrawTime + mScreenBltTime > 0) { - SDL_Log("Avg FPS = %d\r\n", (mDrawCount * 1000) / (mDrawTime + mScreenBltTime)); + LOG_INFO("Avg FPS = %d\r\n", (mDrawCount * 1000) / (mDrawTime + mScreenBltTime)); } PreTerminate(); @@ -3116,7 +2986,7 @@ void AppBase::ShowResourceError(bool doExit) bool AppBase::GetBoolean(const std::string &theId) { StringBoolMap::iterator anItr = mBoolProperties.find(theId); - DBG_ASSERTE(anItr != mBoolProperties.end()); + ASSERT(anItr != mBoolProperties.end()); if (anItr != mBoolProperties.end()) return anItr->second; @@ -3137,7 +3007,7 @@ bool AppBase::GetBoolean(const std::string &theId, bool theDefault) int AppBase::GetInteger(const std::string &theId) { StringIntMap::iterator anItr = mIntProperties.find(theId); - DBG_ASSERTE(anItr != mIntProperties.end()); + ASSERT(anItr != mIntProperties.end()); if (anItr != mIntProperties.end()) return anItr->second; @@ -3158,7 +3028,7 @@ int AppBase::GetInteger(const std::string &theId, int theDefault) double AppBase::GetDouble(const std::string &theId) { StringDoubleMap::iterator anItr = mDoubleProperties.find(theId); - DBG_ASSERTE(anItr != mDoubleProperties.end()); + ASSERT(anItr != mDoubleProperties.end()); if (anItr != mDoubleProperties.end()) return anItr->second; @@ -3179,7 +3049,7 @@ double AppBase::GetDouble(const std::string &theId, double theDefault) PopString AppBase::GetString(const std::string &theId) { StringStringMap::iterator anItr = mStringProperties.find(theId); - DBG_ASSERTE(anItr != mStringProperties.end()); + ASSERT(anItr != mStringProperties.end()); if (anItr != mStringProperties.end()) return anItr->second; @@ -3200,7 +3070,7 @@ PopString AppBase::GetString(const std::string &theId, const PopString &theDefau StringVector AppBase::GetStringVector(const std::string &theId) { StringStringVectorMap::iterator anItr = mStringVectorProperties.find(theId); - DBG_ASSERTE(anItr != mStringVectorProperties.end()); + ASSERT(anItr != mStringVectorProperties.end()); if (anItr != mStringVectorProperties.end()) return anItr->second; @@ -3238,25 +3108,61 @@ void AppBase::SetDouble(const std::string &theId, double theValue) aPair.first->second = theValue; } +#if defined(__APPLE__) +// macOS: access the argv pointer provided by the runtime +extern "C" +{ + extern char ***_NSGetArgv(); + extern int *_NSGetArgc(); +} +#endif + void AppBase::DoParseCmdLine() { - /* - char *aCmdLine = GetCommandLineA(); - char *aCmdLinePtr = aCmdLine; - if (aCmdLinePtr[0] == '"') + std::string cmdLine; + +#ifdef _WIN32 + cmdLine = GetCommandLineA(); + +#elif defined(__APPLE__) + // macOS: grab argc/argv from the Obj-C runtime + int argc = *_NSGetArgc(); + char **argv = *_NSGetArgv(); + for (int i = 0; i < argc; ++i) + { + if (i) + cmdLine += ' '; + cmdLine += argv[i]; + } +#else + std::ifstream f("/proc/self/cmdline", std::ios::binary); + if (f) { - aCmdLinePtr = strchr(aCmdLinePtr + 1, '"'); - if (aCmdLinePtr != nullptr) - aCmdLinePtr++; + std::string arg; + // /proc/self/cmdline is a sequence of NUL-terminated strings: + while (std::getline(f, arg, '\0')) + { + if (!cmdLine.empty()) + cmdLine += ' '; + cmdLine += arg; + } } +#endif - if (aCmdLinePtr != nullptr) + size_t pos = 0; + if (!cmdLine.empty() && cmdLine[0] == '"') { - aCmdLinePtr = strchr(aCmdLinePtr, ' '); - if (aCmdLinePtr != nullptr) - ParseCmdLine(aCmdLinePtr + 1); + // skip leading quoted exe-path + pos = cmdLine.find('"', 1); + if (pos != std::string::npos) + ++pos; + } + + pos = cmdLine.find(' ', pos); + if (pos != std::string::npos) + { + ParseCmdLine(cmdLine.c_str() + pos + 1); } - */ mCmdLineParsed = true; } @@ -3309,37 +3215,42 @@ void AppBase::ParseCmdLine(const std::string &theCmdLine) static int GetMaxDemoFileNum(const std::string &theDemoPrefix, int theMaxToKeep, bool doErase) { typedef std::set IntSet; - IntSet aSet; + IntSet aSet; - std::string prefixDir = fs::path(theDemoPrefix).parent_path().string(); - std::string prefixBase = fs::path(theDemoPrefix).filename().string(); // just the prefix, e.g., "demo" + std::string prefixDir = fs::path(theDemoPrefix).parent_path().string(); + std::string prefixBase = fs::path(theDemoPrefix).filename().string(); // just the prefix, e.g., "demo" - if (prefixDir.empty()) prefixDir = "."; // use current dir if none + if (prefixDir.empty()) + prefixDir = "."; // use current dir if none - std::regex pattern("^" + prefixBase + R"((\d+)\.dmo$)"); + std::regex pattern("^" + prefixBase + R"((\d+)\.dmo$)"); - for (const auto& entry : fs::directory_iterator(prefixDir)) { - if (!entry.is_regular_file()) continue; + for (const auto &entry : fs::directory_iterator(prefixDir)) + { + if (!entry.is_regular_file()) + continue; - std::smatch match; - std::string filename = entry.path().filename().string(); + std::smatch match; + std::string filename = entry.path().filename().string(); - if (std::regex_match(filename, match, pattern)) { - int num = std::stoi(match[1]); - aSet.insert(num); - } - } + if (std::regex_match(filename, match, pattern)) + { + int num = std::stoi(match[1]); + aSet.insert(num); + } + } - if (!aSet.empty() && (int)aSet.size() > theMaxToKeep - 1 && doErase) { - auto anItr = aSet.begin(); - std::string fileToDelete = theDemoPrefix + std::to_string(*anItr) + ".dmo"; - fs::remove(fileToDelete); - } + if (!aSet.empty() && (int)aSet.size() > theMaxToKeep - 1 && doErase) + { + auto anItr = aSet.begin(); + std::string fileToDelete = theDemoPrefix + std::to_string(*anItr) + ".dmo"; + fs::remove(fileToDelete); + } - if (aSet.empty()) - return 0; + if (aSet.empty()) + return 0; - return *aSet.rbegin(); // last (max) value + return *aSet.rbegin(); // last (max) value } void AppBase::HandleCmdLineParam(const std::string &theParamName, const std::string &theParamValue) @@ -3358,6 +3269,16 @@ void AppBase::HandleCmdLineParam(const std::string &theParamName, const std::str { mChangeDirTo = theParamValue; } + //-- rendering + else if (theParamName == "-sdlrenderer") + { + mRendererAPI = Renderers::SDL; + } + else if (theParamName == "-opengl") + { + mRendererAPI = Renderers::OpenGL; + } + //-- end rendering else { Popup(GetString("INVALID_COMMANDLINE_PARAM", "Invalid command line parameter: ") + theParamName); @@ -3369,11 +3290,11 @@ void AppBase::PreDisplayHook() { } -void AppBase::PreSDLInterfaceInitHook() +void AppBase::PreInterfaceInitHook() { } -void AppBase::PostSDLInterfaceInitHook() +void AppBase::PostInterfaceInitHook() { } @@ -3387,7 +3308,7 @@ MusicInterface *AppBase::CreateMusicInterface() if (mNoSoundNeeded) return new MusicInterface; else - return new BassMusicInterface(); + return new OpenMPTMusicInterface(); } void AppBase::InitPropertiesHook() @@ -3401,7 +3322,6 @@ void AppBase::InitHook() void AppBase::Init() { mPrimaryThreadId = SDL_GetCurrentThreadID(); - mErrorHandler = new ErrorHandler(this); if (mShutdown) return; @@ -3442,7 +3362,7 @@ void AppBase::Init() { // How can we be windowed if our screen isn't even big enough? SDL_DisplayID displayID = SDL_GetPrimaryDisplay(); - const SDL_DisplayMode* dm = SDL_GetCurrentDisplayMode(displayID); + const SDL_DisplayMode *dm = SDL_GetCurrentDisplayMode(displayID); int screenWidth = dm->w; int screenHeight = dm->h; @@ -3465,12 +3385,14 @@ void AppBase::Init() SetMusicVolume(mMusicVolume); if (IsScreenSaver()) - { - SetCursor(CURSOR_NONE); - } + SetCursor(CursorType::Unknown); InitHook(); + // ok, i assume here + m_pScriptingBase->SetScriptFolder("scripts/"); + m_pScriptingBase->ExecuteFolder(""); + mInitialized = true; } @@ -3494,13 +3416,13 @@ std::string AppBase::GetClipboard() return aString; } -void AppBase::SetCursor(int theCursorNum) +void AppBase::SetCursor(CursorType theCursorNum) { mCursorNum = theCursorNum; EnforceCursor(); } -int AppBase::GetCursor() +CursorType AppBase::GetCursor() { return mCursorNum; } @@ -3513,24 +3435,24 @@ void AppBase::EnableCustomCursors(bool enabled) void AppBase::SetTaskBarIcon(const std::string &theFileName) { - // H521 + // we use SDL to load taskbar icons int width, height, channels; unsigned char *data = stbi_load(theFileName.c_str(), &width, &height, &channels, 4); if (!data) { - SDL_Log("failed to load image: %s\n", stbi_failure_reason()); + LOG_ERROR("failed to load image: %s\n", stbi_failure_reason()); return; } - SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_RGBA32, data, width*4); + SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_RGBA32, data, width * 4); if (!surface) { - SDL_Log("no surface?\n"); + LOG_ERROR("no surface?\n"); stbi_image_free(data); return; } - SDL_SetWindowIcon(mSDLInterface->mWindow, surface); + SDL_SetWindowIcon(mWindow, surface); stbi_image_free(surface->pixels); SDL_DestroySurface(surface); @@ -3538,56 +3460,57 @@ void AppBase::SetTaskBarIcon(const std::string &theFileName) return; } -PopLib::SDLImage *AppBase::GetImage(const std::string &theFileName, bool commitBits) +PopLib::GPUImage *AppBase::GetImage(const std::string &theFileName, bool commitBits) { ImageLib::Image *aLoadedImage = ImageLib::GetImage(theFileName, true); if (aLoadedImage == nullptr) return nullptr; - SDLImage *anImage = new SDLImage(mSDLInterface); + GPUImage *anImage = mRenderer->NewGPUImage(); anImage->mFilePath = theFileName; - anImage->SetBits((uint32_t*)aLoadedImage->GetBits(), aLoadedImage->GetWidth(), aLoadedImage->GetHeight(), commitBits); + anImage->SetBits((uint32_t *)aLoadedImage->GetBits(), aLoadedImage->GetWidth(), aLoadedImage->GetHeight(), + commitBits); delete aLoadedImage; return anImage; } -PopLib::SDLImage *AppBase::CreateCrossfadeImage(PopLib::Image *theImage1, const Rect &theRect1, - PopLib::Image *theImage2, const Rect &theRect2, double theFadeFactor) +PopLib::GPUImage *AppBase::CreateCrossfadeImage(PopLib::Image *theImage1, const Rect &theRect1, + PopLib::Image *theImage2, const Rect &theRect2, double theFadeFactor) { - MemoryImage *aMemoryImage1 = dynamic_cast(theImage1); - MemoryImage *aMemoryImage2 = dynamic_cast(theImage2); + GPUImage *aGPUImage1 = dynamic_cast(theImage1); + GPUImage *aGPUImage2 = dynamic_cast(theImage2); - if ((aMemoryImage1 == nullptr) || (aMemoryImage2 == nullptr)) + if ((aGPUImage1 == nullptr) || (aGPUImage2 == nullptr)) return nullptr; if ((theRect1.mX < 0) || (theRect1.mY < 0) || (theRect1.mX + theRect1.mWidth > theImage1->GetWidth()) || (theRect1.mY + theRect1.mHeight > theImage1->GetHeight())) { - DBG_ASSERTE("Crossfade Rect1 out of bounds"); + ASSERT("Crossfade Rect1 out of bounds"); return nullptr; } if ((theRect2.mX < 0) || (theRect2.mY < 0) || (theRect2.mX + theRect2.mWidth > theImage2->GetWidth()) || (theRect2.mY + theRect2.mHeight > theImage2->GetHeight())) { - DBG_ASSERTE("Crossfade Rect2 out of bounds"); + ASSERT("Crossfade Rect2 out of bounds"); return nullptr; } int aWidth = theRect1.mWidth; int aHeight = theRect1.mHeight; - SDLImage *anImage = new SDLImage(mSDLInterface); + GPUImage *anImage = mRenderer->NewGPUImage(); anImage->Create(aWidth, aHeight); ulong *aDestBits = anImage->GetBits(); - ulong *aSrcBits1 = aMemoryImage1->GetBits(); - ulong *aSrcBits2 = aMemoryImage2->GetBits(); + ulong *aSrcBits1 = aGPUImage1->GetBits(); + ulong *aSrcBits2 = aGPUImage2->GetBits(); - int aSrc1Width = aMemoryImage1->GetWidth(); - int aSrc2Width = aMemoryImage2->GetWidth(); + int aSrc1Width = aGPUImage1->GetWidth(); + int aSrc2Width = aGPUImage2->GetWidth(); ulong aMult = (int)(theFadeFactor * 256); ulong aOMM = (256 - aMult); @@ -3619,22 +3542,22 @@ PopLib::SDLImage *AppBase::CreateCrossfadeImage(PopLib::Image *theImage1, const void AppBase::ColorizeImage(Image *theImage, const Color &theColor) { - MemoryImage *aSrcMemoryImage = dynamic_cast(theImage); + GPUImage *aSrcGPUImage = dynamic_cast(theImage); - if (aSrcMemoryImage == nullptr) + if (aSrcGPUImage == nullptr) return; ulong *aBits; int aNumColors; - if (aSrcMemoryImage->mColorTable == nullptr) + if (aSrcGPUImage->mColorTable == nullptr) { - aBits = aSrcMemoryImage->GetBits(); + aBits = aSrcGPUImage->GetBits(); aNumColors = theImage->GetWidth() * theImage->GetHeight(); } else { - aBits = aSrcMemoryImage->mColorTable; + aBits = aSrcGPUImage->mColorTable; aNumColors = 256; } @@ -3674,17 +3597,17 @@ void AppBase::ColorizeImage(Image *theImage, const Color &theColor) } } - aSrcMemoryImage->BitsChanged(); + aSrcGPUImage->BitsChanged(); } -SDLImage *AppBase::CreateColorizedImage(Image *theImage, const Color &theColor) +GPUImage *AppBase::CreateColorizedImage(Image *theImage, const Color &theColor) { - MemoryImage *aSrcMemoryImage = dynamic_cast(theImage); + GPUImage *aSrcGPUImage = dynamic_cast(theImage); - if (aSrcMemoryImage == nullptr) + if (aSrcGPUImage == nullptr) return nullptr; - SDLImage *anImage = new SDLImage(mSDLInterface); + GPUImage *anImage = mRenderer->NewGPUImage(); anImage->Create(theImage->GetWidth(), theImage->GetHeight()); @@ -3692,20 +3615,20 @@ SDLImage *AppBase::CreateColorizedImage(Image *theImage, const Color &theColor) ulong *aDestBits; int aNumColors; - if (aSrcMemoryImage->mColorTable == nullptr) + if (aSrcGPUImage->mColorTable == nullptr) { - aSrcBits = aSrcMemoryImage->GetBits(); + aSrcBits = aSrcGPUImage->GetBits(); aDestBits = anImage->GetBits(); aNumColors = theImage->GetWidth() * theImage->GetHeight(); } else { - aSrcBits = aSrcMemoryImage->mColorTable; + aSrcBits = aSrcGPUImage->mColorTable; aDestBits = anImage->mColorTable = new ulong[256]; aNumColors = 256; anImage->mColorIndices = new uchar[anImage->mWidth * theImage->mHeight]; - memcpy(anImage->mColorIndices, aSrcMemoryImage->mColorIndices, anImage->mWidth * theImage->mHeight); + memcpy(anImage->mColorIndices, aSrcGPUImage->mColorIndices, anImage->mWidth * theImage->mHeight); } if ((theColor.mAlpha <= 255) && (theColor.mRed <= 255) && (theColor.mGreen <= 255) && (theColor.mBlue <= 255)) @@ -3749,9 +3672,9 @@ SDLImage *AppBase::CreateColorizedImage(Image *theImage, const Color &theColor) return anImage; } -SDLImage *AppBase::CopyImage(Image *theImage, const Rect &theRect) +GPUImage *AppBase::CopyImage(Image *theImage, const Rect &theRect) { - SDLImage *anImage = new SDLImage(mSDLInterface); + GPUImage *anImage = mRenderer->NewGPUImage(); anImage->Create(theRect.mWidth, theRect.mHeight); @@ -3763,19 +3686,19 @@ SDLImage *AppBase::CopyImage(Image *theImage, const Rect &theRect) return anImage; } -SDLImage *AppBase::CopyImage(Image *theImage) +GPUImage *AppBase::CopyImage(Image *theImage) { return CopyImage(theImage, Rect(0, 0, theImage->GetWidth(), theImage->GetHeight())); } void AppBase::MirrorImage(Image *theImage) { - MemoryImage *aSrcMemoryImage = dynamic_cast(theImage); + GPUImage *aSrcGPUImage = dynamic_cast(theImage); - ulong *aSrcBits = aSrcMemoryImage->GetBits(); + ulong *aSrcBits = aSrcGPUImage->GetBits(); - int aPhysSrcWidth = aSrcMemoryImage->mWidth; - for (int y = 0; y < aSrcMemoryImage->mHeight; y++) + int aPhysSrcWidth = aSrcGPUImage->mWidth; + for (int y = 0; y < aSrcGPUImage->mHeight; y++) { ulong *aLeftBits = aSrcBits + (y * aPhysSrcWidth); ulong *aRightBits = aLeftBits + (aPhysSrcWidth - 1); @@ -3789,17 +3712,17 @@ void AppBase::MirrorImage(Image *theImage) } } - aSrcMemoryImage->BitsChanged(); + aSrcGPUImage->BitsChanged(); } void AppBase::FlipImage(Image *theImage) { - MemoryImage *aSrcMemoryImage = dynamic_cast(theImage); + GPUImage *aSrcGPUImage = dynamic_cast(theImage); - ulong *aSrcBits = aSrcMemoryImage->GetBits(); + ulong *aSrcBits = aSrcGPUImage->GetBits(); - int aPhysSrcHeight = aSrcMemoryImage->mHeight; - int aPhysSrcWidth = aSrcMemoryImage->mWidth; + int aPhysSrcHeight = aSrcGPUImage->mHeight; + int aPhysSrcWidth = aSrcGPUImage->mWidth; for (int x = 0; x < aPhysSrcWidth; x++) { ulong *aTopBits = aSrcBits + x; @@ -3816,16 +3739,16 @@ void AppBase::FlipImage(Image *theImage) } } - aSrcMemoryImage->BitsChanged(); + aSrcGPUImage->BitsChanged(); } -void AppBase::RotateImageHue(PopLib::MemoryImage *theImage, int theDelta) +void AppBase::RotateImageHue(PopLib::GPUImage *theImage, int theDelta) { while (theDelta < 0) theDelta += 256; int aSize = theImage->mWidth * theImage->mHeight; - uint32_t* aPtr = reinterpret_cast(theImage->GetBits()); + uint32_t *aPtr = reinterpret_cast(theImage->GetBits()); for (int i = 0; i < aSize; i++) { uint32_t aPixel = *aPtr; @@ -3864,11 +3787,11 @@ void AppBase::RotateImageHue(PopLib::MemoryImage *theImage, int theDelta) int y = (int)(2 * l - v); int aColorDiv = (6 * h) / 256; - int x = (int)(y + (v - y) * ((h - (aColorDiv * 256 / 6)) * 6) / 255); + int x = (int)(y + (v - y) * ((h - (aColorDiv * 256.0 / 6)) * 6) / 255); if (x > 255) x = 255; - int z = (int)(v - (v - y) * ((h - (aColorDiv * 256 / 6)) * 6) / 255); + int z = (int)(v - (v - y) * ((h - (aColorDiv * 256.0 / 6)) * 6) / 255); if (z < 0) z = 0; @@ -3928,11 +3851,11 @@ ulong AppBase::HSLToRGB(int h, int s, int l) int y = (int)(2 * l - v); int aColorDiv = (6 * h) / 256; - int x = (int)(y + (v - y) * ((h - (aColorDiv * 256 / 6)) * 6) / 255); + int x = (int)(y + (v - y) * ((h - (aColorDiv * 256.0 / 6)) * 6) / 255); if (x > 255) x = 255; - int z = (int)(v - (v - y) * ((h - (aColorDiv * 256 / 6)) * 6) / 255); + int z = (int)(v - (v - y) * ((h - (aColorDiv * 256.0 / 6)) * 6) / 255); if (z < 0) z = 0; @@ -4023,19 +3946,19 @@ void AppBase::RGBToHSL(const ulong *theSource, ulong *theDest, int theSize) } } -void AppBase::PrecacheAdditive(MemoryImage *theImage) +void AppBase::PrecacheAdditive(GPUImage *theImage) { - theImage->GetRLAdditiveData(mSDLInterface); + theImage->GetRLAdditiveData(mRenderer); } -void AppBase::PrecacheAlpha(MemoryImage *theImage) +void AppBase::PrecacheAlpha(GPUImage *theImage) { theImage->GetRLAlphaData(); } -void AppBase::PrecacheNative(MemoryImage *theImage) +void AppBase::PrecacheNative(GPUImage *theImage) { - theImage->GetNativeAlphaData(mSDLInterface); + theImage->GetNativeAlphaData(mRenderer); } void AppBase::PlaySample(int theSoundNum) @@ -4128,68 +4051,62 @@ void AppBase::SetMasterVolume(double theMasterVolume) mSoundManager->SetMasterVolume(mSfxVolume); } -void AppBase::AddMemoryImage(MemoryImage *theMemoryImage) +void AppBase::AddGPUImage(GPUImage *theGPUImage) { - AutoCrit anAutoCrit(mSDLInterface->mCritSect); - mMemoryImageSet.insert(theMemoryImage); + AutoCrit anAutoCrit(mRenderer->mCritSect); + mGPUImageSet.insert(theGPUImage); } -void AppBase::RemoveMemoryImage(MemoryImage *theMemoryImage) +void AppBase::RemoveGPUImage(GPUImage *theGPUImage) { - // AutoCrit anAutoCrit(mSDLInterface->mCritSect); - MemoryImageSet::iterator anItr = mMemoryImageSet.find(theMemoryImage); - if (anItr != mMemoryImageSet.end()) - mMemoryImageSet.erase(anItr); + AutoCrit anAutoCrit(mRenderer->mCritSect); + GPUImageSet::iterator anItr = mGPUImageSet.find(theGPUImage); + if (anItr != mGPUImageSet.end()) + mGPUImageSet.erase(anItr); - Remove3DData(theMemoryImage); + Remove3DData(theGPUImage); } -void AppBase::Remove3DData(MemoryImage *theMemoryImage) +void AppBase::Remove3DData(GPUImage *theGPUImage) { - if (mSDLInterface) - mSDLInterface->Remove3DData(theMemoryImage); + if (mRenderer) + mRenderer->Remove3DData(theGPUImage); } bool AppBase::Is3DAccelerated() { - return mSDLInterface->mIs3D; + return mRenderer->mIs3D; } bool AppBase::Is3DAccelerationSupported() { - // if (mSDLInterface->mD3DTester) - // return mDDInterface->mD3DTester->Is3DSupported(); - // else - return true; + return (APITester::IsDirect3DAvailable(mWindow) || APITester::IsOpenGLAvailable(mWindow)); } bool AppBase::Is3DAccelerationRecommended() { - // if (mSDLInterface->mD3DTester) - // return mDDInterface->mD3DTester->Is3DRecommended(); - // else - return true; + return Is3DAccelerationSupported(); } void AppBase::Set3DAcclerated(bool is3D, bool reinit) { - if (mSDLInterface->mIs3D == is3D) + if (mRenderer->mIs3D == is3D) return; mUserChanged3DSetting = true; - mSDLInterface->mIs3D = is3D; + mRenderer->mIs3D = is3D; if (reinit) { - int aResult = InitSDLInterface(); + int aResult = InitInterface(); - if (is3D && aResult == SDLInterface::RESULT_3D_FAIL) + if (is3D && aResult == Renderer::RESULT_3D_FAIL) { Set3DAcclerated(false, reinit); return; } - if (aResult != SDLInterface::RESULT_OK) + if (aResult != Renderer::RESULT_OK) { Popup(GetString("FAILED_INIT_DIRECTDRAW", "Failed to initialize DirectDraw: ") + SDL_GetError()); DoExit(1); @@ -4197,7 +4114,7 @@ void AppBase::Set3DAcclerated(bool is3D, bool reinit) ReInitImages(); - mWidgetManager->mImage = mSDLInterface->GetScreenImage(); + mWidgetManager->mImage = mRenderer->GetScreenImage(); mWidgetManager->MarkAllDirty(); } } @@ -4211,7 +4128,7 @@ SharedImageRef AppBase::GetSharedImage(const std::string &theFileName, const std SharedImageRef aSharedImageRef; { - AutoCrit anAutoCrit(mSDLInterface->mCritSect); + AutoCrit anAutoCrit(mRenderer->mCritSect); aResultPair = mSharedImageMap.insert( SharedImageMap::value_type(SharedImageMap::key_type(anUpperFileName, anUpperVariant), SharedImage())); aSharedImageRef = &aResultPair.first->second; @@ -4224,7 +4141,7 @@ SharedImageRef AppBase::GetSharedImage(const std::string &theFileName, const std { // Pass in a '!' as the first char of the file name to create a new image if ((theFileName.length() > 0) && (theFileName[0] == '!')) - aSharedImageRef.mSharedImage->mImage = new SDLImage(mSDLInterface); + aSharedImageRef.mSharedImage->mImage = mRenderer->NewGPUImage(); else aSharedImageRef.mSharedImage->mImage = GetImage(theFileName, false); } @@ -4234,7 +4151,7 @@ SharedImageRef AppBase::GetSharedImage(const std::string &theFileName, const std void AppBase::CleanSharedImages() { - AutoCrit anAutoCrit(mSDLInterface->mCritSect); + AutoCrit anAutoCrit(mRenderer->mCritSect); if (mCleanupSharedImages) { diff --git a/PopLib/appbase.hpp b/PopLib/appbase.hpp index 6a9c8ca5..d12e4d4d 100644 --- a/PopLib/appbase.hpp +++ b/PopLib/appbase.hpp @@ -1,8 +1,7 @@ #ifndef __APPBASE_HPP__ #define __APPBASE_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include "math/rect.hpp" @@ -18,19 +17,31 @@ #include #include -// H522 +class ScriptingBase; /** * @brief registry types, but json */ -enum JSON_RTYPE +enum class JSONRegistryType +{ + Unknown = -1, + String = 0, + Integer, + Bool, + Data, + Last +}; + +/** + * @brief the interface types + */ +enum class Renderers { - JSON_NONE = 0, - JSON_STRING, - JSON_INTEGER, - JSON_BOOLEAN, - JSON_DATA, - JSON_LAST + Unknown = -1, + SDL = 0, // SDLRenderer + OpenGL, // GLRenderer + Last, // number of renderer APIs + _3DAccel = OpenGL, // 3D Accelerated APIs }; namespace ImageLib @@ -42,15 +53,13 @@ namespace PopLib { class WidgetManager; -class SDLInterface; +class Renderer; class Image; -class SDLImage; class Widget; class SoundManager; class MusicInterface; -class MemoryImage; +class GPUImage; class HTTPTransfer; -class ErrorHandler; class ImGuiManager; class Dialog; @@ -64,7 +73,7 @@ class WidgetSafeDeleteInfo }; typedef std::list WidgetSafeDeleteList; -typedef std::set MemoryImageSet; +typedef std::set GPUImageSet; typedef std::map DialogMap; typedef std::list DialogList; typedef std::vector StringVector; @@ -80,24 +89,27 @@ typedef std::map StringStringVectorMap; /** * @brief cursor types */ -enum +enum class CursorType { - CURSOR_POINTER, - CURSOR_HAND, - CURSOR_DRAGGING, - CURSOR_TEXT, - CURSOR_CIRCLE_SLASH, - CURSOR_SIZEALL, - CURSOR_SIZENESW, - CURSOR_SIZENS, - CURSOR_SIZENWSE, - CURSOR_SIZEWE, - CURSOR_WAIT, - CURSOR_NONE, - CURSOR_CUSTOM, - NUM_CURSORS + Unknown = -1, + Pointer = 0, + Hand, + Dragging, + Text, + CircleSlash, + SizeAll, + SizeNESW, + SizeNS, + SizeNWSE, + SizeWE, + Wait, + Custom, + Last }; +// dirty stupid hack +static constexpr auto CursorCount = static_cast>(CursorType::Last); + /** * @brief show fps types */ @@ -136,7 +148,7 @@ enum MsgBoxFlags * @brief handles the game window * * the AppBase class is basically the root of all games, demos and stuff. - * uses SDLInterface for window handling, and everything else window-related + * uses Renderer for window handling, and everything else window-related */ class AppBase : public ButtonListener, public DialogListener { @@ -184,8 +196,6 @@ class AppBase : public ButtonListener, public DialogListener /// @brief TBA bool mbAllowExtendedChars; - /// @brief the error handler - ErrorHandler *mErrorHandler; /// @brief imgui manager object, IGUI = ImGui ImGuiManager *mIGUIManager; @@ -239,8 +249,8 @@ class AppBase : public ButtonListener, public DialogListener bool mFullScreenPageFlip; /// @brief true if tablet pc bool mTabletPC; - /// @brief (SDLInterface) the window interface - SDLInterface *mSDLInterface; + /// @brief the renderer + Renderer *mRenderer; /// @brief TBA bool mAlphaDisabled; /// @brief (MusicInterface) the music interface, uses BASS @@ -252,7 +262,7 @@ class AppBase : public ButtonListener, public DialogListener /// @brief the game(product) version std::string mProductVersion; /// @brief cursor images, max.: 13 - Image *mCursorImages[NUM_CURSORS]; + Image *mCursorImages[CursorCount]; /// @brief titlebar icon, can use .rc for that if on windows Image *mTitleBarIcon; /// @brief if set, overrides the cursor @@ -280,7 +290,7 @@ class AppBase : public ButtonListener, public DialogListener /// @brief TBA bool mMuteOnLostFocus; /// @brief TBA - MemoryImageSet mMemoryImageSet; + GPUImageSet mGPUImageSet; /// @brief TBA SharedImageMap mSharedImageMap; /// @brief TBA @@ -335,14 +345,15 @@ class AppBase : public ButtonListener, public DialogListener /// @brief current step mode. 0 = off, 1 = step, 2 = waiting for step int mStepMode; + /// @brief the sdl window + SDL_Window *mWindow; + /// @brief the interface type + Renderers mRendererAPI; + /// @brief cursor number - int mCursorNum; + CursorType mCursorNum; /// @brief (SoundManager) app sound manager SoundManager *mSoundManager; - /// @brief current hand cursor - SDL_Cursor *mHandCursor; - /// @brief current dragging cursor - SDL_Cursor *mDraggingCursor; /// @brief list of widgets to be safely deleted WidgetSafeDeleteList mSafeDeleteList; /// @brief TBA @@ -389,8 +400,6 @@ class AppBase : public ButtonListener, public DialogListener bool mYieldMainThread; /// @brief true if loading failed bool mLoadingFailed; - /// @brief true if cursor thread running - bool mCursorThreadRunning; /// @brief true if has system cursor bool mSysCursor; /// @brief true if custom cursors are enabled @@ -402,6 +411,9 @@ class AppBase : public ButtonListener, public DialogListener /// @brief true if widescreen window bool mIsWideWindow; + /// @brief Scripting interface + ScriptingBase* m_pScriptingBase; + /// @brief the number of loading thread tasks int mNumLoadingThreadTasks; /// @brief the number of completed loading thread tasks @@ -465,11 +477,6 @@ class AppBase : public ButtonListener, public DialogListener /// @brief (ResourceManager) the app resource manager ResourceManager *mResourceManager; -#ifdef ZYLOM - /// @brief zylom game id - uint mZylomGameId; -#endif - protected: /// @brief TBA void RehupFocus(); @@ -511,15 +518,6 @@ class AppBase : public ButtonListener, public DialogListener /// @brief stub for loading thread static int LoadingThreadProcStub(void *theArg); - // Cursor thread methods - - /// @brief TBA - void CursorThreadProc(); - /// @brief stub for cursor thread - static int CursorThreadProcStub(void *theArg); - /// @brief TBA - void StartCursorThread(); - /// @brief TBA void WaitForLoadingThread(); /// @brief TBA @@ -545,7 +543,7 @@ class AppBase : public ButtonListener, public DialogListener /// @param theValue /// @param theLength /// @return true if success - bool RegistryRead(const std::string &theValueName, JSON_RTYPE *theType, uchar *theValue, ulong *theLength); + bool RegistryRead(const std::string &theValueName, JSONRegistryType *theType, uchar *theValue, ulong *theLength); /// @brief reads a saved setting from .json /// @param theValueName /// @param theType @@ -553,7 +551,7 @@ class AppBase : public ButtonListener, public DialogListener /// @param theLength /// @param theMainKey /// @return true if success - bool RegistryReadKey(const std::string &theValueName, JSON_RTYPE *theType, uchar *theValue, ulong *theLength, + bool RegistryReadKey(const std::string &theValueName, JSONRegistryType *theType, uchar *theValue, ulong *theLength, ulong theMainKey = 0); /// @brief writes a setting to .json /// @param theValueName @@ -561,7 +559,7 @@ class AppBase : public ButtonListener, public DialogListener /// @param theValue /// @param theLength /// @return true if success - bool RegistryWrite(const std::string &theValueName, JSON_RTYPE theType, const uchar *theValue, ulong theLength); + bool RegistryWrite(const std::string &theValueName, JSONRegistryType theType, const uchar *theValue, ulong theLength); public: /// @brief constructor @@ -675,9 +673,9 @@ class AppBase : public ButtonListener, public DialogListener /// @brief initializes the app virtual void Init(); /// @brief TBA - virtual void PreSDLInterfaceInitHook(); + virtual void PreInterfaceInitHook(); /// @brief TBA - virtual void PostSDLInterfaceInitHook(); + virtual void PostInterfaceInitHook(); /// @brief change directory hook /// @param theIntendedPath /// @return true if success @@ -734,18 +732,18 @@ class AppBase : public ButtonListener, public DialogListener /// @brief sets a cursor by id /// @param theCursorNum - void SetCursor(int theCursorNum); + void SetCursor(CursorType theCursorNum); /// @brief gets the current cursor id /// @return current cursor id as int - int GetCursor(); + CursorType GetCursor(); /// @brief enables custom cursors /// @param enabled void EnableCustomCursors(bool enabled); /// @brief gets an image /// @param theFileName /// @param commitBits - /// @return SDLImage - virtual SDLImage *GetImage(const std::string &theFileName, bool commitBits = true); + /// @return GPUImage + virtual GPUImage *GetImage(const std::string &theFileName, bool commitBits = true); /// @brief gets a shared image /// @param theFileName /// @param theVariant @@ -757,22 +755,26 @@ class AppBase : public ButtonListener, public DialogListener /// @brief sets taskbar icon /// @param theFileName void SetTaskBarIcon(const std::string &theFileName); + /// @brief Update the titlebar icon + /// @param theImage + /// @return + bool UpdateWindowIcon(Image *theImage); /// @brief cleans shared images void CleanSharedImages(); /// @brief TBA /// @param theImage - void PrecacheAdditive(MemoryImage *theImage); + void PrecacheAdditive(GPUImage *theImage); /// @brief TBA /// @param theImage - void PrecacheAlpha(MemoryImage *theImage); + void PrecacheAlpha(GPUImage *theImage); /// @brief TBA /// @param theImage - void PrecacheNative(MemoryImage *theImage); + void PrecacheNative(GPUImage *theImage); /// @brief sets a cursor by id and image /// @param theCursorNum /// @param theImage - void SetCursorImage(int theCursorNum, Image *theImage); + void SetCursorImage(CursorType theCursorNum, Image *theImage); /// @brief creates a crossfade image /// @param theImage1 @@ -780,8 +782,8 @@ class AppBase : public ButtonListener, public DialogListener /// @param theImage2 /// @param theRect2 /// @param theFadeFactor - /// @return SDLImage - SDLImage *CreateCrossfadeImage(Image *theImage1, const Rect &theRect1, Image *theImage2, const Rect &theRect2, + /// @return GPUImage + GPUImage *CreateCrossfadeImage(Image *theImage1, const Rect &theRect1, Image *theImage2, const Rect &theRect2, double theFadeFactor); /// @brief TBA /// @param theImage @@ -790,17 +792,17 @@ class AppBase : public ButtonListener, public DialogListener /// @brief creates a colorized image /// @param theImage /// @param theColor - /// @return SDLImage - SDLImage *CreateColorizedImage(Image *theImage, const Color &theColor); + /// @return GPUImage + GPUImage *CreateColorizedImage(Image *theImage, const Color &theColor); /// @brief copies an image /// @param theImage /// @param theRect - /// @return SDLImage - SDLImage *CopyImage(Image *theImage, const Rect &theRect); + /// @return GPUImage + GPUImage *CopyImage(Image *theImage, const Rect &theRect); /// @brief copies an image /// @param theImage - /// @return SDLImage - SDLImage *CopyImage(Image *theImage); + /// @return GPUImage + GPUImage *CopyImage(Image *theImage); /// @brief mirrors an image /// @param theImage void MirrorImage(Image *theImage); @@ -810,7 +812,7 @@ class AppBase : public ButtonListener, public DialogListener /// @brief rotates the image's hue /// @param theImage /// @param theDelta - void RotateImageHue(PopLib::MemoryImage *theImage, int theDelta); + void RotateImageHue(PopLib::GPUImage *theImage, int theDelta); /// @brief converts HSL to RGB /// @param r /// @param g @@ -835,14 +837,14 @@ class AppBase : public ButtonListener, public DialogListener void RGBToHSL(const ulong *theSource, ulong *theDest, int theSize); /// @brief adds a memory image - /// @param theMemoryImage - void AddMemoryImage(MemoryImage *theMemoryImage); + /// @param theGPUImage + void AddGPUImage(GPUImage *theGPUImage); /// @brief removes a memory image - /// @param theMemoryImage - void RemoveMemoryImage(MemoryImage *theMemoryImage); + /// @param theGPUImage + void RemoveGPUImage(GPUImage *theGPUImage); /// @brief removes 3d data - /// @param theMemoryImage - void Remove3DData(MemoryImage *theMemoryImage); + /// @param theGPUImage + void Remove3DData(GPUImage *theGPUImage); /// @brief switches the current screenmode virtual void SwitchScreenMode(); /// @brief switches the current screenmode @@ -1036,7 +1038,7 @@ class AppBase : public ButtonListener, public DialogListener /// @param theId /// @param theValue void SetDouble(const std::string &theId, double theValue); - /// @brief sets a string by id (string, widestring) + /// @brief sets a string by id (string) /// @param theId /// @param theValue void SetString(const std::string &theId, const std::string &theValue); @@ -1138,7 +1140,7 @@ class AppBase : public ButtonListener, public DialogListener virtual bool UpdateApp(); /// @brief initializes the SDL interface /// @return int - int InitSDLInterface(); + int InitInterface(); /// @brief TBA /// @param relaxForASecond void ClearUpdateBacklog(bool relaxForASecond = false); diff --git a/PopLib/audio/aureader.hpp b/PopLib/audio/aureader.hpp index 14853b16..34d3690e 100644 --- a/PopLib/audio/aureader.hpp +++ b/PopLib/audio/aureader.hpp @@ -1,8 +1,7 @@ #ifndef __AUREADER_HPP__ #define __AUREADER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include diff --git a/PopLib/audio/bass.h b/PopLib/audio/bass.h deleted file mode 100644 index 04a046c8..00000000 --- a/PopLib/audio/bass.h +++ /dev/null @@ -1,1149 +0,0 @@ -/* - BASS 2.4 C/C++ header file - Copyright (c) 1999-2022 Un4seen Developments Ltd. - - See the BASS.CHM file for more detailed documentation -*/ - -#ifndef BASS_H -#define BASS_H - -#ifdef _WIN32 -#ifdef WINAPI_FAMILY -#include -#endif -#include -typedef unsigned __int64 QWORD; -#else -#include -#define WINAPI -#define CALLBACK -typedef uint8_t BYTE; -typedef uint16_t WORD; -typedef uint32_t DWORD; -typedef uint64_t QWORD; -#ifdef __OBJC__ -typedef int BOOL32; -#define BOOL BOOL32 // override objc's BOOL -#else -typedef int BOOL; -#endif -#ifndef TRUE -#define TRUE 1 -#define FALSE 0 -#endif -#define LOBYTE(a) (BYTE)(a) -#define HIBYTE(a) (BYTE)((a)>>8) -#define LOWORD(a) (WORD)(a) -#define HIWORD(a) (WORD)((a)>>16) -#define MAKEWORD(a,b) (WORD)(((a)&0xff)|((b)<<8)) -#define MAKELONG(a,b) (DWORD)(((a)&0xffff)|((b)<<16)) -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define BASSVERSION 0x204 // API version -#define BASSVERSIONTEXT "2.4" - -#ifndef BASSDEF -#define BASSDEF(f) WINAPI f -#else -#define NOBASSOVERLOADS -#endif - - typedef DWORD HMUSIC; // MOD music handle - typedef DWORD HSAMPLE; // sample handle - typedef DWORD HCHANNEL; // sample playback handle - typedef DWORD HSTREAM; // sample stream handle - typedef DWORD HRECORD; // recording handle - typedef DWORD HSYNC; // synchronizer handle - typedef DWORD HDSP; // DSP handle - typedef DWORD HFX; // effect handle - typedef DWORD HPLUGIN; // plugin handle - - // Error codes returned by BASS_ErrorGetCode -#define BASS_OK 0 // all is OK -#define BASS_ERROR_MEM 1 // memory error -#define BASS_ERROR_FILEOPEN 2 // can't open the file -#define BASS_ERROR_DRIVER 3 // can't find a free/valid driver -#define BASS_ERROR_BUFLOST 4 // the sample buffer was lost -#define BASS_ERROR_HANDLE 5 // invalid handle -#define BASS_ERROR_FORMAT 6 // unsupported sample format -#define BASS_ERROR_POSITION 7 // invalid position -#define BASS_ERROR_INIT 8 // BASS_Init has not been successfully called -#define BASS_ERROR_START 9 // BASS_Start has not been successfully called -#define BASS_ERROR_SSL 10 // SSL/HTTPS support isn't available -#define BASS_ERROR_REINIT 11 // device needs to be reinitialized -#define BASS_ERROR_ALREADY 14 // already initialized/paused/whatever -#define BASS_ERROR_NOTAUDIO 17 // file does not contain audio -#define BASS_ERROR_NOCHAN 18 // can't get a free channel -#define BASS_ERROR_ILLTYPE 19 // an illegal type was specified -#define BASS_ERROR_ILLPARAM 20 // an illegal parameter was specified -#define BASS_ERROR_NO3D 21 // no 3D support -#define BASS_ERROR_NOEAX 22 // no EAX support -#define BASS_ERROR_DEVICE 23 // illegal device number -#define BASS_ERROR_NOPLAY 24 // not playing -#define BASS_ERROR_FREQ 25 // illegal sample rate -#define BASS_ERROR_NOTFILE 27 // the stream is not a file stream -#define BASS_ERROR_NOHW 29 // no hardware voices available -#define BASS_ERROR_EMPTY 31 // the file has no sample data -#define BASS_ERROR_NONET 32 // no internet connection could be opened -#define BASS_ERROR_CREATE 33 // couldn't create the file -#define BASS_ERROR_NOFX 34 // effects are not available -#define BASS_ERROR_NOTAVAIL 37 // requested data/action is not available -#define BASS_ERROR_DECODE 38 // the channel is/isn't a "decoding channel" -#define BASS_ERROR_DX 39 // a sufficient DirectX version is not installed -#define BASS_ERROR_TIMEOUT 40 // connection timedout -#define BASS_ERROR_FILEFORM 41 // unsupported file format -#define BASS_ERROR_SPEAKER 42 // unavailable speaker -#define BASS_ERROR_VERSION 43 // invalid BASS version (used by add-ons) -#define BASS_ERROR_CODEC 44 // codec is not available/supported -#define BASS_ERROR_ENDED 45 // the channel/file has ended -#define BASS_ERROR_BUSY 46 // the device is busy -#define BASS_ERROR_UNSTREAMABLE 47 // unstreamable file -#define BASS_ERROR_PROTOCOL 48 // unsupported protocol -#define BASS_ERROR_DENIED 49 // access denied -#define BASS_ERROR_UNKNOWN -1 // some other mystery problem - -// BASS_SetConfig options -#define BASS_CONFIG_BUFFER 0 -#define BASS_CONFIG_UPDATEPERIOD 1 -#define BASS_CONFIG_GVOL_SAMPLE 4 -#define BASS_CONFIG_GVOL_STREAM 5 -#define BASS_CONFIG_GVOL_MUSIC 6 -#define BASS_CONFIG_CURVE_VOL 7 -#define BASS_CONFIG_CURVE_PAN 8 -#define BASS_CONFIG_FLOATDSP 9 -#define BASS_CONFIG_3DALGORITHM 10 -#define BASS_CONFIG_NET_TIMEOUT 11 -#define BASS_CONFIG_NET_BUFFER 12 -#define BASS_CONFIG_PAUSE_NOPLAY 13 -#define BASS_CONFIG_NET_PREBUF 15 -#define BASS_CONFIG_NET_PASSIVE 18 -#define BASS_CONFIG_REC_BUFFER 19 -#define BASS_CONFIG_NET_PLAYLIST 21 -#define BASS_CONFIG_MUSIC_VIRTUAL 22 -#define BASS_CONFIG_VERIFY 23 -#define BASS_CONFIG_UPDATETHREADS 24 -#define BASS_CONFIG_DEV_BUFFER 27 -#define BASS_CONFIG_REC_LOOPBACK 28 -#define BASS_CONFIG_VISTA_TRUEPOS 30 -#define BASS_CONFIG_IOS_SESSION 34 -#define BASS_CONFIG_IOS_MIXAUDIO 34 -#define BASS_CONFIG_DEV_DEFAULT 36 -#define BASS_CONFIG_NET_READTIMEOUT 37 -#define BASS_CONFIG_VISTA_SPEAKERS 38 -#define BASS_CONFIG_IOS_SPEAKER 39 -#define BASS_CONFIG_MF_DISABLE 40 -#define BASS_CONFIG_HANDLES 41 -#define BASS_CONFIG_UNICODE 42 -#define BASS_CONFIG_SRC 43 -#define BASS_CONFIG_SRC_SAMPLE 44 -#define BASS_CONFIG_ASYNCFILE_BUFFER 45 -#define BASS_CONFIG_OGG_PRESCAN 47 -#define BASS_CONFIG_MF_VIDEO 48 -#define BASS_CONFIG_AIRPLAY 49 -#define BASS_CONFIG_DEV_NONSTOP 50 -#define BASS_CONFIG_IOS_NOCATEGORY 51 -#define BASS_CONFIG_VERIFY_NET 52 -#define BASS_CONFIG_DEV_PERIOD 53 -#define BASS_CONFIG_FLOAT 54 -#define BASS_CONFIG_NET_SEEK 56 -#define BASS_CONFIG_AM_DISABLE 58 -#define BASS_CONFIG_NET_PLAYLIST_DEPTH 59 -#define BASS_CONFIG_NET_PREBUF_WAIT 60 -#define BASS_CONFIG_ANDROID_SESSIONID 62 -#define BASS_CONFIG_WASAPI_PERSIST 65 -#define BASS_CONFIG_REC_WASAPI 66 -#define BASS_CONFIG_ANDROID_AAUDIO 67 -#define BASS_CONFIG_SAMPLE_ONEHANDLE 69 -#define BASS_CONFIG_NET_META 71 -#define BASS_CONFIG_NET_RESTRATE 72 -#define BASS_CONFIG_REC_DEFAULT 73 -#define BASS_CONFIG_NORAMP 74 - -// BASS_SetConfigPtr options -#define BASS_CONFIG_NET_AGENT 16 -#define BASS_CONFIG_NET_PROXY 17 -#define BASS_CONFIG_IOS_NOTIFY 46 -#define BASS_CONFIG_ANDROID_JAVAVM 63 -#define BASS_CONFIG_LIBSSL 64 -#define BASS_CONFIG_FILENAME 75 - -#define BASS_CONFIG_THREAD 0x40000000 // flag: thread-specific setting - -// BASS_CONFIG_IOS_SESSION flags -#define BASS_IOS_SESSION_MIX 1 -#define BASS_IOS_SESSION_DUCK 2 -#define BASS_IOS_SESSION_AMBIENT 4 -#define BASS_IOS_SESSION_SPEAKER 8 -#define BASS_IOS_SESSION_DISABLE 16 -#define BASS_IOS_SESSION_DEACTIVATE 32 -#define BASS_IOS_SESSION_AIRPLAY 64 -#define BASS_IOS_SESSION_BTHFP 128 -#define BASS_IOS_SESSION_BTA2DP 0x100 - -// BASS_Init flags -#define BASS_DEVICE_8BITS 1 // unused -#define BASS_DEVICE_MONO 2 // mono -#define BASS_DEVICE_3D 4 // unused -#define BASS_DEVICE_16BITS 8 // limit output to 16-bit -#define BASS_DEVICE_REINIT 128 // reinitialize -#define BASS_DEVICE_LATENCY 0x100 // unused -#define BASS_DEVICE_CPSPEAKERS 0x400 // unused -#define BASS_DEVICE_SPEAKERS 0x800 // force enabling of speaker assignment -#define BASS_DEVICE_NOSPEAKER 0x1000 // ignore speaker arrangement -#define BASS_DEVICE_DMIX 0x2000 // use ALSA "dmix" plugin -#define BASS_DEVICE_FREQ 0x4000 // set device sample rate -#define BASS_DEVICE_STEREO 0x8000 // limit output to stereo -#define BASS_DEVICE_HOG 0x10000 // hog/exclusive mode -#define BASS_DEVICE_AUDIOTRACK 0x20000 // use AudioTrack output -#define BASS_DEVICE_DSOUND 0x40000 // use DirectSound output -#define BASS_DEVICE_SOFTWARE 0x80000 // disable hardware/fastpath output - -// DirectSound interfaces (for use with BASS_GetDSoundObject) -#define BASS_OBJECT_DS 1 // IDirectSound -#define BASS_OBJECT_DS3DL 2 // IDirectSound3DListener - -// Device info structure - typedef struct { -#if defined(_WIN32_WCE) || (defined(WINAPI_FAMILY) && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - const wchar_t* name; // description - const wchar_t* driver; // driver -#else - const char* name; // description - const char* driver; // driver -#endif - DWORD flags; - } BASS_DEVICEINFO; - - // BASS_DEVICEINFO flags -#define BASS_DEVICE_ENABLED 1 -#define BASS_DEVICE_DEFAULT 2 -#define BASS_DEVICE_INIT 4 -#define BASS_DEVICE_LOOPBACK 8 -#define BASS_DEVICE_DEFAULTCOM 128 - -#define BASS_DEVICE_TYPE_MASK 0xff000000 -#define BASS_DEVICE_TYPE_NETWORK 0x01000000 -#define BASS_DEVICE_TYPE_SPEAKERS 0x02000000 -#define BASS_DEVICE_TYPE_LINE 0x03000000 -#define BASS_DEVICE_TYPE_HEADPHONES 0x04000000 -#define BASS_DEVICE_TYPE_MICROPHONE 0x05000000 -#define BASS_DEVICE_TYPE_HEADSET 0x06000000 -#define BASS_DEVICE_TYPE_HANDSET 0x07000000 -#define BASS_DEVICE_TYPE_DIGITAL 0x08000000 -#define BASS_DEVICE_TYPE_SPDIF 0x09000000 -#define BASS_DEVICE_TYPE_HDMI 0x0a000000 -#define BASS_DEVICE_TYPE_DISPLAYPORT 0x40000000 - -// BASS_GetDeviceInfo flags -#define BASS_DEVICES_AIRPLAY 0x1000000 - - typedef struct { - DWORD flags; // device capabilities (DSCAPS_xxx flags) - DWORD hwsize; // unused - DWORD hwfree; // unused - DWORD freesam; // unused - DWORD free3d; // unused - DWORD minrate; // unused - DWORD maxrate; // unused - BOOL eax; // unused - DWORD minbuf; // recommended minimum buffer length in ms - DWORD dsver; // DirectSound version - DWORD latency; // average delay (in ms) before start of playback - DWORD initflags; // BASS_Init "flags" parameter - DWORD speakers; // number of speakers available - DWORD freq; // current output rate - } BASS_INFO; - - // BASS_INFO flags (from DSOUND.H) -#define DSCAPS_EMULDRIVER 0x00000020 // device does not have hardware DirectSound support -#define DSCAPS_CERTIFIED 0x00000040 // device driver has been certified by Microsoft - -#define DSCAPS_HARDWARE 0x80000000 // hardware mixed - -// Recording device info structure - typedef struct { - DWORD flags; // device capabilities (DSCCAPS_xxx flags) - DWORD formats; // supported standard formats (WAVE_FORMAT_xxx flags) - DWORD inputs; // number of inputs - BOOL singlein; // TRUE = only 1 input can be set at a time - DWORD freq; // current input rate - } BASS_RECORDINFO; - - // BASS_RECORDINFO flags (from DSOUND.H) -#define DSCCAPS_EMULDRIVER DSCAPS_EMULDRIVER // device does not have hardware DirectSound recording support -#define DSCCAPS_CERTIFIED DSCAPS_CERTIFIED // device driver has been certified by Microsoft - -// defines for formats field of BASS_RECORDINFO (from MMSYSTEM.H) -#ifndef WAVE_FORMAT_1M08 -#define WAVE_FORMAT_1M08 0x00000001 /* 11.025 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_1S08 0x00000002 /* 11.025 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_1M16 0x00000004 /* 11.025 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_1S16 0x00000008 /* 11.025 kHz, Stereo, 16-bit */ -#define WAVE_FORMAT_2M08 0x00000010 /* 22.05 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_2S08 0x00000020 /* 22.05 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_2M16 0x00000040 /* 22.05 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_2S16 0x00000080 /* 22.05 kHz, Stereo, 16-bit */ -#define WAVE_FORMAT_4M08 0x00000100 /* 44.1 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_4S08 0x00000200 /* 44.1 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_4M16 0x00000400 /* 44.1 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_4S16 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ -#endif - -// Sample info structure - typedef struct { - DWORD freq; // default playback rate - float volume; // default volume (0-1) - float pan; // default pan (-1=left, 0=middle, 1=right) - DWORD flags; // BASS_SAMPLE_xxx flags - DWORD length; // length (in bytes) - DWORD max; // maximum simultaneous playbacks - DWORD origres; // original resolution - DWORD chans; // number of channels - DWORD mingap; // minimum gap (ms) between creating channels - DWORD mode3d; // BASS_3DMODE_xxx mode - float mindist; // minimum distance - float maxdist; // maximum distance - DWORD iangle; // angle of inside projection cone - DWORD oangle; // angle of outside projection cone - float outvol; // delta-volume outside the projection cone - DWORD vam; // unused - DWORD priority; // unused - } BASS_SAMPLE; - -#define BASS_SAMPLE_8BITS 1 // 8 bit -#define BASS_SAMPLE_FLOAT 256 // 32 bit floating-point -#define BASS_SAMPLE_MONO 2 // mono -#define BASS_SAMPLE_LOOP 4 // looped -#define BASS_SAMPLE_3D 8 // 3D functionality -#define BASS_SAMPLE_SOFTWARE 16 // unused -#define BASS_SAMPLE_MUTEMAX 32 // mute at max distance (3D only) -#define BASS_SAMPLE_VAM 64 // unused -#define BASS_SAMPLE_FX 128 // unused -#define BASS_SAMPLE_OVER_VOL 0x10000 // override lowest volume -#define BASS_SAMPLE_OVER_POS 0x20000 // override longest playing -#define BASS_SAMPLE_OVER_DIST 0x30000 // override furthest from listener (3D only) - -#define BASS_STREAM_PRESCAN 0x20000 // scan file for accurate seeking and length -#define BASS_STREAM_AUTOFREE 0x40000 // automatically free the stream when it stops/ends -#define BASS_STREAM_RESTRATE 0x80000 // restrict the download rate of internet file stream -#define BASS_STREAM_BLOCK 0x100000 // download internet file stream in small blocks -#define BASS_STREAM_DECODE 0x200000 // don't play the stream, only decode -#define BASS_STREAM_STATUS 0x800000 // give server status info (HTTP/ICY tags) in DOWNLOADPROC - -#define BASS_MP3_IGNOREDELAY 0x200 // ignore LAME/Xing/VBRI/iTunes delay & padding info -#define BASS_MP3_SETPOS BASS_STREAM_PRESCAN - -#define BASS_MUSIC_FLOAT BASS_SAMPLE_FLOAT -#define BASS_MUSIC_MONO BASS_SAMPLE_MONO -#define BASS_MUSIC_LOOP BASS_SAMPLE_LOOP -#define BASS_MUSIC_3D BASS_SAMPLE_3D -#define BASS_MUSIC_FX BASS_SAMPLE_FX -#define BASS_MUSIC_AUTOFREE BASS_STREAM_AUTOFREE -#define BASS_MUSIC_DECODE BASS_STREAM_DECODE -#define BASS_MUSIC_PRESCAN BASS_STREAM_PRESCAN // calculate playback length -#define BASS_MUSIC_CALCLEN BASS_MUSIC_PRESCAN -#define BASS_MUSIC_RAMP 0x200 // normal ramping -#define BASS_MUSIC_RAMPS 0x400 // sensitive ramping -#define BASS_MUSIC_SURROUND 0x800 // surround sound -#define BASS_MUSIC_SURROUND2 0x1000 // surround sound (mode 2) -#define BASS_MUSIC_FT2PAN 0x2000 // apply FastTracker 2 panning to XM files -#define BASS_MUSIC_FT2MOD 0x2000 // play .MOD as FastTracker 2 does -#define BASS_MUSIC_PT1MOD 0x4000 // play .MOD as ProTracker 1 does -#define BASS_MUSIC_NONINTER 0x10000 // non-interpolated sample mixing -#define BASS_MUSIC_SINCINTER 0x800000 // sinc interpolated sample mixing -#define BASS_MUSIC_POSRESET 0x8000 // stop all notes when moving position -#define BASS_MUSIC_POSRESETEX 0x400000 // stop all notes and reset bmp/etc when moving position -#define BASS_MUSIC_STOPBACK 0x80000 // stop the music on a backwards jump effect -#define BASS_MUSIC_NOSAMPLE 0x100000 // don't load the samples - - // Speaker assignment flags -#define BASS_SPEAKER_FRONT 0x1000000 // front speakers -#define BASS_SPEAKER_REAR 0x2000000 // rear speakers -#define BASS_SPEAKER_CENLFE 0x3000000 // center & LFE speakers (5.1) -#define BASS_SPEAKER_SIDE 0x4000000 // side speakers (7.1) -#define BASS_SPEAKER_N(n) ((n)<<24) // n'th pair of speakers (max 15) -#define BASS_SPEAKER_LEFT 0x10000000 // modifier: left -#define BASS_SPEAKER_RIGHT 0x20000000 // modifier: right -#define BASS_SPEAKER_FRONTLEFT BASS_SPEAKER_FRONT | BASS_SPEAKER_LEFT -#define BASS_SPEAKER_FRONTRIGHT BASS_SPEAKER_FRONT | BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_REARLEFT BASS_SPEAKER_REAR | BASS_SPEAKER_LEFT -#define BASS_SPEAKER_REARRIGHT BASS_SPEAKER_REAR | BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_CENTER BASS_SPEAKER_CENLFE | BASS_SPEAKER_LEFT -#define BASS_SPEAKER_LFE BASS_SPEAKER_CENLFE | BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_SIDELEFT BASS_SPEAKER_SIDE | BASS_SPEAKER_LEFT -#define BASS_SPEAKER_SIDERIGHT BASS_SPEAKER_SIDE | BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_REAR2 BASS_SPEAKER_SIDE -#define BASS_SPEAKER_REAR2LEFT BASS_SPEAKER_SIDELEFT -#define BASS_SPEAKER_REAR2RIGHT BASS_SPEAKER_SIDERIGHT - -#define BASS_ASYNCFILE 0x40000000 // read file asynchronously -#define BASS_UNICODE 0x80000000 // UTF-16 - -#define BASS_RECORD_ECHOCANCEL 0x2000 -#define BASS_RECORD_AGC 0x4000 -#define BASS_RECORD_PAUSE 0x8000 // start recording paused - -// DX7 voice allocation & management flags -#define BASS_VAM_HARDWARE 1 -#define BASS_VAM_SOFTWARE 2 -#define BASS_VAM_TERM_TIME 4 -#define BASS_VAM_TERM_DIST 8 -#define BASS_VAM_TERM_PRIO 16 - -// Channel info structure - typedef struct { - DWORD freq; // default playback rate - DWORD chans; // channels - DWORD flags; - DWORD ctype; // type of channel - DWORD origres; // original resolution - HPLUGIN plugin; - HSAMPLE sample; - const char* filename; - } BASS_CHANNELINFO; - -#define BASS_ORIGRES_FLOAT 0x10000 - - // BASS_CHANNELINFO types -#define BASS_CTYPE_SAMPLE 1 -#define BASS_CTYPE_RECORD 2 -#define BASS_CTYPE_STREAM 0x10000 -#define BASS_CTYPE_STREAM_VORBIS 0x10002 -#define BASS_CTYPE_STREAM_OGG 0x10002 -#define BASS_CTYPE_STREAM_MP1 0x10003 -#define BASS_CTYPE_STREAM_MP2 0x10004 -#define BASS_CTYPE_STREAM_MP3 0x10005 -#define BASS_CTYPE_STREAM_AIFF 0x10006 -#define BASS_CTYPE_STREAM_CA 0x10007 -#define BASS_CTYPE_STREAM_MF 0x10008 -#define BASS_CTYPE_STREAM_AM 0x10009 -#define BASS_CTYPE_STREAM_SAMPLE 0x1000a -#define BASS_CTYPE_STREAM_DUMMY 0x18000 -#define BASS_CTYPE_STREAM_DEVICE 0x18001 -#define BASS_CTYPE_STREAM_WAV 0x40000 // WAVE flag (LOWORD=codec) -#define BASS_CTYPE_STREAM_WAV_PCM 0x50001 -#define BASS_CTYPE_STREAM_WAV_FLOAT 0x50003 -#define BASS_CTYPE_MUSIC_MOD 0x20000 -#define BASS_CTYPE_MUSIC_MTM 0x20001 -#define BASS_CTYPE_MUSIC_S3M 0x20002 -#define BASS_CTYPE_MUSIC_XM 0x20003 -#define BASS_CTYPE_MUSIC_IT 0x20004 -#define BASS_CTYPE_MUSIC_MO3 0x00100 // MO3 flag - -// BASS_PluginLoad flags -#define BASS_PLUGIN_PROC 1 - - typedef struct { - DWORD ctype; // channel type -#if defined(_WIN32_WCE) || (defined(WINAPI_FAMILY) && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - const wchar_t* name; // format description - const wchar_t* exts; // file extension filter (*.ext1;*.ext2;etc...) -#else - const char* name; // format description - const char* exts; // file extension filter (*.ext1;*.ext2;etc...) -#endif - } BASS_PLUGINFORM; - - typedef struct { - DWORD version; // version (same form as BASS_GetVersion) - DWORD formatc; // number of formats - const BASS_PLUGINFORM* formats; // the array of formats - } BASS_PLUGININFO; - - // 3D vector (for 3D positions/velocities/orientations) - typedef struct BASS_3DVECTOR { -#ifdef __cplusplus - BASS_3DVECTOR() {} - BASS_3DVECTOR(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {} -#endif - float x; // +=right, -=left - float y; // +=up, -=down - float z; // +=front, -=behind - } BASS_3DVECTOR; - - // 3D channel modes -#define BASS_3DMODE_NORMAL 0 // normal 3D processing -#define BASS_3DMODE_RELATIVE 1 // position is relative to the listener -#define BASS_3DMODE_OFF 2 // no 3D processing - -// software 3D mixing algorithms (used with BASS_CONFIG_3DALGORITHM) -#define BASS_3DALG_DEFAULT 0 -#define BASS_3DALG_OFF 1 -#define BASS_3DALG_FULL 2 -#define BASS_3DALG_LIGHT 3 - -// BASS_SampleGetChannel flags -#define BASS_SAMCHAN_NEW 1 // get a new playback channel -#define BASS_SAMCHAN_STREAM 2 // create a stream - - typedef DWORD(CALLBACK STREAMPROC)(HSTREAM handle, void* buffer, DWORD length, void* user); - /* User stream callback function. - handle : The stream that needs writing - buffer : Buffer to write the samples in - length : Number of bytes to write - user : The 'user' parameter value given when calling BASS_StreamCreate - RETURN : Number of bytes written. Set the BASS_STREAMPROC_END flag to end the stream. */ - -#define BASS_STREAMPROC_END 0x80000000 // end of user stream flag - - // Special STREAMPROCs -#define STREAMPROC_DUMMY (STREAMPROC*)0 // "dummy" stream -#define STREAMPROC_PUSH (STREAMPROC*)-1 // push stream -#define STREAMPROC_DEVICE (STREAMPROC*)-2 // device mix stream -#define STREAMPROC_DEVICE_3D (STREAMPROC*)-3 // device 3D mix stream - -// BASS_StreamCreateFileUser file systems -#define STREAMFILE_NOBUFFER 0 -#define STREAMFILE_BUFFER 1 -#define STREAMFILE_BUFFERPUSH 2 - -// User file stream callback functions - typedef void (CALLBACK FILECLOSEPROC)(void* user); - typedef QWORD(CALLBACK FILELENPROC)(void* user); - typedef DWORD(CALLBACK FILEREADPROC)(void* buffer, DWORD length, void* user); - typedef BOOL(CALLBACK FILESEEKPROC)(QWORD offset, void* user); - - typedef struct { - FILECLOSEPROC* close; - FILELENPROC* length; - FILEREADPROC* read; - FILESEEKPROC* seek; - } BASS_FILEPROCS; - - // BASS_StreamPutFileData options -#define BASS_FILEDATA_END 0 // end & close the file - -// BASS_StreamGetFilePosition modes -#define BASS_FILEPOS_CURRENT 0 -#define BASS_FILEPOS_DECODE BASS_FILEPOS_CURRENT -#define BASS_FILEPOS_DOWNLOAD 1 -#define BASS_FILEPOS_END 2 -#define BASS_FILEPOS_START 3 -#define BASS_FILEPOS_CONNECTED 4 -#define BASS_FILEPOS_BUFFER 5 -#define BASS_FILEPOS_SOCKET 6 -#define BASS_FILEPOS_ASYNCBUF 7 -#define BASS_FILEPOS_SIZE 8 -#define BASS_FILEPOS_BUFFERING 9 -#define BASS_FILEPOS_AVAILABLE 10 - - typedef void (CALLBACK DOWNLOADPROC)(const void* buffer, DWORD length, void* user); - /* Internet stream download callback function. - buffer : Buffer containing the downloaded data... NULL=end of download - length : Number of bytes in the buffer - user : The 'user' parameter value given when calling BASS_StreamCreateURL */ - - // BASS_ChannelSetSync types -#define BASS_SYNC_POS 0 -#define BASS_SYNC_END 2 -#define BASS_SYNC_META 4 -#define BASS_SYNC_SLIDE 5 -#define BASS_SYNC_STALL 6 -#define BASS_SYNC_DOWNLOAD 7 -#define BASS_SYNC_FREE 8 -#define BASS_SYNC_SETPOS 11 -#define BASS_SYNC_MUSICPOS 10 -#define BASS_SYNC_MUSICINST 1 -#define BASS_SYNC_MUSICFX 3 -#define BASS_SYNC_OGG_CHANGE 12 -#define BASS_SYNC_DEV_FAIL 14 -#define BASS_SYNC_DEV_FORMAT 15 -#define BASS_SYNC_THREAD 0x20000000 // flag: call sync in other thread -#define BASS_SYNC_MIXTIME 0x40000000 // flag: sync at mixtime, else at playtime -#define BASS_SYNC_ONETIME 0x80000000 // flag: sync only once, else continuously - - typedef void (CALLBACK SYNCPROC)(HSYNC handle, DWORD channel, DWORD data, void* user); - /* Sync callback function. - handle : The sync that has occured - channel: Channel that the sync occured in - data : Additional data associated with the sync's occurance - user : The 'user' parameter given when calling BASS_ChannelSetSync */ - - typedef void (CALLBACK DSPPROC)(HDSP handle, DWORD channel, void* buffer, DWORD length, void* user); - /* DSP callback function. - handle : The DSP handle - channel: Channel that the DSP is being applied to - buffer : Buffer to apply the DSP to - length : Number of bytes in the buffer - user : The 'user' parameter given when calling BASS_ChannelSetDSP */ - - typedef BOOL(CALLBACK RECORDPROC)(HRECORD handle, const void* buffer, DWORD length, void* user); - /* Recording callback function. - handle : The recording handle - buffer : Buffer containing the recorded sample data - length : Number of bytes - user : The 'user' parameter value given when calling BASS_RecordStart - RETURN : TRUE = continue recording, FALSE = stop */ - - // BASS_ChannelIsActive return values -#define BASS_ACTIVE_STOPPED 0 -#define BASS_ACTIVE_PLAYING 1 -#define BASS_ACTIVE_STALLED 2 -#define BASS_ACTIVE_PAUSED 3 -#define BASS_ACTIVE_PAUSED_DEVICE 4 - -// Channel attributes -#define BASS_ATTRIB_FREQ 1 -#define BASS_ATTRIB_VOL 2 -#define BASS_ATTRIB_PAN 3 -#define BASS_ATTRIB_EAXMIX 4 -#define BASS_ATTRIB_NOBUFFER 5 -#define BASS_ATTRIB_VBR 6 -#define BASS_ATTRIB_CPU 7 -#define BASS_ATTRIB_SRC 8 -#define BASS_ATTRIB_NET_RESUME 9 -#define BASS_ATTRIB_SCANINFO 10 -#define BASS_ATTRIB_NORAMP 11 -#define BASS_ATTRIB_BITRATE 12 -#define BASS_ATTRIB_BUFFER 13 -#define BASS_ATTRIB_GRANULE 14 -#define BASS_ATTRIB_USER 15 -#define BASS_ATTRIB_TAIL 16 -#define BASS_ATTRIB_PUSH_LIMIT 17 -#define BASS_ATTRIB_DOWNLOADPROC 18 -#define BASS_ATTRIB_VOLDSP 19 -#define BASS_ATTRIB_VOLDSP_PRIORITY 20 -#define BASS_ATTRIB_MUSIC_AMPLIFY 0x100 -#define BASS_ATTRIB_MUSIC_PANSEP 0x101 -#define BASS_ATTRIB_MUSIC_PSCALER 0x102 -#define BASS_ATTRIB_MUSIC_BPM 0x103 -#define BASS_ATTRIB_MUSIC_SPEED 0x104 -#define BASS_ATTRIB_MUSIC_VOL_GLOBAL 0x105 -#define BASS_ATTRIB_MUSIC_ACTIVE 0x106 -#define BASS_ATTRIB_MUSIC_VOL_CHAN 0x200 // + channel # -#define BASS_ATTRIB_MUSIC_VOL_INST 0x300 // + instrument # - -// BASS_ChannelSlideAttribute flags -#define BASS_SLIDE_LOG 0x1000000 - -// BASS_ChannelGetData flags -#define BASS_DATA_AVAILABLE 0 // query how much data is buffered -#define BASS_DATA_NOREMOVE 0x10000000 // flag: don't remove data from recording buffer -#define BASS_DATA_FIXED 0x20000000 // unused -#define BASS_DATA_FLOAT 0x40000000 // flag: return floating-point sample data -#define BASS_DATA_FFT256 0x80000000 // 256 sample FFT -#define BASS_DATA_FFT512 0x80000001 // 512 FFT -#define BASS_DATA_FFT1024 0x80000002 // 1024 FFT -#define BASS_DATA_FFT2048 0x80000003 // 2048 FFT -#define BASS_DATA_FFT4096 0x80000004 // 4096 FFT -#define BASS_DATA_FFT8192 0x80000005 // 8192 FFT -#define BASS_DATA_FFT16384 0x80000006 // 16384 FFT -#define BASS_DATA_FFT32768 0x80000007 // 32768 FFT -#define BASS_DATA_FFT_INDIVIDUAL 0x10 // FFT flag: FFT for each channel, else all combined -#define BASS_DATA_FFT_NOWINDOW 0x20 // FFT flag: no Hanning window -#define BASS_DATA_FFT_REMOVEDC 0x40 // FFT flag: pre-remove DC bias -#define BASS_DATA_FFT_COMPLEX 0x80 // FFT flag: return complex data -#define BASS_DATA_FFT_NYQUIST 0x100 // FFT flag: return extra Nyquist value - -// BASS_ChannelGetLevelEx flags -#define BASS_LEVEL_MONO 1 // get mono level -#define BASS_LEVEL_STEREO 2 // get stereo level -#define BASS_LEVEL_RMS 4 // get RMS levels -#define BASS_LEVEL_VOLPAN 8 // apply VOL/PAN attributes to the levels -#define BASS_LEVEL_NOREMOVE 16 // don't remove data from recording buffer - -// BASS_ChannelGetTags types : what's returned -#define BASS_TAG_ID3 0 // ID3v1 tags : TAG_ID3 structure -#define BASS_TAG_ID3V2 1 // ID3v2 tags : variable length block -#define BASS_TAG_OGG 2 // OGG comments : series of null-terminated UTF-8 strings -#define BASS_TAG_HTTP 3 // HTTP headers : series of null-terminated ASCII strings -#define BASS_TAG_ICY 4 // ICY headers : series of null-terminated ANSI strings -#define BASS_TAG_META 5 // ICY metadata : ANSI string -#define BASS_TAG_APE 6 // APE tags : series of null-terminated UTF-8 strings -#define BASS_TAG_MP4 7 // MP4/iTunes metadata : series of null-terminated UTF-8 strings -#define BASS_TAG_WMA 8 // WMA tags : series of null-terminated UTF-8 strings -#define BASS_TAG_VENDOR 9 // OGG encoder : UTF-8 string -#define BASS_TAG_LYRICS3 10 // Lyric3v2 tag : ASCII string -#define BASS_TAG_CA_CODEC 11 // CoreAudio codec info : TAG_CA_CODEC structure -#define BASS_TAG_MF 13 // Media Foundation tags : series of null-terminated UTF-8 strings -#define BASS_TAG_WAVEFORMAT 14 // WAVE format : WAVEFORMATEEX structure -#define BASS_TAG_AM_NAME 16 // Android Media codec name : ASCII string -#define BASS_TAG_ID3V2_2 17 // ID3v2 tags (2nd block) : variable length block -#define BASS_TAG_AM_MIME 18 // Android Media MIME type : ASCII string -#define BASS_TAG_LOCATION 19 // redirected URL : ASCII string -#define BASS_TAG_RIFF_INFO 0x100 // RIFF "INFO" tags : series of null-terminated ANSI strings -#define BASS_TAG_RIFF_BEXT 0x101 // RIFF/BWF "bext" tags : TAG_BEXT structure -#define BASS_TAG_RIFF_CART 0x102 // RIFF/BWF "cart" tags : TAG_CART structure -#define BASS_TAG_RIFF_DISP 0x103 // RIFF "DISP" text tag : ANSI string -#define BASS_TAG_RIFF_CUE 0x104 // RIFF "cue " chunk : TAG_CUE structure -#define BASS_TAG_RIFF_SMPL 0x105 // RIFF "smpl" chunk : TAG_SMPL structure -#define BASS_TAG_APE_BINARY 0x1000 // + index #, binary APE tag : TAG_APE_BINARY structure -#define BASS_TAG_MUSIC_NAME 0x10000 // MOD music name : ANSI string -#define BASS_TAG_MUSIC_MESSAGE 0x10001 // MOD message : ANSI string -#define BASS_TAG_MUSIC_ORDERS 0x10002 // MOD order list : BYTE array of pattern numbers -#define BASS_TAG_MUSIC_AUTH 0x10003 // MOD author : UTF-8 string -#define BASS_TAG_MUSIC_INST 0x10100 // + instrument #, MOD instrument name : ANSI string -#define BASS_TAG_MUSIC_CHAN 0x10200 // + channel #, MOD channel name : ANSI string -#define BASS_TAG_MUSIC_SAMPLE 0x10300 // + sample #, MOD sample name : ANSI string - -// ID3v1 tag structure - typedef struct { - char id[3]; - char title[30]; - char artist[30]; - char album[30]; - char year[4]; - char comment[30]; - BYTE genre; - } TAG_ID3; - - // Binary APE tag structure - typedef struct { - const char* key; - const void* data; - DWORD length; - } TAG_APE_BINARY; - - // BWF "bext" tag structure -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4200) -#endif -#pragma pack(push,1) - typedef struct { - char Description[256]; // description - char Originator[32]; // name of the originator - char OriginatorReference[32]; // reference of the originator - char OriginationDate[10]; // date of creation (yyyy-mm-dd) - char OriginationTime[8]; // time of creation (hh-mm-ss) - QWORD TimeReference; // first sample count since midnight (little-endian) - WORD Version; // BWF version (little-endian) - BYTE UMID[64]; // SMPTE UMID - BYTE Reserved[190]; -#if defined(__GNUC__) && __GNUC__<3 - char CodingHistory[0]; // history -#elif 1 // change to 0 if compiler fails the following line - char CodingHistory[]; // history -#else - char CodingHistory[1]; // history -#endif - } TAG_BEXT; -#pragma pack(pop) - - // BWF "cart" tag structures - typedef struct - { - DWORD dwUsage; // FOURCC timer usage ID - DWORD dwValue; // timer value in samples from head - } TAG_CART_TIMER; - - typedef struct - { - char Version[4]; // version of the data structure - char Title[64]; // title of cart audio sequence - char Artist[64]; // artist or creator name - char CutID[64]; // cut number identification - char ClientID[64]; // client identification - char Category[64]; // category ID, PSA, NEWS, etc - char Classification[64]; // classification or auxiliary key - char OutCue[64]; // out cue text - char StartDate[10]; // yyyy-mm-dd - char StartTime[8]; // hh:mm:ss - char EndDate[10]; // yyyy-mm-dd - char EndTime[8]; // hh:mm:ss - char ProducerAppID[64]; // name of vendor or application - char ProducerAppVersion[64]; // version of producer application - char UserDef[64]; // user defined text - DWORD dwLevelReference; // sample value for 0 dB reference - TAG_CART_TIMER PostTimer[8]; // 8 time markers after head - char Reserved[276]; - char URL[1024]; // uniform resource locator -#if defined(__GNUC__) && __GNUC__<3 - char TagText[0]; // free form text for scripts or tags -#elif 1 // change to 0 if compiler fails the following line - char TagText[]; // free form text for scripts or tags -#else - char TagText[1]; // free form text for scripts or tags -#endif - } TAG_CART; - - // RIFF "cue " tag structures - typedef struct - { - DWORD dwName; - DWORD dwPosition; - DWORD fccChunk; - DWORD dwChunkStart; - DWORD dwBlockStart; - DWORD dwSampleOffset; - } TAG_CUE_POINT; - - typedef struct - { - DWORD dwCuePoints; -#if defined(__GNUC__) && __GNUC__<3 - TAG_CUE_POINT CuePoints[0]; -#elif 1 // change to 0 if compiler fails the following line - TAG_CUE_POINT CuePoints[]; -#else - TAG_CUE_POINT CuePoints[1]; -#endif - } TAG_CUE; - - // RIFF "smpl" tag structures - typedef struct - { - DWORD dwIdentifier; - DWORD dwType; - DWORD dwStart; - DWORD dwEnd; - DWORD dwFraction; - DWORD dwPlayCount; - } TAG_SMPL_LOOP; - - typedef struct - { - DWORD dwManufacturer; - DWORD dwProduct; - DWORD dwSamplePeriod; - DWORD dwMIDIUnityNote; - DWORD dwMIDIPitchFraction; - DWORD dwSMPTEFormat; - DWORD dwSMPTEOffset; - DWORD cSampleLoops; - DWORD cbSamplerData; -#if defined(__GNUC__) && __GNUC__<3 - TAG_SMPL_LOOP SampleLoops[0]; -#elif 1 // change to 0 if compiler fails the following line - TAG_SMPL_LOOP SampleLoops[]; -#else - TAG_SMPL_LOOP SampleLoops[1]; -#endif - } TAG_SMPL; -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - // CoreAudio codec info structure - typedef struct { - DWORD ftype; // file format - DWORD atype; // audio format - const char* name; // description - } TAG_CA_CODEC; - -#ifndef _WAVEFORMATEX_ -#define _WAVEFORMATEX_ -#pragma pack(push,1) - typedef struct tWAVEFORMATEX - { - WORD wFormatTag; - WORD nChannels; - DWORD nSamplesPerSec; - DWORD nAvgBytesPerSec; - WORD nBlockAlign; - WORD wBitsPerSample; - WORD cbSize; - } WAVEFORMATEX, * PWAVEFORMATEX, * LPWAVEFORMATEX; - typedef const WAVEFORMATEX* LPCWAVEFORMATEX; -#pragma pack(pop) -#endif - - // BASS_ChannelGetLength/GetPosition/SetPosition modes -#define BASS_POS_BYTE 0 // byte position -#define BASS_POS_MUSIC_ORDER 1 // order.row position, MAKELONG(order,row) -#define BASS_POS_OGG 3 // OGG bitstream number -#define BASS_POS_END 0x10 // trimmed end position -#define BASS_POS_LOOP 0x11 // loop start positiom -#define BASS_POS_FLUSH 0x1000000 // flag: flush decoder/FX buffers -#define BASS_POS_RESET 0x2000000 // flag: reset user file buffers -#define BASS_POS_RELATIVE 0x4000000 // flag: seek relative to the current position -#define BASS_POS_INEXACT 0x8000000 // flag: allow seeking to inexact position -#define BASS_POS_DECODE 0x10000000 // flag: get the decoding (not playing) position -#define BASS_POS_DECODETO 0x20000000 // flag: decode to the position instead of seeking -#define BASS_POS_SCAN 0x40000000 // flag: scan to the position - -// BASS_ChannelSetDevice/GetDevice option -#define BASS_NODEVICE 0x20000 - -// BASS_RecordSetInput flags -#define BASS_INPUT_OFF 0x10000 -#define BASS_INPUT_ON 0x20000 - -#define BASS_INPUT_TYPE_MASK 0xff000000 -#define BASS_INPUT_TYPE_UNDEF 0x00000000 -#define BASS_INPUT_TYPE_DIGITAL 0x01000000 -#define BASS_INPUT_TYPE_LINE 0x02000000 -#define BASS_INPUT_TYPE_MIC 0x03000000 -#define BASS_INPUT_TYPE_SYNTH 0x04000000 -#define BASS_INPUT_TYPE_CD 0x05000000 -#define BASS_INPUT_TYPE_PHONE 0x06000000 -#define BASS_INPUT_TYPE_SPEAKER 0x07000000 -#define BASS_INPUT_TYPE_WAVE 0x08000000 -#define BASS_INPUT_TYPE_AUX 0x09000000 -#define BASS_INPUT_TYPE_ANALOG 0x0a000000 - -// BASS_ChannelSetFX effect types -#define BASS_FX_DX8_CHORUS 0 -#define BASS_FX_DX8_COMPRESSOR 1 -#define BASS_FX_DX8_DISTORTION 2 -#define BASS_FX_DX8_ECHO 3 -#define BASS_FX_DX8_FLANGER 4 -#define BASS_FX_DX8_GARGLE 5 -#define BASS_FX_DX8_I3DL2REVERB 6 -#define BASS_FX_DX8_PARAMEQ 7 -#define BASS_FX_DX8_REVERB 8 -#define BASS_FX_VOLUME 9 - - typedef struct { - float fWetDryMix; - float fDepth; - float fFeedback; - float fFrequency; - DWORD lWaveform; // 0=triangle, 1=sine - float fDelay; - DWORD lPhase; // BASS_DX8_PHASE_xxx - } BASS_DX8_CHORUS; - - typedef struct { - float fGain; - float fAttack; - float fRelease; - float fThreshold; - float fRatio; - float fPredelay; - } BASS_DX8_COMPRESSOR; - - typedef struct { - float fGain; - float fEdge; - float fPostEQCenterFrequency; - float fPostEQBandwidth; - float fPreLowpassCutoff; - } BASS_DX8_DISTORTION; - - typedef struct { - float fWetDryMix; - float fFeedback; - float fLeftDelay; - float fRightDelay; - BOOL lPanDelay; - } BASS_DX8_ECHO; - - typedef struct { - float fWetDryMix; - float fDepth; - float fFeedback; - float fFrequency; - DWORD lWaveform; // 0=triangle, 1=sine - float fDelay; - DWORD lPhase; // BASS_DX8_PHASE_xxx - } BASS_DX8_FLANGER; - - typedef struct { - DWORD dwRateHz; // Rate of modulation in hz - DWORD dwWaveShape; // 0=triangle, 1=square - } BASS_DX8_GARGLE; - - typedef struct { - int lRoom; // [-10000, 0] default: -1000 mB - int lRoomHF; // [-10000, 0] default: 0 mB - float flRoomRolloffFactor; // [0.0, 10.0] default: 0.0 - float flDecayTime; // [0.1, 20.0] default: 1.49s - float flDecayHFRatio; // [0.1, 2.0] default: 0.83 - int lReflections; // [-10000, 1000] default: -2602 mB - float flReflectionsDelay; // [0.0, 0.3] default: 0.007 s - int lReverb; // [-10000, 2000] default: 200 mB - float flReverbDelay; // [0.0, 0.1] default: 0.011 s - float flDiffusion; // [0.0, 100.0] default: 100.0 % - float flDensity; // [0.0, 100.0] default: 100.0 % - float flHFReference; // [20.0, 20000.0] default: 5000.0 Hz - } BASS_DX8_I3DL2REVERB; - - typedef struct { - float fCenter; - float fBandwidth; - float fGain; - } BASS_DX8_PARAMEQ; - - typedef struct { - float fInGain; // [-96.0,0.0] default: 0.0 dB - float fReverbMix; // [-96.0,0.0] default: 0.0 db - float fReverbTime; // [0.001,3000.0] default: 1000.0 ms - float fHighFreqRTRatio; // [0.001,0.999] default: 0.001 - } BASS_DX8_REVERB; - -#define BASS_DX8_PHASE_NEG_180 0 -#define BASS_DX8_PHASE_NEG_90 1 -#define BASS_DX8_PHASE_ZERO 2 -#define BASS_DX8_PHASE_90 3 -#define BASS_DX8_PHASE_180 4 - - typedef struct { - float fTarget; - float fCurrent; - float fTime; - DWORD lCurve; - } BASS_FX_VOLUME_PARAM; - - typedef void (CALLBACK IOSNOTIFYPROC)(DWORD status); - /* iOS notification callback function. - status : The notification (BASS_IOSNOTIFY_xxx) */ - -#define BASS_IOSNOTIFY_INTERRUPT 1 // interruption started -#define BASS_IOSNOTIFY_INTERRUPT_END 2 // interruption ended - - BOOL BASSDEF(BASS_SetConfig)(DWORD option, DWORD value); - DWORD BASSDEF(BASS_GetConfig)(DWORD option); - BOOL BASSDEF(BASS_SetConfigPtr)(DWORD option, const void* value); - const void* BASSDEF(BASS_GetConfigPtr)(DWORD option); - DWORD BASSDEF(BASS_GetVersion)(void); - int BASSDEF(BASS_ErrorGetCode)(void); - - BOOL BASSDEF(BASS_GetDeviceInfo)(DWORD device, BASS_DEVICEINFO* info); -#if defined(_WIN32) && !defined(_WIN32_WCE) && !(defined(WINAPI_FAMILY) && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, HWND win, const void* dsguid); -#else - BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, void* win, const void* dsguid); -#endif - BOOL BASSDEF(BASS_Free)(void); - BOOL BASSDEF(BASS_SetDevice)(DWORD device); - DWORD BASSDEF(BASS_GetDevice)(void); - BOOL BASSDEF(BASS_GetInfo)(BASS_INFO* info); - BOOL BASSDEF(BASS_Start)(void); - BOOL BASSDEF(BASS_Stop)(void); - BOOL BASSDEF(BASS_Pause)(void); - DWORD BASSDEF(BASS_IsStarted)(void); - BOOL BASSDEF(BASS_Update)(DWORD length); - float BASSDEF(BASS_GetCPU)(void); - BOOL BASSDEF(BASS_SetVolume)(float volume); - float BASSDEF(BASS_GetVolume)(void); -#if defined(_WIN32) && !defined(_WIN32_WCE) && !(defined(WINAPI_FAMILY) && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - void* BASSDEF(BASS_GetDSoundObject)(DWORD object); -#endif - - BOOL BASSDEF(BASS_Set3DFactors)(float distf, float rollf, float doppf); - BOOL BASSDEF(BASS_Get3DFactors)(float* distf, float* rollf, float* doppf); - BOOL BASSDEF(BASS_Set3DPosition)(const BASS_3DVECTOR* pos, const BASS_3DVECTOR* vel, const BASS_3DVECTOR* front, const BASS_3DVECTOR* top); - BOOL BASSDEF(BASS_Get3DPosition)(BASS_3DVECTOR* pos, BASS_3DVECTOR* vel, BASS_3DVECTOR* front, BASS_3DVECTOR* top); - void BASSDEF(BASS_Apply3D)(void); - - HPLUGIN BASSDEF(BASS_PluginLoad)(const char* file, DWORD flags); - BOOL BASSDEF(BASS_PluginFree)(HPLUGIN handle); - BOOL BASSDEF(BASS_PluginEnable)(HPLUGIN handle, BOOL enable); - const BASS_PLUGININFO* BASSDEF(BASS_PluginGetInfo)(HPLUGIN handle); - - HSAMPLE BASSDEF(BASS_SampleLoad)(BOOL mem, const void* file, QWORD offset, DWORD length, DWORD max, DWORD flags); - HSAMPLE BASSDEF(BASS_SampleCreate)(DWORD length, DWORD freq, DWORD chans, DWORD max, DWORD flags); - BOOL BASSDEF(BASS_SampleFree)(HSAMPLE handle); - BOOL BASSDEF(BASS_SampleSetData)(HSAMPLE handle, const void* buffer); - BOOL BASSDEF(BASS_SampleGetData)(HSAMPLE handle, void* buffer); - BOOL BASSDEF(BASS_SampleGetInfo)(HSAMPLE handle, BASS_SAMPLE* info); - BOOL BASSDEF(BASS_SampleSetInfo)(HSAMPLE handle, const BASS_SAMPLE* info); - DWORD BASSDEF(BASS_SampleGetChannel)(HSAMPLE handle, DWORD flags); - DWORD BASSDEF(BASS_SampleGetChannels)(HSAMPLE handle, HCHANNEL* channels); - BOOL BASSDEF(BASS_SampleStop)(HSAMPLE handle); - - HSTREAM BASSDEF(BASS_StreamCreate)(DWORD freq, DWORD chans, DWORD flags, STREAMPROC* proc, void* user); - HSTREAM BASSDEF(BASS_StreamCreateFile)(BOOL mem, const void* file, QWORD offset, QWORD length, DWORD flags); - HSTREAM BASSDEF(BASS_StreamCreateURL)(const char* url, DWORD offset, DWORD flags, DOWNLOADPROC* proc, void* user); - HSTREAM BASSDEF(BASS_StreamCreateFileUser)(DWORD system, DWORD flags, const BASS_FILEPROCS* proc, void* user); - BOOL BASSDEF(BASS_StreamFree)(HSTREAM handle); - QWORD BASSDEF(BASS_StreamGetFilePosition)(HSTREAM handle, DWORD mode); - DWORD BASSDEF(BASS_StreamPutData)(HSTREAM handle, const void* buffer, DWORD length); - DWORD BASSDEF(BASS_StreamPutFileData)(HSTREAM handle, const void* buffer, DWORD length); - - HMUSIC BASSDEF(BASS_MusicLoad)(BOOL mem, const void* file, QWORD offset, DWORD length, DWORD flags, DWORD freq); - BOOL BASSDEF(BASS_MusicFree)(HMUSIC handle); - - BOOL BASSDEF(BASS_RecordGetDeviceInfo)(DWORD device, BASS_DEVICEINFO* info); - BOOL BASSDEF(BASS_RecordInit)(int device); - BOOL BASSDEF(BASS_RecordFree)(void); - BOOL BASSDEF(BASS_RecordSetDevice)(DWORD device); - DWORD BASSDEF(BASS_RecordGetDevice)(void); - BOOL BASSDEF(BASS_RecordGetInfo)(BASS_RECORDINFO* info); - const char* BASSDEF(BASS_RecordGetInputName)(int input); - BOOL BASSDEF(BASS_RecordSetInput)(int input, DWORD flags, float volume); - DWORD BASSDEF(BASS_RecordGetInput)(int input, float* volume); - HRECORD BASSDEF(BASS_RecordStart)(DWORD freq, DWORD chans, DWORD flags, RECORDPROC* proc, void* user); - - double BASSDEF(BASS_ChannelBytes2Seconds)(DWORD handle, QWORD pos); - QWORD BASSDEF(BASS_ChannelSeconds2Bytes)(DWORD handle, double pos); - DWORD BASSDEF(BASS_ChannelGetDevice)(DWORD handle); - BOOL BASSDEF(BASS_ChannelSetDevice)(DWORD handle, DWORD device); - DWORD BASSDEF(BASS_ChannelIsActive)(DWORD handle); - BOOL BASSDEF(BASS_ChannelGetInfo)(DWORD handle, BASS_CHANNELINFO* info); - const char* BASSDEF(BASS_ChannelGetTags)(DWORD handle, DWORD tags); - DWORD BASSDEF(BASS_ChannelFlags)(DWORD handle, DWORD flags, DWORD mask); - BOOL BASSDEF(BASS_ChannelLock)(DWORD handle, BOOL lock); - BOOL BASSDEF(BASS_ChannelFree)(DWORD handle); - BOOL BASSDEF(BASS_ChannelPlay)(DWORD handle, BOOL restart); - BOOL BASSDEF(BASS_ChannelStart)(DWORD handle); - BOOL BASSDEF(BASS_ChannelStop)(DWORD handle); - BOOL BASSDEF(BASS_ChannelPause)(DWORD handle); - BOOL BASSDEF(BASS_ChannelUpdate)(DWORD handle, DWORD length); - BOOL BASSDEF(BASS_ChannelSetAttribute)(DWORD handle, DWORD attrib, float value); - BOOL BASSDEF(BASS_ChannelGetAttribute)(DWORD handle, DWORD attrib, float* value); - BOOL BASSDEF(BASS_ChannelSlideAttribute)(DWORD handle, DWORD attrib, float value, DWORD time); - BOOL BASSDEF(BASS_ChannelIsSliding)(DWORD handle, DWORD attrib); - BOOL BASSDEF(BASS_ChannelSetAttributeEx)(DWORD handle, DWORD attrib, void* value, DWORD size); - DWORD BASSDEF(BASS_ChannelGetAttributeEx)(DWORD handle, DWORD attrib, void* value, DWORD size); - BOOL BASSDEF(BASS_ChannelSet3DAttributes)(DWORD handle, int mode, float min, float max, int iangle, int oangle, float outvol); - BOOL BASSDEF(BASS_ChannelGet3DAttributes)(DWORD handle, DWORD* mode, float* min, float* max, DWORD* iangle, DWORD* oangle, float* outvol); - BOOL BASSDEF(BASS_ChannelSet3DPosition)(DWORD handle, const BASS_3DVECTOR* pos, const BASS_3DVECTOR* orient, const BASS_3DVECTOR* vel); - BOOL BASSDEF(BASS_ChannelGet3DPosition)(DWORD handle, BASS_3DVECTOR* pos, BASS_3DVECTOR* orient, BASS_3DVECTOR* vel); - QWORD BASSDEF(BASS_ChannelGetLength)(DWORD handle, DWORD mode); - BOOL BASSDEF(BASS_ChannelSetPosition)(DWORD handle, QWORD pos, DWORD mode); - QWORD BASSDEF(BASS_ChannelGetPosition)(DWORD handle, DWORD mode); - DWORD BASSDEF(BASS_ChannelGetLevel)(DWORD handle); - BOOL BASSDEF(BASS_ChannelGetLevelEx)(DWORD handle, float* levels, float length, DWORD flags); - DWORD BASSDEF(BASS_ChannelGetData)(DWORD handle, void* buffer, DWORD length); - HSYNC BASSDEF(BASS_ChannelSetSync)(DWORD handle, DWORD type, QWORD param, SYNCPROC* proc, void* user); - BOOL BASSDEF(BASS_ChannelRemoveSync)(DWORD handle, HSYNC sync); - BOOL BASSDEF(BASS_ChannelSetLink)(DWORD handle, DWORD chan); - BOOL BASSDEF(BASS_ChannelRemoveLink)(DWORD handle, DWORD chan); - HDSP BASSDEF(BASS_ChannelSetDSP)(DWORD handle, DSPPROC* proc, void* user, int priority); - BOOL BASSDEF(BASS_ChannelRemoveDSP)(DWORD handle, HDSP dsp); - HFX BASSDEF(BASS_ChannelSetFX)(DWORD handle, DWORD type, int priority); - BOOL BASSDEF(BASS_ChannelRemoveFX)(DWORD handle, HFX fx); - - BOOL BASSDEF(BASS_FXSetParameters)(HFX handle, const void* params); - BOOL BASSDEF(BASS_FXGetParameters)(HFX handle, void* params); - BOOL BASSDEF(BASS_FXSetPriority)(HFX handle, int priority); - BOOL BASSDEF(BASS_FXReset)(DWORD handle); - -#ifdef __cplusplus -} - -#if defined(_WIN32) && !defined(NOBASSOVERLOADS) -static inline HPLUGIN BASS_PluginLoad(const WCHAR* file, DWORD flags) -{ - return BASS_PluginLoad((const char*)file, flags | BASS_UNICODE); -} - -static inline HMUSIC BASS_MusicLoad(BOOL mem, const WCHAR* file, QWORD offset, DWORD length, DWORD flags, DWORD freq) -{ - return BASS_MusicLoad(mem, (const void*)file, offset, length, flags | BASS_UNICODE, freq); -} - -static inline HSAMPLE BASS_SampleLoad(BOOL mem, const WCHAR* file, QWORD offset, DWORD length, DWORD max, DWORD flags) -{ - return BASS_SampleLoad(mem, (const void*)file, offset, length, max, flags | BASS_UNICODE); -} - -static inline HSTREAM BASS_StreamCreateFile(BOOL mem, const WCHAR* file, QWORD offset, QWORD length, DWORD flags) -{ - return BASS_StreamCreateFile(mem, (const void*)file, offset, length, flags | BASS_UNICODE); -} - -static inline HSTREAM BASS_StreamCreateURL(const WCHAR* url, DWORD offset, DWORD flags, DOWNLOADPROC* proc, void* user) -{ - return BASS_StreamCreateURL((const char*)url, offset, flags | BASS_UNICODE, proc, user); -} - -static inline BOOL BASS_SetConfigPtr(DWORD option, const WCHAR* value) -{ - return BASS_SetConfigPtr(option | BASS_UNICODE, (const void*)value); -} -#endif -#endif - -#ifdef __OBJC__ -#undef BOOL -#endif - -#endif diff --git a/PopLib/audio/bassmusicinterface.cpp b/PopLib/audio/bassmusicinterface.cpp deleted file mode 100644 index e86ab026..00000000 --- a/PopLib/audio/bassmusicinterface.cpp +++ /dev/null @@ -1,408 +0,0 @@ -#include "bassmusicinterface.hpp" -#include "bass.h" -#include "paklib/pakinterface.hpp" - -using namespace PopLib; - -bool BASS_MusicSetAmplify(HMUSIC handle, uint32_t amp) -{ - BASS_ChannelSetAttribute(handle, BASS_ATTRIB_MUSIC_AMPLIFY, amp); - return true; -} - -bool BASS_MusicPlay(HMUSIC handle) -{ - return BASS_ChannelPlay(handle, true); -} - -bool BASS_MusicPlayEx(HMUSIC handle, uint32_t pos, int flags, bool reset) -{ - BASS_ChannelStop(handle); - BASS_ChannelSetPosition(handle, MAKELONG(pos, 0), BASS_POS_BYTE); - BASS_ChannelFlags(handle, flags, -1); - - return BASS_ChannelPlay(handle, reset); -} - -bool BASS_ChannelResume(uint32_t handle) -{ - return BASS_ChannelPlay(handle, false); -} - -bool BASS_StreamStop(uint32_t handle, uint32_t pos, int flags) -{ - // BASS_ChannelSetPosition(handle, MAKELONG(0,0), BASS_POS_BYTE); - // BASS_ChannelFlags(handle, flags, -1); - - return BASS_ChannelStop(handle); -} - -bool BASS_StreamPlay(HSTREAM handle, bool flush, uint32_t flags) -{ - BASS_ChannelStop(handle); - BASS_ChannelSetPosition(handle, 0, BASS_POS_BYTE); - BASS_ChannelFlags(handle, flags, -1); - return BASS_ChannelPlay(handle, flush); -} - -BassMusicInfo::BassMusicInfo() -{ - mVolume = 0.0; - mVolumeAdd = 0.0; - mVolumeCap = 1.0; - mStopOnFade = false; - mHMusic = NULL; - mStream = {NULL, NULL}; -} - -BassMusicInterface::BassMusicInterface() -{ - bool success = false; - - success = BASS_Init(1, 44100, 0, 0, nullptr); - BASS_SetConfig(BASS_CONFIG_BUFFER, 2000); - - mMaxMusicVolume = 40; - mMusicLoadFlags = BASS_MUSIC_LOOP | BASS_MUSIC_RAMP; -} - -BassMusicInterface::~BassMusicInterface() -{ -} - -bool BassMusicInterface::LoadMusic(int theSongId, const std::string &theFileName) -{ - HMUSIC aHMusic = NULL; - HSTREAM aStream = NULL; - - std::string anExt; - int aDotPos = theFileName.find_last_of('.'); - if (aDotPos != std::string::npos) - anExt = StringToLower(theFileName.substr(aDotPos + 1)); - - PFILE *aFP = p_fopen(theFileName.c_str(), "rb"); - if (!aFP) - return false; - - p_fseek(aFP, 0, SEEK_END); - int aSize = p_ftell(aFP); - p_fseek(aFP, 0, SEEK_SET); - - uchar *aData = new uchar[aSize]; - p_fread(aData, 1, aSize, aFP); - p_fclose(aFP); - - if (anExt == "wav" || anExt == "ogg" || anExt == "mp3") - aStream = BASS_StreamCreateFile(TRUE, aData, 0, aSize, BASS_SAMPLE_LOOP); - else - { - aHMusic = BASS_MusicLoad(TRUE, aData, 0, aSize, BASS_MUSIC_LOOP | BASS_MUSIC_RAMP, 0); - delete[] aData; - } - - int anErrCode = BASS_ErrorGetCode(); - if ((!aHMusic && !aStream )|| anErrCode != BASS_OK) - return false; - - BassMusicInfo aMusicInfo; - aMusicInfo.mHMusic = aHMusic; - aMusicInfo.mStream.mHStream = aStream; - aMusicInfo.mStream.mStreamData = aData; - mMusicMap.insert(BassMusicMap::value_type(theSongId, aMusicInfo)); - - return true; -} - -void BassMusicInterface::PlayMusic(int theSongId, int theOffset, bool noLoop) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - aMusicInfo->mVolume = aMusicInfo->mVolumeCap; - aMusicInfo->mVolumeAdd = 0.0; - aMusicInfo->mStopOnFade = noLoop; - BASS_ChannelSetAttribute(aMusicInfo->GetHandle(), BASS_ATTRIB_MUSIC_VOL_GLOBAL, - (int)(aMusicInfo->mVolume * 100)); - - BASS_ChannelStop(aMusicInfo->GetHandle()); - if (aMusicInfo->mHMusic) - { - BASS_MusicPlayEx(aMusicInfo->mHMusic, theOffset, - BASS_MUSIC_POSRESET | BASS_MUSIC_RAMP | (noLoop ? 0 : BASS_MUSIC_LOOP), TRUE); - } - else - { - BOOL flush = theOffset == -1 ? FALSE : TRUE; - BASS_StreamPlay(aMusicInfo->mStream.mHStream, flush, noLoop ? 0 : BASS_MUSIC_LOOP); - if (theOffset > 0) - BASS_ChannelSetPosition(aMusicInfo->mStream.mHStream, theOffset, BASS_POS_BYTE); - } - } -} - -void BassMusicInterface::StopMusic(int theSongId) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - aMusicInfo->mVolume = 0.0; - BASS_ChannelStop(aMusicInfo->GetHandle()); - } -} - -void BassMusicInterface::StopAllMusic() -{ - BassMusicMap::iterator anItr = mMusicMap.begin(); - while (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - aMusicInfo->mVolume = 0.0; - BASS_ChannelStop(aMusicInfo->GetHandle()); - ++anItr; - } -} - -void BassMusicInterface::UnloadMusic(int theSongId) -{ - StopMusic(theSongId); - - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - if (aMusicInfo->mStream.mHStream) - BASS_StreamFree(aMusicInfo->mStream.mHStream); - else if (aMusicInfo->mHMusic) - BASS_MusicFree(aMusicInfo->mHMusic); - - mMusicMap.erase(anItr); - } -} - -void BassMusicInterface::UnloadAllMusic() -{ - StopAllMusic(); - for (BassMusicMap::iterator anItr = mMusicMap.begin(); anItr != mMusicMap.end(); ++anItr) - { - BassMusicInfo *aMusicInfo = &anItr->second; - if (aMusicInfo->mStream.mHStream) - { - BASS_StreamFree(aMusicInfo->mStream.mHStream); - delete[] aMusicInfo->mStream.mStreamData; - } - else if (aMusicInfo->mHMusic) - BASS_MusicFree(aMusicInfo->mHMusic); - } - mMusicMap.clear(); -} - -void BassMusicInterface::PauseMusic(int theSongId) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - BASS_ChannelPause(aMusicInfo->GetHandle()); - } -} - -void BassMusicInterface::PauseAllMusic() -{ - for (BassMusicMap::iterator anItr = mMusicMap.begin(); anItr != mMusicMap.end(); ++anItr) - { - BassMusicInfo *aMusicInfo = &anItr->second; - if (BASS_ChannelIsActive(aMusicInfo->GetHandle()) == BASS_ACTIVE_PLAYING) - BASS_ChannelPause(aMusicInfo->GetHandle()); - } -} - -void BassMusicInterface::ResumeAllMusic() -{ - for (BassMusicMap::iterator anItr = mMusicMap.begin(); anItr != mMusicMap.end(); ++anItr) - { - BassMusicInfo *aMusicInfo = &anItr->second; - - if (BASS_ChannelIsActive(aMusicInfo->GetHandle()) == BASS_ACTIVE_PAUSED) - BASS_ChannelResume(aMusicInfo->GetHandle()); - } -} - -void BassMusicInterface::ResumeMusic(int theSongId) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - BASS_ChannelResume(aMusicInfo->GetHandle()); - } -} - -void BassMusicInterface::FadeIn(int theSongId, int theOffset, double theSpeed, bool noLoop) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - - aMusicInfo->mVolumeAdd = theSpeed; - aMusicInfo->mStopOnFade = noLoop; - - BASS_ChannelStop(aMusicInfo->GetHandle()); - BASS_ChannelSetAttribute(aMusicInfo->GetHandle(), BASS_ATTRIB_MUSIC_VOL_GLOBAL, - (int)(aMusicInfo->mVolume * 100)); - if (aMusicInfo->mHMusic) - { - if (theOffset == -1) - BASS_MusicPlay(aMusicInfo->mHMusic); - else - { - BASS_MusicPlayEx(aMusicInfo->mHMusic, theOffset, BASS_MUSIC_RAMP | (noLoop ? 0 : BASS_MUSIC_LOOP), - TRUE); - } - } - else - { - BOOL flush = theOffset == -1 ? FALSE : TRUE; - BASS_StreamPlay(aMusicInfo->mStream.mHStream, flush, noLoop ? 0 : BASS_MUSIC_LOOP); - if (theOffset > 0) - BASS_ChannelSetPosition(aMusicInfo->mStream.mHStream, theOffset, BASS_POS_BYTE); - } - } -} - -void BassMusicInterface::FadeOut(int theSongId, bool stopSong, double theSpeed) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - - if (aMusicInfo->mVolume != 0.0) - { - aMusicInfo->mVolumeAdd = -theSpeed; - } - - aMusicInfo->mStopOnFade = stopSong; - } -} - -void BassMusicInterface::FadeOutAll(bool stopSong, double theSpeed) -{ - BassMusicMap::iterator anItr = mMusicMap.begin(); - while (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - - aMusicInfo->mVolumeAdd = -theSpeed; - aMusicInfo->mStopOnFade = stopSong; - - ++anItr; - } -} - -void BassMusicInterface::SetVolume(double theVolume) -{ - int aVolume = (int)(theVolume * 1000); - - BASS_SetConfig(/*BASS_CONFIG_GVOL_MUSIC*/ 6, (int)aVolume); - BASS_SetConfig(/*BASS_CONFIG_GVOL_STREAM*/ 5, (int)aVolume); -} - -void BassMusicInterface::SetSongVolume(int theSongId, double theVolume) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - - aMusicInfo->mVolume = theVolume; - BASS_ChannelSetAttribute(aMusicInfo->GetHandle(), BASS_ATTRIB_MUSIC_VOL_GLOBAL, - (int)(aMusicInfo->mVolume * 100)); - } -} - -void BassMusicInterface::SetSongMaxVolume(int theSongId, double theMaxVolume) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - - aMusicInfo->mVolumeCap = theMaxVolume; - aMusicInfo->mVolume = std::min(aMusicInfo->mVolume, theMaxVolume); - BASS_ChannelSetAttribute(aMusicInfo->GetHandle(), BASS_ATTRIB_MUSIC_VOL_GLOBAL, - (int)(aMusicInfo->mVolume * 100)); - } -} - -bool BassMusicInterface::IsPlaying(int theSongId) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - return BASS_ChannelIsActive(aMusicInfo->GetHandle()) == BASS_ACTIVE_PLAYING; - } - - return false; -} - -void BassMusicInterface::SetMusicAmplify(int theSongId, double theAmp) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - BASS_MusicSetAmplify(aMusicInfo->GetHandle(), (int)(theAmp * 100)); - } -} - -void BassMusicInterface::Update() -{ - BassMusicMap::iterator anItr = mMusicMap.begin(); - while (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - - if (aMusicInfo->mVolumeAdd != 0.0) - { - aMusicInfo->mVolume += aMusicInfo->mVolumeAdd; - - if (aMusicInfo->mVolume > aMusicInfo->mVolumeCap) - { - aMusicInfo->mVolume = aMusicInfo->mVolumeCap; - aMusicInfo->mVolumeAdd = 0.0; - } - else if (aMusicInfo->mVolume < 0.0) - { - aMusicInfo->mVolume = 0.0; - aMusicInfo->mVolumeAdd = 0.0; - - if (aMusicInfo->mStopOnFade) - BASS_ChannelStop(aMusicInfo->GetHandle()); - } - - BASS_ChannelSetAttribute(aMusicInfo->GetHandle(), BASS_ATTRIB_MUSIC_VOL_GLOBAL, - (int)(aMusicInfo->mVolume * 100)); - } - - ++anItr; - } -} - -//////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// -// MODs are broken up into several orders or patterns. This returns the current order a song is on. -int BassMusicInterface::GetMusicOrder(int theSongId) -{ - BassMusicMap::iterator anItr = mMusicMap.find(theSongId); - if (anItr != mMusicMap.end()) - { - BassMusicInfo *aMusicInfo = &anItr->second; - int aPosition = BASS_ChannelGetLength(aMusicInfo->GetHandle(), BASS_POS_BYTE); - return aPosition; - } - return -1; -} \ No newline at end of file diff --git a/PopLib/audio/musicinterface.hpp b/PopLib/audio/musicinterface.hpp index d5a3155d..2d02d274 100644 --- a/PopLib/audio/musicinterface.hpp +++ b/PopLib/audio/musicinterface.hpp @@ -1,14 +1,19 @@ #ifndef __MUSICINTERFACE_HPP__ #define __MUSICINTERFACE_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" namespace PopLib { +struct StreamData +{ + unsigned char *mStreamData = nullptr; + void *mStreamHandle = nullptr; +}; + /** * @brief music interface * diff --git a/PopLib/audio/openalsoundinstance.cpp b/PopLib/audio/openalsoundinstance.cpp index 8a0d0edc..ce0f2ab3 100644 --- a/PopLib/audio/openalsoundinstance.cpp +++ b/PopLib/audio/openalsoundinstance.cpp @@ -2,10 +2,10 @@ #include "openalsoundmanager.hpp" #include -namespace PopLib { +namespace PopLib +{ -OpenALSoundInstance::OpenALSoundInstance(OpenALSoundManager* theSoundManager, - ALuint theSourceSound) +OpenALSoundInstance::OpenALSoundInstance(OpenALSoundManager *theSoundManager, ALuint theSourceSound) { mSoundManagerP = theSoundManager; mReleased = false; @@ -99,7 +99,7 @@ bool OpenALSoundInstance::Play(bool looping, bool autoRelease) if (!mSoundManagerP->mALDeviceD) // hacky hack return false; - + Stop(); mHasPlayed = true; @@ -162,4 +162,4 @@ double OpenALSoundInstance::GetVolume() return mVolume; } -}; \ No newline at end of file +}; // namespace PopLib \ No newline at end of file diff --git a/PopLib/audio/openalsoundinstance.hpp b/PopLib/audio/openalsoundinstance.hpp index 9b3bc0bd..d068eb66 100644 --- a/PopLib/audio/openalsoundinstance.hpp +++ b/PopLib/audio/openalsoundinstance.hpp @@ -1,14 +1,17 @@ #ifndef __OPENALSOUNDINTERFACE_HPP__ #define __OPENALSOUNDINTERFACE_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "soundinstance.hpp" -#define AL_LIBTYPE_STATIC +#ifdef __APPLE__ +#include +#include +#else #include #include +#endif namespace PopLib { diff --git a/PopLib/audio/openalsoundmanager.cpp b/PopLib/audio/openalsoundmanager.cpp index 87cde0ed..355e28c9 100644 --- a/PopLib/audio/openalsoundmanager.cpp +++ b/PopLib/audio/openalsoundmanager.cpp @@ -1,8 +1,8 @@ #include "openalsoundmanager.hpp" #include "openalsoundinstance.hpp" #include "paklib/pakinterface.hpp" -#include "common.hpp" #include "aureader.hpp" +#include "debug/log.hpp" // Vorbis #include "vorbis/codec.h" @@ -13,19 +13,20 @@ #include #if defined(_MSC_VER) - #define DEBUG_BREAK() __debugbreak() +#define DEBUG_BREAK() __debugbreak() #elif defined(__GNUC__) || defined(__clang__) - #include - #define DEBUG_BREAK() raise(SIGTRAP) +#include +#define DEBUG_BREAK() raise(SIGTRAP) #else - #define DEBUG_BREAK() ((void)0) +#define DEBUG_BREAK() ((void)0) #endif -#define AL_CHECK_ERROR() \ - do { \ - if (alGetError() != AL_NO_ERROR) \ - DEBUG_BREAK(); \ - } while (0) +#define AL_CHECK_ERROR() \ + do \ + { \ + if (alGetError() != AL_NO_ERROR) \ + DEBUG_BREAK(); \ + } while (0) using namespace PopLib; @@ -38,7 +39,7 @@ OpenALSoundManager::OpenALSoundManager() mALDevice = alcOpenDevice(NULL); // Default device if (!mALDevice) { - SDL_Log("Failed to open OpenAL device!\n"); + LOG_ERROR("Failed to open OpenAL device!\n"); return; } @@ -47,7 +48,7 @@ OpenALSoundManager::OpenALSoundManager() mALContext = alcCreateContext(mALDevice, NULL); if (!mALContext) { - SDL_Log("Failed to create OpenAL context!\n"); + LOG_ERROR("Failed to create OpenAL context!\n"); alcCloseDevice(mALDevice); return; } @@ -58,7 +59,7 @@ OpenALSoundManager::OpenALSoundManager() for (i = 0; i < MAX_SOURCE_SOUNDS; i++) { - mSourceSounds[i] = NULL; + mSourceSounds[i] = 0; mBaseVolumes[i] = 1; mBasePans[i] = 0; } @@ -232,7 +233,7 @@ static int p_fseek64_wrap(PFILE *f, ogg_int64_t off, int whence) { if (!f) return -1; - + return p_fseek(f, (long)off, whence); } @@ -357,7 +358,7 @@ void OpenALSoundManager::ReleaseSound(unsigned int theSfxID) ForceReleaseSources(mSourceSounds[theSfxID]); alDeleteBuffers(1, &mSourceSounds[theSfxID]); AL_CHECK_ERROR(); - mSourceSounds[theSfxID] = NULL; + mSourceSounds[theSfxID] = 0; mSourceFileNames[theSfxID] = ""; } } @@ -461,7 +462,7 @@ void OpenALSoundManager::ReleaseSounds() if (mSourceSounds[i]) { alDeleteBuffers(1, &mSourceSounds[i]); - mSourceSounds[i] = NULL; + mSourceSounds[i] = 0; } } diff --git a/PopLib/audio/openalsoundmanager.hpp b/PopLib/audio/openalsoundmanager.hpp index b4347980..62a27fc8 100644 --- a/PopLib/audio/openalsoundmanager.hpp +++ b/PopLib/audio/openalsoundmanager.hpp @@ -1,15 +1,20 @@ #ifndef __OPENALSOUNDMANAGER_HPP__ #define __OPENALSOUNDMANAGER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "soundmanager.hpp" -#include "bass.h" +#ifndef AL_LIBTYPE_STATIC #define AL_LIBTYPE_STATIC +#endif +#ifdef __APPLE__ +#include +#include +#else #include #include +#endif namespace PopLib { @@ -18,7 +23,7 @@ class OpenALSoundInstance; class OpenALSoundManager : public SoundManager { friend class OpenALSoundInstance; - friend class BassMusicInterface; + friend class OpenMPTMusicInterface; public: ALuint mSourceSounds[MAX_SOURCE_SOUNDS]; diff --git a/PopLib/audio/openmptmusicinterface.cpp b/PopLib/audio/openmptmusicinterface.cpp new file mode 100644 index 00000000..1d45ffca --- /dev/null +++ b/PopLib/audio/openmptmusicinterface.cpp @@ -0,0 +1,757 @@ +#define MINIAUDIO_IMPLEMENTATION +#include "openmptmusicinterface.hpp" +#include "debug/log.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace PopLib +{ + +struct ScratchBuffers +{ + std::vector left; + std::vector right; + void ensure_capacity(size_t frames) + { + if (left.size() < frames) + left.resize(frames); + if (right.size() < frames) + right.resize(frames); + } +}; + +static std::mutex g_globalMutex; +static std::unordered_map g_scratchMap; +static double g_masterVolume = 1.0; + +OpenMPTMusicInfo::OpenMPTMusicInfo() +{ + mVolume = 0.0; // start at 0 like BassMusicInterface + mVolumeAdd = 0.0; + mVolumeCap = 1.0; + mStopOnFade = false; + mHMusic = nullptr; + mIsModule = false; + mLoop = false; + mStream = {nullptr, nullptr}; + memset(&mDecoder, 0, sizeof(mDecoder)); +} + +OpenMPTMusicInterface::OpenMPTMusicInterface() +{ + mMaxMusicVolume = 40; + mMusicLoadFlags = 0; + mDeviceInitialized = false; + + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.format = ma_format_f32; + config.playback.channels = 2; + config.sampleRate = 44100; // match BassMusicInterface sample rate + config.dataCallback = [](ma_device *pDevice, void *pOutput, const void * /*pInput*/, ma_uint32 frameCount) { + OpenMPTMusicInterface *self = (OpenMPTMusicInterface *)pDevice->pUserData; + if (self) + { + self->dataCallback((float *)pOutput, frameCount); + } + }; + config.pUserData = this; + + if (ma_device_init(NULL, &config, &mDevice) != MA_SUCCESS) + { + LOG_ERROR("OpenMPTMusicInterface: ma_device_init failed\n"); + mDeviceInitialized = false; + } + else + { + mDeviceInitialized = true; + StartDevice(); + } +} + +OpenMPTMusicInterface::~OpenMPTMusicInterface() +{ + if (mDeviceInitialized) + { + ma_device_stop(&mDevice); + ma_device_uninit(&mDevice); + } + + std::lock_guard lock(mMutex); + for (auto &kv : mMusicMap) + { + OpenMPTMusicInfo *info = kv.second.get(); + if (!info) + continue; + + if (info->mIsModule && info->mHMusic) + { + openmpt_module *mod = reinterpret_cast(info->mHMusic); + openmpt_module_destroy(mod); + } + else if (!info->mIsModule) + { + std::lock_guard lk(info->mDecoderMutex); + ma_decoder_uninit(&info->mDecoder); + } + + if (info->mStream.mStreamData) + { + delete[] static_cast(info->mStream.mStreamData); + } + } + mMusicMap.clear(); + + std::lock_guard g(g_globalMutex); + g_scratchMap.clear(); +} + +void OpenMPTMusicInterface::StartDevice() +{ + if (mDeviceInitialized) + ma_device_start(&mDevice); +} + +void OpenMPTMusicInterface::StopDevice() +{ + if (mDeviceInitialized) + ma_device_stop(&mDevice); +} + +bool OpenMPTMusicInterface::LoadMusic(int theSongId, const std::string &theFileName) +{ + std::string ext; + size_t dotPos = theFileName.find_last_of('.'); + if (dotPos != std::string::npos) + { + ext = theFileName.substr(dotPos + 1); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + } + + LOG_INFO("OpenMPTMusicInterface: Loading file with extension: %s\n", ext.c_str()); + + std::ifstream file(theFileName, std::ios::binary | std::ios::ate); + if (!file) + return false; + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + if (size <= 0) + return false; + + unsigned char *data = new unsigned char[size]; + if (!file.read(reinterpret_cast(data), size)) + { + delete[] data; + return false; + } + + auto musicInfo = std::make_unique(); + musicInfo->mStream.mStreamData = data; // store data pointer for cleanup + + if (ext == "wav" || ext == "ogg" || ext == "mp3" || ext == "flac") + { + LOG_INFO("OpenMPTMusicInterface: Using miniaudio decoder\n"); + + ma_decoder_config cfg = ma_decoder_config_init_default(); + ma_result result = ma_decoder_init_memory(data, size, &cfg, &musicInfo->mDecoder); + + if (result != MA_SUCCESS) + { + LOG_ERROR("OpenMPTMusicInterface: Default decoder init failed: %d, trying with specific format\n", result); + + ma_uint32 sampleRate = mDeviceInitialized ? mDevice.sampleRate : 44100; + cfg = ma_decoder_config_init(ma_format_f32, 2, sampleRate); + result = ma_decoder_init_memory(data, size, &cfg, &musicInfo->mDecoder); + + if (result != MA_SUCCESS) + { + LOG_ERROR("OpenMPTMusicInterface: Both decoder configs failed: %d for file: %s\n", result, + theFileName.c_str()); + delete[] data; + return false; + } + } + + LOG_INFO("OpenMPTMusicInterface: Decoder initialized successfully\n"); + musicInfo->mIsModule = false; + } + else + { + LOG_INFO("OpenMPTMusicInterface: Using OpenMPT\n"); + + int err = OPENMPT_ERROR_OK; + openmpt_module *mod = + openmpt_module_create_from_memory2(data, size, nullptr, nullptr, nullptr, nullptr, &err, nullptr, nullptr); + + if (!mod || err != OPENMPT_ERROR_OK) + { + LOG_ERROR("OpenMPTMusicInterface: OpenMPT failed to load module: error %d\n", err); + delete[] data; + return false; + } + + musicInfo->mHMusic = reinterpret_cast(mod); + musicInfo->mIsModule = true; + + std::lock_guard g(g_globalMutex); + ScratchBuffers sb; + sb.left.resize(4096); + sb.right.resize(4096); + g_scratchMap[musicInfo.get()] = std::move(sb); + } + + std::lock_guard lock(mMutex); + mMusicMap[theSongId] = std::move(musicInfo); + LOG_INFO("OpenMPTMusicInterface: Music loaded successfully with ID: %d\n", theSongId); + return true; +} + +void OpenMPTMusicInterface::PlayMusic(int theSongId, int theOffset, bool noLoop) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it == mMusicMap.end()) + return; + + OpenMPTMusicInfo *info = it->second.get(); + if (!info) + return; + + info->mVolume = info->mVolumeCap; // set to full volume immediately + info->mVolumeAdd = 0.0; + info->mStopOnFade = noLoop; + + if (info->mIsModule) + { + openmpt_module *mod = reinterpret_cast(info->mHMusic); + if (mod) + { + openmpt_module_set_repeat_count(mod, noLoop ? 0 : -1); + if (theOffset > 0) + { + double seconds = (double)theOffset / 1000.0; + openmpt_module_set_position_seconds(mod, seconds); + } + else + { + openmpt_module_set_position_seconds(mod, 0.0); + } + } + } + else + { + info->mLoop = !noLoop; + std::lock_guard lk(info->mDecoderMutex); + if (theOffset > 0) + { + ma_uint32 sampleRate = mDeviceInitialized ? mDevice.sampleRate : 44100; + double seconds = (double)theOffset / 1000.0; + ma_uint64 frame = (ma_uint64)(seconds * (double)sampleRate); + ma_result result = ma_decoder_seek_to_pcm_frame(&info->mDecoder, frame); + if (result != MA_SUCCESS) + { + LOG_ERROR("OpenMPTMusicInterface: Failed to seek to frame %llu\n", frame); + return; + } + } // no seek if theOffset == 0; decoder is already at start + } +} + +void OpenMPTMusicInterface::StopMusic(int theSongId) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it == mMusicMap.end()) + return; + + OpenMPTMusicInfo *info = it->second.get(); + if (!info) + return; + + info->mVolume = 0.0; + info->mVolumeAdd = 0.0; + + if (info->mIsModule) + { + openmpt_module *mod = reinterpret_cast(info->mHMusic); + if (mod) + openmpt_module_set_position_seconds(mod, 0.0); + } + else + { + std::lock_guard lk(info->mDecoderMutex); + ma_decoder_seek_to_pcm_frame(&info->mDecoder, 0); + } +} + +void OpenMPTMusicInterface::StopAllMusic() +{ + std::lock_guard lock(mMutex); + for (auto &kv : mMusicMap) + { + OpenMPTMusicInfo *info = kv.second.get(); + if (!info) + continue; + + info->mVolume = 0.0; + info->mVolumeAdd = 0.0; + + if (info->mIsModule) + { + openmpt_module *mod = reinterpret_cast(info->mHMusic); + if (mod) + openmpt_module_set_position_seconds(mod, 0.0); + } + else + { + std::lock_guard lk(info->mDecoderMutex); + ma_decoder_seek_to_pcm_frame(&info->mDecoder, 0); + } + } +} + +void OpenMPTMusicInterface::UnloadMusic(int theSongId) +{ + StopMusic(theSongId); + + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it != mMusicMap.end()) + { + OpenMPTMusicInfo *info = it->second.get(); + if (info) + { + if (info->mIsModule) + { + if (info->mHMusic) + { + openmpt_module *mod = reinterpret_cast(info->mHMusic); + openmpt_module_destroy(mod); + } + std::lock_guard g(g_globalMutex); + g_scratchMap.erase(info); + } + else + { + std::lock_guard lk(info->mDecoderMutex); + ma_decoder_uninit(&info->mDecoder); + } + + if (info->mStream.mStreamData) + { + delete[] static_cast(info->mStream.mStreamData); + } + } + mMusicMap.erase(it); + } +} + +void OpenMPTMusicInterface::UnloadAllMusic() +{ + StopAllMusic(); + + std::lock_guard lock(mMutex); + for (auto &kv : mMusicMap) + { + OpenMPTMusicInfo *info = kv.second.get(); + if (!info) + continue; + + if (info->mIsModule) + { + if (info->mHMusic) + { + openmpt_module *mod = reinterpret_cast(info->mHMusic); + openmpt_module_destroy(mod); + } + } + else + { + std::lock_guard lk(info->mDecoderMutex); + ma_decoder_uninit(&info->mDecoder); + } + + if (info->mStream.mStreamData) + { + delete[] static_cast(info->mStream.mStreamData); + } + } + mMusicMap.clear(); + + std::lock_guard g(g_globalMutex); + g_scratchMap.clear(); +} + +void OpenMPTMusicInterface::PauseMusic(int theSongId) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it != mMusicMap.end() && it->second) + { + it->second->mVolume = 0.0; + it->second->mVolumeAdd = 0.0; + } +} + +void OpenMPTMusicInterface::ResumeMusic(int theSongId) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it != mMusicMap.end() && it->second) + { + it->second->mVolume = it->second->mVolumeCap; + } +} + +void OpenMPTMusicInterface::PauseAllMusic() +{ + std::lock_guard lock(mMutex); + for (auto &kv : mMusicMap) + { + if (kv.second) + { + kv.second->mVolume = 0.0; + kv.second->mVolumeAdd = 0.0; + } + } +} + +void OpenMPTMusicInterface::ResumeAllMusic() +{ + std::lock_guard lock(mMutex); + for (auto &kv : mMusicMap) + { + if (kv.second) + { + kv.second->mVolume = kv.second->mVolumeCap; + } + } +} + +void OpenMPTMusicInterface::FadeIn(int theSongId, int theOffset, double theSpeed, bool noLoop) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it == mMusicMap.end()) + return; + + OpenMPTMusicInfo *info = it->second.get(); + if (!info) + return; + + info->mVolume = 0.0; // start from 0 for fade in + info->mVolumeAdd = fabs(theSpeed) * 3.14; // positive for fade in + info->mStopOnFade = noLoop; + + if (info->mIsModule) + { + openmpt_module *mod = reinterpret_cast(info->mHMusic); + if (mod) + { + openmpt_module_set_repeat_count(mod, noLoop ? 0 : -1); + if (theOffset > 0) + { + double seconds = (double)theOffset / 1000.0; + openmpt_module_set_position_seconds(mod, seconds); + } + else + { + openmpt_module_set_position_seconds(mod, 0.0); + } + } + } + else + { + info->mLoop = !noLoop; + std::lock_guard lk(info->mDecoderMutex); + if (theOffset > 0) + { + ma_uint32 sampleRate = mDeviceInitialized ? mDevice.sampleRate : 44100; + double seconds = (double)theOffset / 1000.0; + ma_uint64 frame = (ma_uint64)(seconds * (double)sampleRate); + ma_result result = ma_decoder_seek_to_pcm_frame(&info->mDecoder, frame); + if (result != MA_SUCCESS) + { + LOG_ERROR("OpenMPTMusicInterface: Failed to seek to frame %llu\n", frame); + return; + } + } // no seek if theOffset == 0; decoder is already at start + } +} + +void OpenMPTMusicInterface::FadeOut(int theSongId, bool stopSong, double theSpeed) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it == mMusicMap.end()) + return; + + OpenMPTMusicInfo *info = it->second.get(); + if (!info) + return; + + if (info->mVolume > 0.0) + { + info->mVolumeAdd = -fabs(theSpeed); // negative for fade out + } + info->mStopOnFade = stopSong; +} + +void OpenMPTMusicInterface::FadeOutAll(bool stopSong, double theSpeed) +{ + std::lock_guard lock(mMutex); + for (auto &kv : mMusicMap) + { + OpenMPTMusicInfo *info = kv.second.get(); + if (!info) + continue; + + if (info->mVolume > 0.0) + { + info->mVolumeAdd = -fabs(theSpeed); + } + info->mStopOnFade = stopSong; + } +} + +void OpenMPTMusicInterface::SetSongVolume(int theSongId, double theVolume) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it == mMusicMap.end()) + return; + + OpenMPTMusicInfo *info = it->second.get(); + if (!info) + return; + + info->mVolume = std::max(0.0, std::min(theVolume, info->mVolumeCap)); +} + +void OpenMPTMusicInterface::SetSongMaxVolume(int theSongId, double theMaxVolume) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it == mMusicMap.end()) + return; + + OpenMPTMusicInfo *info = it->second.get(); + if (!info) + return; + + info->mVolumeCap = theMaxVolume; + if (info->mVolume > info->mVolumeCap) + { + info->mVolume = info->mVolumeCap; + } +} + +bool OpenMPTMusicInterface::IsPlaying(int theSongId) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it == mMusicMap.end()) + return false; + + OpenMPTMusicInfo *info = it->second.get(); + if (!info) + return false; + + return info->mVolume > 0.0; +} + +void OpenMPTMusicInterface::SetVolume(double theVolume) +{ + g_masterVolume = std::max(0.0, theVolume); +} + +void OpenMPTMusicInterface::SetMusicAmplify(int theSongId, double theAmp) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it == mMusicMap.end()) + return; + + OpenMPTMusicInfo *info = it->second.get(); + if (!info || !info->mIsModule) + return; + + openmpt_module *mod = reinterpret_cast(info->mHMusic); + if (!mod) + return; + + int32_t millibel = 0; + if (theAmp <= 0.0) + { + millibel = -12000; // -120 dB (silence) + } + else + { + double dB = 20.0 * log10(theAmp); + millibel = (int32_t)round(dB * 100.0); + } + openmpt_module_set_render_param(mod, OPENMPT_MODULE_RENDER_MASTERGAIN_MILLIBEL, millibel); +} + +void OpenMPTMusicInterface::Update() +{ + std::lock_guard lock(mMutex); + for (auto &kv : mMusicMap) + { + OpenMPTMusicInfo *info = kv.second.get(); + if (!info) + continue; + + if (info->mVolumeAdd != 0.0) + { + info->mVolume += info->mVolumeAdd; + + if (info->mVolume >= info->mVolumeCap) + { + info->mVolume = info->mVolumeCap; + info->mVolumeAdd = 0.0; + } + else if (info->mVolume <= 0.0) + { + info->mVolume = 0.0; + info->mVolumeAdd = 0.0; + + if (info->mStopOnFade) + { + if (info->mIsModule) + { + openmpt_module *mod = reinterpret_cast(info->mHMusic); + if (mod) + openmpt_module_set_position_seconds(mod, 0.0); + } + else + { + std::lock_guard lk(info->mDecoderMutex); + ma_decoder_seek_to_pcm_frame(&info->mDecoder, 0); + } + } + } + } + } +} + +int OpenMPTMusicInterface::GetMusicOrder(int theSongId) +{ + std::lock_guard lock(mMutex); + auto it = mMusicMap.find(theSongId); + if (it == mMusicMap.end()) + return -1; + + OpenMPTMusicInfo *info = it->second.get(); + if (!info || !info->mIsModule) + return -1; + + openmpt_module *mod = reinterpret_cast(info->mHMusic); + if (!mod) + return -1; + + return (int)openmpt_module_get_current_order(mod); +} + +void OpenMPTMusicInterface::dataCallback(float *outF32, ma_uint32 frameCount) +{ + const ma_uint32 outSamples = frameCount * 2; + memset(outF32, 0, outSamples * sizeof(float)); + + // get active tracks + std::vector active; + { + std::lock_guard lock(mMutex); + for (auto &kv : mMusicMap) + { + OpenMPTMusicInfo *info = kv.second.get(); + if (info && info->mVolume > 0.0) + { + active.push_back(info); + } + } + } + + if (active.empty()) + return; + + ma_uint32 sampleRate = mDevice.sampleRate; + + for (OpenMPTMusicInfo *info : active) + { + double vol = info->mVolume * g_masterVolume; + float fvol = (float)vol; + + if (info->mIsModule) + { + openmpt_module *mod = reinterpret_cast(info->mHMusic); + if (!mod) + continue; + + ScratchBuffers *sb = nullptr; + { + std::lock_guard g(g_globalMutex); + auto it = g_scratchMap.find(info); + if (it != g_scratchMap.end()) + { + sb = &it->second; + sb->ensure_capacity(frameCount); + } + } + if (!sb) + continue; + + size_t frames = + openmpt_module_read_float_stereo(mod, sampleRate, frameCount, sb->left.data(), sb->right.data()); + + for (size_t i = 0; i < frames; ++i) + { + size_t idx = i * 2; + outF32[idx] += sb->left[i] * fvol; + outF32[idx + 1] += sb->right[i] * fvol; + } + } + else + { + std::vector buffer(frameCount * 2); + ma_uint64 framesRead = 0; + + { + std::lock_guard lk(info->mDecoderMutex); + ma_result result = ma_decoder_read_pcm_frames(&info->mDecoder, buffer.data(), frameCount, &framesRead); + + if (result != MA_SUCCESS || framesRead == 0) + { + if (info->mLoop) + { + ma_decoder_seek_to_pcm_frame(&info->mDecoder, 0); + ma_decoder_read_pcm_frames(&info->mDecoder, buffer.data(), frameCount, &framesRead); + } + } + } + + for (ma_uint64 i = 0; i < framesRead; ++i) + { + size_t idx = i * 2; + outF32[idx] += buffer[idx] * fvol; + outF32[idx + 1] += buffer[idx + 1] * fvol; + } + } + } + + for (ma_uint32 i = 0; i < outSamples; ++i) + { + outF32[i] = std::max(-1.0f, std::min(1.0f, outF32[i])); + } +} + +} // namespace PopLib \ No newline at end of file diff --git a/PopLib/audio/bassmusicinterface.hpp b/PopLib/audio/openmptmusicinterface.hpp similarity index 51% rename from PopLib/audio/bassmusicinterface.hpp rename to PopLib/audio/openmptmusicinterface.hpp index c942a34a..8aca2906 100644 --- a/PopLib/audio/bassmusicinterface.hpp +++ b/PopLib/audio/openmptmusicinterface.hpp @@ -1,101 +1,71 @@ -#ifndef __BASSMUSICINTERFACE_HPP__ -#define __BASSMUSICINTERFACE_HPP__ -#ifdef _WIN32 +#ifndef __OPENMPTMUSICINTERFACE_HPP__ +#define __OPENMPTMUSICINTERFACE_HPP__ + #pragma once -#endif #include "musicinterface.hpp" -#include "bass.h" +#include "miniaudio.h" +#include +#include +#include +#include +#include namespace PopLib { -class AppBase; - -struct StreamData -{ - /// @brief data that is streamed to mHStream - uchar* mStreamData; - /// @brief stream object - HSTREAM mHStream; -}; - /** - * @brief bass music info - * - * why is this not a struct? + * @brief openmpt music info */ -class BassMusicInfo +struct OpenMPTMusicInfo { - public: - /// @brief music object - HMUSIC mHMusic; - /// @brief stream object + void *mHMusic; // libopenmpt module ptr if module + ma_decoder mDecoder; // miniaudio decoder for MP3/WAV/etc + bool mIsModule; // true = libopenmpt module, false = miniaudio decoder + StreamData mStream; - /// @brief current volume double mVolume; - /// @brief TBA double mVolumeAdd; - /// @brief TBA double mVolumeCap; - /// @brief true if going to stop on fade bool mStopOnFade; - public: - /// @brief constructor - BassMusicInfo(); - /// @brief destructor - virtual ~BassMusicInfo() = default; - - /// @brief gets the current handle - /// @return mHMusic if no mHStream and vice verse - uint32_t GetHandle() - { - return mHMusic ? mHMusic : mStream.mHStream; - } + std::mutex mDecoderMutex; // protects mDecoder / seek / uninit / read + bool mLoop; + + OpenMPTMusicInfo(); }; -/// @brief list -typedef std::map BassMusicMap; +/// @brief list (store pointers to avoid copying non-copyable members) +typedef std::map> OpenMPTMusicMap; /** - * @brief bass music interface - * - * parents MusicInterface + * @brief OpenMPT music interface */ -class BassMusicInterface : public MusicInterface +class OpenMPTMusicInterface : public MusicInterface { public: /// @brief list - BassMusicMap mMusicMap; - /// @brief maximum music volume + OpenMPTMusicMap mMusicMap; + /// @brief maximum music volume (compat) int mMaxMusicVolume; - /// @brief music loading flags + /// @brief music loading flags (compat) int mMusicLoadFlags; public: /// @brief constructor - BassMusicInterface(); + OpenMPTMusicInterface(); /// @brief destructor - virtual ~BassMusicInterface(); + virtual ~OpenMPTMusicInterface(); /// @brief loads music by id - /// @param theSongId - /// @param theFileName - /// @return true if success virtual bool LoadMusic(int theSongId, const std::string &theFileName); /// @brief plays music by id - /// @param theSongId - /// @param theOffset - /// @param noLoop virtual void PlayMusic(int theSongId, int theOffset = 0, bool noLoop = false); /// @brief stops music by id - /// @param theSongId virtual void StopMusic(int theSongId); /// @brief stops all music virtual void StopAllMusic(); /// @brief unloads music by id - /// @param theSongId virtual void UnloadMusic(int theSongId); /// @brief unloads all music virtual void UnloadAllMusic(); @@ -104,55 +74,52 @@ class BassMusicInterface : public MusicInterface /// @brief resume all music virtual void ResumeAllMusic(); /// @brief pauses music by id - /// @param theSongId virtual void PauseMusic(int theSongId); /// @brief resumes music by id - /// @param theSongId virtual void ResumeMusic(int theSongId); /// @brief fades in music by id - /// @param theSongId - /// @param theOffset - /// @param theSpeed - /// @param noLoop virtual void FadeIn(int theSongId, int theOffset = -1, double theSpeed = 0.002, bool noLoop = false); /// @brief fades out music by id - /// @param theSongId - /// @param stopSong - /// @param theSpeed virtual void FadeOut(int theSongId, bool stopSong = true, double theSpeed = 0.004); /// @brief fades out all music - /// @param stopSong - /// @param theSpeed virtual void FadeOutAll(bool stopSong = true, double theSpeed = 0.004); /// @brief sets song volume by id - /// @param theSongId - /// @param theVolume virtual void SetSongVolume(int theSongId, double theVolume); /// @brief sets song maximum volume by id - /// @param theSongId - /// @param theMaxVolume virtual void SetSongMaxVolume(int theSongId, double theMaxVolume); /// @brief is song by id playing? - /// @param theSongId - /// @return true if yes virtual bool IsPlaying(int theSongId); /// @brief sets global volume - /// @param theVolume virtual void SetVolume(double theVolume); - /// @brief sets music amplify, default is 0.50 - /// @param theSongId - /// @param theAmp + /// @brief sets music amplify virtual void SetMusicAmplify(int theSongId, double theAmp); - /// @brief music update + /// @brief music update (call from main thread) virtual void Update(); /// @brief functions for dealing with MODs - /// @param theSongId - /// @return music order int GetMusicOrder(int theSongId); + + public: + /// @brief start the miniaudio device + void StartDevice(); + /// @brief stop the miniaudio device + void StopDevice(); + + private: + /// @brief callback invoked from miniaudio (defined in .cpp) + void dataCallback(float *outF32, ma_uint32 frameCount); + + /// @brief miniaudio device object (header includes miniaudio.h so this is known) + ma_device mDevice; + + /// @brief whether mDevice was successfully initialized + bool mDeviceInitialized; + + /// @brief mutex protecting mMusicMap and other state + std::mutex mMutex; }; } // namespace PopLib -#endif \ No newline at end of file +#endif // __OPENMPTMUSICINTERFACE_HPP__ diff --git a/PopLib/audio/soundinstance.hpp b/PopLib/audio/soundinstance.hpp index 201c5f2c..373db0d9 100644 --- a/PopLib/audio/soundinstance.hpp +++ b/PopLib/audio/soundinstance.hpp @@ -1,8 +1,7 @@ #ifndef __SOUNDINSTANCE_HPP__ #define __SOUNDINSTANCE_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" diff --git a/PopLib/audio/soundmanager.hpp b/PopLib/audio/soundmanager.hpp index 9400137b..27d46402 100644 --- a/PopLib/audio/soundmanager.hpp +++ b/PopLib/audio/soundmanager.hpp @@ -1,8 +1,7 @@ #ifndef __SOUNDMANAGER_HPP__ #define __SOUNDMANAGER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" diff --git a/PopLib/chipmunk/LICENSE.txt b/PopLib/chipmunk/LICENSE.txt new file mode 100644 index 00000000..b82f3497 --- /dev/null +++ b/PopLib/chipmunk/LICENSE.txt @@ -0,0 +1,20 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ \ No newline at end of file diff --git a/PopLib/chipmunk/chipmunk.c b/PopLib/chipmunk/chipmunk.c new file mode 100644 index 00000000..9d5f08a3 --- /dev/null +++ b/PopLib/chipmunk/chipmunk.c @@ -0,0 +1,69 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include "stdlib.h" + +#include "chipmunk.h" + +#ifdef __cplusplus +extern "C" { +#endif + void cpInitCollisionFuncs(void); +#ifdef __cplusplus +} +#endif + + +void +cpInitChipmunk(void) +{ + cpInitCollisionFuncs(); +} + +cpFloat +cpMomentForCircle(cpFloat m, cpFloat r1, cpFloat r2, cpVect offset) +{ + return (1.0f/2.0f)*m*(r1*r1 + r2*r2) + m*cpvdot(offset, offset); +} + +cpFloat +cpMomentForPoly(cpFloat m, const int numVerts, cpVect *verts, cpVect offset) +{ + cpVect *tVerts = (cpVect *)calloc(numVerts, sizeof(cpVect)); + for(int i=0; i b) ? a : b; +} + +static inline cpFloat +cpfmin(cpFloat a, cpFloat b) +{ + return (a < b) ? a : b; +} + +#ifndef INFINITY + #ifdef _WIN32 + union MSVC_EVIL_FLOAT_HACK + { + unsigned __int8 Bytes[4]; + float Value; + }; + static union MSVC_EVIL_FLOAT_HACK INFINITY_HACK = {{0x00, 0x00, 0x80, 0x7F}}; + #define INFINITY (INFINITY_HACK.Value) + #else + #define INFINITY (1e1000) + #endif +#endif + +#include + +#include "cpVect.h" +#include "cpBB.h" +#include "cpBody.h" +#include "cpArray.h" +#include "cpHashSet.h" +#include "cpSpaceHash.h" + +#include "cpShape.h" +#include "cpPolyShape.h" + +#include "cpArbiter.h" +#include "cpCollision.h" + +#include "cpJoint.h" + +#include "cpSpace.h" + +#define CP_HASH_COEF (3344921057ul) +#define CP_HASH_PAIR(A, B) ((uintptr_t)(A)*CP_HASH_COEF ^ (uintptr_t)(B)*CP_HASH_COEF) + +void cpInitChipmunk(void); + +cpFloat cpMomentForCircle(cpFloat m, cpFloat r1, cpFloat r2, cpVect offset); +cpFloat cpMomentForPoly(cpFloat m, int numVerts, cpVect *verts, cpVect offset); + +#ifdef __cplusplus +} +#endif diff --git a/PopLib/chipmunk/cpArbiter.c b/PopLib/chipmunk/cpArbiter.c new file mode 100644 index 00000000..ddb06492 --- /dev/null +++ b/PopLib/chipmunk/cpArbiter.c @@ -0,0 +1,246 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include +#include + +#include "chipmunk.h" + +cpFloat cp_bias_coef = 0.1f; +cpFloat cp_collision_slop = 0.1f; + +cpContact* +cpContactInit(cpContact *con, cpVect p, cpVect n, cpFloat dist, unsigned int hash) +{ + con->p = p; + con->n = n; + con->dist = dist; + + con->jnAcc = 0.0f; + con->jtAcc = 0.0f; + con->jBias = 0.0f; + + con->hash = hash; + + return con; +} + +cpVect +cpContactsSumImpulses(cpContact *contacts, int numContacts) +{ + cpVect sum = cpvzero; + + for(int i=0; in, con->jnAcc); + sum = cpvadd(sum, j); + } + + return sum; +} + +cpVect +cpContactsSumImpulsesWithFriction(cpContact *contacts, int numContacts) +{ + cpVect sum = cpvzero; + + for(int i=0; in); + cpVect j = cpvadd(cpvmult(con->n, con->jnAcc), cpvmult(t, con->jtAcc)); + sum = cpvadd(sum, j); + } + + return sum; +} + +cpArbiter* +cpArbiterAlloc(void) +{ + return (cpArbiter *)calloc(1, sizeof(cpArbiter)); +} + +cpArbiter* +cpArbiterInit(cpArbiter *arb, cpShape *a, cpShape *b, int stamp) +{ + arb->numContacts = 0; + arb->contacts = NULL; + + arb->a = a; + arb->b = b; + + arb->stamp = stamp; + + return arb; +} + +cpArbiter* +cpArbiterNew(cpShape *a, cpShape *b, int stamp) +{ + return cpArbiterInit(cpArbiterAlloc(), a, b, stamp); +} + +void +cpArbiterDestroy(cpArbiter *arb) +{ + free(arb->contacts); +} + +void +cpArbiterFree(cpArbiter *arb) +{ + if(arb) cpArbiterDestroy(arb); + free(arb); +} + +void +cpArbiterInject(cpArbiter *arb, cpContact *contacts, int numContacts) +{ + // Iterate over the possible pairs to look for hash value matches. + for(int i=0; inumContacts; i++){ + cpContact *old = &arb->contacts[i]; + + for(int j=0; jhash == old->hash){ + // Copy the persistant contact information. + new_contact->jnAcc = old->jnAcc; + new_contact->jtAcc = old->jtAcc; + } + } + } + + free(arb->contacts); + + arb->contacts = contacts; + arb->numContacts = numContacts; +} + +void +cpArbiterPreStep(cpArbiter *arb, cpFloat dt_inv) +{ + cpShape *shapea = arb->a; + cpShape *shapeb = arb->b; + + arb->e = shapea->e * shapeb->e; + arb->u = shapea->u * shapeb->u; + arb->target_v = cpvsub(shapeb->surface_v, shapea->surface_v); + + cpBody *a = shapea->body; + cpBody *b = shapeb->body; + + for(int i=0; inumContacts; i++){ + cpContact *con = &arb->contacts[i]; + + // Calculate the offsets. + con->r1 = cpvsub(con->p, a->p); + con->r2 = cpvsub(con->p, b->p); + + // Calculate the mass normal. + cpFloat mass_sum = a->m_inv + b->m_inv; + + cpFloat r1cn = cpvcross(con->r1, con->n); + cpFloat r2cn = cpvcross(con->r2, con->n); + cpFloat kn = mass_sum + a->i_inv*r1cn*r1cn + b->i_inv*r2cn*r2cn; + con->nMass = 1.0f/kn; + + // Calculate the mass tangent. + cpVect t = cpvperp(con->n); + cpFloat r1ct = cpvcross(con->r1, t); + cpFloat r2ct = cpvcross(con->r2, t); + cpFloat kt = mass_sum + a->i_inv*r1ct*r1ct + b->i_inv*r2ct*r2ct; + con->tMass = 1.0f/kt; + + // Calculate the target bias velocity. + con->bias = -cp_bias_coef*dt_inv*cpfmin(0.0f, con->dist + cp_collision_slop); + con->jBias = 0.0f; + + // Calculate the target bounce velocity. + cpVect v1 = cpvadd(a->v, cpvmult(cpvperp(con->r1), a->w)); + cpVect v2 = cpvadd(b->v, cpvmult(cpvperp(con->r2), b->w)); + con->bounce = cpvdot(con->n, cpvsub(v2, v1))*arb->e; + + // Apply the previous accumulated impulse. + cpVect j = cpvadd(cpvmult(con->n, con->jnAcc), cpvmult(t, con->jtAcc)); + cpBodyApplyImpulse(a, cpvneg(j), con->r1); + cpBodyApplyImpulse(b, j, con->r2); + } +} + +void +cpArbiterApplyImpulse(cpArbiter *arb) +{ + cpBody *a = arb->a->body; + cpBody *b = arb->b->body; + + for(int i=0; inumContacts; i++){ + cpContact *con = &arb->contacts[i]; + cpVect n = con->n; + cpVect r1 = con->r1; + cpVect r2 = con->r2; + + // Calculate the relative bias velocities. + cpVect vb1 = cpvadd(a->v_bias, cpvmult(cpvperp(r1), a->w_bias)); + cpVect vb2 = cpvadd(b->v_bias, cpvmult(cpvperp(r2), b->w_bias)); + cpFloat vbn = cpvdot(cpvsub(vb2, vb1), n); + + // Calculate and clamp the bias impulse. + cpFloat jbn = (con->bias - vbn)*con->nMass; + cpFloat jbnOld = con->jBias; + con->jBias = cpfmax(jbnOld + jbn, 0.0f); + jbn = con->jBias - jbnOld; + + // Apply the bias impulse. + cpVect jb = cpvmult(n, jbn); + cpBodyApplyBiasImpulse(a, cpvneg(jb), r1); + cpBodyApplyBiasImpulse(b, jb, r2); + + // Calculate the relative velocity. + cpVect v1 = cpvadd(a->v, cpvmult(cpvperp(r1), a->w)); + cpVect v2 = cpvadd(b->v, cpvmult(cpvperp(r2), b->w)); + cpVect vr = cpvsub(v2, v1); + cpFloat vrn = cpvdot(vr, n); + + // Calculate and clamp the normal impulse. + cpFloat jn = -(con->bounce + vrn)*con->nMass; + cpFloat jnOld = con->jnAcc; + con->jnAcc = cpfmax(jnOld + jn, 0.0f); + jn = con->jnAcc - jnOld; + + // Calculate the relative tangent velocity. + cpVect t = cpvperp(n); + cpFloat vrt = cpvdot(cpvadd(vr, arb->target_v), t); + + // Calculate and clamp the friction impulse. + cpFloat jtMax = arb->u*con->jnAcc; + cpFloat jt = -vrt*con->tMass; + cpFloat jtOld = con->jtAcc; + con->jtAcc = cpfmin(cpfmax(jtOld + jt, -jtMax), jtMax); + jt = con->jtAcc - jtOld; + + // Apply the final impulse. + cpVect j = cpvadd(cpvmult(n, jn), cpvmult(t, jt)); + cpBodyApplyImpulse(a, cpvneg(j), r1); + cpBodyApplyImpulse(b, j, r2); + } +} diff --git a/PopLib/chipmunk/cpArbiter.h b/PopLib/chipmunk/cpArbiter.h new file mode 100644 index 00000000..71075086 --- /dev/null +++ b/PopLib/chipmunk/cpArbiter.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +// Determines how fast penetrations resolve themselves. +extern cpFloat cp_bias_coef; +// Amount of allowed penetration. Used to reduce vibrating contacts. +extern cpFloat cp_collision_slop; + +// Data structure for contact points. +typedef struct cpContact{ + // Contact point and normal. + cpVect p, n; + // Penetration distance. + cpFloat dist; + + // Calculated by cpArbiterPreStep(). + cpVect r1, r2; + cpFloat nMass, tMass, bounce; + + // Persistant contact information. + cpFloat jnAcc, jtAcc, jBias; + cpFloat bias; + + // Hash value used to (mostly) uniquely identify a contact. + unsigned int hash; +} cpContact; + +// Contacts are always allocated in groups. +cpContact* cpContactInit(cpContact *con, cpVect p, cpVect n, cpFloat dist, unsigned int hash); + +// Sum the contact impulses. (Can be used after cpSpaceStep() returns) +cpVect cpContactsSumImpulses(cpContact *contacts, int numContacts); +cpVect cpContactsSumImpulsesWithFriction(cpContact *contacts, int numContacts); + +// Data structure for tracking collisions between shapes. +typedef struct cpArbiter{ + // Information on the contact points between the objects. + int numContacts; + cpContact *contacts; + + // The two shapes involved in the collision. + cpShape *a, *b; + + // Calculated by cpArbiterPreStep(). + cpFloat u, e; + cpVect target_v; + + // Time stamp of the arbiter. (from cpSpace) + int stamp; +} cpArbiter; + +// Basic allocation/destruction functions. +cpArbiter* cpArbiterAlloc(void); +cpArbiter* cpArbiterInit(cpArbiter *arb, cpShape *a, cpShape *b, int stamp); +cpArbiter* cpArbiterNew(cpShape *a, cpShape *b, int stamp); + +void cpArbiterDestroy(cpArbiter *arb); +void cpArbiterFree(cpArbiter *arb); + +// These functions are all intended to be used internally. +// Inject new contact points into the arbiter while preserving contact history. +void cpArbiterInject(cpArbiter *arb, cpContact *contacts, int numContacts); +// Precalculate values used by the solver. +void cpArbiterPreStep(cpArbiter *arb, cpFloat dt_inv); +// Run an iteration of the solver on the arbiter. +void cpArbiterApplyImpulse(cpArbiter *arb); diff --git a/PopLib/chipmunk/cpArray.c b/PopLib/chipmunk/cpArray.c new file mode 100644 index 00000000..dace8115 --- /dev/null +++ b/PopLib/chipmunk/cpArray.c @@ -0,0 +1,114 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include +#include + +#include "chipmunk.h" + + +#define CP_ARRAY_INCREMENT 10 + +// NOTE: cpArray is rarely used and will probably go away. + +cpArray* +cpArrayAlloc(void) +{ + return (cpArray *)calloc(1, sizeof(cpArray)); +} + +cpArray* +cpArrayInit(cpArray *arr, int size) +{ + arr->num = 0; + + size = (size ? size : CP_ARRAY_INCREMENT); + arr->max = size; + arr->arr = (void **)malloc(size*sizeof(void**)); + + return arr; +} + +cpArray* +cpArrayNew(int size) +{ + return cpArrayInit(cpArrayAlloc(), size); +} + +void +cpArrayDestroy(cpArray *arr) +{ + free(arr->arr); +} + +void +cpArrayFree(cpArray *arr) +{ + if(!arr) return; + cpArrayDestroy(arr); + free(arr); +} + +void +cpArrayPush(cpArray *arr, void *object) +{ + if(arr->num == arr->max){ + arr->max += CP_ARRAY_INCREMENT; + arr->arr = (void **)realloc(arr->arr, arr->max*sizeof(void**)); + } + + arr->arr[arr->num] = object; + arr->num++; +} + +void +cpArrayDeleteIndex(cpArray *arr, int index) +{ + int last = --arr->num; + arr->arr[index] = arr->arr[last]; +} + +void +cpArrayDeleteObj(cpArray *arr, void *obj) +{ + for(int i=0; inum; i++){ + if(arr->arr[i] == obj){ + cpArrayDeleteIndex(arr, i); + return; + } + } +} + +void +cpArrayEach(cpArray *arr, cpArrayIter iterFunc, void *data) +{ + for(int i=0; inum; i++) + iterFunc(arr->arr[i], data); +} + +int +cpArrayContains(cpArray *arr, void *ptr) +{ + for(int i=0; inum; i++) + if(arr->arr[i] == ptr) return 1; + + return 0; +} diff --git a/PopLib/chipmunk/cpArray.h b/PopLib/chipmunk/cpArray.h new file mode 100644 index 00000000..7e597ad7 --- /dev/null +++ b/PopLib/chipmunk/cpArray.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +// NOTE: cpArray is rarely used and will probably go away. + +typedef struct cpArray{ + int num, max; + void **arr; +} cpArray; + +typedef void (*cpArrayIter)(void *ptr, void *data); + +cpArray *cpArrayAlloc(void); +cpArray *cpArrayInit(cpArray *arr, int size); +cpArray *cpArrayNew(int size); + +void cpArrayDestroy(cpArray *arr); +void cpArrayFree(cpArray *arr); + +void cpArrayClear(cpArray *arr); + +void cpArrayPush(cpArray *arr, void *object); +void cpArrayDeleteIndex(cpArray *arr, int index); +void cpArrayDeleteObj(cpArray *arr, void *obj); + +void cpArrayEach(cpArray *arr, cpArrayIter iterFunc, void *data); +int cpArrayContains(cpArray *arr, void *ptr); diff --git a/PopLib/chipmunk/cpBB.c b/PopLib/chipmunk/cpBB.c new file mode 100644 index 00000000..569855c8 --- /dev/null +++ b/PopLib/chipmunk/cpBB.c @@ -0,0 +1,46 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include + +#include "chipmunk.h" + +cpVect +cpBBClampVect(const cpBB bb, const cpVect v) +{ + cpFloat x = cpfmin(cpfmax(bb.l, v.x), bb.r); + cpFloat y = cpfmin(cpfmax(bb.b, v.y), bb.t); + return cpv(x, y); +} + +cpVect +cpBBWrapVect(const cpBB bb, const cpVect v) +{ + cpFloat ix = fabsf(bb.r - bb.l); + cpFloat modx = fmodf(v.x - bb.l, ix); + cpFloat x = (modx > 0.0f) ? modx : modx + ix; + + cpFloat iy = fabsf(bb.t - bb.b); + cpFloat mody = fmodf(v.y - bb.b, iy); + cpFloat y = (mody > 0.0f) ? mody : mody + iy; + + return cpv(x + bb.l, y + bb.b); +} diff --git a/PopLib/chipmunk/cpBB.h b/PopLib/chipmunk/cpBB.h new file mode 100644 index 00000000..9ddaf480 --- /dev/null +++ b/PopLib/chipmunk/cpBB.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +typedef struct cpBB{ + cpFloat l, b, r ,t; +} cpBB; + +static inline cpBB +cpBBNew(const cpFloat l, const cpFloat b, + const cpFloat r, const cpFloat t) +{ + cpBB bb = {l, b, r, t}; + return bb; +} + +static inline int +cpBBintersects(const cpBB a, const cpBB b) +{ + return (a.l<=b.r && b.l<=a.r && a.b<=b.t && b.b<=a.t); +} + +static inline int +cpBBcontainsBB(const cpBB bb, const cpBB other) +{ + return (bb.l < other.l && bb.r > other.r && bb.b < other.b && bb.t > other.t); +} + +static inline int +cpBBcontainsVect(const cpBB bb, const cpVect v) +{ + return (bb.l < v.x && bb.r > v.x && bb.b < v.y && bb.t > v.y); +} + +cpVect cpBBClampVect(const cpBB bb, const cpVect v); // clamps the vector to lie within the bbox +cpVect cpBBWrapVect(const cpBB bb, const cpVect v); // wrap a vector to a bbox diff --git a/PopLib/chipmunk/cpBody.c b/PopLib/chipmunk/cpBody.c new file mode 100644 index 00000000..532cedc9 --- /dev/null +++ b/PopLib/chipmunk/cpBody.c @@ -0,0 +1,181 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include +#include +#include + +#include "chipmunk.h" + +cpBody* +cpBodyAlloc(void) +{ + return (cpBody *)malloc(sizeof(cpBody)); +} + +cpBody* +cpBodyInit(cpBody *body, cpFloat m, cpFloat i) +{ + cpBodySetMass(body, m); + cpBodySetMoment(body, i); + + body->p = cpvzero; + body->v = cpvzero; + body->f = cpvzero; + + cpBodySetAngle(body, 0.0f); + body->w = 0.0f; + body->t = 0.0f; + + body->v_bias = cpvzero; + body->w_bias = 0.0f; + +// body->active = 1; + + return body; +} + +cpBody* +cpBodyNew(cpFloat m, cpFloat i) +{ + return cpBodyInit(cpBodyAlloc(), m, i); +} + +void cpBodyDestroy(cpBody *body){} + +void +cpBodyFree(cpBody *body) +{ + if(body) cpBodyDestroy(body); + free(body); +} + +void +cpBodySetMass(cpBody *body, cpFloat m) +{ + body->m = m; + body->m_inv = 1.0f/m; +} + +void +cpBodySetMoment(cpBody *body, cpFloat i) +{ + body->i = i; + body->i_inv = 1.0f/i; +} + +// VC6 workaround +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +void +cpBodySetAngle(cpBody *body, cpFloat a) +{ + body->a = fmod(a, (cpFloat)M_PI*2.0f); + body->rot = cpvforangle(a); +} + +void +cpBodySlew(cpBody *body, cpVect pos, cpFloat dt) +{ + cpVect delta = cpvsub(body->p, pos); + body->v = cpvmult(delta, 1.0/dt); +} + +void +cpBodyUpdateVelocity(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt) +{ + body->v = cpvadd(cpvmult(body->v, damping), cpvmult(cpvadd(gravity, cpvmult(body->f, body->m_inv)), dt)); + body->w = body->w*damping + body->t*body->i_inv*dt; +} + +void +cpBodyUpdatePosition(cpBody *body, cpFloat dt) +{ + body->p = cpvadd(body->p, cpvmult(cpvadd(body->v, body->v_bias), dt)); + cpBodySetAngle(body, body->a + (body->w + body->w_bias)*dt); + + body->v_bias = cpvzero; + body->w_bias = 0.0f; +} + +void +cpBodyResetForces(cpBody *body) +{ + body->f = cpvzero; + body->t = 0.0f; +} + +void +cpBodyApplyForce(cpBody *body, cpVect f, cpVect r) +{ + body->f = cpvadd(body->f, f); + body->t += cpvcross(r, f); +} + +void +cpDampedSpring(cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2, cpFloat rlen, cpFloat k, cpFloat dmp, cpFloat dt) +{ + // Calculate the world space anchor coordinates. + cpVect r1 = cpvrotate(anchr1, a->rot); + cpVect r2 = cpvrotate(anchr2, b->rot); + + cpVect delta = cpvsub(cpvadd(b->p, r2), cpvadd(a->p, r1)); + cpFloat dist = cpvlength(delta); + cpVect n = dist ? cpvmult(delta, 1.0f/dist) : cpvzero; + + cpFloat f_spring = (dist - rlen)*k; + + // Calculate the world relative velocities of the anchor points. + cpVect v1 = cpvadd(a->v, cpvmult(cpvperp(r1), a->w)); + cpVect v2 = cpvadd(b->v, cpvmult(cpvperp(r2), b->w)); + + // Calculate the damping force. + // This really should be in the impulse solver and can produce problems when using large damping values. + cpFloat vrn = cpvdot(cpvsub(v2, v1), n); + cpFloat f_damp = vrn*cpfmin(dmp, 1.0f/(dt*(a->m_inv + b->m_inv))); + + // Apply! + cpVect f = cpvmult(n, f_spring + f_damp); + cpBodyApplyForce(a, f, r1); + cpBodyApplyForce(b, cpvneg(f), r2); +} + +//int +//cpBodyMarkLowEnergy(cpBody *body, cpFloat dvsq, int max) +//{ +// cpFloat ke = body->m*cpvdot(body->v, body->v); +// cpFloat re = body->i*body->w*body->w; +// +// if(ke + re > body->m*dvsq) +// body->active = 1; +// else if(body->active) +// body->active = (body->active + 1)%(max + 1); +// else { +// body->v = cpvzero; +// body->v_bias = cpvzero; +// body->w = 0.0f; +// body->w_bias = 0.0f; +// } +// +// return body->active; +//} diff --git a/PopLib/chipmunk/cpBody.h b/PopLib/chipmunk/cpBody.h new file mode 100644 index 00000000..fa279724 --- /dev/null +++ b/PopLib/chipmunk/cpBody.h @@ -0,0 +1,97 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +typedef struct cpBody{ + // Mass and it's inverse. + cpFloat m, m_inv; + // Moment of inertia and it's inverse. + cpFloat i, i_inv; + + // NOTE: v_bias and w_bias are used internally for penetration/joint correction. + // Linear components of motion (position, velocity, and force) + cpVect p, v, f, v_bias; + // Angular components of motion (angle, angular velocity, and torque) + cpFloat a, w, t, w_bias; + // Unit length + cpVect rot; + +// int active; +} cpBody; + +// Basic allocation/destruction functions +cpBody *cpBodyAlloc(void); +cpBody *cpBodyInit(cpBody *body, cpFloat m, cpFloat i); +cpBody *cpBodyNew(cpFloat m, cpFloat i); + +void cpBodyDestroy(cpBody *body); +void cpBodyFree(cpBody *body); + +// Setters for some of the special properties (mandatory!) +void cpBodySetMass(cpBody *body, cpFloat m); +void cpBodySetMoment(cpBody *body, cpFloat i); +void cpBodySetAngle(cpBody *body, cpFloat a); + +// Modify the velocity of an object so that it will +void cpBodySlew(cpBody *body, cpVect pos, cpFloat dt); + +// Integration functions. +void cpBodyUpdateVelocity(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt); +void cpBodyUpdatePosition(cpBody *body, cpFloat dt); + +// Convert body local to world coordinates +static inline cpVect +cpBodyLocal2World(cpBody *body, cpVect v) +{ + return cpvadd(body->p, cpvrotate(v, body->rot)); +} + +// Convert world to body local coordinates +static inline cpVect +cpBodyWorld2Local(cpBody *body, cpVect v) +{ + return cpvunrotate(cpvsub(v, body->p), body->rot); +} + +// Apply an impulse (in world coordinates) to the body. +static inline void +cpBodyApplyImpulse(cpBody *body, cpVect j, cpVect r) +{ + body->v = cpvadd(body->v, cpvmult(j, body->m_inv)); + body->w += body->i_inv*cpvcross(r, j); +} + +// Not intended for external use. Used by cpArbiter.c and cpJoint.c. +static inline void +cpBodyApplyBiasImpulse(cpBody *body, cpVect j, cpVect r) +{ + body->v_bias = cpvadd(body->v_bias, cpvmult(j, body->m_inv)); + body->w_bias += body->i_inv*cpvcross(r, j); +} + +// Zero the forces on a body. +void cpBodyResetForces(cpBody *body); +// Apply a force (in world coordinates) to a body. +void cpBodyApplyForce(cpBody *body, cpVect f, cpVect r); + +// Apply a damped spring force between two bodies. +void cpDampedSpring(cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2, cpFloat rlen, cpFloat k, cpFloat dmp, cpFloat dt); + +//int cpBodyMarkLowEnergy(cpBody *body, cpFloat dvsq, int max); diff --git a/PopLib/chipmunk/cpCollision.c b/PopLib/chipmunk/cpCollision.c new file mode 100644 index 00000000..22e710d3 --- /dev/null +++ b/PopLib/chipmunk/cpCollision.c @@ -0,0 +1,369 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include +#include +#include +#include + +#include "chipmunk.h" + +typedef int (*collisionFunc)(cpShape*, cpShape*, cpContact**); + +static collisionFunc *colfuncs = NULL; + +// Add contact points for circle to circle collisions. +// Used by several collision tests. +static int +circle2circleQuery(cpVect p1, cpVect p2, cpFloat r1, cpFloat r2, cpContact **con) +{ + cpFloat mindist = r1 + r2; + cpVect delta = cpvsub(p2, p1); + cpFloat distsq = cpvlengthsq(delta); + if(distsq >= mindist*mindist) return 0; + + cpFloat dist = sqrtf(distsq); + // To avoid singularities, do nothing in the case of dist = 0. + cpFloat non_zero_dist = (dist ? dist : INFINITY); + + // Allocate and initialize the contact. + (*con) = (cpContact *)malloc(sizeof(cpContact)); + cpContactInit( + (*con), + cpvadd(p1, cpvmult(delta, 0.5 + (r1 - 0.5*mindist)/non_zero_dist)), + cpvmult(delta, 1.0/non_zero_dist), + dist - mindist, + 0 + ); + + return 1; +} + +// Collide circle shapes. +static int +circle2circle(cpShape *shape1, cpShape *shape2, cpContact **arr) +{ + cpCircleShape *circ1 = (cpCircleShape *)shape1; + cpCircleShape *circ2 = (cpCircleShape *)shape2; + + return circle2circleQuery(circ1->tc, circ2->tc, circ1->r, circ2->r, arr); +} + +// Collide circles to segment shapes. +static int +circle2segment(cpShape *circleShape, cpShape *segmentShape, cpContact **con) +{ + cpCircleShape *circ = (cpCircleShape *)circleShape; + cpSegmentShape *seg = (cpSegmentShape *)segmentShape; + + // Calculate normal distance from segment. + cpFloat dn = cpvdot(seg->tn, circ->tc) - cpvdot(seg->ta, seg->tn); + cpFloat dist = fabs(dn) - circ->r - seg->r; + if(dist > 0.0f) return 0; + + // Calculate tangential distance along segment. + cpFloat dt = -cpvcross(seg->tn, circ->tc); + cpFloat dtMin = -cpvcross(seg->tn, seg->ta); + cpFloat dtMax = -cpvcross(seg->tn, seg->tb); + + // Decision tree to decide which feature of the segment to collide with. + if(dt < dtMin){ + if(dt < (dtMin - circ->r)){ + return 0; + } else { + return circle2circleQuery(circ->tc, seg->ta, circ->r, seg->r, con); + } + } else { + if(dt < dtMax){ + cpVect n = (dn < 0.0f) ? seg->tn : cpvneg(seg->tn); + (*con) = (cpContact *)malloc(sizeof(cpContact)); + cpContactInit( + (*con), + cpvadd(circ->tc, cpvmult(n, circ->r + dist*0.5f)), + n, + dist, + 0 + ); + return 1; + } else { + if(dt < (dtMax + circ->r)) { + return circle2circleQuery(circ->tc, seg->tb, circ->r, seg->r, con); + } else { + return 0; + } + } + } + + return 1; +} + +// Helper function for allocating contact point lists. +static cpContact * +addContactPoint(cpContact **arr, int *max, int *num) +{ + if(*arr == NULL){ + // Allocate the array if it hasn't been done. + (*max) = 2; + (*num) = 0; + (*arr) = (cpContact *)malloc((*max)*sizeof(cpContact)); + } else if(*num == *max){ + // Extend it if necessary. + (*max) *= 2; + (*arr) = (cpContact *)realloc(*arr, (*max)*sizeof(cpContact)); + } + + cpContact *con = &(*arr)[*num]; + (*num)++; + + return con; +} + +// Find the minimum separating axis for the give poly and axis list. +static inline int +findMSA(cpPolyShape *poly, cpPolyShapeAxis *axes, int num, cpFloat *min_out) +{ + int min_index = 0; + cpFloat min = cpPolyShapeValueOnAxis(poly, axes->n, axes->d); + if(min > 0.0) return -1; + + for(int i=1; i 0.0) { + return -1; + } else if(dist > min){ + min = dist; + min_index = i; + } + } + + (*min_out) = min; + return min_index; +} + +// Add contacts for penetrating vertexes. +static inline int +findVerts(cpContact **arr, cpPolyShape *poly1, cpPolyShape *poly2, cpVect n, cpFloat dist) +{ + int max = 0; + int num = 0; + + for(int i=0; inumVerts; i++){ + cpVect v = poly1->tVerts[i]; + if(cpPolyShapeContainsVert(poly2, v)) + cpContactInit(addContactPoint(arr, &max, &num), v, n, dist, CP_HASH_PAIR(poly1, i)); + } + + for(int i=0; inumVerts; i++){ + cpVect v = poly2->tVerts[i]; + if(cpPolyShapeContainsVert(poly1, v)) + cpContactInit(addContactPoint(arr, &max, &num), v, n, dist, CP_HASH_PAIR(poly2, i)); + } + + // if(!num) + // addContactPoint(arr, &size, &num, cpContactNew(shape1->body->p, n, dist, 0)); + + return num; +} + +// Collide poly shapes together. +static int +poly2poly(cpShape *shape1, cpShape *shape2, cpContact **arr) +{ + cpPolyShape *poly1 = (cpPolyShape *)shape1; + cpPolyShape *poly2 = (cpPolyShape *)shape2; + + cpFloat min1; + int mini1 = findMSA(poly2, poly1->tAxes, poly1->numVerts, &min1); + if(mini1 == -1) return 0; + + cpFloat min2; + int mini2 = findMSA(poly1, poly2->tAxes, poly2->numVerts, &min2); + if(mini2 == -1) return 0; + + // There is overlap, find the penetrating verts + if(min1 > min2) + return findVerts(arr, poly1, poly2, poly1->tAxes[mini1].n, min1); + else + return findVerts(arr, poly1, poly2, cpvneg(poly2->tAxes[mini2].n), min2); +} + +// Like cpPolyValueOnAxis(), but for segments. +static inline float +segValueOnAxis(cpSegmentShape *seg, cpVect n, cpFloat d) +{ + cpFloat a = cpvdot(n, seg->ta) - seg->r; + cpFloat b = cpvdot(n, seg->tb) - seg->r; + return cpfmin(a, b) - d; +} + +// Identify vertexes that have penetrated the segment. +static inline void +findPointsBehindSeg(cpContact **arr, int *max, int *num, cpSegmentShape *seg, cpPolyShape *poly, cpFloat pDist, cpFloat coef) +{ + cpFloat dta = cpvcross(seg->tn, seg->ta); + cpFloat dtb = cpvcross(seg->tn, seg->tb); + cpVect n = cpvmult(seg->tn, coef); + + for(int i=0; inumVerts; i++){ + cpVect v = poly->tVerts[i]; + if(cpvdot(v, n) < cpvdot(seg->tn, seg->ta)*coef + seg->r){ + cpFloat dt = cpvcross(seg->tn, v); + if(dta >= dt && dt >= dtb){ + cpContactInit(addContactPoint(arr, max, num), v, n, pDist, CP_HASH_PAIR(poly, i)); + } + } + } +} + +// This one is complicated and gross. Just don't go there... +// TODO: Comment me! +static int +seg2poly(cpShape *shape1, cpShape *shape2, cpContact **arr) +{ + cpSegmentShape *seg = (cpSegmentShape *)shape1; + cpPolyShape *poly = (cpPolyShape *)shape2; + cpPolyShapeAxis *axes = poly->tAxes; + + cpFloat segD = cpvdot(seg->tn, seg->ta); + cpFloat minNorm = cpPolyShapeValueOnAxis(poly, seg->tn, segD) - seg->r; + cpFloat minNeg = cpPolyShapeValueOnAxis(poly, cpvneg(seg->tn), -segD) - seg->r; + if(minNeg > 0.0f || minNorm > 0.0f) return 0; + + int mini = 0; + cpFloat poly_min = segValueOnAxis(seg, axes->n, axes->d); + if(poly_min > 0.0f) return 0; + for(int i=0; inumVerts; i++){ + cpFloat dist = segValueOnAxis(seg, axes[i].n, axes[i].d); + if(dist > 0.0f){ + return 0; + } else if(dist > poly_min){ + poly_min = dist; + mini = i; + } + } + + int max = 0; + int num = 0; + + cpVect poly_n = cpvneg(axes[mini].n); + + cpVect va = cpvadd(seg->ta, cpvmult(poly_n, seg->r)); + cpVect vb = cpvadd(seg->tb, cpvmult(poly_n, seg->r)); + if(cpPolyShapeContainsVert(poly, va)) + cpContactInit(addContactPoint(arr, &max, &num), va, poly_n, poly_min, CP_HASH_PAIR(seg, 0)); + if(cpPolyShapeContainsVert(poly, vb)) + cpContactInit(addContactPoint(arr, &max, &num), vb, poly_n, poly_min, CP_HASH_PAIR(seg, 1)); + + // Floating point precision problems here. + // This will have to do for now. + poly_min -= cp_collision_slop; + if(minNorm >= poly_min || minNeg >= poly_min) { + if(minNorm > minNeg) + findPointsBehindSeg(arr, &max, &num, seg, poly, minNorm, 1.0f); + else + findPointsBehindSeg(arr, &max, &num, seg, poly, minNeg, -1.0f); + } + + return num; +} + +// This one is less gross, but still gross. +// TODO: Comment me! +static int +circle2poly(cpShape *shape1, cpShape *shape2, cpContact **con) +{ + cpCircleShape *circ = (cpCircleShape *)shape1; + cpPolyShape *poly = (cpPolyShape *)shape2; + cpPolyShapeAxis *axes = poly->tAxes; + + int mini = 0; + cpFloat min = cpvdot(axes->n, circ->tc) - axes->d - circ->r; + for(int i=0; inumVerts; i++){ + cpFloat dist = cpvdot(axes[i].n, circ->tc) - axes[i].d - circ->r; + if(dist > 0.0){ + return 0; + } else if(dist > min) { + min = dist; + mini = i; + } + } + + cpVect n = axes[mini].n; + cpVect a = poly->tVerts[mini]; + cpVect b = poly->tVerts[(mini + 1)%poly->numVerts]; + cpFloat dta = cpvcross(n, a); + cpFloat dtb = cpvcross(n, b); + cpFloat dt = cpvcross(n, circ->tc); + + if(dt < dtb){ + return circle2circleQuery(circ->tc, b, circ->r, 0.0f, con); + } else if(dt < dta) { + (*con) = (cpContact *)malloc(sizeof(cpContact)); + cpContactInit( + (*con), + cpvsub(circ->tc, cpvmult(n, circ->r + min/2.0f)), + cpvneg(n), + min, + 0 + ); + + return 1; + } else { + return circle2circleQuery(circ->tc, a, circ->r, 0.0f, con); + } +} + +static void +addColFunc(cpShapeType a, cpShapeType b, collisionFunc func) +{ + colfuncs[a + b*CP_NUM_SHAPES] = func; +} + +#ifdef __cplusplus +extern "C" { +#endif + // Initializes the array of collision functions. + // Called by cpInitChipmunk(). + void + cpInitCollisionFuncs(void) + { + if(!colfuncs) + colfuncs = (collisionFunc *)calloc(CP_NUM_SHAPES*CP_NUM_SHAPES, sizeof(collisionFunc)); + + addColFunc(CP_CIRCLE_SHAPE, CP_CIRCLE_SHAPE, circle2circle); + addColFunc(CP_CIRCLE_SHAPE, CP_SEGMENT_SHAPE, circle2segment); + addColFunc(CP_SEGMENT_SHAPE, CP_POLY_SHAPE, seg2poly); + addColFunc(CP_CIRCLE_SHAPE, CP_POLY_SHAPE, circle2poly); + addColFunc(CP_POLY_SHAPE, CP_POLY_SHAPE, poly2poly); + } +#ifdef __cplusplus +} +#endif + +int +cpCollideShapes(cpShape *a, cpShape *b, cpContact **arr) +{ + // Their shape types must be in order. + assert(a->type <= b->type); + + collisionFunc cfunc = colfuncs[a->type + b->type*CP_NUM_SHAPES]; + return (cfunc) ? cfunc(a, b, arr) : 0; +} diff --git a/PopLib/chipmunk/cpCollision.h b/PopLib/chipmunk/cpCollision.h new file mode 100644 index 00000000..e87a6db1 --- /dev/null +++ b/PopLib/chipmunk/cpCollision.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +// Collides two cpShape structures. (this function is lonely :( ) +int cpCollideShapes(cpShape *a, cpShape *b, cpContact **arr); diff --git a/PopLib/chipmunk/cpHashSet.c b/PopLib/chipmunk/cpHashSet.c new file mode 100644 index 00000000..ae95ea4b --- /dev/null +++ b/PopLib/chipmunk/cpHashSet.c @@ -0,0 +1,219 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include +#include + +#include "chipmunk.h" +#include "prime.h" + +void +cpHashSetDestroy(cpHashSet *set) +{ + // Free the chains. + for(int i=0; isize; i++){ + // Free the bins in the chain. + cpHashSetBin *bin = set->table[i]; + while(bin){ + cpHashSetBin *next = bin->next; + free(bin); + bin = next; + } + } + + // Free the table. + free(set->table); +} + +void +cpHashSetFree(cpHashSet *set) +{ + if(set) cpHashSetDestroy(set); + free(set); +} + +cpHashSet * +cpHashSetAlloc(void) +{ + return (cpHashSet *)calloc(1, sizeof(cpHashSet)); +} + +cpHashSet * +cpHashSetInit(cpHashSet *set, int size, cpHashSetEqlFunc eqlFunc, cpHashSetTransFunc trans) +{ + set->size = next_prime(size); + set->entries = 0; + + set->eql = eqlFunc; + set->trans = trans; + + set->default_value = NULL; + + set->table = (cpHashSetBin **)calloc(set->size, sizeof(cpHashSetBin *)); + + return set; +} + +cpHashSet * +cpHashSetNew(int size, cpHashSetEqlFunc eqlFunc, cpHashSetTransFunc trans) +{ + return cpHashSetInit(cpHashSetAlloc(), size, eqlFunc, trans); +} + +static int +setIsFull(cpHashSet *set) +{ + return (set->entries >= set->size); +} + +static void +cpHashSetResize(cpHashSet *set) +{ + // Get the next approximate doubled prime. + int newSize = next_prime(set->size + 1); + // Allocate a new table. + cpHashSetBin **newTable = (cpHashSetBin **)calloc(newSize, sizeof(cpHashSetBin *)); + + // Iterate over the chains. + for(int i=0; isize; i++){ + // Rehash the bins into the new table. + cpHashSetBin *bin = set->table[i]; + while(bin){ + cpHashSetBin *next = bin->next; + + int index = bin->hash%newSize; + bin->next = newTable[index]; + newTable[index] = bin; + + bin = next; + } + } + + free(set->table); + + set->table = newTable; + set->size = newSize; +} + +void * +cpHashSetInsert(cpHashSet *set, unsigned int hash, void *ptr, void *data) +{ + int index = hash%set->size; + + // Find the bin with the matching element. + cpHashSetBin *bin = set->table[index]; + while(bin && !set->eql(ptr, bin->elt)) + bin = bin->next; + + // Create it necessary. + if(!bin){ + bin = (cpHashSetBin *)malloc(sizeof(cpHashSetBin)); + bin->hash = hash; + bin->elt = set->trans(ptr, data); // Transform the pointer. + + bin->next = set->table[index]; + set->table[index] = bin; + + set->entries++; + + // Resize the set if it's full. + if(setIsFull(set)) + cpHashSetResize(set); + } + + return bin->elt; +} + +void * +cpHashSetRemove(cpHashSet *set, unsigned int hash, void *ptr) +{ + int index = hash%set->size; + + // Pointer to the previous bin pointer. + cpHashSetBin **prev_ptr = &set->table[index]; + // Pointer the the current bin. + cpHashSetBin *bin = set->table[index]; + + // Find the bin + while(bin && !set->eql(ptr, bin->elt)){ + prev_ptr = &bin->next; + bin = bin->next; + } + + // Remove it if it exists. + if(bin){ + // Update the previos bin pointer to point to the next bin. + (*prev_ptr) = bin->next; + set->entries--; + + void *return_value = bin->elt; + free(bin); + return return_value; + } + + return NULL; +} + +void * +cpHashSetFind(cpHashSet *set, unsigned int hash, void *ptr) +{ + int index = hash%set->size; + cpHashSetBin *bin = set->table[index]; + while(bin && !set->eql(ptr, bin->elt)) + bin = bin->next; + + return (bin ? bin->elt : set->default_value); +} + +void +cpHashSetEach(cpHashSet *set, cpHashSetIterFunc func, void *data) +{ + for(int i=0; isize; i++){ + cpHashSetBin *bin; + for(bin = set->table[i]; bin; bin = bin->next) + func(bin->elt, data); + } +} + +void +cpHashSetReject(cpHashSet *set, cpHashSetRejectFunc func, void *data) +{ + // Iterate over all the chains. + for(int i=0; isize; i++){ + // The rest works similarly to cpHashSetRemove() above. + cpHashSetBin **prev_ptr = &set->table[i]; + cpHashSetBin *bin = set->table[i]; + while(bin){ + cpHashSetBin *next = bin->next; + + if(func(bin->elt, data)){ + prev_ptr = &bin->next; + } else { + (*prev_ptr) = next; + + set->entries--; + free(bin); + } + + bin = next; + } + } +} diff --git a/PopLib/chipmunk/cpHashSet.h b/PopLib/chipmunk/cpHashSet.h new file mode 100644 index 00000000..edd453c0 --- /dev/null +++ b/PopLib/chipmunk/cpHashSet.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +// cpHashSet uses a chained hashtable implementation. +// Other than the transformation functions, there is nothing fancy going on. + +// cpHashSetBin's form the linked lists in the chained hash table. +typedef struct cpHashSetBin { + // Pointer to the element. + void *elt; + // Hash value of the element. + unsigned int hash; + // Next element in the chain. + struct cpHashSetBin *next; +} cpHashSetBin; + +// Equality function. Returns true if ptr is equal to elt. +typedef int (*cpHashSetEqlFunc)(void *ptr, void *elt); +// Used by cpHashSetInsert(). Called to transform the ptr into an element. +typedef void *(*cpHashSetTransFunc)(void *ptr, void *data); +// Iterator function for a hashset. +typedef void (*cpHashSetIterFunc)(void *elt, void *data); +// Reject function. Returns true if elt should be dropped. +typedef int (*cpHashSetRejectFunc)(void *elt, void *data); + +typedef struct cpHashSet { + // Number of elements stored in the table. + int entries; + // Number of cells in the table. + int size; + + cpHashSetEqlFunc eql; + cpHashSetTransFunc trans; + + // Default value returned by cpHashSetFind() when no element is found. + // Defaults to NULL. + void *default_value; + + cpHashSetBin **table; +} cpHashSet; + +// Basic allocation/destruction functions. +void cpHashSetDestroy(cpHashSet *set); +void cpHashSetFree(cpHashSet *set); + +cpHashSet *cpHashSetAlloc(void); +cpHashSet *cpHashSetInit(cpHashSet *set, int size, cpHashSetEqlFunc eqlFunc, cpHashSetTransFunc trans); +cpHashSet *cpHashSetNew(int size, cpHashSetEqlFunc eqlFunc, cpHashSetTransFunc trans); + +// Insert an element into the set, returns the element. +// If it doesn't already exist, the transformation function is applied. +void *cpHashSetInsert(cpHashSet *set, unsigned int hash, void *ptr, void *data); +// Remove and return an element from the set. +void *cpHashSetRemove(cpHashSet *set, unsigned int hash, void *ptr); +// Find an element in the set. Returns the default value if the element isn't found. +void *cpHashSetFind(cpHashSet *set, unsigned int hash, void *ptr); + +// Iterate over a hashset. +void cpHashSetEach(cpHashSet *set, cpHashSetIterFunc func, void *data); +// Iterate over a hashset while rejecting certain elements. +void cpHashSetReject(cpHashSet *set, cpHashSetRejectFunc func, void *data); diff --git a/PopLib/chipmunk/cpJoint.c b/PopLib/chipmunk/cpJoint.c new file mode 100644 index 00000000..b54878fe --- /dev/null +++ b/PopLib/chipmunk/cpJoint.c @@ -0,0 +1,534 @@ +/* Copyright (c) 2007 Scott Lembcke +* +* 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. +*/ + +#include +#include + +#include "chipmunk.h" + +// TODO: Comment me! + +cpFloat cp_joint_bias_coef = 0.1f; + +void cpJointDestroy(cpJoint *joint){} + +void +cpJointFree(cpJoint *joint) +{ + if(joint) cpJointDestroy(joint); + free(joint); +} + +static void +pinJointPreStep(cpJoint *joint, cpFloat dt_inv) +{ + cpBody *a = joint->a; + cpBody *b = joint->b; + cpPinJoint *jnt = (cpPinJoint *)joint; + + cpFloat mass_sum = a->m_inv + b->m_inv; + + jnt->r1 = cpvrotate(jnt->anchr1, a->rot); + jnt->r2 = cpvrotate(jnt->anchr2, b->rot); + + cpVect delta = cpvsub(cpvadd(b->p, jnt->r2), cpvadd(a->p, jnt->r1)); + cpFloat dist = cpvlength(delta); + jnt->n = cpvmult(delta, 1.0f/(dist ? dist : INFINITY)); + + // calculate mass normal + cpFloat r1cn = cpvcross(jnt->r1, jnt->n); + cpFloat r2cn = cpvcross(jnt->r2, jnt->n); + cpFloat kn = mass_sum + a->i_inv*r1cn*r1cn + b->i_inv*r2cn*r2cn; + jnt->nMass = 1.0f/kn; + + // calculate bias velocity + jnt->bias = -cp_joint_bias_coef*dt_inv*(dist - jnt->dist); + jnt->jBias = 0.0f; + + // apply accumulated impulse + cpVect j = cpvmult(jnt->n, jnt->jnAcc); + cpBodyApplyImpulse(a, cpvneg(j), jnt->r1); + cpBodyApplyImpulse(b, j, jnt->r2); +} + +static void +pinJointApplyImpulse(cpJoint *joint) +{ + cpBody *a = joint->a; + cpBody *b = joint->b; + + cpPinJoint *jnt = (cpPinJoint *)joint; + cpVect n = jnt->n; + cpVect r1 = jnt->r1; + cpVect r2 = jnt->r2; + + //calculate bias impulse + cpVect vb1 = cpvadd(a->v_bias, cpvmult(cpvperp(r1), a->w_bias)); + cpVect vb2 = cpvadd(b->v_bias, cpvmult(cpvperp(r2), b->w_bias)); + cpFloat vbn = cpvdot(cpvsub(vb2, vb1), n); + + cpFloat jbn = (jnt->bias - vbn)*jnt->nMass; + jnt->jBias += jbn; + + cpVect jb = cpvmult(n, jbn); + cpBodyApplyBiasImpulse(a, cpvneg(jb), r1); + cpBodyApplyBiasImpulse(b, jb, r2); + + // compute relative velocity + cpVect v1 = cpvadd(a->v, cpvmult(cpvperp(r1), a->w)); + cpVect v2 = cpvadd(b->v, cpvmult(cpvperp(r2), b->w)); + cpFloat vrn = cpvdot(cpvsub(v2, v1), n); + + // compute normal impulse + cpFloat jn = -vrn*jnt->nMass; + jnt->jnAcc += jn; + + // apply impulse + cpVect j = cpvmult(n, jn); + cpBodyApplyImpulse(a, cpvneg(j), r1); + cpBodyApplyImpulse(b, j, r2); +} + +cpPinJoint * +cpPinJointAlloc(void) +{ + return (cpPinJoint *)malloc(sizeof(cpPinJoint)); +} + +cpPinJoint * +cpPinJointInit(cpPinJoint *joint, cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2) +{ + joint->joint.preStep = &pinJointPreStep; + joint->joint.applyImpulse = &pinJointApplyImpulse; + + joint->joint.a = a; + joint->joint.b = b; + + ((cpJoint*)joint)->type = CP_PIN_JOINT; + + joint->anchr1 = anchr1; + joint->anchr2 = anchr2; + + cpVect p1 = cpvadd(a->p, cpvrotate(anchr1, a->rot)); + cpVect p2 = cpvadd(b->p, cpvrotate(anchr2, b->rot)); + joint->dist = cpvlength(cpvsub(p2, p1)); + + joint->jnAcc = 0.0; + + return joint; +} + +cpJoint * +cpPinJointNew(cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2) +{ + return (cpJoint *)cpPinJointInit(cpPinJointAlloc(), a, b, anchr1, anchr2); +} + +static void +SlideJointPreStep(cpJoint *joint, cpFloat dt_inv) +{ + cpBody *a = joint->a; + cpBody *b = joint->b; + cpSlideJoint *jnt = (cpSlideJoint *)joint; + + cpFloat mass_sum = a->m_inv + b->m_inv; + + jnt->r1 = cpvrotate(jnt->anchr1, a->rot); + jnt->r2 = cpvrotate(jnt->anchr2, b->rot); + + cpVect delta = cpvsub(cpvadd(b->p, jnt->r2), cpvadd(a->p, jnt->r1)); + cpFloat dist = cpvlength(delta); + cpFloat pdist = 0.0; + if(dist > jnt->max) { + pdist = dist - jnt->max; + } else if(dist < jnt->min) { + pdist = jnt->min - dist; + dist = -dist; + } + jnt->n = cpvmult(delta, 1.0f/(dist ? dist : INFINITY)); + + // calculate mass normal + cpFloat r1cn = cpvcross(jnt->r1, jnt->n); + cpFloat r2cn = cpvcross(jnt->r2, jnt->n); + cpFloat kn = mass_sum + a->i_inv*r1cn*r1cn + b->i_inv*r2cn*r2cn; + jnt->nMass = 1.0f/kn; + + // calculate bias velocity + jnt->bias = -cp_joint_bias_coef*dt_inv*(pdist); + jnt->jBias = 0.0f; + + // apply accumulated impulse + if(!jnt->bias) jnt->jnAcc = 0.0f; + cpVect j = cpvmult(jnt->n, jnt->jnAcc); + cpBodyApplyImpulse(a, cpvneg(j), jnt->r1); + cpBodyApplyImpulse(b, j, jnt->r2); +} + +static void +SlideJointApplyImpulse(cpJoint *joint) +{ + cpSlideJoint *jnt = (cpSlideJoint *)joint; + if(!jnt->bias) return; + + cpBody *a = joint->a; + cpBody *b = joint->b; + + cpVect n = jnt->n; + cpVect r1 = jnt->r1; + cpVect r2 = jnt->r2; + + //calculate bias impulse + cpVect vb1 = cpvadd(a->v_bias, cpvmult(cpvperp(r1), a->w_bias)); + cpVect vb2 = cpvadd(b->v_bias, cpvmult(cpvperp(r2), b->w_bias)); + cpFloat vbn = cpvdot(cpvsub(vb2, vb1), n); + + cpFloat jbn = (jnt->bias - vbn)*jnt->nMass; + cpFloat jbnOld = jnt->jBias; + jnt->jBias = cpfmin(jbnOld + jbn, 0.0f); + jbn = jnt->jBias - jbnOld; + + cpVect jb = cpvmult(n, jbn); + cpBodyApplyBiasImpulse(a, cpvneg(jb), r1); + cpBodyApplyBiasImpulse(b, jb, r2); + + // compute relative velocity + cpVect v1 = cpvadd(a->v, cpvmult(cpvperp(r1), a->w)); + cpVect v2 = cpvadd(b->v, cpvmult(cpvperp(r2), b->w)); + cpFloat vrn = cpvdot(cpvsub(v2, v1), n); + + // compute normal impulse + cpFloat jn = -vrn*jnt->nMass; + cpFloat jnOld = jnt->jnAcc; + jnt->jnAcc = cpfmin(jnOld + jn, 0.0f); + jn = jnt->jnAcc - jnOld; + + // apply impulse + cpVect j = cpvmult(n, jn); + cpBodyApplyImpulse(a, cpvneg(j), r1); + cpBodyApplyImpulse(b, j, r2); +} + +cpSlideJoint * +cpSlideJointAlloc(void) +{ + return (cpSlideJoint *)malloc(sizeof(cpSlideJoint)); +} + +cpSlideJoint * +cpSlideJointInit(cpSlideJoint *joint, cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2, cpFloat min, cpFloat max) +{ + joint->joint.preStep = &SlideJointPreStep; + joint->joint.applyImpulse = &SlideJointApplyImpulse; + + joint->joint.a = a; + joint->joint.b = b; + + ((cpJoint*)joint)->type = CP_SLIDE_JOINT; + + joint->anchr1 = anchr1; + joint->anchr2 = anchr2; + joint->min = min; + joint->max = max; + + joint->jnAcc = 0.0; + + return joint; +} + +cpJoint * +cpSlideJointNew(cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2, cpFloat min, cpFloat max) +{ + return (cpJoint *)cpSlideJointInit(cpSlideJointAlloc(), a, b, anchr1, anchr2, min, max); +} + + + + +static void +pivotJointPreStep(cpJoint *joint, cpFloat dt_inv) +{ + cpBody *a = joint->a; + cpBody *b = joint->b; + cpPivotJoint *jnt = (cpPivotJoint *)joint; + + jnt->r1 = cpvrotate(jnt->anchr1, a->rot); + jnt->r2 = cpvrotate(jnt->anchr2, b->rot); + + // calculate mass matrix + // If I wasn't lazy, this wouldn't be so gross... + cpFloat k11, k12, k21, k22; + + cpFloat m_sum = a->m_inv + b->m_inv; + k11 = m_sum; k12 = 0.0f; + k21 = 0.0f; k22 = m_sum; + + cpFloat r1xsq = jnt->r1.x * jnt->r1.x * a->i_inv; + cpFloat r1ysq = jnt->r1.y * jnt->r1.y * a->i_inv; + cpFloat r1nxy = -jnt->r1.x * jnt->r1.y * a->i_inv; + k11 += r1ysq; k12 += r1nxy; + k21 += r1nxy; k22 += r1xsq; + + cpFloat r2xsq = jnt->r2.x * jnt->r2.x * b->i_inv; + cpFloat r2ysq = jnt->r2.y * jnt->r2.y * b->i_inv; + cpFloat r2nxy = -jnt->r2.x * jnt->r2.y * b->i_inv; + k11 += r2ysq; k12 += r2nxy; + k21 += r2nxy; k22 += r2xsq; + + cpFloat det_inv = 1.0f/(k11*k22 - k12*k21); + jnt->k1 = cpv( k22*det_inv, -k12*det_inv); + jnt->k2 = cpv(-k21*det_inv, k11*det_inv); + + + // calculate bias velocity + cpVect delta = cpvsub(cpvadd(b->p, jnt->r2), cpvadd(a->p, jnt->r1)); + jnt->bias = cpvmult(delta, -cp_joint_bias_coef*dt_inv); + jnt->jBias = cpvzero; + + // apply accumulated impulse + cpBodyApplyImpulse(a, cpvneg(jnt->jAcc), jnt->r1); + cpBodyApplyImpulse(b, jnt->jAcc, jnt->r2); +} + +static void +pivotJointApplyImpulse(cpJoint *joint) +{ + cpBody *a = joint->a; + cpBody *b = joint->b; + + cpPivotJoint *jnt = (cpPivotJoint *)joint; + cpVect r1 = jnt->r1; + cpVect r2 = jnt->r2; + cpVect k1 = jnt->k1; + cpVect k2 = jnt->k2; + + //calculate bias impulse + cpVect vb1 = cpvadd(a->v_bias, cpvmult(cpvperp(r1), a->w_bias)); + cpVect vb2 = cpvadd(b->v_bias, cpvmult(cpvperp(r2), b->w_bias)); + cpVect vbr = cpvsub(jnt->bias, cpvsub(vb2, vb1)); + + cpVect jb = cpv(cpvdot(vbr, k1), cpvdot(vbr, k2)); + jnt->jBias = cpvadd(jnt->jBias, jb); + + cpBodyApplyBiasImpulse(a, cpvneg(jb), r1); + cpBodyApplyBiasImpulse(b, jb, r2); + + // compute relative velocity + cpVect v1 = cpvadd(a->v, cpvmult(cpvperp(r1), a->w)); + cpVect v2 = cpvadd(b->v, cpvmult(cpvperp(r2), b->w)); + cpVect vr = cpvsub(v2, v1); + + // compute normal impulse + cpVect j = cpv(-cpvdot(vr, k1), -cpvdot(vr, k2)); + jnt->jAcc = cpvadd(jnt->jAcc, j); + + // apply impulse + cpBodyApplyImpulse(a, cpvneg(j), r1); + cpBodyApplyImpulse(b, j, r2); +} + +cpPivotJoint * +cpPivotJointAlloc(void) +{ + return (cpPivotJoint *)malloc(sizeof(cpPivotJoint)); +} + +cpPivotJoint * +cpPivotJointInit(cpPivotJoint *joint, cpBody *a, cpBody *b, cpVect pivot) +{ + joint->joint.preStep = &pivotJointPreStep; + joint->joint.applyImpulse = &pivotJointApplyImpulse; + + joint->joint.a = a; + joint->joint.b = b; + + ((cpJoint*)joint)->type = CP_PIVOT_JOINT; + + joint->anchr1 = cpvunrotate(cpvsub(pivot, a->p), a->rot); + joint->anchr2 = cpvunrotate(cpvsub(pivot, b->p), b->rot); + + joint->jAcc = cpvzero; + + return joint; +} + +cpJoint * +cpPivotJointNew(cpBody *a, cpBody *b, cpVect pivot) +{ + return (cpJoint *)cpPivotJointInit(cpPivotJointAlloc(), a, b, pivot); +} + +static void +grooveJointPreStep(cpJoint *joint, cpFloat dt_inv) +{ + cpBody *a = joint->a; + cpBody *b = joint->b; + cpGrooveJoint *jnt = (cpGrooveJoint *)joint; + + // calculate endpoints in worldspace + cpVect ta = cpBodyLocal2World(a, jnt->grv_a); + cpVect tb = cpBodyLocal2World(a, jnt->grv_b); + + // calculate axis + cpVect n = cpvrotate(jnt->grv_n, a->rot); + cpFloat d = cpvdot(ta, n); + + jnt->grv_tn = n; + jnt->r2 = cpvrotate(jnt->anchr2, b->rot); + + // calculate tangential distance along the axis of r2 + cpFloat td = cpvcross(cpvadd(b->p, jnt->r2), n); + // calculate clamping factor and r2 + if(td < cpvcross(ta, n)){ + jnt->clamp = 1.0f; + jnt->r1 = cpvsub(ta, a->p); + } else if(td > cpvcross(tb, n)){ + jnt->clamp = -1.0f; + jnt->r1 = cpvsub(tb, a->p); + } else { + jnt->clamp = 0.0f; + jnt->r1 = cpvadd(cpvmult(cpvperp(n), -td), cpvmult(n, d)); + } + + // calculate mass matrix + // If I wasn't lazy and wrote a proper matrix class, this wouldn't be so gross... + cpFloat k11, k12, k21, k22; + cpFloat m_sum = a->m_inv + b->m_inv; + + // start with I*m_sum + k11 = m_sum; k12 = 0.0f; + k21 = 0.0f; k22 = m_sum; + + // add the influence from r1 + cpFloat r1xsq = jnt->r1.x * jnt->r1.x * a->i_inv; + cpFloat r1ysq = jnt->r1.y * jnt->r1.y * a->i_inv; + cpFloat r1nxy = -jnt->r1.x * jnt->r1.y * a->i_inv; + k11 += r1ysq; k12 += r1nxy; + k21 += r1nxy; k22 += r1xsq; + + // add the influnce from r2 + cpFloat r2xsq = jnt->r2.x * jnt->r2.x * b->i_inv; + cpFloat r2ysq = jnt->r2.y * jnt->r2.y * b->i_inv; + cpFloat r2nxy = -jnt->r2.x * jnt->r2.y * b->i_inv; + k11 += r2ysq; k12 += r2nxy; + k21 += r2nxy; k22 += r2xsq; + + // invert + cpFloat det_inv = 1.0f/(k11*k22 - k12*k21); + jnt->k1 = cpv( k22*det_inv, -k12*det_inv); + jnt->k2 = cpv(-k21*det_inv, k11*det_inv); + + + // calculate bias velocity + cpVect delta = cpvsub(cpvadd(b->p, jnt->r2), cpvadd(a->p, jnt->r1)); + jnt->bias = cpvmult(delta, -cp_joint_bias_coef*dt_inv); + jnt->jBias = cpvzero; + + // apply accumulated impulse + cpBodyApplyImpulse(a, cpvneg(jnt->jAcc), jnt->r1); + cpBodyApplyImpulse(b, jnt->jAcc, jnt->r2); +} + +static inline cpVect +grooveConstrain(cpGrooveJoint *jnt, cpVect j){ + cpVect n = jnt->grv_tn; + cpVect jn = cpvmult(n, cpvdot(j, n)); + + cpVect t = cpvperp(n); + cpFloat coef = (jnt->clamp*cpvcross(j, n) > 0.0f) ? 1.0f : 0.0f; + cpVect jt = cpvmult(t, cpvdot(j, t)*coef); + + return cpvadd(jn, jt); +} + +static void +grooveJointApplyImpulse(cpJoint *joint) +{ + cpBody *a = joint->a; + cpBody *b = joint->b; + + cpGrooveJoint *jnt = (cpGrooveJoint *)joint; + cpVect r1 = jnt->r1; + cpVect r2 = jnt->r2; + cpVect k1 = jnt->k1; + cpVect k2 = jnt->k2; + + //calculate bias impulse + cpVect vb1 = cpvadd(a->v_bias, cpvmult(cpvperp(r1), a->w_bias)); + cpVect vb2 = cpvadd(b->v_bias, cpvmult(cpvperp(r2), b->w_bias)); + cpVect vbr = cpvsub(jnt->bias, cpvsub(vb2, vb1)); + + cpVect jb = cpv(cpvdot(vbr, k1), cpvdot(vbr, k2)); + cpVect jbOld = jnt->jBias; + jnt->jBias = grooveConstrain(jnt, cpvadd(jbOld, jb)); + jb = cpvsub(jnt->jBias, jbOld); + + cpBodyApplyBiasImpulse(a, cpvneg(jb), r1); + cpBodyApplyBiasImpulse(b, jb, r2); + + // compute relative velocity + cpVect v1 = cpvadd(a->v, cpvmult(cpvperp(r1), a->w)); + cpVect v2 = cpvadd(b->v, cpvmult(cpvperp(r2), b->w)); + cpVect vr = cpvsub(v2, v1); + + // compute impulse + cpVect j = cpv(-cpvdot(vr, k1), -cpvdot(vr, k2)); + cpVect jOld = jnt->jAcc; + jnt->jAcc = grooveConstrain(jnt, cpvadd(jOld, j)); + j = cpvsub(jnt->jAcc, jOld); + + // apply impulse + cpBodyApplyImpulse(a, cpvneg(j), r1); + cpBodyApplyImpulse(b, j, r2); +} + +cpGrooveJoint * +cpGrooveJointAlloc(void) +{ + return (cpGrooveJoint *)malloc(sizeof(cpGrooveJoint)); +} + +cpGrooveJoint * +cpGrooveJointInit(cpGrooveJoint *joint, cpBody *a, cpBody *b, cpVect groove_a, cpVect groove_b, cpVect anchr2) +{ + joint->joint.preStep = &grooveJointPreStep; + joint->joint.applyImpulse = &grooveJointApplyImpulse; + + joint->joint.a = a; + joint->joint.b = b; + + ((cpJoint*)joint)->type = CP_GROOVE_JOINT; + + joint->grv_a = groove_a; + joint->grv_b = groove_b; + joint->grv_n = cpvperp(cpvnormalize(cpvsub(groove_b, groove_a))); + joint->anchr2 = anchr2; + + joint->jAcc = cpvzero; + + return joint; +} + +cpJoint * +cpGrooveJointNew(cpBody *a, cpBody *b, cpVect groove_a, cpVect groove_b, cpVect anchr2) +{ + return (cpJoint *)cpGrooveJointInit(cpGrooveJointAlloc(), a, b, groove_a, groove_b, anchr2); +} + diff --git a/PopLib/chipmunk/cpJoint.h b/PopLib/chipmunk/cpJoint.h new file mode 100644 index 00000000..c22531b2 --- /dev/null +++ b/PopLib/chipmunk/cpJoint.h @@ -0,0 +1,115 @@ +/* Copyright (c) 2007 Scott Lembcke +* +* 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. +*/ + +// TODO: Comment me! + +extern cpFloat cp_joint_bias_coef; + +// Enumeration of joint types. +typedef enum cpJointType{ + CP_PIN_JOINT, + CP_SLIDE_JOINT, + CP_PIVOT_JOINT, + CP_GROOVE_JOINT, + CP_NUM_JOINTS +} cpJointType; + +typedef struct cpJoint { + cpJointType type; + cpBody *a, *b; + + void (*preStep)(struct cpJoint *joint, cpFloat dt_inv); + void (*applyImpulse)(struct cpJoint *joint); +} cpJoint; + +void cpJointDestroy(cpJoint *joint); +void cpJointFree(cpJoint *joint); + + +typedef struct cpPinJoint { + cpJoint joint; + cpVect anchr1, anchr2; + cpFloat dist; + + cpVect r1, r2; + cpVect n; + cpFloat nMass; + + cpFloat jnAcc, jBias; + cpFloat bias; +} cpPinJoint; + +cpPinJoint *cpPinJointAlloc(void); +cpPinJoint *cpPinJointInit(cpPinJoint *joint, cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2); +cpJoint *cpPinJointNew(cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2); + + +typedef struct cpSlideJoint { + cpJoint joint; + cpVect anchr1, anchr2; + cpFloat min, max; + + cpVect r1, r2; + cpVect n; + cpFloat nMass; + + cpFloat jnAcc, jBias; + cpFloat bias; +} cpSlideJoint; + +cpSlideJoint *cpSlideJointAlloc(void); +cpSlideJoint *cpSlideJointInit(cpSlideJoint *joint, cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2, cpFloat min, cpFloat max); +cpJoint *cpSlideJointNew(cpBody *a, cpBody *b, cpVect anchr1, cpVect anchr2, cpFloat min, cpFloat max); + + +typedef struct cpPivotJoint { + cpJoint joint; + cpVect anchr1, anchr2; + + cpVect r1, r2; + cpVect k1, k2; + + cpVect jAcc, jBias; + cpVect bias; +} cpPivotJoint; + +cpPivotJoint *cpPivotJointAlloc(void); +cpPivotJoint *cpPivotJointInit(cpPivotJoint *joint, cpBody *a, cpBody *b, cpVect pivot); +cpJoint *cpPivotJointNew(cpBody *a, cpBody *b, cpVect pivot); + + +typedef struct cpGrooveJoint { + cpJoint joint; + cpVect grv_n, grv_a, grv_b; + cpVect anchr2; + + cpVect grv_tn; + cpFloat clamp; + cpVect r1, r2; + cpVect k1, k2; + + cpVect jAcc, jBias; + cpVect bias; +} cpGrooveJoint; + +cpGrooveJoint *cpGrooveJointAlloc(void); +cpGrooveJoint *cpGrooveJointInit(cpGrooveJoint *joint, cpBody *a, cpBody *b, cpVect groove_a, cpVect groove_b, cpVect anchr2); +cpJoint *cpGrooveJointNew(cpBody *a, cpBody *b, cpVect groove_a, cpVect groove_b, cpVect anchr2); diff --git a/PopLib/chipmunk/cpPolyShape.c b/PopLib/chipmunk/cpPolyShape.c new file mode 100644 index 00000000..62628192 --- /dev/null +++ b/PopLib/chipmunk/cpPolyShape.c @@ -0,0 +1,128 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include +#include +#include + +#include "chipmunk.h" + +cpPolyShape * +cpPolyShapeAlloc(void) +{ + return (cpPolyShape *)calloc(1, sizeof(cpPolyShape)); +} + +static void +cpPolyShapeTransformVerts(cpPolyShape *poly, cpVect p, cpVect rot) +{ + cpVect *src = poly->verts; + cpVect *dst = poly->tVerts; + + for(int i=0; inumVerts; i++) + dst[i] = cpvadd(p, cpvrotate(src[i], rot)); +} + +static void +cpPolyShapeTransformAxes(cpPolyShape *poly, cpVect p, cpVect rot) +{ + cpPolyShapeAxis *src = poly->axes; + cpPolyShapeAxis *dst = poly->tAxes; + + for(int i=0; inumVerts; i++){ + cpVect n = cpvrotate(src[i].n, rot); + dst[i].n = n; + dst[i].d = cpvdot(p, n) + src[i].d; + } +} + +static cpBB +cpPolyShapeCacheData(cpShape *shape, cpVect p, cpVect rot) +{ + cpPolyShape *poly = (cpPolyShape *)shape; + + cpFloat l, b, r, t; + + cpPolyShapeTransformAxes(poly, p, rot); + cpPolyShapeTransformVerts(poly, p, rot); + + cpVect *verts = poly->tVerts; + l = r = verts[0].x; + b = t = verts[0].y; + + for(int i=1; inumVerts; i++){ + cpVect v = verts[i]; + + l = cpfmin(l, v.x); + r = cpfmax(r, v.x); + + b = cpfmin(b, v.y); + t = cpfmax(t, v.y); + } + + return cpBBNew(l, b, r, t); +} + +static void +cpPolyShapeDestroy(cpShape *shape) +{ + cpPolyShape *poly = (cpPolyShape *)shape; + + free(poly->verts); + free(poly->tVerts); + + free(poly->axes); + free(poly->tAxes); +} + +cpPolyShape * +cpPolyShapeInit(cpPolyShape *poly, cpBody *body, int numVerts, cpVect *verts, cpVect offset) +{ + poly->numVerts = numVerts; + + poly->verts = (cpVect *)calloc(numVerts, sizeof(cpVect)); + poly->tVerts = (cpVect *)calloc(numVerts, sizeof(cpVect)); + poly->axes = (cpPolyShapeAxis *)calloc(numVerts, sizeof(cpPolyShapeAxis)); + poly->tAxes = (cpPolyShapeAxis *)calloc(numVerts, sizeof(cpPolyShapeAxis)); + + for(int i=0; iverts[i] = a; + poly->axes[i].n = n; + poly->axes[i].d = cpvdot(n, a); + } + + poly->shape.cacheData = &cpPolyShapeCacheData; + poly->shape.destroy = &cpPolyShapeDestroy; + cpShapeInit((cpShape *)poly, CP_POLY_SHAPE, body); + + return poly; +} + +cpShape * +cpPolyShapeNew(cpBody *body, int numVerts, cpVect *verts, cpVect offset) +{ + return (cpShape *)cpPolyShapeInit(cpPolyShapeAlloc(), body, numVerts, verts, offset); +} + diff --git a/PopLib/chipmunk/cpPolyShape.h b/PopLib/chipmunk/cpPolyShape.h new file mode 100644 index 00000000..07e1e7aa --- /dev/null +++ b/PopLib/chipmunk/cpPolyShape.h @@ -0,0 +1,74 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +// Axis structure used by cpPolyShape. +typedef struct cpPolyShapeAxis{ + // normal + cpVect n; + // distance from origin + cpFloat d; +} cpPolyShapeAxis; + +// Convex polygon shape structure. +typedef struct cpPolyShape{ + cpShape shape; + + // Vertex and axis lists. + int numVerts; + cpVect *verts; + cpPolyShapeAxis *axes; + + // Transformed vertex and axis lists. + cpVect *tVerts; + cpPolyShapeAxis *tAxes; +} cpPolyShape; + +// Basic allocation functions. +cpPolyShape *cpPolyShapeAlloc(void); +cpPolyShape *cpPolyShapeInit(cpPolyShape *poly, cpBody *body, int numVerts, cpVect *verts, cpVect offset); +cpShape *cpPolyShapeNew(cpBody *body, int numVerts, cpVect *verts, cpVect offset); + +// Returns the minimum distance of the polygon to the axis. +static inline cpFloat +cpPolyShapeValueOnAxis(const cpPolyShape *poly, const cpVect n, const cpFloat d) +{ + cpVect *verts = poly->tVerts; + cpFloat min = cpvdot(n, verts[0]); + + for(int i=1; inumVerts; i++) + min = cpfmin(min, cpvdot(n, verts[i])); + + return min - d; +} + +// Returns true if the polygon contains the vertex. +static inline int +cpPolyShapeContainsVert(cpPolyShape *poly, cpVect v) +{ + cpPolyShapeAxis *axes = poly->tAxes; + + for(int i=0; inumVerts; i++){ + cpFloat dist = cpvdot(axes[i].n, v) - axes[i].d; + if(dist > 0.0) return 0; + } + + return 1; +} diff --git a/PopLib/chipmunk/cpShape.c b/PopLib/chipmunk/cpShape.c new file mode 100644 index 00000000..d9c41610 --- /dev/null +++ b/PopLib/chipmunk/cpShape.c @@ -0,0 +1,182 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include +#include + +#include "chipmunk.h" + +unsigned int SHAPE_ID_COUNTER = 0; + +void +cpResetShapeIdCounter(void) +{ + SHAPE_ID_COUNTER = 0; +} + + +cpShape* +cpShapeInit(cpShape *shape, cpShapeType type, cpBody *body) +{ + shape->type = type; + + shape->id = SHAPE_ID_COUNTER; + SHAPE_ID_COUNTER++; + + assert(body != NULL); + shape->body = body; + + shape->e = 0.0f; + shape->u = 0.0f; + shape->surface_v = cpvzero; + + shape->collision_type = 0; + shape->group = 0; + shape->layers = 0xFFFF; + + shape->data = NULL; + + cpShapeCacheBB(shape); + + return shape; +} + +void +cpShapeDestroy(cpShape *shape) +{ + if(shape->destroy) shape->destroy(shape); +} + +void +cpShapeFree(cpShape *shape) +{ + if(shape) cpShapeDestroy(shape); + free(shape); +} + +cpBB +cpShapeCacheBB(cpShape *shape) +{ + cpBody *body = shape->body; + + shape->bb = shape->cacheData(shape, body->p, body->rot); + return shape->bb; +} + + +cpCircleShape * +cpCircleShapeAlloc(void) +{ + return (cpCircleShape *)calloc(1, sizeof(cpCircleShape)); +} + +static inline cpBB +bbFromCircle(const cpVect c, const cpFloat r) +{ + return cpBBNew(c.x-r, c.y-r, c.x+r, c.y+r); +} + +static cpBB +cpCircleShapeCacheData(cpShape *shape, cpVect p, cpVect rot) +{ + cpCircleShape *circle = (cpCircleShape *)shape; + + circle->tc = cpvadd(p, cpvrotate(circle->c, rot)); + return bbFromCircle(circle->tc, circle->r); +} + +cpCircleShape * +cpCircleShapeInit(cpCircleShape *circle, cpBody *body, cpFloat radius, cpVect offset) +{ + circle->c = offset; + circle->r = radius; + + circle->shape.cacheData = &cpCircleShapeCacheData; + circle->shape.destroy = NULL; + cpShapeInit((cpShape *)circle, CP_CIRCLE_SHAPE, body); + + return circle; +} + +cpShape * +cpCircleShapeNew(cpBody *body, cpFloat radius, cpVect offset) +{ + return (cpShape *)cpCircleShapeInit(cpCircleShapeAlloc(), body, radius, offset); +} + +cpSegmentShape * +cpSegmentShapeAlloc(void) +{ + return (cpSegmentShape *)calloc(1, sizeof(cpSegmentShape)); +} + +static cpBB +cpSegmentShapeCacheData(cpShape *shape, cpVect p, cpVect rot) +{ + cpSegmentShape *seg = (cpSegmentShape *)shape; + + seg->ta = cpvadd(p, cpvrotate(seg->a, rot)); + seg->tb = cpvadd(p, cpvrotate(seg->b, rot)); + seg->tn = cpvrotate(seg->n, rot); + + cpFloat l,r,s,t; + + if(seg->ta.x < seg->tb.x){ + l = seg->ta.x; + r = seg->tb.x; + } else { + l = seg->tb.x; + r = seg->ta.x; + } + + if(seg->ta.y < seg->tb.y){ + s = seg->ta.y; + t = seg->tb.y; + } else { + s = seg->tb.y; + t = seg->ta.y; + } + + cpFloat rad = seg->r; + return cpBBNew(l - rad, s - rad, r + rad, t + rad); +} + +cpSegmentShape * +cpSegmentShapeInit(cpSegmentShape *seg, cpBody *body, cpVect a, cpVect b, cpFloat r) +{ + seg->a = a; + seg->b = b; + seg->n = cpvperp(cpvnormalize(cpvsub(b, a))); + + seg->r = r; + + seg->shape.cacheData = &cpSegmentShapeCacheData; + seg->shape.destroy = NULL; + cpShapeInit((cpShape *)seg, CP_SEGMENT_SHAPE, body); + + return seg; +} + +cpShape* +cpSegmentShapeNew(cpBody *body, cpVect a, cpVect b, cpFloat r) +{ + return (cpShape *)cpSegmentShapeInit(cpSegmentShapeAlloc(), body, a, b, r); +} diff --git a/PopLib/chipmunk/cpShape.h b/PopLib/chipmunk/cpShape.h new file mode 100644 index 00000000..267b561a --- /dev/null +++ b/PopLib/chipmunk/cpShape.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +// For determinism, you can reset the shape id counter. +void cpResetShapeIdCounter(void); + +// Enumeration of shape types. +typedef enum cpShapeType{ + CP_CIRCLE_SHAPE, + CP_SEGMENT_SHAPE, + CP_POLY_SHAPE, + CP_NUM_SHAPES +} cpShapeType; + +// Basic shape struct that the others inherit from. +typedef struct cpShape{ + cpShapeType type; + + // Called by cpShapeCacheBB(). + cpBB (*cacheData)(struct cpShape *shape, cpVect p, cpVect rot); + // Called to by cpShapeDestroy(). + void (*destroy)(struct cpShape *shape); + + // Unique id used as the hash value. + unsigned int id; + // Cached BBox for the shape. + cpBB bb; + + // User defined collision type for the shape. + unsigned int collision_type; + // User defined collision group for the shape. + unsigned int group; + // User defined layer bitmask for the shape. + unsigned int layers; + + // User defined data pointer for the shape. + void *data; + + // cpBody that the shape is attached to. + cpBody *body; + + // Coefficient of restitution. (elasticity) + cpFloat e; + // Coefficient of friction. + cpFloat u; + // Surface velocity used when solving for friction. + cpVect surface_v; +} cpShape; + +// Low level shape initialization func. +cpShape* cpShapeInit(cpShape *shape, cpShapeType type, cpBody *body); + +// Basic destructor functions. (allocation functions are not shared) +void cpShapeDestroy(cpShape *shape); +void cpShapeFree(cpShape *shape); + +// Cache the BBox of the shape. +cpBB cpShapeCacheBB(cpShape *shape); + + +// Circle shape structure. +typedef struct cpCircleShape{ + cpShape shape; + + // Center. (body space coordinates) + cpVect c; + // Radius. + cpFloat r; + + // Transformed center. (world space coordinates) + cpVect tc; +} cpCircleShape; + +// Basic allocation functions for cpCircleShape. +cpCircleShape *cpCircleShapeAlloc(void); +cpCircleShape *cpCircleShapeInit(cpCircleShape *circle, cpBody *body, cpFloat radius, cpVect offset); +cpShape *cpCircleShapeNew(cpBody *body, cpFloat radius, cpVect offset); + +// Segment shape structure. +typedef struct cpSegmentShape{ + cpShape shape; + + // Endpoints and normal of the segment. (body space coordinates) + cpVect a, b, n; + // Radius of the segment. (Thickness) + cpFloat r; + + // Transformed endpoints and normal. (world space coordinates) + cpVect ta, tb, tn; +} cpSegmentShape; + +// Basic allocation functions for cpSegmentShape. +cpSegmentShape* cpSegmentShapeAlloc(void); +cpSegmentShape* cpSegmentShapeInit(cpSegmentShape *seg, cpBody *body, cpVect a, cpVect b, cpFloat r); +cpShape* cpSegmentShapeNew(cpBody *body, cpVect a, cpVect b, cpFloat r); diff --git a/PopLib/chipmunk/cpSpace.c b/PopLib/chipmunk/cpSpace.c new file mode 100644 index 00000000..cf78e6f7 --- /dev/null +++ b/PopLib/chipmunk/cpSpace.c @@ -0,0 +1,473 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include +#include +#include +#include + +#include "chipmunk.h" + +int cp_contact_persistence = 3; + +// Equal function for contactSet. +static int +contactSetEql(void *ptr, void *elt) +{ + cpShape **shapes = (cpShape **)ptr; + cpShape *a = shapes[0]; + cpShape *b = shapes[1]; + + cpArbiter *arb = (cpArbiter *)elt; + + return ((a == arb->a && b == arb->b) || (b == arb->a && a == arb->b)); +} + +// Transformation function for contactSet. +static void * +contactSetTrans(void *ptr, void *data) +{ + cpShape **shapes = (cpShape **)ptr; + cpShape *a = shapes[0]; + cpShape *b = shapes[1]; + + cpSpace *space = (cpSpace *)data; + + return cpArbiterNew(a, b, space->stamp); +} + +// Collision pair function wrapper struct. +typedef struct collFuncData { + cpCollFunc func; + void *data; +} collFuncData; + +// Equals function for collFuncSet. +static int +collFuncSetEql(void *ptr, void *elt) +{ + unsigned int *ids = (unsigned int *)ptr; + unsigned int a = ids[0]; + unsigned int b = ids[1]; + + cpCollPairFunc *pair = (cpCollPairFunc *)elt; + + return ((a == pair->a && b == pair->b) || (b == pair->a && a == pair->b)); +} + +// Transformation function for collFuncSet. +static void * +collFuncSetTrans(void *ptr, void *data) +{ + unsigned int *ids = (unsigned int *)ptr; + collFuncData *funcData = (collFuncData *)data; + + cpCollPairFunc *pair = (cpCollPairFunc *)malloc(sizeof(cpCollPairFunc)); + pair->a = ids[0]; + pair->b = ids[1]; + pair->func = funcData->func; + pair->data = funcData->data; + + return pair; +} + +// Default collision pair function. +static int +alwaysCollide(cpShape *a, cpShape *b, cpContact *arr, int numCon, cpFloat normal_coef, void *data) +{ + return 1; +} + +// BBfunc callback for the spatial hash. +static cpBB +bbfunc(void *ptr) +{ + cpShape *shape = (cpShape *)ptr; + return shape->bb; +} + +// Iterator functions for destructors. +static void freeWrap(void *ptr, void *unused){ free( ptr);} +static void shapeFreeWrap(void *ptr, void *unused){ cpShapeFree((cpShape *) ptr);} +static void arbiterFreeWrap(void *ptr, void *unused){ cpArbiterFree((cpArbiter *)ptr);} +static void bodyFreeWrap(void *ptr, void *unused){ cpBodyFree((cpBody *) ptr);} +static void jointFreeWrap(void *ptr, void *unused){ cpJointFree((cpJoint *) ptr);} + +cpSpace* +cpSpaceAlloc(void) +{ + return (cpSpace *)calloc(1, sizeof(cpSpace)); +} + +#define DEFAULT_DIM_SIZE 100.0f +#define DEFAULT_COUNT 1000 +#define DEFAULT_ITERATIONS 10 + +cpSpace* +cpSpaceInit(cpSpace *space) +{ + space->iterations = DEFAULT_ITERATIONS; +// space->sleepTicks = 300; + + space->gravity = cpvzero; + space->damping = 1.0f; + + space->stamp = 0; + + space->staticShapes = cpSpaceHashNew(DEFAULT_DIM_SIZE, DEFAULT_COUNT, &bbfunc); + space->activeShapes = cpSpaceHashNew(DEFAULT_DIM_SIZE, DEFAULT_COUNT, &bbfunc); + + space->bodies = cpArrayNew(0); + space->arbiters = cpArrayNew(0); + space->contactSet = cpHashSetNew(0, contactSetEql, contactSetTrans); + + space->joints = cpArrayNew(0); + + cpCollPairFunc pairFunc = {0, 0, alwaysCollide, NULL}; + space->defaultPairFunc = pairFunc; + space->collFuncSet = cpHashSetNew(0, collFuncSetEql, collFuncSetTrans); + space->collFuncSet->default_value = &space->defaultPairFunc; + + return space; +} + +cpSpace* +cpSpaceNew(void) +{ + return cpSpaceInit(cpSpaceAlloc()); +} + +void +cpSpaceDestroy(cpSpace *space) +{ + cpSpaceHashFree(space->staticShapes); + cpSpaceHashFree(space->activeShapes); + + cpArrayFree(space->bodies); + + cpArrayFree(space->joints); + + if(space->contactSet) + cpHashSetEach(space->contactSet, &arbiterFreeWrap, NULL); + cpHashSetFree(space->contactSet); + cpArrayFree(space->arbiters); + + if(space->collFuncSet) + cpHashSetEach(space->collFuncSet, &freeWrap, NULL); + cpHashSetFree(space->collFuncSet); +} + +void +cpSpaceFree(cpSpace *space) +{ + if(space) cpSpaceDestroy(space); + free(space); +} + +void +cpSpaceFreeChildren(cpSpace *space) +{ + cpSpaceHashEach(space->staticShapes, &shapeFreeWrap, NULL); + cpSpaceHashEach(space->activeShapes, &shapeFreeWrap, NULL); + cpArrayEach(space->bodies, &bodyFreeWrap, NULL); + cpArrayEach(space->joints, &jointFreeWrap, NULL); +} + +void +cpSpaceAddCollisionPairFunc(cpSpace *space, unsigned int a, unsigned int b, + cpCollFunc func, void *data) +{ + unsigned int ids[] = {a, b}; + unsigned int hash = CP_HASH_PAIR(a, b); + // Remove any old function so the new one will get added. + cpSpaceRemoveCollisionPairFunc(space, a, b); + + collFuncData funcData = {func, data}; + cpHashSetInsert(space->collFuncSet, hash, ids, &funcData); +} + +void +cpSpaceRemoveCollisionPairFunc(cpSpace *space, unsigned int a, unsigned int b) +{ + unsigned int ids[] = {a, b}; + unsigned int hash = CP_HASH_PAIR(a, b); + cpCollPairFunc *old_pair = (cpCollPairFunc *)cpHashSetRemove(space->collFuncSet, hash, ids); + free(old_pair); +} + +void +cpSpaceSetDefaultCollisionPairFunc(cpSpace *space, cpCollFunc func, void *data) +{ + cpCollPairFunc pairFunc = {0, 0, (func ? func : alwaysCollide), (func ? data : NULL)}; + space->defaultPairFunc = pairFunc; +} + +void +cpSpaceAddShape(cpSpace *space, cpShape *shape) +{ + cpSpaceHashInsert(space->activeShapes, shape, shape->id, shape->bb); +} + +void +cpSpaceAddStaticShape(cpSpace *space, cpShape *shape) +{ + cpSpaceHashInsert(space->staticShapes, shape, shape->id, shape->bb); +} + +void +cpSpaceAddBody(cpSpace *space, cpBody *body) +{ + cpArrayPush(space->bodies, body); +} + +void +cpSpaceAddJoint(cpSpace *space, cpJoint *joint) +{ + cpArrayPush(space->joints, joint); +} + +void +cpSpaceRemoveShape(cpSpace *space, cpShape *shape) +{ + cpSpaceHashRemove(space->activeShapes, shape, shape->id); +} + +void +cpSpaceRemoveStaticShape(cpSpace *space, cpShape *shape) +{ + cpSpaceHashRemove(space->staticShapes, shape, shape->id); +} + +void +cpSpaceRemoveBody(cpSpace *space, cpBody *body) +{ + cpArrayDeleteObj(space->bodies, body); +} + +void +cpSpaceRemoveJoint(cpSpace *space, cpJoint *joint) +{ + cpArrayDeleteObj(space->joints, joint); +} + +void +cpSpaceEachBody(cpSpace *space, cpSpaceBodyIterator func, void *data) +{ + cpArray *bodies = space->bodies; + + for(int i=0; inum; i++) + func((cpBody *)bodies->arr[i], data); +} + +// Iterator function used for updating shape BBoxes. +static void +updateBBCache(void *ptr, void *unused) +{ + cpShape *shape = (cpShape *)ptr; + cpShapeCacheBB(shape); +} + +void +cpSpaceResizeStaticHash(cpSpace *space, cpFloat dim, int count) +{ + cpSpaceHashResize(space->staticShapes, dim, count); + cpSpaceHashRehash(space->staticShapes); +} + +void +cpSpaceResizeActiveHash(cpSpace *space, cpFloat dim, int count) +{ + cpSpaceHashResize(space->activeShapes, dim, count); +} + +void +cpSpaceRehashStatic(cpSpace *space) +{ + cpSpaceHashEach(space->staticShapes, &updateBBCache, NULL); + cpSpaceHashRehash(space->staticShapes); +} + +static inline int +queryReject(cpShape *a, cpShape *b) +{ + return + // BBoxes must overlap + !cpBBintersects(a->bb, b->bb) + // Don't collide shapes attached to the same body. + || a->body == b->body + // Don't collide objects in the same non-zero group + || (a->group && b->group && a->group == b->group) + // Don't collide objects that don't share at least on layer. + || !(a->layers & b->layers); +} + +// Callback from the spatial hash. +// TODO: Refactor this into separate functions? +static int +queryFunc(void *p1, void *p2, void *data) +{ + // Cast the generic pointers from the spatial hash back to usefull types + cpShape *a = (cpShape *)p1; + cpShape *b = (cpShape *)p2; + cpSpace *space = (cpSpace *)data; + + // Reject any of the simple cases + if(queryReject(a,b)) return 0; + + // Shape 'a' should have the lower shape type. (required by cpCollideShapes() ) + if(a->type > b->type){ + cpShape *temp = a; + a = b; + b = temp; + } + + // Find the collision pair function for the shapes. + unsigned int ids[] = {a->collision_type, b->collision_type}; + unsigned int hash = CP_HASH_PAIR(a->collision_type, b->collision_type); + cpCollPairFunc *pairFunc = (cpCollPairFunc *)cpHashSetFind(space->collFuncSet, hash, ids); + if(!pairFunc->func) return 0; // A NULL pair function means don't collide at all. + + // Narrow-phase collision detection. + cpContact *contacts = NULL; + int numContacts = cpCollideShapes(a, b, &contacts); + if(!numContacts) return 0; // Shapes are not colliding. + + // The collision pair function requires objects to be ordered by their collision types. + cpShape *pair_a = a; + cpShape *pair_b = b; + cpFloat normal_coef = 1.0f; + + // Swap them if necessary. + if(pair_a->collision_type != pairFunc->a){ + cpShape *temp = pair_a; + pair_a = pair_b; + pair_b = temp; + normal_coef = -1.0f; + } + + if(pairFunc->func(pair_a, pair_b, contacts, numContacts, normal_coef, pairFunc->data)){ + // The collision pair function OKed the collision. Record the contact information. + + // Get an arbiter from space->contactSet for the two shapes. + // This is where the persistant contact magic comes from. + cpShape *shape_pair[] = {a, b}; + cpArbiter *arb = (cpArbiter *)cpHashSetInsert(space->contactSet, CP_HASH_PAIR(a, b), shape_pair, space); + + // Timestamp the arbiter. + arb->stamp = space->stamp; + arb->a = a; arb->b = b; // TODO: Investigate why this is still necessary? + // Inject the new contact points into the arbiter. + cpArbiterInject(arb, contacts, numContacts); + + // Add the arbiter to the list of active arbiters. + cpArrayPush(space->arbiters, arb); + + return numContacts; + } else { + // The collision pair function rejected the collision. + + free(contacts); + return 0; + } +} + +// Iterator for active/static hash collisions. +static void +active2staticIter(void *ptr, void *data) +{ + cpShape *shape = (cpShape *)ptr; + cpSpace *space = (cpSpace *)data; + cpSpaceHashQuery(space->staticShapes, shape, shape->bb, &queryFunc, space); +} + +// Hashset reject func to throw away old arbiters. +static int +contactSetReject(void *ptr, void *data) +{ + cpArbiter *arb = (cpArbiter *)ptr; + cpSpace *space = (cpSpace *)data; + + if((space->stamp - arb->stamp) > cp_contact_persistence){ + cpArbiterFree(arb); + return 0; + } + + return 1; +} + +void +cpSpaceStep(cpSpace *space, cpFloat dt) +{ + if(!dt) return; // prevents div by zero. + cpFloat dt_inv = 1.0f/dt; + + cpArray *bodies = space->bodies; + cpArray *arbiters = space->arbiters; + cpArray *joints = space->joints; + + // Empty the arbiter list. + cpHashSetReject(space->contactSet, &contactSetReject, space); + space->arbiters->num = 0; + + // Integrate velocities. + cpFloat damping = pow(1.0f/space->damping, -dt); + for(int i=0; inum; i++) + cpBodyUpdateVelocity((cpBody *)bodies->arr[i], space->gravity, damping, dt); + + // Pre-cache BBoxes and shape data. + cpSpaceHashEach(space->activeShapes, &updateBBCache, NULL); + + // Collide! + cpSpaceHashEach(space->activeShapes, &active2staticIter, space); + cpSpaceHashQueryRehash(space->activeShapes, &queryFunc, space); + + // Prestep the arbiters. + for(int i=0; inum; i++) + cpArbiterPreStep((cpArbiter *)arbiters->arr[i], dt_inv); + + // Prestep the joints. + for(int i=0; inum; i++){ + cpJoint *joint = (cpJoint *)joints->arr[i]; + joint->preStep(joint, dt_inv); + } + + // Run the impulse solver. + for(int i=0; iiterations; i++){ + for(int j=0; jnum; j++) + cpArbiterApplyImpulse((cpArbiter *)arbiters->arr[j]); + for(int j=0; jnum; j++){ + cpJoint *joint = (cpJoint *)joints->arr[j]; + joint->applyImpulse(joint); + } + } + +// cpFloat dvsq = cpvdot(space->gravity, space->gravity); +// dvsq *= dt*dt * space->damping*space->damping; +// for(int i=0; inum; i++) +// cpBodyMarkLowEnergy(bodies->arr[i], dvsq, space->sleepTicks); + + // Integrate positions. + for(int i=0; inum; i++) + cpBodyUpdatePosition((cpBody *)bodies->arr[i], dt); + + // Increment the stamp. + space->stamp++; +} diff --git a/PopLib/chipmunk/cpSpace.h b/PopLib/chipmunk/cpSpace.h new file mode 100644 index 00000000..d199703c --- /dev/null +++ b/PopLib/chipmunk/cpSpace.h @@ -0,0 +1,107 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +// Number of frames that contact information should persist. +extern int cp_contact_persistence; + +// User collision pair function. +typedef int (*cpCollFunc)(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data); + +// Structure for holding collision pair function information. +// Used internally. +typedef struct cpCollPairFunc { + unsigned int a; + unsigned int b; + cpCollFunc func; + void *data; +} cpCollPairFunc; + +typedef struct cpSpace{ + // Number of iterations to use in the impulse solver. + int iterations; +// int sleepTicks; + + // Self explanatory. + cpVect gravity; + cpFloat damping; + + // Time stamp. Is incremented on every call to cpSpaceStep(). + int stamp; + + // The static and active shape spatial hashes. + cpSpaceHash *staticShapes; + cpSpaceHash *activeShapes; + + // List of bodies in the system. + cpArray *bodies; + // List of active arbiters for the impulse solver. + cpArray *arbiters; + // Persistant contact set. + cpHashSet *contactSet; + + // List of joints in the system. + cpArray *joints; + + // Set of collisionpair functions. + cpHashSet *collFuncSet; + // Default collision pair function. + cpCollPairFunc defaultPairFunc; +} cpSpace; + +// Basic allocation/destruction functions. +cpSpace* cpSpaceAlloc(void); +cpSpace* cpSpaceInit(cpSpace *space); +cpSpace* cpSpaceNew(void); + +void cpSpaceDestroy(cpSpace *space); +void cpSpaceFree(cpSpace *space); + +// Convenience function. Frees all referenced entities. (bodies, shapes and joints) +void cpSpaceFreeChildren(cpSpace *space); + +// Collision pair function management functions. +void cpSpaceAddCollisionPairFunc(cpSpace *space, unsigned int a, unsigned int b, + cpCollFunc func, void *data); +void cpSpaceRemoveCollisionPairFunc(cpSpace *space, unsigned int a, unsigned int b); +void cpSpaceSetDefaultCollisionPairFunc(cpSpace *space, cpCollFunc func, void *data); + +// Add and remove entities from the system. +void cpSpaceAddShape(cpSpace *space, cpShape *shape); +void cpSpaceAddStaticShape(cpSpace *space, cpShape *shape); +void cpSpaceAddBody(cpSpace *space, cpBody *body); +void cpSpaceAddJoint(cpSpace *space, cpJoint *joint); + +void cpSpaceRemoveShape(cpSpace *space, cpShape *shape); +void cpSpaceRemoveStaticShape(cpSpace *space, cpShape *shape); +void cpSpaceRemoveBody(cpSpace *space, cpBody *body); +void cpSpaceRemoveJoint(cpSpace *space, cpJoint *joint); + +// Iterator function for iterating the bodies in a space. +typedef void (*cpSpaceBodyIterator)(cpBody *body, void *data); +void cpSpaceEachBody(cpSpace *space, cpSpaceBodyIterator func, void *data); + +// Spatial hash management functions. +void cpSpaceResizeStaticHash(cpSpace *space, cpFloat dim, int count); +void cpSpaceResizeActiveHash(cpSpace *space, cpFloat dim, int count); +void cpSpaceRehashStatic(cpSpace *space); + +// Update the space. +void cpSpaceStep(cpSpace *space, cpFloat dt); diff --git a/PopLib/chipmunk/cpSpaceHash.c b/PopLib/chipmunk/cpSpaceHash.c new file mode 100644 index 00000000..2a5b9aa8 --- /dev/null +++ b/PopLib/chipmunk/cpSpaceHash.c @@ -0,0 +1,439 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include +#include +#include +#include + +#include "chipmunk.h" +#include "prime.h" + +static cpHandle* +cpHandleAlloc(void) +{ + return (cpHandle *)malloc(sizeof(cpHandle)); +} + +static cpHandle* +cpHandleInit(cpHandle *hand, void *obj) +{ + hand->obj = obj; + hand->retain = 0; + hand->stamp = 0; + + return hand; +} + +static cpHandle* +cpHandleNew(void *obj) +{ + return cpHandleInit(cpHandleAlloc(), obj); +} + +static inline void +cpHandleRetain(cpHandle *hand) +{ + hand->retain++; +} + +static inline void +cpHandleFree(cpHandle *hand) +{ + free(hand); +} + +static inline void +cpHandleRelease(cpHandle *hand) +{ + hand->retain--; + if(hand->retain == 0) + cpHandleFree(hand); +} + + +cpSpaceHash* +cpSpaceHashAlloc(void) +{ + return (cpSpaceHash *)calloc(1, sizeof(cpSpaceHash)); +} + +// Frees the old table, and allocates a new one. +static void +cpSpaceHashAllocTable(cpSpaceHash *hash, int numcells) +{ + free(hash->table); + + hash->numcells = numcells; + hash->table = (cpSpaceHashBin **)calloc(numcells, sizeof(cpSpaceHashBin *)); +} + +// Equality function for the handleset. +static int +handleSetEql(void *obj, void *elt) +{ + cpHandle *hand = (cpHandle *)elt; + return (obj == hand->obj); +} + +// Transformation function for the handleset. +static void * +handleSetTrans(void *obj, void *unused) +{ + cpHandle *hand = cpHandleNew(obj); + cpHandleRetain(hand); + + return hand; +} + +cpSpaceHash* +cpSpaceHashInit(cpSpaceHash *hash, cpFloat celldim, int numcells, cpSpaceHashBBFunc bbfunc) +{ + cpSpaceHashAllocTable(hash, next_prime(numcells)); + hash->celldim = celldim; + hash->bbfunc = bbfunc; + + hash->bins = NULL; + hash->handleSet = cpHashSetNew(0, &handleSetEql, &handleSetTrans); + + hash->stamp = 1; + + return hash; +} + +cpSpaceHash* +cpSpaceHashNew(cpFloat celldim, int cells, cpSpaceHashBBFunc bbfunc) +{ + return cpSpaceHashInit(cpSpaceHashAlloc(), celldim, cells, bbfunc); +} + +static inline void +clearHashCell(cpSpaceHash *hash, int index) +{ + cpSpaceHashBin *bin = hash->table[index]; + while(bin){ + cpSpaceHashBin *next = bin->next; + + // Release the lock on the handle. + cpHandleRelease(bin->handle); + // Recycle the bin. + bin->next = hash->bins; + hash->bins = bin; + + bin = next; + } + + hash->table[index] = NULL; +} + +// Clear all cells in the hashtable. +static void +clearHash(cpSpaceHash *hash) +{ + for(int i=0; inumcells; i++) + clearHashCell(hash, i); +} + +// Free the recycled hash bins. +static void +freeBins(cpSpaceHash *hash) +{ + cpSpaceHashBin *bin = hash->bins; + while(bin){ + cpSpaceHashBin *next = bin->next; + free(bin); + bin = next; + } +} + +// Hashset iterator function to free the handles. +static void +handleFreeWrap(void *elt, void *unused) +{ + cpHandle *hand = (cpHandle *)elt; + cpHandleFree(hand); +} + +void +cpSpaceHashDestroy(cpSpaceHash *hash) +{ + clearHash(hash); + freeBins(hash); + + // Free the handles. + cpHashSetEach(hash->handleSet, &handleFreeWrap, NULL); + cpHashSetFree(hash->handleSet); + + free(hash->table); +} + +void +cpSpaceHashFree(cpSpaceHash *hash) +{ + if(!hash) return; + cpSpaceHashDestroy(hash); + free(hash); +} + +void +cpSpaceHashResize(cpSpaceHash *hash, cpFloat celldim, int numcells) +{ + // Clear the hash to release the old handle locks. + clearHash(hash); + + hash->celldim = celldim; + cpSpaceHashAllocTable(hash, next_prime(numcells)); +} + +// Return true if the chain contains the handle. +static inline int +containsHandle(cpSpaceHashBin *bin, cpHandle *hand) +{ + while(bin){ + if(bin->handle == hand) return 1; + bin = bin->next; + } + + return 0; +} + +// Get a recycled or new bin. +static inline cpSpaceHashBin * +getEmptyBin(cpSpaceHash *hash) +{ + cpSpaceHashBin *bin = hash->bins; + + // Make a new one if necessary. + if(bin == NULL) return (cpSpaceHashBin *)malloc(sizeof(cpSpaceHashBin)); + + hash->bins = bin->next; + return bin; +} + +// The hash function itself. +static inline unsigned int +hash_func(unsigned int x, unsigned int y, unsigned int n) +{ + return (x*2185031351ul ^ y*4232417593ul) % n; +} + +static inline void +hashHandle(cpSpaceHash *hash, cpHandle *hand, cpBB bb) +{ + // Find the dimensions in cell coordinates. + cpFloat dim = hash->celldim; + int l = bb.l/dim; + int r = bb.r/dim; + int b = bb.b/dim; + int t = bb.t/dim; + + int n = hash->numcells; + for(int i=l; i<=r; i++){ + for(int j=b; j<=t; j++){ + int index = hash_func(i,j,n); + cpSpaceHashBin *bin = hash->table[index]; + + // Don't add an object twice to the same cell. + if(containsHandle(bin, hand)) continue; + + cpHandleRetain(hand); + // Insert a new bin for the handle in this cell. + cpSpaceHashBin *newBin = getEmptyBin(hash); + newBin->handle = hand; + newBin->next = bin; + hash->table[index] = newBin; + } + } +} + +void +cpSpaceHashInsert(cpSpaceHash *hash, void *obj, unsigned int id, cpBB bb) +{ + cpHandle *hand = (cpHandle *)cpHashSetInsert(hash->handleSet, id, obj, NULL); + hashHandle(hash, hand, bb); +} + +void +cpSpaceHashRehashObject(cpSpaceHash *hash, void *obj, unsigned int id) +{ + cpHandle *hand = (cpHandle *)cpHashSetFind(hash->handleSet, id, obj); + hashHandle(hash, hand, hash->bbfunc(obj)); +} + +// Hashset iterator function for rehashing the spatial hash. (hash hash hash hash?) +static void +handleRehashHelper(void *elt, void *data) +{ + cpHandle *hand = (cpHandle *)elt; + cpSpaceHash *hash = (cpSpaceHash *)data; + + hashHandle(hash, hand, hash->bbfunc(hand->obj)); +} + +void +cpSpaceHashRehash(cpSpaceHash *hash) +{ + clearHash(hash); + + // Rehash all of the handles. + cpHashSetEach(hash->handleSet, &handleRehashHelper, hash); +} + +void +cpSpaceHashRemove(cpSpaceHash *hash, void *obj, unsigned int id) +{ + cpHandle *hand = (cpHandle *)cpHashSetRemove(hash->handleSet, id, obj); + hand->obj = NULL; + cpHandleRelease(hand); +} + +// Used by the cpSpaceHashEach() iterator. +typedef struct eachPair { + cpSpaceHashIterator func; + void *data; +} eachPair; + +// Calls the user iterator function. (Gross I know.) +static void +eachHelper(void *elt, void *data) +{ + cpHandle *hand = (cpHandle *)elt; + eachPair *pair = (eachPair *)data; + + pair->func(hand->obj, pair->data); +} + +// Iterate over the objects in the spatial hash. +void +cpSpaceHashEach(cpSpaceHash *hash, cpSpaceHashIterator func, void *data) +{ + // Bundle the callback up to send to the hashset iterator. + eachPair pair = {func, data}; + + cpHashSetEach(hash->handleSet, &eachHelper, &pair); +} + +// Calls the callback function for the objects in a given chain. +static inline void +query(cpSpaceHash *hash, cpSpaceHashBin *bin, void *obj, cpSpaceHashQueryFunc func, void *data) +{ + for(; bin; bin = bin->next){ + cpHandle *hand = bin->handle; + void *other = hand->obj; + + // Skip over certain conditions + if( + // Have we already tried this pair in this query? + hand->stamp == hash->stamp + // Is obj the same as other? + || obj == other + // Has other been removed since the last rehash? + || !other + ) continue; + + func(obj, other, data); + + // Stamp that the handle was checked already against this object. + hand->stamp = hash->stamp; + } +} + +void +cpSpaceHashQuery(cpSpaceHash *hash, void *obj, cpBB bb, cpSpaceHashQueryFunc func, void *data) +{ + // Get the dimensions in cell coordinates. + cpFloat dim = hash->celldim; + int l = bb.l/dim; + int r = bb.r/dim; + int b = bb.b/dim; + int t = bb.t/dim; + + int n = hash->numcells; + + // Iterate over the cells and query them. + for(int i=l; i<=r; i++){ + for(int j=b; j<=t; j++){ + int index = hash_func(i,j,n); + query(hash, hash->table[index], obj, func, data); + } + } + + // Increment the stamp. + hash->stamp++; +} + +// Similar to struct eachPair above. +typedef struct queryRehashPair { + cpSpaceHash *hash; + cpSpaceHashQueryFunc func; + void *data; +} queryRehashPair; + +// Hashset iterator func used with cpSpaceHashQueryRehash(). +static void +handleQueryRehashHelper(void *elt, void *data) +{ + cpHandle *hand = (cpHandle *)elt; + + // Unpack the user callback data. + queryRehashPair *pair = (queryRehashPair *)data; + cpSpaceHash *hash = pair->hash; + cpSpaceHashQueryFunc func = pair->func; + + cpFloat dim = hash->celldim; + int n = hash->numcells; + + void *obj = hand->obj; + cpBB bb = hash->bbfunc(obj); + + int l = bb.l/dim; + int r = bb.r/dim; + int b = bb.b/dim; + int t = bb.t/dim; + + for(int i=l; i<=r; i++){ + for(int j=b; j<=t; j++){ + int index = hash_func(i,j,n); + cpSpaceHashBin *bin = hash->table[index]; + + if(containsHandle(bin, hand)) continue; + query(hash, bin, obj, func, pair->data); + + cpHandleRetain(hand); + cpSpaceHashBin *newBin = getEmptyBin(hash); + newBin->handle = hand; + newBin->next = bin; + hash->table[index] = newBin; + } + } + + // Increment the stamp for each object we hash. + hash->stamp++; +} + +void +cpSpaceHashQueryRehash(cpSpaceHash *hash, cpSpaceHashQueryFunc func, void *data) +{ + clearHash(hash); + + queryRehashPair pair = {hash, func, data}; + cpHashSetEach(hash->handleSet, &handleQueryRehashHelper, &pair); +} diff --git a/PopLib/chipmunk/cpSpaceHash.h b/PopLib/chipmunk/cpSpaceHash.h new file mode 100644 index 00000000..21cb581b --- /dev/null +++ b/PopLib/chipmunk/cpSpaceHash.h @@ -0,0 +1,98 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +// The spatial hash is Chipmunk's default (and currently only) spatial index type. +// Based on a chained hash table. + +// Used internally to track objects added to the hash +typedef struct cpHandle{ + // Pointer to the object + void *obj; + // Retain count + int retain; + // Query stamp. Used to make sure two objects + // aren't identified twice in the same query. + int stamp; +} cpHandle; + +// Linked list element for in the chains. +typedef struct cpSpaceHashBin{ + cpHandle *handle; + struct cpSpaceHashBin *next; +} cpSpaceHashBin; + +// BBox callback. Called whenever the hash needs a bounding box from an object. +typedef cpBB (*cpSpaceHashBBFunc)(void *obj); + +typedef struct cpSpaceHash{ + // Number of cells in the table. + int numcells; + // Dimentions of the cells. + cpFloat celldim; + + // BBox callback. + cpSpaceHashBBFunc bbfunc; + + // Hashset of all the handles. + cpHashSet *handleSet; + + cpSpaceHashBin **table; + // List of recycled bins. + cpSpaceHashBin *bins; + + // Incremented on each query. See cpHandle.stamp. + int stamp; +} cpSpaceHash; + +//Basic allocation/destruction functions. +cpSpaceHash *cpSpaceHashAlloc(void); +cpSpaceHash *cpSpaceHashInit(cpSpaceHash *hash, cpFloat celldim, int cells, cpSpaceHashBBFunc bbfunc); +cpSpaceHash *cpSpaceHashNew(cpFloat celldim, int cells, cpSpaceHashBBFunc bbfunc); + +void cpSpaceHashDestroy(cpSpaceHash *hash); +void cpSpaceHashFree(cpSpaceHash *hash); + +// Resize the hashtable. (Does not rehash! You must call cpSpaceHashRehash() if needed.) +void cpSpaceHashResize(cpSpaceHash *hash, cpFloat celldim, int numcells); + +// Add an object to the hash. +void cpSpaceHashInsert(cpSpaceHash *hash, void *obj, unsigned int id, cpBB bb); +// Remove an object from the hash. +void cpSpaceHashRemove(cpSpaceHash *hash, void *obj, unsigned int id); + +// Iterator function +typedef void (*cpSpaceHashIterator)(void *obj, void *data); +// Iterate over the objects in the hash. +void cpSpaceHashEach(cpSpaceHash *hash, cpSpaceHashIterator func, void *data); + +// Rehash the contents of the hash. +void cpSpaceHashRehash(cpSpaceHash *hash); +// Rehash only a specific object. +void cpSpaceHashRehashObject(cpSpaceHash *hash, void *obj, unsigned int id); + +// Query callback. +typedef int (*cpSpaceHashQueryFunc)(void *obj1, void *obj2, void *data); +// Query the hash for a given BBox. +void cpSpaceHashQuery(cpSpaceHash *hash, void *obj, cpBB bb, cpSpaceHashQueryFunc func, void *data); +// Run a query for the object, then insert it. (Optimized case) +void cpSpaceHashQueryInsert(cpSpaceHash *hash, void *obj, cpBB bb, cpSpaceHashQueryFunc func, void *data); +// Rehashes while querying for each object. (Optimized case) +void cpSpaceHashQueryRehash(cpSpaceHash *hash, cpSpaceHashQueryFunc func, void *data); diff --git a/PopLib/chipmunk/cpVect.c b/PopLib/chipmunk/cpVect.c new file mode 100644 index 00000000..c7192a79 --- /dev/null +++ b/PopLib/chipmunk/cpVect.c @@ -0,0 +1,63 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include "stdio.h" +#include "math.h" + +#include "chipmunk.h" + +cpFloat +cpvlength(const cpVect v) +{ + return sqrtf( cpvdot(v, v) ); +} + +cpFloat +cpvlengthsq(const cpVect v) +{ + return cpvdot(v, v); +} + +cpVect +cpvnormalize(const cpVect v) +{ + return cpvmult( v, 1.0f/cpvlength(v) ); +} + +cpVect +cpvforangle(const cpFloat a) +{ + return cpv(cos(a), sin(a)); +} + +cpFloat +cpvtoangle(const cpVect v) +{ + return atan2(v.y, v.x); +} + +char* +cpvstr(const cpVect v) +{ + static char str[256]; + sprintf(str, "(% .3f, % .3f)", v.x, v.y); + return str; +} diff --git a/PopLib/chipmunk/cpVect.h b/PopLib/chipmunk/cpVect.h new file mode 100644 index 00000000..c70c7ef9 --- /dev/null +++ b/PopLib/chipmunk/cpVect.h @@ -0,0 +1,106 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +typedef struct cpVect{ + cpFloat x,y; +} cpVect; + +static const cpVect cpvzero={0.0f,0.0f}; + +static inline cpVect +cpv(const cpFloat x, const cpFloat y) +{ + cpVect v = {x, y}; + return v; +} + +static inline cpVect +cpvadd(const cpVect v1, const cpVect v2) +{ + return cpv(v1.x + v2.x, v1.y + v2.y); +} + +static inline cpVect +cpvneg(const cpVect v) +{ + return cpv(-v.x, -v.y); +} + +static inline cpVect +cpvsub(const cpVect v1, const cpVect v2) +{ + return cpv(v1.x - v2.x, v1.y - v2.y); +} + +static inline cpVect +cpvmult(const cpVect v, const cpFloat s) +{ + return cpv(v.x*s, v.y*s); +} + +static inline cpFloat +cpvdot(const cpVect v1, const cpVect v2) +{ + return v1.x*v2.x + v1.y*v2.y; +} + +static inline cpFloat +cpvcross(const cpVect v1, const cpVect v2) +{ + return v1.x*v2.y - v1.y*v2.x; +} + +static inline cpVect +cpvperp(const cpVect v) +{ + return cpv(-v.y, v.x); +} + +static inline cpVect +cpvrperp(const cpVect v) +{ + return cpv(v.y, -v.x); +} + +static inline cpVect +cpvproject(const cpVect v1, const cpVect v2) +{ + return cpvmult(v2, cpvdot(v1, v2)/cpvdot(v2, v2)); +} + +static inline cpVect +cpvrotate(const cpVect v1, const cpVect v2) +{ + return cpv(v1.x*v2.x - v1.y*v2.y, v1.x*v2.y + v1.y*v2.x); +} + +static inline cpVect +cpvunrotate(const cpVect v1, const cpVect v2) +{ + return cpv(v1.x*v2.x + v1.y*v2.y, v1.y*v2.x - v1.x*v2.y); +} + +cpFloat cpvlength(const cpVect v); +cpFloat cpvlengthsq(const cpVect v); // no sqrt() call +cpVect cpvnormalize(const cpVect v); +cpVect cpvforangle(const cpFloat a); // convert radians to a normalized vector +cpFloat cpvtoangle(const cpVect v); // convert a vector to radians +char *cpvstr(const cpVect v); // get a string representation of a vector diff --git a/PopLib/chipmunk/prime.h b/PopLib/chipmunk/prime.h new file mode 100644 index 00000000..15ce4564 --- /dev/null +++ b/PopLib/chipmunk/prime.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +// Used for resizing hash tables. +// Values approximately double. + +static int primes[] = { + 5, //2^2 + 1 + 11, //2^3 + 3 + 17, //2^4 + 1 + 37, //2^5 + 5 + 67, //2^6 + 3 + 131, //2^7 + 3 + 257, //2^8 + 1 + 521, //2^9 + 9 + 1031, //2^10 + 7 + 2053, //2^11 + 5 + 4099, //2^12 + 3 + 8209, //2^13 + 17 + 16411, //2^14 + 27 + 32771, //2^15 + 3 + 65537, //2^16 + 1 + 131101, //2^17 + 29 + 262147, //2^18 + 3 + 524309, //2^19 + 21 + 1048583, //2^20 + 7 + 2097169, //2^21 + 17 + 4194319, //2^22 + 15 + 8388617, //2^23 + 9 + 16777259, //2^24 + 43 + 33554467, //2^25 + 35 + 67108879, //2^26 + 15 + 134217757, //2^27 + 29 + 268435459, //2^28 + 3 + 536870923, //2^29 + 11 + 1073741827, //2^30 + 3 + 0, +}; + +static int +next_prime(int n) +{ + int i = 0; + while(n > primes[i]){ + i++; + assert(primes[i]); // realistically this should never happen + } + + return primes[i]; +} diff --git a/PopLib/common.cpp b/PopLib/common.cpp index f0728ad7..bb5cfd09 100644 --- a/PopLib/common.cpp +++ b/PopLib/common.cpp @@ -1,6 +1,6 @@ #include "common.hpp" #include "math/mtrand.hpp" -#include "debug/debug.hpp" +#include "debug/log.hpp" #include #include #include @@ -8,7 +8,7 @@ #include #include -#include "debug/debug.hpp" +#include "debug/log.hpp" bool PopLib::gDebug = false; static PopLib::MTRand gMTRand; @@ -16,6 +16,8 @@ namespace PopLib { #ifdef _WIN32 std::string gAppDataFolder = std::filesystem::path(std::getenv("LOCALAPPDATA")).string() + "/"; +#elif __APPLE__ +std::string gAppDataFolder = std::filesystem::path(std::getenv("HOME")).string() + "/Library/Application Support/"; #else std::string gAppDataFolder = std::filesystem::path(std::getenv("HOME")).string() + "/.config/"; #endif @@ -58,7 +60,7 @@ std::string PopLib::GetAppDataFolder() void PopLib::SetAppDataFolder(const std::string &thePath) { - /* +#if NEVER std::string aPath = thePath; if (!aPath.empty()) { @@ -67,7 +69,7 @@ void PopLib::SetAppDataFolder(const std::string &thePath) } PopLib::gAppDataFolder = aPath; - */ +#endif } std::string PopLib::URLEncode(const std::string &theString) @@ -162,7 +164,7 @@ std::string PopLib::WStringToString(const std::wstring &theString) } else { - DBG_ASSERTE(aRequiredLength != (size_t)-1); + ASSERT(aRequiredLength != (size_t)-1); if (aRequiredLength == (size_t)-1) return ""; diff --git a/PopLib/common.hpp b/PopLib/common.hpp index a334f09a..0fab48b0 100644 --- a/PopLib/common.hpp +++ b/PopLib/common.hpp @@ -1,8 +1,7 @@ #ifndef __COMMON_HPP__ #define __COMMON_HPP__ -#ifdef _WIN32 + #pragma once -#endif #pragma warning(disable : 4786) #pragma warning(disable : 4503) @@ -19,7 +18,6 @@ #define NOMINMAX #endif - //vorbis workaounds since for some fucking reason, vorbis force defines min and max. #ifdef max #undef max @@ -50,9 +48,7 @@ #include #define _stricmp strcasecmp -#ifndef _WIN32 #define stricmp strcasecmp -#endif #define _cdecl typedef uint8_t BYTE; @@ -84,13 +80,43 @@ typedef unsigned int uint; #undef ulong #endif #define ulong uint32_t -typedef __int64 int64; +// int64 has been removed due to Steam API. I hate this job. typedef std::map DefinesMap; typedef std::map WStringWStringMap; typedef PopString::value_type PopChar; #define HAS_PopChar +// @ThePixelMoon: stupid cmake refuses to set these, +// TODO: change these every new version + +#ifndef POPLIB_VERSION_MAJOR +#define POPLIB_VERSION_MAJOR 2 +#endif + +#ifndef POPLIB_VERSION_MINOR +#define POPLIB_VERSION_MINOR 1 +#endif + +#ifndef POPLIB_VERSION_PATCH +#define POPLIB_VERSION_PATCH 0 +#endif + +#ifndef POPLIB_VERSION_STAGE +#define POPLIB_VERSION_STAGE "alpha" +#endif + +#define STR_HELPER(x) #x +#define STR_MACRO(x) STR_HELPER(x) + +#define POPLIB_VERSION STR_MACRO(POPLIB_VERSION_MAJOR) "." STR_MACRO(POPLIB_VERSION_MINOR) "." STR_MACRO(POPLIB_VERSION_PATCH) "-" STR_MACRO(POPLIB_VERSION_STAGE) + +#ifdef _DEBUG +#define LOGGING_ENABLED +#define ASSERT_ENABLED +#endif + + /** * @namespace PopLib * @brief root namespace for all PopLib classes @@ -308,4 +334,7 @@ struct StringLessNoCase } // namespace PopLib +// @ThePixelMoon: compatibility. +namespace Sexy = PopLib; + #endif \ No newline at end of file diff --git a/PopLib/debug/debug.hpp b/PopLib/debug/debug.hpp deleted file mode 100644 index 5bae380b..00000000 --- a/PopLib/debug/debug.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef __DEBUG_HPP__ -#define __DEBUG_HPP__ -#ifdef _WIN32 -#pragma once -#endif - -#include "common.hpp" -#include - -extern bool gInAssert; - -#ifdef TRACING_ENABLED -void Trace(const char *theStr); -#define TRACE(theStr) Trace(theStr) -#else -#define TRACE(theStr) -#endif - -void TraceFmt(const PopChar *fmt, ...); -void OutputDebug(const PopChar *fmt, ...); - -#ifdef NDEBUG - -#define DBG_ASSERTE(exp) ((void)0) -#define DBG_ASSERT(exp) ((void)0) - -#else - -#define DBG_ASSERTE(exp) \ - { \ - gInAssert = true; \ - assert(exp); \ - gInAssert = false; \ - } -#define DBG_ASSERT(exp) \ - { \ - gInAssert = true; \ - assert(exp); \ - gInAssert = false; \ - } - -#endif // NDEBUG - -#endif \ No newline at end of file diff --git a/PopLib/debug/errorhandler.cpp b/PopLib/debug/errorhandler.cpp deleted file mode 100644 index a2a6504e..00000000 --- a/PopLib/debug/errorhandler.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "errorhandler.hpp" -#include "appbase.hpp" - -using namespace PopLib; - -ErrorHandler *PopLib::gErrorHandler = nullptr; // global error handler!!! :P - -ErrorHandler::ErrorHandler(AppBase *theApp) -{ - gErrorHandler = this; - - mApp = theApp; -} - -ErrorHandler::~ErrorHandler() -{ -} \ No newline at end of file diff --git a/PopLib/debug/errorhandler.hpp b/PopLib/debug/errorhandler.hpp deleted file mode 100644 index 266f0781..00000000 --- a/PopLib/debug/errorhandler.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef __ERRORHANDLER_HPP__ -#define __ERRORHANDLER_HPP__ -#ifdef _WIN32 -#pragma once -#endif - -#include "common.hpp" - -namespace PopLib -{ -class AppBase; - -/** - * @brief the error handler - * - * a replacement for SEHCatcher - */ -class ErrorHandler -{ - private: - // data - - public: - /// @brief the appbase - AppBase *mApp; - - public: - /// @brief constructor - /// @param theApp - ErrorHandler(AppBase *theApp); - /// @brief destructor - virtual ~ErrorHandler(); -}; - -/// @brief app error handler -extern ErrorHandler *gErrorHandler; - -} // namespace PopLib - -#endif \ No newline at end of file diff --git a/PopLib/debug/log.cpp b/PopLib/debug/log.cpp new file mode 100644 index 00000000..5d243ee2 --- /dev/null +++ b/PopLib/debug/log.cpp @@ -0,0 +1,65 @@ +#include "log.hpp" + +#include "misc/autocrit.hpp" +#include "misc/critsect.hpp" + +#include +#include +#include + +#include "memmgr.hpp" + +bool PopLib::gInAssert = false; + +using namespace PopLib; + +const char* GetTypeString(LogType type) +{ + switch (type) { + case LogType::TYPE_INFO: return "INFO"; + case LogType::TYPE_WARNING: return "WARN"; + case LogType::TYPE_ERROR: return "ERROR"; + case LogType::TYPE_DEBUG: return "DEBUG"; + } + return "UNKNOWN"; +} + + +void PopLib::Log(LogType type, const char* file, int line, const char* fmt, ...) +{ + const char* logType = GetTypeString(type); + SDL_Log("[%s][%s:%d]", logType, file, line); //Log the type, file and line. + va_list args; + va_start(args, fmt); + + int sdlCategory = SDL_LOG_CATEGORY_APPLICATION; + SDL_LogPriority priority; + + switch (type) { + case TYPE_INFO: priority = SDL_LOG_PRIORITY_INFO; break; + case TYPE_WARNING: priority = SDL_LOG_PRIORITY_WARN; break; + case TYPE_ERROR: priority = SDL_LOG_PRIORITY_ERROR; break; + case TYPE_DEBUG: priority = SDL_LOG_PRIORITY_DEBUG; break; + default: priority = SDL_LOG_PRIORITY_VERBOSE; break; + } + SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, priority, fmt, args); + va_end(args); + SDL_Log("\n"); +} + +void PopLib::Assert(const char* expr, const char* file, int line, const char* msg) +{ + LOG_ERROR("Assertion failed: (%s), [%s:%d]", expr, file, line); + if (msg) + LOG_INFO("Assertion message: %s", msg); + + gInAssert = true; + #if defined(_MSC_VER) + __debugbreak(); + #elif defined(__GNUC__) || defined(__clang__) + __builtin_trap(); + #else + std::abort(); + #endif + gInAssert = false; +} diff --git a/PopLib/debug/log.hpp b/PopLib/debug/log.hpp new file mode 100644 index 00000000..03016479 --- /dev/null +++ b/PopLib/debug/log.hpp @@ -0,0 +1,49 @@ +#ifndef __LOG_HPP__ +#define __LOG_HPP__ + +#pragma once + +#include "common.hpp" +#include +namespace PopLib +{ + extern bool gInAssert; + + enum LogType + { + TYPE_INFO, + TYPE_WARNING, + TYPE_ERROR, + TYPE_DEBUG, + }; + + + void Log(LogType type, const char* file, int line, const char* fmt, ...);; + void Assert(const char* expr, const char* file, int line, const char* msg = nullptr); + + #ifdef LOGGING_ENABLED + #define LOG_INFO(fmt, ...) Log(LogType::TYPE_INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__) + #define LOG_WARN(fmt, ...) Log(LogType::TYPE_WARNING, __FILE__, __LINE__, fmt, ##__VA_ARGS__) + #define LOG_ERROR(fmt, ...) Log(LogType::TYPE_ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__) + #define LOG_DEBUG(fmt, ...) Log(LogType::TYPE_DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__) + #else + #define LOG_INFO(fmt, ...) + #define LOG_WARN(fmt, ...) + #define LOG_ERROR(fmt, ...) + #define LOG_DEBUG(fmt, ...) + #endif + + #ifdef ASSERT_ENABLED + #define ASSERT(expr) \ + ((expr) ? (void)0 : Assert(#expr, __FILE__, __LINE__)) + + #define ASSERT_MSG(expr, msg) \ + ((expr) ? (void)0 : Assert(#expr, __FILE__, __LINE__, msg)) + #else + + #define ASSERT(expr) + #define ASSERT_MSG(expr, msg) + + #endif +} +#endif \ No newline at end of file diff --git a/PopLib/debug/debug.cpp b/PopLib/debug/memmgr.cpp similarity index 66% rename from PopLib/debug/debug.cpp rename to PopLib/debug/memmgr.cpp index 4155758b..1f157575 100644 --- a/PopLib/debug/debug.cpp +++ b/PopLib/debug/memmgr.cpp @@ -1,5 +1,3 @@ -#include "./debug.hpp" - #include "misc/autocrit.hpp" #include "misc/critsect.hpp" @@ -9,8 +7,7 @@ #include "memmgr.hpp" -extern bool gInAssert = false; -extern bool gDumpLeakedMem = false; +bool gDumpLeakedMem = false; static FILE *gTraceFile = nullptr; static int gTraceFileLen = 0; @@ -48,65 +45,6 @@ class AllocMap : public std::map }; static AllocMap gAllocMap; -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -void Trace(const char *theStr) -{ - if (gTraceFile == nullptr) - { - gTraceFileNum = (gTraceFileNum + 1) % 2; - char aBuf[50]; - sprintf(aBuf, "trace%d.txt", gTraceFileNum + 1); - gTraceFile = fopen(aBuf, "w"); - if (gTraceFile == nullptr) - return; - } - - fprintf(gTraceFile, "%s\n", theStr); - fflush(gTraceFile); - - gTraceFileLen += strlen(theStr); - if (gTraceFileLen > 100000) - { - fclose(gTraceFile); - gTraceFile = nullptr; - gTraceFileLen = 0; - } -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -void TraceFmt(const PopChar *fmt, ...) -{ - // Does not append a newline by default, also takes vararg parameters - - va_list argList; - va_start(argList, fmt); - std::string result = vformat(fmt, argList); - va_end(argList); - - if (gTraceFile == nullptr) - { - gTraceFileNum = (gTraceFileNum + 1) % 2; - char aBuf[50]; - sprintf(aBuf, "trace%d.txt", gTraceFileNum + 1); - gTraceFile = fopen(aBuf, "w"); - if (gTraceFile == nullptr) - return; - } - - fprintf(gTraceFile, "%s", result.c_str()); - fflush(gTraceFile); - - gTraceFileLen += result.length(); - if (gTraceFileLen > 100000) - { - fclose(gTraceFile); - gTraceFile = nullptr; - gTraceFileLen = 0; - } -} - /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void MemAddTrack(void *addr, int asize, const char *fname, int lnum) @@ -161,14 +99,14 @@ void DumpUnfreed() time_t aTime = time(nullptr); sprintf(buf, "Memory Leak Report for %s\n", asctime(localtime(&aTime))); - fprintf(f, buf); + fprintf(f, "%s", buf); SDL_Log("\n%s", buf); for (i = gAllocMap.begin(); i != gAllocMap.end(); i++) { sprintf(buf, "%s(%d) : Leak %d byte%s\n", i->second.file, i->second.line, i->second.size, i->second.size > 1 ? "s" : ""); SDL_Log("%s", buf); - fprintf(f, buf); + fprintf(f, "%s", buf); #ifdef DUMP_LEAKED_MEM unsigned char *data = (unsigned char *)i->first; @@ -226,21 +164,11 @@ void DumpUnfreed() } sprintf(buf, "-----------------------------------------------------------\n"); - fprintf(f, buf); + fprintf(f, "%s", buf); SDL_Log("%s", buf); sprintf(buf, "Total Unfreed: %d bytes (%dKB)\n\n", totalSize, totalSize / 1024); SDL_Log("%s", buf); - fprintf(f, buf); + fprintf(f, "%s", buf); } -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -void OutputDebug(const PopChar *fmt, ...) -{ - va_list argList; - va_start(argList, fmt); - std::string result = vformat(fmt, argList); - va_end(argList); - SDL_Log("%s", result.c_str()); -} diff --git a/PopLib/debug/memmgr.hpp b/PopLib/debug/memmgr.hpp index b4ee60a8..99e15f3f 100644 --- a/PopLib/debug/memmgr.hpp +++ b/PopLib/debug/memmgr.hpp @@ -1,8 +1,7 @@ #ifndef __MEMMGR_HPP__ #define __MEMMGR_HPP__ -#ifdef _WIN32 + #pragma once -#endif ////////////////////////////////////////////////////////////////////////// // HOW TO USE THIS FILE diff --git a/PopLib/debug/perftimer.hpp b/PopLib/debug/perftimer.hpp index 25f324d3..62fe02d8 100644 --- a/PopLib/debug/perftimer.hpp +++ b/PopLib/debug/perftimer.hpp @@ -1,8 +1,7 @@ #ifndef __PERFTIMER_HPP__ #define __PERFTIMER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" diff --git a/PopLib/debug/sehcatcher.cpp b/PopLib/debug/sehcatcher.cpp new file mode 100644 index 00000000..ecee9eef --- /dev/null +++ b/PopLib/debug/sehcatcher.cpp @@ -0,0 +1,1170 @@ +#include "sehcatcher.hpp" +#ifdef __APPLE__ +#include +#else +#include +#endif +#include "SDL3/SDL_messagebox.h" +#include "SDL3/SDL_video.h" +#include "appbase.hpp" +#include +#include "imgui/core/imgui_impl_sdlrenderer3.h" +#include "imgui/imguimanager.hpp" +#ifdef _WIN32 +#include +#elif __APPLE__ +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include +#include +#include + +using namespace PopLib; + +// This is the best i couldve come up with. +#ifdef _WIN32 +#ifdef _WIN64 +#define Eip Rip +#define Ebx Rbx +#define Ecx Rcx +#define Edx Rdx +#define Esi Rsi +#define Esp Rsp +#define Edi Rdi +#define Ebp Rbp +#define Eax Rax +#else +#define Eip Eip +#define Ebx Ebx +#define Ecx Ecx +#define Edx Edx +#define Esi Esi +#define Esp Esp +#define Edi Edi +#define Ebp Ebp +#define Eax Eax +#endif +#endif + +AppBase *SEHCatcher::mApp = NULL; +bool SEHCatcher::mHasDemoFile = false; +bool SEHCatcher::mDebugError = false; +std::string SEHCatcher::mErrorTitle; +std::string SEHCatcher::mErrorText; +std::string SEHCatcher::mUserText; +std::string SEHCatcher::mUploadFileName; +HTTPTransfer SEHCatcher::mSubmitReportTransfer; +bool SEHCatcher::mExiting = false; +bool SEHCatcher::mShowUI = true; +bool SEHCatcher::mAllowSubmit = true; +#ifdef _WIN32 +HMODULE SEHCatcher::mImageHelpLib = NULL; +SYMINITIALIZEPROC SEHCatcher::mSymInitialize = NULL; +SYMSETOPTIONSPROC SEHCatcher::mSymSetOptions = NULL; +UNDECORATESYMBOLNAMEPROC SEHCatcher::mUnDecorateSymbolName = NULL; +SYMCLEANUPPROC SEHCatcher::mSymCleanup = NULL; +STACKWALKPROC SEHCatcher::mStackWalk = NULL; +SYMFUNCTIONTABLEACCESSPROC SEHCatcher::mSymFunctionTableAccess = NULL; +SYMGETMODULEBASEPROC SEHCatcher::mSymGetModuleBase = NULL; +SYMGETSYMFROMADDRPROC SEHCatcher::mSymGetSymFromAddr = NULL; +LPTOP_LEVEL_EXCEPTION_FILTER SEHCatcher::mPreviousFilter; +#endif + +std::wstring SEHCatcher::mCrashMessage = + L"An unexpected error has occured!\nSubmit an issue to the framework's GitHub page.\nPlease help out by providing " + L"as much information as you can about this crash.\nThe crash log has been copied to the clipboard."; +std::string SEHCatcher::mIssueWebsite = "https://github.com/teampopwork/PopLib/issues"; +std::wstring SEHCatcher::mSubmitErrorMessage = L"Failed to open the URL: https://github.com/teampopwork/PopLib/issues."; + +static bool gUseDefaultFonts = true; + +#ifdef _WIN32 +struct +{ + DWORD dwExceptionCode; + const char *szMessage; +} gMsgTable[] = {{STATUS_SEGMENT_NOTIFICATION, "Segment Notification"}, + {STATUS_BREAKPOINT, "Breakpoint"}, + {STATUS_SINGLE_STEP, "Single step"}, + {STATUS_WAIT_0, "Wait 0"}, + {STATUS_ABANDONED_WAIT_0, "Abandoned Wait 0"}, + {STATUS_USER_APC, "User APC"}, + {STATUS_TIMEOUT, "Timeout"}, + {STATUS_PENDING, "Pending"}, + {STATUS_GUARD_PAGE_VIOLATION, "Guard Page Violation"}, + {STATUS_DATATYPE_MISALIGNMENT, "Data Type Misalignment"}, + {STATUS_ACCESS_VIOLATION, "Access Violation"}, + {STATUS_IN_PAGE_ERROR, "In Page Error"}, + {STATUS_NO_MEMORY, "No Memory"}, + {STATUS_ILLEGAL_INSTRUCTION, "Illegal Instruction"}, + {STATUS_NONCONTINUABLE_EXCEPTION, "Noncontinuable Exception"}, + {STATUS_INVALID_DISPOSITION, "Invalid Disposition"}, + {STATUS_ARRAY_BOUNDS_EXCEEDED, "Array Bounds Exceeded"}, + {STATUS_FLOAT_DENORMAL_OPERAND, "Float Denormal Operand"}, + {STATUS_FLOAT_DIVIDE_BY_ZERO, "Divide by Zero"}, + {STATUS_FLOAT_INEXACT_RESULT, "Float Inexact Result"}, + {STATUS_FLOAT_INVALID_OPERATION, "Float Invalid Operation"}, + {STATUS_FLOAT_OVERFLOW, "Float Overflow"}, + {STATUS_FLOAT_STACK_CHECK, "Float Stack Check"}, + {STATUS_FLOAT_UNDERFLOW, "Float Underflow"}, + {STATUS_INTEGER_DIVIDE_BY_ZERO, "Integer Divide by Zero"}, + {STATUS_INTEGER_OVERFLOW, "Integer Overflow"}, + {STATUS_PRIVILEGED_INSTRUCTION, "Privileged Instruction"}, + {STATUS_STACK_OVERFLOW, "Stack Overflow"}, + {STATUS_CONTROL_C_EXIT, "Ctrl+C Exit"}, + {0xFFFFFFFF, ""}}; +#else +struct +{ + int sig; + const char *msg; +} gSignalMsgTable[] = {{SIGSEGV, "Segmentation Fault"}, + {SIGABRT, "Abort Signal"}, + {SIGFPE, "Floating Point Exception"}, + {SIGILL, "Illegal Instruction"}, + {SIGBUS, "Bus Error"}, + {SIGTERM, "Termination Signal"}, + {0, nullptr}}; +#endif + +SDL_Renderer *mRenderer; +SDL_Window *mWindow; +ImGuiContext *mImGuiContext; + +SEHCatcher::SEHCatcher() +{ +#ifdef _WIN32 + mPreviousFilter = SetUnhandledExceptionFilter(UnhandledExceptionFilter); +#elif __APPLE__ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = signalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + int signals[] = {SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTERM}; + for (int sig : signals) + sigaction(sig, &sa, nullptr); +#else + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = signalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + int signals[] = {SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTERM}; + for (int sig : signals) + sigaction(sig, &sa, nullptr); +#endif +} + +SEHCatcher::~SEHCatcher() noexcept +{ +#ifdef _WIN32 + SetUnhandledExceptionFilter(mPreviousFilter); +#elif __APPLE__ + int signals[] = {SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTERM}; + for (int sig : signals) + sigaction(sig, nullptr, nullptr); +#else + int signals[] = {SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTERM}; + for (int sig : signals) + sigaction(sig, nullptr, nullptr); +#endif +} + +#ifdef _WIN32 +long __stdcall SEHCatcher::UnhandledExceptionFilter(LPEXCEPTION_POINTERS lpExceptPtr) +{ + if (mApp != NULL) + mApp->SEHOccured(); + DoHandleDebugEvent(lpExceptPtr); + if (!mDebugError) + SetErrorMode(SEM_NOGPFAULTERRORBOX); + return EXCEPTION_CONTINUE_SEARCH; +} +#elif __APPLE__ +void SEHCatcher::signalHandler(int sig, siginfo_t *info, void *ucontext) +{ + const int maxFrames = 64; + void *frames[maxFrames]; + int count = backtrace(frames, maxFrames); + char **symbols = backtrace_symbols(frames, count); + + std::ostringstream oss; + oss << "Signal " << sig << " (" << strsignal(sig) << ")\n"; + oss << "Platform: macOS\n"; + for (int i = 0; i < count; ++i) + { + Dl_info dli; + if (dladdr(frames[i], &dli)) + { + const char *module = dli.dli_fname ? dli.dli_fname : "unknown"; + const char *symbol = dli.dli_sname ? dli.dli_sname : "unknown"; + void *offset = (void *)((char *)frames[i] - (char *)dli.dli_saddr); + oss << " " << GetFilename(module) << "!" << symbol << "+0x" << std::hex << offset << std::dec << "\n"; + } + else + { + oss << " " << symbols[i] << "\n"; + } + } + free(symbols); + std::string dump = oss.str(); + if (mApp != NULL) + { + mApp->CopyToClipboard(dump); + } + WriteToFile(dump); + ShowErrorDialog("Crash Detected", dump); + _exit(1); +} +#else +void SEHCatcher::signalHandler(int sig, siginfo_t *info, void *ucontext) +{ + const int maxFrames = 64; + void *frames[maxFrames]; + int count = backtrace(frames, maxFrames); + char **symbols = backtrace_symbols(frames, count); + + std::ostringstream oss; + oss << "Signal " << sig << " (" << strsignal(sig) << ")\n"; + oss << "Platform: Linux\n"; + for (int i = 0; i < count; ++i) + { + Dl_info dli; + if (dladdr(frames[i], &dli)) + { + const char *module = dli.dli_fname ? dli.dli_fname : "unknown"; + const char *symbol = dli.dli_sname ? dli.dli_sname : "unknown"; + void *offset = (void *)((char *)frames[i] - (char *)dli.dli_saddr); + oss << " " << GetFilename(module) << "!" << symbol << "+0x" << std::hex << offset << std::dec << "\n"; + } + else + { + oss << " " << symbols[i] << "\n"; + } + } + free(symbols); + std::string dump = oss.str(); + if (mApp != NULL) + { + mApp->CopyToClipboard(dump); + } + WriteToFile(dump); + ShowErrorDialog("Crash Detected", dump); + _exit(1); +} +#endif + +static bool StrToLongHex(const std::string &aString, DWORD *theValue) +{ + *theValue = 0; + + for (int i = 0; i < (int)aString.length(); i++) + { + char aChar = aString[i]; + + int aValue; + if ((aChar >= '0') && (aChar <= '9')) + aValue = aChar - '0'; + else if ((aChar >= 'A') && (aChar <= 'F')) + aValue = (aChar - 'A') + 10; + else if ((aChar >= 'a') && (aChar <= 'f')) + aValue = (aChar - 'a') + 10; + else + return false; + + *theValue += aValue << ((aString.length() - i - 1) * 4); + } + + return true; +} + +void SEHCatcher::GetSymbolsFromMapFile(std::string &theDebugDump) +{ +#ifdef _WIN32 + DWORD aTick = GetTickCount(); + WIN32_FIND_DATAA aFindData; + HANDLE aFindHandle = FindFirstFileA("*.map", &aFindData); + if (aFindHandle == INVALID_HANDLE_VALUE) + return; + + FindClose(aFindHandle); + + typedef std::pair SymbolPair; + typedef std::map SymbolMap; + typedef std::pair LineNumPair; + typedef std::map LineNumMap; + + /**/ + SymbolMap aSymbolMap; + LineNumMap aLineNumMap; + std::fstream aFile(aFindData.cFileName, std::ios::in); + + if (!aFile.is_open()) + return; + + // Parse map file + std::string aCurLinesFile; + while (!aFile.eof()) + { + char aStr[4096]; + aFile.getline(aStr, 4096); + + std::string aString = aStr; + + if ((aString.length() > 22) && (aString[5] == ':')) + { + std::string aFoundPreStr = aString.substr(1, 4); + std::string aFoundPostStr = aString.substr(6, 8); + + DWORD aFoundPreVal; + DWORD aFoundPostVal; + + if (StrToLongHex(aFoundPreStr, &aFoundPreVal) && StrToLongHex(aFoundPostStr, &aFoundPostVal)) + { + int aSpacePos = aString.find(' ', 21); + + if ((aString[20] == ' ') && (aSpacePos >= 0)) + aSymbolMap[SymbolPair(aFoundPreVal, aFoundPostVal)] = aString.substr(21, aSpacePos - 21); + } + } + else if (strcmp(aString.substr(0, strlen("Line numbers")).c_str(), "Line numbers") == 0) + { + int aSegmentPos = aString.rfind(')'); + if (aSegmentPos == -1) + aSegmentPos = aString.length(); + + int aPreLen = strlen("Line numbers for "); + + int aStartPos = aString.find('('); + + aCurLinesFile = aString.substr(aStartPos + 1, aSegmentPos - aStartPos - 1); + } + else if ((aCurLinesFile.length() > 0) && (aString.length() == 80)) + { + // Line number stuff + + for (int i = 0; i < 4; i++) + { + int aStartLoc = 20 * i; + + int aLine = atoi(aString.substr(aStartLoc, 6).c_str()); + + std::string aFoundPreStr = aString.substr(aStartLoc + 7, 4); + std::string aFoundPostStr = aString.substr(aStartLoc + 12, 8); + + DWORD aFoundPreVal; + DWORD aFoundPostVal; + + if (StrToLongHex(aFoundPreStr, &aFoundPreVal) && StrToLongHex(aFoundPostStr, &aFoundPostVal)) + { + aLineNumMap[SymbolPair(aFoundPreVal, aFoundPostVal)] = LineNumPair(aCurLinesFile, aLine); + } + } + } + } + + // Parse stack trace + for (int i = 0; i < (int)theDebugDump.length(); i++) + { + if (theDebugDump.at(i) == ':') + { + std::string aFindPreStr = theDebugDump.substr(i - 4, 4); + std::string aFindPostStr = theDebugDump.substr(i + 1, 8); + + DWORD aFindPreVal; + DWORD aFindPostVal; + + if (StrToLongHex(aFindPreStr, &aFindPreVal) && StrToLongHex(aFindPostStr, &aFindPostVal)) + { + + int aBestDist = -1; + SymbolMap::iterator aSymbolItr = aSymbolMap.lower_bound(SymbolPair(aFindPreVal, aFindPostVal)); + if (aSymbolItr != aSymbolMap.end() && aSymbolItr != aSymbolMap.begin() && + aSymbolItr->first.second != aFindPostVal) + --aSymbolItr; + + if (aSymbolItr != aSymbolMap.end() && aSymbolItr->first.first == aFindPreVal) + aBestDist = aFindPostVal - aSymbolItr->first.second; + + if (aBestDist != -1) + { + std::string &aBestName = aSymbolItr->second; + + char aSymbolName[4096]; + + if (mUnDecorateSymbolName(aBestName.c_str(), aSymbolName, 4096, + UNDNAME_NO_ALLOCATION_MODEL | UNDNAME_NO_ACCESS_SPECIFIERS | + UNDNAME_NO_THROW_SIGNATURES | UNDNAME_NO_MEMBER_TYPE) == 0) + strcpy(aSymbolName, aBestName.c_str()); + + if (aBestDist != 0) + { + sprintf(aSymbolName + strlen(aSymbolName), "+0x%X", aBestDist); + } + + std::string aNewText = aSymbolName; + + LineNumMap::iterator aLineNumItr = aLineNumMap.lower_bound(SymbolPair(aFindPreVal, aFindPostVal)); + if (aLineNumItr != aLineNumMap.end() && aLineNumItr != aLineNumMap.begin() && + aLineNumItr->first.second != aFindPostVal) + --aLineNumItr; + + if (aLineNumItr != aLineNumMap.end() && aLineNumItr->first.first == aFindPreVal) + { + std::string &aBestFile = aLineNumItr->second.first; + int aBestLine = aLineNumItr->second.second; + int aBestLineDist = aFindPostVal - aLineNumItr->first.second; + + char aDistStr[4096]; + sprintf(aDistStr, "\r\n %s line %d +0x%X", aBestFile.c_str(), aBestLine, aBestLineDist); + aNewText += aDistStr; + } + + theDebugDump.erase(i - 4, 13); + theDebugDump.insert(i - 4, aNewText.c_str()); + } + } + } + } + +// MessageBox(NULL,StrFormat("%d",GetTickCount()-aTick).c_str(),"Time",MB_OK); +#endif +} + +#ifdef _WIN32 + +std::string SEHCatcher::IntelWalk(PCONTEXT theContext, int theSkipCount) +{ + std::string aDebugDump; + char aBuffer[2048]; + +#ifdef _WIN64 + DWORD64 pc = theContext->Eip; + PDWORD64 pFrame, pPrevFrame; + + pFrame = (PDWORD64)theContext->Ebp; +#else + DWORD pc = theContext->Eip; + PDWORD pFrame, pPrevFrame; + + pFrame = (PDWORD)theContext->Ebp; +#endif + + for (;;) + { + char szModule[MAX_PATH] = ""; + DWORD section = 0, offset = 0; + + GetLogicalAddress((PVOID)pc, szModule, sizeof(szModule), section, offset); + + sprintf(aBuffer, "%08X %08X %04X:%08X %s\r\n", pc, pFrame, section, offset, GetFilename(szModule).c_str()); + aDebugDump += aBuffer; + + if (pFrame == nullptr) + break; + +#ifdef _WIN64 + pc = pFrame[1]; + pPrevFrame = pFrame; + pFrame = (PDWORD64)pFrame[0]; // proceed to next higher frame on stack +#else + pc = pFrame[1]; + pPrevFrame = pFrame; + pFrame = (PDWORD)pFrame[0]; +#endif + +#ifdef _WIN64 + if ((DWORD64)pFrame & 3) // Frame pointer must be aligned on a + break; // Bail if not aligned. +#else + if ((DWORD)pFrame & 3) // Frame pointer must be aligned on a + break; // DWORD boundary. Bail if not so. +#endif + + if (pFrame <= pPrevFrame) + break; + + // Can two DWORDs be read from the supposed frame address? + if (IsBadWritePtr(pFrame, sizeof(PVOID) * 2)) + break; + }; + + return aDebugDump; +} + +void SEHCatcher::DoHandleDebugEvent(LPEXCEPTION_POINTERS lpEP) +{ + bool hasImageHelp = LoadImageHelp(); + + std::string anErrorTitle; + std::string aDebugDump; + + char aBuffer[2048]; + + /////////////////////////// + // first name the exception + const char *szName = NULL; + for (int i = 0; gMsgTable[i].dwExceptionCode != 0xFFFFFFFF; i++) + { + if (gMsgTable[i].dwExceptionCode == lpEP->ExceptionRecord->ExceptionCode) + { + szName = gMsgTable[i].szMessage; + break; + } + } + + if (szName != NULL) + { + sprintf(aBuffer, "Exception: %s (code 0x%x) at address %08X in thread %X\r\n", szName, + lpEP->ExceptionRecord->ExceptionCode, lpEP->ExceptionRecord->ExceptionAddress, GetCurrentThreadId()); + } + else + { + sprintf(aBuffer, "Unknown exception: (code 0x%x) at address %08X in thread %X\r\n", + lpEP->ExceptionRecord->ExceptionCode, lpEP->ExceptionRecord->ExceptionAddress, GetCurrentThreadId()); + } + + aDebugDump += aBuffer; + + /////////////////////////////////////////////////////////// + // Get logical address of the module where exception occurs + DWORD section, offset; + GetLogicalAddress(lpEP->ExceptionRecord->ExceptionAddress, aBuffer, sizeof(aBuffer), section, offset); + aDebugDump += "Module: " + GetFilename(aBuffer) + "\r\n"; + sprintf(aBuffer, "Logical Address: %04X:%08X\r\n", section, offset); + aDebugDump += aBuffer; + + aDebugDump += "\r\n"; + + anErrorTitle = StrFormat("Exception at %04X:%08X", section, offset); + + std::string aWalkString = ""; + + if (hasImageHelp) + aWalkString = ImageHelpWalk(lpEP->ContextRecord, 0); + + if (aWalkString.length() == 0) + aWalkString = IntelWalk(lpEP->ContextRecord, 0); + + aDebugDump += aWalkString; + + aDebugDump += "\r\n"; + sprintf(aBuffer, ("EAX:%08X EBX:%08X ECX:%08X EDX:%08X ESI:%08X EDI:%08X\r\n"), lpEP->ContextRecord->Eax, + lpEP->ContextRecord->Ebx, lpEP->ContextRecord->Ecx, lpEP->ContextRecord->Edx, lpEP->ContextRecord->Esi, + lpEP->ContextRecord->Edi); + aDebugDump += aBuffer; + sprintf(aBuffer, "EIP:%08X ESP:%08X EBP:%08X\r\n", lpEP->ContextRecord->Eip, lpEP->ContextRecord->Esp, + lpEP->ContextRecord->Ebp); + aDebugDump += aBuffer; + sprintf(aBuffer, "CS:%04X SS:%04X DS:%04X ES:%04X FS:%04X GS:%04X\r\n", lpEP->ContextRecord->SegCs, + lpEP->ContextRecord->SegSs, lpEP->ContextRecord->SegDs, lpEP->ContextRecord->SegEs, + lpEP->ContextRecord->SegFs, lpEP->ContextRecord->SegGs); + aDebugDump += aBuffer; + sprintf(aBuffer, "Flags:%08X\r\n", lpEP->ContextRecord->EFlags); + aDebugDump += aBuffer; + + aDebugDump += "\r\n"; + aDebugDump += GetSysInfo(); + if (mApp != NULL) + { + std::string aGameSEHInfo = mApp->GetGameSEHInfo(); + if (aGameSEHInfo.length() > 0) + { + aDebugDump += "\r\n"; + aDebugDump += aGameSEHInfo; + } + mApp->CopyToClipboard(aDebugDump); + } + + // just for any case + // some time we can't go through GetSymbolsFromMapFile, probably because of some memory corruption + WriteToFile(aDebugDump); + + if (hasImageHelp) + GetSymbolsFromMapFile(aDebugDump); + + // rewrite crash file + WriteToFile(aDebugDump); + + if (mShowUI) + ShowErrorDialog(anErrorTitle, aDebugDump); + + //::MessageBox(NULL, aDebugDump.c_str(), "ERROR", MB_ICONERROR); + + UnloadImageHelp(); +} + +bool SEHCatcher::LoadImageHelp() +{ + mImageHelpLib = LoadLibraryA("IMAGEHLP.DLL"); + if (!mImageHelpLib) + return false; + + mSymInitialize = (SYMINITIALIZEPROC)GetProcAddress(mImageHelpLib, "SymInitialize"); + if (!mSymInitialize) + return false; + + mSymSetOptions = (SYMSETOPTIONSPROC)GetProcAddress(mImageHelpLib, "SymSetOptions"); + if (!mSymSetOptions) + return false; + + mSymCleanup = (SYMCLEANUPPROC)GetProcAddress(mImageHelpLib, "SymCleanup"); + if (!mSymCleanup) + return false; + + mUnDecorateSymbolName = (UNDECORATESYMBOLNAMEPROC)GetProcAddress(mImageHelpLib, "UnDecorateSymbolName"); + if (!mUnDecorateSymbolName) + return false; + + mStackWalk = (STACKWALKPROC)GetProcAddress(mImageHelpLib, "StackWalk"); + if (!mStackWalk) + return false; + + mSymFunctionTableAccess = (SYMFUNCTIONTABLEACCESSPROC)GetProcAddress(mImageHelpLib, "SymFunctionTableAccess"); + if (!mSymFunctionTableAccess) + return false; + + mSymGetModuleBase = (SYMGETMODULEBASEPROC)GetProcAddress(mImageHelpLib, "SymGetModuleBase"); + if (!mSymGetModuleBase) + return false; + + mSymGetSymFromAddr = (SYMGETSYMFROMADDRPROC)GetProcAddress(mImageHelpLib, "SymGetSymFromAddr"); + if (!mSymGetSymFromAddr) + return false; + + mSymSetOptions(SYMOPT_DEFERRED_LOADS); + + // Get image filename of the main executable + char filepath[MAX_PATH], *lastdir, *pPath; + DWORD filepathlen = GetModuleFileNameA(NULL, filepath, sizeof(filepath)); + + lastdir = strrchr(filepath, '/'); + if (lastdir == NULL) + lastdir = strrchr(filepath, '\\'); + if (lastdir != NULL) + lastdir[0] = '\0'; + + // Initialize the symbol table routines, supplying a pointer to the path + pPath = filepath; + if (strlen(filepath) == 0) + pPath = NULL; + + if (!mSymInitialize(GetCurrentProcess(), pPath, TRUE)) + return false; + + return true; +} + +void SEHCatcher::UnloadImageHelp() +{ + if (mImageHelpLib != NULL) + FreeLibrary(mImageHelpLib); +} + +std::string SEHCatcher::ImageHelpWalk(PCONTEXT theContext, int theSkipCount) +{ + char aBuffer[2048]; + std::string aDebugDump; + + STACKFRAME sf; + memset(&sf, 0, sizeof(sf)); + + // Initialize the STACKFRAME structure for the first call. This is only + // necessary for Intel CPUs, and isn't mentioned in the documentation. + sf.AddrPC.Offset = theContext->Eip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = theContext->Esp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = theContext->Ebp; + sf.AddrFrame.Mode = AddrModeFlat; + + int aLevelCount = 0; + + for (;;) + { + if (!mStackWalk(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(), &sf, NULL /*theContext*/, + NULL, mSymFunctionTableAccess, mSymGetModuleBase, 0)) + { + DWORD lastErr = GetLastError(); + sprintf(aBuffer, "StackWalk failed (error %d)\r\n", lastErr); + aDebugDump += aBuffer; + break; + } + + if ((sf.AddrFrame.Offset == 0) || (sf.AddrPC.Offset == 0)) + break; + + if (theSkipCount > 0) + { + theSkipCount--; + continue; + } + + BYTE symbolBuffer[sizeof(IMAGEHLP_SYMBOL) + 512]; + PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)symbolBuffer; + pSymbol->SizeOfStruct = sizeof(symbolBuffer); + pSymbol->MaxNameLength = 512; + + DWORD symDisplacement = 0; // Displacement of the input address, + // relative to the start of the symbol + + if (mSymGetSymFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &symDisplacement, pSymbol)) + { + char aUDName[256]; + mUnDecorateSymbolName(pSymbol->Name, aUDName, 256, + UNDNAME_NO_ALLOCATION_MODEL | UNDNAME_NO_ALLOCATION_LANGUAGE | + UNDNAME_NO_MS_THISTYPE | UNDNAME_NO_ACCESS_SPECIFIERS | UNDNAME_NO_THISTYPE | + UNDNAME_NO_MEMBER_TYPE | UNDNAME_NO_RETURN_UDT_MODEL | + UNDNAME_NO_THROW_SIGNATURES | UNDNAME_NO_SPECIAL_SYMS); + + sprintf(aBuffer, "%08X %08X %hs+%X\r\n", sf.AddrFrame.Offset, sf.AddrPC.Offset, aUDName, symDisplacement); + } + else // No symbol found. Print out the logical address instead. + { + char szModule[MAX_PATH]; + DWORD section = 0, offset = 0; + + GetLogicalAddress((PVOID)sf.AddrPC.Offset, szModule, sizeof(szModule), section, offset); + sprintf(aBuffer, "%08X %08X %04X:%08X %s\r\n", sf.AddrFrame.Offset, sf.AddrPC.Offset, section, offset, + GetFilename(szModule).c_str()); + } + aDebugDump += aBuffer; + + sprintf(aBuffer, "Params: %08X %08X %08X %08X\r\n", sf.Params[0], sf.Params[1], sf.Params[2], sf.Params[3]); + aDebugDump += aBuffer; + aDebugDump += "\r\n"; + + aLevelCount++; + + // check for loop + if (aLevelCount > 1000) + break; + } + + return aDebugDump; +} + +#endif + +bool SEHCatcher::GetLogicalAddress(void *addr, char *szModule, DWORD len, DWORD §ion, DWORD &offset) +{ +#ifdef _WIN32 + MEMORY_BASIC_INFORMATION mbi; + if (!VirtualQuery(addr, &mbi, sizeof(mbi))) + return false; + DWORD hMod = (DWORD)mbi.AllocationBase; + if (!GetModuleFileNameA((HMODULE)hMod, szModule, len)) + return false; + PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod; + PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew); + PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHdr); + DWORD rva = (DWORD)addr - hMod; + for (unsigned i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSection++) + { + DWORD sectionStart = pSection->VirtualAddress; + DWORD sectionEnd = sectionStart + std::max(pSection->SizeOfRawData, pSection->Misc.VirtualSize); + if ((rva >= sectionStart) && (rva <= sectionEnd)) + { + section = i + 1; + offset = rva - sectionStart; + return true; + } + } +#elif __APPLE__ + Dl_info dli; + if (dladdr(addr, &dli)) + { + strncpy(szModule, dli.dli_fname ? dli.dli_fname : "unknown", len); + szModule[len - 1] = '\0'; + section = 0; // macOS doesn't use sections like Windows PE + offset = (char *)addr - (char *)dli.dli_fbase; + return true; + } +#else + Dl_info dli; + if (dladdr(addr, &dli)) + { + strncpy(szModule, dli.dli_fname ? dli.dli_fname : "unknown", len); + szModule[len - 1] = '\0'; + section = 0; // Linux doesn't use sections like Windows PE + offset = (char *)addr - (char *)dli.dli_fbase; + return true; + } +#endif + + return false; // Should never get here! +} + +std::string SEHCatcher::GetFilename(const std::string &thePath) +{ + int aLastSlash = std::max((int)thePath.rfind('\\'), (int)thePath.rfind('/')); + + if (aLastSlash >= 0) + { + return thePath.substr(aLastSlash + 1); + } + else + return thePath; +} + +int aCount = 0; + +void SEHCatcher::SEHWindowProc() +{ + SDL_Event e; + while (SDL_PollEvent(&e)) + { + if (e.type == SDL_EVENT_QUIT) + mExiting = true; + ImGui_ImplSDL3_ProcessEvent(&e); + } +} + +void SEHCatcher::WriteToFile(const std::string &theErrorText) +{ + std::fstream aStream("crash.txt", std::ios::out); + aStream << theErrorText.c_str() << std::endl; +} + +void SEHCatcher::SubmitReportThread(void *theArg) +{ + std::string aSeperator = "---------------------------7d3e1f30eec"; + + DefinesMap aSEHWebParams; + + mApp->GetSEHWebParams(&aSEHWebParams); + + std::string aContent; + + DefinesMap::iterator anItr = aSEHWebParams.begin(); + while (anItr != aSEHWebParams.end()) + { + aContent += "--" + aSeperator + + "\r\n" + "Content-Disposition: form-data; name=\"" + + anItr->first + "\"\r\n" + "\r\n" + anItr->second + "\r\n"; + + ++anItr; + } + + aContent += + + /*"--" + aSeperator + "\r\n" + "Content-Disposition: form-data; name=\"username\"\r\n" + + "\r\n" + + mApp->mUserName + "\r\n" + */ + + "--" + aSeperator + + "\r\n" + "Content-Disposition: form-data; name=\"prod\"\r\n" + + "\r\n" + mApp->mProdName + "\r\n" + + + "--" + aSeperator + + "\r\n" + "Content-Disposition: form-data; name=\"version\"\r\n" + + "\r\n" + mApp->mProductVersion + "\r\n" + + + /*"--" + aSeperator + "\r\n" + "Content-Disposition: form-data; name=\"buildnum\"\r\n" + + "\r\n" + + StrFormat("%d", mApp->mBuildNum) + "\r\n" + + + "--" + aSeperator + "\r\n" + "Content-Disposition: form-data; name=\"builddate\"\r\n" + + "\r\n" + + mApp->mBuildDate + "\r\n" + + + "--" + aSeperator + "\r\n" + "Content-Disposition: form-data; name=\"referid\"\r\n" + + "\r\n" + + mApp->mReferId + "\r\n" +*/ + + "--" + aSeperator + + "\r\n" + "Content-Disposition: form-data; name=\"usertext\"\r\n" + + "\r\n" + mUserText + "\r\n" + + + "--" + aSeperator + + "\r\n" + "Content-Disposition: form-data; name=\"errortitle\"\r\n" + + "\r\n" + mErrorTitle + "\r\n" + + + "--" + aSeperator + + "\r\n" + "Content-Disposition: form-data; name=\"errortext\"\r\n" + + "\r\n" + mErrorText + "\r\n"; + + if (mHasDemoFile) + { + Buffer aBuffer; + mApp->ReadBufferFromFile(mUploadFileName, &aBuffer); + + aContent += "--" + aSeperator + + "\r\n" + "Content-Disposition: form-data; name=\"demofile\"; filename=\"popcap.dmo\"\r\n" + + "Content-Type: application/octet-stream\r\n" + "\r\n"; + + aContent.insert(aContent.end(), (char *)aBuffer.GetDataPtr(), + (char *)aBuffer.GetDataPtr() + aBuffer.GetDataLen()); + + aContent += "\r\n"; + } + + aContent += "--" + aSeperator + "--\r\n"; + + std::string aSendString = "POST /deluxe_error.php HTTP/1.1\r\n" + "Content-Type: multipart/form-data; boundary=" + + aSeperator + + "\r\n" + "User-Agent: Mozilla/4.0 (compatible; popcap)\r\n" + + "Host: " + mIssueWebsite + "\r\n" + + "Content-Length: " + StrFormat("%d", aContent.length()) + "\r\n" + + "Connection: close\r\n" + "\r\n" + aContent; + + mSubmitReportTransfer.SendRequestString(mIssueWebsite, aSendString); +} + +void SEHCatcher::ShowErrorDialog(const std::string &theErrorTitle, const std::string &theErrorText) +{ + mExiting = false; + mErrorTitle = theErrorTitle; + mErrorText = theErrorText; + + std::wstring_convert> converter; + std::string narrowStr = converter.to_bytes(mCrashMessage); + const char *cstr = narrowStr.c_str(); + + SDL_Init(SDL_INIT_VIDEO); + mWindow = SDL_CreateWindow(mErrorTitle.c_str(), 600, 400, 0); + mRenderer = SDL_CreateRenderer(mWindow, NULL); + + IMGUI_CHECKVERSION(); + mImGuiContext = ImGui::CreateContext(); + ImGui::SetCurrentContext(mImGuiContext); + ImGuiIO &io = ImGui::GetIO(); + (void)io; // uhhhhhh + ImGui::StyleColorsDark(); + + ImGui_ImplSDL3_InitForSDLRenderer(mWindow, mRenderer); + ImGui_ImplSDLRenderer3_Init(mRenderer); + + // @ThePixelMoon: Oopsie! + SDL_SetClipboardText(mErrorText.c_str()); // we're not lying. + + while (!mExiting) + { + SEHWindowProc(); + + ImGui_ImplSDLRenderer3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + + int windowWidth; + int windowHeight; + SDL_GetWindowSize(mWindow, &windowWidth, &windowHeight); + + ImGui::SetNextWindowSize(ImVec2(windowWidth, windowHeight), ImGuiCond_Always); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + + ImGui::Begin("Fatal Error!", nullptr, + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoSavedSettings); + + ImGui::TextWrapped("%s", cstr); + ImGui::Spacing(); + + float contentHeight = ImGui::GetContentRegionAvail().y; + float logHeight = 180.0f; + float buttonHeight = 30.0f; + float spacingE = 10.0f; + float reservedBottom = logHeight + buttonHeight + spacingE * 3; + ImGui::Dummy(ImVec2(0.0f, contentHeight - reservedBottom)); + + ImGui::BeginChild("ErrorDetails", ImVec2(0, 200), true, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); + ImGui::TextWrapped("%s", mErrorText.c_str()); + ImGui::PopFont(); + ImGui::EndChild(); + + ImGui::Spacing(); + + float buttonWidth = 120.0f; + float spacing = ImGui::GetStyle().ItemSpacing.x; + + int numButtons = 1; +#ifdef _DEBUG + numButtons += 1; +#endif + numButtons += 1; + + float totalWidth = numButtons * buttonWidth + (numButtons - 1) * spacing; + float availWidth = ImGui::GetContentRegionAvail().x; + float startX = (availWidth - totalWidth) * 0.5f; + ImGui::SetCursorPosX(startX); + + if (ImGui::Button("Send Issue", ImVec2(buttonWidth, 0))) + { + if (!mApp || !mApp->OpenURL(mIssueWebsite)) + { + std::wstring_convert> converter2; + std::string narrowStr2 = converter.to_bytes(mSubmitErrorMessage); + const char *cstr2 = narrowStr2.c_str(); + + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", cstr2, mWindow); + } + + mExiting = true; + } +#ifdef _DEBUG + ImGui::SameLine(); + if (ImGui::Button("Debug", ImVec2(buttonWidth, 0))) + { + mDebugError = true; + mExiting = true; + } +#endif + ImGui::SameLine(); + if (ImGui::Button("Close Now", ImVec2(buttonWidth, 0))) + { + mExiting = true; + } + + ImGui::End(); + + ImGui::Render(); + SDL_SetRenderDrawColor(mRenderer, 240, 240, 240, 255); + SDL_RenderClear(mRenderer); + ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), mRenderer); + SDL_RenderPresent(mRenderer); + + SDL_Delay(16); + } + + try + { + ImGui_ImplSDLRenderer3_Shutdown(); + } + catch (...) + { + } + try + { + ImGui_ImplSDL3_Shutdown(); + } + catch (...) + { + } + try + { + ImGui::DestroyContext(mImGuiContext); + } + catch (...) + { + } + try + { + if (mRenderer) + SDL_DestroyRenderer(mRenderer); + } + catch (...) + { + } + try + { + if (mWindow) + SDL_DestroyWindow(mWindow); + } + catch (...) + { + } + + if (mExiting && !mDebugError) + mApp->Shutdown(); +} + +std::string SEHCatcher::GetSysInfo() +{ + std::string aDebugDump; + +#ifdef _WIN32 + OSVERSIONINFOA aVersionInfo; + aVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionExA(&aVersionInfo); + aDebugDump += "Windows Ver: "; + if (aVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) + aDebugDump += "NT "; + else + aDebugDump += "9x "; + char aVersionStr[20]; + sprintf(aVersionStr, "%d.%d", aVersionInfo.dwMajorVersion, aVersionInfo.dwMinorVersion); + aDebugDump += aVersionStr; + aDebugDump += " "; + aDebugDump += aVersionInfo.szCSDVersion; + aDebugDump += " "; + sprintf(aVersionStr, "%d", aVersionInfo.dwBuildNumber); + aDebugDump += "Build "; + aDebugDump += aVersionStr; + aDebugDump += "\r\n"; +#elif __APPLE__ + struct utsname un; + if (uname(&un) == 0) + { + aDebugDump += "OS: macOS " + std::string(un.release) + " (" + un.version + ")\n"; + aDebugDump += "Machine: " + std::string(un.machine) + "\n"; + } + else + { + aDebugDump += "OS: macOS Unknown\n"; + } +#else + struct utsname un; + if (uname(&un) == 0) + { + aDebugDump += "OS: " + std::string(un.sysname) + " " + un.release + " (" + un.version + ")\n"; + aDebugDump += "Machine: " + std::string(un.machine) + "\n"; + } + else + { + aDebugDump += "OS: Unknown\n"; + } +#endif + + char aPath[256]; + char aSDLStr[20]; + char aPopLibStr[40]; + char aOpenALStr[256]; + + if (mApp != NULL) + { + snprintf(aPopLibStr, sizeof(aPopLibStr), "PopLib Ver: %s", POPLIB_VERSION); + aDebugDump += aPopLibStr; + aDebugDump += "\r\n"; + + snprintf(aSDLStr, sizeof(aSDLStr), "SDL Ver: %d.%d.%d", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION); + aDebugDump += aSDLStr; + aDebugDump += "\r\n"; + + const char *version = alGetString(AL_VERSION); + const char *renderer = alGetString(AL_RENDERER); + const char *vendor = alGetString(AL_VENDOR); + snprintf(aOpenALStr, sizeof(aOpenALStr), "OpenAL Ver: %s \r\nOpenAL Renderer: %s \r\nOpenAL Vendor: %s", + version, renderer, vendor); + aDebugDump += aOpenALStr; + aDebugDump += "\r\n"; + } + + return aDebugDump; +} \ No newline at end of file diff --git a/PopLib/debug/sehcatcher.hpp b/PopLib/debug/sehcatcher.hpp new file mode 100644 index 00000000..b8e8cae7 --- /dev/null +++ b/PopLib/debug/sehcatcher.hpp @@ -0,0 +1,124 @@ +#ifndef __SEHCATHER_H__ +#define __SEHCATHER_H__ + +#pragma once + +#include "common.hpp" +#include "misc/httptransfer.hpp" +#ifdef _WIN32 +#include +#else +#include +#endif + +#include + +namespace PopLib +{ + +class AppBase; + +#ifdef _WIN32 +typedef BOOL(__stdcall *SYMINITIALIZEPROC)(HANDLE, LPSTR, BOOL); + +typedef DWORD(__stdcall *SYMSETOPTIONSPROC)(DWORD); + +typedef BOOL(__stdcall *SYMCLEANUPPROC)(HANDLE); + +typedef LPCSTR(__stdcall *UNDECORATESYMBOLNAMEPROC)(LPCSTR, LPSTR, DWORD, DWORD); + +typedef BOOL(__stdcall *STACKWALKPROC)(DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID, PREAD_PROCESS_MEMORY_ROUTINE, + PFUNCTION_TABLE_ACCESS_ROUTINE, PGET_MODULE_BASE_ROUTINE, + PTRANSLATE_ADDRESS_ROUTINE); + +typedef LPVOID(__stdcall *SYMFUNCTIONTABLEACCESSPROC)(HANDLE, DWORD); + +typedef DWORD(__stdcall *SYMGETMODULEBASEPROC)(HANDLE, DWORD); + +typedef BOOL(__stdcall *SYMGETSYMFROMADDRPROC)(HANDLE, DWORD, PDWORD, PIMAGEHLP_SYMBOL); + +#ifdef _WIN64 +#define SYMFUNCTIONTABLEACCESSPROC PFUNCTION_TABLE_ACCESS_ROUTINE64 +#define SYMGETMODULEBASEPROC PGET_MODULE_BASE_ROUTINE64 +#endif +#endif + +#ifndef _WIN32 +using LPEXCEPTION_POINTERS = void *; // dummy. +#endif + +class SEHCatcher +{ + public: + static AppBase *mApp; + static bool mHasDemoFile; + static bool mDebugError; + static std::string mErrorTitle; + static std::string mErrorText; + static std::string mUserText; + static std::string mUploadFileName; + static std::wstring mCrashMessage; + static std::string mIssueWebsite; + static std::wstring mSubmitErrorMessage; + static HTTPTransfer mSubmitReportTransfer; + static bool mExiting; + static bool mShowUI; + static bool mAllowSubmit; +#ifdef _WIN32 + static HMODULE mImageHelpLib; + static SYMINITIALIZEPROC mSymInitialize; + static SYMSETOPTIONSPROC mSymSetOptions; + static UNDECORATESYMBOLNAMEPROC mUnDecorateSymbolName; + static SYMCLEANUPPROC mSymCleanup; + static STACKWALKPROC mStackWalk; + static SYMFUNCTIONTABLEACCESSPROC mSymFunctionTableAccess; + static SYMGETMODULEBASEPROC mSymGetModuleBase; + static SYMGETSYMFROMADDRPROC mSymGetSymFromAddr; +#endif + +#ifdef _WIN32 + protected: + static LPTOP_LEVEL_EXCEPTION_FILTER mPreviousFilter; +#endif + + public: + static void SubmitReportThread(void *theArg); + + static void SEHWindowProc(); +#ifdef _WIN32 + static long __stdcall UnhandledExceptionFilter(LPEXCEPTION_POINTERS lpExceptPtr); + static void DoHandleDebugEvent(LPEXCEPTION_POINTERS lpEP); + static std::string IntelWalk(PCONTEXT theContext, int theSkipCount); + static std::string ImageHelpWalk(PCONTEXT theContext, int theSkipCount); + static bool LoadImageHelp(); + static void UnloadImageHelp(); +#else + static long UnhandledExceptionFilter(LPEXCEPTION_POINTERS) + { + return 0; + } + static void DoHandleDebugEvent(LPEXCEPTION_POINTERS) + { + } + + static void signalHandler(int sig, siginfo_t *info, void *ucontext); +#endif + + static bool GetLogicalAddress(void *addr, char *szModule, DWORD len, DWORD §ion, DWORD &offset); + static std::string GetFilename(const std::string &thePath); + static void WriteToFile(const std::string &theErrorText); + static void ShowErrorDialog(const std::string &theErrorTitle, const std::string &theErrorText); + + static std::string GetSysInfo(); + static void GetSymbolsFromMapFile(std::string &theDebugDump); + + public: + SEHCatcher(); + ~SEHCatcher() noexcept; +}; + +extern SEHCatcher gSEHCatcher; + +} // namespace PopLib + +#endif \ No newline at end of file diff --git a/PopLib/graphics/HeaderFont/LiberationSansRegular.h b/PopLib/graphics/HeaderFont/LiberationSansRegular.h index 727b2c51..5270f478 100644 --- a/PopLib/graphics/HeaderFont/LiberationSansRegular.h +++ b/PopLib/graphics/HeaderFont/LiberationSansRegular.h @@ -1,8 +1,9 @@ #ifndef __LSR_H_ #define __LSR_H_ -#ifdef _WIN32 + #pragma once -#endif + +#include static size_t LiberationSans_Regular_Size = 133829; diff --git a/PopLib/graphics/Inc/MI_SlowStretchBlt.inc b/PopLib/graphics/Inc/MI_SlowStretchBlt.inc index 1c47ca70..8ed254d1 100644 --- a/PopLib/graphics/Inc/MI_SlowStretchBlt.inc +++ b/PopLib/graphics/Inc/MI_SlowStretchBlt.inc @@ -58,7 +58,7 @@ *d++ += aFactor * ((pixel >> 16) & 0xFF); *d++ += aFactor * ((pixel >> 24) & 0xFF); - DBG_ASSERTE(d <= aNewHorzPixelsEnd); + ASSERT(d <= aNewHorzPixelsEnd); d += aTempDestWidth*4 - 4; s1 += aSrcRowWidth; @@ -85,7 +85,7 @@ *d++ += aFactor2 * ((pixel >> 16) & 0xFF); *d++ += aFactor2 * ((pixel >> 24) & 0xFF); - DBG_ASSERTE(d <= aNewHorzPixelsEnd); + ASSERT(d <= aNewHorzPixelsEnd); d += aTempDestWidth*4 - 8; s1 += aSrcRowWidth; @@ -123,7 +123,7 @@ *d++ = (aFactor1 * ((pixel1 >> 16) & 0xFF)) + (aFactor2 * ((pixel2 >> 16) & 0xFF)); *d++ = (aFactor1 * ((pixel1 >> 24) & 0xFF)) + (aFactor2 * ((pixel2 >> 24) & 0xFF)); - DBG_ASSERTE(d <= aNewHorzPixelsEnd); + ASSERT(d <= aNewHorzPixelsEnd); d += aTempDestWidth*4 - 4; s += aSrcRowWidth - 1; @@ -167,7 +167,7 @@ *d++ += aFactor * *s++; } - DBG_ASSERTE(d <= aNewPixelsEnd); + ASSERT(d <= aNewPixelsEnd); } else { @@ -192,8 +192,8 @@ *d2++ += aFactor2 * *s++; } - DBG_ASSERTE(d1 <= aNewPixelsEnd); - DBG_ASSERTE(d2 <= aNewPixelsEnd); + ASSERT(d1 <= aNewPixelsEnd); + ASSERT(d2 <= aNewPixelsEnd); } } } @@ -259,7 +259,7 @@ //a = 255 * a / (a + aDestAlpha); int oma = 256 - a; - DBG_ASSERTE(aDestPixels < aDestEnd); + ASSERT(aDestPixels < aDestEnd); *(aDestPixels++) = (aNewDestAlpha << 24) | (((((dest & 0x0000FF) * oma) >> 8) & 0x0000FF) + (((b * a) >> 8))) | diff --git a/PopLib/graphics/SWTri/SWTri.cpp b/PopLib/graphics/SWTri/SWTri.cpp index d6e47711..c3518885 100644 --- a/PopLib/graphics/SWTri/SWTri.cpp +++ b/PopLib/graphics/SWTri/SWTri.cpp @@ -1,7 +1,7 @@ #pragma warning(disable : 4244 4305 4309) #include "SWTri.hpp" -#include "debug/debug.hpp" +#include "debug/log.hpp" using namespace PopLib; @@ -690,7 +690,7 @@ void SWHelper::SWDrawTriangle(bool textured, bool talpha, bool mod_argb, bool gl DrawTriFunc aFunc = gDrawTriFunc[aType]; if (!aFunc) { - DBG_ASSERT("You need to call SWTri_AddDrawTriFunc or SWTri_AddAllDrawTriFuncs" == nullptr); + ASSERT_MSG(false, "You need to call SWTri_AddDrawTriFunc or SWTri_AddAllDrawTriFuncs"); } else aFunc(pVerts, pFrameBuffer, bytepitch, textureInfo, globalDiffuse); diff --git a/PopLib/graphics/SWTri/SWTri.hpp b/PopLib/graphics/SWTri/SWTri.hpp index e529377a..4ba5eff0 100644 --- a/PopLib/graphics/SWTri/SWTri.hpp +++ b/PopLib/graphics/SWTri/SWTri.hpp @@ -1,8 +1,7 @@ #ifndef __SWTRI_H_ #define __SWTRI_H_ -#ifdef _WIN32 + #pragma once -#endif // dirty hacks but fuck it #include "../color.hpp" diff --git a/PopLib/graphics/color.cpp b/PopLib/graphics/color.cpp index a83a3d32..701d7796 100644 --- a/PopLib/graphics/color.cpp +++ b/PopLib/graphics/color.cpp @@ -1,9 +1,31 @@ #include "color.hpp" +#include + +#define CLIP(X) std::clamp((X), 0, 255)//for YUV conversion using namespace PopLib; Color Color::Black(0, 0, 0); Color Color::White(255, 255, 255); +Color Color::Red (255, 0, 0); +Color Color::Lime (0, 255, 0); +Color Color::Blue (0, 0, 255); +Color Color::Cyan (0, 255, 255); +Color Color::Magenta(255, 0, 255); +Color Color::Yellow (255, 255, 0); +Color Color::SkyBlue (0, 160, 255); +Color Color::Brown (128, 64, 0); +Color Color::Orange(255, 128, 0); +Color Color::Green (0, 128, 0); +Color Color::SpringGreen(112, 224, 0); +Color Color::Lavender(224, 200, 255); +Color Color::Gold (255, 200, 0); +Color Color::Purple (128, 0, 128); +Color Color::Navy (0, 0, 128); +Color Color::Gray (128, 128, 128); +Color Color::Silver (224, 224, 224); +Color Color::Pink (255, 192, 224); +Color Color::Scarlet(255, 64, 0); Color::Color() : mRed(0), mGreen(0), mBlue(0), mAlpha(255) { @@ -11,10 +33,10 @@ Color::Color() : mRed(0), mGreen(0), mBlue(0), mAlpha(255) Color::Color(int theColor) : mAlpha((theColor >> 24) & 0xFF), mRed((theColor >> 16) & 0xFF), mGreen((theColor >> 8) & 0xFF), - mBlue((theColor) & 0xFF) + mBlue((theColor) & 0xFF) { - if (mAlpha == 0) - mAlpha = 0xff; + if (mBlue == 0) + mBlue = 0xff; } Color::Color(int theColor, int theAlpha) @@ -31,19 +53,20 @@ Color::Color(int theRed, int theGreen, int theBlue, int theAlpha) { } -Color::Color(const RGBA &theColor) : mRed(theColor.r), mGreen(theColor.g), mBlue(theColor.b), mAlpha(theColor.a) +Color::Color(const ARGB &theColor) : mAlpha(theColor.a), mRed(theColor.r), mGreen(theColor.g), mBlue(theColor.b) { } Color::Color(const uchar *theElements) - : mRed(theElements[0]), mGreen(theElements[1]), mBlue(theElements[2]), mAlpha(0xFF) + : mAlpha(theElements[0]), mRed(theElements[1]), mGreen(theElements[2]), mBlue(0xFF) { } -Color::Color(const int *theElements) : mRed(theElements[0]), mGreen(theElements[1]), mBlue(theElements[2]), mAlpha(0xFF) +Color::Color(const int *theElements) : mAlpha(theElements[0]), mRed(theElements[1]), mGreen(theElements[2]), mBlue(0xFF) { } + int Color::GetRed() const { return mRed; @@ -71,13 +94,13 @@ int &Color::operator[](int theIdx) switch (theIdx) { case 0: - return mRed; + return mAlpha; case 1: - return mGreen; + return mRed; case 2: - return mBlue; + return mGreen; case 3: - return mAlpha; + return mBlue; default: return aJunk; } @@ -105,25 +128,39 @@ ulong Color::ToInt() const return (mAlpha << 24) | (mRed << 16) | (mGreen << 8) | (mBlue); } -RGBA Color::ToRGBA() const +ARGB Color::ToBGRA() const +{ + + //Swap the values when your doing BGRA, NOT ARGB! This function converts it from ARGB to BGRA. + ARGB TheBGRAValue; + TheBGRAValue.b = mAlpha; + TheBGRAValue.g = mRed; + TheBGRAValue.r = mGreen; + TheBGRAValue.a = mBlue; + + return TheBGRAValue; +} +YUV Color::ToYUV() const { - RGBA anRGBA; - anRGBA.r = mRed; - anRGBA.g = mGreen; - anRGBA.b = mBlue; - anRGBA.a = mAlpha; + YUV yuv; + //YUV conversion here, uses std::clamp to convert hue sat and lum in the RGB space. + yuv.y = CLIP(((66 * mRed + 129 * mGreen + 25 * mBlue + 128) >> 8) + 16); + yuv.u = CLIP(((-38 * mRed - 74 * mGreen + 112 * mBlue + 128) >> 8) + 128); + yuv.v = CLIP(((112 * mRed - 94 * mGreen - 18 * mBlue + 128) >> 8) + 128); + return yuv; + + - return anRGBA; } bool PopLib::operator==(const Color &theColor1, const Color &theColor2) { - return (theColor1.mRed == theColor2.mRed) && (theColor1.mGreen == theColor2.mGreen) && - (theColor1.mBlue == theColor2.mBlue) && (theColor1.mAlpha == theColor2.mAlpha); + return (theColor1.mAlpha == theColor2.mAlpha) && (theColor1.mRed == theColor2.mRed) && + (theColor1.mGreen == theColor2.mGreen) && (theColor1.mBlue == theColor2.mBlue); } bool PopLib::operator!=(const Color &theColor1, const Color &theColor2) { - return (theColor1.mRed != theColor2.mRed) || (theColor1.mGreen != theColor2.mGreen) || - (theColor1.mBlue != theColor2.mBlue) || (theColor1.mAlpha != theColor2.mAlpha); + return (theColor1.mAlpha != theColor2.mAlpha) || (theColor1.mRed != theColor2.mRed) || + (theColor1.mGreen != theColor2.mGreen) || (theColor1.mBlue != theColor2.mBlue); } diff --git a/PopLib/graphics/color.hpp b/PopLib/graphics/color.hpp index 2973e93a..a895e7ba 100644 --- a/PopLib/graphics/color.hpp +++ b/PopLib/graphics/color.hpp @@ -1,8 +1,7 @@ #ifndef __COLOR_HPP__ #define __COLOR_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" @@ -10,9 +9,15 @@ namespace PopLib { #pragma pack(push, 1) -struct RGBA +struct ARGB // change this stupid fucking definition a 3rd TIME to the CORRECT one this time. { - unsigned char b, g, r, a; + unsigned char a, r, g, b; +}; +struct YUV //add YUV color components based off of ITU standard reccomendations BT.601 & BT.709. This will most likely be a conversion set. +{ + unsigned char y, u, v; + + }; #pragma pack(pop) @@ -23,17 +28,37 @@ class Color int mGreen; int mBlue; int mAlpha; - + static Color Black; static Color White; - +//add a bunch of colors in case if a person cannot use designated functions below: + static Color Red; + static Color Lime; + static Color Blue; + static Color Cyan; + static Color Magenta; + static Color Yellow; + static Color SkyBlue; + static Color Brown; + static Color Orange; + static Color Green; + static Color SpringGreen; + static Color Lavender; + static Color Gold; + static Color Purple; + static Color Navy; + static Color Gray; + static Color Silver; + static Color Pink; + static Color Scarlet; + public: Color(); Color(int theColor); Color(int theColor, int theAlpha); Color(int theRed, int theGreen, int theBlue); Color(int theRed, int theGreen, int theBlue, int theAlpha); - Color(const RGBA &theColor); + Color(const ARGB &theColor); Color(const uchar *theElements); Color(const int *theElements); @@ -42,8 +67,8 @@ class Color int GetBlue() const; int GetAlpha() const; ulong ToInt() const; - RGBA ToRGBA() const; - + ARGB ToBGRA() const; + YUV ToYUV() const; int &operator[](int theIdx); int operator[](int theIdx) const; }; @@ -54,3 +79,4 @@ bool operator!=(const Color &theColor1, const Color &theColor2); } // namespace PopLib #endif + diff --git a/PopLib/graphics/font.hpp b/PopLib/graphics/font.hpp index 27db441d..9edca84e 100644 --- a/PopLib/graphics/font.hpp +++ b/PopLib/graphics/font.hpp @@ -1,8 +1,7 @@ #ifndef __FONT_HPP__ #define __FONT_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include "math/rect.hpp" diff --git a/PopLib/graphics/gpuimage.cpp b/PopLib/graphics/gpuimage.cpp new file mode 100644 index 00000000..17e39a3c --- /dev/null +++ b/PopLib/graphics/gpuimage.cpp @@ -0,0 +1,64 @@ +#include "gpuimage.hpp" + +using namespace PopLib; + +void GPUImage::FillScanLinesWithCoverage(Span *, int, const Color &, int, const BYTE *, int, int, int, int) +{ +} + +void GPUImage::Create(int theWidth, int theHeight) +{ +} + +bool GPUImage::PolyFill3D(const Point[], int, const Rect *, const Color &, int, int, int) +{ + return false; +} + +void GPUImage::FillRect(const Rect &, const Color &, int) +{ +} + +void GPUImage::DrawLine(double, double, double, double, const Color &, int) +{ +} + +void GPUImage::DrawLineAA(double, double, double, double, const Color &, int) +{ +} + +void GPUImage::Blt(Image *, int, int, const Rect &, const Color &, int) +{ +} + +void GPUImage::BltF(Image *, float, float, const Rect &, const Rect &, const Color &, int) +{ +} + +void GPUImage::BltRotated(Image *, float, float, const Rect &, const Rect &, const Color &, int, double, float, float) +{ +} + +void GPUImage::StretchBlt(Image *, const Rect &, const Rect &, const Rect &, const Color &, int, bool) +{ +} + +void GPUImage::BltMatrix(Image *, float, float, const Matrix3 &, const Rect &, const Color &, int, const Rect &, bool) +{ +} + +void GPUImage::BltTrianglesTex(Image *, const TriVertex[][3], int, const Rect &, const Color &, int, float, float, bool) +{ +} + +void GPUImage::BltMirror(Image *, int, int, const Rect &, const Color &, int) +{ +} + +void GPUImage::StretchBltMirror(Image *, const Rect &, const Rect &, const Rect &, const Color &, int, bool) +{ +} + +void GPUImage::PurgeBits() +{ +} \ No newline at end of file diff --git a/PopLib/graphics/gpuimage.hpp b/PopLib/graphics/gpuimage.hpp new file mode 100644 index 00000000..c0014d83 --- /dev/null +++ b/PopLib/graphics/gpuimage.hpp @@ -0,0 +1,70 @@ +#ifndef __GPUIMAGE_HPP__ +#define __GPUIMAGE_HPP__ + +#pragma once + +#include "memoryimage.hpp" + +namespace PopLib +{ +class Renderer; +class SysFont; + +enum GPUImageFlags +{ + GPUImageFlag_NearestFiltering = 0x0001, // Uses nearest filtering for the texture + // 0x0002 + // 0x0004 + // 0x0008 +}; + +class GPUImage : public MemoryImage +{ + protected: + friend class SysFont; + + public: + Renderer *mRenderer; + + public: + virtual void FillScanLinesWithCoverage(Span *theSpans, int theSpanCount, const Color &theColor, int theDrawMode, + const BYTE *theCoverage, int theCoverX, int theCoverY, int theCoverWidth, + int theCoverHeight); + + public: + virtual ~GPUImage() = default; + + virtual void Create(int theWidth, int theHeight); + + virtual bool PolyFill3D(const Point theVertices[], int theNumVertices, const Rect *theClipRect, + const Color &theColor, int theDrawMode, int tx, int ty); + virtual void FillRect(const Rect &theRect, const Color &theColor, int theDrawMode); + virtual void DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode); + virtual void DrawLineAA(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode); + virtual void Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode); + virtual void BltF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode); + virtual void BltRotated(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode, double theRot, float theRotCenterX, + float theRotCenterY); + virtual void StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode, bool fastStretch); + virtual void BltMatrix(Image *theImage, float x, float y, const Matrix3 &theMatrix, const Rect &theClipRect, + const Color &theColor, int theDrawMode, const Rect &theSrcRect, bool blend); + virtual void BltTrianglesTex(Image *theTexture, const TriVertex theVertices[][3], int theNumTriangles, + const Rect &theClipRect, const Color &theColor, int theDrawMode, float tx, float ty, + bool blend); + + virtual void BltMirror(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode); + virtual void StretchBltMirror(Image *theImage, const Rect &theDestRectOrig, const Rect &theSrcRect, + const Rect &theClipRect, const Color &theColor, int theDrawMode, bool fastStretch); + + virtual void PurgeBits(); +}; +} // namespace PopLib + +#endif // __GPUIMAGE_HPP__ \ No newline at end of file diff --git a/PopLib/graphics/graphics.cpp b/PopLib/graphics/graphics.cpp index 214934fb..03ec3264 100644 --- a/PopLib/graphics/graphics.cpp +++ b/PopLib/graphics/graphics.cpp @@ -1,6 +1,5 @@ #include "graphics.hpp" #include "font.hpp" -#include "sdlimage.hpp" #include "memoryimage.hpp" #include "math/matrix.hpp" #include @@ -62,7 +61,7 @@ Graphics::Graphics(Image *theDestImage) } else { - mIs3D = SDLImage::Check3D(theDestImage); + mIs3D = MemoryImage::Check3D(theDestImage); } mClipRect = Rect(0, 0, mDestImage->GetWidth(), mDestImage->GetHeight()); @@ -852,7 +851,7 @@ void Graphics::DrawImageMatrix(Image *theImage, const Matrix3 &theMatrix, const void Graphics::DrawImageTransformHelper(Image *theImage, const Transform &theTransform, const Rect &theSrcRect, float x, float y, bool useFloat) { - if (theTransform.mComplex || (SDLImage::Check3D(mDestImage) && useFloat)) + if (theTransform.mComplex || (MemoryImage::Check3D(mDestImage) && useFloat)) { DrawImageMatrix(theImage, theTransform.GetMatrix(), theSrcRect, x, y); return; @@ -871,9 +870,9 @@ void Graphics::DrawImageTransformHelper(Image *theImage, const Transform &theTra y = y + theTransform.mTransY2 - ry + 0.5f; if (useFloat) - DrawImageRotatedF(theImage, x, y, theTransform.mRot, rx, ry, &theSrcRect); + DrawImageRotatedF(theImage, x, y, theTransform.mRot * (180.0f / 3.14159265358979323846f), rx, ry, &theSrcRect); else - DrawImageRotated(theImage, x, y, theTransform.mRot, rx, ry, &theSrcRect); + DrawImageRotated(theImage, x, y, theTransform.mRot * (180.0f / 3.14159265358979323846f), rx, ry, &theSrcRect); } else if (theTransform.mHaveScale) { diff --git a/PopLib/graphics/graphics.hpp b/PopLib/graphics/graphics.hpp index c4b0b6ca..db6b77f6 100644 --- a/PopLib/graphics/graphics.hpp +++ b/PopLib/graphics/graphics.hpp @@ -1,8 +1,7 @@ #ifndef __GRAPHICS_HPP__ #define __GRAPHICS_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include "math/rect.hpp" diff --git a/PopLib/graphics/image.hpp b/PopLib/graphics/image.hpp index 01768e0a..fb02883f 100644 --- a/PopLib/graphics/image.hpp +++ b/PopLib/graphics/image.hpp @@ -1,8 +1,7 @@ #ifndef __IMAGE_HPP__ #define __IMAGE_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include "color.hpp" diff --git a/PopLib/graphics/imagefont.cpp b/PopLib/graphics/imagefont.cpp index a9dc584d..c820b2e9 100644 --- a/PopLib/graphics/imagefont.cpp +++ b/PopLib/graphics/imagefont.cpp @@ -2,9 +2,9 @@ #include "graphics.hpp" #include "image.hpp" #include "appbase.hpp" -#include "memoryimage.hpp" -#include "sdlimage.hpp" +#include "gpuimage.hpp" #include "misc/autocrit.hpp" +#include "renderer.hpp" using namespace PopLib; @@ -1020,7 +1020,7 @@ bool FontData::LoadLegacy(Image *theFontImage, const std::string &theFontDescFil if (anItr == mFontLayerMap.end()) return false; - aFontLayer->mImage = (MemoryImage *)theFontImage; + aFontLayer->mImage = (GPUImage *)theFontImage; aFontLayer->mDefaultHeight = aFontLayer->mImage->GetHeight(); aFontLayer->mAscent = aFontLayer->mImage->GetHeight(); @@ -1121,8 +1121,8 @@ ImageFont::ImageFont(Image *theFontImage) mFontData->mFontLayerList.push_back(FontLayer(mFontData)); FontLayer *aFontLayer = &mFontData->mFontLayerList.back(); - mFontData->mFontLayerMap.insert(FontLayerMap::value_type("", aFontLayer)).first; - aFontLayer->mImage = (MemoryImage *)theFontImage; + mFontData->mFontLayerMap.insert(FontLayerMap::value_type("", aFontLayer)); + aFontLayer->mImage = (GPUImage *)theFontImage; aFontLayer->mDefaultHeight = aFontLayer->mImage->GetHeight(); aFontLayer->mAscent = aFontLayer->mImage->GetHeight(); } @@ -1246,7 +1246,7 @@ void ImageFont::GenerateActiveFontLayers() // Resize font elements int aCharNum; - MemoryImage *aMemoryImage = new MemoryImage(mFontData->mApp); + GPUImage *aGPUImage = gAppBase->mRenderer->NewGPUImage(); int aCurX = 0; int aMaxHeight = 0; @@ -1266,14 +1266,14 @@ void ImageFont::GenerateActiveFontLayers() aCurX += aScaledRect.mWidth; } - anActiveFontLayer->mScaledImage = aMemoryImage; + anActiveFontLayer->mScaledImage = aGPUImage; anActiveFontLayer->mOwnsImage = true; // Create the image now - aMemoryImage->Create(aCurX, aMaxHeight); + aGPUImage->Create(aCurX, aMaxHeight); - Graphics g(aMemoryImage); + Graphics g(aGPUImage); for (aCharNum = 0; aCharNum < 256; aCharNum++) { @@ -1284,14 +1284,14 @@ void ImageFont::GenerateActiveFontLayers() if (mForceScaledImagesWhite) { - int aCount = aMemoryImage->mWidth * aMemoryImage->mHeight; - ulong *aBits = aMemoryImage->GetBits(); + int aCount = aGPUImage->mWidth * aGPUImage->mHeight; + ulong *aBits = aGPUImage->GetBits(); for (int i = 0; i < aCount; i++) *(aBits++) = *aBits | 0x00FFFFFF; } - aMemoryImage->Palletize(); + aGPUImage->Palletize(); } int aLayerAscent = (aFontLayer->mAscent * aPointSize) / aLayerPointSize; @@ -1478,7 +1478,7 @@ void ImageFont::DrawStringEx(Graphics *g, int theX, int theY, const PopString &t double aScale = mScale; if (aLayerPointSize != 0) - aScale *= mPointSize / aLayerPointSize; + aScale *= (double)mPointSize / aLayerPointSize; if (aScale == 1.0) { diff --git a/PopLib/graphics/imagefont.hpp b/PopLib/graphics/imagefont.hpp index 2ea2c054..085850ee 100644 --- a/PopLib/graphics/imagefont.hpp +++ b/PopLib/graphics/imagefont.hpp @@ -1,8 +1,7 @@ #ifndef __IMAGEFONT_HPP__ #define __IMAGEFONT_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "font.hpp" #include "readwrite/descparser.hpp" diff --git a/PopLib/graphics/memoryimage.cpp b/PopLib/graphics/memoryimage.cpp index 24c7adf2..3f67910b 100644 --- a/PopLib/graphics/memoryimage.cpp +++ b/PopLib/graphics/memoryimage.cpp @@ -2,9 +2,8 @@ #include "appbase.hpp" #include "graphics.hpp" -#include "nativedisplay.hpp" -#include "sdlinterface.hpp" -#include "debug/debug.hpp" +#include "renderer.hpp" +#include "debug/log.hpp" #include "quantize.hpp" #include "debug/perftimer.hpp" #include "SWTri/SWTri.hpp" @@ -37,7 +36,7 @@ MemoryImage::MemoryImage(const MemoryImage &theMemoryImage) : Image(theMemoryImage), mApp(theMemoryImage.mApp), mHasAlpha(theMemoryImage.mHasAlpha), mHasTrans(theMemoryImage.mHasTrans), mBitsChanged(theMemoryImage.mBitsChanged), mIsVolatile(theMemoryImage.mIsVolatile), mPurgeBits(theMemoryImage.mPurgeBits), mWantPal(theMemoryImage.mWantPal), - mImageFlags(theMemoryImage.mImageFlags), mBitsChangedCount(theMemoryImage.mBitsChangedCount), mD3DData(nullptr) + mImageFlags(theMemoryImage.mImageFlags), mBitsChangedCount(theMemoryImage.mBitsChangedCount), mGPUData(nullptr) { bool deleteBits = false; @@ -45,7 +44,7 @@ MemoryImage::MemoryImage(const MemoryImage &theMemoryImage) if ((theMemoryImage.mBits == nullptr) && (theMemoryImage.mColorTable == nullptr)) { - // Must be a SDLImage with only a DDSurface + // Must be a Image with only a Surface aNonConstMemoryImage->GetBits(); deleteBits = true; } @@ -114,12 +113,12 @@ MemoryImage::MemoryImage(const MemoryImage &theMemoryImage) else mRLAdditiveData = nullptr; - mApp->AddMemoryImage(this); + mApp->AddGPUImage((GPUImage*)this); } MemoryImage::~MemoryImage() { - mApp->RemoveMemoryImage(this); + mApp->RemoveGPUImage((GPUImage*)this); delete[] mBits; delete[] mNativeAlphaData; @@ -144,14 +143,14 @@ void MemoryImage::Init() mForcedMode = false; mIsVolatile = false; - mD3DData = nullptr; + mGPUData = nullptr; mImageFlags = 0; mBitsChangedCount = 0; mPurgeBits = false; mWantPal = false; - mApp->AddMemoryImage(this); + mApp->AddGPUImage((GPUImage*)this); } void MemoryImage::BitsChanged() @@ -848,7 +847,7 @@ void MemoryImage::SetVolatile(bool isVolatile) mIsVolatile = isVolatile; } -void *MemoryImage::GetNativeAlphaData(NativeDisplay *theDisplay) +void *MemoryImage::GetNativeAlphaData(Renderer *theDisplay) { if (mNativeAlphaData != nullptr) return mNativeAlphaData; @@ -957,7 +956,7 @@ uchar *MemoryImage::GetRLAlphaData() return mRLAlphaData; } -uchar *MemoryImage::GetRLAdditiveData(NativeDisplay *theNative) +uchar *MemoryImage::GetRLAdditiveData(Renderer *theNative) { if (mRLAdditiveData == nullptr) { @@ -1084,7 +1083,7 @@ void MemoryImage::PurgeBits() { // Due to potential D3D threading issues we have to defer the texture creation // and therefore the actual purging - if (mD3DData == nullptr) + if (mGPUData == nullptr) return; } else @@ -1092,13 +1091,13 @@ void MemoryImage::PurgeBits() if ((mBits == nullptr) && (mColorIndices == nullptr)) return; - GetNativeAlphaData(gAppBase->mSDLInterface); + GetNativeAlphaData(gAppBase->mRenderer); } delete[] mBits; mBits = nullptr; - if (mD3DData != nullptr) + if (mGPUData != nullptr) { delete[] mColorIndices; mColorIndices = nullptr; @@ -1125,7 +1124,7 @@ void MemoryImage::DeleteSWBuffers() void MemoryImage::Delete3DBuffers() { - mApp->Remove3DData(this); + mApp->Remove3DData((GPUImage*)this); } void MemoryImage::DeleteExtraBuffers() @@ -1222,7 +1221,7 @@ ulong *MemoryImage::GetBits() } else if (mNativeAlphaData != nullptr) { - NativeDisplay *aDisplay = gAppBase->mSDLInterface; + Renderer *aDisplay = gAppBase->mRenderer; const int rMask = aDisplay->mRedMask; const int gMask = aDisplay->mGreenMask; @@ -1249,7 +1248,7 @@ ulong *MemoryImage::GetBits() *(aDestPtr++) = (r << 16) | (g << 8) | (b) | (anAlpha << 24); } } - else if ((mD3DData == nullptr) || (!mApp->mSDLInterface->RecoverBits(this))) + else if ((mGPUData == nullptr) || (!mApp->mRenderer->RecoverBits((GPUImage*)this))) { memset(mBits, 0, aSize * sizeof(ulong)); } diff --git a/PopLib/graphics/memoryimage.hpp b/PopLib/graphics/memoryimage.hpp index d2f93396..65425f16 100644 --- a/PopLib/graphics/memoryimage.hpp +++ b/PopLib/graphics/memoryimage.hpp @@ -1,8 +1,7 @@ #ifndef __MEMORYIMAGE_HPP__ #define __MEMORYIMAGE_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "image.hpp" @@ -16,7 +15,7 @@ namespace PopLib const ulong MEMORYCHECK_ID = 0x4BEEFADE; -class NativeDisplay; +class Renderer; class AppBase; class MemoryImage : public Image @@ -24,7 +23,7 @@ class MemoryImage : public Image public: ulong *mBits; int mBitsChangedCount; - void *mD3DData; + void *mGPUData; uint32_t mImageFlags; // see D3DInterface.h for possible values ulong *mColorTable; @@ -48,9 +47,9 @@ class MemoryImage : public Image void Init(); public: - virtual void *GetNativeAlphaData(NativeDisplay *theNative); + virtual void *GetNativeAlphaData(Renderer *theNative); virtual uchar *GetRLAlphaData(); - virtual uchar *GetRLAdditiveData(NativeDisplay *theNative); + virtual uchar *GetRLAdditiveData(Renderer *theNative); virtual void PurgeBits(); virtual void DeleteSWBuffers(); virtual void Delete3DBuffers(); @@ -62,6 +61,11 @@ class MemoryImage : public Image virtual void DeleteNativeData(); + static bool Check3D(Image *theImage) + { + return true; + } + void NormalBlt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor); void AdditiveBlt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor); diff --git a/PopLib/graphics/nativedisplay.hpp b/PopLib/graphics/nativedisplay.hpp deleted file mode 100644 index 08827cac..00000000 --- a/PopLib/graphics/nativedisplay.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef __NATIVE_DISPLAY_HPP__ -#define __NATIVE_DISPLAY_HPP__ -#ifdef _WIN32 -#pragma once -#endif - -#include "common.hpp" - -namespace PopLib -{ - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -class NativeDisplay -{ - public: - int mRGBBits; - ulong mRedMask; - ulong mGreenMask; - ulong mBlueMask; - int mRedBits; - int mGreenBits; - int mBlueBits; - int mRedShift; - int mGreenShift; - int mBlueShift; - - public: - NativeDisplay(); - virtual ~NativeDisplay(); -}; - -}; // namespace PopLib - -#endif diff --git a/PopLib/graphics/quantize.hpp b/PopLib/graphics/quantize.hpp index 6ab2b72f..fdf88974 100644 --- a/PopLib/graphics/quantize.hpp +++ b/PopLib/graphics/quantize.hpp @@ -1,8 +1,7 @@ #ifndef __QUANTIZE_HPP__ #define __QUANTIZE_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" diff --git a/PopLib/graphics/nativedisplay.cpp b/PopLib/graphics/renderer.cpp similarity index 54% rename from PopLib/graphics/nativedisplay.cpp rename to PopLib/graphics/renderer.cpp index 93fe883e..f429aae2 100644 --- a/PopLib/graphics/nativedisplay.cpp +++ b/PopLib/graphics/renderer.cpp @@ -1,10 +1,13 @@ -#include "nativedisplay.hpp" +#include "renderer.hpp" +#include "graphics.hpp" using namespace PopLib; +bool PopLib::gRendererPreDrawError = false; + /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -NativeDisplay::NativeDisplay() +Renderer::Renderer() { mRGBBits = 0; @@ -23,6 +26,23 @@ NativeDisplay::NativeDisplay() /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -NativeDisplay::~NativeDisplay() +Renderer::~Renderer() +{ +} + + +BlendMode Renderer::ChooseBlendMode(int theBlendMode) { + BlendMode theBBlendMode; + switch (theBlendMode) + { + case Graphics::DRAWMODE_ADDITIVE: + theBBlendMode = BLENDMODE_ADD; + break; + default: + case Graphics::DRAWMODE_NORMAL: + theBBlendMode = BLENDMODE_BLEND; + break; + } + return theBBlendMode; } diff --git a/PopLib/graphics/renderer.hpp b/PopLib/graphics/renderer.hpp new file mode 100644 index 00000000..7e6225b6 --- /dev/null +++ b/PopLib/graphics/renderer.hpp @@ -0,0 +1,184 @@ +#ifndef __RENDERER_HPP__ +#define __RENDERER_HPP__ + +#pragma once + +#include "common.hpp" +#include "image.hpp" +#include "gpuimage.hpp" +#include "gpuimage.hpp" +#include "appbase.hpp" +#include + +namespace PopLib +{ + +enum BlendMode +{ + BLENDMODE_NONE = 0, + BLENDMODE_BLEND, + BLENDMODE_BLEND_PREMULTIPLIED, + BLENDMODE_ADD, + BLENDMODE_ADD_PREMULTIPLIED, + BLENDMODE_MOD, + BLENDMODE_MUL, + BLENDMODE_LAST, +}; + + +enum TextureFlags +{ + Flag_MinimizeNumSubdivisions = 0x0001, // subdivide image into fewest possible textures (may use more memory) + Flag_Use64By64Subdivisions = 0x0002, // good to use with image strips so the entire texture isn't pulled in when drawing just a piece + Flag_UseA4R4G4B4 = 0x0004, // images with not too many color gradients work well in this format + Flag_UseA8R8G8B8 = 0x0008, // non-alpha images will be stored as R5G6B5 by default so use this option if you want a 32-bit non-alpha image + Flag_NearestFiltering = 0x0016 //use the nearest filtering for texture scaling. +}; + +struct MsgBoxData +{ + MsgBoxFlags mFlags; + const char *mTitle; + const char *mMessage; +}; + +struct ImageData +{ + int width; + int height; + std::vector pixels; // RGBA8 +}; + +enum PixelFormat +{ + PixelFormat_Unknown = 0x0000, + PixelFormat_A8R8G8B8 = 0x0001, + PixelFormat_A4R4G4B4 = 0x0002, + PixelFormat_R5G6B5 = 0x0004, + PixelFormat_Palette8 = 0x0008 +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +class Texture +{ + public: + virtual ~Texture() = default; +}; + +class Renderer +{ + public: + int mRGBBits; + ulong mRedMask; + ulong mGreenMask; + ulong mBlueMask; + int mRedBits; + int mGreenBits; + int mBlueBits; + int mRedShift; + int mGreenShift; + int mBlueShift; + + enum + { + RESULT_OK = 0, + RESULT_FAIL = 1, + RESULT_DD_CREATE_FAIL = 2, + RESULT_SURFACE_FAIL = 3, + RESULT_EXCLUSIVE_FAIL = 4, + RESULT_DISPCHANGE_FAIL = 5, + RESULT_INVALID_COLORDEPTH = 6, + RESULT_3D_FAIL = 7 + }; + + AppBase *mApp; + CritSect mCritSect; + int mWidth; + int mHeight; + int mDisplayWidth; + int mDisplayHeight; + int mVideoOnlyDraw; + + bool mIs3D; + bool mHasInitiated; + + Rect mPresentationRect; + int mRefreshRate; + int mMillisecondsPerFrame; + + GPUImage *mScreenImage; + + public: + Renderer(); + virtual ~Renderer(); + virtual void Cleanup() = 0; + + virtual void AddImage(Image *theImage) = 0; + virtual void RemoveImage(Image *theImage) = 0; + virtual void Remove3DData(GPUImage *theImage) = 0; // for 3d texture cleanup + + virtual void GetOutputSize(int *outWidth, int *outHeight) = 0; + + virtual GPUImage *NewGPUImage() = 0; + + virtual GPUImage *GetScreenImage() + { + return mScreenImage; + } + virtual void UpdateViewport() = 0; + virtual int Init() = 0; + + virtual bool Redraw(Rect *theClipRect) = 0; + virtual void SetVideoOnlyDraw(bool videoOnly) = 0; + + virtual std::unique_ptr CaptureFrameBuffer() = 0; + + virtual bool PreDraw() = 0; + + virtual bool CreateImageTexture(GPUImage *theImage) = 0; + virtual bool RecoverBits(GPUImage *theImage) = 0; + + virtual BlendMode ChooseBlendMode(int theBlendMode); + virtual void DrawText(int theY, int theX, const PopString &theText, const Color &theColor, TTF_Font *theFont) = 0; + + // Draw Funcs + virtual void Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode, bool linearFilter = false) = 0; + virtual void BltClipF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect *theClipRect, + const Color &theColor, int theDrawMode) = 0; + virtual void BltMirror(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode, bool linearFilter = false) = 0; + virtual void StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect *theClipRect, + const Color &theColor, int theDrawMode, bool fastStretch, bool mirror = false) = 0; + virtual void BltRotated(Image *theImage, float theX, float theY, const Rect *theClipRect, const Color &theColor, + int theDrawMode, double theRot, float theRotCenterX, float theRotCenterY, + const Rect &theSrcRect) = 0; + virtual void BltTransformed(Image *theImage, const Rect *theClipRect, const Color &theColor, int theDrawMode, + const Rect &theSrcRect, const Matrix3 &theTransform, bool linearFilter, float theX = 0, + float theY = 0, bool center = false) = 0; + virtual void DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode) = 0; + virtual void FillRect(const Rect &theRect, const Color &theColor, int theDrawMode) = 0; + virtual void DrawTriangle(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, + int theDrawMode) = 0; + virtual void DrawTriangleTex(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, + int theDrawMode, Image *theTexture, bool blend = true) = 0; + virtual void DrawTrianglesTex(const TriVertex theVertices[][3], int theNumTriangles, const Color &theColor, + int theDrawMode, Image *theTexture, float tx = 0, float ty = 0, + bool blend = true) = 0; + virtual void DrawTrianglesTexStrip(const TriVertex theVertices[], int theNumTriangles, const Color &theColor, + int theDrawMode, Image *theTexture, float tx = 0, float ty = 0, + bool blend = true) = 0; + virtual void FillPoly(const Point theVertices[], int theNumVertices, const Rect *theClipRect, const Color &theColor, + int theDrawMode, int tx, int ty) = 0; + + virtual void BltTexture(Texture *theTexture, const Rect &theSrcRect, const Rect &theDestRect, const Color &theColor, + int theDrawMode) = 0; +}; +extern bool gRendererPreDrawError; + +}; // namespace PopLib + +#endif diff --git a/PopLib/graphics/renderer/apitester.cpp b/PopLib/graphics/renderer/apitester.cpp new file mode 100644 index 00000000..cee1a60c --- /dev/null +++ b/PopLib/graphics/renderer/apitester.cpp @@ -0,0 +1,29 @@ +#include "apitester.hpp" + +using namespace PopLib; + +bool APITester::IsSDLRendererAvailable(SDL_Window *window) +{ + return SDL_GetNumRenderDrivers() > 0; +} + +bool APITester::IsOpenGLAvailable(SDL_Window *window) +{ + SDL_GLContext context = SDL_GL_CreateContext(window); + if (context == nullptr) + return false; + + SDL_GL_DestroyContext(context); + + return true; +} + +bool APITester::IsDirect3DAvailable(SDL_Window *window) +{ +#ifdef _WIN32 + // TODO: Implement Direct3D renderer + return false; +#else + return false; +#endif +} \ No newline at end of file diff --git a/PopLib/graphics/renderer/apitester.hpp b/PopLib/graphics/renderer/apitester.hpp new file mode 100644 index 00000000..7d9853ce --- /dev/null +++ b/PopLib/graphics/renderer/apitester.hpp @@ -0,0 +1,22 @@ +#ifndef __APITESTER_HPP__ +#define __APITESTER_HPP__ + +#pragma once + +#include + +namespace PopLib +{ +class APITester +{ + public: + static bool IsSDLRendererAvailable(SDL_Window *window); + + static bool IsOpenGLAvailable(SDL_Window *window); + + static bool IsDirect3DAvailable(SDL_Window *window); +}; + +} // namespace PopLib + +#endif // __APITESTER_HPP__ \ No newline at end of file diff --git a/PopLib/graphics/renderer/glimage.cpp b/PopLib/graphics/renderer/glimage.cpp new file mode 100644 index 00000000..3099ab4d --- /dev/null +++ b/PopLib/graphics/renderer/glimage.cpp @@ -0,0 +1,202 @@ +#include "glimage.hpp" +#include "misc/critsect.hpp" +#include "debug/log.hpp" +#include "glrenderer.hpp" +#include "appbase.hpp" +#include "../image.hpp" + +using namespace PopLib; + +GLImage::GLImage() : GPUImage() +{ + mRenderer = gAppBase->mRenderer; + mRenderer->AddImage(this); +} + +GLImage::GLImage(Renderer *theRenderer) : GPUImage() +{ + mRenderer = theRenderer; + mRenderer->AddImage(this); +} + +GLImage::~GLImage() +{ + mRenderer->RemoveImage(this); +} + +void GLImage::Create(int theWidth, int theHeight) +{ + delete[] mBits; + + mBits = nullptr; + + mWidth = theWidth; + mHeight = theHeight; + + mHasTrans = true; + mHasAlpha = true; + + BitsChanged(); +} + +bool GLImage::PolyFill3D(const Point theVertices[], int theNumVertices, const Rect *theClipRect, const Color &theColor, + int theDrawMode, int tx, int ty) +{ + mRenderer->FillPoly(theVertices, theNumVertices, theClipRect, theColor, theDrawMode, tx, ty); + return true; +} + +void GLImage::FillRect(const Rect &theRect, const Color &theColor, int theDrawMode) +{ + mRenderer->FillRect(theRect, theColor, theDrawMode); +} + +void GLImage::DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode) +{ + mRenderer->DrawLine(theStartX, theStartY, theEndX, theEndY, theColor, theDrawMode); +} + +void GLImage::DrawLineAA(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode) +{ + mRenderer->DrawLine(theStartX, theStartY, theEndX, theEndY, theColor, theDrawMode); +} + +void GLImage::Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, int theDrawMode) +{ + theImage->mDrawn = true; + + CommitBits(); + + mRenderer->Blt(theImage, theX, theY, theSrcRect, theColor, theDrawMode); +} + +void GLImage::BltF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode) +{ + theImage->mDrawn = true; + + FRect aClipRect(theClipRect.mX, theClipRect.mY, theClipRect.mWidth, theClipRect.mHeight); + FRect aDestRect(theX, theY, theSrcRect.mWidth, theSrcRect.mHeight); + + FRect anIntersect = aDestRect.Intersection(aClipRect); + if (anIntersect.mWidth != aDestRect.mWidth || anIntersect.mHeight != aDestRect.mHeight) + { + if (anIntersect.mWidth != 0 && anIntersect.mHeight != 0) + mRenderer->BltClipF(theImage, theX, theY, theSrcRect, &theClipRect, theColor, theDrawMode); + } + else + mRenderer->Blt(theImage, theX, theY, theSrcRect, theColor, theDrawMode, true); +} + +void GLImage::BltRotated(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode, double theRot, float theRotCenterX, + float theRotCenterY) +{ + theImage->mDrawn = true; + + CommitBits(); + + mRenderer->BltRotated(theImage, theX, theY, &theClipRect, theColor, theDrawMode, theRot, theRotCenterX, + theRotCenterY, theSrcRect); +} + +void GLImage::StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode, bool fastStretch) +{ + theImage->mDrawn = true; + + CommitBits(); + + mRenderer->StretchBlt(theImage, theDestRect, theSrcRect, &theClipRect, theColor, theDrawMode, fastStretch); +} + +void GLImage::BltMatrix(Image *theImage, float x, float y, const Matrix3 &theMatrix, const Rect &theClipRect, + const Color &theColor, int theDrawMode, const Rect &theSrcRect, bool blend) +{ + theImage->mDrawn = true; + + mRenderer->BltTransformed(theImage, &theClipRect, theColor, theDrawMode, theSrcRect, theMatrix, blend, x, y, true); +} + +void GLImage::BltTrianglesTex(Image *theTexture, const TriVertex theVertices[][3], int theNumTriangles, + const Rect &theClipRect, const Color &theColor, int theDrawMode, float tx, float ty, + bool blend) +{ + theTexture->mDrawn = true; + + mRenderer->DrawTrianglesTex(theVertices, theNumTriangles, theColor, theDrawMode, theTexture, tx, ty, blend); +} + +void GLImage::BltMirror(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode) +{ + theImage->mDrawn = true; + + CommitBits(); + + mRenderer->BltMirror(theImage, theX, theY, theSrcRect, theColor, theDrawMode); +} + +void GLImage::StretchBltMirror(Image *theImage, const Rect &theDestRectOrig, const Rect &theSrcRect, + const Rect &theClipRect, const Color &theColor, int theDrawMode, bool fastStretch) +{ + theImage->mDrawn = true; + + CommitBits(); + + mRenderer->StretchBlt(theImage, theDestRectOrig, theSrcRect, &theClipRect, theColor, theDrawMode, fastStretch, + true); +} + +void GLImage::FillScanLinesWithCoverage(Span *theSpans, int theSpanCount, const Color &theColor, int theDrawMode, + const BYTE *theCoverage, int theCoverX, int theCoverY, int theCoverWidth, + int theCoverHeight) +{ + if (theSpanCount == 0) + return; + + int l = theSpans[0].mX, t = theSpans[0].mY; + int r = l + theSpans[0].mWidth, b = t; + for (int i = 1; i < theSpanCount; ++i) + { + l = std::min(theSpans[i].mX, l); + r = std::max(theSpans[i].mX + theSpans[i].mWidth - 1, r); + t = std::min(theSpans[i].mY, t); + b = std::max(theSpans[i].mY + theSpans[i].mWidth - 1, b); + } + for (int i = 0; i < theSpanCount; ++i) + { + theSpans[i].mX -= l; + theSpans[i].mY -= t; + } + + MemoryImage aTempImage; + aTempImage.Create(r - l + 1, b - t + 1); + aTempImage.FillScanLinesWithCoverage(theSpans, theSpanCount, theColor, theDrawMode, theCoverage, theCoverX - l, + theCoverY - t, theCoverWidth, theCoverHeight); + Blt(&aTempImage, l, t, Rect(0, 0, r - l + 1, b - t + 1), Color::White, theDrawMode); + return; +} + +bool GLImage::Check3D(GLImage *theImage) +{ + return true; +} + +bool GLImage::Check3D(Image *theImage) +{ + GLImage *anImage = dynamic_cast(theImage); + return anImage != nullptr; +} + +void GLImage::PurgeBits() +{ + mPurgeBits = true; + + CommitBits(); + GetBits(); + + MemoryImage::PurgeBits(); +} \ No newline at end of file diff --git a/PopLib/graphics/renderer/glimage.hpp b/PopLib/graphics/renderer/glimage.hpp new file mode 100644 index 00000000..96fe2b99 --- /dev/null +++ b/PopLib/graphics/renderer/glimage.hpp @@ -0,0 +1,64 @@ +#ifndef __GLIMAGE_HPP__ +#define __GLIMAGE_HPP__ + +#pragma once + +#include "../gpuimage.hpp" + +namespace PopLib +{ +class Renderer; +class SysFont; + +class GLImage : public GPUImage +{ + protected: + friend class SysFont; + + public: + virtual void FillScanLinesWithCoverage(Span *theSpans, int theSpanCount, const Color &theColor, int theDrawMode, + const BYTE *theCoverage, int theCoverX, int theCoverY, int theCoverWidth, + int theCoverHeight); + + static bool Check3D(GLImage *theImage); + static bool Check3D(Image *theImage); + + public: + GLImage(); + GLImage(Renderer *theRenderer); + virtual ~GLImage(); + + virtual void Create(int theWidth, int theHeight); + + virtual bool PolyFill3D(const Point theVertices[], int theNumVertices, const Rect *theClipRect, + const Color &theColor, int theDrawMode, int tx, int ty); + virtual void FillRect(const Rect &theRect, const Color &theColor, int theDrawMode); + virtual void DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode); + virtual void DrawLineAA(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode); + virtual void Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode); + virtual void BltF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode); + virtual void BltRotated(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode, double theRot, float theRotCenterX, + float theRotCenterY); + virtual void StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode, bool fastStretch); + virtual void BltMatrix(Image *theImage, float x, float y, const Matrix3 &theMatrix, const Rect &theClipRect, + const Color &theColor, int theDrawMode, const Rect &theSrcRect, bool blend); + virtual void BltTrianglesTex(Image *theTexture, const TriVertex theVertices[][3], int theNumTriangles, + const Rect &theClipRect, const Color &theColor, int theDrawMode, float tx, float ty, + bool blend); + + virtual void BltMirror(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode); + virtual void StretchBltMirror(Image *theImage, const Rect &theDestRectOrig, const Rect &theSrcRect, + const Rect &theClipRect, const Color &theColor, int theDrawMode, bool fastStretch); + + virtual void PurgeBits(); +}; +} // namespace PopLib + +#endif // __GLIMAGE_HPP__ \ No newline at end of file diff --git a/PopLib/graphics/renderer/glrenderer.cpp b/PopLib/graphics/renderer/glrenderer.cpp new file mode 100644 index 00000000..7ac2f34d --- /dev/null +++ b/PopLib/graphics/renderer/glrenderer.cpp @@ -0,0 +1,1023 @@ +#include "glrenderer.hpp" +#include "appbase.hpp" +#include "math/matrix.hpp" +#include "graphics/graphics.hpp" +#include "misc/autocrit.hpp" +#include +#include "debug/log.hpp" + +using namespace PopLib; + +#define MAX_VERTICES 16384 + +const char *gVertexShaderSrc = R"glsl( +#version 330 core + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aTex; +layout(location = 2) in vec4 aColor; + +uniform mat4 uProjection; + +out vec2 vTexCoord; +out vec4 vColor; + +void main() { + gl_Position = uProjection * vec4(aPos, 0.0, 1.0); + vTexCoord = aTex; + vColor = aColor; +} +)glsl"; + +const char *gFragmentShaderSrc = R"glsl( +#version 330 core + +in vec2 vTexCoord; +in vec4 vColor; + +uniform sampler2D uTexture; +uniform bool uUseTexture; + +out vec4 FragColor; + +void main() { + if (uUseTexture) + FragColor = texture(uTexture, vTexCoord) * vColor; + else + FragColor = vColor; +} +)glsl"; + +GLRenderer::GLRenderer(AppBase *theApp) +{ + mApp = theApp; + mWidth = mApp->mWidth; + mHeight = mApp->mHeight; + mDisplayWidth = mWidth; + mDisplayHeight = mHeight; + mPresentationRect = Rect(0, 0, mWidth, mHeight); + mScreenImage = nullptr; + mHasInitiated = false; + mIs3D = true; + mMillisecondsPerFrame = 0; + mRefreshRate = 0; +} + +GLRenderer::~GLRenderer() +{ + Cleanup(); +} + +int GLRenderer::Init() +{ + int aResult = RESULT_OK; + + if (mHasInitiated) + Cleanup(); + + mRGBBits = 32; + + mRedBits = 8; + mGreenBits = 8; + mBlueBits = 8; + + mRedShift = 0; + mGreenShift = 8; + mBlueShift = 16; + + mRedMask = (0xFFU << mRedShift); + mGreenMask = (0xFFU << mGreenShift); + mBlueMask = (0xFFU << mBlueShift); + + aResult = (InitGLContext() && InitBuffers()) ? aResult : RESULT_FAIL; + mHasInitiated = true; + return aResult; +} + +void GLRenderer::Cleanup() +{ + if (mScreenImage) + delete mScreenImage; + + if (mDefaultShader) + delete mDefaultShader; + + GLImageSet::iterator anItr; + for (anItr = mImageSet.begin(); anItr != mImageSet.end(); ++anItr) + { + GLImage *anImage = *anItr; + GLTextureData *aData = (GLTextureData *)anImage->mGPUData; + delete aData; + anImage->mGPUData = nullptr; + } + mImageSet.clear(); + + for (auto &textEntry : mTextTextureCache) + { + glDeleteTextures(1, &textEntry.second.textureID); + } + mTextTextureCache.clear(); + + mCommandBuffer.clear(); + + SDL_GL_DestroyContext(mContext); + + glDeleteBuffers(1, &mVBO); + glDeleteVertexArrays(1, &mVAO); +} + +bool GLRenderer::InitGLContext() +{ + mContext = SDL_GL_CreateContext(mApp->mWindow); + + if (!gladLoadGL((GLADloadfunc)SDL_GL_GetProcAddress)) + { + // ERRORS HERE + return false; + } + + SDL_GL_MakeCurrent(mApp->mWindow, mContext); + + mDefaultShader = new GLShader(); + mDefaultShader->LoadFromSource(gVertexShaderSrc, gFragmentShaderSrc); + + SetVideoOnlyDraw(false); + + return true; +} + +bool GLRenderer::InitBuffers() +{ + glGenVertexArrays(1, &mVAO); + glGenBuffers(1, &mVBO); + + glBindVertexArray(mVAO); + glBindBuffer(GL_ARRAY_BUFFER, mVBO); + + glBufferData(GL_ARRAY_BUFFER, MAX_VERTICES * sizeof(Vertex), nullptr, GL_DYNAMIC_DRAW); + + // position + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, mPos)); + + // texcoord + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, mTexCoord)); + + // color + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, mColor)); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + return true; +} + +bool GLRenderer::Redraw(Rect *theClipRect) +{ + SDL_GL_SwapWindow(mApp->mWindow); + + if (mCommandBuffer.empty()) + return !gRendererPreDrawError; + + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(mPresentationRect.mX, mPresentationRect.mY, mPresentationRect.mWidth, mPresentationRect.mHeight); + + glBindVertexArray(mVAO); + glBindBuffer(GL_ARRAY_BUFFER, mVBO); + + for (const auto &cmd : mCommandBuffer) + { + if (cmd.mVertices.size() > MAX_VERTICES) + continue; // Add a warning + + ApplyBlendMode(cmd.mBlendMode); + GLShader *aShaderToUse; + if (cmd.mShader != nullptr) + aShaderToUse = cmd.mShader; + else + aShaderToUse = mDefaultShader; + + if (cmd.mClipRect != nullptr) + { + glEnable(GL_SCISSOR_TEST); + // glScissor(cmd.mClipRect->mX, cmd.mClipRect->mY, cmd.mClipRect->mWidth, cmd.mClipRect->mHeight); + } + + aShaderToUse->Use(); + aShaderToUse->SetUniform("uProjection", mProjection); + aShaderToUse->SetUniform("uUseTexture", (cmd.mTextureID != 0)); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, cmd.mTextureID); + glBufferSubData(GL_ARRAY_BUFFER, 0, cmd.mVertices.size() * sizeof(Vertex), cmd.mVertices.data()); + glDrawArrays(cmd.mPrimitiveType, 0, (GLsizei)cmd.mVertices.size()); + } + + mCommandBuffer.clear(); + + glDisable(GL_SCISSOR_TEST); + + return !gRendererPreDrawError; +} + +void GLRenderer::ApplyBlendMode(BlendMode mode) +{ + auto it = blend_mode_funcs.find(mode); + if (it == blend_mode_funcs.end()) + return; + + const auto &blend = it->second; + + if (blend.enable_blend) + { + glEnable(GL_BLEND); + glBlendFunc(blend.src, blend.dst); + } + else + { + glDisable(GL_BLEND); + } +} + +void GLRenderer::SetVideoOnlyDraw(bool videoOnly) +{ + if (mScreenImage) + delete mScreenImage; + mScreenImage = new GLImage(); + mScreenImage->Create(mWidth, mHeight); + mScreenImage->mWidth = mWidth; + mScreenImage->mHeight = mHeight; + mScreenImage->SetImageMode(false, false); +} + +GPUImage *GLRenderer::GetScreenImage() +{ + return mScreenImage; +} + +std::unique_ptr GLRenderer::CaptureFrameBuffer() +{ + uint8_t *pixels = new uint8_t[3 * mWidth * mHeight]; + + glReadPixels(0, 0, mWidth, mHeight, GL_RGB, GL_UNSIGNED_BYTE, pixels); + + auto image = std::make_unique(); + image->width = mWidth; + image->height = mHeight; + image->pixels.resize(mWidth * mHeight * 4); + + uint8_t *src = static_cast(pixels); + uint8_t *dst = image->pixels.data(); + + for (int i = 0; i < mWidth * mHeight * 4; i += 4) + { + dst[i + 0] = src[i + 2]; // R + dst[i + 1] = src[i + 1]; // G + dst[i + 2] = src[i + 0]; // B + dst[i + 3] = src[i + 3]; // A + } + + return image; +} + +bool GLRenderer::CreateImageTexture(GPUImage *theImage) +{ + bool wantPurge = false; + + if (theImage->mGPUData == nullptr) + { + theImage->mGPUData = new GLTextureData(); + + // The actual purging was deferred + wantPurge = theImage->mPurgeBits; + + AutoCrit aCrit(mCritSect); // Make images thread safe + mImageSet.insert(static_cast(theImage)); + } + + GLTextureData *aData = static_cast(theImage->mGPUData); + aData->CheckCreateTextures(static_cast(theImage)); + + if (wantPurge) + theImage->PurgeBits(); + + return true; +} + +void GLRenderer::AddImage(Image *theImage) +{ + AutoCrit anAutoCrit(mCritSect); + + mImageSet.insert((GLImage *)theImage); +} + +void GLRenderer::RemoveImage(Image *theImage) +{ + AutoCrit anAutoCrit(mCritSect); + + GLImageSet::iterator anItr = mImageSet.find((GLImage *)theImage); + if (anItr != mImageSet.end()) + mImageSet.erase(anItr); +} + +void GLRenderer::Remove3DData(GPUImage *theImage) +{ + if (theImage->mGPUData != nullptr) + { + delete (GLTextureData *)theImage->mGPUData; + theImage->mGPUData = nullptr; + + AutoCrit aCrit(mCritSect); // Make images thread safe + mImageSet.erase(static_cast(theImage)); + } +} +void GLRenderer::GetOutputSize(int *outWidth, int *outHeight) +{ + GLint m_viewport[4]; + + glGetIntegerv(GL_VIEWPORT, m_viewport); + *outWidth = m_viewport[2]; + *outHeight = m_viewport[3]; +} + +void GLRenderer::UpdateViewport() +{ + if (SDL_GetCurrentThreadID() != SDL_GetThreadID(nullptr)) + return; + + int aWindowWidth, aWindowHeight; + if (!SDL_GetWindowSize(mApp->mWindow, &aWindowWidth, &aWindowHeight)) + return; + + float windowAspect = (float)aWindowWidth / aWindowHeight; + float logicalAspect = (float)mWidth / mHeight; + + int vpX, vpY, vpW, vpH; + + if (windowAspect > logicalAspect) + { + vpH = aWindowHeight; + vpW = (int)(logicalAspect * vpH); + vpX = (aWindowWidth - vpW) / 2; + vpY = 0; + } + else + { + vpW = aWindowWidth; + vpH = (int)(vpW / logicalAspect); + vpX = 0; + vpY = (aWindowHeight - vpH) / 2; + } + + // Set the OpenGL viewport + glViewport(vpX, vpY, vpW, vpH); + + mPresentationRect = Rect(vpX, vpY, vpW, vpH); + + mProjection = + glm::ortho(0.0f, (float)mPresentationRect.mWidth, (float)mPresentationRect.mHeight, 0.0f, -1.0f, 1.0f) * + glm::mat4(1.0f); +} + +bool GLRenderer::RecoverBits(GPUImage *theImage) +{ + if (theImage->mGPUData == nullptr) + return false; + + GLTextureData *aData = (GLTextureData *)theImage->mGPUData; + if (aData->mBitsChangedCount != theImage->mBitsChangedCount) // bits have changed since texture was created + return false; + + // Reverse the process: copy texture data to theImage + void *pixels = nullptr; + glBindTexture(GL_TEXTURE_2D, aData->mTextureID); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + theImage->SetBits((uint32_t *)pixels, aData->mWidth, aData->mHeight); + + return true; +} + +GLTextureData::GLTextureData() +{ + mWidth = 0; + mHeight = 0; + mBitsChangedCount = 0; + mTextureID = 0; +} + +GLTextureData::~GLTextureData() +{ + ReleaseTextures(); +} + +void GLTextureData::ReleaseTextures() +{ + if (mTextureID != 0) + glDeleteTextures(1, &mTextureID); +} + +void GLTextureData::CreateTextures(GLImage *theImage) +{ + theImage->DeleteSWBuffers(); // we don't need the software buffers anymore + theImage->CommitBits(); + + bool createTexture = false; + + // only recreate the texture if the dimensions or image data have changed + if (mWidth != theImage->mWidth || mHeight != theImage->mHeight || mBitsChangedCount != theImage->mBitsChangedCount) + { + ReleaseTextures(); + createTexture = true; + } + + int aWidth = theImage->GetWidth(); + int aHeight = theImage->GetHeight(); + + if (createTexture) + { + glGenTextures(1, &mTextureID); + glBindTexture(GL_TEXTURE_2D, mTextureID); + + bool doNearestFilter = theImage->mImageFlags & GPUImageFlag_NearestFiltering; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, doNearestFilter ? GL_NEAREST : GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, doNearestFilter ? GL_NEAREST : GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, aWidth, aHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, theImage->GetBits()); + glGenerateMipmap(GL_TEXTURE_2D); + } + else if (mBitsChangedCount != theImage->mBitsChangedCount) + { + void *bits = theImage->GetBits(); + if (bits) + { + glGenTextures(1, &mTextureID); + glBindTexture(GL_TEXTURE_2D, mTextureID); + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, aWidth, aHeight, GL_BGRA, GL_UNSIGNED_BYTE, bits); + glGenerateMipmap(GL_TEXTURE_2D); + } + else + { + LOG_ERROR("Error: Image bits are nullptr, cannot update texture."); + } + } + + mWidth = theImage->mWidth; + mHeight = theImage->mHeight; + mBitsChangedCount = theImage->mBitsChangedCount; +} + +void GLTextureData::CheckCreateTextures(GLImage *theImage) +{ + if (mTextureID != 0) + { + if (mWidth != theImage->mWidth || mHeight != theImage->mHeight || + mBitsChangedCount != theImage->mBitsChangedCount) + CreateTextures(theImage); + return; + } + CreateTextures(theImage); +} + +int GLTextureData::GetMemSize() +{ + int aSize = 0; + + aSize = 4 * mWidth * mHeight; // TODO: ADD MORE PIXEL FORMATS + + return aSize; +} + +bool GLRenderer::PreDraw() +{ + return true; +} + +GLImage *GLRenderer::SetupImage(Image *theImage) +{ + GLImage *aImg = static_cast(theImage); + + if (aImg->mGPUData == nullptr) + CreateImageTexture(aImg); + return aImg; +} + +void GLRenderer::AddCommand(const GLDrawCommand &command) +{ + mCommandBuffer.push_back(command); +} + +///////////////////////////////////////////////////////////////// +/// DRAWING/BLITTING FUNCTIONS ////// +///////////////////////////////////////////////////////////////// + +GLTextCacheEntry GLRenderer::GetOrCreateText(const PopString &theText, TTF_Font *theFont) +{ + auto it = mTextTextureCache.find(theText); + if (it != mTextTextureCache.end()) + { + return it->second; + } + + SDL_Color aColor = {255, 255, 255, 255}; + SDL_Surface *textSurface = TTF_RenderText_Blended(theFont, theText.c_str(), 0, aColor); + + SDL_Surface *convertedSurface = nullptr; + + GLuint aTexID; + glGenTextures(1, &aTexID); + glBindTexture(GL_TEXTURE_2D, aTexID); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, textSurface->pitch / SDL_BYTESPERPIXEL(textSurface->format)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textSurface->w, textSurface->h, 0, GL_BGRA, GL_UNSIGNED_BYTE, + textSurface->pixels); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + GLTextCacheEntry entry; + entry.textureID = aTexID; + entry.mWidth = textSurface->w; + entry.mHeight = textSurface->h; + mTextTextureCache[theText] = entry; + + SDL_DestroySurface(textSurface); + return entry; +} + +void GLRenderer::DrawText(int theY, int theX, const PopString &theText, const Color &theColor, TTF_Font *theFont) +{ + GLTextCacheEntry aTextEntry = GetOrCreateText(theText, theFont); + + GLDrawCommand aCmd; + aCmd.mClipRect = new Rect(0, 0, mPresentationRect.mWidth, mPresentationRect.mHeight); + aCmd.mTextureID = aTextEntry.textureID; + aCmd.mPrimitiveType = GL_TRIANGLES; + aCmd.mBlendMode = ChooseBlendMode(Graphics::DRAWMODE_NORMAL); + + glm::vec2 p0 = {theX, theY}; + glm::vec2 p1 = {theX + aTextEntry.mWidth, theY}; + glm::vec2 p2 = {theX + aTextEntry.mWidth, theY + aTextEntry.mHeight}; + glm::vec2 p3 = {theX, theY + aTextEntry.mHeight}; + + glm::vec2 uv0 = {0, 0}; + glm::vec2 uv1 = {1, 0}; + glm::vec2 uv2 = {1, 1}; + glm::vec2 uv3 = {0, 1}; + + glm::vec4 aColor = {(float)theColor.mRed / 255.0f, (float)theColor.mGreen / 255.0f, (float)theColor.mBlue / 255.0f, + (float)theColor.mAlpha / 255.0f}; + + aCmd.mVertices.push_back({p0, uv0, aColor}); + aCmd.mVertices.push_back({p1, uv1, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p3, uv3, aColor}); + aCmd.mVertices.push_back({p0, uv0, aColor}); + + AddCommand(aCmd); +} + +void GLRenderer::Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode, bool linearFilter) +{ + GLImage *aImg = SetupImage(theImage); + + GLDrawCommand aCmd; + aCmd.mClipRect = nullptr; + aCmd.mTextureID = static_cast(aImg->mGPUData)->mTextureID; + aCmd.mPrimitiveType = GL_TRIANGLES; + aCmd.mBlendMode = ChooseBlendMode(theDrawMode); + + glm::vec2 p0 = {theX, theY}; + glm::vec2 p1 = {theX + theSrcRect.mWidth, theY}; + glm::vec2 p2 = {theX + theSrcRect.mWidth, theY + theSrcRect.mHeight}; + glm::vec2 p3 = {theX, theY + theSrcRect.mHeight}; + + float u0 = (float)theSrcRect.mX / (float)theImage->mWidth; + float v0 = (float)theSrcRect.mY / (float)theImage->mHeight; + float u1 = (float)(theSrcRect.mX + theSrcRect.mWidth) / (float)theImage->mWidth; + float v1 = (float)(theSrcRect.mY + theSrcRect.mHeight) / (float)theImage->mHeight; + + glm::vec2 uv0 = {u0, v0}; + glm::vec2 uv1 = {u1, v0}; + glm::vec2 uv2 = {u1, v1}; + glm::vec2 uv3 = {u0, v1}; + + glm::vec4 aColor = {(float)theColor.mRed / 255.0f, (float)theColor.mGreen / 255.0f, (float)theColor.mBlue / 255.0f, + (float)theColor.mAlpha / 255.0f}; + + aCmd.mVertices.push_back({p0, uv0, aColor}); + aCmd.mVertices.push_back({p1, uv1, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p3, uv3, aColor}); + aCmd.mVertices.push_back({p0, uv0, aColor}); + + AddCommand(aCmd); +} + +void GLRenderer::BltClipF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect *theClipRect, + const Color &theColor, int theDrawMode) +{ + GLImage *aImg = SetupImage(theImage); + + GLDrawCommand aCmd; + aCmd.mClipRect = theClipRect; + aCmd.mTextureID = static_cast(aImg->mGPUData)->mTextureID; + aCmd.mPrimitiveType = GL_TRIANGLES; + aCmd.mBlendMode = ChooseBlendMode(theDrawMode); + + glm::vec2 p0 = {theX, theY}; + glm::vec2 p1 = {theX + theSrcRect.mWidth, theY}; + glm::vec2 p2 = {theX + theSrcRect.mWidth, theY + theSrcRect.mHeight}; + glm::vec2 p3 = {theX, theY + theSrcRect.mHeight}; + + float u0 = (float)theSrcRect.mX / (float)theImage->mWidth; + float v0 = (float)theSrcRect.mY / (float)theImage->mHeight; + float u1 = (float)(theSrcRect.mX + theSrcRect.mWidth) / (float)theImage->mWidth; + float v1 = (float)(theSrcRect.mY + theSrcRect.mHeight) / (float)theImage->mHeight; + + glm::vec2 uv0 = {u0, v0}; + glm::vec2 uv1 = {u1, v0}; + glm::vec2 uv2 = {u1, v1}; + glm::vec2 uv3 = {u0, v1}; + + glm::vec4 aColor = {(float)theColor.mRed / 255.0f, (float)theColor.mGreen / 255.0f, (float)theColor.mBlue / 255.0f, + (float)theColor.mAlpha / 255.0f}; + + aCmd.mVertices.push_back({p0, uv0, aColor}); + aCmd.mVertices.push_back({p1, uv1, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p3, uv3, aColor}); + aCmd.mVertices.push_back({p0, uv0, aColor}); + + AddCommand(aCmd); +} + +void GLRenderer::BltMirror(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode, bool linearFilter) +{ + GLImage *aImg = SetupImage(theImage); + + GLDrawCommand aCmd; + aCmd.mClipRect = nullptr; + aCmd.mTextureID = static_cast(aImg->mGPUData)->mTextureID; + aCmd.mPrimitiveType = GL_TRIANGLES; + aCmd.mBlendMode = ChooseBlendMode(theDrawMode); + + glm::vec2 p0 = {theX, theY}; + glm::vec2 p1 = {theX + theSrcRect.mWidth, theY}; + glm::vec2 p2 = {theX + theSrcRect.mWidth, theY + theSrcRect.mHeight}; + glm::vec2 p3 = {theX, theY + theSrcRect.mHeight}; + + float u0 = (float)theSrcRect.mX / (float)theImage->mWidth; + float v0 = (float)theSrcRect.mY / (float)theImage->mHeight; + float u1 = (float)(theSrcRect.mX + theSrcRect.mWidth) / (float)theImage->mWidth; + float v1 = (float)(theSrcRect.mY + theSrcRect.mHeight) / (float)theImage->mHeight; + + std::swap(u0, u1); + + glm::vec2 uv0 = {u0, v0}; + glm::vec2 uv1 = {u1, v0}; + glm::vec2 uv2 = {u1, v1}; + glm::vec2 uv3 = {u0, v1}; + + glm::vec4 aColor = {(float)theColor.mRed / 255.0f, (float)theColor.mGreen / 255.0f, (float)theColor.mBlue / 255.0f, + (float)theColor.mAlpha / 255.0f}; + + aCmd.mVertices.push_back({p0, uv0, aColor}); + aCmd.mVertices.push_back({p1, uv1, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p3, uv3, aColor}); + aCmd.mVertices.push_back({p0, uv0, aColor}); + + AddCommand(aCmd); +} + +void GLRenderer::StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect *theClipRect, + const Color &theColor, int theDrawMode, bool fastStretch, bool mirror) +{ + GLImage *aImg = SetupImage(theImage); + + GLDrawCommand aCmd; + aCmd.mClipRect = theClipRect; + aCmd.mTextureID = static_cast(aImg->mGPUData)->mTextureID; + aCmd.mPrimitiveType = GL_TRIANGLES; + aCmd.mBlendMode = ChooseBlendMode(theDrawMode); + + glm::vec2 p0 = {theDestRect.mX, theDestRect.mY}; + glm::vec2 p1 = {theDestRect.mX + theDestRect.mWidth, theDestRect.mY}; + glm::vec2 p2 = {theDestRect.mX + theDestRect.mWidth, theDestRect.mY + theDestRect.mHeight}; + glm::vec2 p3 = {theDestRect.mX, theDestRect.mY + theDestRect.mHeight}; + + float u0 = (float)theSrcRect.mX / (float)theImage->mWidth; + float v0 = (float)theSrcRect.mY / (float)theImage->mHeight; + float u1 = (float)(theSrcRect.mX + theSrcRect.mWidth) / (float)theImage->mWidth; + float v1 = (float)(theSrcRect.mY + theSrcRect.mHeight) / (float)theImage->mHeight; + + if (mirror) + { + std::swap(u0, u1); + } + + glm::vec2 uv0 = {u0, v0}; + glm::vec2 uv1 = {u1, v0}; + glm::vec2 uv2 = {u1, v1}; + glm::vec2 uv3 = {u0, v1}; + + glm::vec4 aColor = {(float)theColor.mRed / 255.0f, (float)theColor.mGreen / 255.0f, (float)theColor.mBlue / 255.0f, + (float)theColor.mAlpha / 255.0f}; + + aCmd.mVertices.push_back({p0, uv0, aColor}); + aCmd.mVertices.push_back({p1, uv1, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p3, uv3, aColor}); + aCmd.mVertices.push_back({p0, uv0, aColor}); + + AddCommand(aCmd); +} + +glm::vec2 RotatePointAroundPivot(const glm::vec2 point, const glm::vec2 center, float angleRad) +{ + float sinValue = sin(angleRad); + float cosValue = cos(angleRad); + + glm::vec2 translation = point - center; + + glm::vec2 rotation = {translation.x * cosValue - translation.y * sinValue, + translation.x * sinValue + translation.y * cosValue}; + + return rotation + center; +} + +void GLRenderer::BltRotated(Image *theImage, float theX, float theY, const Rect *theClipRect, const Color &theColor, + int theDrawMode, double theRot, float theRotCenterX, float theRotCenterY, + const Rect &theSrcRect) +{ + GLImage *aImg = SetupImage(theImage); + + GLDrawCommand aCmd; + aCmd.mClipRect = theClipRect; + aCmd.mTextureID = static_cast(aImg->mGPUData)->mTextureID; + aCmd.mPrimitiveType = GL_TRIANGLES; + aCmd.mBlendMode = ChooseBlendMode(theDrawMode); + + glm::vec2 p0 = {theX, theY}; + glm::vec2 p1 = {theX + theSrcRect.mWidth, theY}; + glm::vec2 p2 = {theX + theSrcRect.mWidth, theY + theSrcRect.mHeight}; + glm::vec2 p3 = {theX, theY + theSrcRect.mHeight}; + + float radians = glm::radians(theRot); + glm::vec2 center = {theRotCenterX + theX, theRotCenterY + theY}; + p0 = RotatePointAroundPivot(p0, center, radians); + p1 = RotatePointAroundPivot(p1, center, radians); + p2 = RotatePointAroundPivot(p2, center, radians); + p3 = RotatePointAroundPivot(p3, center, radians); + + float u0 = (float)theSrcRect.mX / (float)theImage->mWidth; + float v0 = (float)theSrcRect.mY / (float)theImage->mHeight; + float u1 = (float)(theSrcRect.mX + theSrcRect.mWidth) / (float)theImage->mWidth; + float v1 = (float)(theSrcRect.mY + theSrcRect.mHeight) / (float)theImage->mHeight; + + glm::vec2 uv0 = {u0, v0}; + glm::vec2 uv1 = {u1, v0}; + glm::vec2 uv2 = {u1, v1}; + glm::vec2 uv3 = {u0, v1}; + + glm::vec4 aColor = {(float)theColor.mRed / 255.0f, (float)theColor.mGreen / 255.0f, (float)theColor.mBlue / 255.0f, + (float)theColor.mAlpha / 255.0f}; + + aCmd.mVertices.push_back({p0, uv0, aColor}); + aCmd.mVertices.push_back({p1, uv1, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p3, uv3, aColor}); + aCmd.mVertices.push_back({p0, uv0, aColor}); + + AddCommand(aCmd); +} + +glm::vec2 TransformToPointGLM(float x, float y, const Matrix3 &m, float aTransX = 0, float aTransY = 0) +{ + glm::vec2 result; + result.x = m.m00 * x + m.m01 * y + m.m02 + aTransX; + result.y = m.m10 * x + m.m11 * y + m.m12 + aTransY; + return result; +} + +void GLRenderer::BltTransformed(Image *theImage, const Rect *theClipRect, const Color &theColor, int theDrawMode, + const Rect &theSrcRect, const Matrix3 &theTransform, bool linearFilter, float theX, + float theY, bool center) +{ + GLImage *aImg = SetupImage(theImage); + + GLDrawCommand aCmd; + aCmd.mClipRect = theClipRect; + aCmd.mTextureID = static_cast(aImg->mGPUData)->mTextureID; + aCmd.mPrimitiveType = GL_TRIANGLES; + aCmd.mBlendMode = ChooseBlendMode(theDrawMode); + + float aWidth = static_cast(theSrcRect.mWidth); + float aHeight = static_cast(theSrcRect.mHeight); + + glm::vec2 origin = {0.0f, 0.0f}; + if (center) + origin = {aWidth * 0.5f, aHeight * 0.5f}; + + glm::vec2 localP0 = {-origin.x, -origin.y}; + glm::vec2 localP1 = {aWidth - origin.x, -origin.y}; + glm::vec2 localP2 = {aWidth - origin.x, aHeight - origin.y}; + glm::vec2 localP3 = {-origin.x, aHeight - origin.y}; + + glm::vec2 p0 = TransformToPointGLM(localP0.x, localP0.y, theTransform, theX, theY); + glm::vec2 p1 = TransformToPointGLM(localP1.x, localP1.y, theTransform, theX, theY); + glm::vec2 p2 = TransformToPointGLM(localP2.x, localP2.y, theTransform, theX, theY); + glm::vec2 p3 = TransformToPointGLM(localP3.x, localP3.y, theTransform, theX, theY); + + float u0 = (float)theSrcRect.mX / (float)theImage->mWidth; + float v0 = (float)theSrcRect.mY / (float)theImage->mHeight; + float u1 = (float)(theSrcRect.mX + theSrcRect.mWidth) / (float)theImage->mWidth; + float v1 = (float)(theSrcRect.mY + theSrcRect.mHeight) / (float)theImage->mHeight; + + glm::vec2 uv0 = {u0, v0}; + glm::vec2 uv1 = {u1, v0}; + glm::vec2 uv2 = {u1, v1}; + glm::vec2 uv3 = {u0, v1}; + + glm::vec4 aColor = {(float)theColor.mRed / 255.0f, (float)theColor.mGreen / 255.0f, (float)theColor.mBlue / 255.0f, + (float)theColor.mAlpha / 255.0f}; + + aCmd.mVertices.push_back({p0, uv0, aColor}); + aCmd.mVertices.push_back({p1, uv1, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p2, uv2, aColor}); + aCmd.mVertices.push_back({p3, uv3, aColor}); + aCmd.mVertices.push_back({p0, uv0, aColor}); + + AddCommand(aCmd); +} + +void GLRenderer::DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode) +{ + GLDrawCommand aCmd; + aCmd.mPrimitiveType = GL_LINES; + aCmd.mTextureID = 0; + aCmd.mBlendMode = ChooseBlendMode(theDrawMode); + aCmd.mClipRect = new Rect(0, 0, mPresentationRect.mWidth, mPresentationRect.mHeight); + glm::vec4 color = + glm::vec4(theColor.mRed / 255.0f, theColor.mGreen / 255.0f, theColor.mBlue / 255.0f, theColor.mAlpha / 255.0f); + aCmd.mVertices.push_back({{theStartX, theStartY}, {}, color}); + aCmd.mVertices.push_back({{theEndX, theEndY}, {}, color}); + + AddCommand(aCmd); +} + +void GLRenderer::FillRect(const Rect &theRect, const Color &theColor, int theDrawMode) +{ + GLDrawCommand aCmd; + aCmd.mClipRect = new Rect(0, 0, mPresentationRect.mWidth, mPresentationRect.mHeight); + aCmd.mTextureID = 0; + aCmd.mPrimitiveType = GL_TRIANGLES; + aCmd.mBlendMode = ChooseBlendMode(theDrawMode); + + glm::vec2 p0 = {theRect.mX, theRect.mY}; + glm::vec2 p1 = {theRect.mX + theRect.mWidth, theRect.mY}; + glm::vec2 p2 = {theRect.mX + theRect.mWidth, theRect.mY + theRect.mHeight}; + glm::vec2 p3 = {theRect.mX, theRect.mY + theRect.mHeight}; + + glm::vec4 aColor = {(float)theColor.mRed / 255.0f, (float)theColor.mGreen / 255.0f, (float)theColor.mBlue / 255.0f, + (float)theColor.mAlpha / 255.0f}; + + aCmd.mVertices.push_back({p0, {}, aColor}); + aCmd.mVertices.push_back({p1, {}, aColor}); + aCmd.mVertices.push_back({p2, {}, aColor}); + aCmd.mVertices.push_back({p2, {}, aColor}); + aCmd.mVertices.push_back({p3, {}, aColor}); + aCmd.mVertices.push_back({p0, {}, aColor}); + + AddCommand(aCmd); +} + +void GLRenderer::DrawTriangle(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, + int theDrawMode) +{ + GLDrawCommand aCmd; + aCmd.mClipRect = new Rect(0, 0, mPresentationRect.mWidth, mPresentationRect.mHeight); + aCmd.mTextureID = 0; + aCmd.mPrimitiveType = GL_TRIANGLES; + aCmd.mBlendMode = ChooseBlendMode(theDrawMode); + + glm::vec2 vert0 = {p1.x, p1.y}; + glm::vec2 vert1 = {p2.x, p2.y}; + glm::vec2 vert2 = {p3.x, p3.y}; + + glm::vec4 aColor = {(float)theColor.mRed / 255.0f, (float)theColor.mGreen / 255.0f, (float)theColor.mBlue / 255.0f, + (float)theColor.mAlpha / 255.0f}; + + aCmd.mVertices.push_back({vert0, {p1.u, p1.v}, aColor}); + aCmd.mVertices.push_back({vert1, {p2.u, p2.v}, aColor}); + aCmd.mVertices.push_back({vert2, {p3.u, p3.v}, aColor}); + + AddCommand(aCmd); +} + +void GLRenderer::DrawTriangleTex(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, + int theDrawMode, Image *theTexture, bool blend) +{ + GLImage *aImg = SetupImage(theTexture); + + GLDrawCommand aCmd; + aCmd.mClipRect = new Rect(0, 0, mPresentationRect.mWidth, mPresentationRect.mHeight); + aCmd.mTextureID = static_cast(aImg->mGPUData)->mTextureID; + aCmd.mPrimitiveType = GL_TRIANGLES; + aCmd.mBlendMode = ChooseBlendMode(theDrawMode); + + glm::vec2 vert0 = {p1.x, p1.y}; + glm::vec2 vert1 = {p2.x, p2.y}; + glm::vec2 vert2 = {p3.x, p3.y}; + + glm::vec4 aColor = {(float)theColor.mRed / 255.0f, (float)theColor.mGreen / 255.0f, (float)theColor.mBlue / 255.0f, + (float)theColor.mAlpha / 255.0f}; + + aCmd.mVertices.push_back({vert0, {p1.u, p1.v}, aColor}); + aCmd.mVertices.push_back({vert1, {p2.u, p2.v}, aColor}); + aCmd.mVertices.push_back({vert2, {p3.u, p3.v}, aColor}); + + AddCommand(aCmd); +} + +void GLRenderer::DrawTrianglesTex(const TriVertex theVertices[][3], int theNumTriangles, const Color &theColor, + int theDrawMode, Image *theTexture, float tx, float ty, bool blend) +{ + for (int aTriangleNum = 0; aTriangleNum < theNumTriangles; aTriangleNum++) + { + TriVertex v0 = theVertices[aTriangleNum][0]; + TriVertex v1 = theVertices[aTriangleNum][1]; + TriVertex v2 = theVertices[aTriangleNum][2]; + + v0.x += tx; + v0.y += ty; + v1.x += tx; + v1.y += ty; + v2.x += tx; + v2.y += ty; + + DrawTriangleTex(v0, v1, v2, theColor, theDrawMode, theTexture, blend); + } +} + +void GLRenderer::DrawTrianglesTexStrip(const TriVertex theVertices[], int theNumTriangles, const Color &theColor, + int theDrawMode, Image *theTexture, float tx, float ty, bool blend) +{ + TriVertex aList[100][3]; + int aTriNum = 0; + while (aTriNum < theNumTriangles) + { + int aMaxTriangles = std::min(100, theNumTriangles - aTriNum); + for (int i = 0; i < aMaxTriangles; i++) + { + aList[i][0] = theVertices[aTriNum]; + aList[i][1] = theVertices[aTriNum + 1]; + aList[i][2] = theVertices[aTriNum + 2]; + aTriNum++; + } + DrawTrianglesTex(aList, aMaxTriangles, theColor, theDrawMode, theTexture, tx, ty, blend); + } +} + +void GLRenderer::FillPoly(const Point theVertices[], int theNumVertices, const Rect *theClipRect, const Color &theColor, + int theDrawMode, int tx, int ty) +{ + if (theNumVertices < 3) + return; + + for (int i = 1; i < theNumVertices - 1; ++i) + { + TriVertex v0, v1, v2; + + v0.x = theVertices[0].mX + tx; + v0.y = theVertices[0].mY + ty; + + v1.x = theVertices[i].mX + tx; + v1.y = theVertices[i].mY + ty; + + v2.x = theVertices[i + 1].mX + tx; + v2.y = theVertices[i + 1].mY + ty; + + DrawTriangle(v0, v1, v2, theColor, theDrawMode); + } +} + +void GLRenderer::BltTexture(Texture *theTexture, const Rect &theSrcRect, const Rect &theDestRect, const Color &theColor, + int theDrawMode) +{ +} diff --git a/PopLib/graphics/renderer/glrenderer.hpp b/PopLib/graphics/renderer/glrenderer.hpp new file mode 100644 index 00000000..1307c338 --- /dev/null +++ b/PopLib/graphics/renderer/glrenderer.hpp @@ -0,0 +1,174 @@ +#ifndef __GLRENDERER_HPP__ +#define __GLRENDERER_HPP__ + +#pragma once + +#include "graphics/renderer.hpp" +#include "glshader.hpp" +#include +#include +#include "glimage.hpp" + +#include + +namespace PopLib +{ +class AppBase; + +struct GLBlendFunc +{ + GLenum src; + GLenum dst; + bool enable_blend = true; +}; + +inline const std::unordered_map blend_mode_funcs = { + {BlendMode::BLENDMODE_BLEND, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, true}}, + {BlendMode::BLENDMODE_ADD, {GL_SRC_ALPHA, GL_ONE, true}}, + {BlendMode::BLENDMODE_MUL, {GL_DST_COLOR, GL_ZERO, true}}}; + +struct Vertex +{ + glm::vec2 mPos; + glm::vec2 mTexCoord; + glm::vec4 mColor; +}; + +struct GLTextCacheEntry +{ + GLuint textureID; + int mWidth; + int mHeight; +}; + +struct GLDrawCommand +{ + GLenum mPrimitiveType; + GLuint mTextureID; + BlendMode mBlendMode; + std::vector mVertices; + GLShader *mShader = nullptr; + const Rect *mClipRect = nullptr; +}; + +struct GLTextureData +{ + public: + GLuint mTextureID; + int mWidth; + int mHeight; + int mBitsChangedCount; + + GLTextureData(); + ~GLTextureData(); + + void ReleaseTextures(); + + void CreateTextures(GLImage *theImage); + void CheckCreateTextures(GLImage *theImage); + + int GetMemSize(); +}; + +typedef std::set GLImageSet; + +class GLRenderer : public Renderer +{ + public: + GLuint mVAO; + GLuint mVBO; + SDL_GLContext mContext; + std::vector mCommandBuffer; + GLShader *mDefaultShader; + GLImageSet mImageSet; + glm::mat4 mProjection; + std::unordered_map mTextTextureCache; + + public: + GLRenderer(AppBase *theApp); + virtual ~GLRenderer(); + + virtual void Cleanup(); + + virtual void AddImage(Image *theImage); + virtual void RemoveImage(Image *theImage); + virtual void Remove3DData(GPUImage *theImage); + + virtual GPUImage *NewGPUImage() + { + return new GLImage(this); + } + + virtual void GetOutputSize(int *outWidth, int *outHeight); + + virtual GPUImage *GetScreenImage(); + virtual void UpdateViewport(); + virtual int Init(); + + bool InitGLContext(); + bool InitBuffers(); + GLImage *SetupImage(Image *theImage); + + virtual bool Redraw(Rect *theClipRect); + virtual void SetVideoOnlyDraw(bool videoOnly); + + virtual std::unique_ptr CaptureFrameBuffer(); + + virtual bool PreDraw(); + + virtual bool CreateImageTexture(GPUImage *theImage); + virtual bool RecoverBits(GPUImage *theImage); + + GLTextCacheEntry GetOrCreateText(const std::string &theText, TTF_Font *theFont); + virtual void DrawText(int theY, int theX, const PopString &theText, const Color &theColor, TTF_Font *theFont); + void AddCommand(const GLDrawCommand &command); + void ApplyBlendMode(BlendMode mode); + + virtual void Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode, bool linearFilter = false); + + virtual void BltClipF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect *theClipRect, + const Color &theColor, int theDrawMode); + + virtual void BltMirror(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode, bool linearFilter = false); + + virtual void StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect *theClipRect, + const Color &theColor, int theDrawMode, bool fastStretch, bool mirror = false); + + virtual void BltRotated(Image *theImage, float theX, float theY, const Rect *theClipRect, const Color &theColor, + int theDrawMode, double theRot, float theRotCenterX, float theRotCenterY, + const Rect &theSrcRect); + + virtual void BltTransformed(Image *theImage, const Rect *theClipRect, const Color &theColor, int theDrawMode, + const Rect &theSrcRect, const Matrix3 &theTransform, bool linearFilter, float theX = 0, + float theY = 0, bool center = false); + + virtual void DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode); + + virtual void FillRect(const Rect &theRect, const Color &theColor, int theDrawMode); + + virtual void DrawTriangle(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, + int theDrawMode); + + virtual void DrawTriangleTex(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, + int theDrawMode, Image *theTexture, bool blend = true); + + virtual void DrawTrianglesTex(const TriVertex theVertices[][3], int theNumTriangles, const Color &theColor, + int theDrawMode, Image *theTexture, float tx = 0, float ty = 0, bool blend = true); + + virtual void DrawTrianglesTexStrip(const TriVertex theVertices[], int theNumTriangles, const Color &theColor, + int theDrawMode, Image *theTexture, float tx = 0, float ty = 0, + bool blend = true); + + virtual void FillPoly(const Point theVertices[], int theNumVertices, const Rect *theClipRect, const Color &theColor, + int theDrawMode, int tx, int ty); + + virtual void BltTexture(Texture *theTexture, const Rect &theSrcRect, const Rect &theDestRect, const Color &theColor, + int theDrawMode); +}; + +} // namespace PopLib + +#endif // __GLRENDERER_HPP__ \ No newline at end of file diff --git a/PopLib/graphics/renderer/glshader.cpp b/PopLib/graphics/renderer/glshader.cpp new file mode 100644 index 00000000..72ccfb2a --- /dev/null +++ b/PopLib/graphics/renderer/glshader.cpp @@ -0,0 +1,93 @@ +#include "glshader.hpp" +#include +#include +#include + +using namespace PopLib; + +std::string ReadShaderFile(const std::filesystem::path &path) +{ + if (!std::filesystem::exists(path)) + throw std::runtime_error("Missing shader: " + path.string()); + + std::ifstream file(path); + return std::string(std::istreambuf_iterator(file), {}); +} + +GLShader::~GLShader() +{ + if (program_ID) + glDeleteProgram(program_ID); +} + +bool GLShader::LoadFromSource(const std::string &vertexSrc, const std::string &fragmentSrc) +{ + GLuint vertex_shader = CompileShader(GL_VERTEX_SHADER, vertexSrc); + GLuint fragment_shader = CompileShader(GL_FRAGMENT_SHADER, fragmentSrc); + + if (!vertex_shader || !fragment_shader) + return false; + + program_ID = glCreateProgram(); + glAttachShader(program_ID, vertex_shader); + glAttachShader(program_ID, fragment_shader); + glLinkProgram(program_ID); + + GLint success; + glGetProgramiv(program_ID, GL_LINK_STATUS, &success); + // TODO: ADD SUCCESS CHECK + + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + return true; +} + +bool GLShader::LoadFromFiles(const std::string &vertexPath, const std::string &fragmentPath) +{ + return LoadFromSource(ReadShaderFile(vertexPath), ReadShaderFile(fragmentPath)); +} + +void GLShader::Use() const +{ + glUseProgram(program_ID); +} + +GLuint GLShader::CompileShader(GLenum type, const std::string &source) +{ + GLuint shader = glCreateShader(type); + const char *src = source.c_str(); + glShaderSource(shader, 1, &src, nullptr); + glCompileShader(shader); + + GLint success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + return shader; +} + +GLuint GLShader::GetUniformLocation(const std::string &name) const +{ + GLuint pos = glGetUniformLocation(program_ID, name.c_str()); + return pos; +} + +void GLShader::SetUniform(const std::string &name, int value) const +{ + glUniform1i(GetUniformLocation(name), value); +} +void GLShader::SetUniform(const std::string &name, float value) const +{ + glUniform1f(GetUniformLocation(name), value); +} +void GLShader::SetUniform(const std::string &name, const glm::vec2 &value) const +{ + glUniform2f(GetUniformLocation(name), value.x, value.y); +} +void GLShader::SetUniform(const std::string &name, const glm::vec4 &value) const +{ + glUniform4f(GetUniformLocation(name), value.r, value.g, value.b, value.a); +} +void GLShader::SetUniform(const std::string &name, const glm::mat4 &value) const +{ + glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, glm::value_ptr(value)); +} \ No newline at end of file diff --git a/PopLib/graphics/renderer/glshader.hpp b/PopLib/graphics/renderer/glshader.hpp new file mode 100644 index 00000000..158f6323 --- /dev/null +++ b/PopLib/graphics/renderer/glshader.hpp @@ -0,0 +1,42 @@ +#ifndef __GLSHADER_HPP__ +#define __GLSHADER_HPP__ + +#pragma once + +#include +#include +#include + +namespace PopLib +{ +class GLShader +{ + private: + GLuint program_ID = 0; + + GLuint CompileShader(GLenum type, const std::string &source); + GLuint GetUniformLocation(const std::string &name) const; + + public: + GLShader() = default; + ~GLShader(); + + bool LoadFromSource(const std::string &vertexSrc, const std::string &fragmentSrc); + bool LoadFromFiles(const std::string &vertexPath, const std::string &fragmentPath); + + void Use() const; + GLuint GetID() const + { + return program_ID; + } + + void SetUniform(const std::string &name, int value) const; + void SetUniform(const std::string &name, float value) const; + void SetUniform(const std::string &name, const glm::vec2 &value) const; + void SetUniform(const std::string &name, const glm::vec4 &value) const; + void SetUniform(const std::string &name, const glm::mat4 &value) const; +}; + +} // namespace PopLib + +#endif // __GLSHADER_HPP__ \ No newline at end of file diff --git a/PopLib/graphics/sdlimage.cpp b/PopLib/graphics/renderer/sdlimage.cpp similarity index 57% rename from PopLib/graphics/sdlimage.cpp rename to PopLib/graphics/renderer/sdlimage.cpp index 7cb9a3de..94512db5 100644 --- a/PopLib/graphics/sdlimage.cpp +++ b/PopLib/graphics/renderer/sdlimage.cpp @@ -1,27 +1,27 @@ #include "sdlimage.hpp" #include "misc/critsect.hpp" -#include "debug/debug.hpp" -#include "sdlinterface.hpp" +#include "debug/log.hpp" +#include "sdlrenderer.hpp" #include "appbase.hpp" -#include "image.hpp" +#include "../image.hpp" using namespace PopLib; -SDLImage::SDLImage() : MemoryImage(gAppBase) +SDLImage::SDLImage() : GPUImage() { - mInterface = gAppBase->mSDLInterface; - mInterface->AddSDLImage(this); + mRenderer = gAppBase->mRenderer; + mRenderer->AddImage(this); } -SDLImage::SDLImage(SDLInterface *theInterface) : MemoryImage(theInterface->mApp) +SDLImage::SDLImage(Renderer *theRenderer) : GPUImage() { - mInterface = theInterface; - mInterface->AddSDLImage(this); + mRenderer = theRenderer; + mRenderer->AddImage(this); } SDLImage::~SDLImage() { - mInterface->RemoveSDLImage(this); + mRenderer->RemoveImage(this); } void SDLImage::Create(int theWidth, int theHeight) @@ -42,39 +42,38 @@ void SDLImage::Create(int theWidth, int theHeight) bool SDLImage::PolyFill3D(const Point theVertices[], int theNumVertices, const Rect *theClipRect, const Color &theColor, int theDrawMode, int tx, int ty) { - mInterface->FillPoly(theVertices, theNumVertices, theClipRect, theColor, theDrawMode, tx, ty); + mRenderer->FillPoly(theVertices, theNumVertices, theClipRect, theColor, theDrawMode, tx, ty); return true; } void SDLImage::FillRect(const Rect &theRect, const Color &theColor, int theDrawMode) { - mInterface->FillRect(theRect, theColor, theDrawMode); + mRenderer->FillRect(theRect, theColor, theDrawMode); } -void SDLImage::DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, - const Color &theColor, int theDrawMode) +void SDLImage::DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode) { - mInterface->DrawLine(theStartX, theStartY, theEndX, theEndY, theColor, theDrawMode); + mRenderer->DrawLine(theStartX, theStartY, theEndX, theEndY, theColor, theDrawMode); } -void SDLImage::DrawLineAA(double theStartX, double theStartY, double theEndX, double theEndY, - const Color &theColor, int theDrawMode) +void SDLImage::DrawLineAA(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode) { - mInterface->DrawLine(theStartX, theStartY, theEndX, theEndY, theColor, theDrawMode); + mRenderer->DrawLine(theStartX, theStartY, theEndX, theEndY, theColor, theDrawMode); } -void SDLImage::Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, - int theDrawMode) +void SDLImage::Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, int theDrawMode) { theImage->mDrawn = true; CommitBits(); - mInterface->Blt(theImage, theX, theY, theSrcRect, theColor, theDrawMode); + mRenderer->Blt(theImage, theX, theY, theSrcRect, theColor, theDrawMode); } void SDLImage::BltF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect &theClipRect, - const Color &theColor, int theDrawMode) + const Color &theColor, int theDrawMode) { theImage->mDrawn = true; @@ -85,72 +84,70 @@ void SDLImage::BltF(Image *theImage, float theX, float theY, const Rect &theSrcR if (anIntersect.mWidth != aDestRect.mWidth || anIntersect.mHeight != aDestRect.mHeight) { if (anIntersect.mWidth != 0 && anIntersect.mHeight != 0) - mInterface->BltClipF(theImage, theX, theY, theSrcRect, &theClipRect, theColor, theDrawMode); + mRenderer->BltClipF(theImage, theX, theY, theSrcRect, &theClipRect, theColor, theDrawMode); } else - mInterface->Blt(theImage, theX, theY, theSrcRect, theColor, theDrawMode, true); + mRenderer->Blt(theImage, theX, theY, theSrcRect, theColor, theDrawMode, true); } -void SDLImage::BltRotated(Image *theImage, float theX, float theY, const Rect &theSrcRect, - const Rect &theClipRect, const Color &theColor, int theDrawMode, double theRot, - float theRotCenterX, float theRotCenterY) +void SDLImage::BltRotated(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode, double theRot, float theRotCenterX, + float theRotCenterY) { theImage->mDrawn = true; CommitBits(); - mInterface->BltRotated(theImage, theX, theY, &theClipRect, theColor, theDrawMode, theRot, theRotCenterX, - theRotCenterY, theSrcRect); + mRenderer->BltRotated(theImage, theX, theY, &theClipRect, theColor, theDrawMode, theRot, theRotCenterX, + theRotCenterY, theSrcRect); } -void SDLImage::StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, - const Rect &theClipRect, const Color &theColor, int theDrawMode, bool fastStretch) +void SDLImage::StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect &theClipRect, + const Color &theColor, int theDrawMode, bool fastStretch) { theImage->mDrawn = true; CommitBits(); - mInterface->StretchBlt(theImage, theDestRect, theSrcRect, &theClipRect, theColor, theDrawMode, fastStretch); + mRenderer->StretchBlt(theImage, theDestRect, theSrcRect, &theClipRect, theColor, theDrawMode, fastStretch); } -void SDLImage::BltMatrix(Image *theImage, float x, float y, const Matrix3 &theMatrix, - const Rect &theClipRect, const Color &theColor, int theDrawMode, - const Rect &theSrcRect, bool blend) +void SDLImage::BltMatrix(Image *theImage, float x, float y, const Matrix3 &theMatrix, const Rect &theClipRect, + const Color &theColor, int theDrawMode, const Rect &theSrcRect, bool blend) { theImage->mDrawn = true; - mInterface->BltTransformed(theImage, &theClipRect, theColor, theDrawMode, theSrcRect, theMatrix, blend, x, y, true); + mRenderer->BltTransformed(theImage, &theClipRect, theColor, theDrawMode, theSrcRect, theMatrix, blend, x, y, true); } void SDLImage::BltTrianglesTex(Image *theTexture, const TriVertex theVertices[][3], int theNumTriangles, - const Rect &theClipRect, const Color &theColor, int theDrawMode, float tx, - float ty, bool blend) + const Rect &theClipRect, const Color &theColor, int theDrawMode, float tx, float ty, + bool blend) { theTexture->mDrawn = true; - mInterface->DrawTrianglesTex(theVertices, theNumTriangles, theColor, theDrawMode, theTexture, tx, ty, blend); + mRenderer->DrawTrianglesTex(theVertices, theNumTriangles, theColor, theDrawMode, theTexture, tx, ty, blend); } void SDLImage::BltMirror(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, - int theDrawMode) + int theDrawMode) { theImage->mDrawn = true; CommitBits(); - mInterface->BltMirror(theImage, theX, theY, theSrcRect, theColor, theDrawMode); + mRenderer->BltMirror(theImage, theX, theY, theSrcRect, theColor, theDrawMode); } void SDLImage::StretchBltMirror(Image *theImage, const Rect &theDestRectOrig, const Rect &theSrcRect, - const Rect &theClipRect, const Color &theColor, int theDrawMode, - bool fastStretch) + const Rect &theClipRect, const Color &theColor, int theDrawMode, bool fastStretch) { theImage->mDrawn = true; CommitBits(); - mInterface->StretchBlt(theImage, theDestRectOrig, theSrcRect, &theClipRect, theColor, theDrawMode, fastStretch, - true); + mRenderer->StretchBlt(theImage, theDestRectOrig, theSrcRect, &theClipRect, theColor, theDrawMode, fastStretch, + true); } void SDLImage::FillScanLinesWithCoverage(Span *theSpans, int theSpanCount, const Color &theColor, int theDrawMode, @@ -202,4 +199,4 @@ void SDLImage::PurgeBits() GetBits(); MemoryImage::PurgeBits(); -} +} \ No newline at end of file diff --git a/PopLib/graphics/sdlimage.hpp b/PopLib/graphics/renderer/sdlimage.hpp similarity index 93% rename from PopLib/graphics/sdlimage.hpp rename to PopLib/graphics/renderer/sdlimage.hpp index 7a875805..1b757da6 100644 --- a/PopLib/graphics/sdlimage.hpp +++ b/PopLib/graphics/renderer/sdlimage.hpp @@ -1,24 +1,20 @@ #ifndef __SDLIMAGE_HPP__ #define __SDLIMAGE_HPP__ -#ifdef _WIN32 + #pragma once -#endif -#include "memoryimage.hpp" +#include "../gpuimage.hpp" namespace PopLib { -class SDLInterface; +class Renderer; class SysFont; -class SDLImage : public MemoryImage +class SDLImage : public GPUImage { protected: friend class SysFont; - public: - SDLInterface *mInterface; - public: virtual void FillScanLinesWithCoverage(Span *theSpans, int theSpanCount, const Color &theColor, int theDrawMode, const BYTE *theCoverage, int theCoverX, int theCoverY, int theCoverWidth, @@ -29,7 +25,7 @@ class SDLImage : public MemoryImage public: SDLImage(); - SDLImage(SDLInterface *theInterface); + SDLImage(Renderer *theRenderer); virtual ~SDLImage(); virtual void Create(int theWidth, int theHeight); diff --git a/PopLib/graphics/sdlinterface.cpp b/PopLib/graphics/renderer/sdlrenderer.cpp similarity index 62% rename from PopLib/graphics/sdlinterface.cpp rename to PopLib/graphics/renderer/sdlrenderer.cpp index b3f96f3a..f9ba01b6 100644 --- a/PopLib/graphics/sdlinterface.cpp +++ b/PopLib/graphics/renderer/sdlrenderer.cpp @@ -1,16 +1,19 @@ -#include "sdlinterface.hpp" +#include "sdlrenderer.hpp" +#include "SDL3/SDL_mouse.h" +#include "SDL3/SDL_render.h" +#include "graphics/graphics.hpp" +#include "graphics/renderer.hpp" #include "sdlimage.hpp" #include "appbase.hpp" #include "misc/autocrit.hpp" #include "misc/critsect.hpp" -#include "graphics.hpp" -#include "memoryimage.hpp" +#include "debug/log.hpp" #include "imgui/imguimanager.hpp" #include using namespace PopLib; -SDL_FPoint TransformToPoint(float x, float y, const Matrix3 &m, float aTransX = 0, float aTransY = 0) +SDL_FPoint TransformToPointSDL(float x, float y, const Matrix3 &m, float aTransX = 0, float aTransY = 0) { SDL_FPoint result; result.x = m.m00 * x + m.m01 * y + m.m02 + aTransX; @@ -18,7 +21,7 @@ SDL_FPoint TransformToPoint(float x, float y, const Matrix3 &m, float aTransX = return result; } -SDLInterface::SDLInterface(AppBase *theApp) +SDLRenderer::SDLRenderer(AppBase *theApp) { mApp = theApp; mWidth = mApp->mWidth; @@ -28,81 +31,126 @@ SDLInterface::SDLInterface(AppBase *theApp) mPresentationRect = Rect(0, 0, mWidth, mHeight); mScreenImage = nullptr; mHasInitiated = false; - mCursorX = 0; - mCursorY = 0; mIs3D = false; mMillisecondsPerFrame = 0; - mNextCursorX = 0; - mNextCursorY = 0; mRefreshRate = 0; mRenderer = nullptr; mScreenTexture = nullptr; - mWindow = nullptr; } -SDLInterface::~SDLInterface() +SDLRenderer::~SDLRenderer() { Cleanup(); SDL_Quit(); } -void SDLInterface::Cleanup() +void SDLRenderer::DrawText(int theY, int theX, const PopString &theText, const Color &theColor, TTF_Font *theFont) +{ + SDL_Color aColor = {(Uint8)theColor.mRed, (Uint8)theColor.mGreen, (Uint8)theColor.mBlue, (Uint8)theColor.mAlpha}; + SDL_Surface *textSurface = TTF_RenderText_Blended(theFont, theText.c_str(), 0, aColor); + if (!textSurface) + { + SDL_ShowSimpleMessageBox(static_cast(MsgBox_OK), "Failed to render text: ", SDL_GetError(), + mApp->mWindow); + return; + } + SDL_Texture *textTexture = SDL_CreateTextureFromSurface(mRenderer, textSurface); + SDL_FRect dstRect = {(float)theX, (float)theY, (float)textSurface->w, (float)textSurface->h}; + SDL_DestroySurface(textSurface); + + if (!textTexture) + { + SDL_ShowSimpleMessageBox(static_cast(MsgBox_OK), + "Failed to create texture from surface: ", SDL_GetError(), mApp->mWindow); + return; + } + + SDL_RenderTexture(mRenderer, textTexture, nullptr, &dstRect); + SDL_DestroyTexture(textTexture); +} + +void SDLRenderer::Cleanup() { ImageSet::iterator anItr; for (anItr = mImageSet.begin(); anItr != mImageSet.end(); ++anItr) { - MemoryImage *anImage = *anItr; - SDLTextureData *aData = (SDLTextureData *)anImage->mD3DData; + SDLImage *anImage = *anItr; + SDLTextureData *aData = (SDLTextureData *)anImage->mGPUData; delete aData; - anImage->mD3DData = nullptr; + anImage->mGPUData = nullptr; } mImageSet.clear(); SDL_DestroyRenderer(mRenderer); - SDL_DestroyWindow(mWindow); mHasInitiated = false; } -void SDLInterface::AddSDLImage(SDLImage *theSDLImage) +void SDLRenderer::AddImage(Image *theImage) { AutoCrit anAutoCrit(mCritSect); - mSDLImageSet.insert(theSDLImage); + mSDLImageSet.insert((SDLImage *)theImage); } -void SDLInterface::RemoveSDLImage(SDLImage *theSDLImage) +void SDLRenderer::RemoveImage(Image *theImage) { AutoCrit anAutoCrit(mCritSect); - SDLImageSet::iterator anItr = mSDLImageSet.find(theSDLImage); + GPUImageSet::iterator anItr = mSDLImageSet.find((SDLImage *)theImage); if (anItr != mSDLImageSet.end()) mSDLImageSet.erase(anItr); } -void SDLInterface::Remove3DData(MemoryImage *theImage) +void SDLRenderer::Remove3DData(GPUImage *theImage) { - if (theImage->mD3DData != nullptr) + if (theImage->mGPUData != nullptr) { - delete (SDLTextureData *)theImage->mD3DData; - theImage->mD3DData = nullptr; + delete (SDLTextureData *)theImage->mGPUData; + theImage->mGPUData = nullptr; AutoCrit aCrit(mCritSect); // Make images thread safe - mImageSet.erase(theImage); + mImageSet.erase(static_cast(theImage)); } } -SDLImage *SDLInterface::GetScreenImage() +void SDLRenderer::GetOutputSize(int *outWidth, int *outHeight) { - return mScreenImage; + SDL_GetCurrentRenderOutputSize(mRenderer, outWidth, outHeight); } -void SDLInterface::UpdateViewport() +std::unique_ptr SDLRenderer::CaptureFrameBuffer() +{ + SDL_Surface *surface = SDL_RenderReadPixels(mRenderer, nullptr); + if (!surface) + return nullptr; + + auto image = std::make_unique(); + image->width = surface->w; + image->height = surface->h; + image->pixels.resize(surface->w * surface->h * 4); + + uint8_t *src = static_cast(surface->pixels); + uint8_t *dst = image->pixels.data(); + + for (int i = 0; i < surface->w * surface->h * 4; i += 4) + { + dst[i + 0] = src[i + 2]; // R + dst[i + 1] = src[i + 1]; // G + dst[i + 2] = src[i + 0]; // B + dst[i + 3] = src[i + 3]; // A + } + + SDL_DestroySurface(surface); + return image; +} + +void SDLRenderer::UpdateViewport() { if (SDL_GetCurrentThreadID() != SDL_GetThreadID(nullptr)) return; int windowWidth, windowHeight; - if (!SDL_GetWindowSize(mWindow, &windowWidth, &windowHeight)) + if (!SDL_GetWindowSize(mApp->mWindow, &windowWidth, &windowHeight)) return; if (!SDL_SetRenderLogicalPresentation(mRenderer, windowWidth, windowHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX)) @@ -111,7 +159,7 @@ void SDLInterface::UpdateViewport() mPresentationRect = Rect(0, 0, windowWidth, windowHeight); } -int SDLInterface::Init(bool IsWindowed) +int SDLRenderer::Init() { int aResult = RESULT_OK; @@ -132,35 +180,19 @@ int SDLInterface::Init(bool IsWindowed) mGreenMask = (0xFFU << mGreenShift); mBlueMask = (0xFFU << mBlueShift); - aResult = InitSDLWindow(IsWindowed) ? aResult : RESULT_FAIL; + aResult = InitSDLWindow() ? aResult : RESULT_FAIL; mHasInitiated = true; return aResult; } -bool SDLInterface::InitSDLWindow(bool IsWindowed) +bool SDLRenderer::InitSDLWindow() { - int aWindowFlags = IsWindowed ? 0 : SDL_WINDOW_FULLSCREEN; - - mWindow = SDL_CreateWindow(mApp->mTitle.c_str(), mWidth, mHeight, aWindowFlags); - if (mWindow == nullptr) - { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Window Creation Failed", SDL_GetError(), nullptr); - return false; - } - - if (!IsWindowed) - SDL_SetWindowFullscreen(mWindow, true); - - UpdateWindowIcon(mApp->mTitleBarIcon); - - SDL_StartTextInput(mWindow); - return InitSDLRenderer(); } -bool SDLInterface::InitSDLRenderer() +bool SDLRenderer::InitSDLRenderer() { - mRenderer = SDL_CreateRenderer(mWindow, nullptr); + mRenderer = SDL_CreateRenderer(mApp->mWindow, nullptr); if (mRenderer == nullptr) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Renderer Creation Failed", SDL_GetError(), nullptr); @@ -175,7 +207,7 @@ bool SDLInterface::InitSDLRenderer() return false; } - const SDL_DisplayMode *aMode = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(mWindow)); + const SDL_DisplayMode *aMode = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(mApp->mWindow)); mRefreshRate = aMode->refresh_rate; if (!mRefreshRate) mRefreshRate = 60; @@ -188,11 +220,7 @@ bool SDLInterface::InitSDLRenderer() return true; } -namespace PopLib { - bool gSDLInterfacePreDrawError = false; -} - -bool SDLInterface::Redraw(Rect *theClipRect) +bool SDLRenderer::Redraw(Rect *theClipRect) { // HACK: i dont know where to put this mApp->mIGUIManager->Frame(); @@ -210,25 +238,26 @@ bool SDLInterface::Redraw(Rect *theClipRect) SDL_SetRenderClipRect(mRenderer, &clipRect); - PopLib::gSDLInterfacePreDrawError = (SDL_RenderTexture(mRenderer, mScreenTexture, nullptr, nullptr) < 0); + PopLib::gRendererPreDrawError = !SDL_RenderTexture(mRenderer, mScreenTexture, nullptr, nullptr); if (ImGui::GetDrawData() != nullptr) ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), mRenderer); SDL_RenderPresent(mRenderer); - return !PopLib::gSDLInterfacePreDrawError; + return !PopLib::gRendererPreDrawError; } /// /// Setup the mScreenImage /// /// -void SDLInterface::SetVideoOnlyDraw(bool videoOnly) +void SDLRenderer::SetVideoOnlyDraw(bool videoOnly) { if (mScreenImage) delete mScreenImage; - mScreenImage = new SDLImage(this); + mScreenImage = new SDLImage(); + mScreenImage->Create(mWidth, mHeight); // mScreenImage->SetSurface(useSecondary ? mSecondarySurface : mDrawSurface); // mScreenImage->mNoLock = mVideoOnlyDraw; // mScreenImage->mVideoMemory = mVideoOnlyDraw; @@ -237,122 +266,28 @@ void SDLInterface::SetVideoOnlyDraw(bool videoOnly) mScreenImage->SetImageMode(false, false); } -/// -/// Set the next cursor position -/// -/// -/// -void SDLInterface::SetCursorPos(int theCursorX, int theCursorY) -{ - mNextCursorX = theCursorX; - mNextCursorY = theCursorY; -} - -/// -/// Set the cursor image to a Image* or nullptr to hide the cursor -/// -/// -/// -bool SDLInterface::SetCursorImage(Image *theImage) -{ - AutoCrit anAutoCrit(mCritSect); - - if (mCursorImage != theImage) - { - mCursorImage = theImage; - if (theImage == nullptr) - return true; - SDL_Surface *aSurface = - SDL_CreateSurfaceFrom(theImage->mWidth, theImage->mHeight, SDL_PIXELFORMAT_ARGB8888, - ((MemoryImage *)mCursorImage)->GetBits(), theImage->mWidth * sizeof(ulong)); - - SDL_Cursor *aCursor = SDL_CreateColorCursor(aSurface, mCursorImage->mWidth / 2, mCursorImage->mHeight / 2); - - SDL_SetCursor(aCursor); - - SDL_DestroySurface(aSurface); - - return true; - } - - return false; -} - -bool SDLInterface::UpdateWindowIcon(Image *theImage) -{ - if (theImage != nullptr) - { - SDL_Surface *aSurface = - SDL_CreateSurfaceFrom(theImage->mWidth, theImage->mHeight, SDL_PIXELFORMAT_ARGB8888, - ((MemoryImage *)theImage)->GetBits(), theImage->mWidth * sizeof(ulong)); - - SDL_SetWindowIcon(mWindow, aSurface); - - SDL_DestroySurface(aSurface); - return true; - } - return false; -} - -void SDLInterface::SetCursor(SDL_SystemCursor theCursorType) -{ - SDL_Cursor *aCursor = SDL_CreateSystemCursor(theCursorType); - SDL_SetCursor(aCursor); - //SDL_DestroyCursor(aCursor); -} - -void SDLInterface::MakeSimpleMessageBox(const char *theTitle, const char *theMessage, SDL_MessageBoxFlags flags) -{ - SDL_ShowSimpleMessageBox(flags, theTitle, theMessage, mWindow); -} - -int SDLInterface::MakeResultMessageBox(SDL_MessageBoxData data) -{ - int buttonid; - SDL_ShowMessageBox(&data, &buttonid); - - return buttonid; -} - -void SDLInterface::PushTransform(const Matrix3 &theTransform, bool concatenate) -{ - if (mTransformStack.empty() || !concatenate) - mTransformStack.push_back(theTransform); - else - { - Matrix3 &aTrans = mTransformStack.back(); - mTransformStack.push_back(theTransform * aTrans); - } -} - -void SDLInterface::PopTransform() -{ - if (!mTransformStack.empty()) - mTransformStack.pop_back(); -} - -bool SDLInterface::PreDraw() +bool SDLRenderer::PreDraw() { return true; } -bool SDLInterface::CreateImageTexture(MemoryImage *theImage) +bool SDLRenderer::CreateImageTexture(GPUImage *theImage) { bool wantPurge = false; - if (theImage->mD3DData == nullptr) + if (theImage->mGPUData == nullptr) { - theImage->mD3DData = new SDLTextureData(mRenderer); + theImage->mGPUData = new SDLTextureData(mRenderer); // The actual purging was deferred wantPurge = theImage->mPurgeBits; AutoCrit aCrit(mCritSect); // Make images thread safe - mImageSet.insert(theImage); + mImageSet.insert(static_cast(theImage)); } - SDLTextureData *aData = static_cast(theImage->mD3DData); - aData->CheckCreateTextures(theImage); + SDLTextureData *aData = static_cast(theImage->mGPUData); + aData->CheckCreateTextures(static_cast(theImage)); if (wantPurge) theImage->PurgeBits(); @@ -360,12 +295,12 @@ bool SDLInterface::CreateImageTexture(MemoryImage *theImage) return true; } -bool SDLInterface::RecoverBits(MemoryImage *theImage) +bool SDLRenderer::RecoverBits(GPUImage *theImage) { - if (theImage->mD3DData == nullptr) + if (theImage->mGPUData == nullptr) return false; - SDLTextureData *aData = (SDLTextureData *)theImage->mD3DData; + SDLTextureData *aData = (SDLTextureData *)theImage->mGPUData; if (aData->mBitsChangedCount != theImage->mBitsChangedCount) // bits have changed since texture was created return false; @@ -385,22 +320,6 @@ bool SDLInterface::RecoverBits(MemoryImage *theImage) return true; } -SDL_BlendMode SDLInterface::ChooseBlendMode(int theBlendMode) -{ - SDL_BlendMode theSDLBlendMode; - switch (theBlendMode) - { - case Graphics::DRAWMODE_ADDITIVE: - theSDLBlendMode = SDL_BLENDMODE_ADD; - break; - default: - case Graphics::DRAWMODE_NORMAL: - theSDLBlendMode = SDL_BLENDMODE_BLEND; - break; - } - return theSDLBlendMode; -} - SDLTextureData::SDLTextureData(SDL_Renderer *theRenderer) { mWidth = 0; @@ -421,7 +340,7 @@ void SDLTextureData::ReleaseTextures() SDL_DestroyTexture(mTexture); } -void SDLTextureData::CreateTextures(MemoryImage *theImage) +void SDLTextureData::CreateTextures(SDLImage *theImage) { theImage->DeleteSWBuffers(); // we don't need the software buffers anymore theImage->CommitBits(); @@ -457,12 +376,12 @@ void SDLTextureData::CreateTextures(MemoryImage *theImage) } else { - SDL_Log("Error: Image bits are nullptr, cannot update texture."); + LOG_ERROR("Error: Image bits are nullptr, cannot update texture."); } } else { - SDL_Log("Failed to create texture: %s", SDL_GetError()); + LOG_ERROR("Failed to create texture: %s", SDL_GetError()); } } else if (mBitsChangedCount != theImage->mBitsChangedCount) @@ -474,7 +393,7 @@ void SDLTextureData::CreateTextures(MemoryImage *theImage) } else { - SDL_Log("Error: Image bits are nullptr, cannot update texture."); + LOG_ERROR("Error: Image bits are nullptr, cannot update texture."); } } @@ -483,7 +402,7 @@ void SDLTextureData::CreateTextures(MemoryImage *theImage) mBitsChangedCount = theImage->mBitsChangedCount; } -void SDLTextureData::CheckCreateTextures(MemoryImage *theImage) +void SDLTextureData::CheckCreateTextures(SDLImage *theImage) { if (mTexture != nullptr) { @@ -508,14 +427,14 @@ int SDLTextureData::GetMemSize() /// DRAWING/BLITTING FUNCTIONS ////// ///////////////////////////////////////////////////////////////// -void SDLInterface::Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, - int theDrawMode, bool linearFilter) +void SDLRenderer::Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode, bool linearFilter) { - MemoryImage *memImg = static_cast(theImage); + SDLImage *memImg = static_cast(theImage); if (!CreateImageTexture(memImg)) return; - SDLTextureData *texData = static_cast(memImg->mD3DData); + SDLTextureData *texData = static_cast(memImg->mGPUData); SDL_Texture *texture = texData->mTexture; SDL_SetRenderTarget(mRenderer, mScreenTexture); @@ -534,15 +453,15 @@ void SDLInterface::Blt(Image *theImage, int theX, int theY, const Rect &theSrcRe return; } -void SDLInterface::BltClipF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect *theClipRect, - const Color &theColor, int theDrawMode) +void SDLRenderer::BltClipF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect *theClipRect, + const Color &theColor, int theDrawMode) { - MemoryImage *aSrcMemoryImage = (MemoryImage *)theImage; + SDLImage *aSrcSDLImage = (SDLImage *)theImage; - if (!CreateImageTexture(aSrcMemoryImage)) + if (!CreateImageTexture(aSrcSDLImage)) return; - SDLTextureData *aData = (SDLTextureData *)aSrcMemoryImage->mD3DData; + SDLTextureData *aData = (SDLTextureData *)aSrcSDLImage->mGPUData; SDL_SetRenderTarget(mRenderer, mScreenTexture); @@ -551,7 +470,7 @@ void SDLInterface::BltClipF(Image *theImage, float theX, float theY, const Rect SDL_SetTextureAlphaMod(aTexture, theColor.GetAlpha()); SDL_SetTextureScaleMode(aTexture, theDrawMode ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST); - SDL_FRect destRect = {theX, theY, theSrcRect.mWidth, theSrcRect.mHeight}; + SDL_FRect destRect = {theX, theY, (float)theSrcRect.mWidth, (float)theSrcRect.mHeight}; if (theClipRect != nullptr) { SDL_Rect clipRect; @@ -559,7 +478,8 @@ void SDLInterface::BltClipF(Image *theImage, float theX, float theY, const Rect SDL_SetRenderClipRect(mRenderer, &clipRect); } - SDL_FRect srcRect = {theSrcRect.mX, theSrcRect.mY, theSrcRect.mWidth, theSrcRect.mHeight}; + SDL_FRect srcRect = {(float)theSrcRect.mX, (float)theSrcRect.mY, (float)theSrcRect.mWidth, + (float)theSrcRect.mHeight}; SDL_SetTextureBlendMode(aTexture, ChooseBlendMode(theDrawMode)); SDL_RenderTexture(mRenderer, aTexture, &srcRect, &destRect); @@ -567,15 +487,15 @@ void SDLInterface::BltClipF(Image *theImage, float theX, float theY, const Rect SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::BltMirror(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Color &theColor, - int theDrawMode, bool linearFilter) +void SDLRenderer::BltMirror(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode, bool linearFilter) { - MemoryImage *aSrcMemoryImage = (MemoryImage *)theImage; + SDLImage *aSrcSDLImage = (SDLImage *)theImage; - if (!CreateImageTexture(aSrcMemoryImage)) + if (!CreateImageTexture(aSrcSDLImage)) return; - SDLTextureData *aData = (SDLTextureData *)aSrcMemoryImage->mD3DData; + SDLTextureData *aData = (SDLTextureData *)aSrcSDLImage->mGPUData; SDL_SetRenderTarget(mRenderer, mScreenTexture); @@ -583,8 +503,9 @@ void SDLInterface::BltMirror(Image *theImage, float theX, float theY, const Rect SDL_SetTextureColorMod(aTexture, theColor.GetRed(), theColor.GetGreen(), theColor.GetBlue()); SDL_SetTextureAlphaMod(aTexture, theColor.GetAlpha()); - SDL_FRect destRect = {theX, theY, theSrcRect.mWidth, theSrcRect.mHeight}; - SDL_FRect srcRect = {theSrcRect.mX, theSrcRect.mY, theSrcRect.mWidth, theSrcRect.mHeight}; + SDL_FRect destRect = {theX, theY, (float)theSrcRect.mWidth, (float)theSrcRect.mHeight}; + SDL_FRect srcRect = {(float)theSrcRect.mX, (float)theSrcRect.mY, (float)theSrcRect.mWidth, + (float)theSrcRect.mHeight}; SDL_SetTextureBlendMode(aTexture, ChooseBlendMode(theDrawMode)); @@ -592,14 +513,14 @@ void SDLInterface::BltMirror(Image *theImage, float theX, float theY, const Rect SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect *theClipRect, - const Color &theColor, int theDrawMode, bool fastStretch, bool mirror) +void SDLRenderer::StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect *theClipRect, + const Color &theColor, int theDrawMode, bool fastStretch, bool mirror) { - MemoryImage *aSrcMemoryImage = static_cast(theImage); - if (!CreateImageTexture(aSrcMemoryImage)) + SDLImage *aSrcSDLImage = static_cast(theImage); + if (!CreateImageTexture(aSrcSDLImage)) return; - SDLTextureData *aData = static_cast(aSrcMemoryImage->mD3DData); + SDLTextureData *aData = static_cast(aSrcSDLImage->mGPUData); SDL_Texture *aTexture = aData->mTexture; SDL_SetRenderTarget(mRenderer, mScreenTexture); @@ -627,15 +548,15 @@ void SDLInterface::StretchBlt(Image *theImage, const Rect &theDestRect, const Re SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::BltRotated(Image *theImage, float theX, float theY, const Rect *theClipRect, const Color &theColor, - int theDrawMode, double theRot, float theRotCenterX, float theRotCenterY, - const Rect &theSrcRect) +void SDLRenderer::BltRotated(Image *theImage, float theX, float theY, const Rect *theClipRect, const Color &theColor, + int theDrawMode, double theRot, float theRotCenterX, float theRotCenterY, + const Rect &theSrcRect) { - MemoryImage *aSrcMemoryImage = static_cast(theImage); - if (!CreateImageTexture(aSrcMemoryImage)) + SDLImage *aSrcSDLImage = static_cast(theImage); + if (!CreateImageTexture(aSrcSDLImage)) return; - SDLTextureData *aData = static_cast(aSrcMemoryImage->mD3DData); + SDLTextureData *aData = static_cast(aSrcSDLImage->mGPUData); SDL_Texture *aTexture = aData ? aData->mTexture : nullptr; if (!aTexture) return; @@ -664,16 +585,16 @@ void SDLInterface::BltRotated(Image *theImage, float theX, float theY, const Rec SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::BltTransformed(Image *theImage, const Rect *theClipRect, const Color &theColor, int theDrawMode, - const Rect &theSrcRect, const Matrix3 &theTransform, bool linearFilter, - float theX, float theY, bool center) +void SDLRenderer::BltTransformed(Image *theImage, const Rect *theClipRect, const Color &theColor, int theDrawMode, + const Rect &theSrcRect, const Matrix3 &theTransform, bool linearFilter, float theX, + float theY, bool center) { - MemoryImage *aSrcMemoryImage = static_cast(theImage); + SDLImage *aSrcSDLImage = static_cast(theImage); - if (!CreateImageTexture(aSrcMemoryImage)) + if (!CreateImageTexture(aSrcSDLImage)) return; - SDLTextureData *aData = static_cast(aSrcMemoryImage->mD3DData); + SDLTextureData *aData = static_cast(aSrcSDLImage->mGPUData); if (!aData || !aData->mTexture) return; @@ -713,10 +634,10 @@ void SDLInterface::BltTransformed(Image *theImage, const Rect *theClipRect, cons theColor.GetAlpha() / 255.0f}; SDL_Vertex vertices[4] = { - {TransformToPoint(x1, y1, theTransform, theX, theY), aColor, {u1, v1}}, // TL - {TransformToPoint(x2, y2, theTransform, theX, theY), aColor, {u2, v1}}, // TR - {TransformToPoint(x3, y3, theTransform, theX, theY), aColor, {u1, v2}}, // BL - {TransformToPoint(x4, y4, theTransform, theX, theY), aColor, {u2, v2}} // BR + {TransformToPointSDL(x1, y1, theTransform, theX, theY), aColor, {u1, v1}}, // TL + {TransformToPointSDL(x2, y2, theTransform, theX, theY), aColor, {u2, v1}}, // TR + {TransformToPointSDL(x3, y3, theTransform, theX, theY), aColor, {u1, v2}}, // BL + {TransformToPointSDL(x4, y4, theTransform, theX, theY), aColor, {u2, v2}} // BR }; int indices[] = {0, 1, 2, 1, 3, 2}; @@ -726,8 +647,8 @@ void SDLInterface::BltTransformed(Image *theImage, const Rect *theClipRect, cons SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, - int theDrawMode) +void SDLRenderer::DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode) { if (!mRenderer) return; @@ -743,14 +664,14 @@ void SDLInterface::DrawLine(double theStartX, double theStartY, double theEndX, SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::FillRect(const Rect &theRect, const Color &theColor, int theDrawMode) +void SDLRenderer::FillRect(const Rect &theRect, const Color &theColor, int theDrawMode) { if (!mRenderer) return; SDL_SetRenderTarget(mRenderer, mScreenTexture); - SDL_FRect theSDLRect = {theRect.mX, theRect.mY, theRect.mWidth, theRect.mHeight}; + SDL_FRect theSDLRect = {(float)theRect.mX, (float)theRect.mY, (float)theRect.mWidth, (float)theRect.mHeight}; SDL_SetRenderDrawColor(mRenderer, theColor.mRed, theColor.mGreen, theColor.mBlue, theColor.mAlpha); @@ -761,12 +682,13 @@ void SDLInterface::FillRect(const Rect &theRect, const Color &theColor, int theD SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::DrawTriangle(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, - int theDrawMode) +void SDLRenderer::DrawTriangle(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, + int theDrawMode) { SDL_SetRenderTarget(mRenderer, mScreenTexture); - SDL_FColor aColor = {theColor.GetRed(), theColor.GetGreen(), theColor.GetBlue(), theColor.GetAlpha()}; + SDL_FColor aColor = {(float)theColor.GetRed(), (float)theColor.GetGreen(), (float)theColor.GetBlue(), + (float)theColor.GetAlpha()}; int indices[] = {0, 1, 2}; @@ -778,15 +700,15 @@ void SDLInterface::DrawTriangle(const TriVertex &p1, const TriVertex &p2, const SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::DrawTriangleTex(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, - int theDrawMode, Image *theTexture, bool blend) +void SDLRenderer::DrawTriangleTex(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, + int theDrawMode, Image *theTexture, bool blend) { - MemoryImage *aSrcMemoryImage = (MemoryImage *)theTexture; + SDLImage *aSrcSDLImage = (SDLImage *)theTexture; - if (!CreateImageTexture(aSrcMemoryImage)) + if (!CreateImageTexture(aSrcSDLImage)) return; - SDLTextureData *aData = (SDLTextureData *)aSrcMemoryImage->mD3DData; + SDLTextureData *aData = (SDLTextureData *)aSrcSDLImage->mGPUData; SDL_SetRenderTarget(mRenderer, mScreenTexture); @@ -795,7 +717,8 @@ void SDLInterface::DrawTriangleTex(const TriVertex &p1, const TriVertex &p2, con SDL_SetTextureAlphaMod(aTexture, theColor.GetAlpha()); SDL_SetTextureBlendMode(aTexture, ChooseBlendMode(theDrawMode)); - SDL_FColor aColor = {theColor.GetRed(), theColor.GetGreen(), theColor.GetBlue(), theColor.GetAlpha()}; + SDL_FColor aColor = {(float)theColor.GetRed(), (float)theColor.GetGreen(), (float)theColor.GetBlue(), + (float)theColor.GetAlpha()}; int indices[] = {0, 1, 2}; @@ -807,15 +730,15 @@ void SDLInterface::DrawTriangleTex(const TriVertex &p1, const TriVertex &p2, con SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::DrawTrianglesTex(const TriVertex theVertices[][3], int theNumTriangles, const Color &theColor, - int theDrawMode, Image *theTexture, float tx, float ty, bool blend) +void SDLRenderer::DrawTrianglesTex(const TriVertex theVertices[][3], int theNumTriangles, const Color &theColor, + int theDrawMode, Image *theTexture, float tx, float ty, bool blend) { - MemoryImage *aSrcMemoryImage = (MemoryImage *)theTexture; + SDLImage *aSrcSDLImage = (SDLImage *)theTexture; - if (!CreateImageTexture(aSrcMemoryImage)) + if (!CreateImageTexture(aSrcSDLImage)) return; - SDLTextureData *aData = (SDLTextureData *)aSrcMemoryImage->mD3DData; + SDLTextureData *aData = (SDLTextureData *)aSrcSDLImage->mGPUData; SDL_SetRenderTarget(mRenderer, mScreenTexture); @@ -865,16 +788,16 @@ void SDLInterface::DrawTrianglesTex(const TriVertex theVertices[][3], int theNum SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::DrawTrianglesTexStrip(const TriVertex theVertices[], int theNumTriangles, const Color &theColor, - int theDrawMode, Image *theTexture, float tx, float ty, bool blend) +void SDLRenderer::DrawTrianglesTexStrip(const TriVertex theVertices[], int theNumTriangles, const Color &theColor, + int theDrawMode, Image *theTexture, float tx, float ty, bool blend) { if (theNumTriangles < 3) return; - MemoryImage *aSrcMemoryImage = (MemoryImage *)theTexture; - if (!CreateImageTexture(aSrcMemoryImage)) + SDLImage *aSrcSDLImage = (SDLImage *)theTexture; + if (!CreateImageTexture(aSrcSDLImage)) return; - SDLTextureData *aData = (SDLTextureData *)aSrcMemoryImage->mD3DData; + SDLTextureData *aData = (SDLTextureData *)aSrcSDLImage->mGPUData; SDL_Texture *aTexture = aData->mTexture; std::vector positions; @@ -889,7 +812,8 @@ void SDLInterface::DrawTrianglesTexStrip(const TriVertex theVertices[], int theN uvs.push_back(v.u); uvs.push_back(v.v); - SDL_FColor color = {(v.color >> 16) & 0xFF, (v.color >> 8) & 0xFF, v.color & 0xFF, (v.color >> 24) & 0xFF}; + SDL_FColor color = {(float)((v.color >> 16) & 0xFF), (float)((v.color >> 8) & 0xFF), (float)(v.color & 0xFF), + (float)((v.color >> 24) & 0xFF)}; colors.push_back(color); } @@ -904,16 +828,16 @@ void SDLInterface::DrawTrianglesTexStrip(const TriVertex theVertices[], int theN SDL_SetRenderTarget(mRenderer, nullptr); } -void SDLInterface::FillPoly(const Point theVertices[], int theNumVertices, const Rect *theClipRect, - const Color &theColor, int theDrawMode, int tx, int ty) +void SDLRenderer::FillPoly(const Point theVertices[], int theNumVertices, const Rect *theClipRect, + const Color &theColor, int theDrawMode, int tx, int ty) { if (theNumVertices < 3) if (theNumVertices == 2) { - DrawLine(theVertices[0].mX + tx, theVertices[0].mY + ty, theVertices[1].mX + tx, theVertices[1].mY + ty, theColor, theDrawMode); + DrawLine(theVertices[0].mX + tx, theVertices[0].mY + ty, theVertices[1].mX + tx, theVertices[1].mY + ty, + theColor, theDrawMode); return; } - if (theClipRect != nullptr) { @@ -923,8 +847,8 @@ void SDLInterface::FillPoly(const Point theVertices[], int theNumVertices, const } for (int i = 0; i < theNumVertices - 1; i++) - DrawLine(theVertices[i].mX + tx, theVertices[i].mY + ty, theVertices[i + 1].mX + tx, theVertices[i + 1].mY + ty, theColor, - theDrawMode); + DrawLine(theVertices[i].mX + tx, theVertices[i].mY + ty, theVertices[i + 1].mX + tx, theVertices[i + 1].mY + ty, + theColor, theDrawMode); DrawLine(theVertices[theNumVertices - 1].mX + tx, theVertices[theNumVertices - 1].mY + ty, theVertices[0].mX + tx, theVertices[0].mY + ty, theColor, theDrawMode); @@ -932,18 +856,35 @@ void SDLInterface::FillPoly(const Point theVertices[], int theNumVertices, const SDL_SetRenderClipRect(mRenderer, nullptr); } -void SDLInterface::BltTexture(SDL_Texture *theTexture, const SDL_FRect &theSrcRect, const SDL_FRect &theDestRect, - const Color &theColor, int theDrawMode) +void SDLRenderer::BltTexture(Texture *theTexture, const Rect &theSrcRect, const Rect &theDestRect, + const Color &theColor, int theDrawMode) { + SDLTexture *sdlTex = dynamic_cast(theTexture); + if (!sdlTex || !sdlTex->GetSDLTexture()) + return; + SDL_SetRenderTarget(mRenderer, mScreenTexture); - SDL_SetTextureColorMod(theTexture, theColor.GetRed(), theColor.GetGreen(), theColor.GetBlue()); - SDL_SetTextureAlphaMod(theTexture, theColor.GetAlpha()); + SDL_SetTextureColorMod(sdlTex->GetSDLTexture(), theColor.GetRed(), theColor.GetGreen(), theColor.GetBlue()); + SDL_SetTextureAlphaMod(sdlTex->GetSDLTexture(), theColor.GetAlpha()); + SDL_SetTextureBlendMode(sdlTex->GetSDLTexture(), ChooseBlendMode(theDrawMode)); - SDL_SetTextureBlendMode(theTexture, ChooseBlendMode(theDrawMode)); - SDL_RenderTexture(mRenderer, theTexture, &theSrcRect, &theDestRect); + SDL_FRect srcRect = { + static_cast(theSrcRect.mX), + static_cast(theSrcRect.mY), + static_cast(theSrcRect.mWidth), + static_cast(theSrcRect.mHeight), + }; - SDL_SetTextureBlendMode(theTexture, SDL_BLENDMODE_NONE); + SDL_FRect destRect = { + static_cast(theDestRect.mX), + static_cast(theDestRect.mY), + static_cast(theDestRect.mWidth), + static_cast(theDestRect.mHeight), + }; + SDL_RenderTexture(mRenderer, sdlTex->GetSDLTexture(), &srcRect, &destRect); + + SDL_SetTextureBlendMode(sdlTex->GetSDLTexture(), SDL_BLENDMODE_NONE); SDL_SetRenderTarget(mRenderer, nullptr); -} \ No newline at end of file +} diff --git a/PopLib/graphics/renderer/sdlrenderer.hpp b/PopLib/graphics/renderer/sdlrenderer.hpp new file mode 100644 index 00000000..44056576 --- /dev/null +++ b/PopLib/graphics/renderer/sdlrenderer.hpp @@ -0,0 +1,151 @@ +#ifndef __SDLRENDERER_HPP__ +#define __SDLRENDERER_HPP__ + +#pragma once + +#include "common.hpp" +#include "misc/critsect.hpp" +#include "../renderer.hpp" +#include "math/rect.hpp" +#include "math/ratio.hpp" +#include "math/matrix.hpp" +#include "sdlimage.hpp" + +#include + +namespace PopLib +{ + +class AppBase; +class SDLImage; +class Matrix3; +class TriVertex; + +typedef std::set SDLImageSet; +typedef std::set ImageSet; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +struct SDLTextureData +{ + public: + SDL_Texture *mTexture; + int mWidth; + int mHeight; + int mBitsChangedCount; + SDL_Renderer *mRenderer; + + SDLTextureData(SDL_Renderer *theRenderer); + ~SDLTextureData(); + + void ReleaseTextures(); + + void CreateTextures(SDLImage *theImage); + void CheckCreateTextures(SDLImage *theImage); + + int GetMemSize(); +}; + +class SDLTexture : public Texture +{ + public: + SDLTexture(SDL_Texture *texture) : mTexture(texture) + { + } + ~SDLTexture() override + { + if (mTexture) + SDL_DestroyTexture(mTexture); + } + + SDL_Texture *GetSDLTexture() const + { + return mTexture; + } + + private: + SDL_Texture *mTexture; +}; + +class SDLRenderer : public Renderer +{ + public: + ImageSet mImageSet; + GPUImageSet mSDLImageSet; + + public: + SDL_Renderer *mRenderer; + SDL_Texture *mScreenTexture; + + public: + void AddImage(Image *theImage); + void RemoveImage(Image *theImage); + void Remove3DData(GPUImage *theImage); // for 3d texture cleanup + + public: + SDLRenderer(AppBase *theApp); + virtual ~SDLRenderer(); + virtual void Cleanup(); + + virtual GPUImage *NewGPUImage() + { + return new SDLImage(); + } + + virtual void UpdateViewport(); + virtual int Init(); + + bool InitSDLWindow(); + bool InitSDLRenderer(); + + virtual void GetOutputSize(int *outWidth, int *outHeight); + + virtual std::unique_ptr CaptureFrameBuffer(); + + virtual bool Redraw(Rect *theClipRect); + virtual void SetVideoOnlyDraw(bool videoOnly); + + virtual void DrawText(int theY, int theX, const PopString &theText, const Color &theColor, TTF_Font *theFont); + + public: + virtual bool PreDraw(); + + virtual bool CreateImageTexture(GPUImage *theImage); + virtual bool RecoverBits(GPUImage *theImage); + + // Draw Funcs + virtual void Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode, bool linearFilter = false); + virtual void BltClipF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect *theClipRect, + const Color &theColor, int theDrawMode); + virtual void BltMirror(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Color &theColor, + int theDrawMode, bool linearFilter = false); + virtual void StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect *theClipRect, + const Color &theColor, int theDrawMode, bool fastStretch, bool mirror = false); + virtual void BltRotated(Image *theImage, float theX, float theY, const Rect *theClipRect, const Color &theColor, + int theDrawMode, double theRot, float theRotCenterX, float theRotCenterY, + const Rect &theSrcRect); + virtual void BltTransformed(Image *theImage, const Rect *theClipRect, const Color &theColor, int theDrawMode, + const Rect &theSrcRect, const Matrix3 &theTransform, bool linearFilter, float theX = 0, + float theY = 0, bool center = false); + virtual void DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, + int theDrawMode); + virtual void FillRect(const Rect &theRect, const Color &theColor, int theDrawMode); + virtual void DrawTriangle(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, + int theDrawMode); + virtual void DrawTriangleTex(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, + int theDrawMode, Image *theTexture, bool blend = true); + virtual void DrawTrianglesTex(const TriVertex theVertices[][3], int theNumTriangles, const Color &theColor, + int theDrawMode, Image *theTexture, float tx = 0, float ty = 0, bool blend = true); + virtual void DrawTrianglesTexStrip(const TriVertex theVertices[], int theNumTriangles, const Color &theColor, + int theDrawMode, Image *theTexture, float tx = 0, float ty = 0, + bool blend = true); + virtual void FillPoly(const Point theVertices[], int theNumVertices, const Rect *theClipRect, const Color &theColor, + int theDrawMode, int tx, int ty); + + virtual void BltTexture(Texture *theTexture, const Rect &theSrcRect, const Rect &theDestRect, const Color &theColor, + int theDrawMode); +}; +} // namespace PopLib + +#endif // __SDLRENDERER_HPP__ \ No newline at end of file diff --git a/PopLib/graphics/sdlinterface.hpp b/PopLib/graphics/sdlinterface.hpp deleted file mode 100644 index fd3fdbaa..00000000 --- a/PopLib/graphics/sdlinterface.hpp +++ /dev/null @@ -1,180 +0,0 @@ -#ifndef __SDLINTERFACE_HPP__ -#define __SDLINTERFACE_HPP__ -#ifdef _WIN32 -#pragma once -#endif - -#include "common.hpp" -#include "memoryimage.hpp" -#include "misc/critsect.hpp" -#include "nativedisplay.hpp" -#include "math/rect.hpp" -#include "math/ratio.hpp" -#include "math/matrix.hpp" - -#include - -namespace PopLib -{ - -class AppBase; -class SDLImage; -class Matrix3; -class TriVertex; - -typedef std::set SDLImageSet; -typedef std::set ImageSet; -typedef std::list TransformStack; - -enum SDLImageFlags -{ - SDLImageFlag_NearestFiltering = 0x0001, // Uses nearest filtering for the texture - // 0x0002 - // 0x0004 - // 0x0008 -}; - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -struct SDLTextureData -{ - public: - SDL_Texture *mTexture; - int mWidth; - int mHeight; - int mBitsChangedCount; - SDL_Renderer *mRenderer; - - SDLTextureData(SDL_Renderer *theRenderer); - ~SDLTextureData(); - - void ReleaseTextures(); - - void CreateTextures(MemoryImage *theImage); - void CheckCreateTextures(MemoryImage *theImage); - - int GetMemSize(); -}; - -class SDLInterface : public NativeDisplay -{ - public: - enum - { - RESULT_OK = 0, - RESULT_FAIL = 1, - RESULT_DD_CREATE_FAIL = 2, - RESULT_SURFACE_FAIL = 3, - RESULT_EXCLUSIVE_FAIL = 4, - RESULT_DISPCHANGE_FAIL = 5, - RESULT_INVALID_COLORDEPTH = 6, - RESULT_3D_FAIL = 7 - }; - - AppBase *mApp; - CritSect mCritSect; - int mWidth; - int mHeight; - int mDisplayWidth; - int mDisplayHeight; - int mVideoOnlyDraw; - - bool mIs3D; - bool mHasInitiated; - - Rect mPresentationRect; - int mRefreshRate; - int mMillisecondsPerFrame; - - Image *mCursorImage; - SDLImage *mScreenImage; - - int mNextCursorX; - int mNextCursorY; - int mCursorX; - int mCursorY; - - ImageSet mImageSet; - SDLImageSet mSDLImageSet; - TransformStack mTransformStack; - - public: - SDL_Renderer *mRenderer; - SDL_Window *mWindow; - SDL_Texture *mScreenTexture; - - public: - void AddSDLImage(SDLImage *theSDLImage); - void RemoveSDLImage(SDLImage *theSDLImage); - void Remove3DData(MemoryImage *theImage); // for 3d texture cleanup - - public: - SDLInterface(AppBase *theApp); - virtual ~SDLInterface(); - void Cleanup(); - - SDLImage *GetScreenImage(); - void UpdateViewport(); - int Init(bool IsWindowed); - - bool InitSDLWindow(bool IsWindowed); - bool InitSDLRenderer(); - - bool Redraw(Rect *theClipRect); - void SetVideoOnlyDraw(bool videoOnly); - - void SetCursorPos(int theCursorX, int theCursorY); - - bool SetCursorImage(Image *theImage); - bool UpdateWindowIcon(Image *theImage); - - void SetCursor(SDL_SystemCursor theCursorType); - - void MakeSimpleMessageBox(const char *theTitle, const char *theMessage, SDL_MessageBoxFlags flags); - int MakeResultMessageBox(SDL_MessageBoxData data); - - public: - void PushTransform(const Matrix3 &theTransform, bool concatenate = true); - void PopTransform(); - - bool PreDraw(); - - bool CreateImageTexture(MemoryImage *theImage); - bool RecoverBits(MemoryImage *theImage); - - SDL_BlendMode ChooseBlendMode(int theBlendMode); - - // Draw Funcs - void Blt(Image *theImage, int theX, int theY, const Rect &theSrcRect, const Color &theColor, int theDrawMode, - bool linearFilter = false); - void BltClipF(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Rect *theClipRect, - const Color &theColor, int theDrawMode); - void BltMirror(Image *theImage, float theX, float theY, const Rect &theSrcRect, const Color &theColor, - int theDrawMode, bool linearFilter = false); - void StretchBlt(Image *theImage, const Rect &theDestRect, const Rect &theSrcRect, const Rect *theClipRect, - const Color &theColor, int theDrawMode, bool fastStretch, bool mirror = false); - void BltRotated(Image *theImage, float theX, float theY, const Rect *theClipRect, const Color &theColor, - int theDrawMode, double theRot, float theRotCenterX, float theRotCenterY, const Rect &theSrcRect); - void BltTransformed(Image *theImage, const Rect *theClipRect, const Color &theColor, int theDrawMode, - const Rect &theSrcRect, const Matrix3 &theTransform, bool linearFilter, float theX = 0, - float theY = 0, bool center = false); - void DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color &theColor, - int theDrawMode); - void FillRect(const Rect &theRect, const Color &theColor, int theDrawMode); - void DrawTriangle(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, - int theDrawMode); - void DrawTriangleTex(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, - int theDrawMode, Image *theTexture, bool blend = true); - void DrawTrianglesTex(const TriVertex theVertices[][3], int theNumTriangles, const Color &theColor, int theDrawMode, - Image *theTexture, float tx = 0, float ty = 0, bool blend = true); - void DrawTrianglesTexStrip(const TriVertex theVertices[], int theNumTriangles, const Color &theColor, - int theDrawMode, Image *theTexture, float tx = 0, float ty = 0, bool blend = true); - void FillPoly(const Point theVertices[], int theNumVertices, const Rect *theClipRect, const Color &theColor, - int theDrawMode, int tx, int ty); - - void BltTexture(SDL_Texture *theTexture, const SDL_FRect &theSrcRect, const SDL_FRect &theDestRect, - const Color &theColor, int theDrawMode); -}; -} // namespace PopLib - -#endif // __SDLINTERFACE_HPP__ \ No newline at end of file diff --git a/PopLib/graphics/sharedimage.cpp b/PopLib/graphics/sharedimage.cpp index 97d9db32..3ba34a10 100644 --- a/PopLib/graphics/sharedimage.cpp +++ b/PopLib/graphics/sharedimage.cpp @@ -1,5 +1,5 @@ #include "sharedimage.hpp" -#include "sdlimage.hpp" +#include "gpuimage.hpp" #include "appbase.hpp" using namespace PopLib; @@ -93,13 +93,13 @@ SharedImageRef::operator MemoryImage *() if (mUnsharedImage != nullptr) return mUnsharedImage; else - return (SDLImage *)*this; + return (GPUImage *)*this; } -SharedImageRef::operator SDLImage *() +SharedImageRef::operator GPUImage *() { if (mSharedImage != nullptr) return mSharedImage->mImage; else return nullptr; -} +} \ No newline at end of file diff --git a/PopLib/graphics/sharedimage.hpp b/PopLib/graphics/sharedimage.hpp index 6b8fd37c..c5b478c3 100644 --- a/PopLib/graphics/sharedimage.hpp +++ b/PopLib/graphics/sharedimage.hpp @@ -1,8 +1,7 @@ #ifndef __SHAREDIMAGE_HPP__ #define __SHAREDIMAGE_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" @@ -10,13 +9,13 @@ namespace PopLib { class Image; -class SDLImage; +class GPUImage; class MemoryImage; class SharedImage { public: - SDLImage *mImage; + GPUImage *mImage; int mRefCount; SharedImage(); @@ -45,7 +44,7 @@ class SharedImageRef MemoryImage *operator->(); operator Image *(); operator MemoryImage *(); - operator SDLImage *(); + operator GPUImage *(); }; } // namespace PopLib diff --git a/PopLib/graphics/sysfont.cpp b/PopLib/graphics/sysfont.cpp index 7229bbc0..aee97865 100644 --- a/PopLib/graphics/sysfont.cpp +++ b/PopLib/graphics/sysfont.cpp @@ -1,10 +1,8 @@ #include "sysfont.hpp" -#include "sdlimage.hpp" #include "appbase.hpp" #include "graphics.hpp" #include "imagefont.hpp" -#include "memoryimage.hpp" -#include "sdlinterface.hpp" +#include "renderer.hpp" #include "widget/widgetmanager.hpp" #include @@ -29,15 +27,14 @@ SysFont::SysFont(AppBase *theApp, const unsigned char aData[], size_t aDataSize, SDL_IOStream *io = SDL_IOFromConstMem((void *)aData, aDataSize); if (!io) { - mApp->mSDLInterface->MakeSimpleMessageBox("Failed to create SDL_IOStream", SDL_GetError(), - SDL_MESSAGEBOX_ERROR); + SDL_ShowSimpleMessageBox(static_cast(MsgBox_OK), "Failed to create SDL_IOStream", SDL_GetError(), mApp->mWindow); return; } mTTFFont = TTF_OpenFontIO(io, false, thePointSize); if (!mTTFFont) { - mApp->mSDLInterface->MakeSimpleMessageBox("Error", SDL_GetError(), SDL_MESSAGEBOX_ERROR); + SDL_ShowSimpleMessageBox(static_cast(MsgBox_OK), "Error", SDL_GetError(), mApp->mWindow); } TTF_SetFontStyle(mTTFFont, (bold ? TTF_STYLE_BOLD : 0) | (italics ? TTF_STYLE_ITALIC : 0) | @@ -58,7 +55,7 @@ void SysFont::Init(AppBase *theApp, const std::string &theFace, int thePointSize mTTFFont = TTF_OpenFont(theFace.c_str(), thePointSize); if (!mTTFFont) { - mApp->mSDLInterface->MakeSimpleMessageBox("Error", SDL_GetError(), SDL_MESSAGEBOX_ERROR); + SDL_ShowSimpleMessageBox(static_cast(MsgBox_OK), "Error", SDL_GetError(), mApp->mWindow); } TTF_SetFontStyle(mTTFFont, (bold ? TTF_STYLE_BOLD : 0) | (italics ? TTF_STYLE_ITALIC : 0) | @@ -192,36 +189,12 @@ int SysFont::StringWidth(const PopString &theString) void SysFont::DrawString(Graphics *g, int theX, int theY, const PopString &theString, const Color &theColor, const Rect &theClipRect) { - SDL_Renderer *renderer = mApp->mSDLInterface->mRenderer; - SDL_Color aColor = {(Uint8)theColor.mRed, (Uint8)theColor.mGreen, (Uint8)theColor.mBlue, (Uint8)theColor.mAlpha}; - SDL_Surface *textSurface = - TTF_RenderText_Blended(mTTFFont, theString.c_str(), 0, aColor); - if (!textSurface) - { - mApp->mSDLInterface->MakeSimpleMessageBox("Failed to render text: ", SDL_GetError(), SDL_MESSAGEBOX_ERROR); - return; - } - - SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface); - SDL_FRect dstRect = {(float)theX, (float)theY - (float)mAscent, (float)textSurface->w, (float)textSurface->h}; - SDL_FRect srcRect = {0, 0, dstRect.w, dstRect.h}; - SDL_DestroySurface(textSurface); - - if (!textTexture) - { - mApp->mSDLInterface->MakeSimpleMessageBox("Failed to create texture from surface: ", SDL_GetError(), - SDL_MESSAGEBOX_ERROR); - } - - if (mDrawShadow) - { - SDL_FRect shadowRect = {(float)theX + 1.f, (float)theY - (float)mAscent + 1.f, dstRect.w, dstRect.h}; - mApp->mSDLInterface->BltTexture(textTexture, srcRect, shadowRect, Color(0, 0, 0), g->GetDrawMode()); - } + Renderer *interface = mApp->mRenderer; - mApp->mSDLInterface->BltTexture(textTexture, srcRect, dstRect, theColor, g->GetDrawMode()); + if (mDrawShadow) + interface->DrawText(theX + 1, theY + 1 - mAscent, theString, Color(0, 0, 0, theColor.mAlpha), mTTFFont); - SDL_DestroyTexture(textTexture); + interface->DrawText(theX, theY - mAscent, theString, theColor, mTTFFont); } Font *SysFont::Duplicate() diff --git a/PopLib/graphics/sysfont.hpp b/PopLib/graphics/sysfont.hpp index 5276d910..af0c795f 100644 --- a/PopLib/graphics/sysfont.hpp +++ b/PopLib/graphics/sysfont.hpp @@ -1,8 +1,7 @@ #ifndef __SYSFONT_HPP__ #define __SYSFONT_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "font.hpp" #include "common.hpp" diff --git a/PopLib/imagelib/imagelib.cpp b/PopLib/imagelib/imagelib.cpp index 9244177e..61dbf8de 100644 --- a/PopLib/imagelib/imagelib.cpp +++ b/PopLib/imagelib/imagelib.cpp @@ -55,7 +55,7 @@ Image *GetImageSTB(const std::string &theFileName) p_fclose(fp); int width, height, num_channels; - unsigned char *stb_image = stbi_load_from_memory(data.data(), fileSize, &width, &height, &num_channels, NULL); + unsigned char *stb_image = stbi_load_from_memory(data.data(), fileSize, &width, &height, &num_channels, 0); ulong *aBits = new ulong[width * height]; for (int i = 0; i < width * height; ++i) @@ -76,7 +76,7 @@ Image *GetImageSTB(const std::string &theFileName) anImage->mBits = aBits; anImage->mNumChannels = num_channels; - delete stb_image; + stbi_image_free(stb_image); return anImage; } @@ -572,7 +572,7 @@ Image *GetGIFImage(const std::string &theFileName) delete prefix; delete packet; - delete colortable; + delete[] colortable; // if (y < image->rows) // failed = true; diff --git a/PopLib/imagelib/imagelib.hpp b/PopLib/imagelib/imagelib.hpp index efca865e..03363630 100644 --- a/PopLib/imagelib/imagelib.hpp +++ b/PopLib/imagelib/imagelib.hpp @@ -1,8 +1,7 @@ #ifndef __IMAGELIB_HPP__ #define __IMAGELIB_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include diff --git a/PopLib/imgui/core/imconfig.h b/PopLib/imgui/core/imconfig.h index a1e29e84..4dab1b60 100644 --- a/PopLib/imgui/core/imconfig.h +++ b/PopLib/imgui/core/imconfig.h @@ -129,6 +129,10 @@ //#define IM_DEBUG_BREAK IM_ASSERT(0) //#define IM_DEBUG_BREAK __debugbreak() +//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set. +// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use) +//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + //---- Debug Tools: Enable slower asserts //#define IMGUI_DEBUG_PARANOID diff --git a/PopLib/imgui/core/imgui.cpp b/PopLib/imgui/core/imgui.cpp index 30263bac..6c1e688e 100644 --- a/PopLib/imgui/core/imgui.cpp +++ b/PopLib/imgui/core/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.0 WIP +// dear imgui, v1.92.2 WIP // (main code and documentation) // Help: @@ -21,9 +21,10 @@ // - Issues & support ........... https://github.com/ocornut/imgui/issues // - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) -// For first-time users having issues compiling/linking/running/loading fonts: +// For first-time users having issues compiling/linking/running: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. // Everything else should be asked in 'Issues'! We are building a database of cross-linked knowledge there. +// Since 1.92, we encourage font loading question to also be posted in 'Issues'. // Copyright (c) 2014-2025 Omar Cornut // Developed by Omar Cornut and every direct or indirect contributors to the GitHub. @@ -52,7 +53,7 @@ DOCUMENTATION - HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI - GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE - HOW A SIMPLE APPLICATION MAY LOOK LIKE - - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE + - USING CUSTOM BACKEND / CUSTOM ENGINE - API BREAKING CHANGES (read me when you update!) - FREQUENTLY ASKED QUESTIONS (FAQ) - Read all answers online: https://www.dearimgui.com/faq, or in docs/FAQ.md (with a Markdown viewer) @@ -77,6 +78,7 @@ CODE // [SECTION] RENDER HELPERS // [SECTION] INITIALIZATION, SHUTDOWN // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) +// [SECTION] FONTS, TEXTURES // [SECTION] ID STACK // [SECTION] INPUTS // [SECTION] ERROR CHECKING, STATE RECOVERY @@ -272,7 +274,8 @@ CODE HOW A SIMPLE APPLICATION MAY LOOK LIKE -------------------------------------- - EXHIBIT 1: USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder). + + USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder). The sub-folders in examples/ contain examples applications following this structure. // Application init: create a dear imgui context, setup some options, load fonts @@ -297,7 +300,7 @@ CODE // Any application code here ImGui::Text("Hello, world!"); - // Render dear imgui into screen + // Render dear imgui into framebuffer ImGui::Render(); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); g_pSwapChain->Present(1, 0); @@ -308,26 +311,36 @@ CODE ImGui_ImplWin32_Shutdown(); ImGui::DestroyContext(); - EXHIBIT 2: IMPLEMENTING CUSTOM BACKEND / CUSTOM ENGINE + To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application, + you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! + Please read the FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" about this. - // Application init: create a dear imgui context, setup some options, load fonts + +USING CUSTOM BACKEND / CUSTOM ENGINE +------------------------------------ + +IMPLEMENTING YOUR PLATFORM BACKEND: + -> see https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md for basic instructions. + -> the Platform backends in impl_impl_XXX.cpp files contain many implementations. + +IMPLEMENTING YOUR RenderDrawData() function: + -> see https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md + -> the Renderer Backends in impl_impl_XXX.cpp files contain many implementations of a ImGui_ImplXXXX_RenderDrawData() function. + +IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: + -> see https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md + -> the Renderer Backends in impl_impl_XXX.cpp files contain many implementations of a ImGui_ImplXXXX_UpdateTexture() function. + + Basic application/backend skeleton: + + // Application init: create a Dear ImGui context, setup some options, load fonts ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); - // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls. - // TODO: Fill optional fields of the io structure later. - // TODO: Load TTF/OTF fonts if you don't want to use the default font. + // TODO: set io.ConfigXXX values, e.g. + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable keyboard controls - // Build and load the texture atlas into a texture - // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer) - int width, height; - unsigned char* pixels = nullptr; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - // At this point you've got the texture data and you need to upload that to your graphic system: - // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'. - // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about ImTextureID. - MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32) - io.Fonts->SetTexID((void*)texture); + // TODO: Load TTF/OTF fonts if you don't want to use the default font. + io.Fonts->AddFontFromFileTTF("NotoSans.ttf"); // Application main loop while (true) @@ -350,77 +363,25 @@ CODE MyGameUpdate(); // may use any Dear ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); MyGameRender(); // may use any Dear ImGui functions as well! - // Render dear imgui, swap buffers + // End the dear imgui frame // (You want to try calling EndFrame/Render as late as you can, to be able to use Dear ImGui in your own game rendering code) - ImGui::EndFrame(); + ImGui::EndFrame(); // this is automatically called by Render(), but available ImGui::Render(); + + // Update textures ImDrawData* draw_data = ImGui::GetDrawData(); - MyImGuiRenderFunction(draw_data); + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + MyImGuiBackend_UpdateTexture(tex); + + // Render dear imgui contents, swap buffers + MyImGuiBackend_RenderDrawData(draw_data); SwapBuffers(); } // Shutdown ImGui::DestroyContext(); - To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application, - you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! - Please read the FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" about this. - - - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE - --------------------------------------------- - The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function. - - void MyImGuiRenderFunction(ImDrawData* draw_data) - { - // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled - // TODO: Setup texture sampling state: sample with bilinear filtering (NOT point/nearest filtering). Use 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering. - // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize - // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize - // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color. - ImVec2 clip_off = draw_data->DisplayPos; - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by Dear ImGui - const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by Dear ImGui - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback) - { - pcmd->UserCallback(cmd_list, pcmd); - } - else - { - // Project scissor/clipping rectangles into framebuffer space - ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y); - ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y); - if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) - continue; - - // We are using scissoring to clip some objects. All low-level graphics API should support it. - // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches - // (some elements visible outside their bounds) but you can fix that once everything else works! - // - Clipping coordinates are provided in imgui coordinates space: - // - For a given viewport, draw_data->DisplayPos == viewport->Pos and draw_data->DisplaySize == viewport->Size - // - In a single viewport application, draw_data->DisplayPos == (0,0) and draw_data->DisplaySize == io.DisplaySize, but always use GetMainViewport()->Pos/Size instead of hardcoding those values. - // - In the interest of supporting multi-viewport applications (see 'docking' branch on github), - // always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport space. - // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min) - MyEngineSetScissor(clip_min.x, clip_min.y, clip_max.x, clip_max.y); - - // The texture for the draw call is specified by pcmd->GetTexID(). - // The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set yourself during initialization. - MyEngineBindTexture((MyTexture*)pcmd->GetTexID()); - - // Render 'pcmd->ElemCount/3' indexed triangles. - // By default the indices ImDrawIdx are 16-bit, you can change them to 32-bit in imconfig.h if your engine doesn't support 16-bit indices. - MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset, vtx_buffer, pcmd->VtxOffset); - } - } - } - } API BREAKING CHANGES @@ -431,6 +392,89 @@ CODE When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2025/06/25 (1.92.0) - layout: commented out legacy ErrorCheckUsingSetCursorPosToExtendParentBoundaries() fallback obsoleted in 1.89 (August 2022) which allowed a SetCursorPos()/SetCursorScreenPos() call WITHOUT AN ITEM + to extend parent window/cell boundaries. Replaced with assert/tooltip that would already happens if previously using IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#5548, #4510, #3355, #1760, #1490, #4152, #150) + - Incorrect way to make a window content size 200x200: + Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); + - Correct ways to make a window content size 200x200: + Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); + Begin(...) + Dummy(ImVec2(200,200)) + End(); + - TL;DR; if the assert triggers, you can add a Dummy({0,0}) call to validate extending parent boundaries. + - 2025/06/11 (1.92.0) - THIS VERSION CONTAINS THE LARGEST AMOUNT OF BREAKING CHANGES SINCE 2015! I TRIED REALLY HARD TO KEEP THEM TO A MINIMUM, REDUCE THE AMOUNT OF INTERFERENCES, BUT INEVITABLY SOME USERS WILL BE AFFECTED. + IN ORDER TO HELP US IMPROVE THE TRANSITION PROCESS, INCL. DOCUMENTATION AND COMMENTS, PLEASE REPORT **ANY** DOUBT, CONFUSION, QUESTIONS, FEEDBACK TO: https://github.com/ocornut/imgui/issues/ + As part of the plan to reduce impact of API breaking changes, several unfinished changes/features/refactors related to font and text systems and scaling will be part of subsequent releases (1.92.1+). + If you are updating from an old version, and expecting a massive or difficult update, consider first updating to 1.91.9 to reduce the amount of changes. + - Hard to read? Refer to 'docs/Changelog.txt' for a less compact and more complete version of this! + - Fonts: **IMPORTANT**: if your app was solving the OSX/iOS Retina screen specific logical vs display scale problem by setting io.DisplayFramebufferScale (e.g. to 2.0f) + setting io.FontGlobalScale (e.g. to 1.0f/2.0f) + loading fonts at scaled sizes (e.g. size X * 2.0f): + This WILL NOT map correctly to the new system! Because font will rasterize as requested size. + - With a legacy backend (< 1.92): Instead of setting io.FontGlobalScale = 1.0f/N -> set ImFontCfg::RasterizerDensity = N. This already worked before, but is now pretty much required. + - With a new backend (1.92+): This should be all automatic. FramebufferScale is automatically used to set current font RasterizerDensity. FramebufferScale is a per-viewport property provided by backend through the Platform_GetWindowFramebufferScale() handler in 'docking' branch. + - Fonts: **IMPORTANT** on Font Sizing: Before 1.92, fonts were of a single size. They can now be dynamically sized. + - PushFont() API now has a REQUIRED size parameter. + - Before 1.92: PushFont() always used font "default" size specified in AddFont() call. It is equivalent to calling PushFont(font, font->LegacySize). + - Since 1.92: PushFont(font, 0.0f) preserve the current font size which is a shared value. + - To use old behavior: use 'ImGui::PushFont(font, font->LegacySize)' at call site. + - Kept inline single parameter function. Will obsolete. + - Fonts: **IMPORTANT** on Font Merging: + - When searching for a glyph in multiple merged fonts: we search for the FIRST font source which contains the desired glyph. + Because the user doesn't need to provide glyph ranges any more, it is possible that a glyph that you expected to fetch from a secondary/merged icon font may be erroneously fetched from the primary font. + - When searching for a glyph in multiple merged fonts: we now search for the FIRST font source which contains the desired glyph. This is technically a different behavior than before! + - e.g. If you are merging fonts you may have glyphs that you expected to load from Font Source 2 which exists in Font Source 1. + After the update and when using a new backend, those glyphs may now loaded from Font Source 1! + - We added `ImFontConfig::GlyphExcludeRanges[]` to specify ranges to exclude from a given font source: + // Add Font Source 1 but ignore ICON_MIN_FA..ICON_MAX_FA range + static ImWchar exclude_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; + ImFontConfig cfg1; + cfg1.GlyphExcludeRanges = exclude_ranges; + io.Fonts->AddFontFromFileTTF("segoeui.ttf", 0.0f, &cfg1); + // Add Font Source 2, which expects to use the range above + ImFontConfig cfg2; + cfg2.MergeMode = true; + io.Fonts->AddFontFromFileTTF("FontAwesome4.ttf", 0.0f, &cfg2); + - You can use `Metrics/Debugger->Fonts->Font->Input Glyphs Overlap Detection Tool` to see list of glyphs available in multiple font sources. This can facilitate unde + - Fonts: **IMPORTANT** on Thread Safety: + - A few functions such as font->CalcTextSizeA() were, by sheer luck (== accidentally) thread-safe even thou we had never provided that guarantee. They are definitively not thread-safe anymore as new glyphs may be loaded. + - Fonts: ImFont::FontSize was removed and does not make sense anymore. ImFont::LegacySize is the size passed to AddFont(). + - Fonts: Removed support for PushFont(NULL) which was a shortcut for "default font". + - Fonts: Renamed/moved 'io.FontGlobalScale' to 'style.FontScaleMain'. + - Textures: all API functions taking a 'ImTextureID' parameter are now taking a 'ImTextureRef'. Affected functions are: ImGui::Image(), ImGui::ImageWithBg(), ImGui::ImageButton(), ImDrawList::AddImage(), ImDrawList::AddImageQuad(), ImDrawList::AddImageRounded(). + - Fonts: obsoleted ImFontAtlas::GetTexDataAsRGBA32(), GetTexDataAsAlpha8(), Build(), SetTexID(), IsBuilt() functions. The new protocol for backends to handle textures doesn't need them. Kept redirection functions (will obsolete). + - Fonts: ImFontConfig::OversampleH/OversampleV default to automatic (== 0) since v1.91.8. It is quite important you keep it automatic until we decide if we want to provide a way to express finer policy, otherwise you will likely waste texture space when using large glyphs. Note that the imgui_freetype backend doesn't use and does not need oversampling. + - Fonts: specifying glyph ranges is now unnecessary. The value of ImFontConfig::GlyphRanges[] is only useful for legacy backends. All GetGlyphRangesXXXX() functions are now marked obsolete: GetGlyphRangesDefault(), GetGlyphRangesGreek(), GetGlyphRangesKorean(), GetGlyphRangesJapanese(), GetGlyphRangesChineseSimplifiedCommon(), GetGlyphRangesChineseFull(), GetGlyphRangesCyrillic(), GetGlyphRangesThai(), GetGlyphRangesVietnamese(). + - Fonts: removed ImFontAtlas::TexDesiredWidth to enforce a texture width. (#327) + - Fonts: if you create and manage ImFontAtlas instances yourself (instead of relying on ImGuiContext to create one, you'll need to call ImFontAtlasUpdateNewFrame() yourself. An assert will trigger if you don't. + - Fonts: obsolete ImGui::SetWindowFontScale() which is not useful anymore. Prefer using 'PushFont(NULL, style.FontSizeBase * factor)' or to manipulate other scaling factors. + - Fonts: obsoleted ImFont::Scale which is not useful anymore. + - Fonts: generally reworked Internals of ImFontAtlas and ImFont. While in theory a vast majority of users shouldn't be affected, some use cases or extensions might be. Among other things: + - ImDrawCmd::TextureId has been changed to ImDrawCmd::TexRef. + - ImFontAtlas::TexID has been changed to ImFontAtlas::TexRef. + - ImFontAtlas::ConfigData[] has been renamed to ImFontAtlas::Sources[] + - ImFont::ConfigData[], ConfigDataCount has been renamed to Sources[], SourceCount. + - Each ImFont has a number of ImFontBaked instances corresponding to actively used sizes. ImFont::GetFontBaked(size) retrieves the one for a given size. + - Fields moved from ImFont to ImFontBaked: IndexAdvanceX[], Glyphs[], Ascent, Descent, FindGlyph(), FindGlyphNoFallback(), GetCharAdvance(). + - Fields moved from ImFontAtlas to ImFontAtlas->Tex: ImFontAtlas::TexWidth => TexData->Width, ImFontAtlas::TexHeight => TexData->Height, ImFontAtlas::TexPixelsAlpha8/TexPixelsRGBA32 => TexData->GetPixels(). + - Widget code may use ImGui::GetFontBaked() instead of ImGui::GetFont() to access font data for current font at current font size (and you may use font->GetFontBaked(size) to access it for any other size.) + - Fonts: (users of imgui_freetype): renamed ImFontAtlas::FontBuilderFlags to ImFontAtlas::FontLoaderFlags. Renamed ImFontConfig::FontBuilderFlags to ImFontConfig::FontLoaderFlags. Renamed ImGuiFreeTypeBuilderFlags to ImGuiFreeTypeLoaderFlags. + If you used runtime imgui_freetype selection rather than the default IMGUI_ENABLE_FREETYPE compile-time option: Renamed/reworked ImFontBuilderIO into ImFontLoader. Renamed ImGuiFreeType::GetBuilderForFreeType() to ImGuiFreeType::GetFontLoader(). + - old: io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType() + - new: io.Fonts->FontLoader = ImGuiFreeType::GetFontLoader() + - new: io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader()) to change dynamically at runtime [from 1.92.1] + - Fonts: (users of custom rectangles, see #8466): Renamed AddCustomRectRegular() to AddCustomRect(). Added GetCustomRect() as a replacement for GetCustomRectByIndex() + CalcCustomRectUV(). + - The output type of GetCustomRect() is now ImFontAtlasRect, which include UV coordinates. X->x, Y->y, Width->w, Height->h. + - old: + const ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(custom_rect_id); + ImVec2 uv0, uv1; + atlas->GetCustomRectUV(r, &uv0, &uv1); + ImGui::Image(atlas->TexRef, ImVec2(r->w, r->h), uv0, uv1); + - new; + ImFontAtlasRect r; + atlas->GetCustomRect(custom_rect_id, &r); + ImGui::Image(atlas->TexRef, ImVec2(r.w, r.h), r.uv0, r.uv1); + - We added a redirecting typedef but haven't attempted to magically redirect the field names, as this API is rarely used and the fix is simple. + - Obsoleted AddCustomRectFontGlyph() as the API does not make sense for scalable fonts. Kept existing function which uses the font "default size" (Sources[0]->LegacySize). Added a helper AddCustomRectFontGlyphForSize() which is immediately marked obsolete, but can facilitate transitioning old code. + - Prefer adding a font source (ImFontConfig) using a custom/procedural loader. + - DrawList: Renamed ImDrawList::PushTextureID()/PopTextureID() to PushTexture()/PopTexture(). + - Backends: removed ImGui_ImplXXXX_CreateFontsTexture()/ImGui_ImplXXXX_DestroyFontsTexture() for all backends that had them. They should not be necessary any more. - 2025/05/23 (1.92.0) - Fonts: changed ImFont::CalcWordWrapPositionA() to ImFont::CalcWordWrapPosition() - old: const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, ....); - new: const char* ImFont::CalcWordWrapPosition (float size, const char* text, ....); @@ -1186,6 +1230,9 @@ CODE #define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Hold CTRL to display for all candidates. CTRL+Arrow to change last direction. #define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window +// Default font size if unspecified in both style.FontSizeBase and AddFontXXX() calls. +static const float FONT_DEFAULT_SIZE = 20.0f; + // When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch. static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear @@ -1268,6 +1315,10 @@ static void UpdateMouseWheel(); static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt); // Misc +static void UpdateFontsNewFrame(); +static void UpdateFontsEndFrame(); +static void UpdateTexturesNewFrame(); +static void UpdateTexturesEndFrame(); static void UpdateSettings(); static int UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect); static void RenderWindowOuterBorders(ImGuiWindow* window); @@ -1332,6 +1383,10 @@ static void* GImAllocatorUserData = NULL; ImGuiStyle::ImGuiStyle() { + FontSizeBase = 0.0f; // Will default to io.Fonts->Fonts[0] on first frame. + FontScaleMain = 1.0f; // Main scale factor. May be set by application once, or exposed to end-user. + FontScaleDpi = 1.0f; // Additional scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. + Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. WindowPadding = ImVec2(8,8); // Padding within a window @@ -1393,14 +1448,20 @@ ImGuiStyle::ImGuiStyle() HoverFlagsForTooltipMouse = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. HoverFlagsForTooltipNav = ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. + // [Internal] + _MainScale = 1.0f; + _NextFrameFontSizeBase = 0.0f; + // Default theme ImGui::StyleColorsDark(this); } -// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. + +// Scale all spacing/padding/thickness values. Do not scale fonts. // Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. void ImGuiStyle::ScaleAllSizes(float scale_factor) { + _MainScale *= scale_factor; WindowPadding = ImTrunc(WindowPadding * scale_factor); WindowRounding = ImTrunc(WindowRounding * scale_factor); WindowMinSize = ImTrunc(WindowMinSize * scale_factor); @@ -1449,9 +1510,11 @@ ImGuiIO::ImGuiIO() UserData = NULL; Fonts = NULL; - FontGlobalScale = 1.0f; FontDefault = NULL; FontAllowUserScaling = false; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + FontGlobalScale = 1.0f; // Use style.FontScaleMain instead! +#endif DisplayFramebufferScale = ImVec2(1.0f, 1.0f); // Keyboard/Gamepad Navigation options @@ -2006,6 +2069,12 @@ char* ImStrdup(const char* str) return (char*)memcpy(buf, (const void*)str, len + 1); } +void* ImMemdup(const void* src, size_t size) +{ + void* dst = IM_ALLOC(size); + return memcpy(dst, src, size); +} + char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src) { size_t dst_buf_size = p_dst_size ? *p_dst_size : ImStrlen(dst) + 1; @@ -2515,11 +2584,11 @@ static inline int ImTextCharToUtf8_inline(char* buf, int buf_size, unsigned int return 0; } -const char* ImTextCharToUtf8(char out_buf[5], unsigned int c) +int ImTextCharToUtf8(char out_buf[5], unsigned int c) { int count = ImTextCharToUtf8_inline(out_buf, 5, c); out_buf[count] = 0; - return out_buf; + return count; } // Not optimal but we very rarely use this function. @@ -3193,10 +3262,17 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) if (table) IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y); - clipper->ItemsHeight = (window->DC.CursorPos.y - clipper->StartPosY) / (float)(clipper->DisplayEnd - clipper->DisplayStart); - bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision(clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); + bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision((float)clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); if (affected_by_floating_point_precision) + { + // Mitigation/hack for very large range: assume last time height constitute line height. clipper->ItemsHeight = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries. + window->DC.CursorPos.y = (float)(clipper->StartPosY + clipper->ItemsHeight); + } + else + { + clipper->ItemsHeight = (float)(window->DC.CursorPos.y - clipper->StartPosY) / (float)(clipper->DisplayEnd - clipper->DisplayStart); + } if (clipper->ItemsHeight == 0.0f && clipper->ItemsCount == INT_MAX) // Accept that no item have been submitted if in indeterminate mode. return false; IM_ASSERT(clipper->ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); @@ -3220,7 +3296,10 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) // Add range selected to be included for navigation const bool is_nav_request = (g.NavMoveScoringItems && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav); if (is_nav_request) + { + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringRect.Min.y, g.NavScoringRect.Max.y, 0, 0)); data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, 0, 0)); + } if (is_nav_request && (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && g.NavTabbingDir == -1) data->Ranges.push_back(ImGuiListClipperRange::FromIndices(clipper->ItemsCount - 1, clipper->ItemsCount)); @@ -3724,7 +3803,8 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con const float font_size = draw_list->_Data->FontSize; const float font_scale = draw_list->_Data->FontScale; const char* text_end_ellipsis = NULL; - const float ellipsis_width = font->EllipsisWidth * font_scale; + ImFontBaked* baked = font->GetFontBaked(font_size); + const float ellipsis_width = baked->GetCharAdvance(font->EllipsisChar) * font_scale; // We can now claim the space between pos_max.x and ellipsis_max.x const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_width) - pos_min.x, 1.0f); @@ -3740,8 +3820,7 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f)); ImVec4 cpu_fine_clip_rect(pos_min.x, pos_min.y, pos_max.x, pos_max.y); ImVec2 ellipsis_pos = ImTrunc(ImVec2(pos_min.x + text_size_clipped_x, pos_min.y)); - for (int i = 0; i < font->EllipsisCharCount; i++, ellipsis_pos.x += font->EllipsisCharStep * font_scale) - font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar, &cpu_fine_clip_rect); + font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar, &cpu_fine_clip_rect); } else { @@ -3817,7 +3896,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso ImGuiContext& g = *GImGui; if (mouse_cursor <= ImGuiMouseCursor_None || mouse_cursor >= ImGuiMouseCursor_COUNT) // We intentionally accept out of bound values. mouse_cursor = ImGuiMouseCursor_Arrow; - ImFontAtlas* font_atlas = g.DrawListSharedData.Font->ContainerAtlas; + ImFontAtlas* font_atlas = g.DrawListSharedData.FontAtlas; for (ImGuiViewportP* viewport : g.Viewports) { // We scale cursor with current viewport/monitor, however Windows 10 for its own hardware cursor seems to be using a different scale factor. @@ -3829,12 +3908,12 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale))) continue; ImDrawList* draw_list = GetForegroundDrawList(viewport); - ImTextureID tex_id = font_atlas->TexID; - draw_list->PushTextureID(tex_id); - draw_list->AddImage(tex_id, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos, pos + size * scale, uv[2], uv[3], col_border); - draw_list->AddImage(tex_id, pos, pos + size * scale, uv[0], uv[1], col_fill); + ImTextureRef tex_ref = font_atlas->TexRef; + draw_list->PushTexture(tex_ref); + draw_list->AddImage(tex_ref, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_ref, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_ref, pos, pos + size * scale, uv[2], uv[3], col_border); + draw_list->AddImage(tex_ref, pos, pos + size * scale, uv[0], uv[1], col_fill); if (mouse_cursor == ImGuiMouseCursor_Wait || mouse_cursor == ImGuiMouseCursor_Progress) { float a_min = ImFmod((float)g.Time * 5.0f, 2.0f * IM_PI); @@ -3842,7 +3921,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso draw_list->PathArcTo(pos + ImVec2(14, -1) * scale, 6.0f * scale, a_min, a_max); draw_list->PathStroke(col_fill, ImDrawFlags_None, 3.0f * scale); } - draw_list->PopTextureID(); + draw_list->PopTexture(); } } @@ -3924,10 +4003,13 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) InputTextState.Ctx = this; Initialized = false; - FontAtlasOwnedByContext = shared_font_atlas ? false : true; Font = NULL; - FontSize = FontBaseSize = FontScale = CurrentDpiScale = 0.0f; + FontBaked = NULL; + FontSize = FontSizeBase = FontBakedScale = CurrentDpiScale = 0.0f; + FontRasterizerDensity = 1.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + if (shared_font_atlas == NULL) + IO.Fonts->OwnerContext = this; Time = 0.0f; FrameCount = 0; FrameCountEnded = FrameCountRendered = -1; @@ -3952,7 +4034,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1; WheelingWindowReleaseTimer = 0.0f; - DebugDrawIdConflicts = 0; + DebugDrawIdConflictsId = 0; DebugHookIdInfo = 0; HoveredId = HoveredIdPreviousFrame = 0; HoveredIdPreviousFrameItemCount = 0; @@ -4063,6 +4145,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) MouseCursor = ImGuiMouseCursor_Arrow; MouseStationaryTimer = 0.0f; + InputTextPasswordFontBackupFlags = ImFontFlags_None; TempInputId = 0; memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue)); BeginMenuDepth = BeginComboDepth = 0; @@ -4182,6 +4265,18 @@ void ImGui::Initialize() #ifdef IMGUI_HAS_DOCK #endif + // Print a debug message when running with debug feature IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS because it is very slow. + // DO NOT COMMENT OUT THIS MESSAGE. IT IS DESIGNED TO REMIND YOU THAT IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS SHOULD ONLY BE TEMPORARILY ENABLED. +#ifdef IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + DebugLog("IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS is enabled.\nMust disable after use! Otherwise Dear ImGui will run slower.\n"); +#endif + + // ImDrawList/ImFontAtlas are designed to function without ImGui, and 99% of it works without an ImGui context. + // But this link allows us to facilitate/handle a few edge cases better. + ImFontAtlas* atlas = g.IO.Fonts; + g.DrawListSharedData.Context = &g; + RegisterFontAtlas(atlas); + g.Initialized = true; } @@ -4193,12 +4288,15 @@ void ImGui::Shutdown() IM_ASSERT_USER_ERROR(g.IO.BackendRendererUserData == NULL, "Forgot to shutdown Renderer backend?"); // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) - if (g.IO.Fonts && g.FontAtlasOwnedByContext) + for (ImFontAtlas* atlas : g.FontAtlases) { - g.IO.Fonts->Locked = false; - IM_DELETE(g.IO.Fonts); + UnregisterFontAtlas(atlas); + if (atlas->OwnerContext == &g) + { + atlas->Locked = false; + IM_DELETE(atlas); + } } - g.IO.Fonts = NULL; g.DrawListSharedData.TempBuffer.clear(); // Cleanup of other data are conditional on actually having initialized Dear ImGui. @@ -4327,7 +4425,7 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL SettingsOffset = -1; DrawList = &DrawListInst; DrawList->_OwnerName = Name; - DrawList->_Data = &Ctx->DrawListSharedData; + DrawList->_SetDrawListSharedData(&Ctx->DrawListSharedData); NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX); } @@ -4347,8 +4445,15 @@ static void SetCurrentWindow(ImGuiWindow* window) g.CurrentDpiScale = 1.0f; // FIXME-DPI: WIP this is modified in docking if (window) { - g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); - g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; + bool backup_skip_items = window->SkipItems; + window->SkipItems = false; + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + { + ImGuiViewport* viewport = window->Viewport; + g.FontRasterizerDensity = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale.x : g.IO.DisplayFramebufferScale.x; // == SetFontRasterizerDensity() + } + ImGui::UpdateCurrentFontSize(0.0f); + window->SkipItems = backup_skip_items; ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } } @@ -4361,6 +4466,8 @@ void ImGui::GcCompactTransientMiscBuffers() g.MultiSelectTempDataStacked = 0; g.MultiSelectTempData.clear_destruct(); TableGcCompactSettings(); + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->CompactCache(); } // Free up/compact internal window buffers, we can use this when a window becomes unused. @@ -4396,15 +4503,6 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) // Clear previous active id if (g.ActiveId != 0) { - // While most behaved code would make an effort to not steal active id during window move/drag operations, - // we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch - // may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that. - if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId) - { - IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow\n"); - g.MovingWindow = NULL; - } - // Store deactivate data ImGuiDeactivatedItemData* deactivated_data = &g.DeactivatedItemData; deactivated_data->ID = g.ActiveId; @@ -4417,6 +4515,15 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) // One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveID() if (g.InputTextState.ID == g.ActiveId) InputTextDeactivateHook(g.ActiveId); + + // While most behaved code would make an effort to not steal active id during window move/drag operations, + // we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch + // may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that. + if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId) + { + IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow\n"); + StopMouseMovingWindow(); + } } // Set active id @@ -4631,7 +4738,8 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) return true; } -// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). +// Internal facing ItemHoverable() used when submitting widgets. THIS IS A SUBMISSION NOT A HOVER CHECK. +// Returns whether the item was hovered, logic differs slightly from IsItemHovered(). // (this does not rely on LastItemData it can be called from a ButtonBehavior() call not following an ItemAdd() call) // FIXME-LEGACY: the 'ImGuiItemFlags item_flags' parameter was added on 2023-06-28. // If you used this in your legacy/custom widgets code: @@ -4643,11 +4751,12 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag ImGuiWindow* window = g.CurrentWindow; // Detect ID conflicts + // (this is specifically done here by comparing on hover because it allows us a detection of duplicates that is algorithmically extra cheap, 1 u32 compare per item. No O(log N) lookup whatsoever) #ifndef IMGUI_DISABLE_DEBUG_TOOLS if (id != 0 && g.HoveredIdPreviousFrame == id && (item_flags & ImGuiItemFlags_AllowDuplicateId) == 0) { g.HoveredIdPreviousFrameItemCount++; - if (g.DebugDrawIdConflicts == id) + if (g.DebugDrawIdConflictsId == id) window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f); } #endif @@ -4913,7 +5022,7 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw if (viewport->BgFgDrawListsLastFrame[drawlist_no] != g.FrameCount) { draw_list->_ResetForNewFrame(); - draw_list->PushTextureID(g.IO.Fonts->TexID); + draw_list->PushTexture(g.IO.Fonts->TexRef); draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount; } @@ -4968,6 +5077,19 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window) g.MovingWindow = window; } +// This is not 100% symetric with StartMouseMovingWindow(). +// We do NOT clear ActiveID, because: +// - It would lead to rather confusing recursive code paths. Caller can call ClearActiveID() if desired. +// - Some code intentionally cancel moving but keep the ActiveID to lock inputs (e.g. code path taken when clicking a disabled item). +void ImGui::StopMouseMovingWindow() +{ + ImGuiContext& g = *GImGui; + + // [nb: docking branch has more stuff in this function] + + g.MovingWindow = NULL; +} + // Handle mouse moving window // Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing() // FIXME: We don't have strong guarantee that g.MovingWindow stay synced with g.ActiveId == g.MovingWindow->MoveId. @@ -4991,7 +5113,7 @@ void ImGui::UpdateMouseMovingWindowNewFrame() } else { - g.MovingWindow = NULL; + StopMouseMovingWindow(); ClearActiveID(); } } @@ -5033,6 +5155,9 @@ void ImGui::UpdateMouseMovingWindowEndFrame() { StartMouseMovingWindow(g.HoveredWindow); //-V595 + // FIXME: In principal we might be able to call StopMouseMovingWindow() below. + // Please note how StartMouseMovingWindow() and StopMouseMovingWindow() and not entirely symetrical, at the later doesn't clear ActiveId. + // Cancel moving if clicked outside of title bar if (g.IO.ConfigWindowsMoveFromTitleBarOnly) if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar)) @@ -5197,7 +5322,6 @@ void ImGui::NewFrame() UpdateSettings(); g.Time += g.IO.DeltaTime; - g.WithinFrameScope = true; g.FrameCount += 1; g.TooltipOverrideCount = 0; g.WindowsActiveCount = 0; @@ -5217,11 +5341,14 @@ void ImGui::NewFrame() // Update viewports (after processing input queue, so io.MouseHoveredViewport is set) UpdateViewportsNewFrame(); + // Update texture list (collect destroyed textures, etc.) + UpdateTexturesNewFrame(); + // Setup current font and draw list shared data - g.IO.Fonts->Locked = true; SetupDrawListSharedData(); - SetCurrentFont(GetDefaultFont()); - IM_ASSERT(g.Font->IsLoaded()); + UpdateFontsNewFrame(); + + g.WithinFrameScope = true; // Mark rendering data as invalid to prevent user who may have a handle on it to use it. for (ImGuiViewportP* viewport : g.Viewports) @@ -5233,9 +5360,9 @@ void ImGui::NewFrame() // [DEBUG] if (!g.IO.ConfigDebugHighlightIdConflicts || !g.IO.KeyCtrl) // Count is locked while holding CTRL - g.DebugDrawIdConflicts = 0; + g.DebugDrawIdConflictsId = 0; if (g.IO.ConfigDebugHighlightIdConflicts && g.HoveredIdPreviousFrameItemCount > 1) - g.DebugDrawIdConflicts = g.HoveredIdPreviousFrame; + g.DebugDrawIdConflictsId = g.HoveredIdPreviousFrame; // Update HoveredId data if (!g.HoveredIdPreviousFrame) @@ -5529,6 +5656,7 @@ static void InitViewportDrawData(ImGuiViewportP* viewport) draw_data->DisplaySize = viewport->Size; draw_data->FramebufferScale = io.DisplayFramebufferScale; draw_data->OwnerViewport = viewport; + draw_data->Textures = &ImGui::GetPlatformIO().Textures; } // Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering. @@ -5702,6 +5830,7 @@ void ImGui::EndFrame() // End frame g.WithinFrameScope = false; g.FrameCountEnded = g.FrameCount; + UpdateFontsEndFrame(); // Initiate moving window + handle left-click and right-click focus UpdateMouseMovingWindowEndFrame(); @@ -5722,8 +5851,11 @@ void ImGui::EndFrame() g.Windows.swap(g.WindowsTempSortBuffer); g.IO.MetricsActiveWindows = g.WindowsActiveCount; + UpdateTexturesEndFrame(); + // Unlock font atlas - g.IO.Fonts->Locked = false; + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->Locked = false; // Clear Input data for next frame g.IO.MousePosPrev = g.IO.MousePos; @@ -5800,6 +5932,12 @@ void ImGui::Render() g.IO.MetricsRenderIndices += draw_data->TotalIdxCount; } +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + for (ImFontAtlas* atlas : g.FontAtlases) + ImFontAtlasDebugLogTextureRequests(atlas); +#endif + CallContextHooks(&g, ImGuiContextHookType_RenderPost); } @@ -6390,10 +6528,10 @@ static void CalcWindowContentSizes(ImGuiWindow* window, ImVec2* content_size_cur return; } - content_size_current->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_TRUNC(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x); - content_size_current->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_TRUNC(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y); - content_size_ideal->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_TRUNC(ImMax(window->DC.CursorMaxPos.x, window->DC.IdealMaxPos.x) - window->DC.CursorStartPos.x); - content_size_ideal->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_TRUNC(ImMax(window->DC.CursorMaxPos.y, window->DC.IdealMaxPos.y) - window->DC.CursorStartPos.y); + content_size_current->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : ImTrunc64(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x); + content_size_current->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : ImTrunc64(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y); + content_size_ideal->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : ImTrunc64(ImMax(window->DC.CursorMaxPos.x, window->DC.IdealMaxPos.x) - window->DC.CursorStartPos.x); + content_size_ideal->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : ImTrunc64(ImMax(window->DC.CursorMaxPos.y, window->DC.IdealMaxPos.y) - window->DC.CursorStartPos.y); } static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_contents) @@ -6422,9 +6560,9 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one axis may be auto-fit when calculating scrollbars, // we may need to compute/store three variants of size_auto_fit, for x/y/xy. // Here we implement a workaround for child windows only, but a full solution would apply to normal windows as well: - if ((window->ChildFlags & ImGuiChildFlags_ResizeX) && !(window->ChildFlags & ImGuiChildFlags_ResizeY)) + if ((window->ChildFlags & ImGuiChildFlags_ResizeX) && !(window->ChildFlags & (ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeY))) size_auto_fit.y = window->SizeFull.y; - else if (!(window->ChildFlags & ImGuiChildFlags_ResizeX) && (window->ChildFlags & ImGuiChildFlags_ResizeY)) + else if ((window->ChildFlags & ImGuiChildFlags_ResizeY) && !(window->ChildFlags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_AutoResizeX))) size_auto_fit.x = window->SizeFull.x; // When the window cannot fit all contents (either because of constraints, either because screen is too small), @@ -6544,7 +6682,9 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si ImGuiContext& g = *GImGui; ImGuiWindowFlags flags = window->Flags; - if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + if ((flags & ImGuiWindowFlags_NoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + return false; + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && (window->ChildFlags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0) return false; if (window->WasActive == false) // Early out to avoid running this code for e.g. a hidden implicit/fallback Debug window. return false; @@ -6936,8 +7076,12 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl // Close button if (has_close_button) + { + g.CurrentItemFlags |= ImGuiItemFlags_NoFocus; if (CloseButton(window->GetID("#CLOSE"), close_button_pos)) *p_open = false; + g.CurrentItemFlags &= ~ImGuiItemFlags_NoFocus; + } window->DC.NavLayerCurrent = ImGuiNavLayer_Main; g.CurrentItemFlags = item_flags_backup; @@ -7452,12 +7596,19 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } #endif + // Decide if we are going to handle borders and resize grips + // 'window->SkipItems' is not updated yet so for child windows we rely on ParentWindow to avoid submitting decorations. (#8815) + // Whenever we add support for full decorated child windows we will likely make this logic more general. + bool handle_borders_and_resize_grips = true; + if ((flags & ImGuiWindowFlags_ChildWindow) && window->ParentWindow->SkipItems) + handle_borders_and_resize_grips = false; + // Handle manual resize: Resize Grips, Borders, Gamepad int border_hovered = -1, border_held = -1; ImU32 resize_grip_col[4] = {}; const int resize_grip_count = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) ? 0 : g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it. const float resize_grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); - if (!window->Collapsed) + if (handle_borders_and_resize_grips && !window->Collapsed) if (int auto_fit_mask = UpdateWindowManualResize(window, size_auto_fit, &border_hovered, &border_held, resize_grip_count, &resize_grip_col[0], visibility_rect)) { if (auto_fit_mask & (1 << ImGuiAxis_X)) @@ -7553,12 +7704,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->InnerClipRect.Max.y = ImFloor(window->InnerRect.Max.y - window->WindowBorderSize * 0.5f); window->InnerClipRect.ClipWithFull(host_rect); - // Default item width. Make it proportional to window size if window manually resizes - if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) - window->ItemWidthDefault = ImTrunc(window->Size.x * 0.65f); - else - window->ItemWidthDefault = ImTrunc(g.FontSize * 16.0f); - // SCROLLING // Lock down maximum scrolling @@ -7576,7 +7721,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Setup draw list and outer clipping rectangle IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0); - window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + window->DrawList->PushTexture(g.Font->ContainerAtlas->TexRef); PushClipRect(host_rect.Min, host_rect.Max, false); // Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call (since 1.71) @@ -7600,7 +7745,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Handle title bar, scrollbar, resize grips and resize borders const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow; const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight); - const bool handle_borders_and_resize_grips = true; // This exists to facilitate merge with 'docking' branch. RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_count, resize_grip_col, resize_grip_draw_size); if (render_decorations_in_parent) @@ -7671,6 +7815,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.LayoutType = ImGuiLayoutType_Vertical; window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; + // Default item width. Make it proportional to window size if window manually resizes + if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) + window->ItemWidthDefault = ImTrunc(window->Size.x * 0.65f); + else + window->ItemWidthDefault = ImTrunc(g.FontSize * 16.0f); window->DC.ItemWidth = window->ItemWidthDefault; window->DC.TextWrapPos = -1.0f; // disabled window->DC.ItemWidthStack.resize(0); @@ -7872,56 +8021,6 @@ void ImGui::End() SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window); } -// Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only. -void ImGui::SetCurrentFont(ImFont* font) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? - IM_ASSERT(font->Scale > 0.0f); - g.Font = font; - g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale); - g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; - g.FontScale = g.FontSize / g.Font->FontSize; - - ImFontAtlas* atlas = g.Font->ContainerAtlas; - g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; - g.DrawListSharedData.TexUvLines = atlas->TexUvLines; - g.DrawListSharedData.Font = g.Font; - g.DrawListSharedData.FontSize = g.FontSize; - g.DrawListSharedData.FontScale = g.FontScale; -} - -// Use ImDrawList::_SetTextureID(), making our shared g.FontStack[] authoritative against window-local ImDrawList. -// - Whereas ImDrawList::PushTextureID()/PopTextureID() is not to be used across Begin() calls. -// - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did... -// - Some code paths never really fully worked with multiple atlas textures. -// - The right-ish solution may be to remove _SetTextureID() and make AddText/RenderText lazily call PushTextureID()/PopTextureID() -// the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem -// because we have a concrete need and a test bed for multiple atlas textures. -void ImGui::PushFont(ImFont* font) -{ - ImGuiContext& g = *GImGui; - if (font == NULL) - font = GetDefaultFont(); - g.FontStack.push_back(font); - SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID); -} - -void ImGui::PopFont() -{ - ImGuiContext& g = *GImGui; - if (g.FontStack.Size <= 0) - { - IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); - return; - } - g.FontStack.pop_back(); - ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back(); - SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID); -} - void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) { ImGuiContext& g = *GImGui; @@ -8374,6 +8473,14 @@ ImFont* ImGui::GetFont() return GImGui->Font; } +ImFontBaked* ImGui::GetFontBaked() +{ + return GImGui->FontBaked; +} + +// Get current font size (= height in pixels) of current font, with global scale factors applied. +// - Use style.FontSizeBase to get value before global scale factors. +// - recap: ImGui::GetFontSize() == style.FontSizeBase * (style.FontScaleMain * style.FontScaleDpi * other_scaling_factors) float ImGui::GetFontSize() { return GImGui->FontSize; @@ -8384,15 +8491,16 @@ ImVec2 ImGui::GetFontTexUvWhitePixel() return GImGui->DrawListSharedData.TexUvWhitePixel; } +// Prefer using PushFont(NULL, style.FontSizeBase * factor), or use style.FontScaleMain to scale all windows. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS void ImGui::SetWindowFontScale(float scale) { IM_ASSERT(scale > 0.0f); - ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); window->FontWindowScale = scale; - g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); - g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; + UpdateCurrentFontSize(0.0f); } +#endif void ImGui::PushFocusScope(ImGuiID id) { @@ -8546,6 +8654,265 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); } +//----------------------------------------------------------------------------- +// [SECTION] FONTS, TEXTURES +//----------------------------------------------------------------------------- +// Most of the relevant font logic is in imgui_draw.cpp. +// Those are high-level support functions. +//----------------------------------------------------------------------------- +// - UpdateTexturesNewFrame() [Internal] +// - UpdateTexturesEndFrame() [Internal] +// - UpdateFontsNewFrame() [Internal] +// - UpdateFontsEndFrame() [Internal] +// - GetDefaultFont() [Internal] +// - RegisterUserTexture() [Internal] +// - UnregisterUserTexture() [Internal] +// - RegisterFontAtlas() [Internal] +// - UnregisterFontAtlas() [Internal] +// - SetCurrentFont() [Internal] +// - UpdateCurrentFontSize() [Internal] +// - SetFontRasterizerDensity() [Internal] +// - PushFont() +// - PopFont() +//----------------------------------------------------------------------------- + +static void ImGui::UpdateTexturesNewFrame() +{ + // Cannot update every atlases based on atlas's FrameCount < g.FrameCount, because an atlas may be shared by multiple contexts with different frame count. + ImGuiContext& g = *GImGui; + const bool has_textures = (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; + for (ImFontAtlas* atlas : g.FontAtlases) + { + if (atlas->OwnerContext == &g) + { + ImFontAtlasUpdateNewFrame(atlas, g.FrameCount, has_textures); + } + else + { + // (1) If you manage font atlases yourself, e.g. create a ImFontAtlas yourself you need to call ImFontAtlasUpdateNewFrame() on it. + // Otherwise, calling ImGui::CreateContext() without parameter will create an atlas owned by the context. + // (2) If you have multiple font atlases, make sure the 'atlas->RendererHasTextures' as specified in the ImFontAtlasUpdateNewFrame() call matches for that. + // (3) If you have multiple imgui contexts, they also need to have a matching value for ImGuiBackendFlags_RendererHasTextures. + IM_ASSERT(atlas->Builder != NULL && atlas->Builder->FrameCount != -1); + IM_ASSERT(atlas->RendererHasTextures == has_textures); + } + } +} + +// Build a single texture list +static void ImGui::UpdateTexturesEndFrame() +{ + ImGuiContext& g = *GImGui; + g.PlatformIO.Textures.resize(0); + for (ImFontAtlas* atlas : g.FontAtlases) + for (ImTextureData* tex : atlas->TexList) + { + // We provide this information so backends can decide whether to destroy textures. + // This means in practice that if N imgui contexts are created with a shared atlas, we assume all of them have a backend initialized. + tex->RefCount = (unsigned short)atlas->RefCount; + g.PlatformIO.Textures.push_back(tex); + } + for (ImTextureData* tex : g.UserTextures) + g.PlatformIO.Textures.push_back(tex); +} + +void ImGui::UpdateFontsNewFrame() +{ + ImGuiContext& g = *GImGui; + if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->Locked = true; + + if (g.Style._NextFrameFontSizeBase != 0.0f) + { + g.Style.FontSizeBase = g.Style._NextFrameFontSizeBase; + g.Style._NextFrameFontSizeBase = 0.0f; + } + + // Apply default font size the first time + ImFont* font = ImGui::GetDefaultFont(); + if (g.Style.FontSizeBase <= 0.0f) + g.Style.FontSizeBase = (font->LegacySize > 0.0f ? font->LegacySize : FONT_DEFAULT_SIZE); + + // Set initial font + g.Font = font; + g.FontSizeBase = g.Style.FontSizeBase; + g.FontSize = 0.0f; + ImFontStackData font_stack_data = { font, g.Style.FontSizeBase, g.Style.FontSizeBase }; // <--- Will restore FontSize + SetCurrentFont(font_stack_data.Font, font_stack_data.FontSizeBeforeScaling, 0.0f); // <--- but use 0.0f to enable scale + g.FontStack.push_back(font_stack_data); + IM_ASSERT(g.Font->IsLoaded()); +} + +void ImGui::UpdateFontsEndFrame() +{ + PopFont(); +} + +ImFont* ImGui::GetDefaultFont() +{ + ImGuiContext& g = *GImGui; + ImFontAtlas* atlas = g.IO.Fonts; + if (atlas->Builder == NULL || atlas->Fonts.Size == 0) + ImFontAtlasBuildMain(atlas); + return g.IO.FontDefault ? g.IO.FontDefault : atlas->Fonts[0]; +} + +// EXPERIMENTAL: DO NOT USE YET. +void ImGui::RegisterUserTexture(ImTextureData* tex) +{ + ImGuiContext& g = *GImGui; + tex->RefCount++; + g.UserTextures.push_back(tex); +} + +void ImGui::UnregisterUserTexture(ImTextureData* tex) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(tex->RefCount > 0); + tex->RefCount--; + g.UserTextures.find_erase(tex); +} + +void ImGui::RegisterFontAtlas(ImFontAtlas* atlas) +{ + ImGuiContext& g = *GImGui; + if (g.FontAtlases.Size == 0) + IM_ASSERT(atlas == g.IO.Fonts); + atlas->RefCount++; + g.FontAtlases.push_back(atlas); + ImFontAtlasAddDrawListSharedData(atlas, &g.DrawListSharedData); +} + +void ImGui::UnregisterFontAtlas(ImFontAtlas* atlas) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(atlas->RefCount > 0); + ImFontAtlasRemoveDrawListSharedData(atlas, &g.DrawListSharedData); + g.FontAtlases.find_erase(atlas); + atlas->RefCount--; +} + +// Use ImDrawList::_SetTexture(), making our shared g.FontStack[] authoritative against window-local ImDrawList. +// - Whereas ImDrawList::PushTexture()/PopTexture() is not to be used across Begin() calls. +// - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did... +// - Some code paths never really fully worked with multiple atlas textures. +// - The right-ish solution may be to remove _SetTexture() and make AddText/RenderText lazily call PushTexture()/PopTexture() +// the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem +// because we have a concrete need and a test bed for multiple atlas textures. +// FIXME-NEWATLAS-V2: perhaps we can now leverage ImFontAtlasUpdateDrawListsTextures() ? +void ImGui::SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling) +{ + ImGuiContext& g = *GImGui; + g.Font = font; + g.FontSizeBase = font_size_before_scaling; + UpdateCurrentFontSize(font_size_after_scaling); + + if (font != NULL) + { + IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + IM_ASSERT(font->Scale > 0.0f); +#endif + ImFontAtlas* atlas = font->ContainerAtlas; + g.DrawListSharedData.FontAtlas = atlas; + g.DrawListSharedData.Font = font; + ImFontAtlasUpdateDrawListsSharedData(atlas); + if (g.CurrentWindow != NULL) + g.CurrentWindow->DrawList->_SetTexture(atlas->TexRef); + } +} + +void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + g.Style.FontSizeBase = g.FontSizeBase; + + // Early out to avoid hidden window keeping bakes referenced and out of GC reach. + // However this would leave a pretty subtle and damning error surface area if g.FontBaked was mismatching, so for now we null it. + // FIXME: perhaps g.FontSize should be updated? + if (window != NULL && window->SkipItems) + if (g.CurrentTable == NULL || g.CurrentTable->CurrentColumn != -1) // See 8465#issuecomment-2951509561. Ideally the SkipItems=true in tables would be amended with extra data. + return; + + // Restoring is pretty much only used by PopFont() + float final_size = (restore_font_size_after_scaling > 0.0f) ? restore_font_size_after_scaling : 0.0f; + if (final_size == 0.0f) + { + final_size = g.FontSizeBase; + + // Global scale factors + final_size *= g.Style.FontScaleMain; // Main global scale factor + final_size *= g.Style.FontScaleDpi; // Per-monitor/viewport DPI scale factor, automatically updated when io.ConfigDpiScaleFonts is enabled. + + // Window scale (mostly obsolete now) + if (window != NULL) + final_size *= window->FontWindowScale; + + // Legacy scale factors +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + final_size *= g.IO.FontGlobalScale; // Use style.FontScaleMain instead! + if (g.Font != NULL) + final_size *= g.Font->Scale; // Was never really useful. +#endif + } + + // Round font size + // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. + // - We may support it better later and remove this rounding. + final_size = GetRoundedFontSize(final_size); + final_size = ImClamp(final_size, 1.0f, IMGUI_FONT_SIZE_MAX); + if (g.Font != NULL && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) + g.Font->CurrentRasterizerDensity = g.FontRasterizerDensity; + g.FontSize = final_size; + g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL; + g.FontBakedScale = (g.Font != NULL && window != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; + g.DrawListSharedData.FontSize = g.FontSize; + g.DrawListSharedData.FontScale = g.FontBakedScale; +} + +// Exposed in case user may want to override setting density. +// IMPORTANT: Begin()/End() is overriding density. Be considerate of this you change it. +void ImGui::SetFontRasterizerDensity(float rasterizer_density) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures); + if (g.FontRasterizerDensity == rasterizer_density) + return; + g.FontRasterizerDensity = rasterizer_density; + UpdateCurrentFontSize(0.0f); +} + +// If you want to scale an existing font size! Read comments in imgui.h! +void ImGui::PushFont(ImFont* font, float font_size_base) +{ + ImGuiContext& g = *GImGui; + if (font == NULL) // Before 1.92 (June 2025), PushFont(NULL) == PushFont(GetDefaultFont()) + font = g.Font; + IM_ASSERT(font != NULL); + IM_ASSERT(font_size_base >= 0.0f); + + g.FontStack.push_back({ g.Font, g.FontSizeBase, g.FontSize }); + if (font_size_base == 0.0f) + font_size_base = g.FontSizeBase; // Keep current font size + SetCurrentFont(font, font_size_base, 0.0f); +} + +void ImGui::PopFont() +{ + ImGuiContext& g = *GImGui; + if (g.FontStack.Size <= 0) + { + IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); + return; + } + ImFontStackData* font_stack_data = &g.FontStack.back(); + SetCurrentFont(font_stack_data->Font, font_stack_data->FontSizeBeforeScaling, font_stack_data->FontSizeAfterScaling); + g.FontStack.pop_back(); +} + //----------------------------------------------------------------------------- // [SECTION] ID STACK //----------------------------------------------------------------------------- @@ -10197,36 +10564,44 @@ bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, si return !error; } -// Until 1.89 (IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos() to extend the boundary of a parent (e.g. window or table cell) -// This is causing issues and ambiguity and we need to retire that. -// See https://github.com/ocornut/imgui/issues/5548 for more details. -// [Scenario 1] +// Until 1.89 (August 2022, IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos()/SetCursorScreenPos() +// to extend contents size of our parent container (e.g. window contents size, which is used for auto-resizing +// windows, table column contents size used for auto-resizing columns, group size). +// This was causing issues and ambiguities and we needed to retire that. +// From 1.89, extending contents size boundaries REQUIRES AN ITEM TO BE SUBMITTED. +// // Previously this would make the window content size ~200x200: -// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK +// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK ANYMORE // Instead, please submit an item: // Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); // OK // Alternative: // Begin(...) + Dummy(ImVec2(200,200)) + End(); // OK -// [Scenario 2] -// For reference this is one of the issue what we aim to fix with this change: -// BeginGroup() + SomeItem("foobar") + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup() -// The previous logic made SetCursorScreenPos(GetCursorScreenPos()) have a side-effect! It would erroneously incorporate ItemSpacing.y after the item into content size, making the group taller! -// While this code is a little twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. Using vertical alignment patterns could trigger this issue. +// +// The assert below detects when the _last_ call in a window was a SetCursorPos() not followed by an Item, +// and with a position that would grow the parent contents size. +// +// Advanced: +// - For reference, old logic was causing issues because it meant that SetCursorScreenPos(GetCursorScreenPos()) +// had a side-effect on layout! In particular this caused problem to compute group boundaries. +// e.g. BeginGroup() + SomeItem() + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup() would cause the +// group to be taller because auto-sizing generally adds padding on bottom and right side. +// - While this code is a little twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. +// Using vertical alignment patterns would frequently trigger this sorts of issue. +// - See https://github.com/ocornut/imgui/issues/5548 for more details. void ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(window->DC.IsSetPos); window->DC.IsSetPos = false; -#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (window->DC.CursorPos.x <= window->DC.CursorMaxPos.x && window->DC.CursorPos.y <= window->DC.CursorMaxPos.y) return; if (window->SkipItems) return; - IM_ASSERT(0 && "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries. Please submit an item e.g. Dummy() to validate extent."); -#else - window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); -#endif + IM_ASSERT_USER_ERROR(0, "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries.\nPlease submit an item e.g. Dummy() afterwards in order to grow window/parent boundaries."); + + // For reference, the old behavior was essentially: + //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); } static void ImGui::ErrorCheckNewFrameSanityChecks() @@ -10254,7 +10629,6 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!"); IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!"); - IM_ASSERT(g.IO.Fonts->IsBuilt() && "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()"); IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations @@ -10269,6 +10643,9 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT(g.IO.ConfigErrorRecoveryEnableAssert || g.IO.ConfigErrorRecoveryEnableDebugLog || g.IO.ConfigErrorRecoveryEnableTooltip || g.ErrorCallback != NULL); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (g.IO.FontGlobalScale > 1.0f) + IM_ASSERT(g.Style.FontScaleMain == 1.0f && "Since 1.92: use style.FontScaleMain instead of g.IO.FontGlobalScale!"); + // Remap legacy names if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) { @@ -10499,9 +10876,9 @@ void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() { #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; - if (g.DebugDrawIdConflicts != 0 && g.IO.KeyCtrl == false) + if (g.DebugDrawIdConflictsId != 0 && g.IO.KeyCtrl == false) g.DebugDrawIdConflictsCount = g.HoveredIdPreviousFrameItemCount; - if (g.DebugDrawIdConflicts != 0 && g.DebugItemPickerActive == false && BeginErrorTooltip()) + if (g.DebugDrawIdConflictsId != 0 && g.DebugItemPickerActive == false && BeginErrorTooltip()) { Text("Programmer error: %d visible items with conflicting ID!", g.DebugDrawIdConflictsCount); BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!"); @@ -10670,6 +11047,21 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // Empty identifier are valid and useful in a small amount of cases, but 99.9% of the time you want to use "##something". // READ THE FAQ: https://dearimgui.com/faq IM_ASSERT(id != window->ID && "Cannot have an empty ID at the root of a window. If you need an empty label, use ## and read the FAQ about how the ID Stack works!"); + + // [DEBUG] Highlight all conflicts WITHOUT needing to hover. THIS WILL SLOW DOWN DEAR IMGUI. DON'T KEEP ACTIVATED. + // This will only work for items submitted with ItemAdd(). Some very rare/odd/unrecommended code patterns are calling ButtonBehavior() without ItemAdd(). +#ifdef IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_AllowDuplicateId) == 0) + { + int* p_alive = g.DebugDrawIdConflictsAliveCount.GetIntRef(id, -1); // Could halve lookups if we knew ImGuiStorage can store 64-bit, or by storing FrameCount as 30-bits + highlight as 2-bits. But the point is that we should not pretend that this is fast. + int* p_highlight = g.DebugDrawIdConflictsHighlightSet.GetIntRef(id, -1); + if (*p_alive == g.FrameCount) + *p_highlight = g.FrameCount; + *p_alive = g.FrameCount; + if (*p_highlight >= g.FrameCount - 1) + window->DrawList->AddRect(bb.Min - ImVec2(1, 1), bb.Max + ImVec2(1, 1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f); + } +#endif } //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] //if ((g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav) == 0) @@ -11151,7 +11543,7 @@ static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) } scroll[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]); } - scroll[axis] = IM_ROUND(ImMax(scroll[axis], 0.0f)); + scroll[axis] = ImRound64(ImMax(scroll[axis], 0.0f)); if (!window->Collapsed && !window->SkipItems) scroll[axis] = ImMin(scroll[axis], window->ScrollMax[axis]); } @@ -12138,10 +12530,10 @@ bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) IM_ASSERT(cur_window); // Not inside a Begin()/End() const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0; - if (flags & ImGuiHoveredFlags_RootWindow) + if (flags & ImGuiFocusedFlags_RootWindow) cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy); - if (flags & ImGuiHoveredFlags_ChildWindows) + if (flags & ImGuiFocusedFlags_ChildWindows) return IsWindowChildOf(ref_window, cur_window, popup_hierarchy); else return (ref_window == cur_window); @@ -13357,7 +13749,7 @@ void ImGui::NavUpdateCreateMoveRequest() //if (!g.NavScoringNoClipRect.IsInverted()) { GetForegroundDrawList()->AddRect(g.NavScoringNoClipRect.Min, g.NavScoringNoClipRect.Max, IM_COL32(255, 200, 0, 255)); } // [DEBUG] } g.NavScoringRect = scoring_rect; - g.NavScoringNoClipRect.Add(scoring_rect); + //g.NavScoringNoClipRect.Add(scoring_rect); } void ImGui::NavUpdateCreateTabbingRequest() @@ -13576,7 +13968,7 @@ static float ImGui::NavUpdatePageUpPageDown() if (g.NavLayer != ImGuiNavLayer_Main) NavRestoreLayer(ImGuiNavLayer_Main); - if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY) + if ((window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Main)) == 0 && window->DC.NavWindowHasScrollY) { // Fallback manual-scroll when window has no navigable item if (IsKeyPressed(ImGuiKey_PageUp, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner)) @@ -15035,6 +15427,8 @@ static void ImGui::UpdateViewportsNewFrame() main_viewport->Flags = ImGuiViewportFlags_IsPlatformWindow | ImGuiViewportFlags_OwnedByApp; main_viewport->Pos = ImVec2(0.0f, 0.0f); main_viewport->Size = g.IO.DisplaySize; + main_viewport->FramebufferScale = g.IO.DisplayFramebufferScale; + IM_ASSERT(main_viewport->FramebufferScale.x > 0.0f && main_viewport->FramebufferScale.y > 0.0f); for (ImGuiViewportP* viewport : g.Viewports) { @@ -15289,11 +15683,16 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImG //----------------------------------------------------------------------------- // [SECTION] METRICS/DEBUGGER WINDOW //----------------------------------------------------------------------------- +// - MetricsHelpMarker() [Internal] // - DebugRenderViewportThumbnail() [Internal] // - RenderViewportsThumbnails() [Internal] +// - DebugRenderKeyboardPreview() [Internal] // - DebugTextEncoding() -// - MetricsHelpMarker() [Internal] -// - ShowFontAtlas() [Internal] +// - DebugFlashStyleColorStop() [Internal] +// - DebugFlashStyleColor() +// - UpdateDebugToolFlashStyleColor() [Internal] +// - ShowFontAtlas() [Internal but called by Demo!] +// - DebugNodeTexture() [Internal] // - ShowMetricsWindow() // - DebugNodeColumns() [Internal] // - DebugNodeDrawList() [Internal] @@ -15309,6 +15708,21 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImG // - DebugNodeWindowsListByBeginStackParent() [Internal] //----------------------------------------------------------------------------- +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) +// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. +static void MetricsHelpMarker(const char* desc) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::BeginItemTooltip()) + { + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} +#endif + #ifndef IMGUI_DISABLE_DEBUG_TOOLS void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb) @@ -15436,10 +15850,12 @@ void ImGui::DebugTextEncoding(const char* str) Text("0x%02X", (int)(unsigned char)p[byte_index]); } TableNextColumn(); - if (GetFont()->FindGlyphNoFallback((ImWchar)c)) - TextUnformatted(p, p + c_utf8_len); - else - TextUnformatted((c == IM_UNICODE_CODEPOINT_INVALID) ? "[invalid]" : "[missing]"); + TextUnformatted(p, p + c_utf8_len); + if (!GetFont()->IsGlyphInFont((ImWchar)c)) + { + SameLine(); + TextUnformatted("[missing]"); + } TableNextColumn(); Text("U+%04X", (int)c); p += c_utf8_len; @@ -15487,33 +15903,101 @@ static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTex return buf; } -// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. -static void MetricsHelpMarker(const char* desc) +static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, const ImDrawCmd* cmd) { - ImGui::TextDisabled("(?)"); - if (ImGui::BeginItemTooltip()) - { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextUnformatted(desc); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + char* buf_end = buf + buf_size; + if (cmd->TexRef._TexData != NULL) + buf += ImFormatString(buf, buf_end - buf, "#%03d: ", cmd->TexRef._TexData->UniqueID); + return FormatTextureIDForDebugDisplay(buf, (int)(buf_end - buf), cmd->TexRef.GetTexID()); // Calling TexRef::GetTexID() to avoid assert of cmd->GetTexID() } +#ifdef IMGUI_ENABLE_FREETYPE +namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetFontLoader(); IMGUI_API bool DebugEditFontLoaderFlags(unsigned int* p_font_builder_flags); } +#endif + // [DEBUG] List fonts in a font atlas and display its texture void ImGui::ShowFontAtlas(ImFontAtlas* atlas) { ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + ImGuiStyle& style = g.Style; + + BeginDisabled(); + CheckboxFlags("io.BackendFlags: RendererHasTextures", &io.BackendFlags, ImGuiBackendFlags_RendererHasTextures); + EndDisabled(); + ShowFontSelector("Font"); + //BeginDisabled((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0); + if (DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) + style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. + SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); + SameLine(); MetricsHelpMarker("- This is scaling font only. General scaling will come later."); + DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 4.0f); + //BeginDisabled(io.ConfigDpiScaleFonts); + DragFloat("FontScaleDpi", &style.FontScaleDpi, 0.02f, 0.5f, 4.0f); + //SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); + //EndDisabled(); + if ((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + { + BulletText("Warning: Font scaling will NOT be smooth, because\nImGuiBackendFlags_RendererHasTextures is not set!"); + BulletText("For instructions, see:"); + SameLine(); + TextLinkOpenURL("docs/BACKENDS.md", "https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md"); + } + BulletText("Load a nice font for better results!"); + BulletText("Please submit feedback:"); + SameLine(); TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465"); + BulletText("Read FAQ for more details:"); + SameLine(); TextLinkOpenURL("dearimgui.com/faq", "https://www.dearimgui.com/faq/"); + //EndDisabled(); - Text("Read "); - SameLine(0, 0); - TextLinkOpenURL("https://www.dearimgui.com/faq/"); - SameLine(0, 0); - Text(" for details on font loading."); + SeparatorText("Font List"); ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; Checkbox("Show font preview", &cfg->ShowFontPreview); + // Font loaders + if (TreeNode("Loader", "Loader: \'%s\'", atlas->FontLoaderName ? atlas->FontLoaderName : "NULL")) + { + const ImFontLoader* loader_current = atlas->FontLoader; + BeginDisabled(!atlas->RendererHasTextures); +#ifdef IMGUI_ENABLE_STB_TRUETYPE + const ImFontLoader* loader_stbtruetype = ImFontAtlasGetFontLoaderForStbTruetype(); + if (RadioButton("stb_truetype", loader_current == loader_stbtruetype)) + atlas->SetFontLoader(loader_stbtruetype); +#else + BeginDisabled(); + RadioButton("stb_truetype", false); + SetItemTooltip("Requires #define IMGUI_ENABLE_STB_TRUETYPE"); + EndDisabled(); +#endif + SameLine(); +#ifdef IMGUI_ENABLE_FREETYPE + const ImFontLoader* loader_freetype = ImGuiFreeType::GetFontLoader(); + if (RadioButton("FreeType", loader_current == loader_freetype)) + atlas->SetFontLoader(loader_freetype); + if (loader_current == loader_freetype) + { + unsigned int loader_flags = atlas->FontLoaderFlags; + Text("Shared FreeType Loader Flags: 0x%08X", loader_flags); + if (ImGuiFreeType::DebugEditFontLoaderFlags(&loader_flags)) + { + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontDestroyOutput(atlas, font); + atlas->FontLoaderFlags = loader_flags; + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontInitOutput(atlas, font); + } + } +#else + BeginDisabled(); + RadioButton("FreeType", false); + SetItemTooltip("Requires #define IMGUI_ENABLE_FREETYPE + imgui_freetype.cpp."); + EndDisabled(); +#endif + EndDisabled(); + TreePop(); + } + // Font list for (ImFont* font : atlas->Fonts) { @@ -15521,13 +16005,101 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) DebugNodeFont(font); PopID(); } - if (TreeNode("Font Atlas", "Font Atlas (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + + SeparatorText("Font Atlas"); + if (Button("Compact")) + atlas->CompactCache(); + SameLine(); + if (Button("Grow")) + ImFontAtlasTextureGrow(atlas); + SameLine(); + if (Button("Clear All")) + ImFontAtlasBuildClear(atlas); + SetItemTooltip("Destroy cache and custom rectangles."); + + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + ImTextureData* tex = atlas->TexList[tex_n]; + if (tex_n > 0) + SameLine(); + Text("Tex: %dx%d", tex->Width, tex->Height); + } + const int packed_surface_sqrt = (int)sqrtf((float)atlas->Builder->RectsPackedSurface); + const int discarded_surface_sqrt = (int)sqrtf((float)atlas->Builder->RectsDiscardedSurface); + Text("Packed rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsPackedCount, atlas->Builder->RectsPackedSurface, packed_surface_sqrt, packed_surface_sqrt); + Text("incl. Discarded rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsDiscardedCount, atlas->Builder->RectsDiscardedSurface, discarded_surface_sqrt, discarded_surface_sqrt); + + ImFontAtlasRectId highlight_r_id = ImFontAtlasRectId_Invalid; + if (TreeNode("Rects Index", "Rects Index (%d)", atlas->Builder->RectsPackedCount)) // <-- Use count of used rectangles + { + PushStyleVar(ImGuiStyleVar_ImageBorderSize, 1.0f); + if (BeginTable("##table", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY, ImVec2(0.0f, GetTextLineHeightWithSpacing() * 12))) + { + for (const ImFontAtlasRectEntry& entry : atlas->Builder->RectsIndex) + if (entry.IsUsed) + { + ImFontAtlasRectId id = ImFontAtlasRectId_Make(atlas->Builder->RectsIndex.index_from_ptr(&entry), entry.Generation); + ImFontAtlasRect r = {}; + atlas->GetCustomRect(id, &r); + const char* buf; + ImFormatStringToTempBuffer(&buf, NULL, "ID:%08X, used:%d, { w:%3d, h:%3d } { x:%4d, y:%4d }", id, entry.IsUsed, r.w, r.h, r.x, r.y); + TableNextColumn(); + Selectable(buf); + if (IsItemHovered()) + highlight_r_id = id; + TableNextColumn(); + Image(atlas->TexRef, ImVec2(r.w, r.h), r.uv0, r.uv1); + } + EndTable(); + } + PopStyleVar(); + TreePop(); + } + + // Texture list + // (ensure the last texture always use the same ID, so we can keep it open neatly) + ImFontAtlasRect highlight_r; + if (highlight_r_id != ImFontAtlasRectId_Invalid) + atlas->GetCustomRect(highlight_r_id, &highlight_r); + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + if (tex_n == atlas->TexList.Size - 1) + SetNextItemOpen(true, ImGuiCond_Once); + DebugNodeTexture(atlas->TexList[tex_n], atlas->TexList.Size - 1 - tex_n, (highlight_r_id != ImFontAtlasRectId_Invalid) ? &highlight_r : NULL); + } +} + +void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect) +{ + ImGuiContext& g = *GImGui; + PushID(int_id); + if (TreeNode("", "Texture #%03d (%dx%d pixels)", tex->UniqueID, tex->Width, tex->Height)) { + ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; + Checkbox("Show used rect", &cfg->ShowTextureUsedRect); PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize)); - ImageWithBg(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImVec2 p = GetCursorScreenPos(); + if (tex->WantDestroyNextFrame) + Dummy(ImVec2((float)tex->Width, (float)tex->Height)); + else + ImageWithBg(tex->GetTexRef(), ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + if (cfg->ShowTextureUsedRect) + GetWindowDrawList()->AddRect(ImVec2(p.x + tex->UsedRect.x, p.y + tex->UsedRect.y), ImVec2(p.x + tex->UsedRect.x + tex->UsedRect.w, p.y + tex->UsedRect.y + tex->UsedRect.h), IM_COL32(255, 0, 255, 255)); + if (highlight_rect != NULL) + { + ImRect r_outer(p.x, p.y, p.x + tex->Width, p.y + tex->Height); + ImRect r_inner(p.x + highlight_rect->x, p.y + highlight_rect->y, p.x + highlight_rect->x + highlight_rect->w, p.y + highlight_rect->y + highlight_rect->h); + RenderRectFilledWithHole(GetWindowDrawList(), r_outer, r_inner, IM_COL32(0, 0, 0, 100), 0.0f); + GetWindowDrawList()->AddRect(r_inner.Min - ImVec2(1, 1), r_inner.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255)); + } PopStyleVar(); + + char texid_desc[30]; + Text("Status = %s (%d), Format = %s (%d), UseColors = %d", ImTextureDataGetStatusName(tex->Status), tex->Status, ImTextureDataGetFormatName(tex->Format), tex->Format, tex->UseColors); + Text("TexID = %s, BackendUserData = %p", FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), tex->TexID), tex->BackendUserData); TreePop(); } + PopID(); } void ImGui::ShowMetricsWindow(bool* p_open) @@ -15610,6 +16182,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) } }; +#ifdef IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS is enabled.\nMust disable after use! Otherwise Dear ImGui will run slower.\n"); +#endif + // Tools if (TreeNode("Tools")) { @@ -15769,6 +16345,14 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } + // Details for Fonts + for (ImFontAtlas* atlas : g.FontAtlases) + if (TreeNode((void*)atlas, "Fonts (%d), Textures (%d)", atlas->Fonts.Size, atlas->TexList.Size)) + { + ShowFontAtlas(atlas); + TreePop(); + } + // Details for Popups if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) { @@ -15805,14 +16389,6 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } - // Details for Fonts - ImFontAtlas* atlas = g.IO.Fonts; - if (TreeNode("Fonts", "Fonts (%d)", atlas->Fonts.Size)) - { - ShowFontAtlas(atlas); - TreePop(); - } - // Details for InputText if (TreeNode("InputText")) { @@ -16220,8 +16796,8 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con continue; } - char texid_desc[20]; - FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TextureId); + char texid_desc[30]; + FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd); char buf[300]; ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount / 3, texid_desc, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); @@ -16310,13 +16886,28 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co out_draw_list->Flags = backup_flags; } +// [DEBUG] Compute mask of inputs with the same codepoint. +static int CalcFontGlyphSrcOverlapMask(ImFontAtlas* atlas, ImFont* font, unsigned int codepoint) +{ + int mask = 0, count = 0; + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + { + ImFontConfig* src = font->Sources[src_n]; + if (!(src->FontLoader ? src->FontLoader : atlas->FontLoader)->FontSrcContainsGlyph(atlas, src, (ImWchar)codepoint)) + continue; + mask |= (1 << src_n); + count++; + } + return count > 1 ? mask : 0; +} + // [DEBUG] Display details for a single font, called by ShowStyleEditor(). void ImGui::DebugNodeFont(ImFont* font) { ImGuiContext& g = *GImGui; ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; - bool opened = TreeNode(font, "Font: \"%s\": %.2f px, %d glyphs, %d sources(s)", - font->Sources ? font->Sources[0].Name : "", font->FontSize, font->Glyphs.Size, font->SourcesCount); + ImFontAtlas* atlas = font->ContainerAtlas; + bool opened = TreeNode(font, "Font: \"%s\": %d sources(s)", font->GetDebugName(), font->Sources.Size); // Display preview text if (!opened) @@ -16324,7 +16915,7 @@ void ImGui::DebugNodeFont(ImFont* font) Indent(); if (cfg->ShowFontPreview) { - PushFont(font); + PushFont(font, 0.0f); Text("The quick brown fox jumps over the lazy dog"); PopFont(); } @@ -16336,90 +16927,182 @@ void ImGui::DebugNodeFont(ImFont* font) } if (SmallButton("Set as default")) GetIO().FontDefault = font; + SameLine(); + BeginDisabled(atlas->Fonts.Size <= 1 || atlas->Locked); + if (SmallButton("Remove")) + atlas->RemoveFont(font); + EndDisabled(); + SameLine(); + if (SmallButton("Clear bakes")) + ImFontAtlasFontDiscardBakes(atlas, font, 0); + SameLine(); + if (SmallButton("Clear unused")) + ImFontAtlasFontDiscardBakes(atlas, font, 2); // Display details +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS SetNextItemWidth(GetFontSize() * 8); DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); - SameLine(); MetricsHelpMarker( + /*SameLine(); MetricsHelpMarker( "Note that the default embedded font is NOT meant to be scaled.\n\n" "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " "You may oversample them to get some flexibility with scaling. " "You can also render at multiple sizes and select which one to use at runtime.\n\n" - "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); - Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)");*/ +#endif + char c_str[5]; - Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar); - Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar); - const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface); - Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); - for (int config_i = 0; config_i < font->SourcesCount; config_i++) - if (font->Sources) - { - const ImFontConfig* src = &font->Sources[config_i]; - int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); - BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", - config_i, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); - } + ImTextCharToUtf8(c_str, font->FallbackChar); + Text("Fallback character: '%s' (U+%04X)", c_str, font->FallbackChar); + ImTextCharToUtf8(c_str, font->EllipsisChar); + Text("Ellipsis character: '%s' (U+%04X)", c_str, font->EllipsisChar); - // Display all glyphs of the fonts in separate pages of 256 characters + for (int src_n = 0; src_n < font->Sources.Size; src_n++) { - if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + ImFontConfig* src = font->Sources[src_n]; + if (TreeNode(src, "Input %d: \'%s\', Oversample: %d,%d, PixelSnapH: %d, Offset: (%.1f,%.1f)", + src_n, src->Name, src->OversampleH, src->OversampleV, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y)) { - ImDrawList* draw_list = GetWindowDrawList(); - const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); - const float cell_size = font->FontSize * 1; - const float cell_spacing = GetStyle().ItemSpacing.y; - for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + Text("Loader: '%s'", loader->Name ? loader->Name : "N/A"); +#ifdef IMGUI_ENABLE_FREETYPE + if (loader->Name != NULL && strcmp(loader->Name, "FreeType") == 0) { - // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) - // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT - // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) - if (!(base & 8191) && font->IsGlyphRangeUnused(base, base + 8191)) + unsigned int loader_flags = src->FontLoaderFlags; + Text("FreeType Loader Flags: 0x%08X", loader_flags); + if (ImGuiFreeType::DebugEditFontLoaderFlags(&loader_flags)) { - base += 8192 - 256; - continue; + ImFontAtlasFontDestroyOutput(atlas, font); + src->FontLoaderFlags = loader_flags; + ImFontAtlasFontInitOutput(atlas, font); } - - int count = 0; - for (unsigned int n = 0; n < 256; n++) - if (font->FindGlyphNoFallback((ImWchar)(base + n))) - count++; - if (count <= 0) - continue; - if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) - continue; - - // Draw a 16x16 grid of glyphs - ImVec2 base_pos = GetCursorScreenPos(); - for (unsigned int n = 0; n < 256; n++) + } +#endif + TreePop(); + } + } + if (font->Sources.Size > 1 && TreeNode("Input Glyphs Overlap Detection Tool")) + { + TextWrapped("- First Input that contains the glyph is used.\n" + "- Use ImFontConfig::GlyphExcludeRanges[] to specify ranges to ignore glyph in given Input.\n- Prefer using a small number of ranges as the list is scanned every time a new glyph is loaded,\n - e.g. GlyphExcludeRanges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };\n- This tool doesn't cache results and is slow, don't keep it open!"); + if (BeginTable("table", 2)) + { + for (unsigned int c = 0; c < 0x10000; c++) + if (int overlap_mask = CalcFontGlyphSrcOverlapMask(atlas, font, c)) { - // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions - // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. - ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); - ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); - draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); - if (!glyph) - continue; - font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); - if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip()) + unsigned int c_end = c + 1; + while (c_end < 0x10000 && CalcFontGlyphSrcOverlapMask(atlas, font, c_end) == overlap_mask) + c_end++; + if (TableNextColumn() && TreeNode((void*)(intptr_t)c, "U+%04X-U+%04X: %d codepoints in %d inputs", c, c_end - 1, c_end - c, ImCountSetBits(overlap_mask))) { - DebugNodeFontGlyph(font, glyph); - EndTooltip(); + char utf8_buf[5]; + for (unsigned int n = c; n < c_end; n++) + { + ImTextCharToUtf8(utf8_buf, n); + BulletText("Codepoint U+%04X (%s)", n, utf8_buf); + } + TreePop(); } + TableNextColumn(); + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + if (overlap_mask & (1 << src_n)) + { + Text("%d ", src_n); + SameLine(); + } + c = c_end - 1; } - Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); - TreePop(); + EndTable(); + } + TreePop(); + } + + // Display all glyphs of the fonts in separate pages of 256 characters + for (int baked_n = 0; baked_n < atlas->Builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &atlas->Builder->BakedPool[baked_n]; + if (baked->ContainerFont != font) + continue; + PushID(baked_n); + if (TreeNode("Glyphs", "Baked at { %.2fpx, d.%.2f }: %d glyphs%s", baked->Size, baked->RasterizerDensity, baked->Glyphs.Size, (baked->LastUsedFrame < atlas->Builder->FrameCount - 1) ? " *Unused*" : "")) + { + if (SmallButton("Load all")) + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base++) + baked->FindGlyph((ImWchar)base); + + const int surface_sqrt = (int)ImSqrt((float)baked->MetricsTotalSurface); + Text("Ascent: %f, Descent: %f, Ascent-Descent: %f", baked->Ascent, baked->Descent, baked->Ascent - baked->Descent); + Text("Texture Area: about %d px ~%dx%d px", baked->MetricsTotalSurface, surface_sqrt, surface_sqrt); + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + { + ImFontConfig* src = font->Sources[src_n]; + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, baked, &oversample_h, &oversample_v); + BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", + src_n, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); } + + DebugNodeFontGlyphesForSrcMask(font, baked, ~0); TreePop(); } + PopID(); } TreePop(); Unindent(); } -void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) +void ImGui::DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask) +{ + ImDrawList* draw_list = GetWindowDrawList(); + const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); + const float cell_size = baked->Size * 1; + const float cell_spacing = GetStyle().ItemSpacing.y; + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + { + // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) + // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT + // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) + if (!(base & 8191) && font->IsGlyphRangeUnused(base, base + 8191)) + { + base += 8192 - 256; + continue; + } + + int count = 0; + for (unsigned int n = 0; n < 256; n++) + if (const ImFontGlyph* glyph = baked->IsGlyphLoaded((ImWchar)(base + n)) ? baked->FindGlyph((ImWchar)(base + n)) : NULL) + if (src_mask & (1 << glyph->SourceIdx)) + count++; + if (count <= 0) + continue; + if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) + continue; + + // Draw a 16x16 grid of glyphs + ImVec2 base_pos = GetCursorScreenPos(); + for (unsigned int n = 0; n < 256; n++) + { + // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions + // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); + const ImFontGlyph* glyph = baked->IsGlyphLoaded((ImWchar)(base + n)) ? baked->FindGlyph((ImWchar)(base + n)) : NULL; + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); + if (!glyph || (src_mask & (1 << glyph->SourceIdx)) == 0) + continue; + font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); + if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip()) + { + DebugNodeFontGlyph(font, glyph); + EndTooltip(); + } + } + Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); + TreePop(); + } +} + +void ImGui::DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph) { Text("Codepoint: U+%04X", glyph->Codepoint); Separator(); @@ -16427,6 +17110,12 @@ void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) Text("AdvanceX: %.1f", glyph->AdvanceX); Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + if (glyph->PackId >= 0) + { + ImTextureRect* r = ImFontAtlasPackGetRect(font->ContainerAtlas, glyph->PackId); + Text("PackId: %d (%dx%d rect at %d,%d)", glyph->PackId, r->w, r->h, r->x, r->y); + } + Text("SourceIdx: %d", glyph->SourceIdx); } // [DEBUG] Display contents of ImGuiStorage @@ -16703,7 +17392,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper); ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus); ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO); - //ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); + ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup); ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); @@ -17089,6 +17778,7 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns*) {} void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {} void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} void ImGui::DebugNodeFont(ImFont*) {} +void ImGui::DebugNodeFontGlyphesForSrcMask(ImFont*, ImFontBaked*, int) {} void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} @@ -17103,6 +17793,40 @@ void ImGui::DebugHookIdInfo(ImGuiID, ImGuiDataType, const void*, const void*) {} #endif // #ifndef IMGUI_DISABLE_DEBUG_TOOLS +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) +// Demo helper function to select among loaded fonts. +// Here we use the regular BeginCombo()/EndCombo() api which is the more flexible one. +void ImGui::ShowFontSelector(const char* label) +{ + ImGuiIO& io = GetIO(); + ImFont* font_current = GetFont(); + if (BeginCombo(label, font_current->GetDebugName())) + { + for (ImFont* font : io.Fonts->Fonts) + { + PushID((void*)font); + if (Selectable(font->GetDebugName(), font == font_current)) + io.FontDefault = font; + if (font == font_current) + SetItemDefaultFocus(); + PopID(); + } + EndCombo(); + } + SameLine(); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + MetricsHelpMarker( + "- Load additional fonts with io.Fonts->AddFontXXX() functions.\n" + "- Read FAQ and docs/FONTS.md for more details."); + else + MetricsHelpMarker( + "- Load additional fonts with io.Fonts->AddFontXXX() functions.\n" + "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" + "- Read FAQ and docs/FONTS.md for more details.\n" + "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); +} +#endif // #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) + //----------------------------------------------------------------------------- // Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. diff --git a/PopLib/imgui/core/imgui.h b/PopLib/imgui/core/imgui.h index 8e0e09b2..62a3a309 100644 --- a/PopLib/imgui/core/imgui.h +++ b/PopLib/imgui/core/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.0 WIP +// dear imgui, v1.92.2 WIP // (headers) // Help: @@ -28,16 +28,17 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.92.0 WIP" -#define IMGUI_VERSION_NUM 19197 -#define IMGUI_HAS_TABLE +#define IMGUI_VERSION "1.92.2 WIP" +#define IMGUI_VERSION_NUM 19212 +#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 +#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 /* Index of this file: // [SECTION] Header mess // [SECTION] Forward declarations and basic types -// [SECTION] Texture identifier (ImTextureID) +// [SECTION] Texture identifiers (ImTextureID, ImTextureRef) // [SECTION] Dear ImGui end-user API functions // [SECTION] Flags & Enumerations // [SECTION] Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) @@ -48,7 +49,8 @@ Index of this file: // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) // [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage) // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) -// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) +// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFontBaked, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) // [SECTION] ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformImeData) // [SECTION] Obsolete functions and types @@ -169,10 +171,15 @@ struct ImDrawListSplitter; // Helper to split a draw list into differen struct ImDrawVert; // A single vertex (pos + uv + col = 20 bytes by default. Override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) struct ImFont; // Runtime data for a single font within a parent ImFontAtlas struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader -struct ImFontBuilderIO; // Opaque interface to a font builder (stb_truetype or FreeType). +struct ImFontAtlasBuilder; // Opaque storage for building a ImFontAtlas +struct ImFontAtlasRect; // Output of ImFontAtlas::GetCustomRect() when using custom rectangles. +struct ImFontBaked; // Baked data for a ImFont at a given size. struct ImFontConfig; // Configuration data when adding a font or merging fonts struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data +struct ImFontLoader; // Opaque interface to a font loading backend (stb_truetype, FreeType etc.). +struct ImTextureData; // Specs and pixel storage for a texture used by Dear ImGui. +struct ImTextureRect; // Coordinates of a rectangle within a texture. struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using) // Forward declarations: ImGui layer @@ -224,7 +231,8 @@ typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A // - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. typedef int ImDrawFlags; // -> enum ImDrawFlags_ // Flags: for ImDrawList functions typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList instance -typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build +typedef int ImFontFlags; // -> enum ImFontFlags_ // Flags: for ImFont +typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas typedef int ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for InvisibleButton() typedef int ImGuiChildFlags; // -> enum ImGuiChildFlags_ // Flags: for BeginChild() @@ -301,18 +309,67 @@ struct ImVec4 IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- -// [SECTION] Texture identifier (ImTextureID) +// [SECTION] Texture identifiers (ImTextureID, ImTextureRef) //----------------------------------------------------------------------------- -// ImTexture: user data for renderer backend to identify a texture [Compile-time configurable type] -// - To use something else than an opaque void* pointer: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file. -// - This can be whatever to you want it to be! read the FAQ about ImTextureID for details. -// - You can make this a structure with various constructors if you need. You will have to implement ==/!= operators. -// - (note: before v1.91.4 (2024/10/08) the default type for ImTextureID was void*. Use intermediary intptr_t cast and read FAQ if you have casting warnings) +// ImTextureID = backend specific, low-level identifier for a texture uploaded in GPU/graphics system. +// [Compile-time configurable type] +// - When a Rendered Backend creates a texture, it store its native identifier into a ImTextureID value. +// (e.g. Used by DX11 backend to a `ID3D11ShaderResourceView*`; Used by OpenGL backends to store `GLuint`; +// Used by SDLGPU backend to store a `SDL_GPUTextureSamplerBinding*`, etc.). +// - User may submit their own textures to e.g. ImGui::Image() function by passing this value. +// - During the rendering loop, the Renderer Backend retrieve the ImTextureID, which stored inside a +// ImTextureRef, which is stored inside a ImDrawCmd. +// - Compile-time type configuration: +// - To use something other than a 64-bit value: add '#define ImTextureID MyTextureType*' in your imconfig.h file. +// - This can be whatever to you want it to be! read the FAQ entry about textures for details. +// - You may decide to store a higher-level structure containing texture, sampler, shader etc. with various +// constructors if you like. You will need to implement ==/!= operators. +// History: +// - In v1.91.4 (2024/10/08): the default type for ImTextureID was changed from 'void*' to 'ImU64'. This allowed backends requirig 64-bit worth of data to build on 32-bit architectures. Use intermediary intptr_t cast and read FAQ if you have casting warnings. +// - In v1.92.0 (2025/06/11): added ImTextureRef which carry either a ImTextureID either a pointer to internal texture atlas. All user facing functions taking ImTextureID changed to ImTextureRef #ifndef ImTextureID -typedef ImU64 ImTextureID; // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that) +typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or integer). A majority of backends are ok with that. #endif +// Define this if you need 0 to be a valid ImTextureID for your backend. +#ifndef ImTextureID_Invalid +#define ImTextureID_Invalid ((ImTextureID)0) +#endif + +// ImTextureRef = higher-level identifier for a texture. Store a ImTextureID _or_ a ImTextureData*. +// The identifier is valid even before the texture has been uploaded to the GPU/graphics system. +// This is what gets passed to functions such as `ImGui::Image()`, `ImDrawList::AddImage()`. +// This is what gets stored in draw commands (`ImDrawCmd`) to identify a texture during rendering. +// - When a texture is created by user code (e.g. custom images), we directly stores the low-level ImTextureID. +// Because of this, when displaying your own texture you are likely to ever only manage ImTextureID values on your side. +// - When a texture is created by the backend, we stores a ImTextureData* which becomes an indirection +// to extract the ImTextureID value during rendering, after texture upload has happened. +// - To create a ImTextureRef from a ImTextureData you can use ImTextureData::GetTexRef(). +// We intentionally do not provide an ImTextureRef constructor for this: we don't expect this +// to be frequently useful to the end-user, and it would be erroneously called by many legacy code. +// - If you want to bind the current atlas when using custom rectangle, you can use io.Fonts->TexRef. +// - Binding generators for languages such as C (which don't have constructors), should provide a helper, e.g. +// inline ImTextureRef ImTextureRefFromID(ImTextureID tex_id) { ImTextureRef tex_ref = { ._TexData = NULL, .TexID = tex_id }; return tex_ref; } +// In 1.92 we changed most drawing functions using ImTextureID to use ImTextureRef. +// We intentionally do not provide an implicit ImTextureRef -> ImTextureID cast operator because it is technically lossy to convert ImTextureRef to ImTextureID before rendering. +IM_MSVC_RUNTIME_CHECKS_OFF +struct ImTextureRef +{ + ImTextureRef() { _TexData = NULL; _TexID = ImTextureID_Invalid; } + ImTextureRef(ImTextureID tex_id) { _TexData = NULL; _TexID = tex_id; } +#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(ImTextureID) + ImTextureRef(void* tex_id) { _TexData = NULL; _TexID = (ImTextureID)(size_t)tex_id; } // For legacy backends casting to ImTextureID +#endif + + inline ImTextureID GetTexID() const; // == (_TexData ? _TexData->TexID : _TexID) // Implemented below in the file. + + // Members (either are set, never both!) + ImTextureData* _TexData; // A texture, generally owned by a ImFontAtlas. Will convert to ImTextureID during render loop, after texture has been uploaded. + ImTextureID _TexID; // _OR_ Low-level backend texture identifier, if already uploaded or created by user/app. Generally provided to e.g. ImGui::Image() calls. +}; +IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] Dear ImGui end-user API functions // (Note that ImGui:: being a namespace, you can add extra ImGui:: functions in your own separate file. Please don't modify imgui source files!) @@ -336,7 +393,7 @@ namespace ImGui IMGUI_API void NewFrame(); // start a new Dear ImGui frame, you can submit any command from this point until Render()/EndFrame(). IMGUI_API void EndFrame(); // ends the Dear ImGui frame. automatically called by Render(). If you don't need to render data (skipping rendering) you may call EndFrame() without Render()... but you'll have wasted CPU already! If you don't need to render, better to not create any windows and not call NewFrame() at all! IMGUI_API void Render(); // ends the Dear ImGui frame, finalize the draw data. You can then get call GetDrawData(). - IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). this is what you have to render. + IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). Call ImGui_ImplXXXX_RenderDrawData() function in your Renderer Backend to render. // Demo, Debug, Information IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create Demo window. demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! @@ -418,7 +475,6 @@ namespace ImGui IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / top-most. prefer using SetNextWindowFocus(). - IMGUI_API void SetWindowFontScale(float scale); // [OBSOLETE] set font scale. Adjust IO.FontGlobalScale if you want to scale all windows. This is an old API! For correct scaling, prefer to reload font + rebuild ImFontAtlas + call style.ScaleAllSizes(). IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state @@ -438,9 +494,29 @@ namespace ImGui IMGUI_API void SetScrollFromPosX(float local_x, float center_x_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. IMGUI_API void SetScrollFromPosY(float local_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. - // Parameters stacks (shared) - IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + // Parameters stacks (font) + // - PushFont(font, 0.0f) // Change font and keep current size + // - PushFont(NULL, 20.0f) // Keep font and change current size + // - PushFont(font, 20.0f) // Change font and set size to 20.0f + // - PushFont(font, style.FontSizeBase * 2.0f) // Change font and set size to be twice bigger than current size. + // - PushFont(font, font->LegacySize) // Change font and set size to size passed to AddFontXXX() function. Same as pre-1.92 behavior. + // *IMPORTANT* before 1.92, fonts had a single size. They can now be dynamically be adjusted. + // - In 1.92 we have REMOVED the single parameter version of PushFont() because it seems like the easiest way to provide an error-proof transition. + // - PushFont(font) before 1.92 = PushFont(font, font->LegacySize) after 1.92 // Use default font size as passed to AddFontXXX() function. + // *IMPORTANT* global scale factors are applied over the provided size. + // - Global scale factors are: 'style.FontScaleMain', 'style.FontScaleDpi' and maybe more. + // - If you want to apply a factor to the _current_ font size: + // - CORRECT: PushFont(NULL, style.FontSizeBase) // use current unscaled size == does nothing + // - CORRECT: PushFont(NULL, style.FontSizeBase * 2.0f) // use current unscaled size x2 == make text twice bigger + // - INCORRECT: PushFont(NULL, GetFontSize()) // INCORRECT! using size after global factors already applied == GLOBAL SCALING FACTORS WILL APPLY TWICE! + // - INCORRECT: PushFont(NULL, GetFontSize() * 2.0f) // INCORRECT! using size after global factors already applied == GLOBAL SCALING FACTORS WILL APPLY TWICE! + IMGUI_API void PushFont(ImFont* font, float font_size_base_unscaled); // Use NULL as a shortcut to keep current font. Use 0.0f to keep current size. IMGUI_API void PopFont(); + IMGUI_API ImFont* GetFont(); // get current font + IMGUI_API float GetFontSize(); // get current scaled font size (= height in pixels). AFTER global scale factors applied. *IMPORTANT* DO NOT PASS THIS VALUE TO PushFont()! Use ImGui::GetStyle().FontSizeBase to get value before global scale factors. + IMGUI_API ImFontBaked* GetFontBaked(); // get current font bound at current size // == GetFont()->GetFontBaked(GetFontSize()) + + // Parameters stacks (shared) IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); // modify a style color. always use this if you modify the style after NewFrame(). IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); IMGUI_API void PopStyleColor(int count = 1); @@ -462,8 +538,6 @@ namespace ImGui // Style read access // - Use the ShowStyleEditor() function to interactively see/edit the colors. - IMGUI_API ImFont* GetFont(); // get current font - IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a white pixel, useful to draw custom shapes via the ImDrawList API IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier, packed as a 32-bit value suitable for ImDrawList IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList @@ -562,14 +636,14 @@ namespace ImGui IMGUI_API bool TextLinkOpenURL(const char* label, const char* url = NULL); // hyperlink text button, automatically open file/url when clicked // Widgets: Images - // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples + // - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. // - Image() pads adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side. // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. // - An obsolete version of Image(), before 1.91.9 (March 2025), had a 'tint_col' parameter which is now supported by the ImageWithBg() function. - IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1)); - IMGUI_API void ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); - IMGUI_API bool ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); + IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1)); + IMGUI_API void ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); + IMGUI_API bool ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); // Widgets: Combo Box (Dropdown) // - The BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() items. @@ -1461,7 +1535,7 @@ enum ImGuiKey : int ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, - ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_LeftAlt, ImGuiKey_LeftSuper, + ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_LeftAlt, ImGuiKey_LeftSuper, // Also see ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiMod_Alt, ImGuiMod_Super below! ImGuiKey_RightCtrl, ImGuiKey_RightShift, ImGuiKey_RightAlt, ImGuiKey_RightSuper, ImGuiKey_Menu, ImGuiKey_0, ImGuiKey_1, ImGuiKey_2, ImGuiKey_3, ImGuiKey_4, ImGuiKey_5, ImGuiKey_6, ImGuiKey_7, ImGuiKey_8, ImGuiKey_9, @@ -1501,32 +1575,34 @@ enum ImGuiKey : int ImGuiKey_AppForward, ImGuiKey_Oem102, // Non-US backslash. - // Gamepad (some of those are analog values, 0.0f to 1.0f) // NAVIGATION ACTION + // Gamepad + // (analog values are 0.0f to 1.0f) // (download controller mapping PNG/PSD at http://dearimgui.com/controls_sheets) - ImGuiKey_GamepadStart, // Menu (Xbox) + (Switch) Start/Options (PS) - ImGuiKey_GamepadBack, // View (Xbox) - (Switch) Share (PS) - ImGuiKey_GamepadFaceLeft, // X (Xbox) Y (Switch) Square (PS) // Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows) - ImGuiKey_GamepadFaceRight, // B (Xbox) A (Switch) Circle (PS) // Cancel / Close / Exit - ImGuiKey_GamepadFaceUp, // Y (Xbox) X (Switch) Triangle (PS) // Text Input / On-screen Keyboard - ImGuiKey_GamepadFaceDown, // A (Xbox) B (Switch) Cross (PS) // Activate / Open / Toggle / Tweak - ImGuiKey_GamepadDpadLeft, // D-pad Left // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadDpadRight, // D-pad Right // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadDpadUp, // D-pad Up // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadDpadDown, // D-pad Down // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadL1, // L Bumper (Xbox) L (Switch) L1 (PS) // Tweak Slower / Focus Previous (in Windowing mode) - ImGuiKey_GamepadR1, // R Bumper (Xbox) R (Switch) R1 (PS) // Tweak Faster / Focus Next (in Windowing mode) - ImGuiKey_GamepadL2, // L Trig. (Xbox) ZL (Switch) L2 (PS) [Analog] - ImGuiKey_GamepadR2, // R Trig. (Xbox) ZR (Switch) R2 (PS) [Analog] - ImGuiKey_GamepadL3, // L Stick (Xbox) L3 (Switch) L3 (PS) - ImGuiKey_GamepadR3, // R Stick (Xbox) R3 (Switch) R3 (PS) - ImGuiKey_GamepadLStickLeft, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadLStickRight, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadLStickUp, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadLStickDown, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadRStickLeft, // [Analog] - ImGuiKey_GamepadRStickRight, // [Analog] - ImGuiKey_GamepadRStickUp, // [Analog] - ImGuiKey_GamepadRStickDown, // [Analog] + // // XBOX | SWITCH | PLAYSTA. | -> ACTION + ImGuiKey_GamepadStart, // Menu | + | Options | + ImGuiKey_GamepadBack, // View | - | Share | + ImGuiKey_GamepadFaceLeft, // X | Y | Square | Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows) + ImGuiKey_GamepadFaceRight, // B | A | Circle | Cancel / Close / Exit + ImGuiKey_GamepadFaceUp, // Y | X | Triangle | Text Input / On-screen Keyboard + ImGuiKey_GamepadFaceDown, // A | B | Cross | Activate / Open / Toggle / Tweak + ImGuiKey_GamepadDpadLeft, // D-pad Left | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadDpadRight, // D-pad Right | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadDpadUp, // D-pad Up | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadDpadDown, // D-pad Down | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadL1, // L Bumper | L | L1 | Tweak Slower / Focus Previous (in Windowing mode) + ImGuiKey_GamepadR1, // R Bumper | R | R1 | Tweak Faster / Focus Next (in Windowing mode) + ImGuiKey_GamepadL2, // L Trigger | ZL | L2 | [Analog] + ImGuiKey_GamepadR2, // R Trigger | ZR | R2 | [Analog] + ImGuiKey_GamepadL3, // L Stick | L3 | L3 | + ImGuiKey_GamepadR3, // R Stick | R3 | R3 | + ImGuiKey_GamepadLStickLeft, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadLStickRight, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadLStickUp, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadLStickDown, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadRStickLeft, // | | | [Analog] + ImGuiKey_GamepadRStickRight, // | | | [Analog] + ImGuiKey_GamepadRStickUp, // | | | [Analog] + ImGuiKey_GamepadRStickDown, // | | | [Analog] // Aliases: Mouse Buttons (auto-submitted from AddMouseButtonEvent() calls) // - This is mirroring the data also written to io.MouseDown[], io.MouseWheel, in a format allowing them to be accessed via standard key API. @@ -1534,11 +1610,15 @@ enum ImGuiKey : int // [Internal] Reserved for mod storage ImGuiKey_ReservedForModCtrl, ImGuiKey_ReservedForModShift, ImGuiKey_ReservedForModAlt, ImGuiKey_ReservedForModSuper, + + // [Internal] If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. ImGuiKey_NamedKey_END, + ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, // Keyboard Modifiers (explicitly submitted by backend via AddKeyEvent() calls) - // - This is mirroring the data also written to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper, in a format allowing - // them to be accessed via standard key API, allowing calls such as IsKeyPressed(), IsKeyReleased(), querying duration etc. + // - Any functions taking a ImGuiKeyChord parameter can binary-or those with regular keys, e.g. Shortcut(ImGuiMod_Ctrl | ImGuiKey_S). + // - Those are written back into io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper for convenience, + // but may be accessed via standard key API such as IsKeyPressed(), IsKeyReleased(), querying duration etc. // - Code polling every key (e.g. an interface to detect a key press for input mapping) might want to ignore those // and prefer using the real keys (e.g. ImGuiKey_LeftCtrl, ImGuiKey_RightCtrl instead of ImGuiMod_Ctrl). // - In theory the value of keyboard modifiers should be roughly equivalent to a logical or of the equivalent left/right keys. @@ -1552,11 +1632,6 @@ enum ImGuiKey : int ImGuiMod_Super = 1 << 15, // Windows/Super (non-macOS), Ctrl (macOS) ImGuiMod_Mask_ = 0xF000, // 4-bits - // [Internal] If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. - ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, - //ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT, // Size of KeysData[]: only hold named keys - //ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN, // Accesses to io.KeysData[] must use (key - ImGuiKey_NamedKey_BEGIN) index. - #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiKey_COUNT = ImGuiKey_NamedKey_END, // Obsoleted in 1.91.5 because it was extremely misleading (since named keys don't start at 0 anymore) ImGuiMod_Shortcut = ImGuiMod_Ctrl, // Removed in 1.90.7, you can now simply use ImGuiMod_Ctrl @@ -1618,6 +1693,7 @@ enum ImGuiBackendFlags_ ImGuiBackendFlags_HasMouseCursors = 1 << 1, // Backend Platform supports honoring GetMouseCursor() value to change the OS cursor shape. ImGuiBackendFlags_HasSetMousePos = 1 << 2, // Backend Platform supports io.WantSetMousePos requests to reposition the OS mouse position (only used if io.ConfigNavMoveSetMousePos is set). ImGuiBackendFlags_RendererHasVtxOffset = 1 << 3, // Backend Renderer supports ImDrawCmd::VtxOffset. This enables output of large meshes (64K+ vertices) while still using 16-bit indices. + ImGuiBackendFlags_RendererHasTextures = 1 << 4, // Backend Renderer supports ImTextureData requests to create/update/destroy textures. This enables incremental texture updates and texture reloads. See https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md for instructions on how to upgrade your custom backend. }; // Enumeration for PushStyleColor() / PopStyleColor() @@ -2156,6 +2232,12 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE struct ImGuiStyle { + // Font scaling + // - recap: ImGui::GetFontSize() == FontSizeBase * (FontScaleMain * FontScaleDpi * other_scaling_factors) + float FontSizeBase; // Current base font size before external global factors are applied. Use PushFont(NULL, size) to modify. Use ImGui::GetFontSize() to obtain scaled value. + float FontScaleMain; // Main global scale factor. May be set by application once, or exposed to end-user. + float FontScaleDpi; // Additional global scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. + float Alpha; // Global alpha applies to everything in Dear ImGui. float DisabledAlpha; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. ImVec2 WindowPadding; // Padding within a window. @@ -2221,8 +2303,13 @@ struct ImGuiStyle ImGuiHoveredFlags HoverFlagsForTooltipMouse;// Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. ImGuiHoveredFlags HoverFlagsForTooltipNav; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. - IMGUI_API ImGuiStyle(); - IMGUI_API void ScaleAllSizes(float scale_factor); + // [Internal] + float _MainScale; // FIXME-WIP: Reference scale, as applied by ScaleAllSizes(). + float _NextFrameFontSizeBase; // FIXME: Temporary hack until we finish remaining work. + + // Functions + IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -2260,7 +2347,8 @@ struct ImGuiIO ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Keyboard/Gamepad navigation options, etc. ImGuiBackendFlags BackendFlags; // = 0 // See ImGuiBackendFlags_ enum. Set by backend (imgui_impl_xxx files or custom backend) to communicate features supported by the backend. - ImVec2 DisplaySize; // // Main display size, in pixels (generally == GetMainViewport()->Size). May change every frame. + ImVec2 DisplaySize; // // Main display size, in pixels (== GetMainViewport()->Size). May change every frame. + ImVec2 DisplayFramebufferScale; // = (1, 1) // Main display density. For retina display where window coordinates are different from framebuffer coordinates. This will affect font density + will end up in ImDrawData::FramebufferScale. float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. May change every frame. float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. const char* IniFilename; // = "imgui.ini" // Path to .ini file (important: default "imgui.ini" is relative to current working dir!). Set NULL to disable automatic .ini loading/saving or if you want to manually call LoadIniSettingsXXX() / SaveIniSettingsXXX() functions. @@ -2269,10 +2357,8 @@ struct ImGuiIO // Font system ImFontAtlas*Fonts; // // Font atlas: load, rasterize and pack one or more fonts into a single texture. - float FontGlobalScale; // = 1.0f // Global scale all fonts - bool FontAllowUserScaling; // = false // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel. ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. - ImVec2 DisplayFramebufferScale; // = (1, 1) // For retina display or other situations where window coordinates are different from framebuffer coordinates. This generally ends up in ImDrawData::FramebufferScale. + bool FontAllowUserScaling; // = false // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel. // Keyboard/Gamepad Navigation options bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout. @@ -2428,14 +2514,14 @@ struct ImGuiIO float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. >0 scrolls Up, <0 scrolls Down. Hold SHIFT to turn vertical scroll into horizontal scroll. float MouseWheelH; // Mouse wheel Horizontal. >0 scrolls Left, <0 scrolls Right. Most users don't have a mouse with a horizontal wheel, may not be filled by all backends. ImGuiMouseSource MouseSource; // Mouse actual input peripheral (Mouse/TouchScreen/Pen). - bool KeyCtrl; // Keyboard modifier down: Control + bool KeyCtrl; // Keyboard modifier down: Ctrl (non-macOS), Cmd (macOS) bool KeyShift; // Keyboard modifier down: Shift bool KeyAlt; // Keyboard modifier down: Alt - bool KeySuper; // Keyboard modifier down: Cmd/Super/Windows + bool KeySuper; // Keyboard modifier down: Windows/Super (non-macOS), Ctrl (macOS) // Other state maintained from data above + IO function calls ImGuiKeyChord KeyMods; // Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags. Read-only, updated by NewFrame() - ImGuiKeyData KeysData[ImGuiKey_NamedKey_COUNT];// Key state for all known keys. Use IsKeyXXX() functions to access this. + ImGuiKeyData KeysData[ImGuiKey_NamedKey_COUNT];// Key state for all known keys. MUST use 'key - ImGuiKey_NamedKey_BEGIN' as index. Use IsKeyXXX() functions to access this. bool WantCaptureMouseUnlessPopupClose; // Alternative to WantCaptureMouse: (WantCaptureMouse == true && WantCaptureMouseUnlessPopupClose == false) when a click over void is expected to close a popup. ImVec2 MousePosPrev; // Previous mouse position (note that MouseDelta is not necessary == MousePos-MousePosPrev, in case either position is invalid) ImVec2 MouseClickedPos[5]; // Position at time of clicking @@ -2469,9 +2555,11 @@ struct ImGuiIO //float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + float FontGlobalScale; // Moved io.FontGlobalScale to style.FontScaleMain in 1.92 (June 2025) + // Legacy: before 1.91.1, clipboard functions were stored in ImGuiIO instead of ImGuiPlatformIO. // As this is will affect all users of custom engines/backends, we are providing proper legacy redirection (will obsolete). -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS const char* (*GetClipboardTextFn)(void* user_data); void (*SetClipboardTextFn)(void* user_data, const char* text); void* ClipboardUserData; @@ -2708,7 +2796,7 @@ struct ImGuiListClipper int DisplayEnd; // End of items to display (exclusive) int ItemsCount; // [Internal] Number of items float ItemsHeight; // [Internal] Height of item after a first step and item submission can calculate it - float StartPosY; // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed + double StartPosY; // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed double StartSeekOffsetY; // [Internal] Account for frozen rows in a table and initial loss of precision in very large windows. void* TempData; // [Internal] Internal data @@ -2746,31 +2834,31 @@ struct ImGuiListClipper #define IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED IM_MSVC_RUNTIME_CHECKS_OFF // ImVec2 operators -static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } -static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } -static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } -static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } -static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } -static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } -static inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } -static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } -static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } -static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } -static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } -static inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } -static inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } -static inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } -static inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } +inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } +inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } +inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } +inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } +inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } +inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } +inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } +inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } +inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } +inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } // ImVec4 operators -static inline ImVec4 operator*(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); } -static inline ImVec4 operator/(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); } -static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } -static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } -static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } -static inline ImVec4 operator/(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); } -static inline ImVec4 operator-(const ImVec4& lhs) { return ImVec4(-lhs.x, -lhs.y, -lhs.z, -lhs.w); } -static inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } -static inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } +inline ImVec4 operator*(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); } +inline ImVec4 operator/(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); } +inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } +inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } +inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } +inline ImVec4 operator/(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); } +inline ImVec4 operator-(const ImVec4& lhs) { return ImVec4(-lhs.x, -lhs.y, -lhs.z, -lhs.w); } +inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } +inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } IM_MSVC_RUNTIME_CHECKS_RESTORE #endif @@ -3004,11 +3092,11 @@ typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* c // - VtxOffset: When 'io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset' is enabled, // this fields allow us to render meshes larger than 64K vertices while keeping 16-bit indices. // Backends made for <1.71. will typically ignore the VtxOffset fields. -// - The ClipRect/TextureId/VtxOffset fields must be contiguous as we memcmp() them together (this is asserted for). +// - The ClipRect/TexRef/VtxOffset fields must be contiguous as we memcmp() them together (this is asserted for). struct ImDrawCmd { ImVec4 ClipRect; // 4*4 // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in "viewport" coordinates - ImTextureID TextureId; // 4-8 // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + ImTextureRef TexRef; // 16 // Reference to a font/texture atlas (where backend called ImTextureData::SetTexID()) or to a user-provided texture ID (via e.g. ImGui::Image() calls). Both will lead to a ImTextureID value. unsigned int VtxOffset; // 4 // Start offset in vertex buffer. ImGuiBackendFlags_RendererHasVtxOffset: always 0, otherwise may be >0 to support meshes larger than 64K vertices with 16-bit indices. unsigned int IdxOffset; // 4 // Start offset in index buffer. unsigned int ElemCount; // 4 // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. @@ -3020,7 +3108,8 @@ struct ImDrawCmd ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) - inline ImTextureID GetTexID() const { return TextureId; } + // Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used! + inline ImTextureID GetTexID() const; // == (TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID }; // Vertex layout @@ -3043,7 +3132,7 @@ IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; struct ImDrawCmdHeader { ImVec4 ClipRect; - ImTextureID TextureId; + ImTextureRef TexRef; unsigned int VtxOffset; }; @@ -3128,7 +3217,7 @@ struct ImDrawList ImDrawCmdHeader _CmdHeader; // [Internal] template of active commands. Fields should match those of CmdBuffer.back(). ImDrawListSplitter _Splitter; // [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!) ImVector _ClipRectStack; // [Internal] - ImVector _TextureIdStack; // [Internal] + ImVector _TextureStack; // [Internal] ImVector _CallbacksDataBuf; // [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content const char* _OwnerName; // Pointer to owner window's name for debugging @@ -3141,8 +3230,8 @@ struct ImDrawList IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) IMGUI_API void PushClipRectFullScreen(); IMGUI_API void PopClipRect(); - IMGUI_API void PushTextureID(ImTextureID texture_id); - IMGUI_API void PopTextureID(); + IMGUI_API void PushTexture(ImTextureRef tex_ref); + IMGUI_API void PopTexture(); inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } @@ -3180,12 +3269,12 @@ struct ImDrawList IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); // Image primitives - // - Read FAQ to understand what ImTextureID is. + // - Read FAQ to understand what ImTextureID/ImTextureRef are. // - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle. // - "uv_min" and "uv_max" represent the normalized texture coordinates to use for those corners. Using (0,0)->(1,1) texture coordinates will generally display the entire texture. - IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE); - IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE); - IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); + IMGUI_API void AddImage(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE); + IMGUI_API void AddImageQuad(ImTextureRef tex_ref, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE); + IMGUI_API void AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); // Stateful path API, add points then finish with PathFillConvex() or PathStroke() // - Important: filled shapes must always use clockwise winding order! The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. @@ -3241,6 +3330,10 @@ struct ImDrawList inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } // Write vertex with unique index // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline void PushTextureID(ImTextureRef tex_ref) { PushTexture(tex_ref); } // RENAMED in 1.92.x + inline void PopTextureID() { PopTexture(); } // RENAMED in 1.92.x +#endif //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments = 0) { PathEllipticalArcTo(center, ImVec2(radius_x, radius_y), rot, a_min, a_max, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) @@ -3248,14 +3341,15 @@ struct ImDrawList //inline void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); } // OBSOLETED in 1.80 (Jan 2021) // [Internal helpers] + IMGUI_API void _SetDrawListSharedData(ImDrawListSharedData* data); IMGUI_API void _ResetForNewFrame(); IMGUI_API void _ClearFreeMemory(); IMGUI_API void _PopUnusedDrawCmd(); IMGUI_API void _TryMergeDrawCmds(); IMGUI_API void _OnChangedClipRect(); - IMGUI_API void _OnChangedTextureID(); + IMGUI_API void _OnChangedTexture(); IMGUI_API void _OnChangedVtxOffset(); - IMGUI_API void _SetTextureID(ImTextureID texture_id); + IMGUI_API void _SetTexture(ImTextureRef tex_ref); IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); @@ -3267,14 +3361,15 @@ struct ImDrawList struct ImDrawData { bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. - int CmdListsCount; // Number of ImDrawList* to render (should always be == CmdLists.size) + int CmdListsCount; // == CmdLists.Size. (OBSOLETE: exists for legacy reasons). Number of ImDrawList* to render. int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size ImVector CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. ImVec2 DisplayPos; // Top-left position of the viewport to render (== top-left of the orthogonal projection matrix to use) (== GetMainViewport()->Pos for the main viewport, == (0.0) in most single-viewport applications) ImVec2 DisplaySize; // Size of the viewport to render (== GetMainViewport()->Size for the main viewport, == io.DisplaySize in most single-viewport applications) - ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Based on io.DisplayFramebufferScale. Generally (1,1) on normal display, (2,2) on OSX with Retina display. + ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Copied from viewport->FramebufferScale (== io.DisplayFramebufferScale for main viewport). Generally (1,1) on normal display, (2,2) on OSX with Retina display. ImGuiViewport* OwnerViewport; // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not). + ImVector* Textures; // List of textures to update. Most of the times the list is shared by all ImDrawData, has only 1 texture and it doesn't need any update. This almost always points to ImGui::GetPlatformIO().Textures[]. May be overriden or set to NULL if you want to manually update textures. // Functions ImDrawData() { Clear(); } @@ -3284,6 +3379,87 @@ struct ImDrawData IMGUI_API void ScaleClipRects(const ImVec2& fb_scale); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than Dear ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. }; +//----------------------------------------------------------------------------- +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) +//----------------------------------------------------------------------------- +// In principle, the only data types that user/application code should care about are 'ImTextureRef' and 'ImTextureID'. +// They are defined above in this header file. Read their description to the difference between ImTextureRef and ImTextureID. +// FOR ALL OTHER ImTextureXXXX TYPES: ONLY CORE LIBRARY AND RENDERER BACKENDS NEED TO KNOW AND CARE ABOUT THEM. +//----------------------------------------------------------------------------- + +#undef Status // X11 headers are leaking this. + +// We intentionally support a limited amount of texture formats to limit burden on CPU-side code and extension. +// Most standard backends only support RGBA32 but we provide a single channel option for low-resource/embedded systems. +enum ImTextureFormat +{ + ImTextureFormat_RGBA32, // 4 components per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + ImTextureFormat_Alpha8, // 1 component per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight +}; + +// Status of a texture to communicate with Renderer Backend. +enum ImTextureStatus +{ + ImTextureStatus_OK, + ImTextureStatus_Destroyed, // Backend destroyed the texture. + ImTextureStatus_WantCreate, // Requesting backend to create the texture. Set status OK when done. + ImTextureStatus_WantUpdates, // Requesting backend to update specific blocks of pixels (write to texture portions which have never been used before). Set status OK when done. + ImTextureStatus_WantDestroy, // Requesting backend to destroy the texture. Set status to Destroyed when done. +}; + +// Coordinates of a rectangle within a texture. +// When a texture is in ImTextureStatus_WantUpdates state, we provide a list of individual rectangles to copy to the graphics system. +// You may use ImTextureData::Updates[] for the list, or ImTextureData::UpdateBox for a single bounding box. +struct ImTextureRect +{ + unsigned short x, y; // Upper-left coordinates of rectangle to update + unsigned short w, h; // Size of rectangle to update (in pixels) +}; + +// Specs and pixel storage for a texture used by Dear ImGui. +// This is only useful for (1) core library and (2) backends. End-user/applications do not need to care about this. +// Renderer Backends will create a GPU-side version of this. +// Why does we store two identifiers: TexID and BackendUserData? +// - ImTextureID TexID = lower-level identifier stored in ImDrawCmd. ImDrawCmd can refer to textures not created by the backend, and for which there's no ImTextureData. +// - void* BackendUserData = higher-level opaque storage for backend own book-keeping. Some backends may have enough with TexID and not need both. + // In columns below: who reads/writes each fields? 'r'=read, 'w'=write, 'core'=main library, 'backend'=renderer backend +struct ImTextureData +{ + //------------------------------------------ core / backend --------------------------------------- + int UniqueID; // w - // [DEBUG] Sequential index to facilitate identifying a texture when debugging/printing. Unique per atlas. + ImTextureStatus Status; // rw rw // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy. Always use SetStatus() to modify! + void* BackendUserData; // - rw // Convenience storage for backend. Some backends may have enough with TexID. + ImTextureID TexID; // r w // Backend-specific texture identifier. Always use SetTexID() to modify! The identifier will stored in ImDrawCmd::GetTexID() and passed to backend's RenderDrawData function. + ImTextureFormat Format; // w r // ImTextureFormat_RGBA32 (default) or ImTextureFormat_Alpha8 + int Width; // w r // Texture width + int Height; // w r // Texture height + int BytesPerPixel; // w r // 4 or 1 + unsigned char* Pixels; // w r // Pointer to buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. + ImTextureRect UsedRect; // w r // Bounding box encompassing all past and queued Updates[]. + ImTextureRect UpdateRect; // w r // Bounding box encompassing all queued Updates[]. + ImVector Updates; // w r // Array of individual updates. + int UnusedFrames; // w r // In order to facilitate handling Status==WantDestroy in some backend: this is a count successive frames where the texture was not used. Always >0 when Status==WantDestroy. + unsigned short RefCount; // w r // Number of contexts using this texture. Used during backend shutdown. + bool UseColors; // w r // Tell whether our texture data is known to use colors (rather than just white + alpha). + bool WantDestroyNextFrame; // rw - // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. + + // Functions + ImTextureData() { memset(this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; } + ~ImTextureData() { DestroyPixels(); } + IMGUI_API void Create(ImTextureFormat format, int w, int h); + IMGUI_API void DestroyPixels(); + void* GetPixels() { IM_ASSERT(Pixels != NULL); return Pixels; } + void* GetPixelsAt(int x, int y) { IM_ASSERT(Pixels != NULL); return Pixels + (x + y * Width) * BytesPerPixel; } + int GetSizeInBytes() const { return Width * Height * BytesPerPixel; } + int GetPitch() const { return Width * BytesPerPixel; } + ImTextureRef GetTexRef() { ImTextureRef tex_ref; tex_ref._TexData = this; tex_ref._TexID = ImTextureID_Invalid; return tex_ref; } + ImTextureID GetTexID() const { return TexID; } + + // Called by Renderer backend + void SetTexID(ImTextureID tex_id) { TexID = tex_id; } // Call after creating or destroying the texture. Never modify TexID directly! + void SetStatus(ImTextureStatus status) { Status = status; } // Call after honoring a request. Never modify Status directly! +}; + //----------------------------------------------------------------------------- // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont) //----------------------------------------------------------------------------- @@ -3291,43 +3467,56 @@ struct ImDrawData // A font input/source (we may rename this to ImFontSource in the future) struct ImFontConfig { + // Data Source + char Name[40]; // // Name (strictly to ease debugging, hence limited size buffer) void* FontData; // // TTF/OTF data int FontDataSize; // // TTF/OTF data size bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). + + // Options bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. - int FontNo; // 0 // Index of font within TTF/OTF file - int OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. - int OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. + bool PixelSnapV; // true // Align Scaled GlyphOffset.y to pixel boundaries. + ImS8 OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. + ImS8 OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. + ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). - //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED IN 1.91.9: use GlyphExtraAdvanceX) - ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. - const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). - float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font + const ImWchar* GlyphRanges; // NULL // *LEGACY* THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). + const ImWchar* GlyphExcludeRanges; // NULL // Pointer to a small user-provided list of Unicode ranges (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. Use "Input Glyphs Overlap Detection Tool" to find about your overlapping ranges. + //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED AT IT SEEMS LARGELY OBSOLETE. PLEASE REPORT IF YOU WERE USING THIS). Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset (in pixels) all glyphs from this font input. Absolute value for default size, other sizes will scale this value. + float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font. Absolute value for default size, other sizes will scale this value. float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs - float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. - unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. + float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. // FIXME-NEWATLAS: Intentionally unscaled + ImU32 FontNo; // 0 // Index of font within TTF/OTF file + unsigned int FontLoaderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. + //unsigned int FontBuilderFlags; // -- // [Renamed in 1.92] Ue FontLoaderFlags. float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. - float RasterizerDensity; // 1.0f // DPI scale for rasterization, not altering other font metrics: make it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. - ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. + float RasterizerDensity; // 1.0f // [LEGACY: this only makes sense when ImGuiBackendFlags_RendererHasTextures is not supported] DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. // [Internal] - char Name[40]; // Name (strictly to ease debugging) - ImFont* DstFont; + ImFontFlags Flags; // Font flags (don't use just yet, will be exposed in upcoming 1.92.X updates) + ImFont* DstFont; // Target font (as we merging fonts, multiple ImFontConfig may target the same font) + const ImFontLoader* FontLoader; // Custom font backend for this source (default source is the one stored in ImFontAtlas) + void* FontLoaderData; // Font loader opaque storage (per font config) IMGUI_API ImFontConfig(); }; // Hold rendering data for one glyph. -// (Note: some language parsers may fail to convert the 31+1 bitfield members, in this case maybe drop store a single u32 or we can rework this) +// (Note: some language parsers may fail to convert the bitfield members, in this case maybe drop store a single u32 or we can rework this) struct ImFontGlyph { unsigned int Colored : 1; // Flag to indicate glyph is colored and should generally ignore tinting (make it usable with no shift on little-endian as this is used in loops) unsigned int Visible : 1; // Flag to indicate glyph has no visible pixels (e.g. space). Allow early out when rendering. - unsigned int Codepoint : 30; // 0x0000..0x10FFFF - float AdvanceX; // Horizontal distance to advance layout with - float X0, Y0, X1, Y1; // Glyph corners - float U0, V0, U1, V1; // Texture coordinates + unsigned int SourceIdx : 4; // Index of source in parent font + unsigned int Codepoint : 26; // 0x0000..0x10FFFF + float AdvanceX; // Horizontal distance to advance cursor/layout position. + float X0, Y0, X1, Y1; // Glyph corners. Offsets from current cursor/layout position. + float U0, V0, U1, V1; // Texture coordinates for the current value of ImFontAtlas->TexRef. Cached equivalent of calling GetCustomRect() with PackId. + int PackId; // [Internal] ImFontAtlasRectId value (FIXME: Cold data, could be moved elsewhere?) + + ImFontGlyph() { memset(this, 0, sizeof(*this)); PackId = -1; } }; // Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges(). @@ -3346,20 +3535,21 @@ struct ImFontGlyphRangesBuilder IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges }; -// See ImFontAtlas::AddCustomRectXXX functions. -struct ImFontAtlasCustomRect +// An opaque identifier to a rectangle in the atlas. -1 when invalid. +// The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it. +typedef int ImFontAtlasRectId; +#define ImFontAtlasRectId_Invalid -1 + +// Output of ImFontAtlas::GetCustomRect() when using custom rectangles. +// Those values may not be cached/stored as they are only valid for the current value of atlas->TexRef +// (this is in theory derived from ImTextureRect but we use separate structures for reasons) +struct ImFontAtlasRect { - unsigned short X, Y; // Output // Packed position in Atlas + unsigned short x, y; // Position (in current texture) + unsigned short w, h; // Size + ImVec2 uv0, uv1; // UV coordinates (in current texture) - // [Internal] - unsigned short Width, Height; // Input // Desired rectangle dimension - unsigned int GlyphID : 31; // Input // For custom font glyphs only (ID < 0x110000) - unsigned int GlyphColored : 1; // Input // For custom font glyphs only: glyph is colored, removed tinting. - float GlyphAdvanceX; // Input // For custom font glyphs only: glyph xadvance - ImVec2 GlyphOffset; // Input // For custom font glyphs only: glyph display offset - ImFont* Font; // Input // For custom font glyphs only: target font - ImFontAtlasCustomRect() { X = Y = 0xFFFF; Width = Height = 0; GlyphID = 0; GlyphColored = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } - bool IsPacked() const { return X != 0xFFFF; } + ImFontAtlasRect() { memset(this, 0, sizeof(*this)); } }; // Flags for ImFontAtlas build @@ -3375,12 +3565,14 @@ enum ImFontAtlasFlags_ // - One or more fonts. // - Custom graphics data needed to render the shapes needed by Dear ImGui. // - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas). -// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by your graphics api. -// - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code will be loaded for you. -// - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. -// - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples) +// - If you don't call any AddFont*** functions, the default font embedded in the code will be loaded for you. +// It is the rendering backend responsibility to upload texture into your graphics API: +// - ImGui_ImplXXXX_RenderDrawData() functions generally iterate platform_io->Textures[] to create/update/destroy each ImTextureData instance. +// - Backend then set ImTextureData's TexID and BackendUserData. +// - Texture id are passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID/ImTextureRef for more details. +// Legacy path: +// - Call Build() + GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. // - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. -// This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. // Common pitfalls: // - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the // atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data. @@ -3394,35 +3586,49 @@ struct ImFontAtlas IMGUI_API ~ImFontAtlas(); IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); - IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); - IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. - IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. - IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. - IMGUI_API void ClearInputData(); // Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. - IMGUI_API void ClearFonts(); // Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). - IMGUI_API void ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. - IMGUI_API void Clear(); // Clear all input and output. - - // Build atlas, retrieve pixel data. - // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). - // The pitch is always = Width * BytesPerPixels (1 or 4) - // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into - // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. - IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. - IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel - IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - bool IsBuilt() const { return Fonts.Size > 0 && TexReady; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... - void SetTexID(ImTextureID id) { TexID = id; } + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. + IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. + IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. + IMGUI_API void RemoveFont(ImFont* font); + + IMGUI_API void Clear(); // Clear everything (input fonts, output glyphs/textures) + IMGUI_API void CompactCache(); // Compact cached glyphs and texture. + IMGUI_API void SetFontLoader(const ImFontLoader* font_loader); // Change font loader at runtime. + + // As we are transitioning toward a new font system, we expect to obsolete those soon: + IMGUI_API void ClearInputData(); // [OBSOLETE] Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. + IMGUI_API void ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). + IMGUI_API void ClearTexData(); // [OBSOLETE] Clear CPU-side copy of the texture data. Saves RAM once the texture has been copied to graphics memory. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Legacy path for build atlas + retrieving pixel data. + // - User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // - The pitch is always = Width * BytesPerPixels (1 or 4) + // - Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into + // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. + // - From 1.92 with backends supporting ImGuiBackendFlags_RendererHasTextures: + // - Calling Build(), GetTexDataAsAlpha8(), GetTexDataAsRGBA32() is not needed. + // - In backend: replace calls to ImFontAtlas::SetTexID() with calls to ImTextureData::SetTexID() after honoring texture creation. + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { IM_ASSERT(TexRef._TexID == ImTextureID_Invalid); TexRef._TexData->TexID = id; } // Called by legacy backends. May be called before texture creation. + void SetTexID(ImTextureRef id) { IM_ASSERT(TexRef._TexID == ImTextureID_Invalid && id._TexData == NULL); TexRef._TexData->TexID = id._TexID; } // Called by legacy backends. + bool IsBuilt() const { return Fonts.Size > 0 && TexIsBuilt; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent.. +#endif //------------------------------------------- // Glyph Ranges //------------------------------------------- + // Since 1.92: specifying glyph ranges is only useful/necessary if your backend doesn't support ImGuiBackendFlags_RendererHasTextures! + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) // NB: Make sure that your string are UTF-8 and NOT in your local code page. // Read https://github.com/ocornut/imgui/blob/master/docs/FONTS.md/#about-utf-8-encoding for details. // NB: Consider using ImFontGlyphRangesBuilder to build glyph ranges from textual data. - IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin IMGUI_API const ImWchar* GetGlyphRangesGreek(); // Default + Greek and Coptic IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs @@ -3431,24 +3637,32 @@ struct ImFontAtlas IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters IMGUI_API const ImWchar* GetGlyphRangesVietnamese(); // Default + Vietnamese characters +#endif //------------------------------------------- // [ALPHA] Custom Rectangles/Glyphs API //------------------------------------------- - // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. - // - After calling Build(), you can query the rectangle position and render your pixels. - // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. - // - You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), - // so you can render e.g. custom colorful icons and use them as regular glyphs. + // Register and retrieve custom rectangles + // - You can request arbitrary rectangles to be packed into the atlas, for your own purpose. + // - Since 1.92.X, packing is done immediately in the function call (previously packing was done during the Build call) + // - You can render your pixels into the texture right after calling the AddCustomRect() functions. + // - VERY IMPORTANT: + // - Texture may be created/resized at any time when calling ImGui or ImFontAtlas functions. + // - IT WILL INVALIDATE RECTANGLE DATA SUCH AS UV COORDINATES. Always use latest values from GetCustomRect(). + // - UV coordinates are associated to the current texture identifier aka 'atlas->TexRef'. Both TexRef and UV coordinates are typically changed at the same time. + // - If you render colored output into your custom rectangles: set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. // - Read docs/FONTS.md for more details about using colorful icons. - // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. - IMGUI_API int AddCustomRectRegular(int width, int height); - IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); - ImFontAtlasCustomRect* GetCustomRectByIndex(int index) { IM_ASSERT(index >= 0); return &CustomRects[index]; } - - // [Internal] - IMGUI_API void CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; + // - Note: this API may be reworked further in order to facilitate supporting e.g. multi-monitor, varying DPI settings. + // - (Pre-1.92 names) ------------> (1.92 names) + // - GetCustomRectByIndex() --> Use GetCustomRect() + // - CalcCustomRectUV() --> Use GetCustomRect() and read uv0, uv1 fields. + // - AddCustomRectRegular() --> Renamed to AddCustomRect() + // - AddCustomRectFontGlyph() --> Prefer using custom ImFontLoader inside ImFontConfig + // - ImFontAtlasCustomRect --> Renamed to ImFontAtlasRect + IMGUI_API ImFontAtlasRectId AddCustomRect(int width, int height, ImFontAtlasRect* out_r = NULL);// Register a rectangle. Return -1 (ImFontAtlasRectId_Invalid) on error. + IMGUI_API void RemoveCustomRect(ImFontAtlasRectId id); // Unregister a rectangle. Existing pixels will stay in texture until resized / garbage collected. + IMGUI_API bool GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const; // Get rectangle coordinates for current texture. Valid immediately, never store this (read above)! //------------------------------------------- // Members @@ -3456,100 +3670,178 @@ struct ImFontAtlas // Input ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) - ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. - int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + ImTextureFormat TexDesiredFormat; // Desired texture format (default to ImTextureFormat_RGBA32 but may be changed to ImTextureFormat_Alpha8). int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). + int TexMinWidth; // Minimum desired texture width. Must be a power of two. Default to 512. + int TexMinHeight; // Minimum desired texture height. Must be a power of two. Default to 128. + int TexMaxWidth; // Maximum desired texture width. Must be a power of two. Default to 8192. + int TexMaxHeight; // Maximum desired texture height. Must be a power of two. Default to 8192. void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). + // Output + // - Because textures are dynamically created/resized, the current texture identifier may changed at *ANY TIME* during the frame. + // - This should not affect you as you can always use the latest value. But note that any precomputed UV coordinates are only valid for the current TexRef. +#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImTextureRef TexRef; // Latest texture identifier == TexData->GetTexRef(). +#else + union { ImTextureRef TexRef; ImTextureRef TexID; }; // Latest texture identifier == TexData->GetTexRef(). // RENAMED TexID to TexRef in 1.92.x +#endif + ImTextureData* TexData; // Latest texture. + // [Internal] - // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. - bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. - bool TexReady; // Set when texture was built matching current font input - bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format. - unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight - unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 - int TexWidth; // Texture width calculated during Build(). - int TexHeight; // Texture height calculated during Build(). - ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) - ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel + ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetDrawData().Textures[]/GetPlatformIO().Textures[] instead! + bool Locked; // Marked as locked during ImGui::NewFrame()..EndFrame() scope if TexUpdates are not supported. Any attempt to modify the atlas will assert. + bool RendererHasTextures;// Copy of (BackendFlags & ImGuiBackendFlags_RendererHasTextures) from supporting context. + bool TexIsBuilt; // Set when texture was built matching current font input. Mostly useful for legacy IsBuilt() call. + bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. + ImVec2 TexUvScale; // = (1.0f/TexData->TexWidth, 1.0f/TexData->TexHeight). May change as new texture gets created. + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel. May change as new texture gets created. ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. - ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines - - // [Internal] Font builder - const ImFontBuilderIO* FontBuilderIO; // Opaque interface to a font builder (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). - unsigned int FontBuilderFlags; // Shared flags (for all fonts) for custom font builder. THIS IS BUILD IMPLEMENTATION DEPENDENT. Per-font override is also available in ImFontConfig. - - // [Internal] Packing data - int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors - int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines + int TexNextUniqueID; // Next value to be stored in TexData->UniqueID + int FontNextUniqueID; // Next value to be stored in ImFont->FontID + ImVector DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context. + ImFontAtlasBuilder* Builder; // Opaque interface to our data that doesn't need to be public and may be discarded when rebuilding. + const ImFontLoader* FontLoader; // Font loader opaque interface (default to use FreeType when IMGUI_ENABLE_FREETYPE is defined, otherwise default to use stb_truetype). Use SetFontLoader() to change this at runtime. + const char* FontLoaderName; // Font loader name (for display e.g. in About box) == FontLoader->Name + void* FontLoaderData; // Font backend opaque storage + unsigned int FontLoaderFlags; // Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. Per-font override is also available in ImFontConfig). + int RefCount; // Number of contexts using this atlas + ImGuiContext* OwnerContext; // Context which own the atlas will be in charge of updating and destroying it. // [Obsolete] - //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ - //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Legacy: You can request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. --> Prefer using a custom ImFontLoader. + ImFontAtlasRect TempRect; // For old GetCustomRectByIndex() API + inline ImFontAtlasRectId AddCustomRectRegular(int w, int h) { return AddCustomRect(w, h); } // RENAMED in 1.92.X + inline const ImFontAtlasRect* GetCustomRectByIndex(ImFontAtlasRectId id) { return GetCustomRect(id, &TempRect) ? &TempRect : NULL; } // OBSOLETED in 1.92.X + inline void CalcCustomRectUV(const ImFontAtlasRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const { *out_uv_min = r->uv0; *out_uv_max = r->uv1; } // OBSOLETED in 1.92.X + IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig + IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // ADDED AND OBSOLETED in 1.92.X +#endif + //unsigned int FontBuilderFlags; // OBSOLETED in 1.92.X: Renamed to FontLoaderFlags. + //int TexDesiredWidth; // OBSOLETED in 1.92.X: Force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) + //typedef ImFontAtlasRect ImFontAtlasCustomRect; // OBSOLETED in 1.92.X + //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ + //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ }; -// Font runtime data and rendering -// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). -struct ImFont +// Font runtime data for a given size +// Important: pointers to ImFontBaked are only valid for the current frame. +struct ImFontBaked { // [Internal] Members: Hot ~20/24 bytes (for CalcTextSize) ImVector IndexAdvanceX; // 12-16 // out // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). - float FallbackAdvanceX; // 4 // out // = FallbackGlyph->AdvanceX - float FontSize; // 4 // in // Height of characters/line, set during loading (don't change after loading) + float FallbackAdvanceX; // 4 // out // FindGlyph(FallbackChar)->AdvanceX + float Size; // 4 // in // Height of characters/line, set during loading (doesn't change after loading) + float RasterizerDensity; // 4 // in // Density this is baked at - // [Internal] Members: Hot ~28/40 bytes (for RenderText loop) + // [Internal] Members: Hot ~28/36 bytes (for RenderText loop) ImVector IndexLookup; // 12-16 // out // Sparse. Index glyphs by Unicode code-point. ImVector Glyphs; // 12-16 // out // All glyphs. - ImFontGlyph* FallbackGlyph; // 4-8 // out // = FindGlyph(FontFallbackChar) + int FallbackGlyphIndex; // 4 // out // Index of FontFallbackChar + + // [Internal] Members: Cold + float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) + unsigned int MetricsTotalSurface:26;// 3 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) + unsigned int WantDestroy:1; // 0 // // Queued for destroy + unsigned int LoadNoFallback:1; // 0 // // Disable loading fallback in lower-level calls. + unsigned int LoadNoRenderOnLayout:1;// 0 // // Enable a two-steps mode where CalcTextSize() calls will load AdvanceX *without* rendering/packing glyphs. Only advantagous if you know that the glyph is unlikely to actually be rendered, otherwise it is slower because we'd do one query on the first CalcTextSize and one query on the first Draw. + int LastUsedFrame; // 4 // // Record of that time this was bounds + ImGuiID BakedId; // 4 // // Unique ID for this baked storage + ImFont* ContainerFont; // 4-8 // in // Parent font + void* FontLoaderDatas; // 4-8 // // Font loader opaque storage (per baked font * sources): single contiguous buffer allocated by imgui, passed to loader. + + // Functions + IMGUI_API ImFontBaked(); + IMGUI_API void ClearOutputData(); + IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); // Return U+FFFD glyph if requested glyph doesn't exists. + IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); // Return NULL if glyph doesn't exist + IMGUI_API float GetCharAdvance(ImWchar c); + IMGUI_API bool IsGlyphLoaded(ImWchar c); +}; - // [Internal] Members: Cold ~32/40 bytes +// Font flags +// (in future versions as we redesign font loading API, this will become more important and better documented. for now please consider this as internal/advanced use) +enum ImFontFlags_ +{ + ImFontFlags_None = 0, + ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. + ImFontFlags_NoLoadGlyphs = 1 << 2, // [Internal] Disable loading new glyphs. + ImFontFlags_LockBakedSizes = 1 << 3, // [Internal] Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. Important: if you use this to preload given sizes, consider the possibility of multiple font density used on Retina display. +}; + +// Font runtime data and rendering +// - ImFontAtlas automatically loads a default embedded font for you if you didn't load one manually. +// - Since 1.92.X a font may be rendered as any size! Therefore a font doesn't have one specific size. +// - Use 'font->GetFontBaked(size)' to retrieve the ImFontBaked* corresponding to a given size. +// - If you used g.Font + g.FontSize (which is frequent from the ImGui layer), you can use g.FontBaked as a shortcut, as g.FontBaked == g.Font->GetFontBaked(g.FontSize). +struct ImFont +{ + // [Internal] Members: Hot ~12-20 bytes + ImFontBaked* LastBaked; // 4-8 // Cache last bound baked. NEVER USE DIRECTLY. Use GetFontBaked(). + ImFontAtlas* ContainerAtlas; // 4-8 // What we have been loaded into. + ImFontFlags Flags; // 4 // Font flags. + float CurrentRasterizerDensity; // Current rasterizer density. This is a varying state of the font. + + // [Internal] Members: Cold ~24-52 bytes // Conceptually Sources[] is the list of font sources merged to create this font. - ImFontAtlas* ContainerAtlas; // 4-8 // out // What we has been loaded into - ImFontConfig* Sources; // 4-8 // in // Pointer within ContainerAtlas->Sources[], to SourcesCount instances - short SourcesCount; // 2 // in // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont. - short EllipsisCharCount; // 1 // out // 1 or 3 + ImGuiID FontId; // Unique identifier for the font + float LegacySize; // 4 // in // Font size passed to AddFont(). Use for old code calling PushFont() expecting to use that size. (use ImGui::GetFontBaked() to get font baked at current bound size). + ImVector Sources; // 16 // in // List of sources. Pointers within ContainerAtlas->Sources[] ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') - float EllipsisWidth; // 4 // out // Total ellipsis Width - float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 - float Scale; // 4 // in // Base font scale (1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() - float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) - int MetricsTotalSurface;// 4 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) - bool DirtyLookupTables; // 1 // out // ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. + bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph needs to be generated. + ImGuiStorage RemapPairs; // 16 // // Remapping pairs when using AddRemapChar(), otherwise empty. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + float Scale; // 4 // in // Legacy base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() +#endif // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); - IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); - float GetCharAdvance(ImWchar c) { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } - const char* GetDebugName() const { return Sources ? Sources->Name : ""; } + const char* GetDebugName() const { return Sources.Size ? Sources[0]->Name : ""; } // Fill ImFontConfig::Name. // [Internal] Don't use! // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. + IMGUI_API ImFontBaked* GetFontBaked(float font_size, float density = -1.0f); // Get or create baked data for given size IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL); // utf8 IMGUI_API const char* CalcWordWrapPosition(float size, const char* text, const char* text_end, float wrap_width); IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip = NULL); IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false); - #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(FontSize * scale, text, text_end, wrap_width); } + inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(LegacySize * scale, text, text_end, wrap_width); } #endif // [Internal] Don't use! - IMGUI_API void BuildLookupTable(); IMGUI_API void ClearOutputData(); - IMGUI_API void GrowIndex(int new_size); - IMGUI_API void AddGlyph(const ImFontConfig* src_cfg, ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); - IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst);// , bool overwrite_dst = true); // Makes 'from_codepoint' character points to 'to_codepoint' character. Currently needs to be called AFTER fonts have been built. + IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint); // Makes 'from_codepoint' character points to 'to_codepoint' glyph. IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); }; +// This is provided for consistency (but we don't actually use this) +inline ImTextureID ImTextureRef::GetTexID() const +{ + IM_ASSERT(!(_TexData != NULL && _TexID != ImTextureID_Invalid)); + return _TexData ? _TexData->TexID : _TexID; +} + +// Using an indirection to avoid patching ImDrawCmd after a SetTexID() call (but this could be an alternative solution too) +inline ImTextureID ImDrawCmd::GetTexID() const +{ + // If you are getting this assert: A renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92) + // must iterate and handle ImTextureData requests stored in ImDrawData::Textures[]. + ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; // == TexRef.GetTexID() above. + if (TexRef._TexData != NULL) + IM_ASSERT(tex_id != ImTextureID_Invalid && "ImDrawCmd is referring to ImTextureData that wasn't uploaded to graphics system. Backend must call ImTextureData::SetTexID() after handling ImTextureStatus_WantCreate request!"); + return tex_id; +} + //----------------------------------------------------------------------------- // [SECTION] Viewports //----------------------------------------------------------------------------- @@ -3576,6 +3868,7 @@ struct ImGuiViewport ImGuiViewportFlags Flags; // See ImGuiViewportFlags_ ImVec2 Pos; // Main Area: Position of the viewport (Dear ImGui coordinates are the same as OS desktop/native coordinates) ImVec2 Size; // Main Area: Size of the viewport. + ImVec2 FramebufferScale; // Density of the viewport for Retina display (always 1,1 on Windows, may be 2,2 etc on macOS/iOS). This will affect font rasterizer density. ImVec2 WorkPos; // Work Area: Position of the viewport minus task bars, menus bars, status bars (>= Pos) ImVec2 WorkSize; // Work Area: Size of the viewport minus task bars, menu bars, status bars (<= Size) @@ -3628,8 +3921,20 @@ struct ImGuiPlatformIO // Input - Interface with Renderer Backend //------------------------------------------------------------------ + // Optional: Maximum texture size supported by renderer (used to adjust how we size textures). 0 if not known. + int Renderer_TextureMaxWidth; + int Renderer_TextureMaxHeight; + // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure. void* Renderer_RenderState; + + //------------------------------------------------------------------ + // Output + //------------------------------------------------------------------ + + // Textures list (the list is updated by calling ImGui::EndFrame or ImGui::Render) + // The ImGui_ImplXXXX_RenderDrawData() function of each backend generally access this via ImDrawData::Textures which points to this. The array is available here mostly because backends will want to destroy textures on shutdown. + ImVector Textures; // List of textures used by Dear ImGui (most often 1) + contents of external texture list is automatically appended into this. }; // (Optional) Support for IME (Input Method Editor) via the platform_io.Platform_SetImeDataFn() function. Handler is called during EndFrame(). @@ -3653,8 +3958,11 @@ struct ImGuiPlatformImeData #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { + // OBSOLETED in 1.92.0 (from June 2025) + static inline void PushFont(ImFont* font) { PushFont(font, font ? font->LegacySize : 0.0f); } + IMGUI_API void SetWindowFontScale(float scale); // Set font scale factor for current window. Prefer using PushFont(NULL, style.FontSizeBase * factor) or use style.FontScaleMain to scale all windows. // OBSOLETED in 1.91.9 (from February 2025) - IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- 'border_col' was removed in favor of ImGuiCol_ImageBorder. If you use 'tint_col', use ImageWithBg() instead. + IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- 'border_col' was removed in favor of ImGuiCol_ImageBorder. If you use 'tint_col', use ImageWithBg() instead. // OBSOLETED in 1.91.0 (from July 2024) static inline void PushButtonRepeat(bool repeat) { PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); } static inline void PopButtonRepeat() { PopItemFlag(); } @@ -3740,6 +4048,25 @@ namespace ImGui //static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETED in 1.42 } +//-- OBSOLETED in 1.92.x: ImFontAtlasCustomRect becomes ImTextureRect +// - ImFontAtlasCustomRect::X,Y --> ImTextureRect::x,y +// - ImFontAtlasCustomRect::Width,Height --> ImTextureRect::w,h +// - ImFontAtlasCustomRect::GlyphColored --> if you need to write to this, instead you can write to 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph() +// We could make ImTextureRect an union to use old names, but 1) this would be confusing 2) the fix is easy 3) ImFontAtlasCustomRect was always a rather esoteric api. +typedef ImFontAtlasRect ImFontAtlasCustomRect; +/*struct ImFontAtlasCustomRect +{ + unsigned short X, Y; // Output // Packed position in Atlas + unsigned short Width, Height; // Input // [Internal] Desired rectangle dimension + unsigned int GlyphID:31; // Input // [Internal] For custom font glyphs only (ID < 0x110000) + unsigned int GlyphColored:1; // Input // [Internal] For custom font glyphs only: glyph is colored, removed tinting. + float GlyphAdvanceX; // Input // [Internal] For custom font glyphs only: glyph xadvance + ImVec2 GlyphOffset; // Input // [Internal] For custom font glyphs only: glyph display offset + ImFont* Font; // Input // [Internal] For custom font glyphs only: target font + ImFontAtlasCustomRect() { X = Y = 0xFFFF; Width = Height = 0; GlyphID = 0; GlyphColored = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } +};*/ + //-- OBSOLETED in 1.82 (from Mars 2021): flags for AddRect(), AddRectFilled(), AddImageRounded(), PathRect() //typedef ImDrawFlags ImDrawCornerFlags; //enum ImDrawCornerFlags_ diff --git a/PopLib/imgui/core/imgui_demo.cpp b/PopLib/imgui/core/imgui_demo.cpp index 69018315..6063a3c6 100644 --- a/PopLib/imgui/core/imgui_demo.cpp +++ b/PopLib/imgui/core/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.0 WIP +// dear imgui, v1.92.2 WIP // (demo code) // Help: @@ -410,9 +410,19 @@ void ImGui::ShowDemoWindow(bool* p_open) return; } - // Most "big" widgets share a common width settings by default. See 'Demo->Layout->Widgets Width' for details. - ImGui::PushItemWidth(ImGui::GetFontSize() * -12); // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets. - //ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align) + // Most framed widgets share a common width settings. Remaining width is used for the label. + // The width of the frame may be changed with PushItemWidth() or SetNextItemWidth(). + // - Positive value for absolute size, negative value for right-alignment. + // - The default value is about GetWindowWidth() * 0.65f. + // - See 'Demo->Layout->Widgets Width' for details. + // Here we change the frame width based on how much width we want to give to the label. + const float label_width_base = ImGui::GetFontSize() * 12; // Some amount of width for label, based on font size. + const float label_width_max = ImGui::GetContentRegionAvail().x * 0.40f; // ...but always leave some room for framed widgets. + const float label_width = IM_MIN(label_width_base, label_width_max); + ImGui::PushItemWidth(-label_width); // Right-align: framed items will leave 'label_width' available for the label. + //ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.40f); // e.g. Use 40% width for framed widgets, leaving 60% width for labels. + //ImGui::PushItemWidth(-ImGui::GetContentRegionAvail().x * 0.40f); // e.g. Use 40% width for labels, leaving 60% width for framed widgets. + //ImGui::PushItemWidth(ImGui::GetFontSize() * -12); // e.g. Use XXX width for labels, leaving the rest for framed widgets. // Menu Bar DemoWindowMenuBar(&demo_data); @@ -566,14 +576,15 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", &io.BackendFlags, ImGuiBackendFlags_HasMouseCursors); ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", &io.BackendFlags, ImGuiBackendFlags_HasSetMousePos); ImGui::CheckboxFlags("io.BackendFlags: RendererHasVtxOffset", &io.BackendFlags, ImGuiBackendFlags_RendererHasVtxOffset); + ImGui::CheckboxFlags("io.BackendFlags: RendererHasTextures", &io.BackendFlags, ImGuiBackendFlags_RendererHasTextures); ImGui::EndDisabled(); ImGui::TreePop(); ImGui::Spacing(); } - IMGUI_DEMO_MARKER("Configuration/Style"); - if (ImGui::TreeNode("Style")) + IMGUI_DEMO_MARKER("Configuration/Style, Fonts"); + if (ImGui::TreeNode("Style, Fonts")) { ImGui::Checkbox("Style Editor", &demo_data.ShowStyleEditor); ImGui::SameLine(); @@ -831,6 +842,9 @@ static void DemoWindowWidgetsBasic() ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); ImGui::RadioButton("radio c", &e, 2); + ImGui::AlignTextToFramePadding(); + ImGui::TextLinkOpenURL("Hyperlink", "https://github.com/ocornut/imgui/wiki/Error-Handling"); + // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. IMGUI_DEMO_MARKER("Widgets/Basic/Buttons (Colored)"); for (int i = 0; i < 7; i++) @@ -1734,6 +1748,7 @@ static void DemoWindowWidgetsFonts() { ImFontAtlas* atlas = ImGui::GetIO().Fonts; ImGui::ShowFontAtlas(atlas); + // FIXME-NEWATLAS: Provide a demo to add/create a procedural font? ImGui::TreePop(); } } @@ -1754,13 +1769,12 @@ static void DemoWindowWidgetsImages() "Hover the texture for a zoomed view!"); // Below we are displaying the font texture because it is the only texture we have access to inside the demo! - // Remember that ImTextureID is just storage for whatever you want it to be. It is essentially a value that - // will be passed to the rendering backend via the ImDrawCmd structure. + // Read description about ImTextureID/ImTextureRef and FAQ for details about texture identifiers. // If you use one of the default imgui_impl_XXXX.cpp rendering backend, they all have comments at the top - // of their respective source file to specify what they expect to be stored in ImTextureID, for example: - // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer + // of their respective source file to specify what they are using as texture identifier, for example: + // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer. // - The imgui_impl_opengl3.cpp renderer expect a GLuint OpenGL texture identifier, etc. - // More: + // So with the DirectX11 backend, you call ImGui::Image() with a 'ID3D11ShaderResourceView*' cast to ImTextureID. // - If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers // to ImGui::Image(), and gather width/height through your own functions, etc. // - You can use ShowMetricsWindow() to inspect the draw data that are being passed to your renderer, @@ -1768,14 +1782,19 @@ static void DemoWindowWidgetsImages() // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples - ImTextureID my_tex_id = io.Fonts->TexID; - float my_tex_w = (float)io.Fonts->TexWidth; - float my_tex_h = (float)io.Fonts->TexHeight; + + // Grab the current texture identifier used by the font atlas. + ImTextureRef my_tex_id = io.Fonts->TexRef; + + // Regular user code should never have to care about TexData-> fields, but since we want to display the entire texture here, we pull Width/Height from it. + float my_tex_w = (float)io.Fonts->TexData->Width; + float my_tex_h = (float)io.Fonts->TexData->Height; + { ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left - ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right + ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left + ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize)); ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); if (ImGui::BeginItemTooltip()) @@ -2151,7 +2170,7 @@ static void DemoWindowWidgetsQueryingStatuses() ); ImGui::BulletText( "with Hovering Delay or Stationary test:\n" - "IsItemHovered() = = %d\n" + "IsItemHovered() = %d\n" "IsItemHovered(_Stationary) = %d\n" "IsItemHovered(_DelayShort) = %d\n" "IsItemHovered(_DelayNormal) = %d\n" @@ -3529,6 +3548,43 @@ static void DemoWindowWidgetsText() ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Text/Font Size"); + if (ImGui::TreeNode("Font Size")) + { + ImGuiStyle& style = ImGui::GetStyle(); + const float global_scale = style.FontScaleMain * style.FontScaleDpi; + ImGui::Text("style.FontScaleMain = %0.2f", style.FontScaleMain); + ImGui::Text("style.FontScaleDpi = %0.2f", style.FontScaleDpi); + ImGui::Text("global_scale = ~%0.2f", global_scale); // This is not technically accurate as internal scales may apply, but conceptually let's pretend it is. + ImGui::Text("FontSize = %0.2f", ImGui::GetFontSize()); + + ImGui::SeparatorText(""); + static float custom_size = 16.0f; + ImGui::SliderFloat("custom_size", &custom_size, 10.0f, 100.0f, "%.0f"); + ImGui::Text("ImGui::PushFont(nullptr, custom_size);"); + ImGui::PushFont(NULL, custom_size); + ImGui::Text("FontSize = %.2f (== %.2f * global_scale)", ImGui::GetFontSize(), custom_size); + ImGui::PopFont(); + + ImGui::SeparatorText(""); + static float custom_scale = 1.0f; + ImGui::SliderFloat("custom_scale", &custom_scale, 0.5f, 4.0f, "%.2f"); + ImGui::Text("ImGui::PushFont(nullptr, style.FontSizeBase * custom_scale);"); + ImGui::PushFont(NULL, style.FontSizeBase * custom_scale); + ImGui::Text("FontSize = %.2f (== style.FontSizeBase * %.2f * global_scale)", ImGui::GetFontSize(), custom_scale); + ImGui::PopFont(); + + ImGui::SeparatorText(""); + for (float scaling = 0.5f; scaling <= 4.0f; scaling += 0.5f) + { + ImGui::PushFont(NULL, style.FontSizeBase * scaling); + ImGui::Text("FontSize = %.2f (== style.FontSizeBase * %.2f * global_scale)", ImGui::GetFontSize(), scaling); + ImGui::PopFont(); + } + + ImGui::TreePop(); + } + IMGUI_DEMO_MARKER("Widgets/Text/Word Wrapping"); if (ImGui::TreeNode("Word Wrapping")) { @@ -4426,16 +4482,27 @@ static void DemoWindowLayout() } ImGui::PopItemWidth(); + ImGui::Text("SetNextItemWidth/PushItemWidth(-Min(GetContentRegionAvail().x * 0.40f, GetFontSize() * 12))"); + ImGui::PushItemWidth(-IM_MIN(ImGui::GetFontSize() * 12, ImGui::GetContentRegionAvail().x * 0.40f)); + ImGui::DragFloat("float##5a", &f); + if (show_indented_items) + { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##5b", &f); + ImGui::Unindent(); + } + ImGui::PopItemWidth(); + // Demonstrate using PushItemWidth to surround three items. // Calling SetNextItemWidth() before each of them would have the same effect. ImGui::Text("SetNextItemWidth/PushItemWidth(-FLT_MIN)"); ImGui::SameLine(); HelpMarker("Align to right edge"); ImGui::PushItemWidth(-FLT_MIN); - ImGui::DragFloat("##float5a", &f); + ImGui::DragFloat("##float6a", &f); if (show_indented_items) { ImGui::Indent(); - ImGui::DragFloat("float (indented)##5b", &f); + ImGui::DragFloat("float (indented)##6b", &f); ImGui::Unindent(); } ImGui::PopItemWidth(); @@ -7997,6 +8064,8 @@ void ImGui::ShowAboutWindow(bool* p_open) ImGui::SameLine(); ImGui::TextLinkOpenURL("Wiki", "https://github.com/ocornut/imgui/wiki"); ImGui::SameLine(); + ImGui::TextLinkOpenURL("Extensions", "https://github.com/ocornut/imgui/wiki/Useful-Extensions"); + ImGui::SameLine(); ImGui::TextLinkOpenURL("Releases", "https://github.com/ocornut/imgui/releases"); ImGui::SameLine(); ImGui::TextLinkOpenURL("Funding", "https://github.com/ocornut/imgui/wiki/Funding"); @@ -8020,7 +8089,7 @@ void ImGui::ShowAboutWindow(bool* p_open) if (copy_to_clipboard) { ImGui::LogToClipboard(); - ImGui::LogText("```\n"); // Back quotes will make text appears without formatting when pasting on GitHub + ImGui::LogText("```cpp\n"); // Back quotes will make text appears without formatting when pasting on GitHub } ImGui::Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); @@ -8116,8 +8185,10 @@ void ImGui::ShowAboutWindow(bool* p_open) if (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) ImGui::Text(" HasMouseCursors"); if (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos) ImGui::Text(" HasSetMousePos"); if (io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) ImGui::Text(" RendererHasVtxOffset"); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) ImGui::Text(" RendererHasTextures"); ImGui::Separator(); - ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexWidth, io.Fonts->TexHeight); + ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexData->Width, io.Fonts->TexData->Height); + ImGui::Text("io.Fonts->FontLoaderName: %s", io.Fonts->FontLoaderName ? io.Fonts->FontLoaderName : "NULL"); ImGui::Text("io.DisplaySize: %.2f,%.2f", io.DisplaySize.x, io.DisplaySize.y); ImGui::Text("io.DisplayFramebufferScale: %.2f,%.2f", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); ImGui::Separator(); @@ -8142,41 +8213,10 @@ void ImGui::ShowAboutWindow(bool* p_open) //----------------------------------------------------------------------------- // [SECTION] Style Editor / ShowStyleEditor() //----------------------------------------------------------------------------- -// - ShowFontSelector() // - ShowStyleSelector() // - ShowStyleEditor() //----------------------------------------------------------------------------- -// Forward declare ShowFontAtlas() which isn't worth putting in public API yet -namespace ImGui { IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); } - -// Demo helper function to select among loaded fonts. -// Here we use the regular BeginCombo()/EndCombo() api which is the more flexible one. -void ImGui::ShowFontSelector(const char* label) -{ - ImGuiIO& io = ImGui::GetIO(); - ImFont* font_current = ImGui::GetFont(); - if (ImGui::BeginCombo(label, font_current->GetDebugName())) - { - for (ImFont* font : io.Fonts->Fonts) - { - ImGui::PushID((void*)font); - if (ImGui::Selectable(font->GetDebugName(), font == font_current)) - io.FontDefault = font; - if (font == font_current) - ImGui::SetItemDefaultFocus(); - ImGui::PopID(); - } - ImGui::EndCombo(); - } - ImGui::SameLine(); - HelpMarker( - "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" - "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" - "- Read FAQ and docs/FONTS.md for more details.\n" - "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); -} - // Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. // Here we use the simplified Combo() api that packs items into a single literal string. // Useful for quick combo boxes where the choices are known locally. @@ -8204,12 +8244,13 @@ static const char* GetTreeLinesFlagsName(ImGuiTreeNodeFlags flags) return ""; } +// We omit the ImGui:: prefix in this function, as we don't expect user to be copy and pasting this code. void ImGui::ShowStyleEditor(ImGuiStyle* ref) { IMGUI_DEMO_MARKER("Tools/Style Editor"); // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to // (without a reference style pointer, we will use one compared locally as a reference) - ImGuiStyle& style = ImGui::GetStyle(); + ImGuiStyle& style = GetStyle(); static ImGuiStyle ref_saved_style; // Default to using internal storage as reference @@ -8220,209 +8261,229 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) if (ref == NULL) ref = &ref_saved_style; - ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + PushItemWidth(GetWindowWidth() * 0.50f); - if (ImGui::ShowStyleSelector("Colors##Selector")) - ref_saved_style = style; - ImGui::ShowFontSelector("Fonts##Selector"); + { + // General + SeparatorText("General"); + if ((GetIO().BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + { + BulletText("Warning: Font scaling will NOT be smooth, because\nImGuiBackendFlags_RendererHasTextures is not set!"); + BulletText("For instructions, see:"); + SameLine(); + TextLinkOpenURL("docs/BACKENDS.md", "https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md"); + } - // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) - if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) - style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding - { bool border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } } - ImGui::SameLine(); - { bool border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } } - ImGui::SameLine(); - { bool border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } } + if (ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; + ShowFontSelector("Fonts##Selector"); + if (DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) + style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. + SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); + DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 4.0f); + //BeginDisabled(GetIO().ConfigDpiScaleFonts); + DragFloat("FontScaleDpi", &style.FontScaleDpi, 0.02f, 0.5f, 4.0f); + //SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); + //EndDisabled(); + + // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) + if (SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding + { bool border = (style.WindowBorderSize > 0.0f); if (Checkbox("WindowBorder", &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } } + SameLine(); + { bool border = (style.FrameBorderSize > 0.0f); if (Checkbox("FrameBorder", &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } } + SameLine(); + { bool border = (style.PopupBorderSize > 0.0f); if (Checkbox("PopupBorder", &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } } + } // Save/Revert button - if (ImGui::Button("Save Ref")) + if (Button("Save Ref")) *ref = ref_saved_style = style; - ImGui::SameLine(); - if (ImGui::Button("Revert Ref")) + SameLine(); + if (Button("Revert Ref")) style = *ref; - ImGui::SameLine(); + SameLine(); HelpMarker( "Save/Revert in local non-persistent storage. Default Colors definition are not affected. " "Use \"Export\" below to save them somewhere."); - ImGui::Separator(); - - if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) - { - if (ImGui::BeginTabItem("Sizes")) - { - ImGui::SeparatorText("Main"); - ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); - ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); - ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); - ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); - - ImGui::SeparatorText("Borders"); - ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); - - ImGui::SeparatorText("Rounding"); - ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); - - ImGui::SeparatorText("Tabs"); - ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, 2.0f, "%.0f"); - ImGui::SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, 3.0f, "%.0f"); - ImGui::SameLine(); HelpMarker("Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set."); - ImGui::DragFloat("TabCloseButtonMinWidthSelected", &style.TabCloseButtonMinWidthSelected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthSelected < 0.0f) ? "%.0f (Always)" : "%.0f"); - ImGui::DragFloat("TabCloseButtonMinWidthUnselected", &style.TabCloseButtonMinWidthUnselected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthUnselected < 0.0f) ? "%.0f (Always)" : "%.0f"); - ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); - - ImGui::SeparatorText("Tables"); - ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderAngle("TableAngledHeadersAngle", &style.TableAngledHeadersAngle, -50.0f, +50.0f); - ImGui::SliderFloat2("TableAngledHeadersTextAlign", (float*)&style.TableAngledHeadersTextAlign, 0.0f, 1.0f, "%.2f"); - - ImGui::SeparatorText("Trees"); - bool combo_open = ImGui::BeginCombo("TreeLinesFlags", GetTreeLinesFlagsName(style.TreeLinesFlags)); - ImGui::SameLine(); + SeparatorText("Details"); + if (BeginTabBar("##tabs", ImGuiTabBarFlags_None)) + { + if (BeginTabItem("Sizes")) + { + SeparatorText("Main"); + SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); + SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); + SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); + SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); + SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); + SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); + SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); + + SeparatorText("Borders"); + SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + + SeparatorText("Rounding"); + SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + + SeparatorText("Tabs"); + SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); + SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, 2.0f, "%.0f"); + SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, 3.0f, "%.0f"); + SameLine(); HelpMarker("Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set."); + DragFloat("TabCloseButtonMinWidthSelected", &style.TabCloseButtonMinWidthSelected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthSelected < 0.0f) ? "%.0f (Always)" : "%.0f"); + DragFloat("TabCloseButtonMinWidthUnselected", &style.TabCloseButtonMinWidthUnselected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthUnselected < 0.0f) ? "%.0f (Always)" : "%.0f"); + SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); + + SeparatorText("Tables"); + SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); + SliderAngle("TableAngledHeadersAngle", &style.TableAngledHeadersAngle, -50.0f, +50.0f); + SliderFloat2("TableAngledHeadersTextAlign", (float*)&style.TableAngledHeadersTextAlign, 0.0f, 1.0f, "%.2f"); + + SeparatorText("Trees"); + bool combo_open = BeginCombo("TreeLinesFlags", GetTreeLinesFlagsName(style.TreeLinesFlags)); + SameLine(); HelpMarker("[Experimental] Tree lines may not work in all situations (e.g. using a clipper) and may incurs slight traversal overhead.\n\nImGuiTreeNodeFlags_DrawLinesFull is faster than ImGuiTreeNodeFlags_DrawLinesToNode."); if (combo_open) { const ImGuiTreeNodeFlags options[] = { ImGuiTreeNodeFlags_DrawLinesNone, ImGuiTreeNodeFlags_DrawLinesFull, ImGuiTreeNodeFlags_DrawLinesToNodes }; for (ImGuiTreeNodeFlags option : options) - if (ImGui::Selectable(GetTreeLinesFlagsName(option), style.TreeLinesFlags == option)) + if (Selectable(GetTreeLinesFlagsName(option), style.TreeLinesFlags == option)) style.TreeLinesFlags = option; - ImGui::EndCombo(); + EndCombo(); } - ImGui::SliderFloat("TreeLinesSize", &style.TreeLinesSize, 0.0f, 2.0f, "%.0f"); - ImGui::SliderFloat("TreeLinesRounding", &style.TreeLinesRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("TreeLinesSize", &style.TreeLinesSize, 0.0f, 2.0f, "%.0f"); + SliderFloat("TreeLinesRounding", &style.TreeLinesRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SeparatorText("Windows"); - ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SliderFloat("WindowBorderHoverPadding", &style.WindowBorderHoverPadding, 1.0f, 20.0f, "%.0f"); + SeparatorText("Windows"); + SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + SliderFloat("WindowBorderHoverPadding", &style.WindowBorderHoverPadding, 1.0f, 20.0f, "%.0f"); int window_menu_button_position = style.WindowMenuButtonPosition + 1; - if (ImGui::Combo("WindowMenuButtonPosition", (int*)&window_menu_button_position, "None\0Left\0Right\0")) + if (Combo("WindowMenuButtonPosition", (int*)&window_menu_button_position, "None\0Left\0Right\0")) style.WindowMenuButtonPosition = (ImGuiDir)(window_menu_button_position - 1); - ImGui::SeparatorText("Widgets"); - ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); - ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); - ImGui::SliderFloat2("SelectableTextAlign", (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SameLine(); HelpMarker("Alignment applies when a selectable is larger than its text content."); - ImGui::SliderFloat("SeparatorTextBorderSize", &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); - ImGui::SliderFloat2("SeparatorTextAlign", (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SliderFloat2("SeparatorTextPadding", (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f"); - ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("ImageBorderSize", &style.ImageBorderSize, 0.0f, 1.0f, "%.0f"); - - ImGui::SeparatorText("Tooltips"); + SeparatorText("Widgets"); + Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); + SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); + SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); + SliderFloat2("SelectableTextAlign", (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); + SameLine(); HelpMarker("Alignment applies when a selectable is larger than its text content."); + SliderFloat("SeparatorTextBorderSize", &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); + SliderFloat2("SeparatorTextAlign", (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, "%.2f"); + SliderFloat2("SeparatorTextPadding", (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f"); + SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); + SliderFloat("ImageBorderSize", &style.ImageBorderSize, 0.0f, 1.0f, "%.0f"); + + SeparatorText("Tooltips"); for (int n = 0; n < 2; n++) - if (ImGui::TreeNodeEx(n == 0 ? "HoverFlagsForTooltipMouse" : "HoverFlagsForTooltipNav")) + if (TreeNodeEx(n == 0 ? "HoverFlagsForTooltipMouse" : "HoverFlagsForTooltipNav")) { ImGuiHoveredFlags* p = (n == 0) ? &style.HoverFlagsForTooltipMouse : &style.HoverFlagsForTooltipNav; - ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNone", p, ImGuiHoveredFlags_DelayNone); - ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayShort", p, ImGuiHoveredFlags_DelayShort); - ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNormal", p, ImGuiHoveredFlags_DelayNormal); - ImGui::CheckboxFlags("ImGuiHoveredFlags_Stationary", p, ImGuiHoveredFlags_Stationary); - ImGui::CheckboxFlags("ImGuiHoveredFlags_NoSharedDelay", p, ImGuiHoveredFlags_NoSharedDelay); - ImGui::TreePop(); + CheckboxFlags("ImGuiHoveredFlags_DelayNone", p, ImGuiHoveredFlags_DelayNone); + CheckboxFlags("ImGuiHoveredFlags_DelayShort", p, ImGuiHoveredFlags_DelayShort); + CheckboxFlags("ImGuiHoveredFlags_DelayNormal", p, ImGuiHoveredFlags_DelayNormal); + CheckboxFlags("ImGuiHoveredFlags_Stationary", p, ImGuiHoveredFlags_Stationary); + CheckboxFlags("ImGuiHoveredFlags_NoSharedDelay", p, ImGuiHoveredFlags_NoSharedDelay); + TreePop(); } - ImGui::SeparatorText("Misc"); - ImGui::SliderFloat2("DisplayWindowPadding", (float*)&style.DisplayWindowPadding, 0.0f, 30.0f, "%.0f"); ImGui::SameLine(); HelpMarker("Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen."); - ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); ImGui::SameLine(); HelpMarker("Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); + SeparatorText("Misc"); + SliderFloat2("DisplayWindowPadding", (float*)&style.DisplayWindowPadding, 0.0f, 30.0f, "%.0f"); SameLine(); HelpMarker("Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen."); + SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); SameLine(); HelpMarker("Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); - ImGui::EndTabItem(); + EndTabItem(); } - if (ImGui::BeginTabItem("Colors")) + if (BeginTabItem("Colors")) { static int output_dest = 0; static bool output_only_modified = true; - if (ImGui::Button("Export")) + if (Button("Export")) { if (output_dest == 0) - ImGui::LogToClipboard(); + LogToClipboard(); else - ImGui::LogToTTY(); - ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + LogToTTY(); + LogText("ImVec4* colors = GetStyle().Colors;" IM_NEWLINE); for (int i = 0; i < ImGuiCol_COUNT; i++) { const ImVec4& col = style.Colors[i]; - const char* name = ImGui::GetStyleColorName(i); + const char* name = GetStyleColorName(i); if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) - ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, + LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23 - (int)strlen(name), "", col.x, col.y, col.z, col.w); } - ImGui::LogFinish(); + LogFinish(); } - ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); - ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); + SameLine(); SetNextItemWidth(120); Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); + SameLine(); Checkbox("Only Modified Colors", &output_only_modified); static ImGuiTextFilter filter; - filter.Draw("Filter colors", ImGui::GetFontSize() * 16); + filter.Draw("Filter colors", GetFontSize() * 16); static ImGuiColorEditFlags alpha_flags = 0; - if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_AlphaOpaque)) { alpha_flags = ImGuiColorEditFlags_AlphaOpaque; } ImGui::SameLine(); - if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine(); - if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine(); + if (RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_AlphaOpaque)) { alpha_flags = ImGuiColorEditFlags_AlphaOpaque; } SameLine(); + if (RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } SameLine(); + if (RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } SameLine(); HelpMarker( "In the color list:\n" "Left-click on color square to open color picker,\n" "Right-click to open edit options menu."); - ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX)); - ImGui::BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); - ImGui::PushItemWidth(ImGui::GetFontSize() * -12); + SetNextWindowSizeConstraints(ImVec2(0.0f, GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX)); + BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); + PushItemWidth(GetFontSize() * -12); for (int i = 0; i < ImGuiCol_COUNT; i++) { - const char* name = ImGui::GetStyleColorName(i); + const char* name = GetStyleColorName(i); if (!filter.PassFilter(name)) continue; - ImGui::PushID(i); + PushID(i); #ifndef IMGUI_DISABLE_DEBUG_TOOLS - if (ImGui::Button("?")) - ImGui::DebugFlashStyleColor((ImGuiCol)i); - ImGui::SetItemTooltip("Flash given color to identify places where it is used."); - ImGui::SameLine(); + if (Button("?")) + DebugFlashStyleColor((ImGuiCol)i); + SetItemTooltip("Flash given color to identify places where it is used."); + SameLine(); #endif - ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); + ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) { // Tips: in a real user application, you may want to merge and use an icon font into the main font, // so instead of "Save"/"Revert" you'd use icons! // Read the FAQ and docs/FONTS.md about using icon fonts. It's really easy and super convenient! - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) { ref->Colors[i] = style.Colors[i]; } - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) { style.Colors[i] = ref->Colors[i]; } + SameLine(0.0f, style.ItemInnerSpacing.x); if (Button("Save")) { ref->Colors[i] = style.Colors[i]; } + SameLine(0.0f, style.ItemInnerSpacing.x); if (Button("Revert")) { style.Colors[i] = ref->Colors[i]; } } - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); - ImGui::TextUnformatted(name); - ImGui::PopID(); + SameLine(0.0f, style.ItemInnerSpacing.x); + TextUnformatted(name); + PopID(); } - ImGui::PopItemWidth(); - ImGui::EndChild(); + PopItemWidth(); + EndChild(); - ImGui::EndTabItem(); + EndTabItem(); } - if (ImGui::BeginTabItem("Fonts")) + if (BeginTabItem("Fonts")) { - ImGuiIO& io = ImGui::GetIO(); + ImGuiIO& io = GetIO(); ImFontAtlas* atlas = io.Fonts; - HelpMarker("Read FAQ and docs/FONTS.md for details on font loading."); - ImGui::ShowFontAtlas(atlas); + ShowFontAtlas(atlas); // Post-baking font scaling. Note that this is NOT the nice way of scaling fonts, read below. // (we enforce hard clamping manually as by default DragFloat/SliderFloat allows CTRL+Click text to get out of bounds). + /* + SeparatorText("Legacy Scaling"); const float MIN_SCALE = 0.3f; const float MAX_SCALE = 2.0f; HelpMarker( @@ -8430,120 +8491,121 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) "However, the _correct_ way of scaling your UI is currently to reload your font at the designed size, " "rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure.\n" "Using those settings here will give you poor quality results."); - static float window_scale = 1.0f; - ImGui::PushItemWidth(ImGui::GetFontSize() * 8); - if (ImGui::DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window - ImGui::SetWindowFontScale(window_scale); - ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp); // Scale everything - ImGui::PopItemWidth(); + PushItemWidth(GetFontSize() * 8); + DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp); // Scale everything + //static float window_scale = 1.0f; + //if (DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window + // SetWindowFontScale(window_scale); + PopItemWidth(); + */ - ImGui::EndTabItem(); + EndTabItem(); } - if (ImGui::BeginTabItem("Rendering")) + if (BeginTabItem("Rendering")) { - ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); - ImGui::SameLine(); + Checkbox("Anti-aliased lines", &style.AntiAliasedLines); + SameLine(); HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); - ImGui::Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); - ImGui::SameLine(); + Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); + SameLine(); HelpMarker("Faster lines using texture data. Require backend to render with bilinear filtering (not point/nearest filtering)."); - ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); - ImGui::PushItemWidth(ImGui::GetFontSize() * 8); - ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); + Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + PushItemWidth(GetFontSize() * 8); + DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. - ImGui::DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); - const bool show_samples = ImGui::IsItemActive(); + DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + const bool show_samples = IsItemActive(); if (show_samples) - ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); - if (show_samples && ImGui::BeginTooltip()) + SetNextWindowPos(GetCursorScreenPos()); + if (show_samples && BeginTooltip()) { - ImGui::TextUnformatted("(R = radius, N = approx number of segments)"); - ImGui::Spacing(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - const float min_widget_width = ImGui::CalcTextSize("R: MMM\nN: MMM").x; + TextUnformatted("(R = radius, N = approx number of segments)"); + Spacing(); + ImDrawList* draw_list = GetWindowDrawList(); + const float min_widget_width = CalcTextSize("R: MMM\nN: MMM").x; for (int n = 0; n < 8; n++) { const float RAD_MIN = 5.0f; const float RAD_MAX = 70.0f; const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (8.0f - 1.0f); - ImGui::BeginGroup(); + BeginGroup(); // N is not always exact here due to how PathArcTo() function work internally - ImGui::Text("R: %.f\nN: %d", rad, draw_list->_CalcCircleAutoSegmentCount(rad)); + Text("R: %.f\nN: %d", rad, draw_list->_CalcCircleAutoSegmentCount(rad)); const float canvas_width = IM_MAX(min_widget_width, rad * 2.0f); const float offset_x = floorf(canvas_width * 0.5f); const float offset_y = floorf(RAD_MAX); - const ImVec2 p1 = ImGui::GetCursorScreenPos(); - draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); - ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + const ImVec2 p1 = GetCursorScreenPos(); + draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad, GetColorU32(ImGuiCol_Text)); + Dummy(ImVec2(canvas_width, RAD_MAX * 2)); /* - const ImVec2 p2 = ImGui::GetCursorScreenPos(); - draw_list->AddCircleFilled(ImVec2(p2.x + offset_x, p2.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); - ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + const ImVec2 p2 = GetCursorScreenPos(); + draw_list->AddCircleFilled(ImVec2(p2.x + offset_x, p2.y + offset_y), rad, GetColorU32(ImGuiCol_Text)); + Dummy(ImVec2(canvas_width, RAD_MAX * 2)); */ - ImGui::EndGroup(); - ImGui::SameLine(); + EndGroup(); + SameLine(); } - ImGui::EndTooltip(); + EndTooltip(); } - ImGui::SameLine(); + SameLine(); HelpMarker("When drawing circle primitives with \"num_segments == 0\" tessellation will be calculated automatically."); - ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. - ImGui::DragFloat("Disabled Alpha", &style.DisabledAlpha, 0.005f, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); HelpMarker("Additional alpha multiplier for disabled items (multiply over current value of Alpha)."); - ImGui::PopItemWidth(); + DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. + DragFloat("Disabled Alpha", &style.DisabledAlpha, 0.005f, 0.0f, 1.0f, "%.2f"); SameLine(); HelpMarker("Additional alpha multiplier for disabled items (multiply over current value of Alpha)."); + PopItemWidth(); - ImGui::EndTabItem(); + EndTabItem(); } - ImGui::EndTabBar(); + EndTabBar(); } - - ImGui::PopItemWidth(); + PopItemWidth(); } //----------------------------------------------------------------------------- // [SECTION] User Guide / ShowUserGuide() //----------------------------------------------------------------------------- +// We omit the ImGui:: prefix in this function, as we don't expect user to be copy and pasting this code. void ImGui::ShowUserGuide() { - ImGuiIO& io = ImGui::GetIO(); - ImGui::BulletText("Double-click on title bar to collapse window."); - ImGui::BulletText( + ImGuiIO& io = GetIO(); + BulletText("Double-click on title bar to collapse window."); + BulletText( "Click and drag on lower corner to resize window\n" "(double-click to auto fit window to its contents)."); - ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); - ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); - ImGui::BulletText("CTRL+Tab to select a window."); + BulletText("CTRL+Click on a slider or drag box to input value as text."); + BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); + BulletText("CTRL+Tab to select a window."); if (io.FontAllowUserScaling) - ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); - ImGui::BulletText("While inputting text:\n"); - ImGui::Indent(); - ImGui::BulletText("CTRL+Left/Right to word jump."); - ImGui::BulletText("CTRL+A or double-click to select all."); - ImGui::BulletText("CTRL+X/C/V to use clipboard cut/copy/paste."); - ImGui::BulletText("CTRL+Z to undo, CTRL+Y/CTRL+SHIFT+Z to redo."); - ImGui::BulletText("ESCAPE to revert."); - ImGui::Unindent(); - ImGui::BulletText("With keyboard navigation enabled:"); - ImGui::Indent(); - ImGui::BulletText("Arrow keys to navigate."); - ImGui::BulletText("Space to activate a widget."); - ImGui::BulletText("Return to input text into a widget."); - ImGui::BulletText("Escape to deactivate a widget, close popup, exit child window."); - ImGui::BulletText("Alt to jump to the menu layer of a window."); - ImGui::Unindent(); + BulletText("CTRL+Mouse Wheel to zoom window contents."); + BulletText("While inputting text:\n"); + Indent(); + BulletText("CTRL+Left/Right to word jump."); + BulletText("CTRL+A or double-click to select all."); + BulletText("CTRL+X/C/V to use clipboard cut/copy/paste."); + BulletText("CTRL+Z to undo, CTRL+Y/CTRL+SHIFT+Z to redo."); + BulletText("ESCAPE to revert."); + Unindent(); + BulletText("With keyboard navigation enabled:"); + Indent(); + BulletText("Arrow keys to navigate."); + BulletText("Space to activate a widget."); + BulletText("Return to input text into a widget."); + BulletText("Escape to deactivate a widget, close popup, exit child window."); + BulletText("Alt to jump to the menu layer of a window."); + Unindent(); } //----------------------------------------------------------------------------- @@ -10760,9 +10822,8 @@ void ImGui::ShowAboutWindow(bool*) {} void ImGui::ShowDemoWindow(bool*) {} void ImGui::ShowUserGuide() {} void ImGui::ShowStyleEditor(ImGuiStyle*) {} -bool ImGui::ShowStyleSelector(const char* label) { return false; } -void ImGui::ShowFontSelector(const char* label) {} +bool ImGui::ShowStyleSelector(const char*) { return false; } -#endif +#endif // #ifndef IMGUI_DISABLE_DEMO_WINDOWS #endif // #ifndef IMGUI_DISABLE diff --git a/PopLib/imgui/core/imgui_draw.cpp b/PopLib/imgui/core/imgui_draw.cpp index 78b0e152..64f9a9f7 100644 --- a/PopLib/imgui/core/imgui_draw.cpp +++ b/PopLib/imgui/core/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.0 WIP +// dear imgui, v1.92.2 WIP // (drawing and font code) /* @@ -13,7 +13,8 @@ Index of this file: // [SECTION] ImDrawData // [SECTION] Helpers ShadeVertsXXX functions // [SECTION] ImFontConfig -// [SECTION] ImFontAtlas +// [SECTION] ImFontAtlas, ImFontAtlasBuilder +// [SECTION] ImFontAtlas: backend for stb_truetype // [SECTION] ImFontAtlas: glyph ranges helpers // [SECTION] ImFontGlyphRangesBuilder // [SECTION] ImFont @@ -39,6 +40,7 @@ Index of this file: #endif #include // vsnprintf, sscanf, printf +#include // intptr_t // Visual Studio warnings #ifdef _MSC_VER @@ -391,6 +393,11 @@ ImDrawListSharedData::ImDrawListSharedData() ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); } +ImDrawListSharedData::~ImDrawListSharedData() +{ + IM_ASSERT(DrawLists.Size == 0); +} + void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) { if (CircleSegmentMaxError == max_error) @@ -409,22 +416,32 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) ImDrawList::ImDrawList(ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); - _Data = shared_data; + _SetDrawListSharedData(shared_data); } ImDrawList::~ImDrawList() { _ClearFreeMemory(); + _SetDrawListSharedData(NULL); +} + +void ImDrawList::_SetDrawListSharedData(ImDrawListSharedData* data) +{ + if (_Data != NULL) + _Data->DrawLists.find_erase_unsorted(this); + _Data = data; + if (_Data != NULL) + _Data->DrawLists.push_back(this); } // Initialize before use in a new frame. We always have a command ready in the buffer. -// In the majority of cases, you would want to call PushClipRect() and PushTextureID() after this. +// In the majority of cases, you would want to call PushClipRect() and PushTexture() after this. void ImDrawList::_ResetForNewFrame() { // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. IM_STATIC_ASSERT(offsetof(ImDrawCmd, ClipRect) == 0); - IM_STATIC_ASSERT(offsetof(ImDrawCmd, TextureId) == sizeof(ImVec4)); - IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureID)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, TexRef) == sizeof(ImVec4)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureRef)); if (_Splitter._Count > 1) _Splitter.Merge(this); @@ -437,7 +454,7 @@ void ImDrawList::_ResetForNewFrame() _VtxWritePtr = NULL; _IdxWritePtr = NULL; _ClipRectStack.resize(0); - _TextureIdStack.resize(0); + _TextureStack.resize(0); _CallbacksDataBuf.resize(0); _Path.resize(0); _Splitter.Clear(); @@ -455,7 +472,7 @@ void ImDrawList::_ClearFreeMemory() _VtxWritePtr = NULL; _IdxWritePtr = NULL; _ClipRectStack.clear(); - _TextureIdStack.clear(); + _TextureStack.clear(); _CallbacksDataBuf.clear(); _Path.clear(); _Splitter.ClearFreeMemory(); @@ -475,7 +492,7 @@ void ImDrawList::AddDrawCmd() { ImDrawCmd draw_cmd; draw_cmd.ClipRect = _CmdHeader.ClipRect; // Same as calling ImDrawCmd_HeaderCopy() - draw_cmd.TextureId = _CmdHeader.TextureId; + draw_cmd.TexRef = _CmdHeader.TexRef; draw_cmd.VtxOffset = _CmdHeader.VtxOffset; draw_cmd.IdxOffset = IdxBuffer.Size; @@ -530,10 +547,10 @@ void ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t use AddDrawCmd(); // Force a new command after us (see comment below) } -// Compare ClipRect, TextureId and VtxOffset with a single memcmp() +// Compare ClipRect, TexRef and VtxOffset with a single memcmp() #define ImDrawCmd_HeaderSize (offsetof(ImDrawCmd, VtxOffset) + sizeof(unsigned int)) -#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TextureId, VtxOffset -#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TextureId, VtxOffset +#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TexRef, VtxOffset +#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TexRef, VtxOffset #define ImDrawCmd_AreSequentialIdxOffset(CMD_0, CMD_1) (CMD_0->IdxOffset + CMD_0->ElemCount == CMD_1->IdxOffset) // Try to merge two last draw commands @@ -573,17 +590,20 @@ void ImDrawList::_OnChangedClipRect() curr_cmd->ClipRect = _CmdHeader.ClipRect; } -void ImDrawList::_OnChangedTextureID() +void ImDrawList::_OnChangedTexture() { // If current command is used with different settings we need to add a new command IM_ASSERT_PARANOID(CmdBuffer.Size > 0); ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; - if (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != _CmdHeader.TextureId) + if (curr_cmd->ElemCount != 0 && curr_cmd->TexRef != _CmdHeader.TexRef) { AddDrawCmd(); return; } - IM_ASSERT(curr_cmd->UserCallback == NULL); + + // Unlike other _OnChangedXXX functions this may be called by ImFontAtlasUpdateDrawListsTextures() in more locations so we need to handle this case. + if (curr_cmd->UserCallback != NULL) + return; // Try to merge with previous command if it matches, else use current command ImDrawCmd* prev_cmd = curr_cmd - 1; @@ -592,7 +612,7 @@ void ImDrawList::_OnChangedTextureID() CmdBuffer.pop_back(); return; } - curr_cmd->TextureId = _CmdHeader.TextureId; + curr_cmd->TexRef = _CmdHeader.TexRef; } void ImDrawList::_OnChangedVtxOffset() @@ -653,27 +673,30 @@ void ImDrawList::PopClipRect() _OnChangedClipRect(); } -void ImDrawList::PushTextureID(ImTextureID texture_id) +void ImDrawList::PushTexture(ImTextureRef tex_ref) { - _TextureIdStack.push_back(texture_id); - _CmdHeader.TextureId = texture_id; - _OnChangedTextureID(); + _TextureStack.push_back(tex_ref); + _CmdHeader.TexRef = tex_ref; + if (tex_ref._TexData != NULL) + IM_ASSERT(tex_ref._TexData->WantDestroyNextFrame == false); + _OnChangedTexture(); } -void ImDrawList::PopTextureID() +void ImDrawList::PopTexture() { - _TextureIdStack.pop_back(); - _CmdHeader.TextureId = (_TextureIdStack.Size == 0) ? (ImTextureID)NULL : _TextureIdStack.Data[_TextureIdStack.Size - 1]; - _OnChangedTextureID(); + _TextureStack.pop_back(); + _CmdHeader.TexRef = (_TextureStack.Size == 0) ? ImTextureRef() : _TextureStack.Data[_TextureStack.Size - 1]; + _OnChangedTexture(); } -// This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTextureID()/PopTextureID(). -void ImDrawList::_SetTextureID(ImTextureID texture_id) +// This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTexture()/PopTexture(). +void ImDrawList::_SetTexture(ImTextureRef tex_ref) { - if (_CmdHeader.TextureId == texture_id) + if (_CmdHeader.TexRef == tex_ref) return; - _CmdHeader.TextureId = texture_id; - _OnChangedTextureID(); + _CmdHeader.TexRef = tex_ref; + _TextureStack.back() = tex_ref; + _OnChangedTexture(); } // Reserve space for a number of vertices and indices. @@ -1686,8 +1709,6 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 if (font_size == 0.0f) font_size = _Data->FontSize; - IM_ASSERT(font->ContainerAtlas->TexID == _CmdHeader.TextureId); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. - ImVec4 clip_rect = _CmdHeader.ClipRect; if (cpu_fine_clip_rect) { @@ -1704,39 +1725,39 @@ void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, c AddText(_Data->Font, _Data->FontSize, pos, col, text_begin, text_end); } -void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col) +void ImDrawList::AddImage(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); PrimReserve(6, 4); PrimRectUV(p_min, p_max, uv_min, uv_max, col); if (push_texture_id) - PopTextureID(); + PopTexture(); } -void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, const ImVec2& uv4, ImU32 col) +void ImDrawList::AddImageQuad(ImTextureRef tex_ref, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, const ImVec2& uv4, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); PrimReserve(6, 4); PrimQuadUV(p1, p2, p3, p4, uv1, uv2, uv3, uv4, col); if (push_texture_id) - PopTextureID(); + PopTexture(); } -void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags) +void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -1744,13 +1765,13 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi flags = FixRectCornerFlags(flags); if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { - AddImage(user_texture_id, p_min, p_max, uv_min, uv_max, col); + AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col); return; } - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); int vert_start_idx = VtxBuffer.Size; PathRect(p_min, p_max, rounding, flags); @@ -1759,7 +1780,7 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true); if (push_texture_id) - PopTextureID(); + PopTexture(); } //----------------------------------------------------------------------------- @@ -2187,7 +2208,7 @@ void ImDrawListSplitter::Merge(ImDrawList* draw_list) // If current command is used with different settings we need to add a new command ImDrawCmd* curr_cmd = &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1]; if (curr_cmd->ElemCount == 0) - ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset + ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TexRef, VtxOffset else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) draw_list->AddDrawCmd(); @@ -2213,7 +2234,7 @@ void ImDrawListSplitter::SetCurrentChannel(ImDrawList* draw_list, int idx) if (curr_cmd == NULL) draw_list->AddDrawCmd(); else if (curr_cmd->ElemCount == 0) - ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset + ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TexRef, VtxOffset else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) draw_list->AddDrawCmd(); } @@ -2229,6 +2250,7 @@ void ImDrawData::Clear() CmdLists.resize(0); // The ImDrawList are NOT owned by ImDrawData but e.g. by ImGuiContext, so we don't clear them. DisplayPos = DisplaySize = FramebufferScale = ImVec2(0.0f, 0.0f); OwnerViewport = NULL; + Textures = NULL; } // Important: 'out_list' is generally going to be draw_data->CmdLists, but may be another temporary list @@ -2290,17 +2312,16 @@ void ImDrawData::DeIndexAllBuffers() { ImVector new_vtx_buffer; TotalVtxCount = TotalIdxCount = 0; - for (int i = 0; i < CmdListsCount; i++) + for (ImDrawList* draw_list : CmdLists) { - ImDrawList* cmd_list = CmdLists[i]; - if (cmd_list->IdxBuffer.empty()) + if (draw_list->IdxBuffer.empty()) continue; - new_vtx_buffer.resize(cmd_list->IdxBuffer.Size); - for (int j = 0; j < cmd_list->IdxBuffer.Size; j++) - new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]]; - cmd_list->VtxBuffer.swap(new_vtx_buffer); - cmd_list->IdxBuffer.resize(0); - TotalVtxCount += cmd_list->VtxBuffer.Size; + new_vtx_buffer.resize(draw_list->IdxBuffer.Size); + for (int j = 0; j < draw_list->IdxBuffer.Size; j++) + new_vtx_buffer[j] = draw_list->VtxBuffer[draw_list->IdxBuffer[j]]; + draw_list->VtxBuffer.swap(new_vtx_buffer); + draw_list->IdxBuffer.resize(0); + TotalVtxCount += draw_list->VtxBuffer.Size; } } @@ -2379,6 +2400,7 @@ void ImGui::ShadeVertsTransformPos(ImDrawList* draw_list, int vert_start_idx, in // [SECTION] ImFontConfig //----------------------------------------------------------------------------- +// FIXME-NEWATLAS: Oversample specification could be more dynamic. For now, favoring automatic selection. ImFontConfig::ImFontConfig() { memset(this, 0, sizeof(*this)); @@ -2392,39 +2414,157 @@ ImFontConfig::ImFontConfig() } //----------------------------------------------------------------------------- -// [SECTION] ImFontAtlas +// [SECTION] ImTextureData +//----------------------------------------------------------------------------- +// - ImTextureData::Create() +// - ImTextureData::DestroyPixels() +//----------------------------------------------------------------------------- + +int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format) +{ + switch (format) + { + case ImTextureFormat_Alpha8: return 1; + case ImTextureFormat_RGBA32: return 4; + } + IM_ASSERT(0); + return 0; +} + +const char* ImTextureDataGetStatusName(ImTextureStatus status) +{ + switch (status) + { + case ImTextureStatus_OK: return "OK"; + case ImTextureStatus_Destroyed: return "Destroyed"; + case ImTextureStatus_WantCreate: return "WantCreate"; + case ImTextureStatus_WantUpdates: return "WantUpdates"; + case ImTextureStatus_WantDestroy: return "WantDestroy"; + } + return "N/A"; +} + +const char* ImTextureDataGetFormatName(ImTextureFormat format) +{ + switch (format) + { + case ImTextureFormat_Alpha8: return "Alpha8"; + case ImTextureFormat_RGBA32: return "RGBA32"; + } + return "N/A"; +} + +void ImTextureData::Create(ImTextureFormat format, int w, int h) +{ + IM_ASSERT(Status == ImTextureStatus_Destroyed); + DestroyPixels(); + Format = format; + Status = ImTextureStatus_WantCreate; + Width = w; + Height = h; + BytesPerPixel = ImTextureDataGetFormatBytesPerPixel(format); + UseColors = false; + Pixels = (unsigned char*)IM_ALLOC(Width * Height * BytesPerPixel); + IM_ASSERT(Pixels != NULL); + memset(Pixels, 0, Width * Height * BytesPerPixel); + UsedRect.x = UsedRect.y = UsedRect.w = UsedRect.h = 0; + UpdateRect.x = UpdateRect.y = (unsigned short)~0; + UpdateRect.w = UpdateRect.h = 0; +} + +void ImTextureData::DestroyPixels() +{ + if (Pixels) + IM_FREE(Pixels); + Pixels = NULL; + UseColors = false; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas, ImFontAtlasBuilder //----------------------------------------------------------------------------- // - Default texture data encoded in ASCII +// - ImFontAtlas() +// - ImFontAtlas::Clear() +// - ImFontAtlas::CompactCache() // - ImFontAtlas::ClearInputData() // - ImFontAtlas::ClearTexData() // - ImFontAtlas::ClearFonts() -// - ImFontAtlas::Clear() -// - ImFontAtlas::GetTexDataAsAlpha8() -// - ImFontAtlas::GetTexDataAsRGBA32() +//----------------------------------------------------------------------------- +// - ImFontAtlasUpdateNewFrame() +// - ImFontAtlasTextureBlockConvert() +// - ImFontAtlasTextureBlockPostProcess() +// - ImFontAtlasTextureBlockPostProcessMultiply() +// - ImFontAtlasTextureBlockFill() +// - ImFontAtlasTextureBlockCopy() +// - ImFontAtlasTextureBlockQueueUpload() +//----------------------------------------------------------------------------- +// - ImFontAtlas::GetTexDataAsAlpha8() [legacy] +// - ImFontAtlas::GetTexDataAsRGBA32() [legacy] +// - ImFontAtlas::Build() [legacy] +//----------------------------------------------------------------------------- // - ImFontAtlas::AddFont() // - ImFontAtlas::AddFontDefault() // - ImFontAtlas::AddFontFromFileTTF() // - ImFontAtlas::AddFontFromMemoryTTF() // - ImFontAtlas::AddFontFromMemoryCompressedTTF() // - ImFontAtlas::AddFontFromMemoryCompressedBase85TTF() -// - ImFontAtlas::AddCustomRectRegular() -// - ImFontAtlas::AddCustomRectFontGlyph() -// - ImFontAtlas::CalcCustomRectUV() +// - ImFontAtlas::RemoveFont() +// - ImFontAtlasBuildNotifySetFont() +//----------------------------------------------------------------------------- +// - ImFontAtlas::AddCustomRect() +// - ImFontAtlas::RemoveCustomRect() +// - ImFontAtlas::GetCustomRect() +// - ImFontAtlas::AddCustomRectFontGlyph() [legacy] +// - ImFontAtlas::AddCustomRectFontGlyphForSize() [legacy] // - ImFontAtlasGetMouseCursorTexData() -// - ImFontAtlas::Build() -// - ImFontAtlasBuildMultiplyCalcLookupTable() -// - ImFontAtlasBuildMultiplyRectAlpha8() -// - ImFontAtlasBuildWithStbTruetype() -// - ImFontAtlasGetBuilderForStbTruetype() -// - ImFontAtlasUpdateSourcesPointers() -// - ImFontAtlasBuildSetupFont() -// - ImFontAtlasBuildPackCustomRects() -// - ImFontAtlasBuildRender8bppRectFromString() -// - ImFontAtlasBuildRender32bppRectFromString() -// - ImFontAtlasBuildRenderDefaultTexData() -// - ImFontAtlasBuildRenderLinesTexData() +//----------------------------------------------------------------------------- +// - ImFontAtlasBuildMain() +// - ImFontAtlasBuildSetupFontLoader() +// - ImFontAtlasBuildPreloadAllGlyphRanges() +// - ImFontAtlasBuildUpdatePointers() +// - ImFontAtlasBuildRenderBitmapFromString() +// - ImFontAtlasBuildUpdateBasicTexData() +// - ImFontAtlasBuildUpdateLinesTexData() +// - ImFontAtlasBuildAddFont() +// - ImFontAtlasBuildSetupFontBakedEllipsis() +// - ImFontAtlasBuildSetupFontBakedBlanks() +// - ImFontAtlasBuildSetupFontBakedFallback() +// - ImFontAtlasBuildSetupFontSpecialGlyphs() +// - ImFontAtlasBuildDiscardBakes() +// - ImFontAtlasBuildDiscardFontBakedGlyph() +// - ImFontAtlasBuildDiscardFontBaked() +// - ImFontAtlasBuildDiscardFontBakes() +//----------------------------------------------------------------------------- +// - ImFontAtlasAddDrawListSharedData() +// - ImFontAtlasRemoveDrawListSharedData() +// - ImFontAtlasUpdateDrawListsTextures() +// - ImFontAtlasUpdateDrawListsSharedData() +//----------------------------------------------------------------------------- +// - ImFontAtlasBuildSetTexture() +// - ImFontAtlasBuildAddTexture() +// - ImFontAtlasBuildMakeSpace() +// - ImFontAtlasBuildRepackTexture() +// - ImFontAtlasBuildGrowTexture() +// - ImFontAtlasBuildRepackOrGrowTexture() +// - ImFontAtlasBuildGetTextureSizeEstimate() +// - ImFontAtlasBuildCompactTexture() // - ImFontAtlasBuildInit() -// - ImFontAtlasBuildFinish() +// - ImFontAtlasBuildDestroy() +//----------------------------------------------------------------------------- +// - ImFontAtlasPackInit() +// - ImFontAtlasPackAllocRectEntry() +// - ImFontAtlasPackReuseRectEntry() +// - ImFontAtlasPackDiscardRect() +// - ImFontAtlasPackAddRect() +// - ImFontAtlasPackGetRect() +//----------------------------------------------------------------------------- +// - ImFontBaked_BuildGrowIndex() +// - ImFontBaked_BuildLoadGlyph() +// - ImFontBaked_BuildLoadGlyphAdvanceX() +// - ImFontAtlasDebugLogTextureRequests() +//----------------------------------------------------------------------------- +// - ImFontAtlasGetFontLoaderForStbTruetype() //----------------------------------------------------------------------------- // A work of art lies ahead! (. = white layer, X = black layer, others are blank) @@ -2479,143 +2619,461 @@ static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3 { ImVec2(109,0),ImVec2(13,15), ImVec2( 6, 7) }, // ImGuiMouseCursor_NotAllowed }; +#define IM_FONTGLYPH_INDEX_UNUSED ((ImU16)-1) // 0xFFFF +#define IM_FONTGLYPH_INDEX_NOT_FOUND ((ImU16)-2) // 0xFFFE + ImFontAtlas::ImFontAtlas() { memset(this, 0, sizeof(*this)); + TexDesiredFormat = ImTextureFormat_RGBA32; TexGlyphPadding = 1; - PackIdMouseCursors = PackIdLines = -1; + TexMinWidth = 512; + TexMinHeight = 128; + TexMaxWidth = 8192; + TexMaxHeight = 8192; + RendererHasTextures = false; // Assumed false by default, as apps can call e.g Atlas::Build() after backend init and before ImGui can update. + TexNextUniqueID = 1; + FontNextUniqueID = 1; + Builder = NULL; } ImFontAtlas::~ImFontAtlas() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - Clear(); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. + ClearFonts(); + ClearTexData(); + TexList.clear_delete(); + TexData = NULL; } -void ImFontAtlas::ClearInputData() +void ImFontAtlas::Clear() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - for (ImFontConfig& font_cfg : Sources) - if (font_cfg.FontData && font_cfg.FontDataOwnedByAtlas) - { - IM_FREE(font_cfg.FontData); - font_cfg.FontData = NULL; - } + bool backup_renderer_has_textures = RendererHasTextures; + RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. + ClearFonts(); + ClearTexData(); + RendererHasTextures = backup_renderer_has_textures; +} + +void ImFontAtlas::CompactCache() +{ + ImFontAtlasTextureCompact(this); +} + +void ImFontAtlas::SetFontLoader(const ImFontLoader* font_loader) +{ + ImFontAtlasBuildSetupFontLoader(this, font_loader); +} + +void ImFontAtlas::ClearInputData() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); - // When clearing this we lose access to the font name and other information used to build the font. for (ImFont* font : Fonts) - if (font->Sources >= Sources.Data && font->Sources < Sources.Data + Sources.Size) - { - font->Sources = NULL; - font->SourcesCount = 0; - } + ImFontAtlasFontDestroyOutput(this, font); + for (ImFontConfig& font_cfg : Sources) + ImFontAtlasFontDestroySourceData(this, &font_cfg); + for (ImFont* font : Fonts) + { + // When clearing this we lose access to the font name and other information used to build the font. + font->Sources.clear(); + font->Flags |= ImFontFlags_NoLoadGlyphs; + } Sources.clear(); - CustomRects.clear(); - PackIdMouseCursors = PackIdLines = -1; - // Important: we leave TexReady untouched } -void ImFontAtlas::ClearTexData() +// Clear CPU-side copy of the texture data. +void ImFontAtlas::ClearTexData() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - if (TexPixelsAlpha8) - IM_FREE(TexPixelsAlpha8); - if (TexPixelsRGBA32) - IM_FREE(TexPixelsRGBA32); - TexPixelsAlpha8 = NULL; - TexPixelsRGBA32 = NULL; - TexPixelsUseColors = false; - // Important: we leave TexReady untouched + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + IM_ASSERT(RendererHasTextures == false && "Not supported for dynamic atlases, but you may call Clear()."); + for (ImTextureData* tex : TexList) + tex->DestroyPixels(); + //Locked = true; // Hoped to be able to lock this down but some reload patterns may not be happy with it. } -void ImFontAtlas::ClearFonts() +void ImFontAtlas::ClearFonts() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + // FIXME-NEWATLAS: Illegal to remove currently bound font. + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + ImFontAtlasBuildDestroy(this); ClearInputData(); Fonts.clear_delete(); - TexReady = false; + TexIsBuilt = false; + for (ImDrawListSharedData* shared_data : DrawListSharedDatas) + if (shared_data->FontAtlas == this) + { + shared_data->Font = NULL; + shared_data->FontScale = shared_data->FontSize = 0.0f; + } } -void ImFontAtlas::Clear() +static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* atlas) { - ClearInputData(); - ClearTexData(); - ClearFonts(); + // [LEGACY] Copy back the ImGuiBackendFlags_RendererHasTextures flag from ImGui context. + // - This is the 1% exceptional case where that dependency if useful, to bypass an issue where otherwise at the + // time of an early call to Build(), it would be impossible for us to tell if the backend supports texture update. + // - Without this hack, we would have quite a pitfall as many legacy codebases have an early call to Build(). + // Whereas conversely, the portion of people using ImDrawList without ImGui is expected to be pathologically rare. + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + if (ImGuiContext* imgui_ctx = shared_data->Context) + { + atlas->RendererHasTextures = (imgui_ctx->IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; + break; + } +} + +// Called by NewFrame() for atlases owned by a context. +// If you manually manage font atlases, you'll need to call this yourself. +// - 'frame_count' needs to be provided because we can gc/prioritize baked fonts based on their age. +// - 'frame_count' may not match those of all imgui contexts using this atlas, as contexts may be updated as different frequencies. But generally you can use ImGui::GetFrameCount() on one of your context. +void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool renderer_has_textures) +{ + IM_ASSERT(atlas->Builder == NULL || atlas->Builder->FrameCount < frame_count); // Protection against being called twice. + atlas->RendererHasTextures = renderer_has_textures; + + // Check that font atlas was built or backend support texture reload in which case we can build now + if (atlas->RendererHasTextures) + { + atlas->TexIsBuilt = true; + if (atlas->Builder == NULL) // This will only happen if fonts were not already loaded. + ImFontAtlasBuildMain(atlas); + } + // Legacy backend + if (!atlas->RendererHasTextures) + IM_ASSERT_USER_ERROR(atlas->TexIsBuilt, "Backend does not support ImGuiBackendFlags_RendererHasTextures, and font atlas is not built! Update backend OR make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()."); + if (atlas->TexIsBuilt && atlas->Builder->PreloadedAllGlyphsRanges) + IM_ASSERT_USER_ERROR(atlas->RendererHasTextures == false, "Called ImFontAtlas::Build() before ImGuiBackendFlags_RendererHasTextures got set! With new backends: you don't need to call Build()."); + + // Clear BakedCurrent cache, this is important because it ensure the uncached path gets taken once. + // We also rely on ImFontBaked* pointers never crossing frames. + ImFontAtlasBuilder* builder = atlas->Builder; + builder->FrameCount = frame_count; + for (ImFont* font : atlas->Fonts) + font->LastBaked = NULL; + + // Garbage collect BakedPool + if (builder->BakedDiscardedCount > 0) + { + int dst_n = 0, src_n = 0; + for (; src_n < builder->BakedPool.Size; src_n++) + { + ImFontBaked* p_src = &builder->BakedPool[src_n]; + if (p_src->WantDestroy) + continue; + ImFontBaked* p_dst = &builder->BakedPool[dst_n++]; + if (p_dst == p_src) + continue; + memcpy(p_dst, p_src, sizeof(ImFontBaked)); + builder->BakedMap.SetVoidPtr(p_dst->BakedId, p_dst); + } + IM_ASSERT(dst_n + builder->BakedDiscardedCount == src_n); + builder->BakedPool.Size -= builder->BakedDiscardedCount; + builder->BakedDiscardedCount = 0; + } + + // Update texture status + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + ImTextureData* tex = atlas->TexList[tex_n]; + bool remove_from_list = false; + if (tex->Status == ImTextureStatus_OK) + { + tex->Updates.resize(0); + tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0; + tex->UpdateRect.w = tex->UpdateRect.h = 0; + } + if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures) + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK."); + + if (tex->Status == ImTextureStatus_Destroyed) + { + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture Status to Destroyed but did not clear TexID/BackendUserData!"); + if (tex->WantDestroyNextFrame) + remove_from_list = true; // Destroy was scheduled by us + else + tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend (e.g. freed resources mid-run) + } + else if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_WantDestroy) + { + // Request destroy. + // - Keep bool to true in order to differentiate a planned destroy vs a destroy decided by the backend. + // - We don't destroy pixels right away, as backend may have an in-flight copy from RAM. + IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); + tex->Status = ImTextureStatus_WantDestroy; + } + + // The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering. + // We allow the texture staying in _WantDestroy state and increment a counter which the backend can use to take its decision. + if (tex->Status == ImTextureStatus_WantDestroy) + tex->UnusedFrames++; + + // If a texture has never reached the backend, they don't need to know about it. + if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL) + remove_from_list = true; + + // Destroy and remove + if (remove_from_list) + { + tex->DestroyPixels(); + IM_DELETE(tex); + atlas->TexList.erase(atlas->TexList.begin() + tex_n); + tex_n--; + } + } +} + +void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) +{ + IM_ASSERT(src_pixels != NULL && dst_pixels != NULL); + if (src_fmt == dst_fmt) + { + int line_sz = w * ImTextureDataGetFormatBytesPerPixel(src_fmt); + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + memcpy(dst_pixels, src_pixels, line_sz); + } + else if (src_fmt == ImTextureFormat_Alpha8 && dst_fmt == ImTextureFormat_RGBA32) + { + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + { + const ImU8* src_p = (const ImU8*)src_pixels; + ImU32* dst_p = (ImU32*)(void*)dst_pixels; + for (int nx = w; nx > 0; nx--) + *dst_p++ = IM_COL32(255, 255, 255, (unsigned int)(*src_p++)); + } + } + else if (src_fmt == ImTextureFormat_RGBA32 && dst_fmt == ImTextureFormat_Alpha8) + { + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + { + const ImU32* src_p = (const ImU32*)(void*)src_pixels; + ImU8* dst_p = (ImU8*)dst_pixels; + for (int nx = w; nx > 0; nx--) + *dst_p++ = ((*src_p++) >> IM_COL32_A_SHIFT) & 0xFF; + } + } + else + { + IM_ASSERT(0); + } } -void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +// Source buffer may be written to (used for in-place mods). +// Post-process hooks may eventually be added here. +void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data) { - // Build atlas on demand - if (TexPixelsAlpha8 == NULL) - Build(); + // Multiply operator (legacy) + if (data->FontSrc->RasterizerMultiply != 1.0f) + ImFontAtlasTextureBlockPostProcessMultiply(data, data->FontSrc->RasterizerMultiply); +} - *out_pixels = TexPixelsAlpha8; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; +void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor) +{ + unsigned char* pixels = (unsigned char*)data->Pixels; + int pitch = data->Pitch; + if (data->Format == ImTextureFormat_Alpha8) + { + for (int ny = data->Height; ny > 0; ny--, pixels += pitch) + { + ImU8* p = (ImU8*)pixels; + for (int nx = data->Width; nx > 0; nx--, p++) + { + unsigned int v = ImMin((unsigned int)(*p * multiply_factor), (unsigned int)255); + *p = (unsigned char)v; + } + } + } + else if (data->Format == ImTextureFormat_RGBA32) //-V547 + { + for (int ny = data->Height; ny > 0; ny--, pixels += pitch) + { + ImU32* p = (ImU32*)(void*)pixels; + for (int nx = data->Width; nx > 0; nx--, p++) + { + unsigned int a = ImMin((unsigned int)(((*p >> IM_COL32_A_SHIFT) & 0xFF) * multiply_factor), (unsigned int)255); + *p = IM_COL32((*p >> IM_COL32_R_SHIFT) & 0xFF, (*p >> IM_COL32_G_SHIFT) & 0xFF, (*p >> IM_COL32_B_SHIFT) & 0xFF, a); + } + } + } + else + { + IM_ASSERT(0); + } } -void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +// Fill with single color. We don't use this directly but it is convenient for anyone working on uploading custom rects. +void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h, ImU32 col) { - // Convert to RGBA32 format on demand - // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp - if (!TexPixelsRGBA32) + if (dst_tex->Format == ImTextureFormat_Alpha8) + { + ImU8 col_a = (col >> IM_COL32_A_SHIFT) & 0xFF; + for (int y = 0; y < h; y++) + memset((ImU8*)dst_tex->GetPixelsAt(dst_x, dst_y + y), col_a, w); + } + else { - unsigned char* pixels = NULL; - GetTexDataAsAlpha8(&pixels, NULL, NULL); - if (pixels) + for (int y = 0; y < h; y++) { - TexPixelsRGBA32 = (unsigned int*)IM_ALLOC((size_t)TexWidth * (size_t)TexHeight * 4); - const unsigned char* src = pixels; - unsigned int* dst = TexPixelsRGBA32; - for (int n = TexWidth * TexHeight; n > 0; n--) - *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + ImU32* p = (ImU32*)(void*)dst_tex->GetPixelsAt(dst_x, dst_y + y); + for (int x = w; x > 0; x--, p++) + *p = col; } } +} + +// Copy block from one texture to another +void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h) +{ + IM_ASSERT(src_tex->Pixels != NULL && dst_tex->Pixels != NULL); + IM_ASSERT(src_tex->Format == dst_tex->Format); + IM_ASSERT(src_x >= 0 && src_x + w <= src_tex->Width); + IM_ASSERT(src_y >= 0 && src_y + h <= src_tex->Height); + IM_ASSERT(dst_x >= 0 && dst_x + w <= dst_tex->Width); + IM_ASSERT(dst_y >= 0 && dst_y + h <= dst_tex->Height); + for (int y = 0; y < h; y++) + memcpy(dst_tex->GetPixelsAt(dst_x, dst_y + y), src_tex->GetPixelsAt(src_x, src_y + y), w * dst_tex->BytesPerPixel); +} + +// Queue texture block update for renderer backend +void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h) +{ + IM_ASSERT(tex->Status != ImTextureStatus_WantDestroy && tex->Status != ImTextureStatus_Destroyed); + IM_ASSERT(x >= 0 && x <= 0xFFFF && y >= 0 && y <= 0xFFFF && w >= 0 && x + w <= 0x10000 && h >= 0 && y + h <= 0x10000); + IM_UNUSED(atlas); + + ImTextureRect req = { (unsigned short)x, (unsigned short)y, (unsigned short)w, (unsigned short)h }; + int new_x1 = ImMax(tex->UpdateRect.w == 0 ? 0 : tex->UpdateRect.x + tex->UpdateRect.w, req.x + req.w); + int new_y1 = ImMax(tex->UpdateRect.h == 0 ? 0 : tex->UpdateRect.y + tex->UpdateRect.h, req.y + req.h); + tex->UpdateRect.x = ImMin(tex->UpdateRect.x, req.x); + tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y); + tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x); + tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y); + tex->UsedRect.x = ImMin(tex->UsedRect.x, req.x); + tex->UsedRect.y = ImMin(tex->UsedRect.y, req.y); + tex->UsedRect.w = (unsigned short)(ImMax(tex->UsedRect.x + tex->UsedRect.w, req.x + req.w) - tex->UsedRect.x); + tex->UsedRect.h = (unsigned short)(ImMax(tex->UsedRect.y + tex->UsedRect.h, req.y + req.h) - tex->UsedRect.y); + atlas->TexIsBuilt = false; + + // No need to queue if status is == ImTextureStatus_WantCreate + if (tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantUpdates) + { + tex->Status = ImTextureStatus_WantUpdates; + tex->Updates.push_back(req); + } +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +static void GetTexDataAsFormat(ImFontAtlas* atlas, ImTextureFormat format, unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + ImTextureData* tex = atlas->TexData; + if (!atlas->TexIsBuilt || tex == NULL || tex->Pixels == NULL || atlas->TexDesiredFormat != format) + { + atlas->TexDesiredFormat = format; + atlas->Build(); + tex = atlas->TexData; + } + if (out_pixels) { *out_pixels = (unsigned char*)tex->Pixels; }; + if (out_width) { *out_width = tex->Width; }; + if (out_height) { *out_height = tex->Height; }; + if (out_bytes_per_pixel) { *out_bytes_per_pixel = tex->BytesPerPixel; } +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + GetTexDataAsFormat(this, ImTextureFormat_Alpha8, out_pixels, out_width, out_height, out_bytes_per_pixel); +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + GetTexDataAsFormat(this, ImTextureFormat_RGBA32, out_pixels, out_width, out_height, out_bytes_per_pixel); +} - *out_pixels = (unsigned char*)TexPixelsRGBA32; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; +bool ImFontAtlas::Build() +{ + ImFontAtlasBuildMain(this); + return true; } +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) +ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); - IM_ASSERT(font_cfg->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); - IM_ASSERT(font_cfg->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); + // Sanity Checks + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + IM_ASSERT((font_cfg_in->FontData != NULL && font_cfg_in->FontDataSize > 0) || (font_cfg_in->FontLoader != NULL)); + //IM_ASSERT(font_cfg_in->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); + IM_ASSERT(font_cfg_in->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); + if (font_cfg_in->GlyphOffset.x != 0.0f || font_cfg_in->GlyphOffset.y != 0.0f || font_cfg_in->GlyphMinAdvanceX != 0.0f || font_cfg_in->GlyphMaxAdvanceX != FLT_MAX) + IM_ASSERT(font_cfg_in->SizePixels != 0.0f && "Specifying glyph offset/advances requires a reference size to base it on."); + + // Lazily create builder on the first call to AddFont + if (Builder == NULL) + ImFontAtlasBuildInit(this); // Create new font - if (!font_cfg->MergeMode) - Fonts.push_back(IM_NEW(ImFont)); + ImFont* font; + if (!font_cfg_in->MergeMode) + { + font = IM_NEW(ImFont)(); + font->FontId = FontNextUniqueID++; + font->Flags = font_cfg_in->Flags; + font->LegacySize = font_cfg_in->SizePixels; + font->CurrentRasterizerDensity = font_cfg_in->RasterizerDensity; + Fonts.push_back(font); + } else + { IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + font = Fonts.back(); + } - Sources.push_back(*font_cfg); - ImFontConfig& new_font_cfg = Sources.back(); - if (new_font_cfg.DstFont == NULL) - new_font_cfg.DstFont = Fonts.back(); - if (!new_font_cfg.FontDataOwnedByAtlas) + // Add to list + Sources.push_back(*font_cfg_in); + ImFontConfig* font_cfg = &Sources.back(); + if (font_cfg->DstFont == NULL) + font_cfg->DstFont = font; + font->Sources.push_back(font_cfg); + ImFontAtlasBuildUpdatePointers(this); // Pointers to Sources are otherwise dangling after we called Sources.push_back(). + + if (font_cfg->FontDataOwnedByAtlas == false) { - new_font_cfg.FontData = IM_ALLOC(new_font_cfg.FontDataSize); - new_font_cfg.FontDataOwnedByAtlas = true; - memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + font_cfg->FontDataOwnedByAtlas = true; + font_cfg->FontData = ImMemdup(font_cfg->FontData, (size_t)font_cfg->FontDataSize); } - // Round font size - // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. - // - Note that using io.FontGlobalScale or SetWindowFontScale(), with are legacy-ish, partially supported features, can still lead to unrounded sizes. - // - We may support it better later and remove this rounding. - new_font_cfg.SizePixels = ImTrunc(new_font_cfg.SizePixels); + // Sanity check + // We don't round cfg.SizePixels yet as relative size of merged fonts are used afterwards. + if (font_cfg->GlyphExcludeRanges != NULL) + { + int size = 0; + for (const ImWchar* p = font_cfg->GlyphExcludeRanges; p[0] != 0; p++, size++) {} + IM_ASSERT((size & 1) == 0 && "GlyphExcludeRanges[] size must be multiple of two!"); + IM_ASSERT((size <= 64) && "GlyphExcludeRanges[] size must be small!"); + font_cfg->GlyphExcludeRanges = (ImWchar*)ImMemdup(font_cfg->GlyphExcludeRanges, sizeof(font_cfg->GlyphExcludeRanges[0]) * (size + 1)); + } + if (font_cfg->FontLoader != NULL) + { + IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL); + IM_ASSERT(font_cfg->FontLoader->LoaderInit == NULL && font_cfg->FontLoader->LoaderShutdown == NULL); // FIXME-NEWATLAS: Unsupported yet. + } + IM_ASSERT(font_cfg->FontLoaderData == NULL); - // Pointers to Sources data are otherwise dangling - ImFontAtlasUpdateSourcesPointers(this); + if (!ImFontAtlasFontSourceInit(this, font_cfg)) + { + // Rollback (this is a fragile/rarely exercised code-path. TestSuite's "misc_atlas_add_invalid_font" aim to test this) + ImFontAtlasFontDestroySourceData(this, font_cfg); + Sources.pop_back(); + font->Sources.pop_back(); + if (!font_cfg->MergeMode) + { + IM_DELETE(font); + Fonts.pop_back(); + } + return NULL; + } + ImFontAtlasFontSourceAddToFont(this, font, font_cfg); - // Invalidate texture - TexReady = false; - ClearTexData(); - return new_font_cfg.DstFont; + return font; } // Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) @@ -2649,9 +3107,9 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f * 1.0f; if (font_cfg.Name[0] == '\0') - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf, %dpx", (int)font_cfg.SizePixels); + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf"); font_cfg.EllipsisChar = (ImWchar)0x0085; - font_cfg.GlyphOffset.y = 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units + font_cfg.GlyphOffset.y += 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units int ttf_compressed_size = 0; const char* ttf_compressed = GetDefaultCompressedFontDataTTF(&ttf_compressed_size); @@ -2667,12 +3125,16 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); size_t data_size = 0; void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); if (!data) { - IM_ASSERT_USER_ERROR(0, "Could not load font file!"); + if (font_cfg_template == NULL || (font_cfg_template->Flags & ImFontFlags_NoLoadError) == 0) + { + IMGUI_DEBUG_LOG("While loading '%s'\n", filename); + IM_ASSERT_USER_ERROR(0, "Could not load font file!"); + } return NULL; } ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); @@ -2681,7 +3143,7 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, // Store a short copy of filename into into the font name for convenience const char* p; for (p = filename + ImStrlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s", p); } return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges); } @@ -2689,7 +3151,7 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, // NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); IM_ASSERT(font_cfg.FontData == NULL); IM_ASSERT(font_data_size > 100 && "Incorrect value for font_data_size!"); // Heuristic to prevent accidentally passing a wrong value to font_data_size. @@ -2723,43 +3185,156 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed return font; } -int ImFontAtlas::AddCustomRectRegular(int width, int height) +// On font removal we need to remove references (otherwise we could queue removal?) +// We allow old_font == new_font which forces updating all values (e.g. sizes) +static void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, ImFont* new_font) +{ + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + { + if (shared_data->Font == old_font) + shared_data->Font = new_font; + if (ImGuiContext* ctx = shared_data->Context) + { + if (ctx->IO.FontDefault == old_font) + ctx->IO.FontDefault = new_font; + if (ctx->Font == old_font) + { + ImGuiContext* curr_ctx = ImGui::GetCurrentContext(); + bool need_bind_ctx = ctx != curr_ctx; + if (need_bind_ctx) + ImGui::SetCurrentContext(ctx); + ImGui::SetCurrentFont(new_font, ctx->FontSizeBase, ctx->FontSize); + if (need_bind_ctx) + ImGui::SetCurrentContext(curr_ctx); + } + for (ImFontStackData& font_stack_data : ctx->FontStack) + if (font_stack_data.Font == old_font) + font_stack_data.Font = new_font; + } + } +} + +void ImFontAtlas::RemoveFont(ImFont* font) +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + font->ClearOutputData(); + + ImFontAtlasFontDestroyOutput(this, font); + for (ImFontConfig* src : font->Sources) + ImFontAtlasFontDestroySourceData(this, src); + for (int src_n = 0; src_n < Sources.Size; src_n++) + if (Sources[src_n].DstFont == font) + Sources.erase(&Sources[src_n--]); + + bool removed = Fonts.find_erase(font); + IM_ASSERT(removed); + IM_UNUSED(removed); + + ImFontAtlasBuildUpdatePointers(this); + + font->ContainerAtlas = NULL; + IM_DELETE(font); + + // Notify external systems + ImFont* new_current_font = Fonts.empty() ? NULL : Fonts[0]; + ImFontAtlasBuildNotifySetFont(this, font, new_current_font); +} + +// At it is common to do an AddCustomRect() followed by a GetCustomRect(), we provide an optional 'ImFontAtlasRect* out_r = NULL' argument to retrieve the info straight away. +ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height, ImFontAtlasRect* out_r) { IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - ImFontAtlasCustomRect r; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - CustomRects.push_back(r); - return CustomRects.Size - 1; // Return index + + if (Builder == NULL) + ImFontAtlasBuildInit(this); + + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); + if (r_id == ImFontAtlasRectId_Invalid) + return ImFontAtlasRectId_Invalid; + if (out_r != NULL) + GetCustomRect(r_id, out_r); + + if (RendererHasTextures) + { + ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); + ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); + } + return r_id; +} + +void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id) +{ + if (ImFontAtlasPackGetRectSafe(this, id) == NULL) + return; + ImFontAtlasPackDiscardRect(this, id); } -int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// This API does not make sense anymore with scalable fonts. +// - Prefer adding a font source (ImFontConfig) using a custom/procedural loader. +// - You may use ImFontFlags_LockBakedSizes to limit an existing font to known baked sizes: +// ImFont* myfont = io.Fonts->AddFontFromFileTTF(....); +// myfont->GetFontBaked(16.0f); +// myfont->Flags |= ImFontFlags_LockBakedSizes; +ImFontAtlasRectId ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) +{ + float font_size = font->LegacySize; + return AddCustomRectFontGlyphForSize(font, font_size, codepoint, width, height, advance_x, offset); +} +// FIXME: we automatically set glyph.Colored=true by default. +// If you need to alter this, you can write 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph(). +ImFontAtlasRectId ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) { #ifdef IMGUI_USE_WCHAR32 - IM_ASSERT(id <= IM_UNICODE_CODEPOINT_MAX); + IM_ASSERT(codepoint <= IM_UNICODE_CODEPOINT_MAX); #endif IM_ASSERT(font != NULL); IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - ImFontAtlasCustomRect r; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - r.GlyphID = id; - r.GlyphColored = 0; // Set to 1 manually to mark glyph as colored // FIXME: No official API for that (#8133) - r.GlyphAdvanceX = advance_x; - r.GlyphOffset = offset; - r.Font = font; - CustomRects.push_back(r); - return CustomRects.Size - 1; // Return index -} -void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const -{ - IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates - IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed - *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); - *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); + ImFontBaked* baked = font->GetFontBaked(font_size); + + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); + if (r_id == ImFontAtlasRectId_Invalid) + return ImFontAtlasRectId_Invalid; + ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); + if (RendererHasTextures) + ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); + + if (baked->IsGlyphLoaded(codepoint)) + ImFontAtlasBakedDiscardFontGlyph(this, font, baked, baked->FindGlyph(codepoint)); + + ImFontGlyph glyph; + glyph.Codepoint = codepoint; + glyph.AdvanceX = advance_x; + glyph.X0 = offset.x; + glyph.Y0 = offset.y; + glyph.X1 = offset.x + r->w; + glyph.Y1 = offset.y + r->h; + glyph.Visible = true; + glyph.Colored = true; // FIXME: Arbitrary + glyph.PackId = r_id; + ImFontAtlasBakedAddFontGlyph(this, baked, font->Sources[0], &glyph); + return r_id; +} +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +bool ImFontAtlas::GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const +{ + ImTextureRect* r = ImFontAtlasPackGetRectSafe((ImFontAtlas*)this, id); + if (r == NULL) + return false; + IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates + if (out_r == NULL) + return true; + out_r->x = r->x; + out_r->y = r->y; + out_r->w = r->w; + out_r->h = r->h; + out_r->uv0 = ImVec2((float)(r->x), (float)(r->y)) * TexUvScale; + out_r->uv1 = ImVec2((float)(r->x + r->w), (float)(r->y + r->h)) * TexUvScale; + return true; } bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) @@ -2769,9 +3344,8 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) return false; - IM_ASSERT(atlas->PackIdMouseCursors != -1); - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors); - ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->X, (float)r->Y); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, atlas->Builder->PackIdMouseCursors); + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->x, (float)r->y); ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; *out_size = size; *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; @@ -2783,604 +3357,1383 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso return true; } -bool ImFontAtlas::Build() +// When atlas->RendererHasTextures = true, this is only called if no font were loaded. +void ImFontAtlasBuildMain(ImFontAtlas* atlas) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - - // Default font is none are specified - if (Sources.Size == 0) - AddFontDefault(); - - // Select builder - // - Note that we do not reassign to atlas->FontBuilderIO, since it is likely to point to static data which - // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are - // using a hot-reloading scheme that messes up static data, store your own instance of ImFontBuilderIO somewhere - // and point to it instead of pointing directly to return value of the GetBuilderXXX functions. - const ImFontBuilderIO* builder_io = FontBuilderIO; - if (builder_io == NULL) + IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); + if (atlas->TexData && atlas->TexData->Format != atlas->TexDesiredFormat) { -#ifdef IMGUI_ENABLE_FREETYPE - builder_io = ImGuiFreeType::GetBuilderForFreeType(); -#elif defined(IMGUI_ENABLE_STB_TRUETYPE) - builder_io = ImFontAtlasGetBuilderForStbTruetype(); -#else - IM_ASSERT(0); // Invalid Build function -#endif + ImVec2i new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas); + ImFontAtlasBuildDestroy(atlas); + ImFontAtlasTextureAdd(atlas, new_tex_size.x, new_tex_size.y); } - // Build - return builder_io->FontBuilder_Build(this); -} + if (atlas->Builder == NULL) + ImFontAtlasBuildInit(atlas); -void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) -{ - for (unsigned int i = 0; i < 256; i++) - { - unsigned int value = (unsigned int)(i * in_brighten_factor); - out_table[i] = value > 255 ? 255 : (value & 0xFF); - } -} + // Default font is none are specified + if (atlas->Sources.Size == 0) + atlas->AddFontDefault(); -void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) -{ - IM_ASSERT_PARANOID(w <= stride); - unsigned char* data = pixels + x + y * stride; - for (int j = h; j > 0; j--, data += stride - w) - for (int i = w; i > 0; i--, data++) - *data = table[*data]; + // [LEGACY] For backends not supporting RendererHasTextures: preload all glyphs + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); + if (atlas->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures + ImFontAtlasBuildLegacyPreloadAllGlyphRanges(atlas); + atlas->TexIsBuilt = true; } -void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v) +void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v) { // Automatically disable horizontal oversampling over size 36 - *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (src->SizePixels * src->RasterizerDensity > 36.0f || src->PixelSnapH) ? 1 : 2; + const float raster_size = baked->Size * baked->RasterizerDensity * src->RasterizerDensity; + *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (raster_size > 36.0f || src->PixelSnapH) ? 1 : 2; *out_oversample_v = (src->OversampleV != 0) ? src->OversampleV : 1; } -#ifdef IMGUI_ENABLE_STB_TRUETYPE -// Temporary data for one source font (multiple source fonts can be merged into one destination ImFont) -// (C++03 doesn't allow instancing ImVector<> with function-local types so we declare the type here.) -struct ImFontBuildSrcData -{ - stbtt_fontinfo FontInfo; - stbtt_pack_range PackRange; // Hold the list of codepoints to pack (essentially points to Codepoints.Data) - stbrp_rect* Rects; // Rectangle to pack. We first fill in their size and the packer will give us their position. - stbtt_packedchar* PackedChars; // Output glyphs - const ImWchar* SrcRanges; // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF) - int DstIndex; // Index into atlas->Fonts[] and dst_tmp_array[] - int GlyphsHighest; // Highest requested codepoint - int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font) - ImBitVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB) - ImVector GlyphsList; // Glyph codepoints list (flattened version of GlyphsSet) -}; - -// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont) -struct ImFontBuildDstData +// Setup main font loader for the atlas +// Every font source (ImFontConfig) will use this unless ImFontConfig::FontLoader specify a custom loader. +void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader) { - int SrcCount; // Number of source fonts targeting this destination font. - int GlyphsHighest; - int GlyphsCount; - ImBitVector GlyphsSet; // This is used to resolve collision when multiple sources are merged into a same destination font. -}; + if (atlas->FontLoader == font_loader) + return; + IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); -static void UnpackBitVectorToFlatIndexList(const ImBitVector* in, ImVector* out) -{ - IM_ASSERT(sizeof(in->Storage.Data[0]) == sizeof(int)); - const ImU32* it_begin = in->Storage.begin(); - const ImU32* it_end = in->Storage.end(); - for (const ImU32* it = it_begin; it < it_end; it++) - if (ImU32 entries_32 = *it) - for (ImU32 bit_n = 0; bit_n < 32; bit_n++) - if (entries_32 & ((ImU32)1 << bit_n)) - out->push_back((int)(((it - it_begin) << 5) + bit_n)); -} + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontDestroyOutput(atlas, font); + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) + atlas->FontLoader->LoaderShutdown(atlas); -static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) -{ - IM_ASSERT(atlas->Sources.Size > 0); + atlas->FontLoader = font_loader; + atlas->FontLoaderName = font_loader ? font_loader->Name : "NULL"; + IM_ASSERT(atlas->FontLoaderData == NULL); - ImFontAtlasBuildInit(atlas); + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontInitOutput(atlas, font); + for (ImFont* font : atlas->Fonts) + for (ImFontConfig* src : font->Sources) + ImFontAtlasFontSourceAddToFont(atlas, font, src); +} - // Clear atlas - atlas->TexID = (ImTextureID)NULL; - atlas->TexWidth = atlas->TexHeight = 0; - atlas->TexUvScale = ImVec2(0.0f, 0.0f); - atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); - atlas->ClearTexData(); - - // Temporary storage for building - ImVector src_tmp_array; - ImVector dst_tmp_array; - src_tmp_array.resize(atlas->Sources.Size); - dst_tmp_array.resize(atlas->Fonts.Size); - memset(src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes()); - memset(dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes()); - - // 1. Initialize font loading structure, check font data validity - for (int src_i = 0; src_i < atlas->Sources.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontConfig& src = atlas->Sources[src_i]; - IM_ASSERT(src.DstFont && (!src.DstFont->IsLoaded() || src.DstFont->ContainerAtlas == atlas)); - - // Find index from src.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices) - src_tmp.DstIndex = -1; - for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++) - if (src.DstFont == atlas->Fonts[output_i]) - src_tmp.DstIndex = output_i; - if (src_tmp.DstIndex == -1) +// Preload all glyph ranges for legacy backends. +// This may lead to multiple texture creation which might be a little slower than before. +void ImFontAtlasBuildLegacyPreloadAllGlyphRanges(ImFontAtlas* atlas) +{ + atlas->Builder->PreloadedAllGlyphsRanges = true; + for (ImFont* font : atlas->Fonts) + { + ImFontBaked* baked = font->GetFontBaked(font->LegacySize); + if (font->FallbackChar != 0) + baked->FindGlyph(font->FallbackChar); + if (font->EllipsisChar != 0) + baked->FindGlyph(font->EllipsisChar); + for (ImFontConfig* src : font->Sources) { - IM_ASSERT(src_tmp.DstIndex != -1); // src.DstFont not pointing within atlas->Fonts[] array? - return false; + const ImWchar* ranges = src->GlyphRanges ? src->GlyphRanges : atlas->GetGlyphRangesDefault(); + for (; ranges[0]; ranges += 2) + for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 + baked->FindGlyph((ImWchar)c); } - // Initialize helper structure for font loading and verify that the TTF/OTF data is correct - const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src.FontData, src.FontNo); - IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); - if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)src.FontData, font_offset)) + } +} + +// FIXME: May make ImFont::Sources a ImSpan<> and move ownership to ImFontAtlas +void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) +{ + for (ImFont* font : atlas->Fonts) + font->Sources.resize(0); + for (ImFontConfig& src : atlas->Sources) + src.DstFont->Sources.push_back(&src); +} + +// Render a white-colored bitmap encoded in a string +void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char) +{ + ImTextureData* tex = atlas->TexData; + IM_ASSERT(x >= 0 && x + w <= tex->Width); + IM_ASSERT(y >= 0 && y + h <= tex->Height); + + switch (tex->Format) + { + case ImTextureFormat_Alpha8: + { + ImU8* out_p = (ImU8*)tex->GetPixelsAt(x, y); + for (int off_y = 0; off_y < h; off_y++, out_p += tex->Width, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_p[off_x] = (in_str[off_x] == in_marker_char) ? 0xFF : 0x00; + break; + } + case ImTextureFormat_RGBA32: + { + ImU32* out_p = (ImU32*)tex->GetPixelsAt(x, y); + for (int off_y = 0; off_y < h; off_y++, out_p += tex->Width, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_p[off_x] = (in_str[off_x] == in_marker_char) ? IM_COL32_WHITE : IM_COL32_BLACK_TRANS; + break; + } + } +} + +static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas) +{ + // Pack and store identifier so we can refresh UV coordinates on texture resize. + // FIXME-NEWATLAS: User/custom rects where user code wants to store UV coordinates will need to do the same thing. + ImFontAtlasBuilder* builder = atlas->Builder; + ImVec2i pack_size = (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) ? ImVec2i(2, 2) : ImVec2i(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + + ImFontAtlasRect r; + bool add_and_draw = (atlas->GetCustomRect(builder->PackIdMouseCursors, &r) == false); + if (add_and_draw) + { + builder->PackIdMouseCursors = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdMouseCursors != ImFontAtlasRectId_Invalid); + + // Draw to texture + if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) { - IM_ASSERT(0 && "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); - return false; + // 2x2 white pixels + ImFontAtlasBuildRenderBitmapFromString(atlas, r.x, r.y, 2, 2, "XX" "XX", 'X'); } + else + { + // 2x2 white pixels + mouse cursors + const int x_for_white = r.x; + const int x_for_black = r.x + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_white, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.'); + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_black, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X'); + } + } - // Measure highest codepoints - ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.SrcRanges = src.GlyphRanges ? src.GlyphRanges : atlas->GetGlyphRangesDefault(); - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) + // Refresh UV coordinates + atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y); +} + +static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas) +{ + if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) + return; + + // Pack and store identifier so we can refresh UV coordinates on texture resize. + ImTextureData* tex = atlas->TexData; + ImFontAtlasBuilder* builder = atlas->Builder; + + ImFontAtlasRect r; + bool add_and_draw = atlas->GetCustomRect(builder->PackIdLinesTexData, &r) == false; + if (add_and_draw) + { + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + builder->PackIdLinesTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdLinesTexData != ImFontAtlasRectId_Invalid); + } + + // Register texture region for thick lines + // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row + // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row + { + // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + const int y = n; + const int line_width = n; + const int pad_left = (r.w - line_width) / 2; + const int pad_right = r.w - (pad_left + line_width); + IM_ASSERT(pad_left + line_width + pad_right == r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels + + // Write each slice + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = 0x00; + + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = 0xFF; + + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = 0x00; + } + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) { - // Check for valid range. This may also help detect *some* dangling pointers, because a common - // user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent, - // or to forget to zero-terminate the glyph range array. - IM_ASSERT(src_range[0] <= src_range[1] && "Invalid range: is your glyph range array persistent? it is zero-terminated?"); - src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]); + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = IM_COL32_WHITE; + + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); } - dst_tmp.SrcCount++; - dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest); + + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; + float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts + atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); + } +} + +//----------------------------------------------------------------------------------------------------------------------------- + +// Was tempted to lazily init FontSrc but wouldn't save much + makes it more complicated to detect invalid data at AddFont() +bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font) +{ + bool ret = true; + for (ImFontConfig* src : font->Sources) + if (!ImFontAtlasFontSourceInit(atlas, src)) + ret = false; + IM_ASSERT(ret); // Unclear how to react to this meaningfully. Assume that result will be same as initial AddFont() call. + return ret; +} + +// Keep source/input FontData +void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font) +{ + font->ClearOutputData(); + for (ImFontConfig* src : font->Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader && loader->FontSrcDestroy != NULL) + loader->FontSrcDestroy(atlas, src); } +} + +//----------------------------------------------------------------------------------------------------------------------------- + +bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src) +{ + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontSrcInit != NULL && !loader->FontSrcInit(atlas, src)) + return false; + return true; +} - // 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs. - int total_glyphs_count = 0; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) +void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src) +{ + if (src->MergeMode == false) { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.GlyphsSet.Create(src_tmp.GlyphsHighest + 1); - if (dst_tmp.GlyphsSet.Storage.empty()) - dst_tmp.GlyphsSet.Create(dst_tmp.GlyphsHighest + 1); + font->ClearOutputData(); + //font->FontSize = src->SizePixels; + font->ContainerAtlas = atlas; + IM_ASSERT(font->Sources[0] == src); + } + atlas->TexIsBuilt = false; // For legacy backends + ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, font, src); +} + +void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + if (src->FontDataOwnedByAtlas) + IM_FREE(src->FontData); + src->FontData = NULL; + if (src->GlyphExcludeRanges) + IM_FREE((void*)src->GlyphExcludeRanges); + src->GlyphExcludeRanges = NULL; +} + +// Create a compact, baked "..." if it doesn't exist, by using the ".". +// This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. +// FIXME-NEWATLAS: This borrows too much from FontLoader's FontLoadGlyph() handlers and suggest that we should add further helpers. +static ImFontGlyph* ImFontAtlasBuildSetupFontBakedEllipsis(ImFontAtlas* atlas, ImFontBaked* baked) +{ + ImFont* font = baked->ContainerFont; + IM_ASSERT(font->EllipsisChar != 0); - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) - for (unsigned int codepoint = src_range[0]; codepoint <= src_range[1]; codepoint++) + const ImFontGlyph* dot_glyph = baked->FindGlyphNoFallback((ImWchar)'.'); + if (dot_glyph == NULL) + dot_glyph = baked->FindGlyphNoFallback((ImWchar)0xFF0E); + if (dot_glyph == NULL) + return NULL; + ImFontAtlasRectId dot_r_id = dot_glyph->PackId; // Deep copy to avoid invalidation of glyphs and rect pointers + ImTextureRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_r_id); + const int dot_spacing = 1; + const float dot_step = (dot_glyph->X1 - dot_glyph->X0) + dot_spacing; + + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, (dot_r->w * 3 + dot_spacing * 2), dot_r->h); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + + ImFontGlyph glyph_in = {}; + ImFontGlyph* glyph = &glyph_in; + glyph->Codepoint = font->EllipsisChar; + glyph->AdvanceX = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + dot_step * 3.0f - dot_spacing); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. + glyph->X0 = dot_glyph->X0; + glyph->Y0 = dot_glyph->Y0; + glyph->X1 = dot_glyph->X0 + dot_step * 3 - dot_spacing; + glyph->Y1 = dot_glyph->Y1; + glyph->Visible = true; + glyph->PackId = pack_id; + glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, glyph); + dot_glyph = NULL; // Invalidated + + // Copy to texture, post-process and queue update for backend + // FIXME-NEWATLAS-V2: Dot glyph is already post-processed as this point, so this would damage it. + dot_r = ImFontAtlasPackGetRect(atlas, dot_r_id); + ImTextureData* tex = atlas->TexData; + for (int n = 0; n < 3; n++) + ImFontAtlasTextureBlockCopy(tex, dot_r->x, dot_r->y, tex, r->x + (dot_r->w + dot_spacing) * n, r->y, dot_r->w, dot_r->h); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); + + return glyph; +} + +// Load fallback in order to obtain its index +// (this is called from in hot-path so we avoid extraneous parameters to minimize impact on code size) +static void ImFontAtlasBuildSetupFontBakedFallback(ImFontBaked* baked) +{ + IM_ASSERT(baked->FallbackGlyphIndex == -1); + IM_ASSERT(baked->FallbackAdvanceX == 0.0f); + ImFont* font = baked->ContainerFont; + ImFontGlyph* fallback_glyph = NULL; + if (font->FallbackChar != 0) + fallback_glyph = baked->FindGlyphNoFallback(font->FallbackChar); + if (fallback_glyph == NULL) + { + ImFontGlyph* space_glyph = baked->FindGlyphNoFallback((ImWchar)' '); + ImFontGlyph glyph; + glyph.Codepoint = 0; + glyph.AdvanceX = space_glyph ? space_glyph->AdvanceX : IM_ROUND(baked->Size * 0.40f); + fallback_glyph = ImFontAtlasBakedAddFontGlyph(font->ContainerAtlas, baked, NULL, &glyph); + } + baked->FallbackGlyphIndex = baked->Glyphs.index_from_ptr(fallback_glyph); // Storing index avoid need to update pointer on growth and simplify inner loop code + baked->FallbackAdvanceX = fallback_glyph->AdvanceX; +} + +static void ImFontAtlasBuildSetupFontBakedBlanks(ImFontAtlas* atlas, ImFontBaked* baked) +{ + // Mark space as always hidden (not strictly correct/necessary. but some e.g. icons fonts don't have a space. it tends to look neater in previews) + ImFontGlyph* space_glyph = baked->FindGlyphNoFallback((ImWchar)' '); + if (space_glyph != NULL) + space_glyph->Visible = false; + + // Setup Tab character. + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (baked->FindGlyphNoFallback('\t') == NULL && space_glyph != NULL) + { + ImFontGlyph tab_glyph; + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX = space_glyph->AdvanceX * IM_TABSIZE; + ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, &tab_glyph); + } +} + +// Load/identify special glyphs +// (note that this is called again for fonts with MergeMode) +void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src) +{ + IM_UNUSED(atlas); + IM_ASSERT(font->Sources.contains(src)); + + // Find Fallback character. Actual glyph loaded in GetFontBaked(). + const ImWchar fallback_chars[] = { font->FallbackChar, (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; + if (font->FallbackChar == 0) + for (ImWchar candidate_char : fallback_chars) + if (candidate_char != 0 && font->IsGlyphInFont(candidate_char)) { - if (dst_tmp.GlyphsSet.TestBit(codepoint)) // Don't overwrite existing glyphs. We could make this an option for MergeMode (e.g. MergeOverwrite==true) - continue; - if (!stbtt_FindGlyphIndex(&src_tmp.FontInfo, codepoint)) // It is actually in the font? - continue; + font->FallbackChar = (ImWchar)candidate_char; + break; + } - // Add to avail set/counters - src_tmp.GlyphsCount++; - dst_tmp.GlyphsCount++; - src_tmp.GlyphsSet.SetBit(codepoint); - dst_tmp.GlyphsSet.SetBit(codepoint); - total_glyphs_count++; + // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). + // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. + // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. + const ImWchar ellipsis_chars[] = { src->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; + if (font->EllipsisChar == 0) + for (ImWchar candidate_char : ellipsis_chars) + if (candidate_char != 0 && font->IsGlyphInFont(candidate_char)) + { + font->EllipsisChar = candidate_char; + break; } + if (font->EllipsisChar == 0) + { + font->EllipsisChar = 0x0085; + font->EllipsisAutoBake = true; } +} - // 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another) - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount); - UnpackBitVectorToFlatIndexList(&src_tmp.GlyphsSet, &src_tmp.GlyphsList); - src_tmp.GlyphsSet.Clear(); - IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount); - } - for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++) - dst_tmp_array[dst_i].GlyphsSet.Clear(); - dst_tmp_array.clear(); - - // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) - // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity) - ImVector buf_rects; - ImVector buf_packedchars; - buf_rects.resize(total_glyphs_count); - buf_packedchars.resize(total_glyphs_count); - memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes()); - memset(buf_packedchars.Data, 0, (size_t)buf_packedchars.size_in_bytes()); - - // 4. Gather glyphs sizes so we can pack them in our virtual canvas. - int total_surface = 0; - int buf_rects_out_n = 0; - int buf_packedchars_out_n = 0; - const int pack_padding = atlas->TexGlyphPadding; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) +void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph) +{ + if (glyph->PackId != ImFontAtlasRectId_Invalid) { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; + ImFontAtlasPackDiscardRect(atlas, glyph->PackId); + glyph->PackId = ImFontAtlasRectId_Invalid; + } + ImWchar c = (ImWchar)glyph->Codepoint; + IM_ASSERT(font->FallbackChar != c && font->EllipsisChar != c); // Unsupported for simplicity + IM_ASSERT(glyph >= baked->Glyphs.Data && glyph < baked->Glyphs.Data + baked->Glyphs.Size); + IM_UNUSED(font); + baked->IndexLookup[c] = IM_FONTGLYPH_INDEX_UNUSED; + baked->IndexAdvanceX[c] = baked->FallbackAdvanceX; +} + +ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density, ImGuiID baked_id) +{ + IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", font_size); + ImFontBaked* baked = atlas->Builder->BakedPool.push_back(ImFontBaked()); + baked->Size = font_size; + baked->RasterizerDensity = font_rasterizer_density; + baked->BakedId = baked_id; + baked->ContainerFont = font; + baked->LastUsedFrame = atlas->Builder->FrameCount; + + // Initialize backend data + size_t loader_data_size = 0; + for (ImFontConfig* src : font->Sources) // Cannot easily be cached as we allow changing backend + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + loader_data_size += loader->FontBakedSrcLoaderDataSize; + } + baked->FontLoaderDatas = (loader_data_size > 0) ? IM_ALLOC(loader_data_size) : NULL; + char* loader_data_p = (char*)baked->FontLoaderDatas; + for (ImFontConfig* src : font->Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontBakedInit) + loader->FontBakedInit(atlas, src, baked, loader_data_p); + loader_data_p += loader->FontBakedSrcLoaderDataSize; + } + + ImFontAtlasBuildSetupFontBakedBlanks(atlas, baked); + return baked; +} - src_tmp.Rects = &buf_rects[buf_rects_out_n]; - src_tmp.PackedChars = &buf_packedchars[buf_packedchars_out_n]; - buf_rects_out_n += src_tmp.GlyphsCount; - buf_packedchars_out_n += src_tmp.GlyphsCount; - - // Automatic selection of oversampling parameters - ImFontConfig& src = atlas->Sources[src_i]; - int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(&src, &oversample_h, &oversample_v); - - // Convert our ranges in the format stb_truetype wants - src_tmp.PackRange.font_size = src.SizePixels * src.RasterizerDensity; - src_tmp.PackRange.first_unicode_codepoint_in_range = 0; - src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.Data; - src_tmp.PackRange.num_chars = src_tmp.GlyphsList.Size; - src_tmp.PackRange.chardata_for_range = src_tmp.PackedChars; - src_tmp.PackRange.h_oversample = (unsigned char)oversample_h; - src_tmp.PackRange.v_oversample = (unsigned char)oversample_v; - - // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects) - const float scale = (src.SizePixels > 0.0f) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels * src.RasterizerDensity) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -src.SizePixels * src.RasterizerDensity); - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++) +// FIXME-OPT: This is not a fast query. Adding a BakedCount field in Font might allow to take a shortcut for the most common case. +ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + for (int step_n = 0; step_n < 2; step_n++) + { + ImFontBaked* closest_larger_match = NULL; + ImFontBaked* closest_smaller_match = NULL; + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) { - int x0, y0, x1, y1; - const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]); - IM_ASSERT(glyph_index_in_font != 0); - stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * oversample_h, scale * oversample_v, 0, 0, &x0, &y0, &x1, &y1); - src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + pack_padding + oversample_h - 1); - src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + pack_padding + oversample_v - 1); - total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->ContainerFont != font || baked->WantDestroy) + continue; + if (step_n == 0 && baked->RasterizerDensity != font_rasterizer_density) // First try with same density + continue; + if (baked->Size > font_size && (closest_larger_match == NULL || baked->Size < closest_larger_match->Size)) + closest_larger_match = baked; + if (baked->Size < font_size && (closest_smaller_match == NULL || baked->Size > closest_smaller_match->Size)) + closest_smaller_match = baked; } + if (closest_larger_match) + if (closest_smaller_match == NULL || (closest_larger_match->Size >= font_size * 2.0f && closest_smaller_match->Size > font_size * 0.5f)) + return closest_larger_match; + if (closest_smaller_match) + return closest_smaller_match; } - for (int i = 0; i < atlas->CustomRects.Size; i++) - total_surface += (atlas->CustomRects[i].Width + pack_padding) * (atlas->CustomRects[i].Height + pack_padding); + return NULL; +} - // We need a width for the skyline algorithm, any width! - // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. - // User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface. - const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1; - atlas->TexHeight = 0; - if (atlas->TexDesiredWidth > 0) - atlas->TexWidth = atlas->TexDesiredWidth; - else - atlas->TexWidth = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512; +void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + IMGUI_DEBUG_LOG_FONT("[font] Discard baked %.2f for \"%s\"\n", baked->Size, font->GetDebugName()); - // 5. Start packing - // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). - const int TEX_HEIGHT_MAX = 1024 * 32; - stbtt_pack_context spc = {}; - stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, 0, NULL); - spc.padding = atlas->TexGlyphPadding; // Because we mixup stbtt_PackXXX and stbrp_PackXXX there's a bit of a hack here, not passing the value to stbtt_PackBegin() allows us to still pack a TexWidth-1 wide item. (#8107) - ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); + for (ImFontGlyph& glyph : baked->Glyphs) + if (glyph.PackId != ImFontAtlasRectId_Invalid) + ImFontAtlasPackDiscardRect(atlas, glyph.PackId); - // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point. - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + char* loader_data_p = (char*)baked->FontLoaderDatas; + for (ImFontConfig* src : font->Sources) { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontBakedDestroy) + loader->FontBakedDestroy(atlas, src, baked, loader_data_p); + loader_data_p += loader->FontBakedSrcLoaderDataSize; + } + if (baked->FontLoaderDatas) + { + IM_FREE(baked->FontLoaderDatas); + baked->FontLoaderDatas = NULL; + } + builder->BakedMap.SetVoidPtr(baked->BakedId, NULL); + builder->BakedDiscardedCount++; + baked->ClearOutputData(); + baked->WantDestroy = true; + font->LastBaked = NULL; +} + +// use unused_frames==0 to discard everything. +void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames) +{ + if (ImFontAtlasBuilder* builder = atlas->Builder) // This can be called from font destructor + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->LastUsedFrame + unused_frames > atlas->Builder->FrameCount) + continue; + if (baked->ContainerFont != font || baked->WantDestroy) + continue; + ImFontAtlasBakedDiscard(atlas, font, baked); + } +} + +// use unused_frames==0 to discard everything. +void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->LastUsedFrame + unused_frames > atlas->Builder->FrameCount) + continue; + if (baked->WantDestroy || (baked->ContainerFont->Flags & ImFontFlags_LockBakedSizes)) continue; + ImFontAtlasBakedDiscard(atlas, baked->ContainerFont, baked); + } +} - stbrp_pack_rects((stbrp_context*)spc.pack_info, src_tmp.Rects, src_tmp.GlyphsCount); +// Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* +void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) +{ + IM_ASSERT(!atlas->DrawListSharedDatas.contains(data)); + atlas->DrawListSharedDatas.push_back(data); +} + +void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) +{ + IM_ASSERT(atlas->DrawListSharedDatas.contains(data)); + atlas->DrawListSharedDatas.find_erase(data); +} + +// Update texture identifier in all active draw lists +void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex) +{ + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + for (ImDrawList* draw_list : shared_data->DrawLists) + { + // Replace in command-buffer + // (there is not need to replace in ImDrawListSplitter: current channel is in ImDrawList's CmdBuffer[], + // other channels will be on SetCurrentChannel() which already needs to compare CmdHeader anyhow) + if (draw_list->CmdBuffer.Size > 0 && draw_list->_CmdHeader.TexRef == old_tex) + draw_list->_SetTexture(new_tex); + + // Replace in stack + for (ImTextureRef& stacked_tex : draw_list->_TextureStack) + if (stacked_tex == old_tex) + stacked_tex = new_tex; + } +} + +// Update texture coordinates in all draw list shared context +// FIXME-NEWATLAS FIXME-OPT: Doesn't seem necessary to update for all, only one bound to current context? +void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) +{ + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + if (shared_data->FontAtlas == atlas) + { + shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; + shared_data->TexUvLines = atlas->TexUvLines; + } +} + +// Set current texture. This is mostly called from AddTexture() + to handle a failed resize. +static void ImFontAtlasBuildSetTexture(ImFontAtlas* atlas, ImTextureData* tex) +{ + ImTextureRef old_tex_ref = atlas->TexRef; + atlas->TexData = tex; + atlas->TexUvScale = ImVec2(1.0f / tex->Width, 1.0f / tex->Height); + atlas->TexRef._TexData = tex; + //atlas->TexRef._TexID = tex->TexID; // <-- We intentionally don't do that. It would be misleading and betray promise that both fields aren't set. + ImFontAtlasUpdateDrawListsTextures(atlas, old_tex_ref, atlas->TexRef); +} + +// Create a new texture, discard previous one +ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h) +{ + ImTextureData* old_tex = atlas->TexData; + ImTextureData* new_tex; - // Extend texture height and mark missing glyphs as non-packed so we won't render them. - // FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?) - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) - if (src_tmp.Rects[glyph_i].was_packed) - atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h); + // FIXME: Cannot reuse texture because old UV may have been used already (unless we remap UV). + /*if (old_tex != NULL && old_tex->Status == ImTextureStatus_WantCreate) + { + // Reuse texture not yet used by backend. + IM_ASSERT(old_tex->TexID == ImTextureID_Invalid && old_tex->BackendUserData == NULL); + old_tex->DestroyPixels(); + old_tex->Updates.clear(); + new_tex = old_tex; + old_tex = NULL; + } + else*/ + { + // Add new + new_tex = IM_NEW(ImTextureData)(); + new_tex->UniqueID = atlas->TexNextUniqueID++; + atlas->TexList.push_back(new_tex); + } + if (old_tex != NULL) + { + // Queue old as to destroy next frame + old_tex->WantDestroyNextFrame = true; + IM_ASSERT(old_tex->Status == ImTextureStatus_OK || old_tex->Status == ImTextureStatus_WantCreate || old_tex->Status == ImTextureStatus_WantUpdates); } - // 7. Allocate texture - atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); - atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); - atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight); - memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); - spc.pixels = atlas->TexPixelsAlpha8; - spc.height = atlas->TexHeight; + new_tex->Create(atlas->TexDesiredFormat, w, h); + atlas->TexIsBuilt = false; - // 8. Render/rasterize font characters into the texture - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + ImFontAtlasBuildSetTexture(atlas, new_tex); + + return new_tex; +} + +#if 0 +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "../stb/stb_image_write.h" +static void ImFontAtlasDebugWriteTexToDisk(ImTextureData* tex, const char* description) +{ + ImGuiContext& g = *GImGui; + char buf[128]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "[%05d] Texture #%03d - %s.png", g.FrameCount, tex->UniqueID, description); + stbi_write_png(buf, tex->Width, tex->Height, tex->BytesPerPixel, tex->Pixels, tex->GetPitch()); // tex->BytesPerPixel is technically not component, but ok for the formats we support. +} +#endif + +void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + builder->LockDisableResize = true; + + ImTextureData* old_tex = atlas->TexData; + ImTextureData* new_tex = ImFontAtlasTextureAdd(atlas, w, h); + new_tex->UseColors = old_tex->UseColors; + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize+repack %dx%d => Texture #%03d: %dx%d\n", old_tex->UniqueID, old_tex->Width, old_tex->Height, new_tex->UniqueID, new_tex->Width, new_tex->Height); + //for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + // IMGUI_DEBUG_LOG_FONT("[font] - Baked %.2fpx, %d glyphs, want_destroy=%d\n", builder->BakedPool[baked_n].FontSize, builder->BakedPool[baked_n].Glyphs.Size, builder->BakedPool[baked_n].WantDestroy); + //IMGUI_DEBUG_LOG_FONT("[font] - Old packed rects: %d, area %d px\n", builder->RectsPackedCount, builder->RectsPackedSurface); + //ImFontAtlasDebugWriteTexToDisk(old_tex, "Before Pack"); + + // Repack, lose discarded rectangle, copy pixels + // FIXME-NEWATLAS: This is unstable because packing order is based on RectsIndex + // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic, and fix stability. + // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. + ImFontAtlasPackInit(atlas); + ImVector old_rects; + ImVector old_index = builder->RectsIndex; + old_rects.swap(builder->Rects); + + for (ImFontAtlasRectEntry& index_entry : builder->RectsIndex) { - ImFontConfig& src = atlas->Sources[src_i]; - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) + if (index_entry.IsUsed == false) + continue; + ImTextureRect& old_r = old_rects[index_entry.TargetIndex]; + if (old_r.w == 0 && old_r.h == 0) continue; + ImFontAtlasRectId new_r_id = ImFontAtlasPackAddRect(atlas, old_r.w, old_r.h, &index_entry); + if (new_r_id == ImFontAtlasRectId_Invalid) + { + // Undo, grow texture and try repacking again. + // FIXME-NEWATLAS-TESTS: This is a very rarely exercised path! It needs to be automatically tested properly. + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize failed. Will grow.\n", new_tex->UniqueID); + new_tex->WantDestroyNextFrame = true; + builder->Rects.swap(old_rects); + builder->RectsIndex = old_index; + ImFontAtlasBuildSetTexture(atlas, old_tex); + ImFontAtlasTextureGrow(atlas, w, h); // Recurse + return; + } + IM_ASSERT(ImFontAtlasRectId_GetIndex(new_r_id) == builder->RectsIndex.index_from_ptr(&index_entry)); + ImTextureRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id); + ImFontAtlasTextureBlockCopy(old_tex, old_r.x, old_r.y, new_tex, new_r->x, new_r->y, new_r->w, new_r->h); + } + IM_ASSERT(old_rects.Size == builder->Rects.Size + builder->RectsDiscardedCount); + builder->RectsDiscardedCount = 0; + builder->RectsDiscardedSurface = 0; + + // Patch glyphs UV + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + for (ImFontGlyph& glyph : builder->BakedPool[baked_n].Glyphs) + if (glyph.PackId != ImFontAtlasRectId_Invalid) + { + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); + glyph.U0 = (r->x) * atlas->TexUvScale.x; + glyph.V0 = (r->y) * atlas->TexUvScale.y; + glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + } + + // Update other cached UV + ImFontAtlasBuildUpdateLinesTexData(atlas); + ImFontAtlasBuildUpdateBasicTexData(atlas); + + builder->LockDisableResize = false; + ImFontAtlasUpdateDrawListsSharedData(atlas); + //ImFontAtlasDebugWriteTexToDisk(new_tex, "After Pack"); +} + +void ImFontAtlasTextureGrow(ImFontAtlas* atlas, int old_tex_w, int old_tex_h) +{ + //ImFontAtlasDebugWriteTexToDisk(atlas->TexData, "Before Grow"); + ImFontAtlasBuilder* builder = atlas->Builder; + if (old_tex_w == -1) + old_tex_w = atlas->TexData->Width; + if (old_tex_h == -1) + old_tex_h = atlas->TexData->Height; + + // FIXME-NEWATLAS-V2: What to do when reaching limits exposed by backend? + // FIXME-NEWATLAS-V2: Does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? + IM_ASSERT(ImIsPowerOfTwo(old_tex_w) && ImIsPowerOfTwo(old_tex_h)); + IM_ASSERT(ImIsPowerOfTwo(atlas->TexMinWidth) && ImIsPowerOfTwo(atlas->TexMaxWidth) && ImIsPowerOfTwo(atlas->TexMinHeight) && ImIsPowerOfTwo(atlas->TexMaxHeight)); + + // Grow texture so it follows roughly a square. + // - Grow height before width, as width imply more packing nodes. + // - Caller should be taking account of RectsDiscardedSurface and may not need to grow. + int new_tex_w = (old_tex_h <= old_tex_w) ? old_tex_w : old_tex_w * 2; + int new_tex_h = (old_tex_h <= old_tex_w) ? old_tex_h * 2 : old_tex_h; + + // Handle minimum size first (for pathologically large packed rects) + const int pack_padding = atlas->TexGlyphPadding; + new_tex_w = ImMax(new_tex_w, ImUpperPowerOfTwo(builder->MaxRectSize.x + pack_padding)); + new_tex_h = ImMax(new_tex_h, ImUpperPowerOfTwo(builder->MaxRectSize.y + pack_padding)); + new_tex_w = ImClamp(new_tex_w, atlas->TexMinWidth, atlas->TexMaxWidth); + new_tex_h = ImClamp(new_tex_h, atlas->TexMinHeight, atlas->TexMaxHeight); + if (new_tex_w == old_tex_w && new_tex_h == old_tex_h) + return; + + ImFontAtlasTextureRepack(atlas, new_tex_w, new_tex_h); +} + +void ImFontAtlasTextureMakeSpace(ImFontAtlas* atlas) +{ + // Can some baked contents be ditched? + //IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlasBuildMakeSpace()\n"); + ImFontAtlasBuilder* builder = atlas->Builder; + ImFontAtlasBuildDiscardBakes(atlas, 2); + + // Currently using a heuristic for repack without growing. + if (builder->RectsDiscardedSurface < builder->RectsPackedSurface * 0.20f) + ImFontAtlasTextureGrow(atlas); + else + ImFontAtlasTextureRepack(atlas, atlas->TexData->Width, atlas->TexData->Height); +} + +ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas) +{ + int min_w = ImUpperPowerOfTwo(atlas->TexMinWidth); + int min_h = ImUpperPowerOfTwo(atlas->TexMinHeight); + if (atlas->Builder == NULL || atlas->TexData == NULL || atlas->TexData->Status == ImTextureStatus_WantDestroy) + return ImVec2i(min_w, min_h); + + ImFontAtlasBuilder* builder = atlas->Builder; + min_w = ImMax(ImUpperPowerOfTwo(builder->MaxRectSize.x), min_w); + min_h = ImMax(ImUpperPowerOfTwo(builder->MaxRectSize.y), min_h); + const int surface_approx = builder->RectsPackedSurface - builder->RectsDiscardedSurface; // Expected surface after repack + const int surface_sqrt = (int)sqrtf((float)surface_approx); + + int new_tex_w; + int new_tex_h; + if (min_w >= min_h) + { + new_tex_w = ImMax(min_w, ImUpperPowerOfTwo(surface_sqrt)); + new_tex_h = ImMax(min_h, (int)((surface_approx + new_tex_w - 1) / new_tex_w)); + if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) + new_tex_h = ImUpperPowerOfTwo(new_tex_h); + } + else + { + new_tex_h = ImMax(min_h, ImUpperPowerOfTwo(surface_sqrt)); + if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) + new_tex_h = ImUpperPowerOfTwo(new_tex_h); + new_tex_w = ImMax(min_w, (int)((surface_approx + new_tex_h - 1) / new_tex_h)); + } + + IM_ASSERT(ImIsPowerOfTwo(new_tex_w) && ImIsPowerOfTwo(new_tex_h)); + return ImVec2i(new_tex_w, new_tex_h); +} + +// Clear all output. Invalidates all AddCustomRect() return values! +void ImFontAtlasBuildClear(ImFontAtlas* atlas) +{ + ImVec2i new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas); + ImFontAtlasBuildDestroy(atlas); + ImFontAtlasTextureAdd(atlas, new_tex_size.x, new_tex_size.y); + ImFontAtlasBuildInit(atlas); + for (ImFontConfig& src : atlas->Sources) + ImFontAtlasFontSourceInit(atlas, &src); + for (ImFont* font : atlas->Fonts) + for (ImFontConfig* src : font->Sources) + ImFontAtlasFontSourceAddToFont(atlas, font, src); +} + +// You should not need to call this manually! +// If you think you do, let us know and we can advise about policies auto-compact. +void ImFontAtlasTextureCompact(ImFontAtlas* atlas) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + ImFontAtlasBuildDiscardBakes(atlas, 1); + + ImTextureData* old_tex = atlas->TexData; + ImVec2i old_tex_size = ImVec2i(old_tex->Width, old_tex->Height); + ImVec2i new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas); + if (builder->RectsDiscardedCount == 0 && new_tex_size.x == old_tex_size.x && new_tex_size.y == old_tex_size.y) + return; + + ImFontAtlasTextureRepack(atlas, new_tex_size.x, new_tex_size.y); +} + +// Start packing over current empty texture +void ImFontAtlasBuildInit(ImFontAtlas* atlas) +{ + // Select Backend + // - Note that we do not reassign to atlas->FontLoader, since it is likely to point to static data which + // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are + // using a hot-reloading scheme that messes up static data, store your own instance of FontLoader somewhere + // and point to it instead of pointing directly to return value of the GetFontLoaderXXX functions. + if (atlas->FontLoader == NULL) + { +#ifdef IMGUI_ENABLE_FREETYPE + atlas->SetFontLoader(ImGuiFreeType::GetFontLoader()); +#elif defined(IMGUI_ENABLE_STB_TRUETYPE) + atlas->SetFontLoader(ImFontAtlasGetFontLoaderForStbTruetype()); +#else + IM_ASSERT(0); // Invalid Build function +#endif + } + + // Create initial texture size + if (atlas->TexData == NULL || atlas->TexData->Pixels == NULL) + ImFontAtlasTextureAdd(atlas, ImUpperPowerOfTwo(atlas->TexMinWidth), ImUpperPowerOfTwo(atlas->TexMinHeight)); + + atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); + if (atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); + + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); + + ImFontAtlasPackInit(atlas); + + // Add required texture data + ImFontAtlasBuildUpdateLinesTexData(atlas); + ImFontAtlasBuildUpdateBasicTexData(atlas); + + // Register fonts + ImFontAtlasBuildUpdatePointers(atlas); + + // Update UV coordinates etc. stored in bound ImDrawListSharedData instance + ImFontAtlasUpdateDrawListsSharedData(atlas); + + //atlas->TexIsBuilt = true; +} + +// Destroy builder and all cached glyphs. Do not destroy actual fonts. +void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) +{ + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontDestroyOutput(atlas, font); + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) + { + atlas->FontLoader->LoaderShutdown(atlas); + IM_ASSERT(atlas->FontLoaderData == NULL); + } + IM_DELETE(atlas->Builder); + atlas->Builder = NULL; +} + +void ImFontAtlasPackInit(ImFontAtlas * atlas) +{ + ImTextureData* tex = atlas->TexData; + ImFontAtlasBuilder* builder = atlas->Builder; + + // In theory we could decide to reduce the number of nodes, e.g. halve them, and waste a little texture space, but it doesn't seem worth it. + const int pack_node_count = tex->Width / 2; + builder->PackNodes.resize(pack_node_count); + IM_STATIC_ASSERT(sizeof(stbrp_context) <= sizeof(stbrp_context_opaque)); + stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width, tex->Height, builder->PackNodes.Data, builder->PackNodes.Size); + builder->RectsPackedSurface = builder->RectsPackedCount = 0; + builder->MaxRectSize = ImVec2i(0, 0); + builder->MaxRectBounds = ImVec2i(0, 0); +} + +// This is essentially a free-list pattern, it may be nice to wrap it into a dedicated type. +static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int rect_idx) +{ + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + int index_idx; + ImFontAtlasRectEntry* index_entry; + if (builder->RectsIndexFreeListStart < 0) + { + builder->RectsIndex.resize(builder->RectsIndex.Size + 1); + index_idx = builder->RectsIndex.Size - 1; + index_entry = &builder->RectsIndex[index_idx]; + memset(index_entry, 0, sizeof(*index_entry)); + } + else + { + index_idx = builder->RectsIndexFreeListStart; + index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->IsUsed == false && index_entry->Generation > 0); // Generation is incremented during DiscardRect + builder->RectsIndexFreeListStart = index_entry->TargetIndex; + } + index_entry->TargetIndex = rect_idx; + index_entry->IsUsed = 1; + return ImFontAtlasRectId_Make(index_idx, index_entry->Generation); +} + +// Overwrite existing entry +static ImFontAtlasRectId ImFontAtlasPackReuseRectEntry(ImFontAtlas* atlas, ImFontAtlasRectEntry* index_entry) +{ + IM_ASSERT(index_entry->IsUsed); + index_entry->TargetIndex = atlas->Builder->Rects.Size - 1; + int index_idx = atlas->Builder->RectsIndex.index_from_ptr(index_entry); + return ImFontAtlasRectId_Make(index_idx, index_entry->Generation); +} + +// This is expected to be called in batches and followed by a repack +void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + IM_ASSERT(id != ImFontAtlasRectId_Invalid); + + ImTextureRect* rect = ImFontAtlasPackGetRect(atlas, id); + if (rect == NULL) + return; + + ImFontAtlasBuilder* builder = atlas->Builder; + int index_idx = ImFontAtlasRectId_GetIndex(id); + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->IsUsed && index_entry->TargetIndex >= 0); + index_entry->IsUsed = false; + index_entry->TargetIndex = builder->RectsIndexFreeListStart; + index_entry->Generation++; + + const int pack_padding = atlas->TexGlyphPadding; + builder->RectsIndexFreeListStart = index_idx; + builder->RectsDiscardedCount++; + builder->RectsDiscardedSurface += (rect->w + pack_padding) * (rect->h + pack_padding); + rect->w = rect->h = 0; // Clear rectangle so it won't be packed again +} + +// Important: Calling this may recreate a new texture and therefore change atlas->TexData +// FIXME-NEWFONTS: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 +ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry) +{ + IM_ASSERT(w > 0 && w <= 0xFFFF); + IM_ASSERT(h > 0 && h <= 0xFFFF); + + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + const int pack_padding = atlas->TexGlyphPadding; + builder->MaxRectSize.x = ImMax(builder->MaxRectSize.x, w); + builder->MaxRectSize.y = ImMax(builder->MaxRectSize.y, h); + + // Pack + ImTextureRect r = { 0, 0, (unsigned short)w, (unsigned short)h }; + for (int attempts_remaining = 3; attempts_remaining >= 0; attempts_remaining--) + { + // Try packing + stbrp_rect pack_r = {}; + pack_r.w = w + pack_padding; + pack_r.h = h + pack_padding; + stbrp_pack_rects((stbrp_context*)(void*)&builder->PackContext, &pack_r, 1); + r.x = (unsigned short)pack_r.x; + r.y = (unsigned short)pack_r.y; + if (pack_r.was_packed) + break; + + // If we ran out of attempts, return fallback + if (attempts_remaining == 0 || builder->LockDisableResize) + { + IMGUI_DEBUG_LOG_FONT("[font] Failed packing %dx%d rectangle. Returning fallback.\n", w, h); + return ImFontAtlasRectId_Invalid; + } + + // Resize or repack atlas! (this should be a rare event) + ImFontAtlasTextureMakeSpace(atlas); + } + + builder->MaxRectBounds.x = ImMax(builder->MaxRectBounds.x, r.x + r.w + pack_padding); + builder->MaxRectBounds.y = ImMax(builder->MaxRectBounds.y, r.y + r.h + pack_padding); + builder->RectsPackedCount++; + builder->RectsPackedSurface += (w + pack_padding) * (h + pack_padding); + + builder->Rects.push_back(r); + if (overwrite_entry != NULL) + return ImFontAtlasPackReuseRectEntry(atlas, overwrite_entry); // Write into an existing entry instead of adding one (used during repack) + else + return ImFontAtlasPackAllocRectEntry(atlas, builder->Rects.Size - 1); +} + +// Generally for non-user facing functions: assert on invalid ID. +ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + IM_ASSERT(id != ImFontAtlasRectId_Invalid); + int index_idx = ImFontAtlasRectId_GetIndex(id); + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->Generation == ImFontAtlasRectId_GetGeneration(id)); + IM_ASSERT(index_entry->IsUsed); + return &builder->Rects[index_entry->TargetIndex]; +} + +// For user-facing functions: return NULL on invalid ID. +// Important: return pointer is valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. +ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + if (id == ImFontAtlasRectId_Invalid) + return NULL; + int index_idx = ImFontAtlasRectId_GetIndex(id); + if (atlas->Builder == NULL) + ImFontAtlasBuildInit(atlas); + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + if (index_idx >= builder->RectsIndex.Size) + return NULL; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + if (index_entry->Generation != ImFontAtlasRectId_GetGeneration(id) || !index_entry->IsUsed) + return NULL; + return &builder->Rects[index_entry->TargetIndex]; +} - stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects); +// Important! This assume by ImFontConfig::GlyphExcludeRanges[] is a SMALL ARRAY (e.g. <10 entries) +// Use "Input Glyphs Overlap Detection Tool" to display a list of glyphs provided by multiple sources in order to set this array up. +static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint) +{ + if (const ImWchar* exclude_list = src->GlyphExcludeRanges) + for (; exclude_list[0] != 0; exclude_list += 2) + if (codepoint >= exclude_list[0] && codepoint <= exclude_list[1]) + return false; + return true; +} - // Apply multiply operator - if (src.RasterizerMultiply != 1.0f) - { - unsigned char multiply_table[256]; - ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, src.RasterizerMultiply); - stbrp_rect* r = &src_tmp.Rects[0]; - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++, r++) - if (r->was_packed) - ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, atlas->TexPixelsAlpha8, r->x, r->y, r->w, r->h, atlas->TexWidth * 1); - } - src_tmp.Rects = NULL; - } +static void ImFontBaked_BuildGrowIndex(ImFontBaked* baked, int new_size) +{ + IM_ASSERT(baked->IndexAdvanceX.Size == baked->IndexLookup.Size); + if (new_size <= baked->IndexLookup.Size) + return; + baked->IndexAdvanceX.resize(new_size, -1.0f); + baked->IndexLookup.resize(new_size, IM_FONTGLYPH_INDEX_UNUSED); +} - // End packing - stbtt_PackEnd(&spc); - buf_rects.clear(); +static void ImFontAtlas_FontHookRemapCodepoint(ImFontAtlas* atlas, ImFont* font, ImWchar* c) +{ + IM_UNUSED(atlas); + if (font->RemapPairs.Data.Size != 0) + *c = (ImWchar)font->RemapPairs.GetInt((ImGuiID)*c, (int)*c); +} - // 9. Setup ImFont and glyphs for runtime - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) +static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codepoint, float* only_load_advance_x) +{ + ImFont* font = baked->ContainerFont; + ImFontAtlas* atlas = font->ContainerAtlas; + if (atlas->Locked || (font->Flags & ImFontFlags_NoLoadGlyphs)) { - // When merging fonts with MergeMode=true: - // - We can have multiple input fonts writing into a same destination font. - // - dst_font->Sources is != from src which is our source configuration. - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontConfig& src = atlas->Sources[src_i]; - ImFont* dst_font = src.DstFont; + // Lazily load fallback glyph + if (baked->FallbackGlyphIndex == -1 && baked->LoadNoFallback == 0) + ImFontAtlasBuildSetupFontBakedFallback(baked); + return NULL; + } - const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels); - int unscaled_ascent, unscaled_descent, unscaled_line_gap; - stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + // User remapping hooks + ImWchar src_codepoint = codepoint; + ImFontAtlas_FontHookRemapCodepoint(atlas, font, &codepoint); - const float ascent = ImCeil(unscaled_ascent * font_scale); - const float descent = ImFloor(unscaled_descent * font_scale); - ImFontAtlasBuildSetupFont(atlas, dst_font, &src, ascent, descent); - const float font_off_x = src.GlyphOffset.x; - const float font_off_y = src.GlyphOffset.y + IM_ROUND(dst_font->Ascent); + //char utf8_buf[5]; + //IMGUI_DEBUG_LOG("[font] BuildLoadGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); - const float inv_rasterization_scale = 1.0f / src.RasterizerDensity; + // Special hook + // FIXME-NEWATLAS: it would be nicer if this used a more standardized way of hooking + if (codepoint == font->EllipsisChar && font->EllipsisAutoBake) + if (ImFontGlyph* glyph = ImFontAtlasBuildSetupFontBakedEllipsis(atlas, baked)) + return glyph; - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) + // Call backend + char* loader_user_data_p = (char*)baked->FontLoaderDatas; + int src_n = 0; + for (ImFontConfig* src : font->Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) { - // Register glyph - const int codepoint = src_tmp.GlyphsList[glyph_i]; - const stbtt_packedchar& pc = src_tmp.PackedChars[glyph_i]; - stbtt_aligned_quad q; - float unused_x = 0.0f, unused_y = 0.0f; - stbtt_GetPackedQuad(src_tmp.PackedChars, atlas->TexWidth, atlas->TexHeight, glyph_i, &unused_x, &unused_y, &q, 0); - float x0 = q.x0 * inv_rasterization_scale + font_off_x; - float y0 = q.y0 * inv_rasterization_scale + font_off_y; - float x1 = q.x1 * inv_rasterization_scale + font_off_x; - float y1 = q.y1 * inv_rasterization_scale + font_off_y; - dst_font->AddGlyph(&src, (ImWchar)codepoint, x0, y0, x1, y1, q.s0, q.t0, q.s1, q.t1, pc.xadvance * inv_rasterization_scale); + if (only_load_advance_x == NULL) + { + ImFontGlyph glyph_buf; + if (loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint, &glyph_buf, NULL)) + { + // FIXME: Add hooks for e.g. #7962 + glyph_buf.Codepoint = src_codepoint; + glyph_buf.SourceIdx = src_n; + return ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph_buf); + } + } + else + { + // Special mode but only loading glyphs metrics. Will rasterize and pack later. + if (loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint, NULL, only_load_advance_x)) + { + ImFontAtlasBakedAddFontGlyphAdvancedX(atlas, baked, src, codepoint, *only_load_advance_x); + return NULL; + } + } } + loader_user_data_p += loader->FontBakedSrcLoaderDataSize; + src_n++; } - // Cleanup - src_tmp_array.clear_destruct(); - - ImFontAtlasBuildFinish(atlas); - return true; -} + // Lazily load fallback glyph + if (baked->LoadNoFallback) + return NULL; + if (baked->FallbackGlyphIndex == -1) + ImFontAtlasBuildSetupFontBakedFallback(baked); -const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype() -{ - static ImFontBuilderIO io; - io.FontBuilder_Build = ImFontAtlasBuildWithStbTruetype; - return &io; + // Mark index as not found, so we don't attempt the search twice + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); + baked->IndexAdvanceX[codepoint] = baked->FallbackAdvanceX; + baked->IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; + return NULL; } -#endif // IMGUI_ENABLE_STB_TRUETYPE - -void ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas) +static float ImFontBaked_BuildLoadGlyphAdvanceX(ImFontBaked* baked, ImWchar codepoint) { - for (ImFontConfig& src : atlas->Sources) + if (baked->Size >= IMGUI_FONT_SIZE_THRESHOLD_FOR_LOADADVANCEXONLYMODE || baked->LoadNoRenderOnLayout) { - ImFont* font = src.DstFont; - if (!src.MergeMode) - { - font->Sources = &src; - font->SourcesCount = 0; - } - font->SourcesCount++; + // First load AdvanceX value used by CalcTextSize() API then load the rest when loaded by drawing API. + float only_advance_x = 0.0f; + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(baked, (ImWchar)codepoint, &only_advance_x); + return glyph ? glyph->AdvanceX : only_advance_x; } -} - -void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) -{ - if (!font_config->MergeMode) + else { - font->ClearOutputData(); - font->FontSize = font_config->SizePixels; - IM_ASSERT(font->Sources == font_config); - font->ContainerAtlas = atlas; - font->Ascent = ascent; - font->Descent = descent; + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(baked, (ImWchar)codepoint, NULL); + return glyph ? glyph->AdvanceX : baked->FallbackAdvanceX; } } -void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque) +// The point of this indirection is to not be inlined in debug mode in order to not bloat inner loop.b +IM_MSVC_RUNTIME_CHECKS_OFF +static float BuildLoadGlyphGetAdvanceOrFallback(ImFontBaked* baked, unsigned int codepoint) { - stbrp_context* pack_context = (stbrp_context*)stbrp_context_opaque; - IM_ASSERT(pack_context != NULL); - - ImVector& user_rects = atlas->CustomRects; - IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. -#ifdef __GNUC__ - if (user_rects.Size < 1) { __builtin_unreachable(); } // Workaround for GCC bug if IM_ASSERT() is defined to conditionally throw (see #5343) -#endif + return ImFontBaked_BuildLoadGlyphAdvanceX(baked, (ImWchar)codepoint); +} +IM_MSVC_RUNTIME_CHECKS_RESTORE - const int pack_padding = atlas->TexGlyphPadding; - ImVector pack_rects; - pack_rects.resize(user_rects.Size); - memset(pack_rects.Data, 0, (size_t)pack_rects.size_in_bytes()); - for (int i = 0; i < user_rects.Size; i++) +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas) +{ + // [DEBUG] Log texture update requests + ImGuiContext& g = *GImGui; + IM_UNUSED(g); + for (ImTextureData* tex : atlas->TexList) { - pack_rects[i].w = user_rects[i].Width + pack_padding; - pack_rects[i].h = user_rects[i].Height + pack_padding; - } - stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); - for (int i = 0; i < pack_rects.Size; i++) - if (pack_rects[i].was_packed) + if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + IM_ASSERT(tex->Updates.Size == 0); + if (tex->Status == ImTextureStatus_WantCreate) + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: create %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + else if (tex->Status == ImTextureStatus_WantDestroy) + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: destroy %dx%d, texid=0x%" IM_PRIX64 ", backend_data=%p\n", tex->UniqueID, tex->Width, tex->Height, tex->TexID, tex->BackendUserData); + else if (tex->Status == ImTextureStatus_WantUpdates) { - user_rects[i].X = (unsigned short)pack_rects[i].x; - user_rects[i].Y = (unsigned short)pack_rects[i].y; - IM_ASSERT(pack_rects[i].w == user_rects[i].Width + pack_padding && pack_rects[i].h == user_rects[i].Height + pack_padding); - atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update %d regions, texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, tex->Updates.Size, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); + for (const ImTextureRect& r : tex->Updates) + { + IM_UNUSED(r); + IM_ASSERT(r.x >= 0 && r.y >= 0); + IM_ASSERT(r.x + r.w <= tex->Width && r.y + r.h <= tex->Height); // In theory should subtract PackPadding but it's currently part of atlas and mid-frame change would wreck assert. + //IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update (% 4d..%-4d)->(% 4d..%-4d), texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, r.x, r.y, r.x + r.w, r.y + r.h, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); + } } + } } +#endif -void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value) -{ - IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); - IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); - unsigned char* out_pixel = atlas->TexPixelsAlpha8 + x + (y * atlas->TexWidth); - for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) - for (int off_x = 0; off_x < w; off_x++) - out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : 0x00; -} +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas: backend for stb_truetype +//------------------------------------------------------------------------- +// (imstb_truetype.h in included near the top of this file, when IMGUI_ENABLE_STB_TRUETYPE is set) +//------------------------------------------------------------------------- + +#ifdef IMGUI_ENABLE_STB_TRUETYPE -void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value) +// One for each ConfigData +struct ImGui_ImplStbTrueType_FontSrcData { - IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); - IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); - unsigned int* out_pixel = atlas->TexPixelsRGBA32 + x + (y * atlas->TexWidth); - for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) - for (int off_x = 0; off_x < w; off_x++) - out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : IM_COL32_BLACK_TRANS; -} + stbtt_fontinfo FontInfo; + float ScaleFactor; +}; -static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) +static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) { - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors); - IM_ASSERT(r->IsPacked()); + IM_UNUSED(atlas); - const int w = atlas->TexWidth; - if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplStbTrueType_FontSrcData); + IM_ASSERT(src->FontLoaderData == NULL); + + // Initialize helper structure for font loading and verify that the TTF/OTF data is correct + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src->FontData, src->FontNo); + if (font_offset < 0) { - // White pixels only - IM_ASSERT(r->Width == 2 && r->Height == 2); - const int offset = (int)r->X + (int)r->Y * w; - if (atlas->TexPixelsAlpha8 != NULL) - { - atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; - } - else - { - atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE; - } + IM_DELETE(bd_font_data); + IM_ASSERT_USER_ERROR(0, "stbtt_GetFontOffsetForIndex(): FontData is incorrect, or FontNo cannot be found."); + return false; } - else + if (!stbtt_InitFont(&bd_font_data->FontInfo, (unsigned char*)src->FontData, font_offset)) { - // White pixels and mouse cursor - IM_ASSERT(r->Width == FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1 && r->Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); - const int x_for_white = r->X; - const int x_for_black = r->X + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; - if (atlas->TexPixelsAlpha8 != NULL) - { - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', 0xFF); - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', 0xFF); - } - else - { - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', IM_COL32_WHITE); - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', IM_COL32_WHITE); - } + IM_DELETE(bd_font_data); + IM_ASSERT_USER_ERROR(0, "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); + return false; } - atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); -} - -static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) -{ - if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) - return; + src->FontLoaderData = bd_font_data; - // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdLines); - IM_ASSERT(r->IsPacked()); - for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row - { - // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - int y = n; - int line_width = n; - int pad_left = (r->Width - line_width) / 2; - int pad_right = r->Width - (pad_left + line_width); + if (src->MergeMode && src->SizePixels == 0.0f) + src->SizePixels = src->DstFont->Sources[0]->SizePixels; - // Write each slice - IM_ASSERT(pad_left + line_width + pad_right == r->Width && y < r->Height); // Make sure we're inside the texture bounds before we start writing pixels - if (atlas->TexPixelsAlpha8 != NULL) - { - unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = 0x00; + if (src->SizePixels >= 0.0f) + bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); + else + bd_font_data->ScaleFactor = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); + if (src->MergeMode && src->SizePixels != 0.0f) + bd_font_data->ScaleFactor *= src->SizePixels / src->DstFont->Sources[0]->SizePixels; // FIXME-NEWATLAS: Should tidy up that a bit - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = 0xFF; + return true; +} - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = 0x00; - } - else - { - unsigned int* write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = IM_COL32(255, 255, 255, 0); +static void ImGui_ImplStbTrueType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_DELETE(bd_font_data); + src->FontLoaderData = NULL; +} - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = IM_COL32_WHITE; +static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) +{ + IM_UNUSED(atlas); - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); - } + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_ASSERT(bd_font_data != NULL); - // Calculate UVs for this line - ImVec2 uv0 = ImVec2((float)(r->X + pad_left - 1), (float)(r->Y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r->X + pad_left + line_width + 1), (float)(r->Y + y + 1)) * atlas->TexUvScale; - float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); - } + int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); + return glyph_index != 0; } -// Note: this is called / shared by both the stb_truetype and the FreeType builder -void ImFontAtlasBuildInit(ImFontAtlas* atlas) +static bool ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*) { - // Register texture region for mouse cursors or standard white pixels - if (atlas->PackIdMouseCursors < 0) - { - if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) - atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); - else - atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(2, 2); - } + IM_UNUSED(atlas); - // Register texture region for thick lines - // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - if (atlas->PackIdLines < 0) + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + if (src->MergeMode == false) { - if (!(atlas->Flags & ImFontAtlasFlags_NoBakedLines)) - atlas->PackIdLines = atlas->AddCustomRectRegular(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + // FIXME-NEWFONTS: reevaluate how to use sizing metrics + // FIXME-NEWFONTS: make use of line gap value + float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + baked->Ascent = ImCeil(unscaled_ascent * scale_for_layout); + baked->Descent = ImFloor(unscaled_descent * scale_for_layout); } + return true; } -// This is called/shared by both the stb_truetype and the FreeType builder. -void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +static bool ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x) { - // Render into our custom data blocks - IM_ASSERT(atlas->TexPixelsAlpha8 != NULL || atlas->TexPixelsRGBA32 != NULL); - ImFontAtlasBuildRenderDefaultTexData(atlas); - ImFontAtlasBuildRenderLinesTexData(atlas); + // Search for first font which has the glyph + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_ASSERT(bd_font_data); + int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); + if (glyph_index == 0) + return false; - // Register custom rectangle glyphs - for (int i = 0; i < atlas->CustomRects.Size; i++) + // Fonts unit to pixels + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, baked, &oversample_h, &oversample_v); + const float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; + const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity; + const float scale_for_raster_x = bd_font_data->ScaleFactor * baked->Size * rasterizer_density * oversample_h; + const float scale_for_raster_y = bd_font_data->ScaleFactor * baked->Size * rasterizer_density * oversample_v; + + // Obtain size and advance + int x0, y0, x1, y1; + int advance, lsb; + stbtt_GetGlyphBitmapBoxSubpixel(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, 0, 0, &x0, &y0, &x1, &y1); + stbtt_GetGlyphHMetrics(&bd_font_data->FontInfo, glyph_index, &advance, &lsb); + + // Load metrics only mode + if (out_advance_x != NULL) { - const ImFontAtlasCustomRect* r = &atlas->CustomRects[i]; - if (r->Font == NULL || r->GlyphID == 0) - continue; + IM_ASSERT(out_glyph == NULL); + *out_advance_x = advance * scale_for_layout; + return true; + } - // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, PixelSnapH - IM_ASSERT(r->Font->ContainerAtlas == atlas); - ImVec2 uv0, uv1; - atlas->CalcCustomRectUV(r, &uv0, &uv1); - r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); - if (r->GlyphColored) - r->Font->Glyphs.back().Colored = 1; + // Prepare glyph + out_glyph->Codepoint = codepoint; + out_glyph->AdvanceX = advance * scale_for_layout; + + // Pack and retrieve position inside texture atlas + // (generally based on stbtt_PackFontRangesRenderIntoRects) + const bool is_visible = (x0 != x1 && y0 != y1); + if (is_visible) + { + const int w = (x1 - x0 + oversample_h - 1); + const int h = (y1 - y0 + oversample_v - 1); + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); + if (pack_id == ImFontAtlasRectId_Invalid) + { + // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) + IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory."); + return false; + } + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + + // Render + stbtt_GetGlyphBitmapBox(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, &x0, &y0, &x1, &y1); + ImFontAtlasBuilder* builder = atlas->Builder; + builder->TempBuffer.resize(w * h * 1); + unsigned char* bitmap_pixels = builder->TempBuffer.Data; + memset(bitmap_pixels, 0, w * h * 1); + + // Render with oversampling + // (those functions conveniently assert if pixels are not cleared, which is another safety layer) + float sub_x, sub_y; + stbtt_MakeGlyphBitmapSubpixelPrefilter(&bd_font_data->FontInfo, bitmap_pixels, w, h, w, + scale_for_raster_x, scale_for_raster_y, 0, 0, oversample_h, oversample_v, &sub_x, &sub_y, glyph_index); + + const float ref_size = baked->ContainerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; + float font_off_x = (src->GlyphOffset.x * offsets_scale); + float font_off_y = (src->GlyphOffset.y * offsets_scale); + if (src->PixelSnapH) // Snap scaled offset. This is to mitigate backward compatibility issues for GlyphOffset, but a better design would be welcome. + font_off_x = IM_ROUND(font_off_x); + if (src->PixelSnapV) + font_off_y = IM_ROUND(font_off_y); + font_off_x += sub_x; + font_off_y += sub_y + IM_ROUND(baked->Ascent); + float recip_h = 1.0f / (oversample_h * rasterizer_density); + float recip_v = 1.0f / (oversample_v * rasterizer_density); + + // Register glyph + // r->x r->y are coordinates inside texture (in pixels) + // glyph.X0, glyph.Y0 are drawing coordinates from base text position, and accounting for oversampling. + out_glyph->X0 = x0 * recip_h + font_off_x; + out_glyph->Y0 = y0 * recip_v + font_off_y; + out_glyph->X1 = (x0 + (int)r->w) * recip_h + font_off_x; + out_glyph->Y1 = (y0 + (int)r->h) * recip_v + font_off_y; + out_glyph->Visible = true; + out_glyph->PackId = pack_id; + ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, r, bitmap_pixels, ImTextureFormat_Alpha8, w); } - // Build all fonts lookup tables - for (ImFont* font : atlas->Fonts) - if (font->DirtyLookupTables) - font->BuildLookupTable(); + return true; +} - atlas->TexReady = true; +const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype() +{ + static ImFontLoader loader; + loader.Name = "stb_truetype"; + loader.FontSrcInit = ImGui_ImplStbTrueType_FontSrcInit; + loader.FontSrcDestroy = ImGui_ImplStbTrueType_FontSrcDestroy; + loader.FontSrcContainsGlyph = ImGui_ImplStbTrueType_FontSrcContainsGlyph; + loader.FontBakedInit = ImGui_ImplStbTrueType_FontBakedInit; + loader.FontBakedDestroy = NULL; + loader.FontBakedLoadGlyph = ImGui_ImplStbTrueType_FontBakedLoadGlyph; + return &loader; } +#endif // IMGUI_ENABLE_STB_TRUETYPE + //------------------------------------------------------------------------- // [SECTION] ImFontAtlas: glyph ranges helpers //------------------------------------------------------------------------- // - GetGlyphRangesDefault() +// Obsolete functions since 1.92: // - GetGlyphRangesGreek() // - GetGlyphRangesKorean() // - GetGlyphRangesChineseFull() @@ -3402,6 +4755,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesDefault() return &ranges[0]; } +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS const ImWchar* ImFontAtlas::GetGlyphRangesGreek() { static const ImWchar ranges[] = @@ -3651,6 +5005,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesVietnamese() }; return &ranges[0]; } +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //----------------------------------------------------------------------------- // [SECTION] ImFontGlyphRangesBuilder @@ -3694,124 +5049,43 @@ void ImFontGlyphRangesBuilder::BuildRanges(ImVector* out_ranges) // [SECTION] ImFont //----------------------------------------------------------------------------- -ImFont::ImFont() +ImFontBaked::ImFontBaked() { memset(this, 0, sizeof(*this)); - Scale = 1.0f; -} - -ImFont::~ImFont() -{ - ClearOutputData(); + FallbackGlyphIndex = -1; } -void ImFont::ClearOutputData() +void ImFontBaked::ClearOutputData() { - FontSize = 0.0f; FallbackAdvanceX = 0.0f; Glyphs.clear(); IndexAdvanceX.clear(); IndexLookup.clear(); - FallbackGlyph = NULL; - ContainerAtlas = NULL; - DirtyLookupTables = true; + FallbackGlyphIndex = -1; Ascent = Descent = 0.0f; MetricsTotalSurface = 0; - memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); } -static ImWchar FindFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) +ImFont::ImFont() { - for (int n = 0; n < candidate_chars_count; n++) - if (font->FindGlyphNoFallback(candidate_chars[n]) != NULL) - return candidate_chars[n]; - return 0; + memset(this, 0, sizeof(*this)); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + Scale = 1.0f; +#endif } -void ImFont::BuildLookupTable() +ImFont::~ImFont() { - int max_codepoint = 0; - for (int i = 0; i != Glyphs.Size; i++) - max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); + ClearOutputData(); +} - // Build lookup table - IM_ASSERT(Glyphs.Size > 0 && "Font has not loaded glyph!"); - IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved - IndexAdvanceX.clear(); - IndexLookup.clear(); - DirtyLookupTables = false; +void ImFont::ClearOutputData() +{ + if (ImFontAtlas* atlas = ContainerAtlas) + ImFontAtlasFontDiscardBakes(atlas, this, 0); + FallbackChar = EllipsisChar = 0; memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); - GrowIndex(max_codepoint + 1); - for (int i = 0; i < Glyphs.Size; i++) - { - int codepoint = (int)Glyphs[i].Codepoint; - IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; - IndexLookup[codepoint] = (ImU16)i; - - // Mark 4K page as used - const int page_n = codepoint / 8192; - Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); - } - - // Create a glyph to handle TAB - // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) - if (FindGlyph((ImWchar)' ')) - { - if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times (FIXME: Flaky) - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& tab_glyph = Glyphs.back(); - tab_glyph = *FindGlyph((ImWchar)' '); - tab_glyph.Codepoint = '\t'; - tab_glyph.AdvanceX *= IM_TABSIZE; - IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; - IndexLookup[(int)tab_glyph.Codepoint] = (ImU16)(Glyphs.Size - 1); - } - - // Mark special glyphs as not visible (note that AddGlyph already mark as non-visible glyphs with zero-size polygons) - if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)' ')) - glyph->Visible = false; - if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)'\t')) - glyph->Visible = false; - - // Setup Fallback character - const ImWchar fallback_chars[] = { (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - if (FallbackGlyph == NULL) - { - FallbackChar = FindFirstExistingGlyph(this, fallback_chars, IM_ARRAYSIZE(fallback_chars)); - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - if (FallbackGlyph == NULL) - { - FallbackGlyph = &Glyphs.back(); - FallbackChar = (ImWchar)FallbackGlyph->Codepoint; - } - } - FallbackAdvanceX = FallbackGlyph->AdvanceX; - for (int i = 0; i < max_codepoint + 1; i++) - if (IndexAdvanceX[i] < 0.0f) - IndexAdvanceX[i] = FallbackAdvanceX; - - // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). - // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. - // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. - const ImWchar ellipsis_chars[] = { Sources->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; - const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; - if (EllipsisChar == 0) - EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars)); - const ImWchar dot_char = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); - if (EllipsisChar != 0) - { - EllipsisCharCount = 1; - EllipsisWidth = EllipsisCharStep = FindGlyph(EllipsisChar)->X1; - } - else if (dot_char != 0) - { - const ImFontGlyph* dot_glyph = FindGlyph(dot_char); - EllipsisChar = dot_char; - EllipsisCharCount = 3; - EllipsisCharStep = (float)(int)(dot_glyph->X1 - dot_glyph->X0) + 1.0f; - EllipsisWidth = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + EllipsisCharStep * 3.0f - 1.0f); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. - } + LastBaked = NULL; } // API is designed this way to avoid exposing the 8K page size @@ -3827,98 +5101,241 @@ bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) return true; } -void ImFont::GrowIndex(int new_size) -{ - IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); - if (new_size <= IndexLookup.Size) - return; - IndexAdvanceX.resize(new_size, -1.0f); - IndexLookup.resize(new_size, (ImU16)-1); -} - // x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. // Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). -// 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. -void ImFont::AddGlyph(const ImFontConfig* src, ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +// - 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. +ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph) { + int glyph_idx = baked->Glyphs.Size; + baked->Glyphs.push_back(*in_glyph); + ImFontGlyph* glyph = &baked->Glyphs[glyph_idx]; + IM_ASSERT(baked->Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. + + // Set UV from packed rectangle + if (glyph->PackId != ImFontAtlasRectId_Invalid) + { + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph->PackId); + IM_ASSERT(glyph->U0 == 0.0f && glyph->V0 == 0.0f && glyph->U1 == 0.0f && glyph->V1 == 0.0f); + glyph->U0 = (r->x) * atlas->TexUvScale.x; + glyph->V0 = (r->y) * atlas->TexUvScale.y; + glyph->U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph->V1 = (r->y + r->h) * atlas->TexUvScale.y; + baked->MetricsTotalSurface += r->w * r->h; + } + if (src != NULL) { // Clamp & recenter if needed - const float advance_x_original = advance_x; - advance_x = ImClamp(advance_x, src->GlyphMinAdvanceX, src->GlyphMaxAdvanceX); - if (advance_x != advance_x_original) + const float ref_size = baked->ContainerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; + float advance_x = ImClamp(glyph->AdvanceX, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); + if (advance_x != glyph->AdvanceX) { - float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - advance_x_original) * 0.5f) : (advance_x - advance_x_original) * 0.5f; - x0 += char_off_x; - x1 += char_off_x; + float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - glyph->AdvanceX) * 0.5f) : (advance_x - glyph->AdvanceX) * 0.5f; + glyph->X0 += char_off_x; + glyph->X1 += char_off_x; } // Snap to pixel if (src->PixelSnapH) advance_x = IM_ROUND(advance_x); - // Bake extra spacing + // Bake spacing + glyph->AdvanceX = advance_x + src->GlyphExtraAdvanceX; + } + if (glyph->Colored) + atlas->TexPixelsUseColors = atlas->TexData->UseColors = true; + + // Update lookup tables + const int codepoint = glyph->Codepoint; + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); + baked->IndexAdvanceX[codepoint] = glyph->AdvanceX; + baked->IndexLookup[codepoint] = (ImU16)glyph_idx; + const int page_n = codepoint / 8192; + baked->ContainerFont->Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); + + return glyph; +} + +// FIXME: Code is duplicated with code above. +void ImFontAtlasBakedAddFontGlyphAdvancedX(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImWchar codepoint, float advance_x) +{ + IM_UNUSED(atlas); + if (src != NULL) + { + // Clamp & recenter if needed + const float ref_size = baked->ContainerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; + advance_x = ImClamp(advance_x, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); + + // Snap to pixel + if (src->PixelSnapH) + advance_x = IM_ROUND(advance_x); + + // Bake spacing advance_x += src->GlyphExtraAdvanceX; } - int glyph_idx = Glyphs.Size; - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& glyph = Glyphs[glyph_idx]; - glyph.Codepoint = (unsigned int)codepoint; - glyph.Visible = (x0 != x1) && (y0 != y1); - glyph.Colored = false; - glyph.X0 = x0; - glyph.Y0 = y0; - glyph.X1 = x1; - glyph.Y1 = y1; - glyph.U0 = u0; - glyph.V0 = v0; - glyph.U1 = u1; - glyph.V1 = v1; - glyph.AdvanceX = advance_x; - IM_ASSERT(Glyphs.Size < 0xFFFF); // IndexLookup[] hold 16-bit values and -1 is reserved. + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); + baked->IndexAdvanceX[codepoint] = advance_x; +} - // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) - // We use (U1-U0)*TexWidth instead of X1-X0 to account for oversampling. - float pad = ContainerAtlas->TexGlyphPadding + 0.99f; - DirtyLookupTables = true; - MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + pad) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + pad); +// Copy to texture, post-process and queue update for backend +void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch) +{ + ImTextureData* tex = atlas->TexData; + IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); + ImFontAtlasTextureBlockConvert(src_pixels, src_fmt, src_pitch, (unsigned char*)tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), r->w, r->h); + ImFontAtlasPostProcessData pp_data = { atlas, baked->ContainerFont, src, baked, glyph, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), r->w, r->h }; + ImFontAtlasTextureBlockPostProcess(&pp_data); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } -void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint, bool overwrite_dst) +void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint) { - IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. - unsigned int index_size = (unsigned int)IndexLookup.Size; + RemapPairs.SetInt((ImGuiID)from_codepoint, (int)to_codepoint); +} - if (from_codepoint < index_size && IndexLookup.Data[from_codepoint] == (ImU16)-1 && !overwrite_dst) // 'from_codepoint' already exists - return; - if (to_codepoint >= index_size && from_codepoint >= index_size) // both 'from_codepoint' and 'to_codepoint' don't exist -> no-op - return; +// Find glyph, load if necessary, return fallback if missing +ImFontGlyph* ImFontBaked::FindGlyph(ImWchar c) +{ + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return &Glyphs.Data[FallbackGlyphIndex]; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return &Glyphs.Data[i]; + } + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(this, c, NULL); + return glyph ? glyph : &Glyphs.Data[FallbackGlyphIndex]; +} + +// Attempt to load but when missing, return NULL instead of FallbackGlyph +ImFontGlyph* ImFontBaked::FindGlyphNoFallback(ImWchar c) +{ + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return NULL; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return &Glyphs.Data[i]; + } + LoadNoFallback = true; // This is actually a rare call, not done in hot-loop, so we prioritize not adding extra cruft to ImFontBaked_BuildLoadGlyph() call sites. + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(this, c, NULL); + LoadNoFallback = false; + return glyph; +} - GrowIndex(from_codepoint + 1); - IndexLookup[from_codepoint] = (to_codepoint < index_size) ? IndexLookup.Data[to_codepoint] : (ImU16)-1; - IndexAdvanceX[from_codepoint] = (to_codepoint < index_size) ? IndexAdvanceX.Data[to_codepoint] : 1.0f; +bool ImFontBaked::IsGlyphLoaded(ImWchar c) +{ + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return false; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return true; + } + return false; } -// Find glyph, return fallback if missing -ImFontGlyph* ImFont::FindGlyph(ImWchar c) +// This is not fast query +bool ImFont::IsGlyphInFont(ImWchar c) { - if (c >= (size_t)IndexLookup.Size) - return FallbackGlyph; - const ImU16 i = IndexLookup.Data[c]; - if (i == (ImU16)-1) - return FallbackGlyph; - return &Glyphs.Data[i]; + ImFontAtlas* atlas = ContainerAtlas; + ImFontAtlas_FontHookRemapCodepoint(atlas, this, &c); + for (ImFontConfig* src : Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontSrcContainsGlyph != NULL && loader->FontSrcContainsGlyph(atlas, src, c)) + return true; + } + return false; } -ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) +// This is manually inlined in CalcTextSizeA() and CalcWordWrapPosition(), with a non-inline call to BuildLoadGlyphGetAdvanceOrFallback(). +IM_MSVC_RUNTIME_CHECKS_OFF +float ImFontBaked::GetCharAdvance(ImWchar c) { - if (c >= (size_t)IndexLookup.Size) - return NULL; - const ImU16 i = IndexLookup.Data[c]; - if (i == (ImU16)-1) + if ((int)c < IndexAdvanceX.Size) + { + // Missing glyphs fitting inside index will have stored FallbackAdvanceX already. + const float x = IndexAdvanceX.Data[c]; + if (x >= 0.0f) + return x; + } + return ImFontBaked_BuildLoadGlyphAdvanceX(this, c); +} +IM_MSVC_RUNTIME_CHECKS_RESTORE + +ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density) +{ + struct { ImGuiID FontId; float BakedSize; float RasterizerDensity; } hashed_data; + hashed_data.FontId = font_id; + hashed_data.BakedSize = baked_size; + hashed_data.RasterizerDensity = rasterizer_density; + return ImHashData(&hashed_data, sizeof(hashed_data)); +} + +// ImFontBaked pointers are valid for the entire frame but shall never be kept between frames. +ImFontBaked* ImFont::GetFontBaked(float size, float density) +{ + ImFontBaked* baked = LastBaked; + + // Round font size + // - ImGui::PushFont() will already round, but other paths calling GetFontBaked() directly also needs it (e.g. ImFontAtlasBuildPreloadAllGlyphRanges) + size = ImGui::GetRoundedFontSize(size); + + if (density < 0.0f) + density = CurrentRasterizerDensity; + if (baked && baked->Size == size && baked->RasterizerDensity == density) + return baked; + + ImFontAtlas* atlas = ContainerAtlas; + ImFontAtlasBuilder* builder = atlas->Builder; + baked = ImFontAtlasBakedGetOrAdd(atlas, this, size, density); + if (baked == NULL) return NULL; - return &Glyphs.Data[i]; + baked->LastUsedFrame = builder->FrameCount; + LastBaked = baked; + return baked; +} + +ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density) +{ + // FIXME-NEWATLAS: Design for picking a nearest size based on some criteria? + // FIXME-NEWATLAS: Altering font density won't work right away. + IM_ASSERT(font_size > 0.0f && font_rasterizer_density > 0.0f); + ImGuiID baked_id = ImFontAtlasBakedGetId(font->FontId, font_size, font_rasterizer_density); + ImFontAtlasBuilder* builder = atlas->Builder; + ImFontBaked** p_baked_in_map = (ImFontBaked**)builder->BakedMap.GetVoidPtrRef(baked_id); + ImFontBaked* baked = *p_baked_in_map; + if (baked != NULL) + { + IM_ASSERT(baked->Size == font_size && baked->ContainerFont == font && baked->BakedId == baked_id); + return baked; + } + + // If atlas is locked, find closest match + // FIXME-OPT: This is not an optimal query. + if ((font->Flags & ImFontFlags_LockBakedSizes) || atlas->Locked) + { + baked = ImFontAtlasBakedGetClosestMatch(atlas, font, font_size, font_rasterizer_density); + if (baked != NULL) + return baked; + if (atlas->Locked) + { + IM_ASSERT(!atlas->Locked && "Cannot use dynamic font size with a locked ImFontAtlas!"); // Locked because rendering backend does not support ImGuiBackendFlags_RendererHasTextures! + return NULL; + } + } + + // Create new + baked = ImFontAtlasBakedAdd(atlas, font, font_size, font_rasterizer_density, baked_id); + *p_baked_in_map = baked; // To avoid 'builder->BakedMap.SetVoidPtr(baked_id, baked);' while we can. + return baked; } // Trim trailing space and find beginning of next line @@ -3931,8 +5348,6 @@ static inline const char* CalcWordWrapNextLineStartA(const char* text, const cha return text; } -#define ImFontGetCharAdvanceX(_FONT, _CH) ((int)(_CH) < (_FONT)->IndexAdvanceX.Size ? (_FONT)->IndexAdvanceX.Data[_CH] : (_FONT)->FallbackAdvanceX) - // Simple word-wrapping for English, not full-featured. Please submit failing cases! // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) @@ -3949,10 +5364,13 @@ const char* ImFont::CalcWordWrapPosition(float size, const char* text, const cha // Cut words that cannot possibly fit within one line. // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" + + ImFontBaked* baked = GetFontBaked(size); + const float scale = size / baked->Size; + float line_width = 0.0f; float word_width = 0.0f; float blank_width = 0.0f; - const float scale = size / FontSize; wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters const char* word_end = text; @@ -3986,7 +5404,11 @@ const char* ImFont::CalcWordWrapPosition(float size, const char* text, const cha } } - const float char_width = ImFontGetCharAdvanceX(this, c); + // Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' + float char_width = (c < (unsigned int)baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f; + if (char_width < 0.0f) + char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c); + if (ImCharIsBlankW(c)) { if (inside_word) @@ -4041,7 +5463,8 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons text_end = text_begin + ImStrlen(text_begin); // FIXME-OPT: Need to avoid this. const float line_height = size; - const float scale = size / FontSize; + ImFontBaked* baked = GetFontBaked(size); + const float scale = size / baked->Size; ImVec2 text_size = ImVec2(0, 0); float line_width = 0.0f; @@ -4091,7 +5514,12 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons continue; } - const float char_width = ImFontGetCharAdvanceX(this, c) * scale; + // Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' + float char_width = (c < (unsigned int)baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f; + if (char_width < 0.0f) + char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c); + char_width *= scale; + if (line_width + char_width >= max_width) { s = prev_s; @@ -4116,12 +5544,13 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip) { - const ImFontGlyph* glyph = FindGlyph(c); + ImFontBaked* baked = GetFontBaked(size); + const ImFontGlyph* glyph = baked->FindGlyph(c); if (!glyph || !glyph->Visible) return; if (glyph->Colored) col |= ~IM_COL32_A_MASK; - float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; + float scale = (size >= 0.0f) ? (size / baked->Size) : 1.0f; float x = IM_TRUNC(pos.x); float y = IM_TRUNC(pos.y); @@ -4155,6 +5584,7 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) { // Align to be pixel perfect +begin: float x = IM_TRUNC(pos.x); float y = IM_TRUNC(pos.y); if (y > clip_rect.w) @@ -4163,8 +5593,10 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im if (!text_end) text_end = text_begin + ImStrlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. - const float scale = size / FontSize; - const float line_height = FontSize * scale; + const float line_height = size; + ImFontBaked* baked = GetFontBaked(size); + + const float scale = size / baked->Size; const float origin_x = x; const bool word_wrap_enabled = (wrap_width > 0.0f); @@ -4214,6 +5646,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im ImDrawVert* vtx_write = draw_list->_VtxWritePtr; ImDrawIdx* idx_write = draw_list->_IdxWritePtr; unsigned int vtx_index = draw_list->_VtxCurrentIdx; + const int cmd_count = draw_list->CmdBuffer.Size; const ImU32 col_untinted = col | ~IM_COL32_A_MASK; const char* word_wrap_eol = NULL; @@ -4259,9 +5692,9 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im continue; } - const ImFontGlyph* glyph = FindGlyph((ImWchar)c); - if (glyph == NULL) - continue; + const ImFontGlyph* glyph = baked->FindGlyph((ImWchar)c); + //if (glyph == NULL) + // continue; float char_width = glyph->AdvanceX * scale; if (glyph->Visible) @@ -4329,6 +5762,20 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im x += char_width; } + // Edge case: calling RenderText() with unloaded glyphs triggering texture change. It doesn't happen via ImGui:: calls because CalcTextSize() is always used. + if (cmd_count != draw_list->CmdBuffer.Size) //-V547 + { + IM_ASSERT(draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount == 0); + draw_list->CmdBuffer.pop_back(); + draw_list->PrimUnreserve(idx_count_max, vtx_count_max); + draw_list->AddDrawCmd(); + //IMGUI_DEBUG_LOG("RenderText: cancel and retry to missing glyphs.\n"); // [DEBUG] + //draw_list->AddRectFilled(pos, pos + ImVec2(10, 10), IM_COL32(255, 0, 0, 255)); // [DEBUG] + goto begin; + //RenderText(draw_list, size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip); // FIXME-OPT: Would a 'goto begin' be better for code-gen? + //return; + } + // Give back unused vertices (clipped ones, blanks) ~ this is essentially a PrimUnreserve() action. draw_list->VtxBuffer.Size = (int)(vtx_write - draw_list->VtxBuffer.Data); // Same as calling shrink() draw_list->IdxBuffer.Size = (int)(idx_write - draw_list->IdxBuffer.Data); diff --git a/PopLib/imgui/core/imgui_impl_opengl3.cpp b/PopLib/imgui/core/imgui_impl_opengl3.cpp new file mode 100644 index 00000000..607d2437 --- /dev/null +++ b/PopLib/imgui/core/imgui_impl_opengl3.cpp @@ -0,0 +1,1050 @@ +// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline +// - Desktop GL: 2.x 3.x 4.x +// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) +// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!] +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). + +// About WebGL/ES: +// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. +// - This is done automatically on iOS, Android and Emscripten targets. +// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2025-07-22: OpenGL: Add and call embedded loader shutdown during ImGui_ImplOpenGL3_Shutdown() to facilitate multiple init/shutdown cycles in same process. (#8792) +// 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures (#8802) + restore non-WebGL/ES update path that doesn't require a CPU-side copy. +// 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL3_CreateFontsTexture() and ImGui_ImplOpenGL3_DestroyFontsTexture(). +// 2025-06-04: OpenGL: Made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor GL_PRIMITIVE_RESTART. (#8664) +// 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406) +// 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap. +// 2024-06-28: OpenGL: ImGui_ImplOpenGL3_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL3_DestroyFontsTexture(). (#7748) +// 2024-05-07: OpenGL: Update loader for Linux to support EGL/GLVND. (#7562) +// 2024-04-16: OpenGL: Detect ES3 contexts on desktop based on version string, to e.g. avoid calling glPolygonMode() on them. (#7447) +// 2024-01-09: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" and variants, fixing regression on distros missing a symlink. +// 2023-11-08: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" instead of "libGL.so.1", accommodating for NetBSD systems having only "libGL.so.3" available. (#6983) +// 2023-10-05: OpenGL: Rename symbols in our internal loader so that LTO compilation with another copy of gl3w is possible. (#6875, #6668, #4445) +// 2023-06-20: OpenGL: Fixed erroneous use glGetIntegerv(GL_CONTEXT_PROFILE_MASK) on contexts lower than 3.2. (#6539, #6333) +// 2023-05-09: OpenGL: Support for glBindSampler() backup/restore on ES3. (#6375) +// 2023-04-18: OpenGL: Restore front and back polygon mode separately when supported by context. (#6333) +// 2023-03-23: OpenGL: Properly restoring "no shader program bound" if it was the case prior to running the rendering function. (#6267, #6220, #6224) +// 2023-03-15: OpenGL: Fixed GL loader crash when GL_VERSION returns NULL. (#6154, #4445, #3530) +// 2023-03-06: OpenGL: Fixed restoration of a potentially deleted OpenGL program, by calling glIsProgram(). (#6220, #6224) +// 2022-11-09: OpenGL: Reverted use of glBufferSubData(), too many corruptions issues + old issues seemingly can't be reproed with Intel drivers nowadays (revert 2021-12-15 and 2022-05-23 changes). +// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. +// 2022-09-27: OpenGL: Added ability to '#define IMGUI_IMPL_OPENGL_DEBUG'. +// 2022-05-23: OpenGL: Reworking 2021-12-15 "Using buffer orphaning" so it only happens on Intel GPU, seems to cause problems otherwise. (#4468, #4825, #4832, #5127). +// 2022-05-13: OpenGL: Fixed state corruption on OpenGL ES 2.0 due to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute states. +// 2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers. +// 2021-08-23: OpenGL: Fixed ES 3.0 shader ("#version 300 es") use normal precision floats to avoid wobbly rendering at HD resolutions. +// 2021-08-19: OpenGL: Embed and use our own minimal GL loader (imgui_impl_opengl3_loader.h), removing requirement and support for third-party loader. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-06-25: OpenGL: Use OES_vertex_array extension on Emscripten + backup/restore current state. +// 2021-06-21: OpenGL: Destroy individual vertex/fragment shader objects right after they are linked into the main shader. +// 2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when "GL_ARB_clip_control" extension is detected, inside of just OpenGL 4.5 version. +// 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater. +// 2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer. +// 2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state. +// 2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state. +// 2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x) +// 2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre-3.3 context which have the defines set by a loader. +// 2020-07-10: OpenGL: Added support for glad2 OpenGL loader. +// 2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX. +// 2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix. +// 2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset. +// 2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader. +// 2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader. +// 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders. +// 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility. +// 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call. +// 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. +// 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. +// 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop. +// 2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early. +// 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0). +// 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader. +// 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. +// 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450). +// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. +// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN. +// 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used. +// 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES". +// 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation. +// 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link. +// 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples. +// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. +// 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state. +// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a nullptr pointer. +// 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150". +// 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself. +// 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. +// 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. +// 2017-05-01: OpenGL: Fixed save and restore of current blend func state. +// 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. +// 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752) + +//---------------------------------------- +// OpenGL GLSL GLSL +// version version string +//---------------------------------------- +// 2.0 110 "#version 110" +// 2.1 120 "#version 120" +// 3.0 130 "#version 130" +// 3.1 140 "#version 140" +// 3.2 150 "#version 150" +// 3.3 330 "#version 330 core" +// 4.0 400 "#version 400 core" +// 4.1 410 "#version 410 core" +// 4.2 420 "#version 410 core" +// 4.3 430 "#version 430 core" +// ES 2.0 100 "#version 100" = WebGL 1.0 +// ES 3.0 300 "#version 300 es" = WebGL 2.0 +//---------------------------------------- + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE +#include "imgui_impl_opengl3.h" +#include +#include // intptr_t +#if defined(__APPLE__) +#include +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: ignore unknown flags +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used +#pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#pragma clang diagnostic ignored "-Wcast-function-type" // warning: cast between incompatible function types (for loader) +#endif +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' +#pragma GCC diagnostic ignored "-Wcast-function-type" // warning: cast between incompatible function types (for loader) +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1 +#endif + +// GL includes +#if defined(IMGUI_IMPL_OPENGL_ES2) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) +#include // Use GL ES 2 +#else +#include // Use GL ES 2 +#endif +#if defined(__EMSCRIPTEN__) +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES +#endif +#include +#endif +#elif defined(IMGUI_IMPL_OPENGL_ES3) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) +#include // Use GL ES 3 +#else +#include // Use GL ES 3 +#endif +#elif !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) +// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. +// Helper libraries are often used for this purpose! Here we are using our own minimal custom loader based on gl3w. +// In the rest of your app/engine, you can use another loader of your choice (gl3w, glew, glad, glbinding, glext, glLoadGen, etc.). +// If you happen to be developing a new feature for this backend (imgui_impl_opengl3.cpp): +// - You may need to regenerate imgui_impl_opengl3_loader.h to add new symbols. See https://github.com/dearimgui/gl3w_stripped +// Typically you would run: python3 ./gl3w_gen.py --output ../imgui/backends/imgui_impl_opengl3_loader.h --ref ../imgui/backends/imgui_impl_opengl3.cpp ./extra_symbols.txt +// - You can temporarily use an unstripped version. See https://github.com/dearimgui/gl3w_stripped/releases +// Changes to this backend using new APIs should be accompanied by a regenerated stripped loader version. +#define IMGL3W_IMPL +#define IMGUI_IMPL_OPENGL_LOADER_IMGL3W +#include "imgui_impl_opengl3_loader.h" +#endif + +// Vertex arrays are not supported on ES2/WebGL1 unless Emscripten which uses an extension +#ifndef IMGUI_IMPL_OPENGL_ES2 +#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +#elif defined(__EMSCRIPTEN__) +#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +#define glBindVertexArray glBindVertexArrayOES +#define glGenVertexArrays glGenVertexArraysOES +#define glDeleteVertexArrays glDeleteVertexArraysOES +#define GL_VERTEX_ARRAY_BINDING GL_VERTEX_ARRAY_BINDING_OES +#endif + +// Desktop GL 2.0+ has extension and glPolygonMode() which GL ES and WebGL don't have.. +// A desktop ES context can technically compile fine with our loader, so we also perform a runtime checks +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) +#define IMGUI_IMPL_OPENGL_HAS_EXTENSIONS // has glGetIntegerv(GL_NUM_EXTENSIONS) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE // may have glPolygonMode() +#endif + +// Desktop GL 2.1+ and GL ES 3.0+ have glBindBuffer() with GL_PIXEL_UNPACK_BUFFER target. +#if !defined(IMGUI_IMPL_OPENGL_ES2) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK +#endif + +// Desktop GL 3.1+ has GL_PRIMITIVE_RESTART state +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_1) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART +#endif + +// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have. +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET +#endif + +// Desktop GL 3.3+ and GL ES 3.0+ have glBindSampler() +#if !defined(IMGUI_IMPL_OPENGL_ES2) && (defined(IMGUI_IMPL_OPENGL_ES3) || defined(GL_VERSION_3_3)) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER +#endif + +// [Debugging] +//#define IMGUI_IMPL_OPENGL_DEBUG +#ifdef IMGUI_IMPL_OPENGL_DEBUG +#include +#define GL_CALL(_CALL) do { _CALL; GLenum gl_err = glGetError(); if (gl_err != 0) fprintf(stderr, "GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); } while (0) // Call with error check +#else +#define GL_CALL(_CALL) _CALL // Call without error check +#endif + +// OpenGL Data +struct ImGui_ImplOpenGL3_Data +{ + GLuint GlVersion; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2) + char GlslVersionString[32]; // Specified by user or detected based on compile time GL settings. + bool GlProfileIsES2; + bool GlProfileIsES3; + bool GlProfileIsCompat; + GLint GlProfileMask; + GLint MaxTextureSize; + GLuint ShaderHandle; + GLint AttribLocationTex; // Uniforms location + GLint AttribLocationProjMtx; + GLuint AttribLocationVtxPos; // Vertex attributes location + GLuint AttribLocationVtxUV; + GLuint AttribLocationVtxColor; + unsigned int VboHandle, ElementsHandle; + GLsizeiptr VertexBufferSize; + GLsizeiptr IndexBufferSize; + bool HasPolygonMode; + bool HasClipOrigin; + bool UseBufferSubData; + ImVector TempBuffer; + + ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; +} + +// OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only) +#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +struct ImGui_ImplOpenGL3_VtxAttribState +{ + GLint Enabled, Size, Type, Normalized, Stride; + GLvoid* Ptr; + + void GetState(GLint index) + { + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &Enabled); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_SIZE, &Size); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_TYPE, &Type); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &Normalized); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &Stride); + glGetVertexAttribPointerv(index, GL_VERTEX_ATTRIB_ARRAY_POINTER, &Ptr); + } + void SetState(GLint index) + { + glVertexAttribPointer(index, Size, Type, (GLboolean)Normalized, Stride, Ptr); + if (Enabled) glEnableVertexAttribArray(index); else glDisableVertexAttribArray(index); + } +}; +#endif + +// Not static to allow third-party code to use that if they want to (but undocumented) +bool ImGui_ImplOpenGL3_InitLoader(); +bool ImGui_ImplOpenGL3_InitLoader() +{ + // Initialize our loader +#ifdef IMGUI_IMPL_OPENGL_LOADER_IMGL3W + if (glGetIntegerv == nullptr && imgl3wInit() != 0) + { + fprintf(stderr, "Failed to initialize OpenGL loader!\n"); + return false; + } +#endif + return true; +} + +// Functions +bool ImGui_ImplOpenGL3_Init(const char* glsl_version) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + // Initialize loader + if (!ImGui_ImplOpenGL3_InitLoader()) + return false; + + // Setup backend capabilities flags + ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_opengl3"; + + // Query for GL version (e.g. 320 for GL 3.2) + const char* gl_version_str = (const char*)glGetString(GL_VERSION); +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GLES 2 + bd->GlVersion = 200; + bd->GlProfileIsES2 = true; + IM_UNUSED(gl_version_str); +#else + // Desktop or GLES 3 + GLint major = 0; + GLint minor = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + if (major == 0 && minor == 0) + sscanf(gl_version_str, "%d.%d", &major, &minor); // Query GL_VERSION in desktop GL 2.x, the string will start with "." + bd->GlVersion = (GLuint)(major * 100 + minor * 10); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &bd->MaxTextureSize); + +#if defined(IMGUI_IMPL_OPENGL_ES3) + bd->GlProfileIsES3 = true; +#else + if (strncmp(gl_version_str, "OpenGL ES 3", 11) == 0) + bd->GlProfileIsES3 = true; +#endif + +#if defined(GL_CONTEXT_PROFILE_MASK) + if (!bd->GlProfileIsES3 && bd->GlVersion >= 320) + glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask); + bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0; +#endif + + bd->UseBufferSubData = false; + /* + // Query vendor to enable glBufferSubData kludge +#ifdef _WIN32 + if (const char* vendor = (const char*)glGetString(GL_VENDOR)) + if (strncmp(vendor, "Intel", 5) == 0) + bd->UseBufferSubData = true; +#endif + */ +#endif + +#ifdef IMGUI_IMPL_OPENGL_DEBUG + printf("GlVersion = %d, \"%s\"\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2/IsEs3 = %d/%d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, gl_version_str, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG] +#endif + +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET + if (bd->GlVersion >= 320) + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. +#endif + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = (int)bd->MaxTextureSize; + + // Store GLSL version string so we can refer to it later in case we recreate shaders. + // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure. + if (glsl_version == nullptr) + { +#if defined(IMGUI_IMPL_OPENGL_ES2) + glsl_version = "#version 100"; +#elif defined(IMGUI_IMPL_OPENGL_ES3) + glsl_version = "#version 300 es"; +#elif defined(__APPLE__) + glsl_version = "#version 150"; +#else + glsl_version = "#version 130"; +#endif + } + IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString)); + strcpy(bd->GlslVersionString, glsl_version); + strcat(bd->GlslVersionString, "\n"); + + // Make an arbitrary GL call (we don't actually need the result) + // IF YOU GET A CRASH HERE: it probably means the OpenGL function loader didn't do its job. Let us know! + GLint current_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); + + // Detect extensions we support +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE + bd->HasPolygonMode = (!bd->GlProfileIsES2 && !bd->GlProfileIsES3); +#endif + bd->HasClipOrigin = (bd->GlVersion >= 450); +#ifdef IMGUI_IMPL_OPENGL_HAS_EXTENSIONS + GLint num_extensions = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); + for (GLint i = 0; i < num_extensions; i++) + { + const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i); + if (extension != nullptr && strcmp(extension, "GL_ARB_clip_control") == 0) + bd->HasClipOrigin = true; + } +#endif + + return true; +} + +void ImGui_ImplOpenGL3_Shutdown() +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + ImGui_ImplOpenGL3_DestroyDeviceObjects(); + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + IM_DELETE(bd); + +#ifdef IMGUI_IMPL_OPENGL_LOADER_IMGL3W + imgl3wShutdown(); +#endif +} + +void ImGui_ImplOpenGL3_NewFrame() +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplOpenGL3_Init()?"); + + ImGui_ImplOpenGL3_InitLoader(); // Lazily init loader if not already done for e.g. DLL boundaries. + + if (!bd->ShaderHandle) + if (!ImGui_ImplOpenGL3_CreateDeviceObjects()) + IM_ASSERT(0 && "ImGui_ImplOpenGL3_CreateDeviceObjects() failed!"); +} + +static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glEnable(GL_SCISSOR_TEST); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART + if (!bd->GlProfileIsES3 && bd->GlVersion >= 310) + glDisable(GL_PRIMITIVE_RESTART); +#endif +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE + if (bd->HasPolygonMode) + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif + + // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) +#if defined(GL_CLIP_ORIGIN) + bool clip_origin_lower_left = true; + if (bd->HasClipOrigin) + { + GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)¤t_clip_origin); + if (current_clip_origin == GL_UPPER_LEFT) + clip_origin_lower_left = false; + } +#endif + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + GL_CALL(glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height)); + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; +#if defined(GL_CLIP_ORIGIN) + if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left +#endif + const float ortho_projection[4][4] = + { + { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, + }; + glUseProgram(bd->ShaderHandle); + glUniform1i(bd->AttribLocationTex, 0); + glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + if (bd->GlVersion >= 330 || bd->GlProfileIsES3) + glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise. +#endif + + (void)vertex_array_object; +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindVertexArray(vertex_array_object); +#endif + + // Bind vertex/index buffers and setup attributes for ImDrawVert + GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle)); + GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle)); + GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos)); + GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV)); + GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor)); + GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos))); + GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv))); + GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col))); +} + +// OpenGL3 Render function. +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. +// This is in order to be able to run within an OpenGL engine that doesn't do so. +void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0) + return; + + ImGui_ImplOpenGL3_InitLoader(); // Lazily init loader if not already done for e.g. DLL boundaries. + + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplOpenGL3_UpdateTexture(tex); + + // Backup GL state + GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); + glActiveTexture(GL_TEXTURE0); + GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program); + GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + GLuint last_sampler; if (bd->GlVersion >= 330 || bd->GlProfileIsES3) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; } +#endif + GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + // This is part of VAO on OpenGL 3.0+ and OpenGL ES 3.0+. + GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer); + ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_pos; last_vtx_attrib_state_pos.GetState(bd->AttribLocationVtxPos); + ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_uv; last_vtx_attrib_state_uv.GetState(bd->AttribLocationVtxUV); + ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_color; last_vtx_attrib_state_color.GetState(bd->AttribLocationVtxColor); +#endif +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + GLuint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object); +#endif +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE + GLint last_polygon_mode[2]; if (bd->HasPolygonMode) { glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); } +#endif + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); + GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); + GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); + GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); + GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); + GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); + GLboolean last_enable_blend = glIsEnabled(GL_BLEND); + GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); + GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); + GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST); + GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART + GLboolean last_enable_primitive_restart = (!bd->GlProfileIsES3 && bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE; +#endif + + // Setup desired GL state + // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) + // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. + GLuint vertex_array_object = 0; +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + GL_CALL(glGenVertexArrays(1, &vertex_array_object)); +#endif + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + for (const ImDrawList* draw_list : draw_data->CmdLists) + { + // Upload vertex/index buffers + // - OpenGL drivers are in a very sorry state nowadays.... + // During 2021 we attempted to switch from glBufferData() to orphaning+glBufferSubData() following reports + // of leaks on Intel GPU when using multi-viewports on Windows. + // - After this we kept hearing of various display corruptions issues. We started disabling on non-Intel GPU, but issues still got reported on Intel. + // - We are now back to using exclusively glBufferData(). So bd->UseBufferSubData IS ALWAYS FALSE in this code. + // We are keeping the old code path for a while in case people finding new issues may want to test the bd->UseBufferSubData path. + // - See https://github.com/ocornut/imgui/issues/4468 and please report any corruption issues. + const GLsizeiptr vtx_buffer_size = (GLsizeiptr)draw_list->VtxBuffer.Size * (int)sizeof(ImDrawVert); + const GLsizeiptr idx_buffer_size = (GLsizeiptr)draw_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx); + if (bd->UseBufferSubData) + { + if (bd->VertexBufferSize < vtx_buffer_size) + { + bd->VertexBufferSize = vtx_buffer_size; + GL_CALL(glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, nullptr, GL_STREAM_DRAW)); + } + if (bd->IndexBufferSize < idx_buffer_size) + { + bd->IndexBufferSize = idx_buffer_size; + GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, nullptr, GL_STREAM_DRAW)); + } + GL_CALL(glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)draw_list->VtxBuffer.Data)); + GL_CALL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)draw_list->IdxBuffer.Data)); + } + else + { + GL_CALL(glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid*)draw_list->VtxBuffer.Data, GL_STREAM_DRAW)); + GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid*)draw_list->IdxBuffer.Data, GL_STREAM_DRAW)); + } + + for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != nullptr) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + else + pcmd->UserCallback(draw_list, pcmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + + // Apply scissor/clipping rectangle (Y is inverted in OpenGL) + GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y))); + + // Bind texture, Draw + GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID())); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET + if (bd->GlVersion >= 320) + GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset)); + else +#endif + GL_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)))); + } + } + } + + // Destroy the temporary VAO +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + GL_CALL(glDeleteVertexArrays(1, &vertex_array_object)); +#endif + + // Restore modified GL state + // This "glIsProgram()" check is required because if the program is "pending deletion" at the time of binding backup, it will have been deleted by now and will cause an OpenGL error. See #6220. + if (last_program == 0 || glIsProgram(last_program)) glUseProgram(last_program); + glBindTexture(GL_TEXTURE_2D, last_texture); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + if (bd->GlVersion >= 330 || bd->GlProfileIsES3) + glBindSampler(0, last_sampler); +#endif + glActiveTexture(last_active_texture); +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindVertexArray(last_vertex_array_object); +#endif + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer); + last_vtx_attrib_state_pos.SetState(bd->AttribLocationVtxPos); + last_vtx_attrib_state_uv.SetState(bd->AttribLocationVtxUV); + last_vtx_attrib_state_color.SetState(bd->AttribLocationVtxColor); +#endif + glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); + glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); + if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); + if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); + if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); + if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); + if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART + if (!bd->GlProfileIsES3 && bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); } +#endif + +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE + // Desktop OpenGL 3.0 and OpenGL 3.1 had separate polygon draw modes for front-facing and back-facing faces of polygons + if (bd->HasPolygonMode) { if (bd->GlVersion <= 310 || bd->GlProfileIsCompat) { glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); } else { glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); } } +#endif // IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE + + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); + (void)bd; // Not all compilation paths use this +} + +static void ImGui_ImplOpenGL3_DestroyTexture(ImTextureData* tex) +{ + GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID; + glDeleteTextures(1, &gl_tex_id); + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); +} + +void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) +{ + // FIXME: Consider backing up and restoring + if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) + { +#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); +#endif +#ifdef GL_UNPACK_ALIGNMENT + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); +#endif + } + + if (tex->Status == ImTextureStatus_WantCreate) + { + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + const void* pixels = tex->GetPixels(); + GLuint gl_texture_id = 0; + + // Upload texture to graphics system + // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + GLint last_texture; + GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); + GL_CALL(glGenTextures(1, &gl_texture_id)); + GL_CALL(glBindTexture(GL_TEXTURE_2D, gl_texture_id)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->Width, tex->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)gl_texture_id); + tex->SetStatus(ImTextureStatus_OK); + + // Restore state + GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); + } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + GLint last_texture; + GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); + + GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID; + GL_CALL(glBindTexture(GL_TEXTURE_2D, gl_tex_id)); +#if GL_UNPACK_ROW_LENGTH // Not on WebGL/ES + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->Width)); + for (ImTextureRect& r : tex->Updates) + GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixelsAt(r.x, r.y))); + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); +#else + // GL ES doesn't have GL_UNPACK_ROW_LENGTH, so we need to (A) copy to a contiguous buffer or (B) upload line by line. + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + for (ImTextureRect& r : tex->Updates) + { + const int src_pitch = r.w * tex->BytesPerPixel; + bd->TempBuffer.resize(r.h * src_pitch); + char* out_p = bd->TempBuffer.Data; + for (int y = 0; y < r.h; y++, out_p += src_pitch) + memcpy(out_p, tex->GetPixelsAt(r.x, r.y + y), src_pitch); + IM_ASSERT(out_p == bd->TempBuffer.end()); + GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, bd->TempBuffer.Data)); + } +#endif + tex->SetStatus(ImTextureStatus_OK); + GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); // Restore state + } + else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) + ImGui_ImplOpenGL3_DestroyTexture(tex); +} + +// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. +static bool CheckShader(GLuint handle, const char* desc) +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + GLint status = 0, log_length = 0; + glGetShaderiv(handle, GL_COMPILE_STATUS, &status); + glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s! With GLSL: %s\n", desc, bd->GlslVersionString); + if (log_length > 1) + { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetShaderInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +// If you get an error please report on GitHub. You may try different GL context version or GLSL version. +static bool CheckProgram(GLuint handle, const char* desc) +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + GLint status = 0, log_length = 0; + glGetProgramiv(handle, GL_LINK_STATUS, &status); + glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! With GLSL %s\n", desc, bd->GlslVersionString); + if (log_length > 1) + { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetProgramInfoLog(handle, log_length, nullptr, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +bool ImGui_ImplOpenGL3_CreateDeviceObjects() +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Backup GL state + GLint last_texture, last_array_buffer; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK + GLint last_pixel_unpack_buffer = 0; + if (bd->GlVersion >= 210) { glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &last_pixel_unpack_buffer); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } +#endif +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + GLint last_vertex_array; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); +#endif + + // Parse GLSL version string + int glsl_version = 130; + sscanf(bd->GlslVersionString, "#version %d", &glsl_version); + + const GLchar* vertex_shader_glsl_120 = + "uniform mat4 ProjMtx;\n" + "attribute vec2 Position;\n" + "attribute vec2 UV;\n" + "attribute vec4 Color;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_130 = + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_300_es = + "precision highp float;\n" + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_410_core = + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_120 = + "#ifdef GL_ES\n" + " precision mediump float;\n" + "#endif\n" + "uniform sampler2D Texture;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_130 = + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_300_es = + "precision mediump float;\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_410_core = + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "uniform sampler2D Texture;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + // Select shaders matching our GLSL versions + const GLchar* vertex_shader = nullptr; + const GLchar* fragment_shader = nullptr; + if (glsl_version < 130) + { + vertex_shader = vertex_shader_glsl_120; + fragment_shader = fragment_shader_glsl_120; + } + else if (glsl_version >= 410) + { + vertex_shader = vertex_shader_glsl_410_core; + fragment_shader = fragment_shader_glsl_410_core; + } + else if (glsl_version == 300) + { + vertex_shader = vertex_shader_glsl_300_es; + fragment_shader = fragment_shader_glsl_300_es; + } + else + { + vertex_shader = vertex_shader_glsl_130; + fragment_shader = fragment_shader_glsl_130; + } + + // Create shaders + const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader }; + GLuint vert_handle; + GL_CALL(vert_handle = glCreateShader(GL_VERTEX_SHADER)); + glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr); + glCompileShader(vert_handle); + if (!CheckShader(vert_handle, "vertex shader")) + return false; + + const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader }; + GLuint frag_handle; + GL_CALL(frag_handle = glCreateShader(GL_FRAGMENT_SHADER)); + glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr); + glCompileShader(frag_handle); + if (!CheckShader(frag_handle, "fragment shader")) + return false; + + // Link + bd->ShaderHandle = glCreateProgram(); + glAttachShader(bd->ShaderHandle, vert_handle); + glAttachShader(bd->ShaderHandle, frag_handle); + glLinkProgram(bd->ShaderHandle); + if (!CheckProgram(bd->ShaderHandle, "shader program")) + return false; + + glDetachShader(bd->ShaderHandle, vert_handle); + glDetachShader(bd->ShaderHandle, frag_handle); + glDeleteShader(vert_handle); + glDeleteShader(frag_handle); + + bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture"); + bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx"); + bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position"); + bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV"); + bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color"); + + // Create buffers + glGenBuffers(1, &bd->VboHandle); + glGenBuffers(1, &bd->ElementsHandle); + + // Restore modified GL state + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK + if (bd->GlVersion >= 210) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, last_pixel_unpack_buffer); } +#endif +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindVertexArray(last_vertex_array); +#endif + + return true; +} + +void ImGui_ImplOpenGL3_DestroyDeviceObjects() +{ + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; } + if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; } + if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; } + + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplOpenGL3_DestroyTexture(tex); +} + +//----------------------------------------------------------------------------- + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/PopLib/imgui/core/imgui_impl_opengl3.h b/PopLib/imgui/core/imgui_impl_opengl3.h new file mode 100644 index 00000000..b72b5c88 --- /dev/null +++ b/PopLib/imgui/core/imgui_impl_opengl3.h @@ -0,0 +1,68 @@ +// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline +// - Desktop GL: 2.x 3.x 4.x +// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) +// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!] +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). + +// About WebGL/ES: +// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. +// - This is done automatically on iOS, Android and Emscripten targets. +// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +// About GLSL version: +// The 'glsl_version' initialization parameter should be nullptr (default) or a "#version XXX" string. +// On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" +// Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API +#ifndef IMGUI_DISABLE + +// Follow "Getting Started" link and check examples/ folder to learn about using backends! +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); + +// (Optional) Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); + +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex); + +// Configuration flags to add in your imconfig file: +//#define IMGUI_IMPL_OPENGL_ES2 // Enable ES 2 (Auto-detected on Emscripten) +//#define IMGUI_IMPL_OPENGL_ES3 // Enable ES 3 (Auto-detected on iOS/Android) + +// You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. +#if !defined(IMGUI_IMPL_OPENGL_ES2) \ + && !defined(IMGUI_IMPL_OPENGL_ES3) + +// Try to detect GLES on matching platforms +#if defined(__APPLE__) +#include +#endif +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) +#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" +#elif defined(__EMSCRIPTEN__) || defined(__amigaos4__) +#define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" +#else +// Otherwise imgui_impl_opengl3_loader.h will be used. +#endif + +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/PopLib/imgui/core/imgui_impl_opengl3_loader.h b/PopLib/imgui/core/imgui_impl_opengl3_loader.h new file mode 100644 index 00000000..c3f5a93f --- /dev/null +++ b/PopLib/imgui/core/imgui_impl_opengl3_loader.h @@ -0,0 +1,929 @@ +//----------------------------------------------------------------------------- +// About imgui_impl_opengl3_loader.h: +// +// We embed our own OpenGL loader to not require user to provide their own or to have to use ours, +// which proved to be endless problems for users. +// Our loader is custom-generated, based on gl3w but automatically filtered to only include +// enums/functions that we use in our imgui_impl_opengl3.cpp source file in order to be small. +// +// YOU SHOULD NOT NEED TO INCLUDE/USE THIS DIRECTLY. THIS IS USED BY imgui_impl_opengl3.cpp ONLY. +// THE REST OF YOUR APP SHOULD USE A DIFFERENT GL LOADER: ANY GL LOADER OF YOUR CHOICE. +// +// IF YOU GET BUILD ERRORS IN THIS FILE (commonly macro redefinitions or function redefinitions): +// IT LIKELY MEANS THAT YOU ARE BUILDING 'imgui_impl_opengl3.cpp' OR INCLUDING 'imgui_impl_opengl3_loader.h' +// IN THE SAME COMPILATION UNIT AS ONE OF YOUR FILE WHICH IS USING A THIRD-PARTY OPENGL LOADER. +// (e.g. COULD HAPPEN IF YOU ARE DOING A UNITY/JUMBO BUILD, OR INCLUDING .CPP FILES FROM OTHERS) +// YOU SHOULD NOT BUILD BOTH IN THE SAME COMPILATION UNIT. +// BUT IF YOU REALLY WANT TO, you can '#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM' and imgui_impl_opengl3.cpp +// WILL NOT BE USING OUR LOADER, AND INSTEAD EXPECT ANOTHER/YOUR LOADER TO BE AVAILABLE IN THE COMPILATION UNIT. +// +// Regenerate with: +// python3 gl3w_gen.py --output ../imgui/backends/imgui_impl_opengl3_loader.h --ref ../imgui/backends/imgui_impl_opengl3.cpp ./extra_symbols.txt +// +// More info: +// https://github.com/dearimgui/gl3w_stripped +// https://github.com/ocornut/imgui/issues/4445 +//----------------------------------------------------------------------------- + +/* + * This file was generated with gl3w_gen.py, part of imgl3w + * (hosted at https://github.com/dearimgui/gl3w_stripped) + * + * 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. + */ + +#ifndef __gl3w_h_ +#define __gl3w_h_ + +// Adapted from KHR/khrplatform.h to avoid including entire file. +#ifndef __khrplatform_h_ +typedef float khronos_float_t; +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; +#ifdef _WIN64 +typedef signed long long int khronos_intptr_t; +typedef signed long long int khronos_ssize_t; +#else +typedef signed long int khronos_intptr_t; +typedef signed long int khronos_ssize_t; +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +typedef signed __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100) +#include +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#else +typedef signed long long khronos_int64_t; +typedef unsigned long long khronos_uint64_t; +#endif +#endif // __khrplatform_h_ + +#ifndef __gl_glcorearb_h_ +#define __gl_glcorearb_h_ 1 +#ifdef __cplusplus +extern "C" { +#endif +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: MIT +** +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** https://github.com/KhronosGroup/OpenGL-Registry +*/ +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif +/* glcorearb.h is for use with OpenGL core profile implementations. +** It should should be placed in the same directory as gl.h and +** included as . +** +** glcorearb.h includes only APIs in the latest OpenGL core profile +** implementation together with APIs in newer ARB extensions which +** can be supported by the core profile. It does not, and never will +** include functionality removed from the core profile, such as +** fixed-function vertex and fragment processing. +** +** Do not #include both and either of or +** in the same source file. +*/ +/* Generated C header for: + * API: gl + * Profile: core + * Versions considered: .* + * Versions emitted: .* + * Default extensions included: glcore + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ +#ifndef GL_VERSION_1_0 +typedef void GLvoid; +typedef unsigned int GLenum; + +typedef khronos_float_t GLfloat; +typedef int GLint; +typedef int GLsizei; +typedef unsigned int GLbitfield; +typedef double GLdouble; +typedef unsigned int GLuint; +typedef unsigned char GLboolean; +typedef khronos_uint8_t GLubyte; +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_TRIANGLES 0x0004 +#define GL_ONE 1 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_POLYGON_MODE 0x0B40 +#define GL_CULL_FACE 0x0B44 +#define GL_DEPTH_TEST 0x0B71 +#define GL_STENCIL_TEST 0x0B90 +#define GL_VIEWPORT 0x0BA2 +#define GL_BLEND 0x0BE2 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_RGBA 0x1908 +#define GL_FILL 0x1B02 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_LINEAR 0x2601 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_REPEAT 0x2901 +typedef void (APIENTRYP PFNGLPOLYGONMODEPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLSCISSORPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXPARAMETERIPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXIMAGE2DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCLEARPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLCLEARCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (APIENTRYP PFNGLDISABLEPROC) (GLenum cap); +typedef void (APIENTRYP PFNGLENABLEPROC) (GLenum cap); +typedef void (APIENTRYP PFNGLFLUSHPROC) (void); +typedef void (APIENTRYP PFNGLPIXELSTOREIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLREADPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +typedef GLenum (APIENTRYP PFNGLGETERRORPROC) (void); +typedef void (APIENTRYP PFNGLGETINTEGERVPROC) (GLenum pname, GLint *data); +typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGPROC) (GLenum name); +typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC) (GLenum cap); +typedef void (APIENTRYP PFNGLVIEWPORTPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonMode (GLenum face, GLenum mode); +GLAPI void APIENTRY glScissor (GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glClear (GLbitfield mask); +GLAPI void APIENTRY glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI void APIENTRY glDisable (GLenum cap); +GLAPI void APIENTRY glEnable (GLenum cap); +GLAPI void APIENTRY glFlush (void); +GLAPI void APIENTRY glPixelStorei (GLenum pname, GLint param); +GLAPI void APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +GLAPI GLenum APIENTRY glGetError (void); +GLAPI void APIENTRY glGetIntegerv (GLenum pname, GLint *data); +GLAPI const GLubyte *APIENTRY glGetString (GLenum name); +GLAPI GLboolean APIENTRY glIsEnabled (GLenum cap); +GLAPI void APIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height); +#endif +#endif /* GL_VERSION_1_0 */ +#ifndef GL_VERSION_1_1 +typedef khronos_float_t GLclampf; +typedef double GLclampd; +#define GL_TEXTURE_BINDING_2D 0x8069 +typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices); +GLAPI void APIENTRY glTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glBindTexture (GLenum target, GLuint texture); +GLAPI void APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures); +GLAPI void APIENTRY glGenTextures (GLsizei n, GLuint *textures); +#endif +#endif /* GL_VERSION_1_1 */ +#ifndef GL_VERSION_1_2 +#define GL_CLAMP_TO_EDGE 0x812F +#endif /* GL_VERSION_1_2 */ +#ifndef GL_VERSION_1_3 +#define GL_TEXTURE0 0x84C0 +#define GL_ACTIVE_TEXTURE 0x84E0 +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum texture); +#endif +#endif /* GL_VERSION_1_3 */ +#ifndef GL_VERSION_1_4 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_FUNC_ADD 0x8006 +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +GLAPI void APIENTRY glBlendEquation (GLenum mode); +#endif +#endif /* GL_VERSION_1_4 */ +#ifndef GL_VERSION_1_5 +typedef khronos_ssize_t GLsizeiptr; +typedef khronos_intptr_t GLintptr; +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_STREAM_DRAW 0x88E0 +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers); +GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +#endif +#endif /* GL_VERSION_1_5 */ +#ifndef GL_VERSION_2_0 +typedef char GLchar; +typedef khronos_int16_t GLshort; +typedef khronos_int8_t GLbyte; +typedef khronos_uint16_t GLushort; +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_UPPER_LEFT 0x8CA2 +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, void **pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glCompileShader (GLuint shader); +GLAPI GLuint APIENTRY glCreateProgram (void); +GLAPI GLuint APIENTRY glCreateShader (GLenum type); +GLAPI void APIENTRY glDeleteProgram (GLuint program); +GLAPI void APIENTRY glDeleteShader (GLuint shader); +GLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glDisableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glEnableVertexAttribArray (GLuint index); +GLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, void **pointer); +GLAPI GLboolean APIENTRY glIsProgram (GLuint program); +GLAPI void APIENTRY glLinkProgram (GLuint program); +GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +GLAPI void APIENTRY glUseProgram (GLuint program); +GLAPI void APIENTRY glUniform1i (GLint location, GLint v0); +GLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +#endif +#endif /* GL_VERSION_2_0 */ +#ifndef GL_VERSION_2_1 +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#endif /* GL_VERSION_2_1 */ +#ifndef GL_VERSION_3_0 +typedef khronos_uint16_t GLhalf; +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data); +typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index); +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI const GLubyte *APIENTRY glGetStringi (GLenum name, GLuint index); +GLAPI void APIENTRY glBindVertexArray (GLuint array); +GLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays); +#endif +#endif /* GL_VERSION_3_0 */ +#ifndef GL_VERSION_3_1 +#define GL_VERSION_3_1 1 +#define GL_PRIMITIVE_RESTART 0x8F9D +#endif /* GL_VERSION_3_1 */ +#ifndef GL_VERSION_3_2 +#define GL_VERSION_3_2 1 +typedef struct __GLsync *GLsync; +typedef khronos_uint64_t GLuint64; +typedef khronos_int64_t GLint64; +#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define GL_CONTEXT_PROFILE_MASK 0x9126 +typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +#endif +#endif /* GL_VERSION_3_2 */ +#ifndef GL_VERSION_3_3 +#define GL_VERSION_3_3 1 +#define GL_SAMPLER_BINDING 0x8919 +typedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler); +#endif +#endif /* GL_VERSION_3_3 */ +#ifndef GL_VERSION_4_1 +typedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data); +#endif /* GL_VERSION_4_1 */ +#ifndef GL_VERSION_4_3 +typedef void (APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#endif /* GL_VERSION_4_3 */ +#ifndef GL_VERSION_4_5 +#define GL_CLIP_ORIGIN 0x935C +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint *param); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint64 *param); +#endif /* GL_VERSION_4_5 */ +#ifndef GL_ARB_bindless_texture +typedef khronos_uint64_t GLuint64EXT; +#endif /* GL_ARB_bindless_texture */ +#ifndef GL_ARB_cl_event +struct _cl_context; +struct _cl_event; +#endif /* GL_ARB_cl_event */ +#ifndef GL_ARB_clip_control +#define GL_ARB_clip_control 1 +#endif /* GL_ARB_clip_control */ +#ifndef GL_ARB_debug_output +typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#endif /* GL_ARB_debug_output */ +#ifndef GL_EXT_EGL_image_storage +typedef void *GLeglImageOES; +#endif /* GL_EXT_EGL_image_storage */ +#ifndef GL_EXT_direct_state_access +typedef void (APIENTRYP PFNGLGETFLOATI_VEXTPROC) (GLenum pname, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VEXTPROC) (GLenum pname, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPOINTERI_VEXTPROC) (GLenum pname, GLuint index, void **params); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, void **param); +#endif /* GL_EXT_direct_state_access */ +#ifndef GL_NV_draw_vulkan_image +typedef void (APIENTRY *GLVULKANPROCNV)(void); +#endif /* GL_NV_draw_vulkan_image */ +#ifndef GL_NV_gpu_shader5 +typedef khronos_int64_t GLint64EXT; +#endif /* GL_NV_gpu_shader5 */ +#ifndef GL_NV_vertex_buffer_unified_memory +typedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result); +#endif /* GL_NV_vertex_buffer_unified_memory */ +#ifdef __cplusplus +} +#endif +#endif + +#ifndef GL3W_API +#define GL3W_API +#endif + +#ifndef __gl_h_ +#define __gl_h_ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define GL3W_OK 0 +#define GL3W_ERROR_INIT -1 +#define GL3W_ERROR_LIBRARY_OPEN -2 +#define GL3W_ERROR_OPENGL_VERSION -3 + +typedef void (*GL3WglProc)(void); +typedef GL3WglProc (*GL3WGetProcAddressProc)(const char *proc); + +/* gl3w api */ +GL3W_API int imgl3wInit(void); +GL3W_API int imgl3wInit2(GL3WGetProcAddressProc proc); +GL3W_API void imgl3wShutdown(void); +GL3W_API int imgl3wIsSupported(int major, int minor); +GL3W_API GL3WglProc imgl3wGetProcAddress(const char *proc); + +/* gl3w internal state */ +union ImGL3WProcs { + GL3WglProc ptr[60]; + struct { + PFNGLACTIVETEXTUREPROC ActiveTexture; + PFNGLATTACHSHADERPROC AttachShader; + PFNGLBINDBUFFERPROC BindBuffer; + PFNGLBINDSAMPLERPROC BindSampler; + PFNGLBINDTEXTUREPROC BindTexture; + PFNGLBINDVERTEXARRAYPROC BindVertexArray; + PFNGLBLENDEQUATIONPROC BlendEquation; + PFNGLBLENDEQUATIONSEPARATEPROC BlendEquationSeparate; + PFNGLBLENDFUNCSEPARATEPROC BlendFuncSeparate; + PFNGLBUFFERDATAPROC BufferData; + PFNGLBUFFERSUBDATAPROC BufferSubData; + PFNGLCLEARPROC Clear; + PFNGLCLEARCOLORPROC ClearColor; + PFNGLCOMPILESHADERPROC CompileShader; + PFNGLCREATEPROGRAMPROC CreateProgram; + PFNGLCREATESHADERPROC CreateShader; + PFNGLDELETEBUFFERSPROC DeleteBuffers; + PFNGLDELETEPROGRAMPROC DeleteProgram; + PFNGLDELETESHADERPROC DeleteShader; + PFNGLDELETETEXTURESPROC DeleteTextures; + PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays; + PFNGLDETACHSHADERPROC DetachShader; + PFNGLDISABLEPROC Disable; + PFNGLDISABLEVERTEXATTRIBARRAYPROC DisableVertexAttribArray; + PFNGLDRAWELEMENTSPROC DrawElements; + PFNGLDRAWELEMENTSBASEVERTEXPROC DrawElementsBaseVertex; + PFNGLENABLEPROC Enable; + PFNGLENABLEVERTEXATTRIBARRAYPROC EnableVertexAttribArray; + PFNGLFLUSHPROC Flush; + PFNGLGENBUFFERSPROC GenBuffers; + PFNGLGENTEXTURESPROC GenTextures; + PFNGLGENVERTEXARRAYSPROC GenVertexArrays; + PFNGLGETATTRIBLOCATIONPROC GetAttribLocation; + PFNGLGETERRORPROC GetError; + PFNGLGETINTEGERVPROC GetIntegerv; + PFNGLGETPROGRAMINFOLOGPROC GetProgramInfoLog; + PFNGLGETPROGRAMIVPROC GetProgramiv; + PFNGLGETSHADERINFOLOGPROC GetShaderInfoLog; + PFNGLGETSHADERIVPROC GetShaderiv; + PFNGLGETSTRINGPROC GetString; + PFNGLGETSTRINGIPROC GetStringi; + PFNGLGETUNIFORMLOCATIONPROC GetUniformLocation; + PFNGLGETVERTEXATTRIBPOINTERVPROC GetVertexAttribPointerv; + PFNGLGETVERTEXATTRIBIVPROC GetVertexAttribiv; + PFNGLISENABLEDPROC IsEnabled; + PFNGLISPROGRAMPROC IsProgram; + PFNGLLINKPROGRAMPROC LinkProgram; + PFNGLPIXELSTOREIPROC PixelStorei; + PFNGLPOLYGONMODEPROC PolygonMode; + PFNGLREADPIXELSPROC ReadPixels; + PFNGLSCISSORPROC Scissor; + PFNGLSHADERSOURCEPROC ShaderSource; + PFNGLTEXIMAGE2DPROC TexImage2D; + PFNGLTEXPARAMETERIPROC TexParameteri; + PFNGLTEXSUBIMAGE2DPROC TexSubImage2D; + PFNGLUNIFORM1IPROC Uniform1i; + PFNGLUNIFORMMATRIX4FVPROC UniformMatrix4fv; + PFNGLUSEPROGRAMPROC UseProgram; + PFNGLVERTEXATTRIBPOINTERPROC VertexAttribPointer; + PFNGLVIEWPORTPROC Viewport; + } gl; +}; + +GL3W_API extern union ImGL3WProcs imgl3wProcs; + +/* OpenGL functions */ +#define glActiveTexture imgl3wProcs.gl.ActiveTexture +#define glAttachShader imgl3wProcs.gl.AttachShader +#define glBindBuffer imgl3wProcs.gl.BindBuffer +#define glBindSampler imgl3wProcs.gl.BindSampler +#define glBindTexture imgl3wProcs.gl.BindTexture +#define glBindVertexArray imgl3wProcs.gl.BindVertexArray +#define glBlendEquation imgl3wProcs.gl.BlendEquation +#define glBlendEquationSeparate imgl3wProcs.gl.BlendEquationSeparate +#define glBlendFuncSeparate imgl3wProcs.gl.BlendFuncSeparate +#define glBufferData imgl3wProcs.gl.BufferData +#define glBufferSubData imgl3wProcs.gl.BufferSubData +#define glClear imgl3wProcs.gl.Clear +#define glClearColor imgl3wProcs.gl.ClearColor +#define glCompileShader imgl3wProcs.gl.CompileShader +#define glCreateProgram imgl3wProcs.gl.CreateProgram +#define glCreateShader imgl3wProcs.gl.CreateShader +#define glDeleteBuffers imgl3wProcs.gl.DeleteBuffers +#define glDeleteProgram imgl3wProcs.gl.DeleteProgram +#define glDeleteShader imgl3wProcs.gl.DeleteShader +#define glDeleteTextures imgl3wProcs.gl.DeleteTextures +#define glDeleteVertexArrays imgl3wProcs.gl.DeleteVertexArrays +#define glDetachShader imgl3wProcs.gl.DetachShader +#define glDisable imgl3wProcs.gl.Disable +#define glDisableVertexAttribArray imgl3wProcs.gl.DisableVertexAttribArray +#define glDrawElements imgl3wProcs.gl.DrawElements +#define glDrawElementsBaseVertex imgl3wProcs.gl.DrawElementsBaseVertex +#define glEnable imgl3wProcs.gl.Enable +#define glEnableVertexAttribArray imgl3wProcs.gl.EnableVertexAttribArray +#define glFlush imgl3wProcs.gl.Flush +#define glGenBuffers imgl3wProcs.gl.GenBuffers +#define glGenTextures imgl3wProcs.gl.GenTextures +#define glGenVertexArrays imgl3wProcs.gl.GenVertexArrays +#define glGetAttribLocation imgl3wProcs.gl.GetAttribLocation +#define glGetError imgl3wProcs.gl.GetError +#define glGetIntegerv imgl3wProcs.gl.GetIntegerv +#define glGetProgramInfoLog imgl3wProcs.gl.GetProgramInfoLog +#define glGetProgramiv imgl3wProcs.gl.GetProgramiv +#define glGetShaderInfoLog imgl3wProcs.gl.GetShaderInfoLog +#define glGetShaderiv imgl3wProcs.gl.GetShaderiv +#define glGetString imgl3wProcs.gl.GetString +#define glGetStringi imgl3wProcs.gl.GetStringi +#define glGetUniformLocation imgl3wProcs.gl.GetUniformLocation +#define glGetVertexAttribPointerv imgl3wProcs.gl.GetVertexAttribPointerv +#define glGetVertexAttribiv imgl3wProcs.gl.GetVertexAttribiv +#define glIsEnabled imgl3wProcs.gl.IsEnabled +#define glIsProgram imgl3wProcs.gl.IsProgram +#define glLinkProgram imgl3wProcs.gl.LinkProgram +#define glPixelStorei imgl3wProcs.gl.PixelStorei +#define glPolygonMode imgl3wProcs.gl.PolygonMode +#define glReadPixels imgl3wProcs.gl.ReadPixels +#define glScissor imgl3wProcs.gl.Scissor +#define glShaderSource imgl3wProcs.gl.ShaderSource +#define glTexImage2D imgl3wProcs.gl.TexImage2D +#define glTexParameteri imgl3wProcs.gl.TexParameteri +#define glTexSubImage2D imgl3wProcs.gl.TexSubImage2D +#define glUniform1i imgl3wProcs.gl.Uniform1i +#define glUniformMatrix4fv imgl3wProcs.gl.UniformMatrix4fv +#define glUseProgram imgl3wProcs.gl.UseProgram +#define glVertexAttribPointer imgl3wProcs.gl.VertexAttribPointer +#define glViewport imgl3wProcs.gl.Viewport + +#ifdef __cplusplus +} +#endif + +#endif + +#ifdef IMGL3W_IMPL +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define GL3W_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +static HMODULE libgl = NULL; +typedef PROC(__stdcall* GL3WglGetProcAddr)(LPCSTR); +static GL3WglGetProcAddr wgl_get_proc_address; + +static int open_libgl(void) +{ + libgl = LoadLibraryA("opengl32.dll"); + if (!libgl) + return GL3W_ERROR_LIBRARY_OPEN; + wgl_get_proc_address = (GL3WglGetProcAddr)GetProcAddress(libgl, "wglGetProcAddress"); + return GL3W_OK; +} + +static void close_libgl(void) { FreeLibrary(libgl); libgl = NULL; } +static GL3WglProc get_proc(const char *proc) +{ + GL3WglProc res; + res = (GL3WglProc)wgl_get_proc_address(proc); + if (!res) + res = (GL3WglProc)GetProcAddress(libgl, proc); + return res; +} +#elif defined(__APPLE__) +#include + +static void *libgl = NULL; +static int open_libgl(void) +{ + libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY | RTLD_LOCAL); + if (!libgl) + return GL3W_ERROR_LIBRARY_OPEN; + return GL3W_OK; +} + +static void close_libgl(void) { dlclose(libgl); libgl = NULL; } + +static GL3WglProc get_proc(const char *proc) +{ + GL3WglProc res; + *(void **)(&res) = dlsym(libgl, proc); + return res; +} +#else +#include + +static void* libgl; // OpenGL library +static void* libglx; // GLX library +static void* libegl; // EGL library +static GL3WGetProcAddressProc gl_get_proc_address; + +static void close_libgl(void) +{ + if (libgl) { + dlclose(libgl); + libgl = NULL; + } + if (libegl) { + dlclose(libegl); + libegl = NULL; + } + if (libglx) { + dlclose(libglx); + libglx = NULL; + } +} + +static int is_library_loaded(const char* name, void** lib) +{ + *lib = dlopen(name, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + return *lib != NULL; +} + +static int open_libs(void) +{ + // On Linux we have two APIs to get process addresses: EGL and GLX. + // EGL is supported under both X11 and Wayland, whereas GLX is X11-specific. + + libgl = NULL; + libegl = NULL; + libglx = NULL; + + // First check what's already loaded, the windowing library might have + // already loaded either EGL or GLX and we want to use the same one. + + if (is_library_loaded("libEGL.so.1", &libegl) || + is_library_loaded("libGLX.so.0", &libglx)) { + libgl = dlopen("libOpenGL.so.0", RTLD_LAZY | RTLD_LOCAL); + if (libgl) + return GL3W_OK; + else + close_libgl(); + } + + if (is_library_loaded("libGL.so", &libgl)) + return GL3W_OK; + if (is_library_loaded("libGL.so.1", &libgl)) + return GL3W_OK; + if (is_library_loaded("libGL.so.3", &libgl)) + return GL3W_OK; + + // Neither is already loaded, so we have to load one. Try EGL first + // because it is supported under both X11 and Wayland. + + // Load OpenGL + EGL + libgl = dlopen("libOpenGL.so.0", RTLD_LAZY | RTLD_LOCAL); + libegl = dlopen("libEGL.so.1", RTLD_LAZY | RTLD_LOCAL); + if (libgl && libegl) + return GL3W_OK; + else + close_libgl(); + + // Fall back to legacy libGL, which includes GLX + // While most systems use libGL.so.1, NetBSD seems to use that libGL.so.3. See https://github.com/ocornut/imgui/issues/6983 + libgl = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL); + if (!libgl) + libgl = dlopen("libGL.so.1", RTLD_LAZY | RTLD_LOCAL); + if (!libgl) + libgl = dlopen("libGL.so.3", RTLD_LAZY | RTLD_LOCAL); + + if (libgl) + return GL3W_OK; + + return GL3W_ERROR_LIBRARY_OPEN; +} + +static int open_libgl(void) +{ + int res = open_libs(); + if (res) + return res; + + if (libegl) + *(void**)(&gl_get_proc_address) = dlsym(libegl, "eglGetProcAddress"); + else if (libglx) + *(void**)(&gl_get_proc_address) = dlsym(libglx, "glXGetProcAddressARB"); + else + *(void**)(&gl_get_proc_address) = dlsym(libgl, "glXGetProcAddressARB"); + + if (!gl_get_proc_address) { + close_libgl(); + return GL3W_ERROR_LIBRARY_OPEN; + } + + return GL3W_OK; +} + +static GL3WglProc get_proc(const char* proc) +{ + GL3WglProc res = NULL; + + // Before EGL version 1.5, eglGetProcAddress doesn't support querying core + // functions and may return a dummy function if we try, so try to load the + // function from the GL library directly first. + if (libegl) + *(void**)(&res) = dlsym(libgl, proc); + + if (!res) + res = gl_get_proc_address(proc); + + if (!libegl && !res) + *(void**)(&res) = dlsym(libgl, proc); + + return res; +} +#endif + +static struct { int major, minor; } version; + +static int parse_version(void) +{ + if (!glGetIntegerv) + return GL3W_ERROR_INIT; + glGetIntegerv(GL_MAJOR_VERSION, &version.major); + glGetIntegerv(GL_MINOR_VERSION, &version.minor); + if (version.major == 0 && version.minor == 0) + { + // Query GL_VERSION in desktop GL 2.x, the string will start with "." + if (const char* gl_version = (const char*)glGetString(GL_VERSION)) + sscanf(gl_version, "%d.%d", &version.major, &version.minor); + } + if (version.major < 2) + return GL3W_ERROR_OPENGL_VERSION; + return GL3W_OK; +} + +static void load_procs(GL3WGetProcAddressProc proc); + +int imgl3wInit(void) +{ + int res = open_libgl(); + if (res) + return res; + atexit(close_libgl); + return imgl3wInit2(get_proc); +} + +int imgl3wInit2(GL3WGetProcAddressProc proc) +{ + load_procs(proc); + return parse_version(); +} + +void imgl3wShutdown(void) +{ + close_libgl(); +} + +int imgl3wIsSupported(int major, int minor) +{ + if (major < 2) + return 0; + if (version.major == major) + return version.minor >= minor; + return version.major >= major; +} + +GL3WglProc imgl3wGetProcAddress(const char *proc) { return get_proc(proc); } + +static const char *proc_names[] = { + "glActiveTexture", + "glAttachShader", + "glBindBuffer", + "glBindSampler", + "glBindTexture", + "glBindVertexArray", + "glBlendEquation", + "glBlendEquationSeparate", + "glBlendFuncSeparate", + "glBufferData", + "glBufferSubData", + "glClear", + "glClearColor", + "glCompileShader", + "glCreateProgram", + "glCreateShader", + "glDeleteBuffers", + "glDeleteProgram", + "glDeleteShader", + "glDeleteTextures", + "glDeleteVertexArrays", + "glDetachShader", + "glDisable", + "glDisableVertexAttribArray", + "glDrawElements", + "glDrawElementsBaseVertex", + "glEnable", + "glEnableVertexAttribArray", + "glFlush", + "glGenBuffers", + "glGenTextures", + "glGenVertexArrays", + "glGetAttribLocation", + "glGetError", + "glGetIntegerv", + "glGetProgramInfoLog", + "glGetProgramiv", + "glGetShaderInfoLog", + "glGetShaderiv", + "glGetString", + "glGetStringi", + "glGetUniformLocation", + "glGetVertexAttribPointerv", + "glGetVertexAttribiv", + "glIsEnabled", + "glIsProgram", + "glLinkProgram", + "glPixelStorei", + "glPolygonMode", + "glReadPixels", + "glScissor", + "glShaderSource", + "glTexImage2D", + "glTexParameteri", + "glTexSubImage2D", + "glUniform1i", + "glUniformMatrix4fv", + "glUseProgram", + "glVertexAttribPointer", + "glViewport", +}; + +GL3W_API union ImGL3WProcs imgl3wProcs; + +static void load_procs(GL3WGetProcAddressProc proc) +{ + size_t i; + for (i = 0; i < GL3W_ARRAY_SIZE(proc_names); i++) + imgl3wProcs.ptr[i] = proc(proc_names[i]); +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/PopLib/imgui/core/imgui_impl_sdlrenderer3.cpp b/PopLib/imgui/core/imgui_impl_sdlrenderer3.cpp index 42fefa0c..42c17388 100644 --- a/PopLib/imgui/core/imgui_impl_sdlrenderer3.cpp +++ b/PopLib/imgui/core/imgui_impl_sdlrenderer3.cpp @@ -10,8 +10,9 @@ // and it might be difficult to step out of those boundaries. // Implemented features: -// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -23,6 +24,7 @@ // - Introduction, links and more at the top of imgui.cpp // CHANGELOG +// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer3_CreateFontsTexture() and ImGui_ImplSDLRenderer3_DestroyFontsTexture(). // 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color. // 2024-10-09: Expose selected render state in ImGui_ImplSDLRenderer3_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks. // 2024-07-01: Update for SDL3 api changes: SDL_RenderGeometryRaw() uint32 version was removed (SDL#9009). @@ -39,6 +41,8 @@ #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe #endif // SDL @@ -51,7 +55,6 @@ struct ImGui_ImplSDLRenderer3_Data { SDL_Renderer* Renderer; // Main viewport's renderer - SDL_Texture* FontTexture; ImVector ColorBuffer; ImGui_ImplSDLRenderer3_Data() { memset((void*)this, 0, sizeof(*this)); } @@ -77,6 +80,7 @@ bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer) io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_sdlrenderer3"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. bd->Renderer = renderer; @@ -93,7 +97,7 @@ void ImGui_ImplSDLRenderer3_Shutdown() io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); IM_DELETE(bd); } @@ -109,9 +113,7 @@ void ImGui_ImplSDLRenderer3_NewFrame() { ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLRenderer3_Init()?"); - - if (!bd->FontTexture) - ImGui_ImplSDLRenderer3_CreateDeviceObjects(); + IM_UNUSED(bd); } // https://github.com/libsdl-org/SDL/issues/9009 @@ -152,6 +154,13 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* if (fb_width == 0 || fb_height == 0) return; + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplSDLRenderer3_UpdateTexture(tex); + // Backup SDL_Renderer state that will be modified to restore it afterwards struct BackupSDLRendererState { @@ -180,9 +189,8 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* ImVec2 clip_scale = render_scale; // Render command lists - for (int n = 0; n < draw_data->CmdListsCount; n++) + for (const ImDrawList* draw_list : draw_data->CmdLists) { - const ImDrawList* draw_list = draw_data->CmdLists[n]; const ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data; const ImDrawIdx* idx_buffer = draw_list->IdxBuffer.Data; @@ -235,55 +243,67 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* SDL_SetRenderClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr); } -// Called by Init/NewFrame/Shutdown -bool ImGui_ImplSDLRenderer3_CreateFontsTexture() +void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) { - ImGuiIO& io = ImGui::GetIO(); ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); - // Build texture atlas - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. - - // Upload texture to graphics system - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) - bd->FontTexture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, width, height); - if (bd->FontTexture == nullptr) + if (tex->Status == ImTextureStatus_WantCreate) { - SDL_Log("error creating texture"); - return false; + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + + // Create texture + // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + SDL_Texture* sdl_texture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, tex->Width, tex->Height); + IM_ASSERT(sdl_texture != nullptr && "Backend failed to create texture!"); + SDL_UpdateTexture(sdl_texture, nullptr, tex->GetPixels(), tex->GetPitch()); + SDL_SetTextureBlendMode(sdl_texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureScaleMode(sdl_texture, SDL_SCALEMODE_LINEAR); + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)sdl_texture); + tex->SetStatus(ImTextureStatus_OK); } - SDL_UpdateTexture(bd->FontTexture, nullptr, pixels, 4 * width); - SDL_SetTextureBlendMode(bd->FontTexture, SDL_BLENDMODE_BLEND); - SDL_SetTextureScaleMode(bd->FontTexture, SDL_SCALEMODE_LINEAR); - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); - - return true; -} - -void ImGui_ImplSDLRenderer3_DestroyFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); - if (bd->FontTexture) + else if (tex->Status == ImTextureStatus_WantUpdates) { - io.Fonts->SetTexID(0); - SDL_DestroyTexture(bd->FontTexture); - bd->FontTexture = nullptr; + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID; + for (ImTextureRect& r : tex->Updates) + { + SDL_Rect sdl_r = { r.x, r.y, r.w, r.h }; + SDL_UpdateTexture(sdl_texture, &sdl_r, tex->GetPixelsAt(r.x, r.y), tex->GetPitch()); + } + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy) + { + SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID; + if (sdl_texture == nullptr) + return; + SDL_DestroyTexture(sdl_texture); + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->SetStatus(ImTextureStatus_Destroyed); } } -bool ImGui_ImplSDLRenderer3_CreateDeviceObjects() +void ImGui_ImplSDLRenderer3_CreateDeviceObjects() { - return ImGui_ImplSDLRenderer3_CreateFontsTexture(); } void ImGui_ImplSDLRenderer3_DestroyDeviceObjects() { - ImGui_ImplSDLRenderer3_DestroyFontsTexture(); + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + { + tex->SetStatus(ImTextureStatus_WantDestroy); + ImGui_ImplSDLRenderer3_UpdateTexture(tex); + } } //----------------------------------------------------------------------------- diff --git a/PopLib/imgui/core/imgui_impl_sdlrenderer3.h b/PopLib/imgui/core/imgui_impl_sdlrenderer3.h index 3473bcc7..618cc243 100644 --- a/PopLib/imgui/core/imgui_impl_sdlrenderer3.h +++ b/PopLib/imgui/core/imgui_impl_sdlrenderer3.h @@ -10,8 +10,9 @@ // and it might be difficult to step out of those boundaries. // Implemented features: -// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! +// [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -35,11 +36,12 @@ IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_NewFrame(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer); // Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_CreateFontsTexture(); -IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_DestroyFontsTexture(); -IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_DestroyDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex); + // [BETA] Selected render state data shared with callbacks. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplSDLRenderer3_RenderDrawData() call. // (Please open an issue if you feel you need access to more data) diff --git a/PopLib/imgui/core/imgui_internal.h b/PopLib/imgui/core/imgui_internal.h index fb23c6cf..35ffb0d5 100644 --- a/PopLib/imgui/core/imgui_internal.h +++ b/PopLib/imgui/core/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.0 WIP +// dear imgui, v1.92.2 WIP // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -37,6 +37,7 @@ Index of this file: // [SECTION] Tab bar, Tab item support // [SECTION] Table support // [SECTION] ImGui internal API +// [SECTION] ImFontLoader // [SECTION] ImFontAtlas internal API // [SECTION] Test Engine specific hooks (imgui_test_engine) @@ -132,7 +133,7 @@ Index of this file: //----------------------------------------------------------------------------- // Utilities -// (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImPool<>, ImChunkStream<>) +// (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImStableVector<>, ImPool<>, ImChunkStream<>) struct ImBitVector; // Store 1-bit per value struct ImRect; // An axis-aligned rectangle (2 points) struct ImGuiTextIndex; // Maintain a line index for a text buffer. @@ -140,6 +141,9 @@ struct ImGuiTextIndex; // Maintain a line index for a text buffer. // ImDrawList/ImFontAtlas struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawListSharedData; // Data shared between all ImDrawList instances +struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas +struct ImFontAtlasPostProcessData; // Data available to potential texture post-processing functions +struct ImFontAtlasRectEntry; // Packed rectangle lookup entry // ImGui struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) @@ -242,7 +246,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) -#define IMGUI_DEBUG_LOG_FONT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_FONT(...) do { ImGuiContext* g2 = GImGui; if (g2 && g2->DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Called from ImFontAtlas function which may operate without a context. #define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Static Asserts @@ -352,6 +356,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer // - Helper: ImBitArray // - Helper: ImBitVector // - Helper: ImSpan<>, ImSpanAllocator<> +// - Helper: ImStableVector<> // - Helper: ImPool<> // - Helper: ImChunkStream<> // - Helper: ImGuiTextIndex @@ -364,17 +369,17 @@ IMGUI_API ImGuiID ImHashStr(const char* data, size_t data_size = 0, ImGuiI // Helpers: Sorting #ifndef ImQsort -static inline void ImQsort(void* base, size_t count, size_t size_of_element, int(IMGUI_CDECL *compare_func)(void const*, void const*)) { if (count > 1) qsort(base, count, size_of_element, compare_func); } +inline void ImQsort(void* base, size_t count, size_t size_of_element, int(IMGUI_CDECL *compare_func)(void const*, void const*)) { if (count > 1) qsort(base, count, size_of_element, compare_func); } #endif // Helpers: Color Blending IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b); // Helpers: Bit manipulation -static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } -static inline bool ImIsPowerOfTwo(ImU64 v) { return v != 0 && (v & (v - 1)) == 0; } -static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } -static inline unsigned int ImCountSetBits(unsigned int v) { unsigned int count = 0; while (v > 0) { v = v & (v - 1); count++; } return count; } +inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +inline bool ImIsPowerOfTwo(ImU64 v) { return v != 0 && (v & (v - 1)) == 0; } +inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } +inline unsigned int ImCountSetBits(unsigned int v) { unsigned int count = 0; while (v > 0) { v = v & (v - 1); count++; } return count; } // Helpers: String #define ImStrlen strlen @@ -383,6 +388,7 @@ IMGUI_API int ImStricmp(const char* str1, const char* str2); IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); // Case insensitive compare to a certain count. IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); // Copy to a certain count and always zero terminate (strncpy doesn't). IMGUI_API char* ImStrdup(const char* str); // Duplicate a string. +IMGUI_API void* ImMemdup(const void* src, size_t size); // Duplicate a chunk of memory. IMGUI_API char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* str); // Copy in provided buffer, recreate buffer if needed. IMGUI_API const char* ImStrchrRange(const char* str_begin, const char* str_end, char c); // Find first occurrence of 'c' in string range. IMGUI_API const char* ImStreolRange(const char* str, const char* str_end); // End end-of-line @@ -392,10 +398,10 @@ IMGUI_API const char* ImStrSkipBlank(const char* str); IMGUI_API int ImStrlenW(const ImWchar* str); // Computer string length (ImWchar string) IMGUI_API const char* ImStrbol(const char* buf_mid_line, const char* buf_begin); // Find beginning-of-line IM_MSVC_RUNTIME_CHECKS_OFF -static inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } -static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } -static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } -static inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } +inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } +inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } +inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } +inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Formatting @@ -411,7 +417,7 @@ IMGUI_API const char* ImParseFormatSanitizeForScanning(const char* fmt_in, cha IMGUI_API int ImParseFormatPrecision(const char* format, int default_value); // Helpers: UTF-8 <> wchar conversions -IMGUI_API const char* ImTextCharToUtf8(char out_buf[5], unsigned int c); // return out_buf +IMGUI_API int ImTextCharToUtf8(char out_buf[5], unsigned int c); // return output UTF-8 bytes count IMGUI_API int ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // read one character. return input UTF-8 bytes count IMGUI_API int ImTextStrFromUtf8(ImWchar* out_buf, int out_buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count @@ -425,11 +431,11 @@ IMGUI_API int ImTextCountLines(const char* in_text, const char* in_tex #ifdef IMGUI_DISABLE_FILE_FUNCTIONS #define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS typedef void* ImFileHandle; -static inline ImFileHandle ImFileOpen(const char*, const char*) { return NULL; } -static inline bool ImFileClose(ImFileHandle) { return false; } -static inline ImU64 ImFileGetSize(ImFileHandle) { return (ImU64)-1; } -static inline ImU64 ImFileRead(void*, ImU64, ImU64, ImFileHandle) { return 0; } -static inline ImU64 ImFileWrite(const void*, ImU64, ImU64, ImFileHandle) { return 0; } +inline ImFileHandle ImFileOpen(const char*, const char*) { return NULL; } +inline bool ImFileClose(ImFileHandle) { return false; } +inline ImU64 ImFileGetSize(ImFileHandle) { return (ImU64)-1; } +inline ImU64 ImFileRead(void*, ImU64, ImU64, ImFileHandle) { return 0; } +inline ImU64 ImFileWrite(const void*, ImU64, ImU64, ImFileHandle) { return 0; } #endif #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS typedef FILE* ImFileHandle; @@ -456,54 +462,56 @@ IM_MSVC_RUNTIME_CHECKS_OFF #define ImAtan2(Y, X) atan2f((Y), (X)) #define ImAtof(STR) atof(STR) #define ImCeil(X) ceilf(X) -static inline float ImPow(float x, float y) { return powf(x, y); } // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision -static inline double ImPow(double x, double y) { return pow(x, y); } -static inline float ImLog(float x) { return logf(x); } // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision -static inline double ImLog(double x) { return log(x); } -static inline int ImAbs(int x) { return x < 0 ? -x : x; } -static inline float ImAbs(float x) { return fabsf(x); } -static inline double ImAbs(double x) { return fabs(x); } -static inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : (x > 0.0f) ? 1.0f : 0.0f; } // Sign operator - returns -1, 0 or 1 based on sign of argument -static inline double ImSign(double x) { return (x < 0.0) ? -1.0 : (x > 0.0) ? 1.0 : 0.0; } +inline float ImPow(float x, float y) { return powf(x, y); } // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision +inline double ImPow(double x, double y) { return pow(x, y); } +inline float ImLog(float x) { return logf(x); } // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision +inline double ImLog(double x) { return log(x); } +inline int ImAbs(int x) { return x < 0 ? -x : x; } +inline float ImAbs(float x) { return fabsf(x); } +inline double ImAbs(double x) { return fabs(x); } +inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : (x > 0.0f) ? 1.0f : 0.0f; } // Sign operator - returns -1, 0 or 1 based on sign of argument +inline double ImSign(double x) { return (x < 0.0) ? -1.0 : (x > 0.0) ? 1.0 : 0.0; } #ifdef IMGUI_ENABLE_SSE -static inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } +inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } #else -static inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } +inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } #endif -static inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } +inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } #endif // - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double // (Exceptionally using templates here but we could also redefine them for those types) -template static inline T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } -template static inline T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } -template static inline T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } -template static inline T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); } -template static inline void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } -template static inline T ImAddClampOverflow(T a, T b, T mn, T mx) { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; } -template static inline T ImSubClampOverflow(T a, T b, T mn, T mx) { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; } +template T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } +template T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } +template T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +template T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); } +template void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } +template T ImAddClampOverflow(T a, T b, T mn, T mx) { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; } +template T ImSubClampOverflow(T a, T b, T mn, T mx) { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; } // - Misc maths helpers -static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } -static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } -static inline ImVec2 ImClamp(const ImVec2& v, const ImVec2&mn, const ImVec2&mx) { return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); } -static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } -static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } -static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } -static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } -static inline float ImLengthSqr(const ImVec2& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y); } -static inline float ImLengthSqr(const ImVec4& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); } -static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; } -static inline float ImTrunc(float f) { return (float)(int)(f); } -static inline ImVec2 ImTrunc(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); } -static inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() -static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } -static inline int ImModPositive(int a, int b) { return (a + b) % b; } -static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } -static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } -static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } -static inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x) { return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0; } -static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } -static inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } -static inline float ImExponentialMovingAverage(float avg, float sample, int n) { avg -= avg / n; avg += sample / n; return avg; } +inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } +inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } +inline ImVec2 ImClamp(const ImVec2& v, const ImVec2&mn, const ImVec2&mx){ return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); } +inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } +inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } +inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } +inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +inline float ImLengthSqr(const ImVec2& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y); } +inline float ImLengthSqr(const ImVec4& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); } +inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; } +inline float ImTrunc(float f) { return (float)(int)(f); } +inline ImVec2 ImTrunc(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); } +inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() +inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } +inline float ImTrunc64(float f) { return (float)(ImS64)(f); } +inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } +inline int ImModPositive(int a, int b) { return (a + b) % b; } +inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } +inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } +inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x) { return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0; } +inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } +inline float ImExponentialMovingAverage(float avg, float sample, int n){ avg -= avg / n; avg += sample / n; return avg; } IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Geometry @@ -528,6 +536,14 @@ struct ImVec1 constexpr ImVec1(float _x) : x(_x) { } }; +// Helper: ImVec2i (2D vector, integer) +struct ImVec2i +{ + int x, y; + constexpr ImVec2i() : x(0), y(0) {} + constexpr ImVec2i(int _x, int _y) : x(_x), y(_y) {} +}; + // Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage) struct ImVec2ih { @@ -679,6 +695,39 @@ struct ImSpanAllocator inline void GetSpan(int n, ImSpan* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); } }; +// Helper: ImStableVector<> +// Allocating chunks of BLOCK_SIZE items. Objects pointers are never invalidated when growing, only by clear(). +// Important: does not destruct anything! +// Implemented only the minimum set of functions we need for it. +template +struct ImStableVector +{ + int Size = 0; + int Capacity = 0; + ImVector Blocks; + + // Functions + inline ~ImStableVector() { for (T* block : Blocks) IM_FREE(block); } + + inline void clear() { Size = Capacity = 0; Blocks.clear_delete(); } + inline void resize(int new_size) { if (new_size > Capacity) reserve(new_size); Size = new_size; } + inline void reserve(int new_cap) + { + new_cap = IM_MEMALIGN(new_cap, BLOCK_SIZE); + int old_count = Capacity / BLOCK_SIZE; + int new_count = new_cap / BLOCK_SIZE; + if (new_count <= old_count) + return; + Blocks.resize(new_count); + for (int n = old_count; n < new_count; n++) + Blocks[n] = (T*)IM_ALLOC(sizeof(T) * BLOCK_SIZE); + Capacity = new_cap; + } + inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; } + inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; } + inline T* push_back(const T& v) { int i = Size; IM_ASSERT(i >= 0); if (Size == Capacity) reserve(Capacity + BLOCK_SIZE); void* ptr = &Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; memcpy(ptr, &v, sizeof(v)); Size++; return (T*)ptr; } +}; + // Helper: ImPool<> // Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer, // Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object. @@ -787,17 +836,20 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag // You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. struct IMGUI_API ImDrawListSharedData { - ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas - const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas - ImFont* Font; // Current/default font (optional, for simplified AddText overload) - float FontSize; // Current/default font size (optional, for simplified AddText overload) - float FontScale; // Current/default font scale (== FontSize / Font->FontSize) + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) + ImFontAtlas* FontAtlas; // Current font atlas + ImFont* Font; // Current font (used for simplified AddText overload) + float FontSize; // Current font size (used for for simplified AddText overload) + float FontScale; // Current font scale (== FontSize / Font->FontSize) float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc float InitialFringeScale; // Initial scale to apply to AA fringe ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImVector TempBuffer; // Temporary write buffer + ImVector DrawLists; // All draw lists associated to this ImDrawListSharedData + ImGuiContext* Context; // [OPTIONAL] Link to Dear ImGui context. 99% of ImDrawList/ImFontAtlas can function without an ImGui context, but this facilitate handling one legacy edge case. // Lookup tables ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. @@ -805,6 +857,7 @@ struct IMGUI_API ImDrawListSharedData ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) ImDrawListSharedData(); + ~ImDrawListSharedData(); void SetCircleTessellationMaxError(float max_error); }; @@ -816,6 +869,13 @@ struct ImDrawDataBuilder ImDrawDataBuilder() { memset(this, 0, sizeof(*this)); } }; +struct ImFontStackData +{ + ImFont* Font; + float FontSizeBeforeScaling; // ~~ style.FontSizeBase + float FontSizeAfterScaling; // ~~ g.FontSize +}; + //----------------------------------------------------------------------------- // [SECTION] Style support //----------------------------------------------------------------------------- @@ -887,6 +947,7 @@ enum ImGuiItemFlagsPrivate_ ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false). ImGuiItemFlags_NoMarkEdited = 1 << 16, // false // Skip calling MarkItemEdited() + ImGuiItemFlags_NoFocus = 1 << 17, // false // [EXPERIMENTAL: Not very well specced] Clicking doesn't take focus. Automatically sets ImGuiButtonFlags_NoFocus + ImGuiButtonFlags_NoNavFocus in ButtonBehavior(). // Controlled by widget code ImGuiItemFlags_Inputable = 1 << 20, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. @@ -915,6 +976,7 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_Visible = 1 << 8, // [WIP] Set when item is overlapping the current clipping rectangle (Used internally. Please don't use yet: API/system will change as we refactor Itemadd()). ImGuiItemStatusFlags_HasClipRect = 1 << 9, // g.LastItemData.ClipRect is valid. ImGuiItemStatusFlags_HasShortcut = 1 << 10, // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd(). + //ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Removed IN 1.90.1 (Dec 2023). The trigger is part of g.NavActivateId. See commit 54c1bdeceb. // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE @@ -964,8 +1026,10 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19, // don't report as hovered when nav focus is on this item ImGuiButtonFlags_NoSetKeyOwner = 1 << 20, // don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) ImGuiButtonFlags_NoTestKeyOwner = 1 << 21, // don't test key/input owner when polling the key (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) + ImGuiButtonFlags_NoFocus = 1 << 22, // [EXPERIMENTAL: Not very well specced]. Don't focus parent window when clicking. ImGuiButtonFlags_PressedOnMask_ = ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_PressedOnDragDropHold, ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease, + //ImGuiButtonFlags_NoKeyModifiers = ImGuiButtonFlags_NoKeyModsAllowed, // Renamed in 1.91.4 }; // Extend ImGuiComboFlags_ @@ -1616,6 +1680,7 @@ enum ImGuiNavRenderCursorFlags_ ImGuiNavHighlightFlags_Compact = ImGuiNavRenderCursorFlags_Compact, // Renamed in 1.91.4 ImGuiNavHighlightFlags_AlwaysDraw = ImGuiNavRenderCursorFlags_AlwaysDraw, // Renamed in 1.91.4 ImGuiNavHighlightFlags_NoRounding = ImGuiNavRenderCursorFlags_NoRounding, // Renamed in 1.91.4 + //ImGuiNavHighlightFlags_TypeThin = ImGuiNavRenderCursorFlags_Compact, // Renamed in 1.90.2 #endif }; @@ -2016,6 +2081,7 @@ struct ImGuiMetricsConfig bool ShowDrawCmdMesh = true; bool ShowDrawCmdBoundingBoxes = true; bool ShowTextEncodingViewer = false; + bool ShowTextureUsedRect = false; int ShowWindowsRectsType = -1; int ShowTablesRectsType = -1; int HighlightMonitorIdx = -1; @@ -2073,15 +2139,17 @@ struct ImGuiContextHook struct ImGuiContext { bool Initialized; - bool FontAtlasOwnedByContext; // IO.Fonts-> is owned by the ImGuiContext and will be destructed along with it. ImGuiIO IO; ImGuiPlatformIO PlatformIO; ImGuiStyle Style; - ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() - float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. - float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. - float FontScale; // == FontSize / Font->FontSize - float CurrentDpiScale; // Current window/viewport DpiScale + ImVector FontAtlases; // List of font atlases used by the context (generally only contains g.IO.Fonts aka the main font atlas) + ImFont* Font; // Currently bound font. (== FontStack.back().Font) + ImFontBaked* FontBaked; // Currently bound font at currently bound size. (== Font->GetFontBaked(FontSize)) + float FontSize; // Currently bound font size == line height (== FontSizeBase + externals scales applied in the UpdateCurrentFontSize() function). + float FontSizeBase; // Font size before scaling == style.FontSizeBase == value passed to PushFont() when specified. + float FontBakedScale; // == FontBaked->Size / FontSize. Scale factor over baked size. Rarely used nowadays, very often == 1.0f. + float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). + float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; double Time; int FrameCount; @@ -2124,7 +2192,7 @@ struct ImGuiContext ImVec2 WheelingAxisAvg; // Item/widgets state and tracking information - ImGuiID DebugDrawIdConflicts; // Set when we detect multiple items with the same identifier + ImGuiID DebugDrawIdConflictsId; // Set when we detect multiple items with the same identifier ImGuiID DebugHookIdInfo; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame ImGuiID HoveredIdPreviousFrame; @@ -2182,7 +2250,7 @@ struct ImGuiContext ImGuiCol DebugFlashStyleColorIdx; // (Keep close to ColorStack to share cache line) ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() ImVector StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() - ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() + ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin() ImVector ItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() ImVector GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() @@ -2204,18 +2272,19 @@ struct ImGuiContext ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' ImGuiID NavFocusScopeId; // Focused focus scope (e.g. selection code often wants to "clear other items" when landing on an item of the same scope) ImGuiNavLayer NavLayer; // Focused layer (main scrolling layer, or menu/title bar layer) - ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem() + ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItemByID() ImGuiID NavActivateDownId; // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) ImGuiActivateFlags NavActivateFlags; ImVector NavFocusRoute; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain. ImGuiID NavHighlightActivatedId; float NavHighlightActivatedTimer; - ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. + ImGuiID NavNextActivateId; // Set by ActivateItemByID(), queued until next frame. ImGuiActivateFlags NavNextActivateFlags; ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Mouse ImGuiSelectionUserData NavLastValidSelectionUserData; // Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data. ImS8 NavCursorHideFrames; + //ImGuiID NavActivateInputId; // Removed in 1.89.4 (July 2023). This is now part of g.NavActivateId and sets g.NavActivateFlags |= ImGuiActivateFlags_PreferInput. See commit c9a53aa74, issue #5606. // Navigation: Init & Move Requests bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest this is to perform early out in ItemAdd() @@ -2329,7 +2398,8 @@ struct ImGuiContext // Widget state ImGuiInputTextState InputTextState; ImGuiInputTextDeactivatedState InputTextDeactivatedState; - ImFont InputTextPasswordFont; + ImFontBaked InputTextPasswordFontBackupBaked; + ImFontFlags InputTextPasswordFontBackupFlags; ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. ImGuiDataTypeStorage DataTypeZeroValue; // 0 for all data types int BeginMenuDepth; @@ -2364,6 +2434,10 @@ struct ImGuiContext ImGuiPlatformImeData PlatformImeData; // Data updated by current frame. Will be applied at end of the frame. For some backends, this is required to have WantVisible=true in order to receive text message. ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data. When changed we call the platform_io.Platform_SetImeDataFn() handler. + // Extensions + // FIXME: We could provide an API to register one slot in an array held in ImGuiContext? + ImVector UserTextures; // List of textures created/managed by user or third-party extension. Automatically appended into platform_io.Textures[]. + // Settings bool SettingsLoaded; float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero @@ -2421,6 +2495,10 @@ struct ImGuiContext ImGuiMetricsConfig DebugMetricsConfig; ImGuiIDStackTool DebugIDStackTool; ImGuiDebugAllocInfo DebugAllocInfo; +#if defined(IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS) && !defined(IMGUI_DISABLE_DEBUG_TOOLS) + ImGuiStorage DebugDrawIdConflictsAliveCount; + ImGuiStorage DebugDrawIdConflictsHighlightSet; +#endif // Misc float FramerateSecPerFrame[60]; // Calculate estimate of framerate for user over the last 60 frames.. @@ -2429,7 +2507,7 @@ struct ImGuiContext float FramerateSecPerFrameAccum; int WantCaptureMouseNextFrame; // Explicit capture override via SetNextFrameWantCaptureMouse()/SetNextFrameWantCaptureKeyboard(). Default to -1. int WantCaptureKeyboardNextFrame; // " - int WantTextInputNextFrame; // Copied in EndFrame() from g.PlatformImeData.WanttextInput. Needs to be set for some backends (SDL3) to emit character inputs. + int WantTextInputNextFrame; // Copied in EndFrame() from g.PlatformImeData.WantTextInput. Needs to be set for some backends (SDL3) to emit character inputs. ImVector TempBuffer; // Temporary text buffer char TempKeychordName[64]; @@ -2619,9 +2697,11 @@ struct IMGUI_API ImGuiWindow // We don't use g.FontSize because the window may be != g.CurrentWindow. ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } - float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontBaseSize * FontWindowScale * FontWindowScaleParents; } ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); } ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); } + + // [Obsolete] ImGuiWindow::CalcFontSize() was removed in 1.92.x because error-prone/misleading. You can use window->FontRefSize for a copy of g.FontSize at the time of the last Begin() call for this window. + //float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontSizeBase * FontWindowScale * FontWindowScaleParents; }; //----------------------------------------------------------------------------- @@ -2910,7 +2990,7 @@ struct IMGUI_API ImGuiTable bool IsSortSpecsDirty; bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag. bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted). - bool DisableDefaultContextMenu; // Disable default context menu contents. You may submit your own using TableBeginContextMenuPopup()/EndPopup() + bool DisableDefaultContextMenu; // Disable default context menu. You may submit your own using TableBeginContextMenuPopup()/EndPopup() bool IsSettingsRequestLoad; bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data. bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) @@ -3044,9 +3124,18 @@ namespace ImGui IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); // Fonts, drawing - IMGUI_API void SetCurrentFont(ImFont* font); - inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } + IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture. EXPERIMENTAL: DO NOT USE YET. + IMGUI_API void UnregisterUserTexture(ImTextureData* tex); + IMGUI_API void RegisterFontAtlas(ImFontAtlas* atlas); + IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas); + IMGUI_API void SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling); + IMGUI_API void UpdateCurrentFontSize(float restore_font_size_after_scaling); + IMGUI_API void SetFontRasterizerDensity(float rasterizer_density); + inline float GetFontRasterizerDensity() { return GImGui->FontRasterizerDensity; } + inline float GetRoundedFontSize(float size) { return IM_ROUND(size); } + IMGUI_API ImFont* GetDefaultFont(); IMGUI_API void PushPasswordFont(); + IMGUI_API void PopPasswordFont(); inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { IM_UNUSED(window); return GetForegroundDrawList(); } // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches. IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport); // get background draw list for the given viewport. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport); // get foreground draw list for the given viewport. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. @@ -3061,6 +3150,7 @@ namespace ImGui IMGUI_API void UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos); IMGUI_API void FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window); IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); + IMGUI_API void StopMouseMovingWindow(); IMGUI_API void UpdateMouseMovingWindowNewFrame(); IMGUI_API void UpdateMouseMovingWindowEndFrame(); @@ -3200,7 +3290,7 @@ namespace ImGui // This should be part of a larger set of API: FocusItem(offset = -1), FocusItemByID(id), ActivateItem(offset = -1), ActivateItemByID(id) etc. which are // much harder to design and implement than expected. I have a couple of private branches on this matter but it's not simple. For now implementing the easy ones. IMGUI_API void FocusItem(); // Focus last item (no selection/activation). - IMGUI_API void ActivateItemByID(ImGuiID id); // Activate an item by ID (button, checkbox, tree node etc.). Activation is queued and processed on the next frame when the item is encountered again. + IMGUI_API void ActivateItemByID(ImGuiID id); // Activate an item by ID (button, checkbox, tree node etc.). Activation is queued and processed on the next frame when the item is encountered again. Was called 'ActivateItem()' before 1.89.7. // Inputs // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. @@ -3452,7 +3542,7 @@ namespace ImGui // Widgets IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); - IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); + IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags, float thickness = 1.0f); IMGUI_API void SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_width); IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); @@ -3558,7 +3648,9 @@ namespace ImGui IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label); IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); + IMGUI_API void DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); + IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect = NULL); // ID used to facilitate persisting the "current" texture. IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); @@ -3592,30 +3684,191 @@ namespace ImGui //----------------------------------------------------------------------------- -// [SECTION] ImFontAtlas internal API +// [SECTION] ImFontLoader //----------------------------------------------------------------------------- +// Hooks and storage for a given font backend. // This structure is likely to evolve as we add support for incremental atlas updates. -// Conceptually this could be in ImGuiPlatformIO, but we are far from ready to make this public. -struct ImFontBuilderIO +// Conceptually this could be public, but API is still going to be evolve. +struct ImFontLoader { - bool (*FontBuilder_Build)(ImFontAtlas* atlas); + const char* Name; + bool (*LoaderInit)(ImFontAtlas* atlas); + void (*LoaderShutdown)(ImFontAtlas* atlas); + bool (*FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src); + void (*FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src); + bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); + bool (*FontBakedInit)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); + void (*FontBakedDestroy)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); + bool (*FontBakedLoadGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x); + + // Size of backend data, Per Baked * Per Source. Buffers are managed by core to avoid excessive allocations. + // FIXME: At this point the two other types of buffers may be managed by core to be consistent? + size_t FontBakedSrcLoaderDataSize; + + ImFontLoader() { memset(this, 0, sizeof(*this)); } }; -// Helper for font builder #ifdef IMGUI_ENABLE_STB_TRUETYPE -IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype(); +IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); +#endif +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +typedef ImFontLoader ImFontBuilderIO; // [renamed/changed in 1.92] The types are not actually compatible but we provide this as a compile-time error report helper. +#endif + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas internal API +//----------------------------------------------------------------------------- + +#define IMGUI_FONT_SIZE_MAX (512.0f) +#define IMGUI_FONT_SIZE_THRESHOLD_FOR_LOADADVANCEXONLYMODE (128.0f) + +// Helpers: ImTextureRef ==/!= operators provided as convenience +// (note that _TexID and _TexData are never set simultaneously) +inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._TexData == rhs._TexData; } +inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._TexData != rhs._TexData; } + +// Refer to ImFontAtlasPackGetRect() to better understand how this works. +#define ImFontAtlasRectId_IndexMask_ (0x000FFFFF) // 20-bits: index to access builder->RectsIndex[]. +#define ImFontAtlasRectId_GenerationMask_ (0x3FF00000) // 10-bits: entry generation, so each ID is unique and get can safely detected old identifiers. +#define ImFontAtlasRectId_GenerationShift_ (20) +inline int ImFontAtlasRectId_GetIndex(ImFontAtlasRectId id) { return id & ImFontAtlasRectId_IndexMask_; } +inline int ImFontAtlasRectId_GetGeneration(ImFontAtlasRectId id) { return (id & ImFontAtlasRectId_GenerationMask_) >> ImFontAtlasRectId_GenerationShift_; } +inline ImFontAtlasRectId ImFontAtlasRectId_Make(int index_idx, int gen_idx) { IM_ASSERT(index_idx < ImFontAtlasRectId_IndexMask_ && gen_idx < (ImFontAtlasRectId_GenerationMask_ >> ImFontAtlasRectId_GenerationShift_)); return (ImFontAtlasRectId)(index_idx | (gen_idx << ImFontAtlasRectId_GenerationShift_)); } + +// Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) +// User are returned ImFontAtlasRectId values which are meant to be persistent. +// We handle this with an indirection. While Rects[] may be in theory shuffled, compacted etc., RectsIndex[] cannot it is keyed by ImFontAtlasRectId. +// RectsIndex[] is used both as an index into Rects[] and an index into itself. This is basically a free-list. See ImFontAtlasBuildAllocRectIndexEntry() code. +// Having this also makes it easier to e.g. sort rectangles during repack. +struct ImFontAtlasRectEntry +{ + int TargetIndex : 20; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. + int Generation : 10; // Increased each time the entry is reused for a new rectangle. + unsigned int IsUsed : 1; +}; + +// Data available to potential texture post-processing functions +struct ImFontAtlasPostProcessData +{ + ImFontAtlas* FontAtlas; + ImFont* Font; + ImFontConfig* FontSrc; + ImFontBaked* FontBaked; + ImFontGlyph* Glyph; + + // Pixel data + void* Pixels; + ImTextureFormat Format; + int Pitch; + int Width; + int Height; +}; + +// We avoid dragging imstb_rectpack.h into public header (partly because binding generators are having issues with it) +#ifdef IMGUI_STB_NAMESPACE +namespace IMGUI_STB_NAMESPACE { struct stbrp_node; } +typedef IMGUI_STB_NAMESPACE::stbrp_node stbrp_node_im; +#else +struct stbrp_node; +typedef stbrp_node stbrp_node_im; +#endif +struct stbrp_context_opaque { char data[80]; }; + +// Internal storage for incrementally packing and building a ImFontAtlas +struct ImFontAtlasBuilder +{ + stbrp_context_opaque PackContext; // Actually 'stbrp_context' but we don't want to define this in the header file. + ImVector PackNodes; + ImVector Rects; + ImVector RectsIndex; // ImFontAtlasRectId -> index into Rects[] + ImVector TempBuffer; // Misc scratch buffer + int RectsIndexFreeListStart;// First unused entry + int RectsPackedCount; // Number of packed rectangles. + int RectsPackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size. + int RectsDiscardedCount; + int RectsDiscardedSurface; + int FrameCount; // Current frame count + ImVec2i MaxRectSize; // Largest rectangle to pack (de-facto used as a "minimum texture size") + ImVec2i MaxRectBounds; // Bottom-right most used pixels + bool LockDisableResize; // Disable resizing texture + bool PreloadedAllGlyphsRanges; // Set when missing ImGuiBackendFlags_RendererHasTextures features forces atlas to preload everything. + + // Cache of all ImFontBaked + ImStableVector BakedPool; + ImGuiStorage BakedMap; // BakedId --> ImFontBaked* + int BakedDiscardedCount; + + // Custom rectangle identifiers + ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. + ImFontAtlasRectId PackIdLinesTexData; + + ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } +}; + +IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildDestroy(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildMain(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader); +IMGUI_API void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char); +IMGUI_API void ImFontAtlasBuildClear(ImFontAtlas* atlas); // Clear output and custom rects + +IMGUI_API ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasTextureMakeSpace(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasTextureGrow(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); +IMGUI_API void ImFontAtlasTextureCompact(ImFontAtlas* atlas); +IMGUI_API ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas); + +IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildLegacyPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy +IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v); +IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); + +IMGUI_API bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); +IMGUI_API void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font); // Using FontDestroyOutput/FontInitOutput sequence useful notably if font loader params have changed +IMGUI_API void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font); +IMGUI_API void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames); + +IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density, ImGuiID baked_id); +IMGUI_API void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); +IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); +IMGUI_API void ImFontAtlasBakedAddFontGlyphAdvancedX(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImWchar codepoint, float advance_x); +IMGUI_API void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); +IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch); + +IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); +IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); +IMGUI_API ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); + +IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool renderer_has_textures); +IMGUI_API void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); +IMGUI_API void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); +IMGUI_API void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex); +IMGUI_API void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas); + +IMGUI_API void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data); +IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor); +IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h, ImU32 col); +IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); + +IMGUI_API int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format); +IMGUI_API const char* ImTextureDataGetStatusName(ImTextureStatus status); +IMGUI_API const char* ImTextureDataGetFormatName(ImTextureFormat format); + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +IMGUI_API void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas); #endif -IMGUI_API void ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, float ascent, float descent); -IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque); -IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value); -IMGUI_API void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value); -IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); -IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); -IMGUI_API void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); IMGUI_API bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); diff --git a/PopLib/imgui/core/imgui_tables.cpp b/PopLib/imgui/core/imgui_tables.cpp index e1813110..a1504853 100644 --- a/PopLib/imgui/core/imgui_tables.cpp +++ b/PopLib/imgui/core/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.0 WIP +// dear imgui, v1.92.2 WIP // (tables and columns code) /* @@ -1251,7 +1251,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // [Part 11] Default context menu // - To append to this menu: you can call TableBeginContextMenuPopup()/.../EndPopup(). - // - To modify or replace this: set table->IsContextPopupNoDefaultContents = true, then call TableBeginContextMenuPopup()/.../EndPopup(). + // - To modify or replace this: set table->DisableDefaultContextMenu = true, then call TableBeginContextMenuPopup()/.../EndPopup(). // - You may call TableDrawDefaultContextMenu() with selected flags to display specific sections of the default menu, // e.g. TableDrawDefaultContextMenu(table, table->Flags & ~ImGuiTableFlags_Hideable) will display everything EXCEPT columns visibility options. if (table->DisableDefaultContextMenu == false && TableBeginContextMenuPopup(table)) @@ -1825,6 +1825,11 @@ void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; IM_ASSERT(target != ImGuiTableBgTarget_None); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } if (color == IM_COL32_DISABLE) color = 0; @@ -2876,9 +2881,7 @@ ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL); - - if (!(table->Flags & ImGuiTableFlags_Sortable)) + if (table == NULL || !(table->Flags & ImGuiTableFlags_Sortable)) return NULL; // Require layout (in case TableHeadersRow() hasn't been called) as it may alter IsSortSpecsDirty in some paths. @@ -3375,7 +3378,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ButtonBehavior(row_r, row_id, NULL, NULL); KeepAliveID(row_id); - const float ascent_scaled = g.Font->Ascent * g.FontScale; // FIXME: Standardize those scaling factors better + const float ascent_scaled = g.FontBaked->Ascent * g.FontBakedScale; // FIXME: Standardize those scaling factors better const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f); const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component const ImVec2 align = g.Style.TableAngledHeadersTextAlign; diff --git a/PopLib/imgui/core/imgui_widgets.cpp b/PopLib/imgui/core/imgui_widgets.cpp index b99e6e5c..63dd9dc2 100644 --- a/PopLib/imgui/core/imgui_widgets.cpp +++ b/PopLib/imgui/core/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.0 WIP +// dear imgui, v1.92.2 WIP // (widgets code) /* @@ -548,6 +548,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags); if (flags & ImGuiButtonFlags_AllowOverlap) item_flags |= ImGuiItemFlags_AllowOverlap; + if (item_flags & ImGuiItemFlags_NoFocus) + flags |= ImGuiButtonFlags_NoFocus | ImGuiButtonFlags_NoNavFocus; // Default only reacts to left mouse button if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) @@ -571,7 +573,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool bool pressed = false; bool hovered = ItemHoverable(bb, id, item_flags); - // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button + // Special mode for Drag and Drop used by openables (tree nodes, tabs etc.) + // where holding the button pressed for a long time while drag a payload item triggers the button. if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) { @@ -623,7 +626,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool SetFocusID(id, window); FocusWindow(window); } - else + else if (!(flags & ImGuiButtonFlags_NoFocus)) { FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child } @@ -641,7 +644,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool SetFocusID(id, window); FocusWindow(window); } - else + else if (!(flags & ImGuiButtonFlags_NoFocus)) { FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child } @@ -1103,9 +1106,9 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 return held; } -// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples +// - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. -void ImGui::ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) +void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -1123,28 +1126,28 @@ void ImGui::ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, c window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); } -void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1) +void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1) { - ImageWithBg(user_texture_id, image_size, uv0, uv1); + ImageWithBg(tex_ref, image_size, uv0, uv1); } // 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) { ImGuiContext& g = *GImGui; PushStyleVar(ImGuiStyleVar_ImageBorderSize, (border_col.w > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); // Preserve legacy behavior where border is always visible when border_col's Alpha is >0.0f PushStyleColor(ImGuiCol_Border, border_col); - ImageWithBg(user_texture_id, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col); + ImageWithBg(tex_ref, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col); PopStyleColor(); PopStyleVar(); } #endif -bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +bool ImGui::ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -1166,21 +1169,21 @@ bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); return pressed; } // - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design? -bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) +bool ImGui::ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; - return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col); + return ImageButtonEx(window->GetID(str_id), tex_ref, image_size, uv0, uv1, bg_col, tint_col); } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -1518,7 +1521,7 @@ bool ImGui::TextLink(const char* label) ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z); } - float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f); + float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f); window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode // FIXME-DPI PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); @@ -3956,9 +3959,10 @@ static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line) { ImGuiContext& g = *ctx; - ImFont* font = g.Font; + //ImFont* font = g.Font; + ImFontBaked* baked = g.FontBaked; const float line_height = g.FontSize; - const float scale = line_height / font->FontSize; + const float scale = line_height / baked->Size; ImVec2 text_size = ImVec2(0, 0); float line_width = 0.0f; @@ -3984,8 +3988,7 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c if (c == '\r') continue; - const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale; - line_width += char_width; + line_width += baked->GetCharAdvance((ImWchar)c) * scale; } if (text_size.x < line_width) @@ -4012,7 +4015,7 @@ namespace ImStb { static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; } static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; } -static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.FontBaked->GetCharAdvance((ImWchar)c) * g.FontBakedScale; } static char STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) { @@ -4280,23 +4283,24 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons if (new_text == new_text_end) return; + ImGuiContext& g = *Ctx; + ImGuiInputTextState* obj = &g.InputTextState; + IM_ASSERT(obj->ID != 0 && g.ActiveId == obj->ID); + // Grow internal buffer if needed const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)ImStrlen(new_text); - if (new_text_len + BufTextLen >= BufSize) + if (new_text_len + BufTextLen + 1 > obj->TextA.Size && (Flags & ImGuiInputTextFlags_ReadOnly) == 0) { if (!is_resizable) return; - ImGuiContext& g = *Ctx; - ImGuiInputTextState* edit_state = &g.InputTextState; - IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); - IM_ASSERT(Buf == edit_state->TextA.Data); + IM_ASSERT(Buf == obj->TextA.Data); int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; - edit_state->TextA.resize(new_buf_size + 1); - edit_state->TextSrc = edit_state->TextA.Data; - Buf = edit_state->TextA.Data; - BufSize = edit_state->BufCapacity = new_buf_size; + obj->TextA.resize(new_buf_size + 1); + obj->TextSrc = obj->TextA.Data; + Buf = obj->TextA.Data; + BufSize = obj->BufCapacity = new_buf_size; } if (BufTextLen != pos) @@ -4314,18 +4318,29 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons void ImGui::PushPasswordFont() { ImGuiContext& g = *GImGui; - ImFont* in_font = g.Font; - ImFont* out_font = &g.InputTextPasswordFont; - ImFontGlyph* glyph = in_font->FindGlyph('*'); - out_font->FontSize = in_font->FontSize; - out_font->Scale = in_font->Scale; - out_font->Ascent = in_font->Ascent; - out_font->Descent = in_font->Descent; - out_font->ContainerAtlas = in_font->ContainerAtlas; - out_font->FallbackGlyph = glyph; - out_font->FallbackAdvanceX = glyph->AdvanceX; - IM_ASSERT(out_font->Glyphs.Size == 0 && out_font->IndexAdvanceX.Size == 0 && out_font->IndexLookup.Size == 0); - PushFont(out_font); + ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked; + IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0); + ImFontGlyph* glyph = g.FontBaked->FindGlyph('*'); + g.InputTextPasswordFontBackupFlags = g.Font->Flags; + backup->FallbackGlyphIndex = g.FontBaked->FallbackGlyphIndex; + backup->FallbackAdvanceX = g.FontBaked->FallbackAdvanceX; + backup->IndexLookup.swap(g.FontBaked->IndexLookup); + backup->IndexAdvanceX.swap(g.FontBaked->IndexAdvanceX); + g.Font->Flags |= ImFontFlags_NoLoadGlyphs; + g.FontBaked->FallbackGlyphIndex = g.FontBaked->Glyphs.index_from_ptr(glyph); + g.FontBaked->FallbackAdvanceX = glyph->AdvanceX; +} + +void ImGui::PopPasswordFont() +{ + ImGuiContext& g = *GImGui; + ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked; + g.Font->Flags = g.InputTextPasswordFontBackupFlags; + g.FontBaked->FallbackGlyphIndex = backup->FallbackGlyphIndex; + g.FontBaked->FallbackAdvanceX = backup->FallbackAdvanceX; + g.FontBaked->IndexLookup.swap(backup->IndexLookup); + g.FontBaked->IndexAdvanceX.swap(backup->IndexAdvanceX); + IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0); } // Return false to discard a character. @@ -5227,7 +5242,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (new_is_displaying_hint != is_displaying_hint) { if (is_password && !is_displaying_hint) - PopFont(); + PopPasswordFont(); is_displaying_hint = new_is_displaying_hint; if (is_password && !is_displaying_hint) PushPasswordFont(); @@ -5352,7 +5367,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else { ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true); - if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines + if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn)); rect.ClipWith(clip_rect); if (rect.Overlaps(clip_rect)) @@ -5418,7 +5433,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (is_password && !is_displaying_hint) - PopFont(); + PopPasswordFont(); if (is_multiline) { @@ -5470,7 +5485,7 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); DebugLocateItemOnHover(state->ID); Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->TextLen, stb_state->cursor, stb_state->select_start, stb_state->select_end); - Text("BufCapacityA: %d", state->BufCapacity); + Text("BufCapacity: %d", state->BufCapacity); Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity); Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x); Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); @@ -6690,13 +6705,12 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if ((flags & ImGuiTreeNodeFlags_DrawLinesMask_) == 0) flags |= g.Style.TreeLinesFlags; const bool draw_tree_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) && (frame_bb.Min.y < window->ClipRect.Max.y) && (g.Style.TreeLinesSize > 0.0f); - if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) { + store_tree_node_stack_data = draw_tree_lines; if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsToParent) && !g.NavIdIsAlive) if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) store_tree_node_stack_data = true; - if (draw_tree_lines) - store_tree_node_stack_data = true; } const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; @@ -6889,7 +6903,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l TablePopBackgroundChannel(); } - if (store_tree_node_stack_data && is_open) + if (is_open && store_tree_node_stack_data) TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID() if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice @@ -6904,7 +6918,7 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - if ((window->DC.TreeHasStackDataDepthMask & (1 << (window->DC.TreeDepth - 1))) == 0) + if (window->DC.TreeDepth == 0 || (window->DC.TreeHasStackDataDepthMask & (1 << (window->DC.TreeDepth - 1))) == 0) return; ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; @@ -6990,7 +7004,7 @@ void ImGui::TreePop() window->DC.TreeDepth--; ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); - if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request + if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) { const ImGuiTreeNodeStackData* data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; IM_ASSERT(data->ID == window->IDStack.back()); @@ -7006,6 +7020,7 @@ void ImGui::TreePop() g.TreeNodeStack.pop_back(); window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask; + window->DC.TreeRecordsClippedNodesY2Mask &= ~tree_depth_mask; } IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much. diff --git a/PopLib/imgui/core/imstb_truetype.h b/PopLib/imgui/core/imstb_truetype.h index 976f09cb..1a277877 100644 --- a/PopLib/imgui/core/imstb_truetype.h +++ b/PopLib/imgui/core/imstb_truetype.h @@ -4017,7 +4017,8 @@ STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int s #define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) -static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +/*static*/ +void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) { unsigned char buffer[STBTT_MAX_OVERSAMPLE]; int safe_w = w - kernel_width; @@ -4079,7 +4080,8 @@ static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_i } } -static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +/*static*/ +void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) { unsigned char buffer[STBTT_MAX_OVERSAMPLE]; int safe_h = h - kernel_width; @@ -4141,7 +4143,8 @@ static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_i } } -static float stbtt__oversample_shift(int oversample) +/*static*/ +float stbtt__oversample_shift(int oversample) { if (!oversample) return 0.0f; @@ -4516,8 +4519,8 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex q2[0] = (float)x2; q2[1] = (float)y2; if (equal(q0,q1) || equal(q1,q2)) { - x0 = (int)verts[i-1].x; - y0 = (int)verts[i-1].y; + x0 = (int)verts[i-1].x; //-V1048 + y0 = (int)verts[i-1].y; //-V1048 x1 = (int)verts[i ].x; y1 = (int)verts[i ].y; if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { diff --git a/PopLib/imgui/imguimanager.cpp b/PopLib/imgui/imguimanager.cpp index 0176ba58..540a05e6 100644 --- a/PopLib/imgui/imguimanager.cpp +++ b/PopLib/imgui/imguimanager.cpp @@ -1,7 +1,10 @@ #include "imguimanager.hpp" -#include "graphics/sdlinterface.hpp" #include "appbase.hpp" +// renderers +#include "graphics/renderer/glrenderer.hpp" +#include "graphics/renderer/sdlrenderer.hpp" + using namespace PopLib; bool demoWind = false; @@ -28,9 +31,9 @@ void RegisterImGuiWindows() //////////////////////////// -ImGuiManager::ImGuiManager(SDLInterface *theInterface) +ImGuiManager::ImGuiManager(Renderer *theInterface) { - mInterface = theInterface; + mRenderer = theInterface; IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -38,8 +41,31 @@ ImGuiManager::ImGuiManager(SDLInterface *theInterface) (void)io; // uhhhhhh ImGui::StyleColorsDark(); - ImGui_ImplSDL3_InitForSDLRenderer(mInterface->mWindow, mInterface->mRenderer); - ImGui_ImplSDLRenderer3_Init(mInterface->mRenderer); + switch (gAppBase->mRendererAPI) + { + case Renderers::SDL: + { + SDLRenderer *aInterface = (SDLRenderer*)mRenderer; + ImGui_ImplSDL3_InitForSDLRenderer(gAppBase->mWindow, aInterface->mRenderer); + ImGui_ImplSDLRenderer3_Init(aInterface->mRenderer); + break; + } + case Renderers::OpenGL: + { + GLRenderer *aInterface = (GLRenderer*)mRenderer; + ImGui_ImplSDL3_InitForOpenGL(gAppBase->mWindow, aInterface->mContext); + ImGui_ImplOpenGL3_Init(); + break; + } + default: + { + // in appbase.cpp we're using SDL as default, so do we here + SDLRenderer *aInterface = (SDLRenderer*)mRenderer; + ImGui_ImplSDL3_InitForSDLRenderer(gAppBase->mWindow, aInterface->mRenderer); + ImGui_ImplSDLRenderer3_Init(aInterface->mRenderer); + break; + } + } } void ImGuiManager::RenderAll(void) @@ -54,8 +80,28 @@ void ImGuiManager::RenderAll(void) void ImGuiManager::Frame(void) { - ImGui_ImplSDLRenderer3_NewFrame(); - ImGui_ImplSDL3_NewFrame(); + switch (gAppBase->mRendererAPI) + { + case Renderers::SDL: + { + ImGui_ImplSDLRenderer3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + break; + } + case Renderers::OpenGL: + { + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + break; + } + default: + { + // in appbase.cpp we're using SDL as default, so do we here + ImGui_ImplSDLRenderer3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + break; + } + } ImGui::NewFrame(); if (demoWind) @@ -68,7 +114,28 @@ void ImGuiManager::Frame(void) ImGuiManager::~ImGuiManager() { - ImGui_ImplSDLRenderer3_Shutdown(); - ImGui_ImplSDL3_Shutdown(); + switch (gAppBase->mRendererAPI) + { + case Renderers::SDL: + { + ImGui_ImplSDLRenderer3_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + break; + } + case Renderers::OpenGL: + { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + break; + } + default: + { + // in appbase.cpp we're using SDL as default, so do we here + ImGui_ImplSDLRenderer3_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + break; + } + } + ImGui::DestroyContext(); } \ No newline at end of file diff --git a/PopLib/imgui/imguimanager.hpp b/PopLib/imgui/imguimanager.hpp index 1bfa0d36..70c5deb5 100644 --- a/PopLib/imgui/imguimanager.hpp +++ b/PopLib/imgui/imguimanager.hpp @@ -1,12 +1,12 @@ #ifndef __IMGUIMANAGER_HPP__ #define __IMGUIMANAGER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include "core/imgui.h" #include "core/imgui_impl_sdl3.h" +#include "core/imgui_impl_opengl3.h" #include "core/imgui_impl_sdlrenderer3.h" #include @@ -14,10 +14,10 @@ namespace PopLib { -class SDLInterface; +class Renderer; /** - * @brief imgui manager for sdlinterface + * @brief imgui manager that automaticaly calls the window's functions. */ class ImGuiManager { @@ -33,7 +33,7 @@ class ImGuiManager } /// @brief constructor - ImGuiManager(SDLInterface *theInterface); + ImGuiManager(Renderer *theInterface); /// @brief destructor virtual ~ImGuiManager(); @@ -45,8 +45,8 @@ class ImGuiManager /// @param none virtual void Frame(void); - /// @brief the sdlinterface - SDLInterface *mInterface; + /// @brief the interface + Renderer *mRenderer; private: /// @brief the imgui windows list diff --git a/PopLib/math/math.hpp b/PopLib/math/math.hpp index 8754cfbb..2d5a0009 100644 --- a/PopLib/math/math.hpp +++ b/PopLib/math/math.hpp @@ -1,8 +1,7 @@ #ifndef __MATH_HPP__ #define __MATH_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include diff --git a/PopLib/math/matrix.hpp b/PopLib/math/matrix.hpp index 87f2fc0b..3ac96a76 100644 --- a/PopLib/math/matrix.hpp +++ b/PopLib/math/matrix.hpp @@ -1,8 +1,7 @@ #ifndef __MATRIX_HPP__ #define __MATRIX_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "vector.hpp" diff --git a/PopLib/math/mtrand.hpp b/PopLib/math/mtrand.hpp index 0f37aab8..03d4472b 100644 --- a/PopLib/math/mtrand.hpp +++ b/PopLib/math/mtrand.hpp @@ -1,8 +1,7 @@ #ifndef __MTRAND_HPP__ #define __MTRAND_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include #include diff --git a/PopLib/math/point.hpp b/PopLib/math/point.hpp index b7e4c1ce..31dc2b58 100644 --- a/PopLib/math/point.hpp +++ b/PopLib/math/point.hpp @@ -1,8 +1,7 @@ #ifndef __POINT_HPP__ #define __POINT_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" diff --git a/PopLib/math/ratio.hpp b/PopLib/math/ratio.hpp index b65e014a..1d0fd9ce 100644 --- a/PopLib/math/ratio.hpp +++ b/PopLib/math/ratio.hpp @@ -1,8 +1,7 @@ #ifndef __RATIO_HPP__ #define __RATIO_HPP__ -#ifdef _WIN32 + #pragma once -#endif namespace PopLib { diff --git a/PopLib/math/rect.hpp b/PopLib/math/rect.hpp index 02f8da84..d167db9f 100644 --- a/PopLib/math/rect.hpp +++ b/PopLib/math/rect.hpp @@ -1,12 +1,11 @@ #ifndef __RECT_HPP__ #define __RECT_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include "point.hpp" - +#include #include #ifndef _WIN32 @@ -57,10 +56,10 @@ template class TRect TRect<_T> Union(const TRect<_T> &theTRect) { - _T x1 = min(mX, theTRect.mX); - _T x2 = max(mX + mWidth, theTRect.mX + theTRect.mWidth); - _T y1 = min(mY, theTRect.mY); - _T y2 = max(mY + mHeight, theTRect.mY + theTRect.mHeight); + _T x1 = std::min(mX, theTRect.mX); + _T x2 = std::max(mX + mWidth, theTRect.mX + theTRect.mWidth); + _T y1 = std::min(mY, theTRect.mY); + _T y2 = std::max(mY + mHeight, theTRect.mY + theTRect.mHeight); return TRect<_T>(x1, y1, x2 - x1, y2 - y1); } diff --git a/PopLib/math/trivertex.hpp b/PopLib/math/trivertex.hpp index 8f177091..6de158d6 100644 --- a/PopLib/math/trivertex.hpp +++ b/PopLib/math/trivertex.hpp @@ -1,8 +1,9 @@ #ifndef __TRIVERTEX_HPP__ #define __TRIVERTEX_HPP__ -#ifdef _WIN32 + #pragma once -#endif + +#include namespace PopLib { diff --git a/PopLib/math/vector.hpp b/PopLib/math/vector.hpp index 3f9dd1c4..70e4f6ea 100644 --- a/PopLib/math/vector.hpp +++ b/PopLib/math/vector.hpp @@ -1,8 +1,7 @@ #ifndef __VECTOR_HPP__ #define __VECTOR_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "math.hpp" diff --git a/PopLib/misc/autocrit.hpp b/PopLib/misc/autocrit.hpp index ea175d08..220358eb 100644 --- a/PopLib/misc/autocrit.hpp +++ b/PopLib/misc/autocrit.hpp @@ -1,8 +1,7 @@ #ifndef __AUTOCRIT_HPP__ #define __AUTOCRIT_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include "critsect.hpp" diff --git a/PopLib/misc/buffer.cpp b/PopLib/misc/buffer.cpp index 70d7b6b9..f53fe4b9 100644 --- a/PopLib/misc/buffer.cpp +++ b/PopLib/misc/buffer.cpp @@ -1,5 +1,5 @@ #include "buffer.hpp" -#include "debug/debug.hpp" +#include "debug/log.hpp" #define POLYNOMIAL 0x04c11db7L @@ -447,7 +447,7 @@ int Buffer::ReadNumBits(int theBits, bool isSigned) const if (aBytePos >= aByteLength) break; - if (bset = (mData[aBytePos] & (1 << (mReadBitPos % 8))) != 0) + if ((bset = (mData[aBytePos] & (1 << (mReadBitPos % 8))) != 0)) theNum |= 1 << aBitNum; mReadBitPos++; diff --git a/PopLib/misc/buffer.hpp b/PopLib/misc/buffer.hpp index 6f54ce10..d1739d0b 100644 --- a/PopLib/misc/buffer.hpp +++ b/PopLib/misc/buffer.hpp @@ -1,8 +1,7 @@ #ifndef __BUFFER_HPP__ #define __BUFFER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include #include "common.hpp" diff --git a/PopLib/misc/critsect.hpp b/PopLib/misc/critsect.hpp index 5dcf1003..08c3a1a8 100644 --- a/PopLib/misc/critsect.hpp +++ b/PopLib/misc/critsect.hpp @@ -1,8 +1,7 @@ #ifndef __CRITSECT_HPP__ #define __CRITSECT_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include diff --git a/PopLib/misc/flags.hpp b/PopLib/misc/flags.hpp index e4499f3a..c26f7267 100644 --- a/PopLib/misc/flags.hpp +++ b/PopLib/misc/flags.hpp @@ -1,8 +1,7 @@ #ifndef __FLAGS_HPP__ #define __FLAGS_HPP__ -#ifdef _WIN32 + #pragma once -#endif namespace PopLib { diff --git a/PopLib/misc/httptransfer.cpp b/PopLib/misc/httptransfer.cpp index 624d5afa..46235dfd 100644 --- a/PopLib/misc/httptransfer.cpp +++ b/PopLib/misc/httptransfer.cpp @@ -1,4 +1,5 @@ #include "httptransfer.hpp" +#include "debug/log.hpp" #include "appbase.hpp" using namespace PopLib; @@ -114,7 +115,7 @@ void HTTPTransfer::GetHelper(const std::string &theURL) CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { - SDL_Log("CURL error: %s (%d)", curl_easy_strerror(res), (int)res); + LOG_ERROR("CURL error: %s (%d)", curl_easy_strerror(res), (int)res); Fail(RESULT_HTTP_ERROR); } else diff --git a/PopLib/misc/httptransfer.hpp b/PopLib/misc/httptransfer.hpp index 11d38717..2f85569b 100644 --- a/PopLib/misc/httptransfer.hpp +++ b/PopLib/misc/httptransfer.hpp @@ -1,10 +1,11 @@ #ifndef __HTTPTRANSFER_HPP__ #define __HTTPTRANSFER_HPP__ -#ifdef _WIN32 + #pragma once -#endif +#ifndef CURL_STATICLIB #define CURL_STATICLIB +#endif #include "common.hpp" #include "curl/curl.h" diff --git a/PopLib/misc/keycodes.hpp b/PopLib/misc/keycodes.hpp index faedf217..462cda36 100644 --- a/PopLib/misc/keycodes.hpp +++ b/PopLib/misc/keycodes.hpp @@ -1,8 +1,7 @@ #ifndef __KEYCODES_HPP__ #define __KEYCODES_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include diff --git a/PopLib/misc/profile.hpp b/PopLib/misc/profile.hpp index 646c1209..c0414d0f 100644 --- a/PopLib/misc/profile.hpp +++ b/PopLib/misc/profile.hpp @@ -1,8 +1,7 @@ #ifndef __PROFILE_HPP__ #define __PROFILE_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include diff --git a/PopLib/misc/smartptr.hpp b/PopLib/misc/smartptr.hpp index 777cefda..9b356570 100644 --- a/PopLib/misc/smartptr.hpp +++ b/PopLib/misc/smartptr.hpp @@ -1,8 +1,7 @@ #ifndef __SMARTPTR_HPP__ #define __SMARTPTR_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" diff --git a/PopLib/misc/workerthread.hpp b/PopLib/misc/workerthread.hpp index 7954f781..044bc05a 100644 --- a/PopLib/misc/workerthread.hpp +++ b/PopLib/misc/workerthread.hpp @@ -1,8 +1,7 @@ #ifndef __WORKERTHREAD_HPP__ #define __WORKERTHREAD_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include diff --git a/PopLib/paklib/gpak.hpp b/PopLib/paklib/gpak.hpp index b6da10f9..9bd9add7 100644 --- a/PopLib/paklib/gpak.hpp +++ b/PopLib/paklib/gpak.hpp @@ -1,14 +1,12 @@ #ifndef __GPAK_HPP__ #define __GPAK_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include #include #include - /** * @brief GPAK header */ diff --git a/PopLib/paklib/pakinterface.cpp b/PopLib/paklib/pakinterface.cpp index 1a4e1223..7a990cfe 100644 --- a/PopLib/paklib/pakinterface.cpp +++ b/PopLib/paklib/pakinterface.cpp @@ -10,7 +10,6 @@ extern "C" #include } - using namespace std; PakInterface *gPakInterface = new PakInterface(); @@ -108,32 +107,33 @@ bool PakInterface::AddPakFile(const string &fileName) fread(collection.data(), 1, fileSize, fp); fclose(fp); - //Check for the GPAK in the file header. If it's not there, it's not a valid GPAK file + // check for gpak header GPAKHeader *gpakHeader = reinterpret_cast(collection.data()); if (memcmp(gpakHeader->magic, "GPAK", 4) != 0 || gpakHeader->version != 1) - return false; + return false; - GPAKFileEntry *entriesPtr = reinterpret_cast(reinterpret_cast(collection.data()) + gpakHeader->fileTableOffset); + GPAKFileEntry *entriesPtr = + reinterpret_cast(reinterpret_cast(collection.data()) + gpakHeader->fileTableOffset); std::vector entries(entriesPtr, entriesPtr + gpakHeader->fileCount); - // the decompressed buffer to fill up. + // the decompressed buffer to fill up std::vector finalBuffer; finalBuffer.reserve(fileSize); - //Size of the header for recalculating the start pos + // Size of the header for recalculating the start pos size_t headerSize = gpakHeader->fileTableOffset + gpakHeader->fileCount * sizeof(GPAKFileEntry); - for (const GPAKFileEntry& entry : entries) + for (const GPAKFileEntry &entry : entries) { std::string upperName = toupper(std::string(entry.path)); - PakRecord& rec = mPakRecordMap[upperName]; + PakRecord &rec = mPakRecordMap[upperName]; rec.mCollection = &collection; rec.mFileName = entry.path; rec.mSize = entry.originalSize; rec.mFileTime = filesystem::file_time_type::min(); // GPAK doesn't store this yet - //Decompress and Decrypt the GPAK data. - const uint8_t* compressedData = collection.data() + entry.dataOffset; + // Decompress and Decrypt the GPAK data. + const uint8_t *compressedData = collection.data() + entry.dataOffset; std::vector compressed(compressedData, compressedData + entry.compressedSize); if (!gDecryptPassword.empty()) compressed = AESDecrypt(compressed, gDecryptPassword); @@ -144,7 +144,7 @@ bool PakInterface::AddPakFile(const string &fileName) finalBuffer.insert(finalBuffer.end(), decompressed.begin(), decompressed.end()); } - //Move the readable data into the collection for fread to use + // move the readable data into the collection for fread to use collection.vector() = std::move(finalBuffer); return true; @@ -212,9 +212,9 @@ size_t PakInterface::FRead(void *buf, int size, int count, PFILE *pf) { if (pf->mRecord) { - PakRecord* rec = pf->mRecord; + PakRecord *rec = pf->mRecord; - int aSizeBytes = std::min(size*count, static_cast(pf->mRecord->mSize - pf->mPos)); + int aSizeBytes = std::min(size * count, static_cast(pf->mRecord->mSize - pf->mPos)); std::memcpy(buf, rec->mCollection->data() + rec->mStartPos + pf->mPos, aSizeBytes); diff --git a/PopLib/paklib/pakinterface.hpp b/PopLib/paklib/pakinterface.hpp index 2437224e..c1a94f4b 100644 --- a/PopLib/paklib/pakinterface.hpp +++ b/PopLib/paklib/pakinterface.hpp @@ -1,8 +1,7 @@ #ifndef __PAKINTERFACE_HPP__ #define __PAKINTERFACE_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include #include @@ -32,33 +31,34 @@ typedef std::map PakRecordMap; class PakCollection { public: - explicit PakCollection(std::size_t size) - : mData(size) {} - - uint8_t* data() - { - return mData.data(); - } - - const uint8_t* data() const - { - return mData.data(); - } - - std::size_t size() const - { - return mData.size(); - } - - std::vector& vector() - { - return mData; - } - - const std::vector& vector() const - { - return mData; - } + explicit PakCollection(std::size_t size) : mData(size) + { + } + + uint8_t *data() + { + return mData.data(); + } + + const uint8_t *data() const + { + return mData.data(); + } + + std::size_t size() const + { + return mData.size(); + } + + std::vector &vector() + { + return mData; + } + + const std::vector &vector() const + { + return mData; + } private: std::vector mData; @@ -163,7 +163,7 @@ class PakInterface : public PakInterfaceBase PakInterface(); ~PakInterface(); - virtual bool AddPakFile(const std::string &fileName); + virtual bool AddPakFile(const std::string &fileName) override; PFILE *FOpen(const char *fn, const char *mode) override; int FClose(PFILE *pf) override; diff --git a/PopLib/physics/physics.cpp b/PopLib/physics/physics.cpp new file mode 100644 index 00000000..24af07fa --- /dev/null +++ b/PopLib/physics/physics.cpp @@ -0,0 +1,1043 @@ +/* Sexy Chipmunk, a physics engine for the PopCap Games Framework using Scott Lembcke's excellent chipmunk physics + * library */ +/* Copyright (c) 2007-2008 W.P. van Paassen + * + * 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. + */ + +#include "physics.hpp" +#include +#include +#include +#include +#include +#include +#include "graphics/graphics.hpp" + +/* TODO + * inline + * copy constructor, assignment operator + */ + +using namespace PopLib; + +Physics::Physics() : space(NULL), steps(1), delta(0.0f), objects(), body_to_object(), joints(), listener(NULL) +{ + cpInitChipmunk(); +} + +Physics::~Physics() +{ + Clear(); + cpSpaceFreeChildren(space); + cpSpaceFree(space); + space = NULL; +} + +void Physics::Init() +{ + if (space == NULL) + { + cpResetShapeIdCounter(); + space = cpSpaceNew(); + assert(space != NULL); + + cpSpaceSetDefaultCollisionPairFunc(space, NULL, NULL); + + space->gravity = cpv(0, 100); + + delta = 1.0f / 60.0f / (cpFloat)steps; + } +} + +void Physics::SetSteps(int steps) +{ + this->steps = steps; + delta = 1.0f / 60.0f / (cpFloat)steps; +} + +int Physics::CollFunc(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) +{ + assert(data != NULL); + + TypedData *t_data = reinterpret_cast(data); + + PhysicsObject *obj1 = t_data->physics->FindObject(a->body, a); + PhysicsObject *obj2 = t_data->physics->FindObject(b->body, b); + + assert(obj1 != NULL); + assert(obj2 != NULL); + assert(sizeof(CollisionPoint) == sizeof(cpContact)); + + CollisionObject col(obj1, obj2, reinterpret_cast(contacts), numContacts, normal_coef); + + return t_data->physics->listener->HandleTypedCollision(&col); +} + +Vector2 Physics::SumCollisionImpulses(int numContacts, CollisionPoint *contacts) +{ + assert(sizeof(CollisionPoint) == sizeof(cpContact)); + cpVect sum_impulse = cpContactsSumImpulses((cpContact *)contacts, numContacts); + return Vector2(sum_impulse.x, sum_impulse.y); +} + +Vector2 Physics::SumCollisionImpulsesWithFriction(int numContacts, CollisionPoint *contacts) +{ + assert(sizeof(CollisionPoint) == sizeof(cpContact)); + cpVect sum_impulse = cpContactsSumImpulsesWithFriction(reinterpret_cast(contacts), numContacts); + return Vector2(sum_impulse.x, sum_impulse.y); +} + +void Physics::AllCollisions(void *ptr, void *data) +{ + assert(data != NULL && ptr != NULL); + + cpArbiter *arb = reinterpret_cast(ptr); + + Physics *p = reinterpret_cast(data); + + PhysicsObject *obj1 = p->FindObject(arb->a->body, arb->a); + PhysicsObject *obj2 = p->FindObject(arb->b->body, arb->b); + + assert(obj1 != NULL); + assert(obj2 != NULL); + assert(sizeof(CollisionPoint) == sizeof(cpContact)); + + CollisionObject col(obj1, obj2, reinterpret_cast(arb->contacts), arb->numContacts); + + p->listener->HandleCollision(&col); +} + +void Physics::HashQuery(void *ptr, void *data) +{ + assert(ptr != NULL && data != NULL); + + TypedData *t_data = reinterpret_cast(data); + + cpShape *shape = reinterpret_cast(ptr); + PhysicsObject *obj = t_data->physics->FindObject(shape); + assert(obj != NULL); + + t_data->physics->listener->DrawPhysicsObject(obj, t_data->graphics); +} + +void Physics::Update() +{ + { + assert(listener != NULL); + for (int i = 0; i < steps; i++) + { + listener->BeforePhysicsStep(); + cpSpaceStep(space, delta); + listener->AfterPhysicsStep(); + + cpArrayEach(space->arbiters, &AllCollisions, this); + } + } +} + +void Physics::Draw(Graphics *g) +{ + TypedData data; + data.graphics = g; + data.physics = this; + + cpSpaceHashEach(space->activeShapes, &HashQuery, reinterpret_cast(&data)); + cpSpaceHashEach(space->staticShapes, &HashQuery, reinterpret_cast(&data)); +} + +void Physics::SetGravity(const Vector2 &gravity) +{ + assert(space != NULL); + space->gravity = cpv(gravity.x, gravity.y); +} + +void Physics::SetDamping(cpFloat damping) +{ + assert(space != NULL); + space->damping = damping; +} + +void Physics::SetIterations(int iter) +{ + assert(space != NULL); + space->iterations = iter; +} + +void Physics::ResizeStaticHash(float dimension, int count) +{ + assert(space != NULL); + cpSpaceResizeStaticHash(space, dimension, count); +} + +void Physics::ResizeActiveHash(float dimension, int count) +{ + assert(space != NULL); + cpSpaceResizeActiveHash(space, dimension, count); +} + +void Physics::Clear() +{ + std::vector::iterator it = objects.begin(); + while (it != objects.end()) + { + // The false means it won't be removed from the vector. It would mess up our iteratiom, I guess. + DestroyObject(*it, false); + ++it; + } + + body_to_object.clear(); + objects.clear(); + joints.clear(); +} + +PhysicsObject *Physics::CreateObject(cpFloat mass, cpFloat inertia) +{ + assert(IsInitialized()); + PhysicsObject *obj = new PhysicsObject(mass, inertia, this); + objects.push_back(obj); + cpBody *body = obj->GetBody(); + if (body) + { + assert(!findObjectByBody(body)); + body_to_object[body] = obj; + } + return obj; +} + +PhysicsObject *Physics::CreateStaticObject() +{ + PhysicsObject *obj = new PhysicsObject(INFINITY, INFINITY, this, true); + objects.push_back(obj); + cpBody *body = obj->GetBody(); + if (body) + { + assert(!findObjectByBody(body)); + body_to_object[body] = obj; + } + return obj; +} + +void Physics::DestroyObject(PhysicsObject *object, bool erase) +{ + assert(IsValidObject(object)); + + if (!object->shapes.empty()) + { + if (object->is_static) + { + std::vector::iterator it = object->shapes.begin(); + while (it != object->shapes.end()) + { + cpSpaceRemoveStaticShape(space, *it); + ++it; + } + } + else + { + std::vector::iterator it = object->shapes.begin(); + while (it != object->shapes.end()) + { + cpSpaceRemoveShape(space, *it); + ++it; + } + } + } + + if (erase) + { + // Let's assume the body_to_object will be deleted soon, by the caller. + std::map::iterator it = body_to_object.find(object->body); + if (it != body_to_object.end()) + { + body_to_object.erase(it); + } + } + + if (!object->is_static) + { + cpSpaceRemoveBody(space, object->body); + } + else + { + cpSpaceRehashStatic(space); + } + + std::vector j = GetJointsOfObject(object); + std::vector::iterator it = j.begin(); + while (it != j.end()) + { + RemoveJoint(*it); + ++it; + } + + cpBodyFree(object->body); + std::vector::iterator sit = object->shapes.begin(); + while (sit != object->shapes.end()) + { + cpShapeFree(*sit); + ++sit; + } + + if (erase) + { + // If it is part of the "objects", delete and remove it. + std::vector::iterator pit = std::find(objects.begin(), objects.end(), object); + if (pit != objects.end()) + { + delete (*pit); + objects.erase(pit); + } + } + // ???? Doing this will cause a crash in ~WP_Sprite() object->body = NULL; +} + +bool Physics::IsValidObject(PhysicsObject *object) const +{ + std::vector::const_iterator it = std::find(objects.begin(), objects.end(), object); + return (it != objects.end()); +} + +void Physics::RegisterCollisionType(uint32_t type_a, uint32_t type_b) +{ + TypedData *data = new TypedData; + data->graphics = NULL; // ???? no graphics, hmmm + data->physics = this; + cpSpaceAddCollisionPairFunc(space, type_a, type_b, (cpCollFunc)&CollFunc, reinterpret_cast(data)); +} + +void Physics::UnregisterCollisionType(uint32_t type_a, uint32_t type_b) +{ + unsigned int ids[] = {type_a, type_b}; + unsigned int hash = CP_HASH_PAIR(type_a, type_b); + cpCollPairFunc *old_pair = static_cast(cpHashSetFind(space->collFuncSet, hash, ids)); + delete reinterpret_cast(old_pair->data); + cpSpaceRemoveCollisionPairFunc(space, type_a, type_b); +} + +void Physics::ApplySpringForce(PhysicsObject *obj1, PhysicsObject *obj2, const Vector2 &anchor1, const Vector2 &anchor2, + float rest_length, float spring, float damping) +{ + cpDampedSpring(obj1->body, obj2->body, cpv(anchor1.x, anchor1.y), cpv(anchor2.x, anchor2.y), rest_length, spring, + damping, delta); +} + +Joint Physics::CreatePinJoint(const PhysicsObject *obj1, const PhysicsObject *obj2, const Vector2 &anchor1, + const Vector2 &anchor2) +{ + cpJoint *joint = cpPinJointNew(obj1->body, obj2->body, cpv(anchor1.x, anchor1.y), cpv(anchor2.x, anchor2.y)); + joints.push_back(joint); + cpSpaceAddJoint(space, joint); + return Joint(joint, const_cast(obj1), const_cast(obj2), anchor1, anchor2); +} + +Joint Physics::CreateSlideJoint(const PhysicsObject *obj1, const PhysicsObject *obj2, const Vector2 &anchor1, + const Vector2 &anchor2, float min, float max) +{ + cpJoint *joint = + cpSlideJointNew(obj1->body, obj2->body, cpv(anchor1.x, anchor1.y), cpv(anchor2.x, anchor2.y), min, max); + joints.push_back(joint); + cpSpaceAddJoint(space, joint); + return Joint(joint, const_cast(obj1), const_cast(obj2), anchor1, anchor2); +} + +Joint Physics::CreatePivotJoint(const PhysicsObject *obj1, const PhysicsObject *obj2, const Vector2 &pivot) +{ + cpJoint *joint = cpPivotJointNew(obj1->body, obj2->body, cpv(pivot.x, pivot.y)); + joints.push_back(joint); + cpSpaceAddJoint(space, joint); + return Joint(joint, const_cast(obj1), const_cast(obj2), pivot); +} + +std::vector> Physics::GetJoints() const +{ + std::vector::const_iterator it = joints.begin(); + std::vector> v; + + while (it != joints.end()) + { + cpVect pos1 = (*it)->a->p; + cpVect pos2 = (*it)->b->p; + cpVect s, e, v1, v2; + switch ((*it)->type) + { + case CP_PIN_JOINT: + v1 = ((cpPinJoint *)(*it))->anchr1; + s = cpvadd(pos1, cpvrotate((*it)->a->rot, v1)); + v2 = ((cpPinJoint *)(*it))->anchr2; + e = cpvadd(pos2, cpvrotate((*it)->b->rot, v2)); + break; + case CP_SLIDE_JOINT: + v1 = ((cpSlideJoint *)(*it))->anchr1; + s = cpvadd(pos1, cpvrotate((*it)->a->rot, v1)); + v2 = ((cpSlideJoint *)(*it))->anchr2; + e = cpvadd(pos2, cpvrotate((*it)->b->rot, v2)); + break; + case CP_PIVOT_JOINT: + v1 = ((cpPivotJoint *)(*it))->anchr1; + s = pos1; // cpvadd(pos1, cpvrotate(v1, (*it)->a->rot)); + v2 = ((cpPivotJoint *)(*it))->anchr2; + e = pos2; // cpvadd(pos2, cpvrotate(v2, (*it)->b->rot)); + break; + case CP_GROOVE_JOINT: + // TODO. What to do with these? + assert(0); + break; + case CP_NUM_JOINTS: + default: + // This should never happen. + assert(0); + break; + } + AddUniqueJoint(&v, Vector2(s.x, s.y), Vector2(e.x, e.y)); + ++it; + } + return v; +} + +std::vector> Physics::GetJoints(const PhysicsObject *obj1, const PhysicsObject *obj2) const +{ + + const std::vector j = GetJointsOfObject(obj1); + std::vector> v; + + std::vector::const_iterator it = j.begin(); + while (it != j.end()) + { + if (((*it)->a == obj1->body || (*it)->b == obj1->body) && ((*it)->a == obj2->body || (*it)->b == obj2->body)) + { + + Vector2 start = obj1->GetPosition(); + Vector2 end = obj2->GetPosition(); + cpVect v1, v2; + + switch ((*it)->type) + { + case CP_PIN_JOINT: + v1 = ((cpPinJoint *)(*it))->anchr1; + v2 = ((cpPinJoint *)(*it))->anchr2; + if ((*it)->a == obj1->body) + { + cpVect rot = cpvrotate((*it)->a->rot, v1); + start += Vector2(rot.x, rot.y); + rot = cpvrotate((*it)->b->rot, v2); + end += Vector2(rot.x, rot.y); + } + else + { + cpVect rot = cpvrotate((*it)->b->rot, v1); + start += Vector2(rot.x, rot.y); + rot = cpvrotate((*it)->a->rot, v2); + end += Vector2(rot.x, rot.y); + } + break; + case CP_SLIDE_JOINT: + v1 = ((cpSlideJoint *)(*it))->anchr1; + v2 = ((cpSlideJoint *)(*it))->anchr2; + if ((*it)->a == obj1->body) + { + cpVect rot = cpvrotate((*it)->a->rot, v1); + start += Vector2(rot.x, rot.y); + rot = cpvrotate((*it)->b->rot, v2); + end += Vector2(rot.x, rot.y); + } + else + { + cpVect rot = cpvrotate((*it)->b->rot, v1); + start += Vector2(rot.x, rot.y); + rot = cpvrotate((*it)->a->rot, v2); + end += Vector2(rot.x, rot.y); + } + break; + case CP_PIVOT_JOINT: + // ???? Why skip these? + break; + case CP_GROOVE_JOINT: + // TODO. What to do with these? + assert(0); + break; + case CP_NUM_JOINTS: + default: + // This should never happen. + assert(0); + break; + } + AddUniqueJoint(&v, start, end); + } + ++it; + } + return v; +} + +std::vector> Physics::GetJoints(const PhysicsObject *obj1) const +{ + + const std::vector j = GetJointsOfObject(obj1); + std::vector> v; + + std::vector::const_iterator it = j.begin(); + while (it != j.end()) + { + Vector2 start = obj1->GetPosition(); + Vector2 end; + if ((*it)->a == obj1->body) + { + cpVect v = (*it)->b->p; + end = Vector2(v.x, v.y); + } + else + { + cpVect v = (*it)->a->p; + end = Vector2(v.x, v.y); + } + cpVect v1, v2; + switch ((*it)->type) + { + case CP_PIN_JOINT: + v1 = ((cpPinJoint *)(*it))->anchr1; + v2 = ((cpPinJoint *)(*it))->anchr2; + + if ((*it)->a == obj1->body) + { + cpVect rot = cpvrotate((*it)->a->rot, v1); + start += Vector2(rot.x, rot.y); + rot = cpvrotate((*it)->b->rot, v2); + end += Vector2(rot.x, rot.y); + } + else + { + cpVect rot = cpvrotate((*it)->b->rot, v1); + start += Vector2(rot.x, rot.y); + rot = cpvrotate((*it)->a->rot, v2); + end += Vector2(rot.x, rot.y); + } + break; + case CP_SLIDE_JOINT: + v1 = ((cpSlideJoint *)(*it))->anchr1; + v2 = ((cpSlideJoint *)(*it))->anchr2; + if ((*it)->a == obj1->body) + { + cpVect rot = cpvrotate((*it)->a->rot, v1); + start += Vector2(rot.x, rot.y); + rot = cpvrotate((*it)->b->rot, v2); + end += Vector2(rot.x, rot.y); + } + else + { + cpVect rot = cpvrotate((*it)->b->rot, v1); + start += Vector2(rot.x, rot.y); + rot = cpvrotate((*it)->a->rot, v2); + end += Vector2(rot.x, rot.y); + } + break; + case CP_PIVOT_JOINT: + // ???? + break; + case CP_GROOVE_JOINT: + // TODO. What to do with these? + assert(0); + break; + case CP_NUM_JOINTS: + default: + // This should never happen. + assert(0); + break; + } + AddUniqueJoint(&v, start, end); + ++it; + } + return v; +} + +void Physics::AddUniqueJoint(std::vector> *v, const Vector2 &start, + const Vector2 &end) const +{ + std::vector>::iterator vit = v->begin(); + while (vit != v->end()) + { + if (((*vit).first == start && (*vit).second == end) || ((*vit).first == end && (*vit).second == start)) + { + return; + } + ++vit; + } + v->push_back(std::make_pair(start, end)); +} + +bool Physics::IsJoined(const PhysicsObject *obj1, const PhysicsObject *obj2) const +{ + std::vector::const_iterator it = joints.begin(); + while (it != joints.end()) + { + if (((*it)->a == obj1->body || (*it)->b == obj1->body) && ((*it)->a == obj2->body || (*it)->b == obj2->body)) + { + return true; + } + ++it; + } + return false; +} + +void Physics::RemoveJoint(const PhysicsObject *obj1, const PhysicsObject *obj2) +{ + assert(obj1->body != NULL && obj2->body != NULL); + std::vector::iterator it = joints.begin(); + while (it != joints.end()) + { + if (((*it)->a == obj1->body || (*it)->b == obj1->body) && ((*it)->a == obj2->body || (*it)->b == obj2->body)) + { + + cpSpaceRemoveJoint(space, *it); + joints.erase(it); + } + else + ++it; + } +} + +void Physics::RemoveJoints(const PhysicsObject *obj) +{ + std::vector joints = GetJointsOfObject(obj); + std::vector::const_iterator it = joints.begin(); + while (it != joints.end()) + { + RemoveJoint(*it); + ++it; + } +} + +void Physics::RemoveJoint(const Joint &joint) +{ + RemoveJoint(joint.joint); +} + +void Physics::RemoveJoint(const cpJoint *joint) +{ + std::vector::iterator it = std::find(joints.begin(), joints.end(), joint); + if (it != joints.end()) + { + cpSpaceRemoveJoint(space, *it); + joints.erase(it); + } +} + +const std::vector Physics::GetJointsOfObject(const PhysicsObject *obj) const +{ + assert(obj->body != NULL); + + std::vector j; + + std::vector::const_iterator it = joints.begin(); + while (it != joints.end()) + { + if ((*it)->a == obj->body || (*it)->b == obj->body) + { + j.push_back(*it); + } + ++it; + } + return j; +} + +PhysicsObject *Physics::findObjectByBody(cpBody *body) const +{ + if (!body) + { + return NULL; + } + std::map::const_iterator it = body_to_object.find(body); + if (it != body_to_object.end()) + { + return it->second; + } + return NULL; +} + +// Find object with this body and this shape +PhysicsObject *Physics::FindObject(cpBody *body, cpShape *shape) +{ + PhysicsObject *obj = findObjectByBody(body); + if (obj) + { + std::vector::const_iterator sit = obj->shapes.begin(); + int count = 0; + while (sit != obj->shapes.end()) + { + if (*sit == shape) + { + obj->colliding_shape_index = count; + return obj; + } + ++count; + ++sit; + } + } + return NULL; +} + +// Find object with this shape +PhysicsObject *Physics::FindObject(cpShape *shape) +{ + // TODO. Use shape_to_obj map, if at all possible + std::vector::const_iterator it = objects.begin(); + while (it != objects.end()) + { + std::vector::const_iterator sit = (*it)->shapes.begin(); + int count = 0; + while (sit != (*it)->shapes.end()) + { + if (*sit == shape) + { + (*it)->colliding_shape_index = count; + return *it; + } + ++count; + ++sit; + } + ++it; + } + return NULL; +} + +std::set Physics::GetJoinedPhysicsObjects(const PhysicsObject *obj1) const +{ + const std::vector j = GetJointsOfObject(obj1); + std::set v; + + std::vector::const_iterator it = j.begin(); + while (it != j.end()) + { + cpBody *body; + if ((*it)->a == obj1->body) + { + body = (*it)->b; + } + else + { + body = (*it)->a; + } + PhysicsObject *obj = findObjectByBody(body); + if (obj) + { + v.insert(obj); + // ???? Can we find this body in more joints? + } + ++it; + } + return v; +} + +/***********************************************PhysicsObject**************************/ + +PhysicsObject::PhysicsObject(cpFloat mass, cpFloat inertia, Physics *physics, bool is_static) + : physics(physics), colliding_shape_index(0), is_static(is_static) +{ + assert(physics != NULL); + body = cpBodyNew(mass, inertia); + if (!is_static) + { + assert(physics->space != NULL); + cpSpaceAddBody(physics->space, body); + } + shapes.clear(); +} + +PhysicsObject::~PhysicsObject() +{ +} + +void PhysicsObject::AddCircleShape(cpFloat radius, const Vector2 &offset, cpFloat elasticity, cpFloat friction) +{ + assert(body != NULL); + cpShape *shape = cpCircleShapeNew(body, radius, cpv(offset.x, offset.y)); + assert(shape != NULL); + shape->e = elasticity; + shape->u = friction; + if (physics->space != NULL) + { + if (is_static) + cpSpaceAddStaticShape(physics->space, shape); + else + cpSpaceAddShape(physics->space, shape); + } + shapes.push_back(shape); +} + +void PhysicsObject::AddSegmentShape(const Vector2 &begin, const Vector2 &end, cpFloat radius, cpFloat elasticity, + cpFloat friction) +{ + assert(body != NULL); + cpShape *shape = cpSegmentShapeNew(body, cpv(begin.x, begin.y), cpv(end.x, end.y), radius); + assert(shape != NULL); + shape->e = elasticity; + shape->u = friction; + if (physics->space != NULL) + { + if (is_static) + cpSpaceAddStaticShape(physics->space, shape); + else + cpSpaceAddShape(physics->space, shape); + } + shapes.push_back(shape); +} + +void PhysicsObject::AddPolyShape(int numVerts, Vector2 *vectors, const Vector2 &offset, cpFloat elasticity, + cpFloat friction) +{ + assert(body != NULL); + assert(sizeof(Vector2) == sizeof(cpVect)); + + cpShape *shape = cpPolyShapeNew(body, numVerts, (cpVect *)vectors, cpv(offset.x, offset.y)); + assert(shape != NULL); + shape->e = elasticity; + shape->u = friction; + if (physics->space != NULL) + { + if (is_static) + cpSpaceAddStaticShape(physics->space, shape); + else + cpSpaceAddShape(physics->space, shape); + } + shapes.push_back(shape); +} + +void PhysicsObject::RemoveShape(int shape_index) +{ + assert((int)shapes.size() > shape_index); + + std::vector::iterator it = shapes.begin(); + int count = 0; + while (it != shapes.end()) + { + if (shape_index == count) + { + if (is_static) + { + cpSpaceRemoveStaticShape(physics->space, *it); + cpSpaceRehashStatic(physics->space); + } + else + { + cpSpaceRemoveShape(physics->space, *it); + cpSpaceHashRehash(physics->space->activeShapes); + } + shapes.erase(it); + return; + } + count++; + ++it; + } +} + +float PhysicsObject::GetFriction(int shape_index) const +{ + assert((int)shapes.size() > shape_index); + return shapes[shape_index]->u; +} + +float PhysicsObject::GetElasticity(int shape_index) const +{ + assert((int)shapes.size() > shape_index); + return shapes[shape_index]->e; +} + +void PhysicsObject::SetAngularVelocity(cpFloat w) +{ + assert(body != NULL); + body->w = w; +} + +void PhysicsObject::SetVelocity(const Vector2 &v) +{ + assert(body != NULL); + body->v = cpv(v.x, v.y); +} + +void PhysicsObject::SetCollisionType(unsigned int type, int shape_index) +{ + assert((int)shapes.size() > shape_index); + shapes[shape_index]->collision_type = type; +} + +void PhysicsObject::SetGroup(unsigned int group, int shape_index) +{ + assert((int)shapes.size() > shape_index); + shapes[shape_index]->group = group; +} + +void PhysicsObject::SetLayers(unsigned int layers, int shape_index) +{ + assert((int)shapes.size() > shape_index); + shapes[shape_index]->layers = layers; +} + +void PhysicsObject::SetData(void *data, int shape_index) +{ + assert((int)shapes.size() > shape_index); + shapes[shape_index]->data = data; +} + +unsigned int PhysicsObject::GetCollisionType(int shape_index) const +{ + assert((int)shapes.size() > shape_index); + return shapes[shape_index]->collision_type; +} + +unsigned int PhysicsObject::GetGroup(int shape_index) const +{ + assert((int)shapes.size() > shape_index); + return shapes[shape_index]->group; +} + +unsigned int PhysicsObject::GetLayers(int shape_index) const +{ + assert((int)shapes.size() > shape_index); + return shapes[shape_index]->layers; +} + +void *PhysicsObject::GetData(int shape_index) const +{ + assert((int)shapes.size() > shape_index); + return shapes[shape_index]->data; +} + +int PhysicsObject::GetShapeType(int shape_index) const +{ + assert((int)shapes.size() > shape_index); + return shapes[shape_index]->type; +} + +int PhysicsObject::GetNumberVertices(int shape_index) const +{ + assert((int)shapes.size() > shape_index && shapes[shape_index]->type == (cpShapeType)POLY_SHAPE); + return ((cpPolyShape *)shapes[shape_index])->numVerts; +} + +Vector2 PhysicsObject::GetVertex(int vertex_index, int shape_index) const +{ + assert((int)shapes.size() > shape_index && shapes[shape_index]->type == (cpShapeType)POLY_SHAPE); + cpVect position = cpvadd(body->p, cpvrotate(((cpPolyShape *)shapes[shape_index])->verts[vertex_index], body->rot)); + return Vector2(position.x, position.y); +} + +Vector2 PhysicsObject::GetSegmentShapeBegin(int shape_index) const +{ + assert((int)shapes.size() > shape_index && shapes[shape_index]->type == (cpShapeType)SEGMENT_SHAPE); + cpVect position = cpvadd(body->p, cpvrotate(((cpSegmentShape *)shapes[shape_index])->a, body->rot)); + return Vector2(position.x, position.y); +} + +Vector2 PhysicsObject::GetSegmentShapeEnd(int shape_index) const +{ + assert((int)shapes.size() > shape_index && shapes[shape_index]->type == (cpShapeType)SEGMENT_SHAPE); + cpVect position = cpvadd(body->p, cpvrotate(((cpSegmentShape *)shapes[shape_index])->b, body->rot)); + return Vector2(position.x, position.y); +} + +float PhysicsObject::GetSegmentShapeRadius(int shape_index) const +{ + assert((int)shapes.size() > shape_index && shapes[shape_index]->type == (cpShapeType)SEGMENT_SHAPE); + return (float)((cpSegmentShape *)shapes[shape_index])->r; +} + +float PhysicsObject::GetCircleShapeRadius(int shape_index) const +{ + assert((int)shapes.size() > shape_index && shapes[shape_index]->type == (cpShapeType)CIRCLE_SHAPE); + return (float)((cpCircleShape *)shapes[shape_index])->r; +} + +Vector2 PhysicsObject::GetCircleShapeCenter(int shape_index) const +{ + assert((int)shapes.size() > shape_index && shapes[shape_index]->type == (cpShapeType)CIRCLE_SHAPE); + cpVect position = cpvadd(body->p, cpvrotate(((cpCircleShape *)shapes[shape_index])->c, body->rot)); + return Vector2(position.x, position.y); +} + +Vector2 PhysicsObject::GetCircleShapeOffset(int shape_index) const +{ + assert((int)shapes.size() > shape_index && shapes[shape_index]->type == (cpShapeType)CIRCLE_SHAPE); + cpVect position = ((cpCircleShape *)shapes[shape_index])->c; + return Vector2(position.x, position.y); +} + +void PhysicsObject::UpdateVelocity() +{ + assert(is_static && physics->space != NULL); + cpBodyUpdateVelocity(body, physics->space->gravity, physics->space->damping, physics->delta); +} + +void PhysicsObject::UpdatePosition() +{ + assert(is_static && physics->space != NULL); + cpBodyUpdatePosition(body, physics->delta); + cpSpaceRehashStatic(physics->space); +} + +int PhysicsObject::GetNumberOfShapes() const +{ + return shapes.size(); +} + +int PhysicsObject::GetCollidingShapeIndex() const +{ + return colliding_shape_index; +} + +std::pair Joint::GetPoints() const +{ + std::pair v; + + cpVect pos1 = joint->a->p; + cpVect pos2 = joint->b->p; + cpVect s, e, v1, v2; + switch (joint->type) + { + case CP_PIN_JOINT: + v1 = ((cpPinJoint *)(joint))->anchr1; + s = cpvadd(pos1, cpvrotate((joint)->a->rot, v1)); + v2 = ((cpPinJoint *)(joint))->anchr2; + e = cpvadd(pos2, cpvrotate((joint)->b->rot, v2)); + break; + case CP_SLIDE_JOINT: + v1 = ((cpSlideJoint *)(joint))->anchr1; + s = cpvadd(pos1, cpvrotate((joint)->a->rot, v1)); + v2 = ((cpSlideJoint *)(joint))->anchr2; + e = cpvadd(pos2, cpvrotate((joint)->b->rot, v2)); + break; + case CP_PIVOT_JOINT: + v1 = ((cpPivotJoint *)(joint))->anchr1; + s = pos1; // cpvadd(pos1, cpvrotate(v1, (joint)->a->rot)); + v2 = ((cpPivotJoint *)(joint))->anchr2; + e = pos2; // cpvadd(pos2, cpvrotate(v2, (joint)->b->rot)); + break; + case CP_GROOVE_JOINT: + // TODO. What to do with these? + assert(0); + break; + case CP_NUM_JOINTS: + default: + // This should never happen. + assert(0); + break; + } + + v.first = Vector2(s.x, s.y); + v.second = Vector2(e.x, e.y); + return v; +} \ No newline at end of file diff --git a/PopLib/physics/physics.hpp b/PopLib/physics/physics.hpp new file mode 100644 index 00000000..e02e50d5 --- /dev/null +++ b/PopLib/physics/physics.hpp @@ -0,0 +1,416 @@ +/* Sexy Chipmunk, a physics engine for the PopCap Games Framework using Scott Lembcke's excellent chipmunk physics + * library */ +/* Copyright (c) 2007-2008 W.P. van Paassen + * + * 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. + */ + +#ifndef __PHYSICS_HPP__ +#define __PHYSICS_HPP__ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "chipmunk/chipmunk.h" +#include "physicslistener.hpp" +#include "math/vector.hpp" + +namespace PopLib +{ +class CollisionPoint; +class PhysicsObject; +class Joint; + +class Physics +{ + friend class PhysicsObject; + + public: + Physics(); + ~Physics(); + + void Init(); + + bool IsInitialized() + { + return space != 0; + } + + void SetGravity(const Vector2 &gravity); + void SetDamping(cpFloat damping); + void SetIterations(int iter); + void ResizeStaticHash(float dimension, int count); + void ResizeActiveHash(float dimension, int count); + + void Update(); + void Draw(Graphics *g); + void Clear(); + + void SetSteps(int steps); + + void SetDelta(float delta) + { + this->delta = delta; + } + + PhysicsObject *CreateObject(cpFloat mass, cpFloat inertia); + PhysicsObject *CreateStaticObject(); + void DestroyObject(PhysicsObject *object, bool erase = true); + bool IsValidObject(PhysicsObject *object) const; + + void SetPhysicsListener(PhysicsListener *p) + { + listener = p; + } + + void RegisterCollisionType(uint32_t type_a, uint32_t type_b = 0); + void UnregisterCollisionType(uint32_t type_a, uint32_t type_b = 0); + + std::vector &GetPhysicsObjects() + { + return objects; + } + + // help functions + + void ApplySpringForce(PhysicsObject *obj1, PhysicsObject *obj2, const Vector2 &anchor1, const Vector2 &anchor2, + float rest_length, float spring, float damping); + + Joint CreatePinJoint(const PhysicsObject *obj1, const PhysicsObject *obj2, const Vector2 &anchor1, + const Vector2 &anchor2); + Joint CreateSlideJoint(const PhysicsObject *obj1, const PhysicsObject *obj2, const Vector2 &anchor1, + const Vector2 &anchor2, float min, float max); + Joint CreatePivotJoint(const PhysicsObject *obj1, const PhysicsObject *obj2, const Vector2 &pivot); + void RemoveJoint(const Joint &joint); + void RemoveJoint(const PhysicsObject *obj1, const PhysicsObject *obj2); + void RemoveJoints(const PhysicsObject *obj); + bool IsJoined(const PhysicsObject *obj1, const PhysicsObject *obj2) const; + std::vector> GetJoints(const PhysicsObject *obj1, const PhysicsObject *obj2) const; + std::vector> GetJoints(const PhysicsObject *obj1) const; + std::vector> GetJoints() const; + std::set GetJoinedPhysicsObjects(const PhysicsObject *obj1) const; + + static cpFloat ComputeMomentForPoly(cpFloat moment, int numVerts, Vector2 *vectors, const Vector2 &offset) + { + return cpMomentForPoly(moment, numVerts, (cpVect *)vectors, cpv(offset.x, offset.y)); + } + + static cpFloat ComputeMomentForCircle(cpFloat moment, cpFloat r1, cpFloat r2, const Vector2 &offset) + { + return cpMomentForCircle(moment, r1, r2, cpv(offset.x, offset.y)); + } + + static Vector2 RotateVector(const Vector2 &v1, const Vector2 &v2) + { + cpVect r = cpvrotate(cpv(v1.x, v1.y), cpv(v2.x, v2.y)); + return Vector2(r.x, r.y); + } + + static Vector2 SumCollisionImpulses(int numContacts, CollisionPoint *contacts); + static Vector2 SumCollisionImpulsesWithFriction(int numContacts, CollisionPoint *contacts); + + private: + cpSpace *space; + int steps; + cpFloat delta; + std::vector objects; + std::map body_to_object; + std::vector joints; + PhysicsListener *listener; + + void AddUniqueJoint(std::vector> *v, const Vector2 &start, const Vector2 &end) const; + const std::vector GetJointsOfObject(const PhysicsObject *obj) const; + void RemoveJoint(const cpJoint *joint); + + static void AllCollisions(void *ptr, void *data); + static void HashQuery(void *ptr, void *data); + static int CollFunc(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data); + PhysicsObject *findObjectByBody(cpBody *body) const; + PhysicsObject *FindObject(cpBody *body, cpShape *shape); + PhysicsObject *FindObject(cpShape *shape); + + typedef struct typed_data + { + Graphics *graphics; + Physics *physics; + } TypedData; +}; + +class PhysicsObject +{ + private: + explicit PhysicsObject(cpFloat mass, cpFloat inertia, Physics *physics, bool is_static = false); + ~PhysicsObject(); + + friend class Physics; + friend class CollisionObject; + + cpBody *body; + std::vector shapes; + Physics *physics; + int colliding_shape_index; + + public: + bool is_static; + + // body functions + + void SetMass(cpFloat m) + { + cpBodySetMass(body, m); + } + + void SetMoment(cpFloat i) + { + cpBodySetMoment(body, i); + } + + void SetAngle(cpFloat a) + { + cpBodySetAngle(body, a); + } + + void ResetForces() + { + cpBodyResetForces(body); + } + void SetAngularVelocity(cpFloat w); + void SetVelocity(const Vector2 &v); + + void SetPosition(const Vector2 &p) + { + body->p = cpv(p.x, p.y); + } + void UpdatePosition(); + void UpdateVelocity(); + + void ApplyImpulse(const Vector2 &j, const Vector2 &r) + { + cpBodyApplyImpulse(body, cpv(j.x, j.y), cpv(r.x, r.y)); + } + + void ApplyForce(const Vector2 &f, const Vector2 &r) + { + cpBodyApplyForce(body, cpv(f.x, f.y), cpv(r.x, r.y)); + } + cpBody *GetBody() const + { + return body; + } + float GetAngle() const + { + return (float)body->a; + } + Vector2 GetRotation() const + { + return Vector2(body->rot.x, body->rot.y); + } + Vector2 GetPosition() const + { + return Vector2(body->p.x, body->p.y); + } + float GetPosX() const + { + return body->p.x; + } + float GetPosY() const + { + return body->p.y; + } + Vector2 GetVelocity() const + { + return Vector2(body->v.x, body->v.y); + } + float GetVeloX() const + { + return body->v.x; + } + float GetVeloY() const + { + return body->v.y; + } + + // shape functions + + void AddCircleShape(cpFloat radius, const Vector2 &offset, cpFloat elasticity, cpFloat friction); + void AddSegmentShape(const Vector2 &begin, const Vector2 &end, cpFloat radius, cpFloat elasticity, + cpFloat friction); + void AddPolyShape(int numVerts, Vector2 *vectors, const Vector2 &offset, cpFloat elasticity, cpFloat friction); + void SetCollisionType(unsigned int type, int shape_index = 0); + void SetGroup(unsigned int group, int shape_index = 0); + void SetLayers(unsigned int layers, int shape_index = 0); + void SetData(void *data, int shape_index = 0); + unsigned int GetCollisionType(int shape_index = 0) const; + unsigned int GetGroup(int shape_index = 0) const; + unsigned int GetLayers(int shape_index = 0) const; + void *GetData(int shape_index = 0) const; + int GetNumberVertices(int shape_index = 0) const; + Vector2 GetVertex(int vertex_index, int shape_index = 0) const; + Vector2 GetSegmentShapeBegin(int shape_index = 0) const; + Vector2 GetSegmentShapeEnd(int shape_index = 0) const; + float GetSegmentShapeRadius(int shape_index = 0) const; + float GetCircleShapeRadius(int shape_index = 0) const; + Vector2 GetCircleShapeCenter(int shape_index = 0) const; + Vector2 GetCircleShapeOffset(int shape_index = 0) const; + int GetShapeType(int shape_index = 0) const; + int GetNumberOfShapes() const; + int GetCollidingShapeIndex() const; + void RemoveShape(int shape_index = 0); + float GetFriction(int shape_index = 0) const; + float GetElasticity(int shape_index = 0) const; + + enum SHAPE_TYPE + { + CIRCLE_SHAPE = CP_CIRCLE_SHAPE, + SEGMENT_SHAPE, + POLY_SHAPE, + NR_SHAPE_TYPES + }; +}; + +class CollisionPoint +{ + public: + CollisionPoint(const Vector2 &point, const Vector2 &normal, float distance) + : point(point), normal(normal), distance(distance) + { + } + + ~CollisionPoint() + { + } + + Vector2 point; + Vector2 normal; + float distance; + + // Calculated by cpArbiterPreStep(). + Vector2 r1, r2; + float nMass, tMass, bounce; + // Persistant contact information. + float jnAcc, jtAcc, jBias; + float bias; + + // Hash value used to (mostly) uniquely identify a contact. + uint32_t hash; +}; + +class CollisionObject +{ + public: + CollisionObject(PhysicsObject *object1, PhysicsObject *object2, const CollisionPoint *points, int num_points, + float normal_coef = 1.0f) + : object1(object1), object2(object2), points(points), num_points(num_points), normal_coef(normal_coef) + { + } + + ~CollisionObject() + { + } + + PhysicsObject *object1; + PhysicsObject *object2; + const CollisionPoint *points; + int num_points; + float normal_coef; +}; + +class Joint +{ + private: + Joint(cpJoint *joint, PhysicsObject *obj1, PhysicsObject *obj2, const Vector2 &anchor1, const Vector2 &anchor2) + : joint(joint), object1(obj1), object2(obj2), anchor1(anchor1), anchor2(anchor2) + { + } + + Joint(cpJoint *joint, PhysicsObject *obj1, PhysicsObject *obj2, const Vector2 &pivot) + : joint(joint), object1(obj1), object2(obj2), pivot(pivot) + { + } + + friend class Physics; + + cpJoint *joint; + PhysicsObject *object1; + PhysicsObject *object2; + Vector2 anchor1; + Vector2 anchor2; + Vector2 pivot; + + public: + Joint() + { + } + ~Joint() + { + } + + const PhysicsObject *GetPhysicsObject1() + { + return object1; + } + + const PhysicsObject *GetPhysicsObject2() + { + return object2; + } + + const Vector2 *GetAnchor1() + { + if (joint->type == CP_PIN_JOINT || joint->type == CP_SLIDE_JOINT) + return &anchor1; + return NULL; + } + + const Vector2 *GetAnchor2() + { + if (joint->type == CP_PIN_JOINT || joint->type == CP_SLIDE_JOINT) + return &anchor2; + return NULL; + } + + const Vector2 *GetPivot() + { + if (joint->type == CP_PIVOT_JOINT) + return &pivot; + return NULL; + } + + float GetMinOfSlide() const + { + assert(joint->type == CP_SLIDE_JOINT); + return ((cpSlideJoint *)joint)->min; + } + + float GetMaxOfSlide() const + { + assert(joint->type == CP_SLIDE_JOINT); + return ((cpSlideJoint *)joint)->max; + } + std::pair GetPoints() const; +}; + +} // namespace PopLib + +#endif \ No newline at end of file diff --git a/PopLib/physics/physicslistener.hpp b/PopLib/physics/physicslistener.hpp new file mode 100644 index 00000000..d49657e8 --- /dev/null +++ b/PopLib/physics/physicslistener.hpp @@ -0,0 +1,63 @@ +/* Sexy Chipmunk, a physics engine for the PopCap Games Framework using Scott Lembcke's excellent chipmunk physics + * library */ +/* Copyright (c) 2007 W.P. van Paassen + * + * 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. + */ + +#ifndef __PHYSICSLISTENER_HPP__ +#define __PHYSICSLISTENER_HPP__ + +#pragma once + +namespace PopLib +{ + +class PhysicsObject; +class CollisionObject; +class Graphics; + +class PhysicsListener +{ + public: + virtual ~PhysicsListener() + { + } + + virtual void DrawPhysicsObject(PhysicsObject *object, Graphics *g) + { + } + virtual void HandleCollision(CollisionObject *col) + { + } + virtual bool HandleTypedCollision(CollisionObject *col) + { + return true; + } + virtual void BeforePhysicsStep() + { + } + virtual void AfterPhysicsStep() + { + } +}; + +}; // namespace PopLib + +#endif diff --git a/PopLib/popapp.cpp b/PopLib/popapp.cpp index 1db1d267..424d8a08 100644 --- a/PopLib/popapp.cpp +++ b/PopLib/popapp.cpp @@ -1,34 +1,36 @@ #include "popapp.hpp" -#include "api/discord.hpp" #include "math/mtrand.hpp" #include -#include "debug/debug.hpp" +#include "debug/log.hpp" #include #include "common.hpp" #include #include "readwrite/jsonparser.hpp" #include +#ifdef POP_FEATURE_DISCORD_RPC +#include "api/discord.hpp" +#endif +#ifdef POP_FEATURE_STEAM_API +#include "api/steam.hpp" +#endif namespace fs = std::filesystem; - using namespace PopLib; -PopApp* PopLib::gPopApp = nullptr; +PopApp *PopLib::gPopApp = nullptr; // Groups of 80-byte data -const char DYNAMIC_DATA_BLOCK[400] = - "DYN00000PACPOPPOPCAPPACPOPPOPCAPBUILDINFOMARKERPACPOPPOPCAPPACPOPPOPCAPXXXXXXXXX" - "00000000PACPOPPOPCAPPACPOPPOPCAPBUILDINFOMARKERPACPOPPOPCAPPACPOPPOPCAPXXXXXXXXX"; - -const char* BUILD_INFO_MARKER = DYNAMIC_DATA_BLOCK + 80; -const char* SIGNATURE_CODE_MARKER = DYNAMIC_DATA_BLOCK + 80*2; -const char* BETA_ID_MARKER = DYNAMIC_DATA_BLOCK + 80*3; - - +const char DYNAMIC_DATA_BLOCK[400] = "DYN00000PACPOPPOPCAPPACPOPPOPCAPBUILDINFOMARKERPACPOPPOPCAPPACPOPPOPCAPXXXXXXXXX" + "00000000PACPOPPOPCAPPACPOPPOPCAPBUILDINFOMARKERPACPOPPOPCAPPACPOPPOPCAPXXXXXXXXX"; + +const char *BUILD_INFO_MARKER = DYNAMIC_DATA_BLOCK + 80; +const char *SIGNATURE_CODE_MARKER = DYNAMIC_DATA_BLOCK + 80 * 2; +const char *BETA_ID_MARKER = DYNAMIC_DATA_BLOCK + 80 * 3; + PopApp::PopApp() { - gPopApp = this; + gPopApp = this; mTimesPlayed = 0; mTimesExecuted = 0; @@ -43,59 +45,76 @@ PopApp::PopApp() mLastVerCheckQueryTime = 0; mCompanyName = "PLACEHOLDER"; - mFullCompanyName= "PLACEHOLDER"; + mFullCompanyName = "PLACEHOLDER"; mBetaValidate = true; char aStr[9] = {0}; strncpy(aStr, BUILD_INFO_MARKER, 8); mBuildNum = atoi(aStr); - if (mBuildNum != 0) + if (mBuildNum != 0) mBuildDate = BUILD_INFO_MARKER + 8; - #ifdef _FEATURE_DISCORD_RPC - mRPCAppID = "1369297870456488057"; - mDiscordRPC = nullptr; +#ifdef POP_FEATURE_DISCORD_RPC + mRPCAppID = "1369297870456488057"; + mDiscordRPC = nullptr; +#endif + +#ifdef POP_FEATURE_STEAM_API + mSteamAPI = nullptr; + + #ifdef _DEBUG + mSteamAppID = "480"; + #else + mSteamAppID = "0"; #endif +#endif } - + PopApp::~PopApp() { - #ifdef _FEATURE_DISCORD_RPC - if (mDiscordRPC) - delete mDiscordRPC; - #endif +#ifdef POP_FEATURE_DISCORD_RPC + if (mDiscordRPC) + delete mDiscordRPC; +#endif } - void PopApp::Init() { AppBase::Init(); - if (IsScreenSaver()) - mSkipAd = true; + if (IsScreenSaver()) + mSkipAd = true; mTimesExecuted++; } void PopApp::InitHook() { - #if _FEATURE_DISCORD_RPC - InitDiscordRPC(); - #endif +#ifdef POP_FEATURE_DISCORD_RPC + InitDiscordRPC(); +#endif +#ifdef POP_FEATURE_STEAM_API + InitSteamAPI(); +#endif } void PopApp::UpdateFrames() { AppBase::UpdateFrames(); - #ifdef _FEATURE_DISCORD_RPC - if (mDiscordRPC && mDiscordRPC->mHasInitialized) - mDiscordRPC->UpdateRPC(); - #endif +#ifdef POP_FEATURE_DISCORD_RPC + if (mDiscordRPC && mDiscordRPC->mHasInitialized) + mDiscordRPC->UpdateRPC(); +#endif + +#ifdef POP_FEATURE_STEAM_API + if (mSteamAPI) + mSteamAPI->RunCallbacks(); +#endif } -bool PopApp::CheckSignature(const Buffer& theBuffer, const std::string& theFileName) +bool PopApp::CheckSignature(const Buffer &theBuffer, const std::string &theFileName) { #ifdef _DEBUG // Don't check signatures on debug version because it's annoying and the build number @@ -119,7 +138,7 @@ bool PopApp::CheckSignature(const Buffer& theBuffer, const std::string& theFileN char* aFileData = new char[theBuffer.GetDataLen()+4]; int aFileDataPos = 0; - + char aStr[9] = {0}; strncpy(aStr, SIGNATURE_CODE_MARKER, 8); int aSignatureCode = atoi(aStr); @@ -136,15 +155,15 @@ bool PopApp::CheckSignature(const Buffer& theBuffer, const std::string& theFileN fread(&c, 1, 1, aFP); if (!::isspace(c)) aFileData[aFileDataPos++] = c; - } + } // Public RSA stuff BigInt n("D99BC76AB7B2578738E606F7"); BigInt e("11"); - + BigInt aHash = HashData(aFileData, aFileDataPos, 94); delete aFileData; - + BigInt aSignature(aSigStr); BigInt aHashTest = aSignature.ModPow(e, n); @@ -166,22 +185,21 @@ void PopApp::InitPropertiesHook() } mProdName = GetString("ProdName", mProdName); - mIsWindowed = GetBoolean("DefaultWindowed", mIsWindowed); + mIsWindowed = GetBoolean("DefaultWindowed", mIsWindowed); PopString aNewTitle = GetString("Title", ""); if (aNewTitle.length() > 0) - mTitle = aNewTitle + " " + mProductVersion; - + mTitle = aNewTitle + " " + mProductVersion; } -bool PopApp::Validate(const std::string& theUserName, const std::string& theRegCode) +bool PopApp::Validate(const std::string &theUserName, const std::string &theRegCode) { /*BigInt n("42BF94023BBA6D040C8B81D9"); BigInt e("11"); ulong i; std::string aDataString; - bool space = false; + bool space = false; for (i = 0; i < theUserName.size(); i++) { if (theUserName[i] == ' ') @@ -213,8 +231,8 @@ bool PopApp::Validate(const std::string& theUserName, const std::string& theRegC aDataString += "\n"; aDataString += aProduct; - BigInt aHash = HashString(aDataString, 94); - + BigInt aHash = HashString(aDataString, 94); + BigInt aSignature = KeyToInt(theRegCode); BigInt aHashTest = aSignature.ModPow(e, n); @@ -222,66 +240,63 @@ bool PopApp::Validate(const std::string& theUserName, const std::string& theRegC return true; } - void PopApp::WriteToRegistry() { AppBase::WriteToRegistry(); - std::string jsonFileName = GetAppDataFolder() + "popcinfo.json"; + std::string jsonFileName = GetAppDataFolder() + "popcinfo.json"; - JsonParser parser; + JsonParser parser; - bool loaded = parser.OpenFile(jsonFileName); - nlohmann::json &jData = parser.mJson; + bool loaded = parser.OpenFile(jsonFileName); + nlohmann::json &jData = parser.mJson; - if (!loaded) - { - // Failed to load or file doesn't exist: start fresh JSON - jData = nlohmann::json::object(); - } + if (!loaded) + { + // Failed to load or file doesn't exist: start fresh JSON + jData = nlohmann::json::object(); + } - if (!jData.contains("products") || !jData["products"].is_array()) - { - jData["products"] = nlohmann::json::array(); - } + if (!jData.contains("products") || !jData["products"].is_array()) + { + jData["products"] = nlohmann::json::array(); + } - bool productFound = false; - for (auto& product : jData["products"]) - { - if (product.contains("name") && product["name"].is_string() && product["name"] == mProdName) - { - product["timesPlayed"] = mTimesPlayed; - product["timesExecuted"] = mTimesExecuted; - productFound = true; - break; - } - } + bool productFound = false; + for (auto &product : jData["products"]) + { + if (product.contains("name") && product["name"].is_string() && product["name"] == mProdName) + { + product["timesPlayed"] = mTimesPlayed; + product["timesExecuted"] = mTimesExecuted; + productFound = true; + break; + } + } - if (!productFound) - { - nlohmann::json newProduct; - newProduct["name"] = mProdName; - newProduct["timesPlayed"] = mTimesPlayed; - newProduct["timesExecuted"] = mTimesExecuted; - jData["products"].push_back(newProduct); - } + if (!productFound) + { + nlohmann::json newProduct; + newProduct["name"] = mProdName; + newProduct["timesPlayed"] = mTimesPlayed; + newProduct["timesExecuted"] = mTimesExecuted; + jData["products"].push_back(newProduct); + } - // Write updated JSON back to file - std::ofstream outFile(jsonFileName, std::ios::trunc | std::ios::binary); - if (outFile.is_open()) - { - outFile << jData.dump(4); // pretty print - outFile.close(); - } + // Write updated JSON back to file + std::ofstream outFile(jsonFileName, std::ios::trunc | std::ios::binary); + if (outFile.is_open()) + { + outFile << jData.dump(4); // pretty print + outFile.close(); + } RegistryWriteInteger("LastVerCheckQueryTime", mLastVerCheckQueryTime); RegistryWriteInteger("TimesPlayed", mTimesPlayed); RegistryWriteInteger("TimesExecuted", mTimesExecuted); // This is for "compatibility" - if ((mRegUserName.length() == 0) && - (mUserName.length() > 0) && - (mRegCode.length() > 0)) + if ((mRegUserName.length() == 0) && (mUserName.length() > 0) && (mRegCode.length() > 0)) mRegUserName = mUserName; if (mRegUserName.length() > 0) @@ -295,41 +310,41 @@ void PopApp::ReadFromRegistry() { AppBase::ReadFromRegistry(); - mTimesPlayed = 0; + mTimesPlayed = 0; mTimesExecuted = 0; - std::string jsonFileName = GetAppDataFolder() + "popcinfo.json"; + std::string jsonFileName = GetAppDataFolder() + "popcinfo.json"; - JsonParser parser; + JsonParser parser; - if (parser.OpenFile(jsonFileName)) - { - nlohmann::json &jData = parser.mJson; - - if (jData.contains("products") && jData["products"].is_array()) - { - const std::string prodNameStr = mProdName; // assuming std::string - - for (const auto& product : jData["products"]) - { - if (product.contains("name") && product["name"].is_string() && product["name"] == prodNameStr) - { - if (product.contains("timesPlayed") && product["timesPlayed"].is_number_integer()) - mTimesPlayed = product["timesPlayed"].get(); - - if (product.contains("timesExecuted") && product["timesExecuted"].is_number_integer()) - mTimesExecuted = product["timesExecuted"].get(); - - break; - } - } - } - } - // if file does not exist or parse failed, keep default values (0) + if (parser.OpenFile(jsonFileName)) + { + nlohmann::json &jData = parser.mJson; + + if (jData.contains("products") && jData["products"].is_array()) + { + const std::string prodNameStr = mProdName; // assuming std::string + + for (const auto &product : jData["products"]) + { + if (product.contains("name") && product["name"].is_string() && product["name"] == prodNameStr) + { + if (product.contains("timesPlayed") && product["timesPlayed"].is_number_integer()) + mTimesPlayed = product["timesPlayed"].get(); + + if (product.contains("timesExecuted") && product["timesExecuted"].is_number_integer()) + mTimesExecuted = product["timesExecuted"].get(); + + break; + } + } + } + } + // if file does not exist or parse failed, keep default values (0) RegistryReadString("ReferId", &mReferId); mReferId = GetString("ReferId", mReferId); - mRegisterLink = "http://www.popcap.com/register.php?theGame=" + mProdName + "&referid=" + mReferId; + mRegisterLink = "http://www.popcap.com/register.php?theGame=" + mProdName + "&referid=" + mReferId; RegistryReadString("RegisterLink", &mRegisterLink); int anInt; @@ -344,8 +359,8 @@ void PopApp::ReadFromRegistry() if (RegistryReadInteger("TimesPlayed", &anInt)) { - if (mTimesPlayed != anInt) - mTimesPlayed = 100; + if (mTimesPlayed != anInt) + mTimesPlayed = 100; } if (RegistryReadInteger("TimesExecuted", &anInt)) @@ -353,7 +368,7 @@ void PopApp::ReadFromRegistry() if (mTimesExecuted != anInt) mTimesExecuted = 100; } - + if (RegistryReadInteger("LastVerCheckQueryTime", &anInt)) { mLastVerCheckQueryTime = anInt; @@ -367,39 +382,47 @@ void PopApp::ReadFromRegistry() } RegistryReadString("RegName", &mRegUserName); - RegistryReadString("RegCode", &mRegCode); + RegistryReadString("RegCode", &mRegCode); - mIsRegistered |= Validate(mRegUserName, mRegCode); + mIsRegistered |= Validate(mRegUserName, mRegCode); // Override registry values with partner.xml values mRegisterLink = GetString("RegisterLink", mRegisterLink); mDontUpdate = GetBoolean("DontUpdate", mDontUpdate); } -#ifdef _FEATURE_DISCORD_RPC +#ifdef POP_FEATURE_DISCORD_RPC void PopApp::InitDiscordRPC() { - if (mDiscordRPC) - delete mDiscordRPC; + if (mDiscordRPC) + delete mDiscordRPC; + + mDiscordRPC = new DiscordRPC(mRPCAppID.c_str()); +} +#endif - mDiscordRPC = new DiscordRPC(mRPCAppID.c_str()); +#ifdef POP_FEATURE_STEAM_API +void PopApp::InitSteamAPI() +{ + if (mSteamAPI) + delete mSteamAPI; + mSteamAPI = new SteamAPI(); + if (!mSteamAPI->Init(mSteamAppID)) + LOG_ERROR("Failed to initialize Steam API with AppID: %s", mSteamAppID.c_str()); } #endif -void PopApp::HandleCmdLineParam(const std::string& theParamName, const std::string& theParamValue) +void PopApp::HandleCmdLineParam(const std::string &theParamName, const std::string &theParamValue) { if (theParamName == "-version") { // Just print version info and then quit - - std::string aVersionString = - "Product: " + mProdName + "\n" + - "Version: " + mProductVersion + "\n" + - "Build Num: " + StrFormat("%d", mBuildNum) + "\n" + - "Build Date: " + mBuildDate; - - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Version Info", aVersionString.c_str(), NULL); + + std::string aVersionString = "Product: " + mProdName + "\n" + "Version: " + mProductVersion + "\n" + + "Build Num: " + StrFormat("%d", mBuildNum) + "\n" + "Build Date: " + mBuildDate; + + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Version Info", aVersionString.c_str(), NULL); DoExit(0); } else @@ -411,37 +434,48 @@ void PopApp::OpenUpdateURL() Shutdown(); } -bool PopApp::OpenHTMLTemplate(const std::string& theTemplateFile, const DefinesMap& theDefinesMap) +void PopApp::ShutdownHook() +{ +#ifdef POP_FEATURE_STEAM_API + if (mSteamAPI) + { + mSteamAPI->Shutdown(); + delete mSteamAPI; + mSteamAPI = nullptr; + } +#endif +} + +bool PopApp::OpenHTMLTemplate(const std::string &theTemplateFile, const DefinesMap &theDefinesMap) { std::fstream anInStream(theTemplateFile.c_str(), std::ios::in); if (!anInStream.is_open()) return false; - std::filesystem::path tempDir = "temp"; - if (fs::exists(tempDir) && fs::is_directory(tempDir)) - { - for (auto& entry : fs::directory_iterator(tempDir)) - { - if (entry.is_regular_file()) - { - std::string filename = entry.path().filename().string(); - // Check if filename matches tpl*.html pattern - if (filename.size() >= 8 && - filename.substr(0, 3) == "tpl" && - filename.substr(filename.size() - 5) == ".html") - { - fs::remove(entry.path()); - } - } - } - } + std::filesystem::path tempDir = "temp"; + if (fs::exists(tempDir) && fs::is_directory(tempDir)) + { + for (auto &entry : fs::directory_iterator(tempDir)) + { + if (entry.is_regular_file()) + { + std::string filename = entry.path().filename().string(); + // Check if filename matches tpl*.html pattern + if (filename.size() >= 8 && filename.substr(0, 3) == "tpl" && + filename.substr(filename.size() - 5) == ".html") + { + fs::remove(entry.path()); + } + } + } + } MkDir("temp"); - std::string anOutFilename = StrFormat("temp\\tpl%04d.html", PopLib::Rand()%10000); + std::string anOutFilename = StrFormat("temp\\tpl%04d.html", PopLib::Rand() % 10000); - //TODO: A better failover case? + // TODO: A better failover case? std::fstream anOutStream(anOutFilename.c_str(), std::ios::out); if (!anOutStream.is_open()) return false; @@ -450,20 +484,20 @@ bool PopApp::OpenHTMLTemplate(const std::string& theTemplateFile, const DefinesM while (!anInStream.eof()) { anInStream.getline(aStr, 4096); - + std::string aNewString = Evaluate(aStr, theDefinesMap); anOutStream << aNewString.c_str() << std::endl; } - + return OpenURL(GetFullPath(anOutFilename)); } bool PopApp::OpenRegisterPage(DefinesMap theStatsMap) { - // Insert standard defines + // Insert standard defines DefinesMap aDefinesMap; - + aDefinesMap.insert(DefinesMap::value_type("Src", mRegSource)); aDefinesMap.insert(DefinesMap::value_type("ProdName", mProdName)); aDefinesMap.insert(DefinesMap::value_type("Version", mProductVersion)); @@ -474,7 +508,7 @@ bool PopApp::OpenRegisterPage(DefinesMap theStatsMap) aDefinesMap.insert(DefinesMap::value_type("TimesExecuted", StrFormat("%d", mTimesExecuted).c_str())); aDefinesMap.insert(DefinesMap::value_type("TimedOut", mTimedOut ? "Y" : "N")); - // Insert game specific stats + // Insert game specific stats std::string aStatsString; DefinesMap::iterator anItr = theStatsMap.begin(); while (anItr != theStatsMap.end()) @@ -482,10 +516,8 @@ bool PopApp::OpenRegisterPage(DefinesMap theStatsMap) std::string aKeyString = anItr->first; std::string aValueString = anItr->second; - aStatsString += - StrFormat("%04X", aKeyString.length()).c_str() + aKeyString + - "S" + - StrFormat("%04X", aValueString.length()).c_str() + aValueString; + aStatsString += StrFormat("%04X", aKeyString.length()).c_str() + aKeyString + "S" + + StrFormat("%04X", aValueString.length()).c_str() + aValueString; ++anItr; } @@ -499,7 +531,7 @@ bool PopApp::OpenRegisterPage(DefinesMap theStatsMap) else { return OpenURL(mRegisterLink); - } + } } bool PopApp::ShouldCheckForUpdate() @@ -510,10 +542,8 @@ bool PopApp::ShouldCheckForUpdate() time(&aTimeNow); // It is set to 0 if we crash, otherwise ask every week - return ((mLastVerCheckQueryTime == 0) || - (!mLastShutdownWasGraceful) || - ((mLastVerCheckQueryTime != 0) && - (aTimeNow - mLastVerCheckQueryTime > 7*24*60*60))); + return ((mLastVerCheckQueryTime == 0) || (!mLastShutdownWasGraceful) || + ((mLastVerCheckQueryTime != 0) && (aTimeNow - mLastVerCheckQueryTime > 7 * 24 * 60 * 60))); } void PopApp::UpdateCheckQueried() @@ -524,7 +554,7 @@ void PopApp::UpdateCheckQueried() mLastVerCheckQueryTime = aTimeNow; } -void PopApp::URLOpenSucceeded(const std::string& theURL) +void PopApp::URLOpenSucceeded(const std::string &theURL) { AppBase::URLOpenSucceeded(theURL); diff --git a/PopLib/popapp.hpp b/PopLib/popapp.hpp index 5846de54..bacd4f07 100644 --- a/PopLib/popapp.hpp +++ b/PopLib/popapp.hpp @@ -1,80 +1,96 @@ #ifndef __POPAPP_HPP__ #define __POPAPP_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "appbase.hpp" namespace PopLib { - class DiscordRPC; - - class PopApp: public AppBase - { - public: - #if _FEATURE_DISCORD_RPC - DiscordRPC* mDiscordRPC; - - std::string mRPCAppID; - #endif - - std::string mBetaSupportSiteOverride; - std::string mBetaSupportProdNameOverride; - std::string mReferId; - std::string mVariation; - ulong mDownloadId; - std::string mRegSource; - ulong mLastVerCheckQueryTime; - bool mSkipAd; - bool mDontUpdate; - - int mBuildNum; - std::string mBuildDate; - - std::string mUserName; - std::string mRegUserName; - std::string mRegCode; - bool mIsRegistered; - bool mBuildUnlocked; - - int mTimesPlayed; - int mTimesExecuted; - bool mTimedOut; - public: - virtual void UpdateFrames(); - - virtual void WriteToRegistry(); - virtual void ReadFromRegistry(); - - virtual bool CheckSignature(const Buffer& theBuffer, const std::string& theFileName); - - virtual bool ShouldCheckForUpdate(); - virtual void UpdateCheckQueried(); - - virtual void URLOpenSucceeded(const std::string& theURL); - public: - PopApp(); - virtual ~PopApp(); - - virtual void Init(); - virtual void InitHook(); - virtual void InitPropertiesHook(); - virtual void OpenUpdateURL(); - - bool Validate(const std::string& theUserName, const std::string& theRegCode); - - virtual bool OpenRegisterPage(DefinesMap theDefinesMap); - virtual bool OpenRegisterPage(); - - virtual void HandleCmdLineParam(const std::string& theParamName, const std::string& theParamValue); - virtual bool OpenHTMLTemplate(const std::string& theTemplateFile, const DefinesMap& theDefinesMap); - #if _FEATURE_DISCORD_RPC - virtual void InitDiscordRPC(); - #endif - }; - -extern PopApp* gPopApp; -} +#ifdef POP_FEATURE_DISCORD_RPC +class DiscordRPC; +#endif +#ifdef POP_FEATURE_STEAM_API +class SteamAPI; +#endif + +class PopApp : public AppBase +{ + public: +#ifdef POP_FEATURE_DISCORD_RPC + DiscordRPC *mDiscordRPC; + + std::string mRPCAppID; +#endif +#ifdef POP_FEATURE_STEAM_API + SteamAPI *mSteamAPI; + + std::string mSteamAppID; +#endif + + std::string mBetaSupportSiteOverride; + std::string mBetaSupportProdNameOverride; + std::string mReferId; + std::string mVariation; + ulong mDownloadId; + std::string mRegSource; + ulong mLastVerCheckQueryTime; + bool mSkipAd; + bool mDontUpdate; + + int mBuildNum; + std::string mBuildDate; + + std::string mUserName; + std::string mRegUserName; + std::string mRegCode; + bool mIsRegistered; + bool mBuildUnlocked; + + int mTimesPlayed; + int mTimesExecuted; + bool mTimedOut; + + public: + virtual void UpdateFrames(); + + virtual void WriteToRegistry(); + virtual void ReadFromRegistry(); + + virtual bool CheckSignature(const Buffer &theBuffer, const std::string &theFileName); + + virtual bool ShouldCheckForUpdate(); + virtual void UpdateCheckQueried(); + + virtual void URLOpenSucceeded(const std::string &theURL); + + virtual void ShutdownHook(); + + public: + PopApp(); + virtual ~PopApp(); + + virtual void Init(); + virtual void InitHook(); + virtual void InitPropertiesHook(); + virtual void OpenUpdateURL(); + + bool Validate(const std::string &theUserName, const std::string &theRegCode); + + virtual bool OpenRegisterPage(DefinesMap theDefinesMap); + virtual bool OpenRegisterPage(); + + virtual void HandleCmdLineParam(const std::string &theParamName, const std::string &theParamValue); + virtual bool OpenHTMLTemplate(const std::string &theTemplateFile, const DefinesMap &theDefinesMap); +#ifdef POP_FEATURE_DISCORD_RPC + virtual void InitDiscordRPC(); +#endif +#ifdef POP_FEATURE_STEAM_API + virtual void InitSteamAPI(); +#endif +}; + +extern PopApp *gPopApp; +} // namespace PopLib #endif \ No newline at end of file diff --git a/PopLib/readwrite/descparser.hpp b/PopLib/readwrite/descparser.hpp index 9d8cacca..12623763 100644 --- a/PopLib/readwrite/descparser.hpp +++ b/PopLib/readwrite/descparser.hpp @@ -1,8 +1,7 @@ #ifndef __DESCPARSER_HPP__ #define __DESCPARSER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" diff --git a/PopLib/readwrite/jsonparser.hpp b/PopLib/readwrite/jsonparser.hpp index fde98c5a..f81f18d8 100644 --- a/PopLib/readwrite/jsonparser.hpp +++ b/PopLib/readwrite/jsonparser.hpp @@ -1,8 +1,7 @@ #ifndef __JSONPARSER_HPP__ #define __JSONPARSER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include @@ -18,43 +17,43 @@ namespace PopLib */ class JsonParser { - protected: - /// @brief the json file name - std::string mFileName; - /// @brief the error text - PopString mErrorText; - /// @brief current line number - int mLineNum; - /// @brief true if failed - bool mHasFailed; - - /// @brief fail - /// @param theErrorText - void Fail(const PopString &theErrorText); - - public: - /// @brief the nlohmann::json object - nlohmann::json mJson; - - public: - /// @brief constructor - JsonParser(); - /// @brief destructor - virtual ~JsonParser(); - - /// @brief opens a file - /// @param theFilename - /// @return true if success - bool OpenFile(const std::string &theFilename); - - /// @brief gets an int from the json file - /// @param theName - /// @return the integer - int GetValueInt(const std::string &theName); - /// @brief gets a string from the json file - /// @param theName - /// @return the string - PopString GetValueStr(const std::string &theName); + protected: + /// @brief the json file name + std::string mFileName; + /// @brief the error text + PopString mErrorText; + /// @brief current line number + int mLineNum; + /// @brief true if failed + bool mHasFailed; + + /// @brief fail + /// @param theErrorText + void Fail(const PopString &theErrorText); + + public: + /// @brief the nlohmann::json object + nlohmann::json mJson; + + public: + /// @brief constructor + JsonParser(); + /// @brief destructor + virtual ~JsonParser(); + + /// @brief opens a file + /// @param theFilename + /// @return true if success + bool OpenFile(const std::string &theFilename); + + /// @brief gets an int from the json file + /// @param theName + /// @return the integer + int GetValueInt(const std::string &theName); + /// @brief gets a string from the json file + /// @param theName + /// @return the string + PopString GetValueStr(const std::string &theName); }; }; // namespace PopLib diff --git a/PopLib/readwrite/modval.hpp b/PopLib/readwrite/modval.hpp index 1aee50bc..4d8290f5 100644 --- a/PopLib/readwrite/modval.hpp +++ b/PopLib/readwrite/modval.hpp @@ -1,8 +1,7 @@ #ifndef __MODVAL_HPP__ #define __MODVAL_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include @@ -51,7 +50,7 @@ namespace PopLib #define MODVAL_STR_COUNTER2(x, y, z) x #y "," #z #define MODVAL_STR_COUNTER1(x, y, z) MODVAL_STR_COUNTER2(x, y, z) #define MODVAL_STR_COUNTER(x) MODVAL_STR_COUNTER1(x, __COUNTER__, __LINE__) -#define M(val) ModVal(0, MODVAL_STR_COUNTER("POPLIB_POPLIBMODVAL"__FILE__), (val)) +#define M(val) ModVal(0, MODVAL_STR_COUNTER("POPLIB_POPLIBMODVAL" __FILE__), (val)) #define M1(val) M(val) #define M2(val) M(val) #define M3(val) M(val) diff --git a/PopLib/readwrite/xmlparser.cpp b/PopLib/readwrite/xmlparser.cpp index e387ceb2..ed1fe9b2 100644 --- a/PopLib/readwrite/xmlparser.cpp +++ b/PopLib/readwrite/xmlparser.cpp @@ -1,5 +1,5 @@ #include "xmlparser.hpp" -#include "debug/debug.hpp" +#include "debug/log.hpp" #include "paklib/pakinterface.hpp" using namespace PopLib; @@ -8,8 +8,9 @@ XMLParser::XMLParser() { mFile = NULL; mLineNum = 0; - mCurrentNode = nullptr; - mFirstStart = true; + mAllowComments = false; + mGetCharFunc = &XMLParser::GetUTF8Char; + mForcedEncodingType = false; } XMLParser::~XMLParser() @@ -18,6 +19,33 @@ XMLParser::~XMLParser() p_fclose(mFile); } +void XMLParser::SetEncodingType(XMLEncodingType theEncoding) +{ + switch (theEncoding) + { + case ASCII: + mGetCharFunc = &XMLParser::GetAsciiChar; + mForcedEncodingType = true; + break; + case UTF_8: + mGetCharFunc = &XMLParser::GetUTF8Char; + mForcedEncodingType = true; + break; + case UTF_16: + mGetCharFunc = &XMLParser::GetUTF16Char; + mForcedEncodingType = true; + break; + case UTF_16_LE: + mGetCharFunc = &XMLParser::GetUTF16LEChar; + mForcedEncodingType = true; + break; + case UTF_16_BE: + mGetCharFunc = &XMLParser::GetUTF16BEChar; + mForcedEncodingType = true; + break; + } +} + void XMLParser::Fail(const PopString &theErrorText) { mHasFailed = true; @@ -26,17 +54,17 @@ void XMLParser::Fail(const PopString &theErrorText) void XMLParser::Init() { - mLineNum = 1; - mHasFailed = false; - mCurrentNode = nullptr; - mSectionStack.clear(); + mSection = ""; + mLineNum = 1; + mHasFailed = false; + mErrorText = ""; + mFirstChar = true; + mByteSwap = false; } -bool XMLParser::AddAttribute(XMLElement *theElement, const PopString &theAttributeKey, const PopString &theAttributeValue) +bool XMLParser::AddAttribute(XMLElement *theElement, const PopString &theAttributeKey, + const PopString &theAttributeValue) { - if (theAttributeValue == "SUNFLOWER_HEAD_SING1") - int asd = 2; - std::pair aRet; aRet = theElement->mAttributes.insert(XMLParamMap::value_type(theAttributeKey, theAttributeValue)); @@ -49,61 +77,211 @@ bool XMLParser::AddAttribute(XMLElement *theElement, const PopString &theAttribu return aRet.second; } -bool XMLParser::OpenBuffer(const std::string &theBuffer, const std::string &custom_root) +bool XMLParser::GetAsciiChar(wchar_t *theChar, bool *error) { - mCurrentNode = nullptr; - mSectionStack.clear(); - mEndPending.clear(); - mHasFailed = false; + (void)error; + wchar_t aChar = 0; + if (p_fread(&aChar, 1, 1, mFile) != 1) + return false; + + *theChar = aChar; + return true; +} +bool XMLParser::GetUTF8Char(wchar_t *theChar, bool *error) +{ + static const unsigned short aMaskData[] = { + 0xC0, // 1 extra byte + 0xE0, // 2 extra bytes + 0xF0, // 3 extra bytes + 0xF8, // 4 extra bytes + 0xFC // 5 extra bytes + }; + *error = true; + + int aTempChar = 0; + if (p_fread(&aTempChar, 1, 1, mFile) == 1) + { + if ((aTempChar & 0x80) != 0) + { + if ((aTempChar & 0xC0) != 0xC0) + return false; // sanity check: high bit should not be set without the next highest bit being set too. + + int aBytesRead[6]; + int *aBytesReadPtr = &aBytesRead[0]; + + *aBytesReadPtr++ = aTempChar; + + int aLen; + for (aLen = 0; aLen < (int)(sizeof(aMaskData) / sizeof(*aMaskData)); ++aLen) + { + if ((aTempChar & aMaskData[aLen]) == ((aMaskData[aLen] << 1) & aMaskData[aLen])) + break; + } + if (aLen >= (int)(sizeof(aMaskData) / sizeof(*aMaskData))) + return false; + + aTempChar &= ~aMaskData[aLen]; + int aTotalLen = aLen + 1; + + ASSERT(aTotalLen >= 2 && aTotalLen <= 6); + + int anExtraChar = 0; + while (aLen > 0) + { + if (p_fread(&anExtraChar, 1, 1, mFile) != 1) + return false; + if ((anExtraChar & 0xC0) != 0x80) + return false; // sanity check: high bit set, and next highest bit NOT set. + + *aBytesReadPtr++ = anExtraChar; + + aTempChar = (aTempChar << 6) | (anExtraChar & 0x3F); + --aLen; + } + + // validate substrings + bool valid = true; + switch (aTotalLen) + { + case 2: + valid = !((aBytesRead[0] & 0x3E) == 0); + break; + case 3: + valid = !((aBytesRead[0] & 0x1F) == 0 && (aBytesRead[1] & 0x20) == 0); + break; + case 4: + valid = !((aBytesRead[0] & 0x0F) == 0 && (aBytesRead[1] & 0x30) == 0); + break; + case 5: + valid = !((aBytesRead[0] & 0x07) == 0 && (aBytesRead[1] & 0x38) == 0); + break; + case 6: + valid = !((aBytesRead[0] & 0x03) == 0 && (aBytesRead[1] & 0x3C) == 0); + break; + } + if (!valid) + return false; + } + + if ((aTempChar >= 0xD800 && aTempChar <= 0xDFFF) || (aTempChar >= 0xFFFE && aTempChar <= 0xFFFF)) + return false; + + if (aTempChar == 0xFEFF && + mFirstChar) // zero-width non breaking space as the first char is a byte order marker. + { + mFirstChar = false; + return GetUTF8Char(theChar, error); + } + + *theChar = (wchar_t)aTempChar; + *error = false; + return true; + } - // UTF-8 stuff - std::string input = theBuffer; - if (input.size() >= 3 && - (unsigned char)input[0] == 0xEF && - (unsigned char)input[1] == 0xBB && - (unsigned char)input[2] == 0xBF) - { - input = input.substr(3); - } - - std::string aNewWrappedBuffer; - - if (custom_root != "") - { - // get the declaration so we dont add the custom root before it - std::string xmlDecl; - if (input.find(""); - if (declEnd != std::string::npos) - { - declEnd += 2; - xmlDecl = input.substr(0, declEnd); - input = input.substr(declEnd); // remove declaration from the main content - } - } - - // Wrap with custom root - aNewWrappedBuffer = xmlDecl + "<" + custom_root + ">" + input + ""; - } - else - aNewWrappedBuffer = input; - - const char* data = aNewWrappedBuffer.c_str(); - size_t size = aNewWrappedBuffer.size(); - - mDocument = new XMLDocument(); - XMLError err = mDocument->Parse(data, size); - if (err != XML_SUCCESS) { - std::string anError = mDocument->ErrorStr(); - Fail("Parse error: " + anError); + *error = false; + return false; +} + +bool XMLParser::GetUTF16Char(wchar_t *theChar, bool *error) +{ + wchar_t aTempChar = 0; + if (p_fread(&aTempChar, 2, 1, mFile) != 1) return false; + + if (mFirstChar) + { + mFirstChar = false; + if (aTempChar == 0xFEFF) + { + mByteSwap = false; + return GetUTF16Char(theChar, error); + } + else if (aTempChar == 0xFFFE) + { + mByteSwap = true; + return GetUTF16Char(theChar, error); + } + } + if (mByteSwap) + aTempChar = (wchar_t)((aTempChar << 8) | (aTempChar >> 8)); + + if ((aTempChar & 0xD800) == 0xD800) + { + wchar_t aNextChar = 0; + if (p_fread(&aNextChar, 2, 1, mFile) != 1) + return false; + + if (mByteSwap) + aNextChar = (wchar_t)((aNextChar << 8) | (aNextChar >> 8)); + if ((aNextChar & 0xDC00) == 0xDC00) + { + *theChar = (wchar_t)((((aTempChar & ~0xD800) << 10) | (aNextChar & ~0xDC00)) + 0x10000); + } + else + return false; } + else + *theChar = aTempChar; + return true; } -bool XMLParser::OpenFile(const std::string &theFileName, const std::string &custom_root) +bool XMLParser::GetUTF16LEChar(wchar_t *theChar, bool *error) +{ + (void)error; + wchar_t aTempChar = 0; + if (p_fread(&aTempChar, 2, 1, mFile) != 1) + return false; + + aTempChar = WORD_LITTLEE_TO_NATIVE(aTempChar); + + if ((aTempChar & 0xD800) == 0xD800) + { + wchar_t aNextChar = 0; + if (p_fread(&aNextChar, 2, 1, mFile) != 1) + return false; + + aNextChar = WORD_LITTLEE_TO_NATIVE(aTempChar); + if ((aNextChar & 0xDC00) == 0xDC00) + { + *theChar = (wchar_t)((((aTempChar & ~0xD800) << 10) | (aNextChar & ~0xDC00)) + 0x10000); + } + else + return false; + } + + return true; +} + +bool XMLParser::GetUTF16BEChar(wchar_t *theChar, bool *error) +{ + (void)error; + wchar_t aTempChar = 0; + if (p_fread(&aTempChar, 2, 1, mFile) != 1) + return false; + + aTempChar = WORD_BIGE_TO_NATIVE(aTempChar); + + if ((aTempChar & 0xD800) == 0xD800) + { + wchar_t aNextChar = 0; + if (p_fread(&aNextChar, 2, 1, mFile) != 1) + return false; + + aNextChar = WORD_BIGE_TO_NATIVE(aTempChar); + if ((aNextChar & 0xDC00) == 0xDC00) + { + *theChar = (wchar_t)((((aTempChar & ~0xD800) << 10) | (aNextChar & ~0xDC00)) + 0x10000); + } + else + return false; + } + + return true; +} + +bool XMLParser::OpenFile(const std::string &theFileName) { mFile = p_fopen(theFileName.c_str(), "r"); @@ -113,173 +291,456 @@ bool XMLParser::OpenFile(const std::string &theFileName, const std::string &cust Fail("Unable to open file " + theFileName); return false; } - - p_fseek(mFile, 0, SEEK_END); - long size = p_ftell(mFile); - p_fseek(mFile, 0, SEEK_SET); - - if (size <= 0) + else if (!mForcedEncodingType) { - p_fclose(mFile); - Fail("Empty or unreadable file: " + theFileName); - return false; + p_fseek(mFile, 0, SEEK_END); + long aFileLen = p_ftell(mFile); + p_fseek(mFile, 0, SEEK_SET); + + mGetCharFunc = &XMLParser::GetAsciiChar; + if (aFileLen >= 2) // UTF-16? + { + int aChar1 = p_fgetc(mFile); + int aChar2 = p_fgetc(mFile); + + if ((aChar1 == 0xFF && aChar2 == 0xFE) || (aChar1 == 0xFE && aChar2 == 0xFF)) + mGetCharFunc = &XMLParser::GetUTF16Char; + + p_ungetc(aChar2, mFile); + p_ungetc(aChar1, mFile); + } + if ((mGetCharFunc = &XMLParser::GetAsciiChar)) + { + if (aFileLen >= 3) // UTF-8? + { + int aChar1 = p_fgetc(mFile); + int aChar2 = p_fgetc(mFile); + int aChar3 = p_fgetc(mFile); + + if (aChar1 == 0xEF && aChar2 == 0xBB && aChar3 == 0xBF) + mGetCharFunc = &XMLParser::GetUTF8Char; + + p_ungetc(aChar3, mFile); + p_ungetc(aChar2, mFile); + p_ungetc(aChar1, mFile); + } + } } - std::string content(size, '\0'); - p_fread(content.data(), size, 1, mFile); - - OpenBuffer(content, custom_root); + mFileName = theFileName.c_str(); + Init(); return true; } -bool XMLParser::NextElement(XMLElement *theElement) +void XMLParser::SetStringSource(const std::wstring &theString) { + Init(); - theElement->mAttributes.clear(); - theElement->mInstruction.clear(); - theElement->mValue.clear(); - theElement->mSection.clear(); - - - if (!mEndPending.empty()) - { - theElement->mType = XMLElement::TYPE_END; - theElement->mValue = mEndPending.back(); - theElement->mSection = mSectionStack.back(); - mEndPending.pop_back(); - mSectionStack.pop_back(); - return true; - } - - while (!mNodeStack.empty() && mCurrentNode == nullptr) - { - XMLNode* parent = mNodeStack.back(); - mNodeStack.pop_back(); - - theElement->mType = XMLElement::TYPE_END; - theElement->mValue = parent->ToElement()->Name(); - theElement->mSection = mSectionStack.back(); - mSectionStack.pop_back(); - - if (mSectionStack.empty()) - return false; //We left the root? - - mCurrentNode = parent->NextSibling(); - while (mCurrentNode && - !mCurrentNode->ToElement() && - !mCurrentNode->ToComment() && - !mCurrentNode->ToDeclaration()) - { - mCurrentNode = mCurrentNode->NextSibling(); - } - if (mCurrentNode) - mLineNum = mCurrentNode->GetLineNum(); - return true; - } - - if (!mCurrentNode && mFirstStart) - { - mCurrentNode = mDocument->FirstChild(); - mFirstStart = false; - } - else if (!mCurrentNode && !mFirstStart) - { - // No more nodes to process, end of document reached - return false; - } - - while (mCurrentNode && - !mCurrentNode->ToElement() && - !mCurrentNode->ToComment() && - !mCurrentNode->ToDeclaration()) - { - mCurrentNode = mCurrentNode->NextSibling(); - } - - if (!mCurrentNode) - return false; - - if (auto com = mCurrentNode->ToComment()) // Comments - { - theElement->mType = XMLElement::TYPE_COMMENT; - theElement->mInstruction = com->Value(); - mCurrentNode = mCurrentNode->NextSibling(); - return true; - } - else if (auto decl = mCurrentNode->ToDeclaration()) // XML declaration / PI - { - theElement->mType = XMLElement::TYPE_INSTRUCTION; - theElement->mInstruction = decl->Value(); - mCurrentNode = mCurrentNode->NextSibling(); - return true; - } - else if (auto elem = mCurrentNode->ToElement()) // Element start - { - auto closeType = elem->ClosingType(); - - if (closeType == tinyxml2::XMLElement::OPEN) - { - // Start tag - theElement->mType = XMLElement::TYPE_START; - theElement->mValue = elem->Name(); - - if (mSectionStack.empty()) - mSectionStack.push_back(elem->Name()); - else - mSectionStack.push_back(mSectionStack.back() + "/" + elem->Name()); - theElement->mSection = mSectionStack.back(); - - for (auto attr = elem->FirstAttribute(); attr; attr = attr->Next()) - AddAttribute(theElement, attr->Name(), attr->Value()); - - mCurrentNode = elem->FirstChild(); - mNodeStack.push_back(elem); - - return true; - } - else if (closeType == tinyxml2::XMLElement::CLOSED) - { - // Self-closing tag - // Emit START event now - theElement->mType = XMLElement::TYPE_START; - theElement->mValue = elem->Name(); - - if (mSectionStack.empty()) - mSectionStack.push_back(elem->Name()); - else - mSectionStack.push_back(mSectionStack.back() + "/" + elem->Name()); - theElement->mSection = mSectionStack.back(); - - for (auto attr = elem->FirstAttribute(); attr; attr = attr->Next()) - AddAttribute(theElement, attr->Name(), attr->Value()); - - // Immediately emit the END event for the same element on next call - mEndPending.push_back(elem->Name()); - - mCurrentNode = mCurrentNode->NextSibling(); - - return true; - } - else if (closeType == tinyxml2::XMLElement::CLOSING) - { - // This means an end tag node () - normally tinyxml2 skips these as separate nodes, - // but if you get here, emit END event. - - theElement->mType = XMLElement::TYPE_END; - theElement->mValue = elem->Name(); - - if (!mSectionStack.empty()) - mSectionStack.pop_back(); - - theElement->mSection = mSectionStack.empty() ? "" : mSectionStack.back(); - - mCurrentNode = mCurrentNode->NextSibling(); - - return true; - } - } + int aSize = theString.size(); - return false; + mBufferedText.resize(aSize); + for (int i = 0; i < aSize; i++) + mBufferedText[i] = theString[aSize - i - 1]; +} + +void XMLParser::SetStringSource(const std::string &theString) +{ + SetStringSource(StringToWString(theString)); +} + +bool XMLParser::NextElement(XMLElement *theElement) +{ + for (;;) + { + theElement->mType = XMLElement::TYPE_NONE; + theElement->mSection = mSection; + theElement->mValue = ""; + theElement->mAttributes.clear(); + theElement->mInstruction.erase(); + + bool hasSpace = false; + bool inQuote = false; + bool gotEndQuote = false; + + bool doingAttribute = false; + bool AttributeVal = false; + std::wstring aAttributeKey; + std::wstring aAttributeValue; + + std::wstring aLastAttributeKey; + + for (;;) + { + // Process character by character + + wchar_t c; + int aVal; + + if (mBufferedText.size() > 0) + { + c = mBufferedText[mBufferedText.size() - 1]; + mBufferedText.pop_back(); + + aVal = 1; + } + else + { + if (mFile != NULL) + { + bool error = false; + if ((this->*mGetCharFunc)(&c, &error)) + { + aVal = 1; + } + else + { + if (error) + Fail("Illegal Character"); + aVal = 0; + } + } + else + { + aVal = 0; + } + } + + if (aVal == 1) + { + bool processChar = false; + + if (c == L'\n') + { + mLineNum++; + } + + if (theElement->mType == XMLElement::TYPE_COMMENT) + { + // Just add text to theElement->mInstruction until we find --> + + PopString *aStrPtr = &theElement->mInstruction; + + *aStrPtr += (PopChar)c; + + int aLen = aStrPtr->length(); + + if ((c == L'>') && (aLen >= 3) && ((*aStrPtr)[aLen - 2] == L'-') && ((*aStrPtr)[aLen - 3] == L'-')) + { + *aStrPtr = aStrPtr->substr(0, aLen - 3); + break; + } + } + else if (theElement->mType == XMLElement::TYPE_INSTRUCTION) + { + // Just add text to theElement->mInstruction until we find ?> + + PopString *aStrPtr = &theElement->mValue; + + if ((theElement->mInstruction.length() != 0) || (::iswspace(c))) + aStrPtr = &theElement->mInstruction; + + *aStrPtr += (PopChar)c; + + int aLen = aStrPtr->length(); + + if ((c == L'>') && (aLen >= 2) && ((*aStrPtr)[aLen - 2] == L'?')) + { + *aStrPtr = aStrPtr->substr(0, aLen - 2); + break; + } + } + else + { + if (c == L'"') + { + inQuote = !inQuote; + if (theElement->mType == XMLElement::TYPE_NONE || theElement->mType == XMLElement::TYPE_ELEMENT) + processChar = true; + + if (!inQuote) + gotEndQuote = true; + } + else if (!inQuote) + { + if (c == L'<') + { + if (theElement->mType == XMLElement::TYPE_ELEMENT) + { + // TODO: Fix buffered text. Not sure what I meant by that. + + // OLD: mBufferedText = c + mBufferedText; + + mBufferedText.push_back(c); + break; + } + + if (theElement->mType == XMLElement::TYPE_NONE) + { + theElement->mType = XMLElement::TYPE_START; + } + else + { + Fail("Unexpected '<'"); + return false; + } + } + else if (c == L'>') + { + if (theElement->mType == XMLElement::TYPE_START) + { + bool insertEnd = false; + + if (aAttributeKey == L"/") + { + // We will get this if we have a space before the />, so we can ignore it + // and go about our business now + insertEnd = true; + } + else + { + // Probably isn't committed yet + if (aAttributeKey.length() > 0) + { + // theElement->mAttributes[aLastAttributeKey] + //= aAttributeValue; + + aAttributeKey = XMLDecodeString(aAttributeKey); + aAttributeValue = XMLDecodeString(aAttributeValue); + + aLastAttributeKey = aAttributeKey; + AddAttribute(theElement, WStringToString(aLastAttributeKey), + WStringToString(aAttributeValue)); + + aAttributeKey = L""; + aAttributeValue = L""; + } + + if (aLastAttributeKey.length() > 0) + { + PopString aVal = theElement->mAttributes[WStringToString(aLastAttributeKey)]; + + int aLen = aVal.length(); + + if ((aLen > 0) && (aVal[aLen - 1] == '/')) + { + // Its an empty element, fake start and end segments + // theElement->mAttributes[aLastAttributeKey] + //= aVal.substr(0, aLen - 1); + + AddAttribute(theElement, WStringToString(aLastAttributeKey), + XMLDecodeString(aVal.substr(0, aLen - 1))); + + insertEnd = true; + } + } + else + { + int aLen = theElement->mValue.length(); + + if ((aLen > 0) && (theElement->mValue[aLen - 1] == '/')) + { + // Its an empty element, fake start and end segments + theElement->mValue = theElement->mValue.substr(0, aLen - 1); + insertEnd = true; + } + } + } + + // Do we want to fake an ending section? + if (insertEnd) + { + PopString anAddString = "mValue + ">"; + + int anOldSize = mBufferedText.size(); + int anAddLength = anAddString.length(); + + mBufferedText.resize(anOldSize + anAddLength); + + for (int i = 0; i < anAddLength; i++) + mBufferedText[anOldSize + i] = (wchar_t)(anAddString[anAddLength - i - 1]); + + // clear out aAttributeKey, since it contains "/" as its value and will insert + // it into the element's attribute map. + aAttributeKey = L""; + + // OLD: mBufferedText = "mValue + ">" + mBufferedText; + } + + if (mSection.length() != 0) + mSection += "/"; + + mSection += theElement->mValue; + + break; + } + else if (theElement->mType == XMLElement::TYPE_END) + { + int aLastSlash = mSection.rfind('/'); + if ((aLastSlash == -1) && (mSection.length() == 0)) + { + Fail("Unexpected End"); + return false; + } + + PopString aLastSectionName = mSection.substr(aLastSlash + 1); + + if (aLastSectionName != theElement->mValue) + { + Fail("End '" + theElement->mValue + "' Doesn't Match Start '" + aLastSectionName + + "'"); + return false; + } + + if (aLastSlash == -1) + mSection.erase(mSection.begin(), mSection.end()); + else + mSection.erase(mSection.begin() + aLastSlash, mSection.end()); + + break; + } + else + { + Fail("Unexpected '>'"); + return false; + } + } + else if ((c == L'/') && (theElement->mType == XMLElement::TYPE_START) && + (theElement->mValue == "")) + { + theElement->mType = XMLElement::TYPE_END; + } + else if ((c == L'?') && (theElement->mType == XMLElement::TYPE_START) && + (theElement->mValue == "")) + { + theElement->mType = XMLElement::TYPE_INSTRUCTION; + } + else if (::isspace((uchar)c)) + { + if (theElement->mValue != "") + hasSpace = true; + + // It's a comment! + if ((theElement->mType == XMLElement::TYPE_START) && (theElement->mValue == "!--")) + theElement->mType = XMLElement::TYPE_COMMENT; + } + else if (c > 32) + { + processChar = true; + } + else + { + Fail("Illegal Character"); + return false; + } + } + else + { + processChar = true; + } + + if (processChar) + { + if (theElement->mType == XMLElement::TYPE_NONE) + theElement->mType = XMLElement::TYPE_ELEMENT; + + if (theElement->mType == XMLElement::TYPE_START) + { + if (hasSpace) + { + if ((!doingAttribute) || ((!AttributeVal) && (c != '=')) || + ((AttributeVal) && ((aAttributeValue.length() > 0) || gotEndQuote))) + { + if (doingAttribute) + { + aAttributeKey = XMLDecodeString(aAttributeKey); + aAttributeValue = XMLDecodeString(aAttributeValue); + + // theElement->mAttributes[aAttributeKey] = + //aAttributeValue; + + AddAttribute(theElement, WStringToString(aAttributeKey), + WStringToString(aAttributeValue)); + + aAttributeKey = L""; + aAttributeValue = L""; + + aLastAttributeKey = aAttributeKey; + } + else + { + doingAttribute = true; + } + + AttributeVal = false; + } + + hasSpace = false; + } + + std::wstring *aStrPtr = NULL; + + if (!doingAttribute) + { + theElement->mValue += (PopChar)c; + } + else + { + if (c == L'=') + { + AttributeVal = true; + gotEndQuote = false; + } + else + { + if (!AttributeVal) + aStrPtr = &aAttributeKey; + else + aStrPtr = &aAttributeValue; + } + } + + if (aStrPtr != NULL) + { + *aStrPtr += c; + } + } + else + { + if (hasSpace) + { + theElement->mValue += " "; + hasSpace = false; + } + + theElement->mValue += (PopChar)c; + } + } + } + } + else + { + if (theElement->mType != XMLElement::TYPE_NONE) + Fail("Unexpected End of File"); + + return false; + } + } + + if (aAttributeKey.length() > 0) + { + aAttributeKey = XMLDecodeString(aAttributeKey); + aAttributeValue = XMLDecodeString(aAttributeValue); + // theElement->mAttributes[aAttributeKey] = aAttributeValue; + + AddAttribute(theElement, WStringToString(aAttributeKey), WStringToString(aAttributeValue)); + } + + theElement->mValue = XMLDecodeString(theElement->mValue); + + // Ignore comments + if ((theElement->mType != XMLElement::TYPE_COMMENT) || mAllowComments) + return true; + } } bool XMLParser::HasFailed() diff --git a/PopLib/readwrite/xmlparser.hpp b/PopLib/readwrite/xmlparser.hpp index e1ab9c59..6962e0c7 100644 --- a/PopLib/readwrite/xmlparser.hpp +++ b/PopLib/readwrite/xmlparser.hpp @@ -1,14 +1,11 @@ #ifndef __XMLPARSER_HPP__ #define __XMLPARSER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include "debug/perftimer.hpp" -#include -using namespace tinyxml2; struct PFILE; namespace PopLib @@ -24,7 +21,7 @@ class XMLParam typedef std::map XMLParamMap; typedef std::list XMLParamMapIteratorList; -typedef std::vector XMLParserBuffer; +typedef std::vector XMLParserBuffer; class XMLElement { @@ -56,20 +53,13 @@ class XMLParser int mLineNum; PFILE *mFile; bool mHasFailed; + bool mAllowComments; + XMLParserBuffer mBufferedText; + PopString mSection; + bool (XMLParser::*mGetCharFunc)(wchar_t *theChar, bool *error); + bool mForcedEncodingType; bool mFirstChar; bool mByteSwap; - XMLDocument* mDocument; - XMLNode* mCurrentNode; - /// @brief current elements - std::vector mSectionStack; - /// @brief names of elements that await TYPE_END - std::vector mEndPending; - - /// @brief the processed children - std::vector mProcessedChildren; - /// @brief stack of parent elements - std::vector mNodeStack; - bool mFirstStart; protected: void Fail(const PopString &theErrorText); @@ -77,18 +67,42 @@ class XMLParser bool AddAttribute(XMLElement *theElement, const PopString &aAttributeKey, const PopString &aAttributeValue); + bool GetAsciiChar(wchar_t *theChar, bool *error); + bool GetUTF8Char(wchar_t *theChar, bool *error); + bool GetUTF16Char(wchar_t *theChar, bool *error); + bool GetUTF16LEChar(wchar_t *theChar, bool *error); + bool GetUTF16BEChar(wchar_t *theChar, bool *error); + + public: + enum XMLEncodingType + { + ASCII, + UTF_8, + UTF_16, + UTF_16_LE, + UTF_16_BE + }; + public: XMLParser(); virtual ~XMLParser(); - bool OpenFile(const std::string &theFilename, const std::string &custom_root = ""); - bool OpenBuffer(const std::string &theBuffer, const std::string &custom_root = ""); + void SetEncodingType(XMLEncodingType theEncoding); + bool OpenFile(const std::string &theFilename); + void SetStringSource(const std::wstring &theString); + void SetStringSource(const std::string &theString); bool NextElement(XMLElement *theElement); PopString GetErrorText(); int GetCurrentLineNum(); std::string GetFileName(); + inline void AllowComments(bool doAllow) + { + mAllowComments = doAllow; + } + bool HasFailed(); + bool EndOfFile(); }; }; // namespace PopLib diff --git a/PopLib/readwrite/xmlwriter.hpp b/PopLib/readwrite/xmlwriter.hpp index 2510495f..68977de0 100644 --- a/PopLib/readwrite/xmlwriter.hpp +++ b/PopLib/readwrite/xmlwriter.hpp @@ -44,8 +44,7 @@ class XMLWriter XMLWriter(); virtual ~XMLWriter(); - static bool AddAttribute(XMLElement *theElement, const PopString &aAttributeKey, - const PopString &aAttributeValue); + static bool AddAttribute(XMLElement *theElement, const PopString &aAttributeKey, const PopString &aAttributeValue); bool WriteAttribute(const PopString &aAttributeKey, const PopString &aAttributeValue); bool WriteAttribute(const PopString &aAttributeKey, const float &aAttributeValue); bool WriteAttribute(const PopString &aAttributeKey, const int &aAttributeValue); diff --git a/PopLib/resources/propertiesparser.cpp b/PopLib/resources/propertiesparser.cpp index d404b581..2c27017a 100644 --- a/PopLib/resources/propertiesparser.cpp +++ b/PopLib/resources/propertiesparser.cpp @@ -246,16 +246,7 @@ bool PropertiesParser::DoParseProperties() bool PropertiesParser::ParsePropertiesBuffer(const Buffer &theBuffer) { mXMLParser = new XMLParser(); - - // mXMLParser->SetStringSource(theBuffer.UTF8ToWideString()); - - // HACK: see framework 1.22 - // here we disable converting from UTF-8, and alway load file as plain ANSI - std::string aString; - aString.insert(aString.begin(), (char *)theBuffer.GetDataPtr(), - (char *)theBuffer.GetDataPtr() + theBuffer.GetDataLen()); - mXMLParser->OpenBuffer(aString); - + mXMLParser->SetStringSource(theBuffer.UTF8ToWideString()); return DoParseProperties(); } diff --git a/PopLib/resources/propertiesparser.hpp b/PopLib/resources/propertiesparser.hpp index c527ea86..59808b45 100644 --- a/PopLib/resources/propertiesparser.hpp +++ b/PopLib/resources/propertiesparser.hpp @@ -1,8 +1,7 @@ #ifndef __PROPERTIES_PARSER_HPP__ #define __PROPERTIES_PARSER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "appbase.hpp" diff --git a/PopLib/resources/resourcemanager.cpp b/PopLib/resources/resourcemanager.cpp index e0fdbd2e..1f47ff51 100644 --- a/PopLib/resources/resourcemanager.cpp +++ b/PopLib/resources/resourcemanager.cpp @@ -1,9 +1,8 @@ #include #include "resourcemanager.hpp" +#include "graphics/gpuimage.hpp" #include "readwrite/xmlparser.hpp" #include "audio/soundmanager.hpp" -#include "graphics/sdlimage.hpp" -#include "graphics/sdlinterface.hpp" #include "graphics/imagefont.hpp" #include "graphics/sysfont.hpp" #include "imagelib/imagelib.hpp" @@ -112,7 +111,7 @@ void ResourceManager::DeleteExtraImageBuffers(const std::string &theGroup) if (theGroup.empty() || anItr->second->mResGroup == theGroup) { ImageRes *aRes = (ImageRes *)anItr->second; - MemoryImage *anImage = (MemoryImage *)aRes->mImage; + GPUImage *anImage = (GPUImage *)aRes->mImage; if (anImage != NULL) anImage->DeleteExtraBuffers(); } @@ -666,7 +665,7 @@ bool ResourceManager::ReparseResourcesFile(const std::string &theFilename) /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -bool ResourceManager::LoadAlphaGridImage(ImageRes *theRes, SDLImage *theImage) +bool ResourceManager::LoadAlphaGridImage(ImageRes *theRes, GPUImage *theImage) { ImageLib::Image *anAlphaImage = ImageLib::GetImage(theRes->mAlphaGridImage, true); if (!anAlphaImage) @@ -715,7 +714,7 @@ bool ResourceManager::LoadAlphaGridImage(ImageRes *theRes, SDLImage *theImage) /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// -bool ResourceManager::LoadAlphaImage(ImageRes *theRes, SDLImage *theImage) +bool ResourceManager::LoadAlphaImage(ImageRes *theRes, GPUImage *theImage) { PERF_BEGIN("ResourceManager::GetImage"); ImageLib::Image *anAlphaImage = ImageLib::GetImage(theRes->mAlphaImage, true); @@ -762,8 +761,8 @@ bool ResourceManager::DoLoadImage(ImageRes *theRes) SharedImageRef aSharedImageRef = gAppBase->GetSharedImage(theRes->mPath, theRes->mVariant, &isNew); ImageLib::gAlphaComposeColor = 0xFFFFFF; - SDLImage *aSDLImage = (SDLImage *)aSharedImageRef; - if (!aSDLImage) + GPUImage *aGPUImage = (GPUImage *)aSharedImageRef; + if (!aGPUImage) return Fail(StrFormat("Failed to load image: %s", theRes->mPath.c_str())); if (isNew) @@ -781,20 +780,20 @@ bool ResourceManager::DoLoadImage(ImageRes *theRes) } } - aSDLImage->CommitBits(); + aGPUImage->CommitBits(); theRes->mImage = aSharedImageRef; - aSDLImage->mPurgeBits = theRes->mPurgeBits; + aGPUImage->mPurgeBits = theRes->mPurgeBits; if (theRes->mDDSurface) { PERF_BEGIN("ResourceManager:DDSurface"); - aSDLImage->CommitBits(); + aGPUImage->CommitBits(); - if (!aSDLImage->mHasAlpha) + if (!aGPUImage->mHasAlpha) { - // aSDLImage->mWantDDSurface = true; - aSDLImage->mPurgeBits = true; + // aGPUImage->mWantDDSurface = true; + aGPUImage->mPurgeBits = true; } PERF_END("ResourceManager:DDSurface"); @@ -803,24 +802,24 @@ bool ResourceManager::DoLoadImage(ImageRes *theRes) if (theRes->mPalletize) { // PERF_BEGIN("ResourceManager:Palletize"); - // if (aSDLImage->mSurface==NULL) - // aSDLImage->Palletize(); + // if (aGPUImage->mSurface==NULL) + // aGPUImage->Palletize(); // else - // aSDLImage->mWantPal = true; + // aGPUImage->mWantPal = true; // PERF_END("ResourceManager:Palletize"); } if (theRes->mNearestFilter) - aSDLImage->mImageFlags |= SDLImageFlag_NearestFiltering; + aGPUImage->mImageFlags |= GPUImageFlag_NearestFiltering; if (theRes->mAnimInfo.mAnimType != AnimType_None) - aSDLImage->mAnimInfo = new AnimInfo(theRes->mAnimInfo); + aGPUImage->mAnimInfo = new AnimInfo(theRes->mAnimInfo); - aSDLImage->mNumRows = theRes->mRows; - aSDLImage->mNumCols = theRes->mCols; + aGPUImage->mNumRows = theRes->mRows; + aGPUImage->mNumCols = theRes->mCols; - if (aSDLImage->mPurgeBits) - aSDLImage->PurgeBits(); + if (aGPUImage->mPurgeBits) + aGPUImage->PurgeBits(); ResourceLoadedHook(theRes); return true; @@ -842,7 +841,7 @@ SharedImageRef ResourceManager::LoadImage(const std::string &theName) return NULL; ImageRes *aRes = (ImageRes *)anItr->second; - if ((SDLImage *)aRes->mImage != NULL) + if ((GPUImage *)aRes->mImage != NULL) return aRes->mImage; if (aRes->mFromProgram) @@ -1039,7 +1038,7 @@ bool ResourceManager::LoadNextResource() { case ResType_Image: { ImageRes *anImageRes = (ImageRes *)aRes; - if ((SDLImage *)anImageRes->mImage != NULL) + if ((GPUImage *)anImageRes->mImage != NULL) continue; return DoLoadImage(anImageRes); @@ -1279,7 +1278,7 @@ SharedImageRef ResourceManager::GetImageThrow(const std::string &theId) if (anItr != mImageMap.end()) { ImageRes *aRes = (ImageRes *)anItr->second; - if ((MemoryImage *)aRes->mImage != NULL) + if ((GPUImage *)aRes->mImage != NULL) return aRes->mImage; if (mAllowMissingProgramResources && aRes->mFromProgram) @@ -1343,7 +1342,7 @@ bool ResourceManager::ReplaceImage(const std::string &theId, Image *theImage) if (anItr != mImageMap.end()) { anItr->second->DeleteResource(); - ((ImageRes *)anItr->second)->mImage = (MemoryImage *)theImage; + ((ImageRes *)anItr->second)->mImage = (GPUImage *)theImage; ((ImageRes *)anItr->second)->mImage.mOwnsUnshared = true; return true; } diff --git a/PopLib/resources/resourcemanager.hpp b/PopLib/resources/resourcemanager.hpp index d4fa037b..3ab60148 100644 --- a/PopLib/resources/resourcemanager.hpp +++ b/PopLib/resources/resourcemanager.hpp @@ -1,8 +1,7 @@ #ifndef __RESOURCEMANAGER_HPP__ #define __RESOURCEMANAGER_HPP__ -#ifdef _WIN32 + #pragma once -#endif #include "common.hpp" #include "graphics/image.hpp" @@ -23,6 +22,7 @@ class XMLElement; class Image; class SoundInstance; class AppBase; +class GPUImage; class Font; typedef std::map StringToStringMap; @@ -163,8 +163,8 @@ class ResourceManager void DeleteMap(ResMap &theMap); virtual void DeleteResources(ResMap &theMap, const std::string &theGroup); - bool LoadAlphaGridImage(ImageRes *theRes, SDLImage *theImage); - bool LoadAlphaImage(ImageRes *theRes, SDLImage *theImage); + bool LoadAlphaGridImage(ImageRes *theRes, GPUImage *theImage); + bool LoadAlphaImage(ImageRes *theRes, GPUImage *theImage); virtual bool DoLoadImage(ImageRes *theRes); virtual bool DoLoadFont(FontRes *theRes); virtual bool DoLoadSound(SoundRes *theRes); diff --git a/PopLib/scripting/lua/lcolor.cpp b/PopLib/scripting/lua/lcolor.cpp new file mode 100644 index 00000000..c4ca490b --- /dev/null +++ b/PopLib/scripting/lua/lcolor.cpp @@ -0,0 +1,30 @@ +#include "lpoplib.hpp" +#include "graphics/color.hpp" + +using namespace PopLib; + +void open_color(sol::state_view lua) +{ + sol::table poplib; + if (lua["PopLib"].valid() && lua["PopLib"].get_type() == sol::type::table) + poplib = lua["PopLib"]; + else + { + poplib = lua.create_table(); + lua["PopLib"] = poplib; + } + + poplib.new_usertype( + "Color", + sol::constructors(), + + "GetRed", &PopLib::Color::GetRed, "GetGreen", &PopLib::Color::GetGreen, "GetBlue", &PopLib::Color::GetBlue, + "GetAlpha", &PopLib::Color::GetAlpha, "ToInt", &PopLib::Color::ToInt, + + "mRed", &PopLib::Color::mRed, "mGreen", &PopLib::Color::mGreen, "mBlue", &PopLib::Color::mBlue, "mAlpha", + &PopLib::Color::mAlpha); + + poplib["Color"]["Black"] = PopLib::Color::Black; + poplib["Color"]["White"] = PopLib::Color::White; +} \ No newline at end of file diff --git a/PopLib/scripting/lua/lkeycodes.cpp b/PopLib/scripting/lua/lkeycodes.cpp new file mode 100644 index 00000000..dfaf5266 --- /dev/null +++ b/PopLib/scripting/lua/lkeycodes.cpp @@ -0,0 +1,46 @@ +#include "lpoplib.hpp" +#include "misc/keycodes.hpp" + +using namespace PopLib; + +void open_keycodes(sol::state_view lua) +{ + sol::table poplib; + if (lua["PopLib"].valid() && lua["PopLib"].get_type() == sol::type::table) + poplib = lua["PopLib"]; + else + { + poplib = lua.create_table(); + lua["PopLib"] = poplib; + } + + sol::table keycode = lua.create_table(); + keycode["UNKNOWN"] = KEYCODE_UNKNOWN; + keycode["RETURN"] = KEYCODE_RETURN; + keycode["BACK"] = KEYCODE_BACK; + keycode["TAB"] = KEYCODE_TAB; + keycode["SPACE"] = KEYCODE_SPACE; + keycode["ESCAPE"] = KEYCODE_ESCAPE; + keycode["LEFT"] = KEYCODE_LEFT; + keycode["RIGHT"] = KEYCODE_RIGHT; + keycode["UP"] = KEYCODE_UP; + keycode["DOWN"] = KEYCODE_DOWN; + keycode["SHIFT"] = KEYCODE_SHIFT; + keycode["CONTROL"] = KEYCODE_CONTROL; + keycode["ALT"] = KEYCODE_MENU; + keycode["DELETE"] = KEYCODE_DELETE; + keycode["INSERT"] = KEYCODE_INSERT; + keycode["HOME"] = KEYCODE_HOME; + keycode["END"] = KEYCODE_END; + + // F1–F12 + for (int i = 1; i <= 12; ++i) + { + keycode["F" + std::to_string(i)] = KEYCODE_F1 + (i - 1); + } + + poplib["KeyCode"] = keycode; + + poplib.set_function("GetKeyCodeFromName", &GetKeyCodeFromName); + poplib.set_function("GetKeyNameFromCode", &GetKeyNameFromCode); +} \ No newline at end of file diff --git a/PopLib/scripting/lua/lmatrix.cpp b/PopLib/scripting/lua/lmatrix.cpp new file mode 100644 index 00000000..fe810cd1 --- /dev/null +++ b/PopLib/scripting/lua/lmatrix.cpp @@ -0,0 +1,42 @@ +#include "lpoplib.hpp" +#include "math/matrix.hpp" + +using namespace PopLib; + +void open_matrix(sol::state_view lua) +{ + sol::table poplib; + if (lua["PopLib"].valid() && lua["PopLib"].get_type() == sol::type::table) + poplib = lua["PopLib"]; + else + { + poplib = lua.create_table(); + lua["PopLib"] = poplib; + } + + poplib.new_usertype( + "Matrix3", sol::constructors(), "ZeroMatrix", &PopLib::Matrix3::ZeroMatrix, + "LoadIdentity", &PopLib::Matrix3::LoadIdentity, sol::meta_function::multiplication, + sol::resolve(&PopLib::Matrix3::operator*), + sol::meta_function::to_string, [](const PopLib::Matrix3 &m) { return "Matrix3(...)"; }); + + poplib.new_usertype( + "Transform2D", + sol::constructors(), + sol::base_classes, sol::bases(), + + "Translate", &PopLib::Transform2D::Translate, "RotateRad", &PopLib::Transform2D::RotateRad, + "RotateDeg", &PopLib::Transform2D::RotateDeg, "Scale", &PopLib::Transform2D::Scale); + + poplib.new_usertype( + "Transform", sol::constructors(), "Reset", &PopLib::Transform::Reset, "Translate", + &PopLib::Transform::Translate, "RotateRad", &PopLib::Transform::RotateRad, "RotateDeg", + &PopLib::Transform::RotateDeg, "Scale", &PopLib::Transform::Scale, "GetMatrix", &PopLib::Transform::GetMatrix, + + "mTransX1", &PopLib::Transform::mTransX1, "mTransY1", &PopLib::Transform::mTransY1, "mTransX2", + &PopLib::Transform::mTransX2, "mTransY2", &PopLib::Transform::mTransY2, "mScaleX", &PopLib::Transform::mScaleX, + "mScaleY", &PopLib::Transform::mScaleY, "mRot", &PopLib::Transform::mRot, "mHaveRot", + &PopLib::Transform::mHaveRot, "mHaveScale", &PopLib::Transform::mHaveScale, "mComplex", + &PopLib::Transform::mComplex); +} \ No newline at end of file diff --git a/PopLib/scripting/lua/lpoint.cpp b/PopLib/scripting/lua/lpoint.cpp new file mode 100644 index 00000000..569e75e7 --- /dev/null +++ b/PopLib/scripting/lua/lpoint.cpp @@ -0,0 +1,110 @@ +#include "lpoplib.hpp" +#include "math/point.hpp" + +using namespace PopLib; + +template struct TPointWrapper : public TPoint +{ + using TPoint::TPoint; // inherit constructors + + TPointWrapper() : TPoint() + { + } + TPointWrapper(T x, T y) : TPoint(x, y) + { + } + TPointWrapper(const TPoint &p) : TPoint(p) + { + } + + TPointWrapper operator+(const TPointWrapper &other) const + { + return TPointWrapper(this->mX + other.mX, this->mY + other.mY); + } + + TPointWrapper operator-(const TPointWrapper &other) const + { + return TPointWrapper(this->mX - other.mX, this->mY - other.mY); + } + + TPointWrapper operator*(const TPointWrapper &other) const + { + return TPointWrapper(this->mX * other.mX, this->mY * other.mY); + } + + TPointWrapper operator/(const TPointWrapper &other) const + { + return TPointWrapper(this->mX / other.mX, this->mY / other.mY); + } + + TPointWrapper operator*(T s) const + { + return TPointWrapper(this->mX * s, this->mY * s); + } + + TPointWrapper operator/(T s) const + { + return TPointWrapper(this->mX / s, this->mY / s); + } + + bool operator==(const TPointWrapper &other) const + { + return this->mX == other.mX && this->mY == other.mY; + } +}; + +void open_point(sol::state_view lua) +{ + sol::table poplib; + if (lua["PopLib"].valid() && lua["PopLib"].get_type() == sol::type::table) + { + poplib = lua["PopLib"]; + } + else + { + poplib = lua.create_table(); + lua["PopLib"] = poplib; + } + + poplib.new_usertype>( + "Point", + sol::constructors(), TPointWrapper(int, int), + TPointWrapper(const TPoint &)>(), + "mX", &TPointWrapper::mX, "mY", &TPointWrapper::mY, sol::meta_function::equal_to, + &TPointWrapper::operator==, sol::meta_function::addition, &TPointWrapper::operator+, + sol::meta_function::subtraction, &TPointWrapper::operator-, sol::meta_function::multiplication, + sol::overload( + static_cast (TPointWrapper::*)(const TPointWrapper &) const>( + &TPointWrapper::operator*), + static_cast (TPointWrapper::*)(int) const>(&TPointWrapper::operator*)), + sol::meta_function::division, + sol::overload( + static_cast (TPointWrapper::*)(const TPointWrapper &) const>( + &TPointWrapper::operator/), + static_cast (TPointWrapper::*)(int) const>(&TPointWrapper::operator/)), + sol::meta_function::to_string, [](const TPointWrapper &p) { + return "Point(" + std::to_string(p.mX) + ", " + std::to_string(p.mY) + ")"; + }); + + poplib.new_usertype>( + "FPoint", + sol::constructors(), TPointWrapper(double, double), + TPointWrapper(const TPoint &)>(), + "mX", &TPointWrapper::mX, "mY", &TPointWrapper::mY, sol::meta_function::equal_to, + &TPointWrapper::operator==, sol::meta_function::addition, &TPointWrapper::operator+, + sol::meta_function::subtraction, &TPointWrapper::operator-, sol::meta_function::multiplication, + sol::overload( + static_cast (TPointWrapper::*)(const TPointWrapper &) const>( + &TPointWrapper::operator*), + static_cast (TPointWrapper::*)(double) const>( + &TPointWrapper::operator*)), + sol::meta_function::division, + sol::overload( + static_cast (TPointWrapper::*)(const TPointWrapper &) const>( + &TPointWrapper::operator/), + static_cast (TPointWrapper::*)(double) const>( + &TPointWrapper::operator/)), + sol::meta_function::to_string, [](const TPointWrapper &p) { + return "FPoint(" + std::to_string(p.mX) + ", " + std::to_string(p.mY) + ")"; + }); +} \ No newline at end of file diff --git a/PopLib/scripting/lua/lpoplib.cpp b/PopLib/scripting/lua/lpoplib.cpp new file mode 100644 index 00000000..78e05706 --- /dev/null +++ b/PopLib/scripting/lua/lpoplib.cpp @@ -0,0 +1,10 @@ +#include "lpoplib.hpp" + +void pop_openlibs(sol::state &lua) +{ + open_rect(lua); + open_color(lua); + open_keycodes(lua); + open_point(lua); + open_matrix(lua); +} \ No newline at end of file diff --git a/PopLib/scripting/lua/lpoplib.hpp b/PopLib/scripting/lua/lpoplib.hpp new file mode 100644 index 00000000..6f95e097 --- /dev/null +++ b/PopLib/scripting/lua/lpoplib.hpp @@ -0,0 +1,17 @@ +#ifndef LPOPLIB_HPP +#define LPOPLIB_HPP + +#pragma once + +#include +#include "../scripting_base.hpp" + +void(open_rect)(sol::state_view lua); +void(open_color)(sol::state_view lua); +void(open_keycodes)(sol::state_view lua); +void(open_point)(sol::state_view lua); +void(open_matrix)(sol::state_view lua); + +void(pop_openlibs)(sol::state &lua); + +#endif \ No newline at end of file diff --git a/PopLib/scripting/lua/lrect.cpp b/PopLib/scripting/lua/lrect.cpp new file mode 100644 index 00000000..a7161795 --- /dev/null +++ b/PopLib/scripting/lua/lrect.cpp @@ -0,0 +1,57 @@ +#include "lpoplib.hpp" +#include "math/rect.hpp" + +using namespace PopLib; + +void open_rect(sol::state_view lua) +{ + sol::table poplib; + if (lua["PopLib"].valid() && lua["PopLib"].get_type() == sol::type::table) + poplib = lua["PopLib"]; + else + { + poplib = lua.create_table(); + lua["PopLib"] = poplib; + } + + poplib.new_usertype( + "Rect", + sol::constructors(), "mX", + &PopLib::Rect::mX, "mY", &PopLib::Rect::mY, "mWidth", &PopLib::Rect::mWidth, "mHeight", &PopLib::Rect::mHeight, + + "Intersects", &PopLib::Rect::Intersects, "Intersection", &PopLib::Rect::Intersection, "Union", + static_cast(&PopLib::Rect::Union), "Contains", + sol::overload(static_cast(&PopLib::Rect::Contains), + static_cast &) const>(&PopLib::Rect::Contains)), + "Offset", + sol::overload(static_cast(&PopLib::Rect::Offset), + static_cast &)>(&PopLib::Rect::Offset)), + "Inflate", &PopLib::Rect::Inflate, sol::meta_function::to_string, + [](const PopLib::Rect &r) { + return "Rect(" + std::to_string(r.mX) + "," + std::to_string(r.mY) + "," + std::to_string(r.mWidth) + "," + + std::to_string(r.mHeight) + ")"; + }, + sol::meta_function::equal_to, &PopLib::Rect::operator==); + + poplib.new_usertype( + "FRect", + sol::constructors(), + "mX", &PopLib::FRect::mX, "mY", &PopLib::FRect::mY, "mWidth", &PopLib::FRect::mWidth, "mHeight", + &PopLib::FRect::mHeight, + + "Intersects", &PopLib::FRect::Intersects, "Intersection", &PopLib::FRect::Intersection, "Union", + static_cast(&PopLib::FRect::Union), "Contains", + sol::overload( + static_cast(&PopLib::FRect::Contains), + static_cast &) const>(&PopLib::FRect::Contains)), + "Offset", + sol::overload(static_cast(&PopLib::FRect::Offset), + static_cast &)>(&PopLib::FRect::Offset)), + "Inflate", &PopLib::FRect::Inflate, sol::meta_function::to_string, + [](const PopLib::FRect &r) { + return "FRect(" + std::to_string(r.mX) + "," + std::to_string(r.mY) + "," + std::to_string(r.mWidth) + "," + + std::to_string(r.mHeight) + ")"; + }, + sol::meta_function::equal_to, &PopLib::FRect::operator==); +} \ No newline at end of file diff --git a/PopLib/scripting/scripting_base.cpp b/PopLib/scripting/scripting_base.cpp new file mode 100644 index 00000000..b1dd1cbd --- /dev/null +++ b/PopLib/scripting/scripting_base.cpp @@ -0,0 +1,137 @@ +#include "scripting_base.hpp" +#include "debug/log.hpp" +#include "lua/lpoplib.hpp" +#include + +using namespace PopLib; + +bool ScriptingBase::Initialize() +{ + lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::os, sol::lib::string, sol::lib::math, + sol::lib::table); + + LOG_INFO("Lua scripting initialized"); + + pop_openlibs(lua); + + // replace print + lua["print"] = [](sol::variadic_args va) { + std::string combined; + bool first = true; + for (auto v : va) + { + if (!first) + combined += "\t"; + first = false; + + if (v.is()) + { + combined += v.as(); + } + else if (v.is()) + { + combined += (v.as() ? "true" : "false"); + } + else if (v.is()) + { + combined += std::to_string(v.as()); + } + else if (v.is()) + { + combined += std::to_string(v.as()); + } + else + { + combined += ""; + } + } + LOG_INFO(combined.c_str()); + }; + + return true; +} + +void ScriptingBase::Shutdown() +{ + lua.collect_garbage(); + LOG_INFO("Lua scripting shut down"); +} + +bool ScriptingBase::ExecuteFile(const PopString &fileName) +{ + std::filesystem::path fullPath = std::filesystem::path(scriptFolder) / fileName; + + if (!std::filesystem::exists(fullPath)) + { + LOG_ERROR("Lua file not found: %s", fullPath.c_str()); + return false; + } + + auto result = lua.safe_script_file( + fullPath.string(), [](lua_State * /*L*/, sol::protected_function_result pfr) -> sol::protected_function_result { + if (!pfr.valid()) + { + sol::error err = pfr; + LOG_ERROR("Lua execution error: %s", err.what()); + } + return pfr; + }); + + if (!result.valid()) + { + sol::error err = result; + LOG_ERROR("Failed to execute file '%s': %s", fullPath.string().c_str(), err.what()); + return false; + } + + LOG_INFO("Executed Lua file: %s", fullPath.string().c_str()); + return true; +} + +bool ScriptingBase::ExecuteString(const PopString &code) +{ + auto result = lua.safe_script( + std::string(code), [](lua_State * /*L*/, sol::protected_function_result pfr) -> sol::protected_function_result { + if (!pfr.valid()) + { + sol::error err = pfr; + LOG_ERROR("Lua execution error: %s", err.what()); + } + return pfr; + }); + + if (!result.valid()) + { + sol::error err = result; + LOG_ERROR("Failed to execute Lua string: %s", err.what()); + return false; + } + + return true; +} + +bool ScriptingBase::ExecuteFolder(const PopString &folderPath) +{ + std::filesystem::path fullPath = folderPath.empty() ? scriptFolder : folderPath; + + if (!std::filesystem::exists(fullPath) || !std::filesystem::is_directory(fullPath)) + { + LOG_ERROR("Lua folder not found: %s", fullPath.string().c_str()); + return false; + } + + for (auto &entry : std::filesystem::directory_iterator(fullPath)) + { + if (entry.is_regular_file() && entry.path().extension() == ".lua") + { + LOG_INFO("Executing Lua file: %s", entry.path().string().c_str()); + if (!ExecuteFile(entry.path().filename().string())) + { + LOG_ERROR("Failed to execute Lua file: %s", entry.path().string().c_str()); + return false; + } + } + } + + return true; +} diff --git a/PopLib/scripting/scripting_base.hpp b/PopLib/scripting/scripting_base.hpp new file mode 100644 index 00000000..7121d52a --- /dev/null +++ b/PopLib/scripting/scripting_base.hpp @@ -0,0 +1,48 @@ +#ifndef SCRIPTING_BASE_HPP +#define SCRIPTING_BASE_HPP + +#pragma once + +#include "../common.hpp" +#include "../appbase.hpp" +#include +#include +#include + +class ScriptingBase +{ + public: + ScriptingBase() = default; + virtual ~ScriptingBase() + { + Shutdown(); + } + + bool Initialize(); + void Shutdown(); + + bool ExecuteFile(const PopString &fileName); + bool ExecuteString(const PopString &code); + bool ExecuteFolder(const PopString &folderPath); + + template void RegisterFunction(const std::string &name, Func fn) + { + lua.set_function(name, fn); + } + + sol::state &GetLuaState() + { + return lua; + } + + void SetScriptFolder(const PopString &folder) + { + scriptFolder = folder; + } + + private: + sol::state lua; + PopString scriptFolder = "scripts/"; +}; + +#endif \ No newline at end of file diff --git a/PopLib/sol/config.hpp b/PopLib/sol/config.hpp new file mode 100644 index 00000000..c7c47192 --- /dev/null +++ b/PopLib/sol/config.hpp @@ -0,0 +1,53 @@ +// The MIT License (MIT) + +// Copyright (c) 2013-2020 Rapptz, ThePhD and contributors + +// 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. + +// This file was generated with a script. +// Generated 2022-06-25 08:14:19.336233 UTC +// This header was generated with sol v3.3.0 (revision eba86625) +// https://github.com/ThePhD/sol2 + +#ifndef SOL_SINGLE_CONFIG_HPP +#define SOL_SINGLE_CONFIG_HPP + +// beginning of sol/config.hpp + +/* Base, empty configuration file! + + To override, place a file in your include paths of the form: + +. (your include path here) +| sol (directory, or equivalent) + | config.hpp (your config.hpp file) + + So that when sol2 includes the file + +#include + + it gives you the configuration values you desire. Configuration values can be +seen in the safety.rst of the doc/src, or at +https://sol2.readthedocs.io/en/latest/safety.html ! You can also pass them through +the build system, or the command line options of your compiler. + +*/ + +// end of sol/config.hpp + +#endif // SOL_SINGLE_CONFIG_HPP \ No newline at end of file diff --git a/PopLib/sol/forward.hpp b/PopLib/sol/forward.hpp new file mode 100644 index 00000000..ccfcc3b1 --- /dev/null +++ b/PopLib/sol/forward.hpp @@ -0,0 +1,1321 @@ +// The MIT License (MIT) + +// Copyright (c) 2013-2020 Rapptz, ThePhD and contributors + +// 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. + +// This file was generated with a script. +// Generated 2022-06-25 08:14:19.328625 UTC +// This header was generated with sol v3.3.0 (revision eba86625) +// https://github.com/ThePhD/sol2 + +#ifndef SOL_SINGLE_INCLUDE_FORWARD_HPP +#define SOL_SINGLE_INCLUDE_FORWARD_HPP + +// beginning of sol/forward.hpp + +#ifndef SOL_FORWARD_HPP +#define SOL_FORWARD_HPP + +// beginning of sol/version.hpp + +#include + +#define SOL_VERSION_MAJOR 3 +#define SOL_VERSION_MINOR 2 +#define SOL_VERSION_PATCH 3 +#define SOL_VERSION_STRING "3.2.3" +#define SOL_VERSION ((SOL_VERSION_MAJOR * 100000) + (SOL_VERSION_MINOR * 100) + (SOL_VERSION_PATCH)) + +#define SOL_TOKEN_TO_STRING_POST_EXPANSION_I_(_TOKEN) #_TOKEN +#define SOL_TOKEN_TO_STRING_I_(_TOKEN) SOL_TOKEN_TO_STRING_POST_EXPANSION_I_(_TOKEN) + +#define SOL_CONCAT_TOKENS_POST_EXPANSION_I_(_LEFT, _RIGHT) _LEFT##_RIGHT +#define SOL_CONCAT_TOKENS_I_(_LEFT, _RIGHT) SOL_CONCAT_TOKENS_POST_EXPANSION_I_(_LEFT, _RIGHT) + +#define SOL_RAW_IS_ON(OP_SYMBOL) ((3 OP_SYMBOL 3) != 0) +#define SOL_RAW_IS_OFF(OP_SYMBOL) ((3 OP_SYMBOL 3) == 0) +#define SOL_RAW_IS_DEFAULT_ON(OP_SYMBOL) ((3 OP_SYMBOL 3) > 3) +#define SOL_RAW_IS_DEFAULT_OFF(OP_SYMBOL) ((3 OP_SYMBOL 3 OP_SYMBOL 3) < 0) + +#define SOL_IS_ON(OP_SYMBOL) SOL_RAW_IS_ON(OP_SYMBOL ## _I_) +#define SOL_IS_OFF(OP_SYMBOL) SOL_RAW_IS_OFF(OP_SYMBOL ## _I_) +#define SOL_IS_DEFAULT_ON(OP_SYMBOL) SOL_RAW_IS_DEFAULT_ON(OP_SYMBOL ## _I_) +#define SOL_IS_DEFAULT_OFF(OP_SYMBOL) SOL_RAW_IS_DEFAULT_OFF(OP_SYMBOL ## _I_) + +#define SOL_ON | +#define SOL_OFF ^ +#define SOL_DEFAULT_ON + +#define SOL_DEFAULT_OFF - + +#if defined(SOL_BUILD_CXX_MODE) + #if (SOL_BUILD_CXX_MODE != 0) + #define SOL_BUILD_CXX_MODE_I_ SOL_ON + #else + #define SOL_BUILD_CXX_MODE_I_ SOL_OFF + #endif +#elif defined(__cplusplus) + #define SOL_BUILD_CXX_MODE_I_ SOL_DEFAULT_ON +#else + #define SOL_BUILD_CXX_MODE_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_BUILD_C_MODE) + #if (SOL_BUILD_C_MODE != 0) + #define SOL_BUILD_C_MODE_I_ SOL_ON + #else + #define SOL_BUILD_C_MODE_I_ SOL_OFF + #endif +#elif defined(__STDC__) + #define SOL_BUILD_C_MODE_I_ SOL_DEFAULT_ON +#else + #define SOL_BUILD_C_MODE_I_ SOL_DEFAULT_OFF +#endif + +#if SOL_IS_ON(SOL_BUILD_C_MODE) + #include + #include + #include +#else + #include + #include + #include +#endif + +#if defined(SOL_COMPILER_VCXX) + #if defined(SOL_COMPILER_VCXX != 0) + #define SOL_COMPILER_VCXX_I_ SOL_ON + #else + #define SOL_COMPILER_VCXX_I_ SOL_OFF + #endif +#elif defined(_MSC_VER) + #define SOL_COMPILER_VCXX_I_ SOL_DEFAULT_ON +#else + #define SOL_COMPILER_VCXX_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_COMPILER_GCC) + #if defined(SOL_COMPILER_GCC != 0) + #define SOL_COMPILER_GCC_I_ SOL_ON + #else + #define SOL_COMPILER_GCC_I_ SOL_OFF + #endif +#elif defined(__GNUC__) + #define SOL_COMPILER_GCC_I_ SOL_DEFAULT_ON +#else + #define SOL_COMPILER_GCC_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_COMPILER_CLANG) + #if defined(SOL_COMPILER_CLANG != 0) + #define SOL_COMPILER_CLANG_I_ SOL_ON + #else + #define SOL_COMPILER_CLANG_I_ SOL_OFF + #endif +#elif defined(__clang__) + #define SOL_COMPILER_CLANG_I_ SOL_DEFAULT_ON +#else + #define SOL_COMPILER_CLANG_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_COMPILER_EDG) + #if defined(SOL_COMPILER_EDG != 0) + #define SOL_COMPILER_EDG_I_ SOL_ON + #else + #define SOL_COMPILER_EDG_I_ SOL_OFF + #endif +#else + #define SOL_COMPILER_EDG_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_COMPILER_MINGW) + #if (SOL_COMPILER_MINGW != 0) + #define SOL_COMPILER_MINGW_I_ SOL_ON + #else + #define SOL_COMPILER_MINGW_I_ SOL_OFF + #endif +#elif defined(__MINGW32__) + #define SOL_COMPILER_MINGW_I_ SOL_DEFAULT_ON +#else + #define SOL_COMPILER_MINGW_I_ SOL_DEFAULT_OFF +#endif + +#if SIZE_MAX <= 0xFFFFULL + #define SOL_PLATFORM_X16_I_ SOL_ON + #define SOL_PLATFORM_X86_I_ SOL_OFF + #define SOL_PLATFORM_X64_I_ SOL_OFF +#elif SIZE_MAX <= 0xFFFFFFFFULL + #define SOL_PLATFORM_X16_I_ SOL_OFF + #define SOL_PLATFORM_X86_I_ SOL_ON + #define SOL_PLATFORM_X64_I_ SOL_OFF +#else + #define SOL_PLATFORM_X16_I_ SOL_OFF + #define SOL_PLATFORM_X86_I_ SOL_OFF + #define SOL_PLATFORM_X64_I_ SOL_ON +#endif + +#define SOL_PLATFORM_ARM32_I_ SOL_OFF +#define SOL_PLATFORM_ARM64_I_ SOL_OFF + +#if defined(SOL_PLATFORM_WINDOWS) + #if (SOL_PLATFORM_WINDOWS != 0) + #define SOL_PLATFORM_WINDOWS_I_ SOL_ON + #else + #define SOL_PLATFORM_WINDOWS_I_ SOL_OFF + #endif +#elif defined(_WIN32) + #define SOL_PLATFORM_WINDOWS_I_ SOL_DEFAULT_ON +#else + #define SOL_PLATFORM_WINDOWS_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_PLATFORM_CYGWIN) + #if (SOL_PLATFORM_CYGWIN != 0) + #define SOL_PLATFORM_CYGWIN_I_ SOL_ON + #else + #define SOL_PLATFORM_CYGWIN_I_ SOL_ON + #endif +#elif defined(__CYGWIN__) + #define SOL_PLATFORM_CYGWIN_I_ SOL_DEFAULT_ON +#else + #define SOL_PLATFORM_CYGWIN_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_PLATFORM_APPLE) + #if (SOL_PLATFORM_APPLE != 0) + #define SOL_PLATFORM_APPLE_I_ SOL_ON + #else + #define SOL_PLATFORM_APPLE_I_ SOL_OFF + #endif +#elif defined(__APPLE__) + #define SOL_PLATFORM_APPLE_I_ SOL_DEFAULT_ON +#else + #define SOL_PLATFORM_APPLE_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_PLATFORM_UNIX) + #if (SOL_PLATFORM_UNIX != 0) + #define SOL_PLATFORM_UNIXLIKE_I_ SOL_ON + #else + #define SOL_PLATFORM_UNIXLIKE_I_ SOL_OFF + #endif +#elif defined(__unix__) + #define SOL_PLATFORM_UNIXLIKE_I_ SOL_DEFAUKT_ON +#else + #define SOL_PLATFORM_UNIXLIKE_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_PLATFORM_LINUX) + #if (SOL_PLATFORM_LINUX != 0) + #define SOL_PLATFORM_LINUXLIKE_I_ SOL_ON + #else + #define SOL_PLATFORM_LINUXLIKE_I_ SOL_OFF + #endif +#elif defined(__LINUX__) + #define SOL_PLATFORM_LINUXLIKE_I_ SOL_DEFAUKT_ON +#else + #define SOL_PLATFORM_LINUXLIKE_I_ SOL_DEFAULT_OFF +#endif + +#define SOL_PLATFORM_APPLE_IPHONE_I_ SOL_OFF +#define SOL_PLATFORM_BSDLIKE_I_ SOL_OFF + +#if defined(SOL_IN_DEBUG_DETECTED) + #if SOL_IN_DEBUG_DETECTED != 0 + #define SOL_DEBUG_BUILD_I_ SOL_ON + #else + #define SOL_DEBUG_BUILD_I_ SOL_OFF + #endif +#elif !defined(NDEBUG) + #if SOL_IS_ON(SOL_COMPILER_VCXX) && defined(_DEBUG) + #define SOL_DEBUG_BUILD_I_ SOL_ON + #elif (SOL_IS_ON(SOL_COMPILER_CLANG) || SOL_IS_ON(SOL_COMPILER_GCC)) && !defined(__OPTIMIZE__) + #define SOL_DEBUG_BUILD_I_ SOL_ON + #else + #define SOL_DEBUG_BUILD_I_ SOL_OFF + #endif +#else + #define SOL_DEBUG_BUILD_I_ SOL_DEFAULT_OFF +#endif // We are in a debug mode of some sort + +#if defined(SOL_NO_EXCEPTIONS) + #if (SOL_NO_EXCEPTIONS != 0) + #define SOL_EXCEPTIONS_I_ SOL_OFF + #else + #define SOL_EXCEPTIONS_I_ SOL_ON + #endif +#elif SOL_IS_ON(SOL_COMPILER_VCXX) + #if !defined(_CPPUNWIND) + #define SOL_EXCEPTIONS_I_ SOL_OFF + #else + #define SOL_EXCEPTIONS_I_ SOL_ON + #endif +#elif SOL_IS_ON(SOL_COMPILER_CLANG) || SOL_IS_ON(SOL_COMPILER_GCC) + #if !defined(__EXCEPTIONS) + #define SOL_EXCEPTIONS_I_ SOL_OFF + #else + #define SOL_EXCEPTIONS_I_ SOL_ON + #endif +#else + #define SOL_EXCEPTIONS_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_NO_RTTI) + #if (SOL_NO_RTTI != 0) + #define SOL_RTTI_I_ SOL_OFF + #else + #define SOL_RTTI_I_ SOL_ON + #endif +#elif SOL_IS_ON(SOL_COMPILER_VCXX) + #if !defined(_CPPRTTI) + #define SOL_RTTI_I_ SOL_OFF + #else + #define SOL_RTTI_I_ SOL_ON + #endif +#elif SOL_IS_ON(SOL_COMPILER_CLANG) || SOL_IS_ON(SOL_COMPILER_GCC) + #if !defined(__GXX_RTTI) + #define SOL_RTTI_I_ SOL_OFF + #else + #define SOL_RTTI_I_ SOL_ON + #endif +#else + #define SOL_RTTI_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_NO_THREAD_LOCAL) + #if SOL_NO_THREAD_LOCAL != 0 + #define SOL_USE_THREAD_LOCAL_I_ SOL_OFF + #else + #define SOL_USE_THREAD_LOCAL_I_ SOL_ON + #endif +#else + #define SOL_USE_THREAD_LOCAL_I_ SOL_DEFAULT_ON +#endif // thread_local keyword is bjorked on some platforms + +#if defined(SOL_ALL_SAFETIES_ON) + #if SOL_ALL_SAFETIES_ON != 0 + #define SOL_ALL_SAFETIES_ON_I_ SOL_ON + #else + #define SOL_ALL_SAFETIES_ON_I_ SOL_OFF + #endif +#else + #define SOL_ALL_SAFETIES_ON_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_SAFE_GETTER) + #if SOL_SAFE_GETTER != 0 + #define SOL_SAFE_GETTER_I_ SOL_ON + #else + #define SOL_SAFE_GETTER_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_GETTER_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_GETTER_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_GETTER_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_USERTYPE) + #if SOL_SAFE_USERTYPE != 0 + #define SOL_SAFE_USERTYPE_I_ SOL_ON + #else + #define SOL_SAFE_USERTYPE_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_USERTYPE_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_USERTYPE_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_USERTYPE_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_REFERENCES) + #if SOL_SAFE_REFERENCES != 0 + #define SOL_SAFE_REFERENCES_I_ SOL_ON + #else + #define SOL_SAFE_REFERENCES_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_REFERENCES_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_REFERENCES_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_REFERENCES_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_FUNCTIONS) + #if SOL_SAFE_FUNCTIONS != 0 + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_ON + #else + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_OFF + #endif +#elif defined (SOL_SAFE_FUNCTION_OBJECTS) + #if SOL_SAFE_FUNCTION_OBJECTS != 0 + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_ON + #else + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_FUNCTION_CALLS) + #if SOL_SAFE_FUNCTION_CALLS != 0 + #define SOL_SAFE_FUNCTION_CALLS_I_ SOL_ON + #else + #define SOL_SAFE_FUNCTION_CALLS_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_FUNCTION_CALLS_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_FUNCTION_CALLS_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_FUNCTION_CALLS_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_PROXIES) + #if SOL_SAFE_PROXIES != 0 + #define SOL_SAFE_PROXIES_I_ SOL_ON + #else + #define SOL_SAFE_PROXIES_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_PROXIES_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_PROXIES_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_PROXIES_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_NUMERICS) + #if SOL_SAFE_NUMERICS != 0 + #define SOL_SAFE_NUMERICS_I_ SOL_ON + #else + #define SOL_SAFE_NUMERICS_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_NUMERICS_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_NUMERICS_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_NUMERICS_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_ALL_INTEGER_VALUES_FIT) + #if (SOL_ALL_INTEGER_VALUES_FIT != 0) + #define SOL_ALL_INTEGER_VALUES_FIT_I_ SOL_ON + #else + #define SOL_ALL_INTEGER_VALUES_FIT_I_ SOL_OFF + #endif +#elif !SOL_IS_DEFAULT_OFF(SOL_SAFE_NUMERICS) && SOL_IS_OFF(SOL_SAFE_NUMERICS) + // if numerics is intentionally turned off, flip this on + #define SOL_ALL_INTEGER_VALUES_FIT_I_ SOL_DEFAULT_ON +#else + // default to off + #define SOL_ALL_INTEGER_VALUES_FIT_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_SAFE_STACK_CHECK) + #if SOL_SAFE_STACK_CHECK != 0 + #define SOL_SAFE_STACK_CHECK_I_ SOL_ON + #else + #define SOL_SAFE_STACK_CHECK_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_STACK_CHECK_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_STACK_CHECK_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_STACK_CHECK_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_NO_CHECK_NUMBER_PRECISION) + #if SOL_NO_CHECK_NUMBER_PRECISION != 0 + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_OFF + #else + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_ON + #endif +#elif defined(SOL_NO_CHECKING_NUMBER_PRECISION) + #if SOL_NO_CHECKING_NUMBER_PRECISION != 0 + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_OFF + #else + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_ON + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_ON + #elif SOL_IS_ON(SOL_SAFE_NUMERICS) + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_DEFAULT_ON + #else + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_STRINGS_ARE_NUMBERS) + #if (SOL_STRINGS_ARE_NUMBERS != 0) + #define SOL_STRINGS_ARE_NUMBERS_I_ SOL_ON + #else + #define SOL_STRINGS_ARE_NUMBERS_I_ SOL_OFF + #endif +#else + #define SOL_STRINGS_ARE_NUMBERS_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_ENABLE_INTEROP) + #if SOL_ENABLE_INTEROP != 0 + #define SOL_USE_INTEROP_I_ SOL_ON + #else + #define SOL_USE_INTEROP_I_ SOL_OFF + #endif +#elif defined(SOL_USE_INTEROP) + #if SOL_USE_INTEROP != 0 + #define SOL_USE_INTEROP_I_ SOL_ON + #else + #define SOL_USE_INTEROP_I_ SOL_OFF + #endif +#else + #define SOL_USE_INTEROP_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_NO_NIL) + #if (SOL_NO_NIL != 0) + #define SOL_NIL_I_ SOL_OFF + #else + #define SOL_NIL_I_ SOL_ON + #endif +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) || defined(__OBJC__) || defined(nil) + #define SOL_NIL_I_ SOL_DEFAULT_OFF +#else + #define SOL_NIL_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_USERTYPE_TYPE_BINDING_INFO) + #if (SOL_USERTYPE_TYPE_BINDING_INFO != 0) + #define SOL_USERTYPE_TYPE_BINDING_INFO_I_ SOL_ON + #else + #define SOL_USERTYPE_TYPE_BINDING_INFO_I_ SOL_OFF + #endif +#else + #define SOL_USERTYPE_TYPE_BINDING_INFO_I_ SOL_DEFAULT_ON +#endif // We should generate a my_type.__type table with lots of class information for usertypes + +#if defined(SOL_AUTOMAGICAL_TYPES_BY_DEFAULT) + #if (SOL_AUTOMAGICAL_TYPES_BY_DEFAULT != 0) + #define SOL_DEFAULT_AUTOMAGICAL_USERTYPES_I_ SOL_ON + #else + #define SOL_DEFAULT_AUTOMAGICAL_USERTYPES_I_ SOL_OFF + #endif +#elif defined(SOL_DEFAULT_AUTOMAGICAL_USERTYPES) + #if (SOL_DEFAULT_AUTOMAGICAL_USERTYPES != 0) + #define SOL_DEFAULT_AUTOMAGICAL_USERTYPES_I_ SOL_ON + #else + #define SOL_DEFAULT_AUTOMAGICAL_USERTYPES_I_ SOL_OFF + #endif +#else + #define SOL_DEFAULT_AUTOMAGICAL_USERTYPES_I_ SOL_DEFAULT_ON +#endif // make is_automagical on/off by default + +#if defined(SOL_STD_VARIANT) + #if (SOL_STD_VARIANT != 0) + #define SOL_STD_VARIANT_I_ SOL_ON + #else + #define SOL_STD_VARIANT_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_COMPILER_CLANG) && SOL_IS_ON(SOL_PLATFORM_APPLE) + #if defined(__has_include) + #if __has_include() + #define SOL_STD_VARIANT_I_ SOL_DEFAULT_ON + #else + #define SOL_STD_VARIANT_I_ SOL_DEFAULT_OFF + #endif + #else + #define SOL_STD_VARIANT_I_ SOL_DEFAULT_OFF + #endif + #else + #define SOL_STD_VARIANT_I_ SOL_DEFAULT_ON + #endif +#endif // make is_automagical on/off by default + +#if defined(SOL_NOEXCEPT_FUNCTION_TYPE) + #if (SOL_NOEXCEPT_FUNCTION_TYPE != 0) + #define SOL_USE_NOEXCEPT_FUNCTION_TYPE_I_ SOL_ON + #else + #define SOL_USE_NOEXCEPT_FUNCTION_TYPE_I_ SOL_OFF + #endif +#else + #if defined(__cpp_noexcept_function_type) + #define SOL_USE_NOEXCEPT_FUNCTION_TYPE_I_ SOL_ON + #elif SOL_IS_ON(SOL_COMPILER_VCXX) && (defined(_MSVC_LANG) && (_MSVC_LANG < 201403L)) + // There is a bug in the VC++ compiler?? + // on /std:c++latest under x86 conditions (VS 15.5.2), + // compiler errors are tossed for noexcept markings being on function types + // that are identical in every other way to their non-noexcept marked types function types... + // VS 2019: There is absolutely a bug. + #define SOL_USE_NOEXCEPT_FUNCTION_TYPE_I_ SOL_OFF + #else + #define SOL_USE_NOEXCEPT_FUNCTION_TYPE_I_ SOL_DEFAULT_ON + #endif +#endif // noexcept is part of a function's type + +#if defined(SOL_STACK_STRING_OPTIMIZATION_SIZE) && SOL_STACK_STRING_OPTIMIZATION_SIZE > 0 + #define SOL_OPTIMIZATION_STRING_CONVERSION_STACK_SIZE_I_ SOL_STACK_STRING_OPTIMIZATION_SIZE +#else + #define SOL_OPTIMIZATION_STRING_CONVERSION_STACK_SIZE_I_ 1024 +#endif + +#if defined(SOL_ID_SIZE) && SOL_ID_SIZE > 0 + #define SOL_ID_SIZE_I_ SOL_ID_SIZE +#else + #define SOL_ID_SIZE_I_ 512 +#endif + +#if defined(LUA_IDSIZE) && LUA_IDSIZE > 0 + #define SOL_FILE_ID_SIZE_I_ LUA_IDSIZE +#elif defined(SOL_ID_SIZE) && SOL_ID_SIZE > 0 + #define SOL_FILE_ID_SIZE_I_ SOL_FILE_ID_SIZE +#else + #define SOL_FILE_ID_SIZE_I_ 2048 +#endif + +#if defined(SOL_PRINT_ERRORS) + #if (SOL_PRINT_ERRORS != 0) + #define SOL_PRINT_ERRORS_I_ SOL_ON + #else + #define SOL_PRINT_ERRORS_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_PRINT_ERRORS_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_PRINT_ERRORS_I_ SOL_DEFAULT_ON + #else + #define SOL_PRINT_ERRORS_I_ SOL_OFF + #endif +#endif + +#if defined(SOL_DEFAULT_PASS_ON_ERROR) + #if (SOL_DEFAULT_PASS_ON_ERROR != 0) + #define SOL_DEFAULT_PASS_ON_ERROR_I_ SOL_ON + #else + #define SOL_DEFAULT_PASS_ON_ERROR_I_ SOL_OFF + #endif +#else + #define SOL_DEFAULT_PASS_ON_ERROR_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_USING_CXX_LUA) + #if (SOL_USING_CXX_LUA != 0) + #define SOL_USE_CXX_LUA_I_ SOL_ON + #else + #define SOL_USE_CXX_LUA_I_ SOL_OFF + #endif +#elif defined(SOL_USE_CXX_LUA) + #if (SOL_USE_CXX_LUA != 0) + #define SOL_USE_CXX_LUA_I_ SOL_ON + #else + #define SOL_USE_CXX_LUA_I_ SOL_OFF + #endif +#else + #define SOL_USE_CXX_LUA_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_USING_CXX_LUAJIT) + #if (SOL_USING_CXX_LUA != 0) + #define SOL_USE_CXX_LUAJIT_I_ SOL_ON + #else + #define SOL_USE_CXX_LUAJIT_I_ SOL_OFF + #endif +#elif defined(SOL_USE_CXX_LUAJIT) + #if (SOL_USE_CXX_LUA != 0) + #define SOL_USE_CXX_LUAJIT_I_ SOL_ON + #else + #define SOL_USE_CXX_LUAJIT_I_ SOL_OFF + #endif +#else + #define SOL_USE_CXX_LUAJIT_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_NO_LUA_HPP) + #if (SOL_NO_LUA_HPP != 0) + #define SOL_USE_LUA_HPP_I_ SOL_OFF + #else + #define SOL_USE_LUA_HPP_I_ SOL_ON + #endif +#elif defined(SOL_USING_CXX_LUA) + #define SOL_USE_LUA_HPP_I_ SOL_OFF +#elif defined(__has_include) + #if __has_include() + #define SOL_USE_LUA_HPP_I_ SOL_ON + #else + #define SOL_USE_LUA_HPP_I_ SOL_OFF + #endif +#else + #define SOL_USE_LUA_HPP_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_CONTAINERS_START) + #define SOL_CONTAINER_START_INDEX_I_ SOL_CONTAINERS_START +#elif defined(SOL_CONTAINERS_START_INDEX) + #define SOL_CONTAINER_START_INDEX_I_ SOL_CONTAINERS_START_INDEX +#elif defined(SOL_CONTAINER_START_INDEX) + #define SOL_CONTAINER_START_INDEX_I_ SOL_CONTAINER_START_INDEX +#else + #define SOL_CONTAINER_START_INDEX_I_ 1 +#endif + +#if defined (SOL_NO_MEMORY_ALIGNMENT) + #if (SOL_NO_MEMORY_ALIGNMENT != 0) + #define SOL_ALIGN_MEMORY_I_ SOL_OFF + #else + #define SOL_ALIGN_MEMORY_I_ SOL_ON + #endif +#else + #define SOL_ALIGN_MEMORY_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_USE_BOOST) + #if (SOL_USE_BOOST != 0) + #define SOL_USE_BOOST_I_ SOL_ON + #else + #define SOL_USE_BOOST_I_ SOL_OFF + #endif +#else + #define SOL_USE_BOOST_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_USE_UNSAFE_BASE_LOOKUP) + #if (SOL_USE_UNSAFE_BASE_LOOKUP != 0) + #define SOL_USE_UNSAFE_BASE_LOOKUP_I_ SOL_ON + #else + #define SOL_USE_UNSAFE_BASE_LOOKUP_I_ SOL_OFF + #endif +#else + #define SOL_USE_UNSAFE_BASE_LOOKUP_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_INSIDE_UNREAL) + #if (SOL_INSIDE_UNREAL != 0) + #define SOL_INSIDE_UNREAL_ENGINE_I_ SOL_ON + #else + #define SOL_INSIDE_UNREAL_ENGINE_I_ SOL_OFF + #endif +#else + #if defined(UE_BUILD_DEBUG) || defined(UE_BUILD_DEVELOPMENT) || defined(UE_BUILD_TEST) || defined(UE_BUILD_SHIPPING) || defined(UE_SERVER) + #define SOL_INSIDE_UNREAL_ENGINE_I_ SOL_DEFAULT_ON + #else + #define SOL_INSIDE_UNREAL_ENGINE_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_NO_COMPAT) + #if (SOL_NO_COMPAT != 0) + #define SOL_USE_COMPATIBILITY_LAYER_I_ SOL_OFF + #else + #define SOL_USE_COMPATIBILITY_LAYER_I_ SOL_ON + #endif +#else + #define SOL_USE_COMPATIBILITY_LAYER_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_GET_FUNCTION_POINTER_UNSAFE) + #if (SOL_GET_FUNCTION_POINTER_UNSAFE != 0) + #define SOL_GET_FUNCTION_POINTER_UNSAFE_I_ SOL_ON + #else + #define SOL_GET_FUNCTION_POINTER_UNSAFE_I_ SOL_OFF + #endif +#else + #define SOL_GET_FUNCTION_POINTER_UNSAFE_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_FUNCTION_CALL_VALUE_SEMANTICS) + #if (SOL_FUNCTION_CALL_VALUE_SEMANTICS != 0) + #define SOL_FUNCTION_CALL_VALUE_SEMANTICS_I_ SOL_ON + #else + #define SOL_FUNCTION_CALL_VALUE_SEMANTICS_I_ SOL_OFF + #endif +#else + #define SOL_FUNCTION_CALL_VALUE_SEMANTICS_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_MINGW_CCTYPE_IS_POISONED) + #if (SOL_MINGW_CCTYPE_IS_POISONED != 0) + #define SOL_MINGW_CCTYPE_IS_POISONED_I_ SOL_ON + #else + #define SOL_MINGW_CCTYPE_IS_POISONED_I_ SOL_OFF + #endif +#elif SOL_IS_ON(SOL_COMPILER_MINGW) && defined(__GNUC__) && (__GNUC__ < 6) + // MinGW is off its rocker in some places... + #define SOL_MINGW_CCTYPE_IS_POISONED_I_ SOL_DEFAULT_ON +#else + #define SOL_MINGW_CCTYPE_IS_POISONED_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_CHAR8_T) + #if (SOL_CHAR8_T != 0) + #define SOL_CHAR8_T_I_ SOL_ON + #else + #define SOL_CHAR8_T_I_ SOL_OFF + #endif +#else + #if defined(__cpp_char8_t) + #define SOL_CHAR8_T_I_ SOL_DEFAULT_ON + #else + #define SOL_CHAR8_T_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if SOL_IS_ON(SOL_USE_BOOST) + #include + + #if BOOST_VERSION >= 107500 // Since Boost 1.75.0 boost::none is constexpr + #define SOL_BOOST_NONE_CONSTEXPR_I_ constexpr + #else + #define SOL_BOOST_NONE_CONSTEXPR_I_ const + #endif // BOOST_VERSION +#else + // assume boost isn't using a garbage version + #define SOL_BOOST_NONE_CONSTEXPR_I_ constexpr +#endif + +#if defined(SOL2_CI) + #if (SOL2_CI != 0) + #define SOL2_CI_I_ SOL_ON + #else + #define SOL2_CI_I_ SOL_OFF + #endif +#else + #define SOL2_CI_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_C_ASSERT) + #define SOL_USER_C_ASSERT_I_ SOL_ON +#else + #define SOL_USER_C_ASSERT_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_M_ASSERT) + #define SOL_USER_M_ASSERT_I_ SOL_ON +#else + #define SOL_USER_M_ASSERT_I_ SOL_DEFAULT_OFF +#endif + +// beginning of sol/prologue.hpp + +#if defined(SOL_PROLOGUE_I_) + #error "[sol2] Library Prologue was already included in translation unit and not properly ended with an epilogue." +#endif + +#define SOL_PROLOGUE_I_ 1 + +#if SOL_IS_ON(SOL_BUILD_CXX_MODE) + #define _FWD(...) static_cast( __VA_ARGS__ ) + + #if SOL_IS_ON(SOL_COMPILER_GCC) || SOL_IS_ON(SOL_COMPILER_CLANG) + #define _MOVE(...) static_cast<__typeof( __VA_ARGS__ )&&>( __VA_ARGS__ ) + #else + #include + + #define _MOVE(...) static_cast<::std::remove_reference_t<( __VA_ARGS__ )>&&>( __VA_OPT__(,) ) + #endif +#endif + +// end of sol/prologue.hpp + +// beginning of sol/epilogue.hpp + +#if !defined(SOL_PROLOGUE_I_) + #error "[sol2] Library Prologue is missing from this translation unit." +#else + #undef SOL_PROLOGUE_I_ +#endif + +#if SOL_IS_ON(SOL_BUILD_CXX_MODE) + #undef _FWD + #undef _MOVE +#endif + +// end of sol/epilogue.hpp + +// beginning of sol/detail/build_version.hpp + +#if defined(SOL_DLL) + #if (SOL_DLL != 0) + #define SOL_DLL_I_ SOL_ON + #else + #define SOL_DLL_I_ SOL_OFF + #endif +#elif SOL_IS_ON(SOL_COMPILER_VCXX) && (defined(DLL_) || defined(_DLL)) + #define SOL_DLL_I_ SOL_DEFAULT_ON +#else + #define SOL_DLL_I_ SOL_DEFAULT_OFF +#endif // DLL definition + +#if defined(SOL_HEADER_ONLY) + #if (SOL_HEADER_ONLY != 0) + #define SOL_HEADER_ONLY_I_ SOL_ON + #else + #define SOL_HEADER_ONLY_I_ SOL_OFF + #endif +#else + #define SOL_HEADER_ONLY_I_ SOL_DEFAULT_OFF +#endif // Header only library + +#if defined(SOL_BUILD) + #if (SOL_BUILD != 0) + #define SOL_BUILD_I_ SOL_ON + #else + #define SOL_BUILD_I_ SOL_OFF + #endif +#elif SOL_IS_ON(SOL_HEADER_ONLY) + #define SOL_BUILD_I_ SOL_DEFAULT_OFF +#else + #define SOL_BUILD_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_UNITY_BUILD) + #if (SOL_UNITY_BUILD != 0) + #define SOL_UNITY_BUILD_I_ SOL_ON + #else + #define SOL_UNITY_BUILD_I_ SOL_OFF + #endif +#else + #define SOL_UNITY_BUILD_I_ SOL_DEFAULT_OFF +#endif // Header only library + +#if defined(SOL_C_FUNCTION_LINKAGE) + #define SOL_C_FUNCTION_LINKAGE_I_ SOL_C_FUNCTION_LINKAGE +#else + #if SOL_IS_ON(SOL_BUILD_CXX_MODE) + // C++ + #define SOL_C_FUNCTION_LINKAGE_I_ extern "C" + #else + // normal + #define SOL_C_FUNCTION_LINKAGE_I_ + #endif // C++ or not +#endif // Linkage specification for C functions + +#if defined(SOL_API_LINKAGE) + #define SOL_API_LINKAGE_I_ SOL_API_LINKAGE +#else + #if SOL_IS_ON(SOL_DLL) + #if SOL_IS_ON(SOL_COMPILER_VCXX) || SOL_IS_ON(SOL_PLATFORM_WINDOWS) || SOL_IS_ON(SOL_PLATFORM_CYGWIN) + // MSVC Compiler; or, Windows, or Cygwin platforms + #if SOL_IS_ON(SOL_BUILD) + // Building the library + #if SOL_IS_ON(SOL_COMPILER_GCC) + // Using GCC + #define SOL_API_LINKAGE_I_ __attribute__((dllexport)) + #else + // Using Clang, MSVC, etc... + #define SOL_API_LINKAGE_I_ __declspec(dllexport) + #endif + #else + #if SOL_IS_ON(SOL_COMPILER_GCC) + #define SOL_API_LINKAGE_I_ __attribute__((dllimport)) + #else + #define SOL_API_LINKAGE_I_ __declspec(dllimport) + #endif + #endif + #else + // extern if building normally on non-MSVC + #define SOL_API_LINKAGE_I_ extern + #endif + #elif SOL_IS_ON(SOL_UNITY_BUILD) + // Built-in library, like how stb typical works + #if SOL_IS_ON(SOL_HEADER_ONLY) + // Header only, so functions are defined "inline" + #define SOL_API_LINKAGE_I_ inline + #else + // Not header only, so seperately compiled files + #define SOL_API_LINKAGE_I_ extern + #endif + #else + // Normal static library + #if SOL_IS_ON(SOL_BUILD_CXX_MODE) + #define SOL_API_LINKAGE_I_ + #else + #define SOL_API_LINKAGE_I_ extern + #endif + #endif // DLL or not +#endif // Build definitions + +#if defined(SOL_PUBLIC_FUNC_DECL) + #define SOL_PUBLIC_FUNC_DECL_I_ SOL_PUBLIC_FUNC_DECL +#else + #define SOL_PUBLIC_FUNC_DECL_I_ SOL_API_LINKAGE_I_ +#endif + +#if defined(SOL_INTERNAL_FUNC_DECL_) + #define SOL_INTERNAL_FUNC_DECL_I_ SOL_INTERNAL_FUNC_DECL_ +#else + #define SOL_INTERNAL_FUNC_DECL_I_ SOL_API_LINKAGE_I_ +#endif + +#if defined(SOL_PUBLIC_FUNC_DEF) + #define SOL_PUBLIC_FUNC_DEF_I_ SOL_PUBLIC_FUNC_DEF +#else + #define SOL_PUBLIC_FUNC_DEF_I_ SOL_API_LINKAGE_I_ +#endif + +#if defined(SOL_INTERNAL_FUNC_DEF) + #define SOL_INTERNAL_FUNC_DEF_I_ SOL_INTERNAL_FUNC_DEF +#else + #define SOL_INTERNAL_FUNC_DEF_I_ SOL_API_LINKAGE_I_ +#endif + +#if defined(SOL_FUNC_DECL) + #define SOL_FUNC_DECL_I_ SOL_FUNC_DECL +#elif SOL_IS_ON(SOL_HEADER_ONLY) + #define SOL_FUNC_DECL_I_ +#elif SOL_IS_ON(SOL_DLL) + #if SOL_IS_ON(SOL_COMPILER_VCXX) + #if SOL_IS_ON(SOL_BUILD) + #define SOL_FUNC_DECL_I_ extern __declspec(dllexport) + #else + #define SOL_FUNC_DECL_I_ extern __declspec(dllimport) + #endif + #elif SOL_IS_ON(SOL_COMPILER_GCC) || SOL_IS_ON(SOL_COMPILER_CLANG) + #define SOL_FUNC_DECL_I_ extern __attribute__((visibility("default"))) + #else + #define SOL_FUNC_DECL_I_ extern + #endif +#endif + +#if defined(SOL_FUNC_DEFN) + #define SOL_FUNC_DEFN_I_ SOL_FUNC_DEFN +#elif SOL_IS_ON(SOL_HEADER_ONLY) + #define SOL_FUNC_DEFN_I_ inline +#elif SOL_IS_ON(SOL_DLL) + #if SOL_IS_ON(SOL_COMPILER_VCXX) + #if SOL_IS_ON(SOL_BUILD) + #define SOL_FUNC_DEFN_I_ __declspec(dllexport) + #else + #define SOL_FUNC_DEFN_I_ __declspec(dllimport) + #endif + #elif SOL_IS_ON(SOL_COMPILER_GCC) || SOL_IS_ON(SOL_COMPILER_CLANG) + #define SOL_FUNC_DEFN_I_ __attribute__((visibility("default"))) + #else + #define SOL_FUNC_DEFN_I_ + #endif +#endif + +#if defined(SOL_HIDDEN_FUNC_DECL) + #define SOL_HIDDEN_FUNC_DECL_I_ SOL_HIDDEN_FUNC_DECL +#elif SOL_IS_ON(SOL_HEADER_ONLY) + #define SOL_HIDDEN_FUNC_DECL_I_ +#elif SOL_IS_ON(SOL_DLL) + #if SOL_IS_ON(SOL_COMPILER_VCXX) + #if SOL_IS_ON(SOL_BUILD) + #define SOL_HIDDEN_FUNC_DECL_I_ extern __declspec(dllexport) + #else + #define SOL_HIDDEN_FUNC_DECL_I_ extern __declspec(dllimport) + #endif + #elif SOL_IS_ON(SOL_COMPILER_GCC) || SOL_IS_ON(SOL_COMPILER_CLANG) + #define SOL_HIDDEN_FUNC_DECL_I_ extern __attribute__((visibility("default"))) + #else + #define SOL_HIDDEN_FUNC_DECL_I_ extern + #endif +#endif + +#if defined(SOL_HIDDEN_FUNC_DEFN) + #define SOL_HIDDEN_FUNC_DEFN_I_ SOL_HIDDEN_FUNC_DEFN +#elif SOL_IS_ON(SOL_HEADER_ONLY) + #define SOL_HIDDEN_FUNC_DEFN_I_ inline +#elif SOL_IS_ON(SOL_DLL) + #if SOL_IS_ON(SOL_COMPILER_VCXX) + #if SOL_IS_ON(SOL_BUILD) + #define SOL_HIDDEN_FUNC_DEFN_I_ + #else + #define SOL_HIDDEN_FUNC_DEFN_I_ + #endif + #elif SOL_IS_ON(SOL_COMPILER_GCC) || SOL_IS_ON(SOL_COMPILER_CLANG) + #define SOL_HIDDEN_FUNC_DEFN_I_ __attribute__((visibility("hidden"))) + #else + #define SOL_HIDDEN_FUNC_DEFN_I_ + #endif +#endif + +// end of sol/detail/build_version.hpp + +// end of sol/version.hpp + +#include +#include +#include + +#if SOL_IS_ON(SOL_USE_CXX_LUA) || SOL_IS_ON(SOL_USE_CXX_LUAJIT) +struct lua_State; +#else +extern "C" { +struct lua_State; +} +#endif // C++ Mangling for Lua vs. Not + +namespace sol { + + enum class type; + + class stateless_reference; + template + class basic_reference; + using reference = basic_reference; + using main_reference = basic_reference; + class stateless_stack_reference; + class stack_reference; + + template + class basic_bytecode; + + struct lua_value; + + struct proxy_base_tag; + template + struct proxy_base; + template + struct table_proxy; + + template + class basic_table_core; + template + using table_core = basic_table_core; + template + using main_table_core = basic_table_core; + template + using stack_table_core = basic_table_core; + template + using basic_table = basic_table_core; + using table = table_core; + using global_table = table_core; + using main_table = main_table_core; + using main_global_table = main_table_core; + using stack_table = stack_table_core; + using stack_global_table = stack_table_core; + + template + struct basic_lua_table; + using lua_table = basic_lua_table; + using stack_lua_table = basic_lua_table; + + template + class basic_usertype; + template + using usertype = basic_usertype; + template + using stack_usertype = basic_usertype; + + template + class basic_metatable; + using metatable = basic_metatable; + using stack_metatable = basic_metatable; + + template + struct basic_environment; + using environment = basic_environment; + using main_environment = basic_environment; + using stack_environment = basic_environment; + + template + class basic_function; + template + class basic_protected_function; + using unsafe_function = basic_function; + using safe_function = basic_protected_function; + using main_unsafe_function = basic_function; + using main_safe_function = basic_protected_function; + using stack_unsafe_function = basic_function; + using stack_safe_function = basic_protected_function; + using stack_aligned_unsafe_function = basic_function; + using stack_aligned_safe_function = basic_protected_function; + using protected_function = safe_function; + using main_protected_function = main_safe_function; + using stack_protected_function = stack_safe_function; + using stack_aligned_protected_function = stack_aligned_safe_function; +#if SOL_IS_ON(SOL_SAFE_FUNCTION_OBJECTS) + using function = protected_function; + using main_function = main_protected_function; + using stack_function = stack_protected_function; + using stack_aligned_function = stack_aligned_safe_function; +#else + using function = unsafe_function; + using main_function = main_unsafe_function; + using stack_function = stack_unsafe_function; + using stack_aligned_function = stack_aligned_unsafe_function; +#endif + using stack_aligned_stack_handler_function = basic_protected_function; + + struct unsafe_function_result; + struct protected_function_result; + using safe_function_result = protected_function_result; +#if SOL_IS_ON(SOL_SAFE_FUNCTION_OBJECTS) + using function_result = safe_function_result; +#else + using function_result = unsafe_function_result; +#endif + + template + class basic_object_base; + template + class basic_object; + template + class basic_userdata; + template + class basic_lightuserdata; + template + class basic_coroutine; + template + class basic_packaged_coroutine; + template + class basic_thread; + + using object = basic_object; + using userdata = basic_userdata; + using lightuserdata = basic_lightuserdata; + using thread = basic_thread; + using coroutine = basic_coroutine; + using packaged_coroutine = basic_packaged_coroutine; + using main_object = basic_object; + using main_userdata = basic_userdata; + using main_lightuserdata = basic_lightuserdata; + using main_coroutine = basic_coroutine; + using stack_object = basic_object; + using stack_userdata = basic_userdata; + using stack_lightuserdata = basic_lightuserdata; + using stack_thread = basic_thread; + using stack_coroutine = basic_coroutine; + + struct stack_proxy_base; + struct stack_proxy; + struct variadic_args; + struct variadic_results; + struct stack_count; + struct this_state; + struct this_main_state; + struct this_environment; + + class state_view; + class state; + + template + struct as_table_t; + template + struct as_container_t; + template + struct nested; + template + struct light; + template + struct user; + template + struct as_args_t; + template + struct protect_t; + template + struct policy_wrapper; + + template + struct usertype_traits; + template + struct unique_usertype_traits; + + template + struct types { + typedef std::make_index_sequence indices; + static constexpr std::size_t size() { + return sizeof...(Args); + } + }; + + template + struct derive : std::false_type { + typedef types<> type; + }; + + template + struct base : std::false_type { + typedef types<> type; + }; + + template + struct weak_derive { + static bool value; + }; + + template + bool weak_derive::value = false; + + namespace stack { + struct record; + } + +#if SOL_IS_OFF(SOL_USE_BOOST) + template + class optional; + + template + class optional; +#endif + + using check_handler_type = int(lua_State*, int, type, type, const char*); + +} // namespace sol + +#define SOL_BASE_CLASSES(T, ...) \ + namespace sol { \ + template <> \ + struct base : std::true_type { \ + typedef ::sol::types<__VA_ARGS__> type; \ + }; \ + } \ + void a_sol3_detail_function_decl_please_no_collide() +#define SOL_DERIVED_CLASSES(T, ...) \ + namespace sol { \ + template <> \ + struct derive : std::true_type { \ + typedef ::sol::types<__VA_ARGS__> type; \ + }; \ + } \ + void a_sol3_detail_function_decl_please_no_collide() + +#endif // SOL_FORWARD_HPP +// end of sol/forward.hpp + +#endif // SOL_SINGLE_INCLUDE_FORWARD_HPP \ No newline at end of file diff --git a/PopLib/sol/sol.hpp b/PopLib/sol/sol.hpp new file mode 100644 index 00000000..ef0c8050 --- /dev/null +++ b/PopLib/sol/sol.hpp @@ -0,0 +1,28907 @@ +// The MIT License (MIT) + +// Copyright (c) 2013-2020 Rapptz, ThePhD and contributors + +// 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. + +// This file was generated with a script. +// Generated 2022-06-25 08:14:19.151876 UTC +// This header was generated with sol v3.3.0 (revision eba86625) +// https://github.com/ThePhD/sol2 + +#ifndef SOL_SINGLE_INCLUDE_HPP +#define SOL_SINGLE_INCLUDE_HPP + +// beginning of sol/sol.hpp + +#ifndef SOL_HPP +#define SOL_HPP + +// beginning of sol/version.hpp + +#include + +#define SOL_VERSION_MAJOR 3 +#define SOL_VERSION_MINOR 2 +#define SOL_VERSION_PATCH 3 +#define SOL_VERSION_STRING "3.2.3" +#define SOL_VERSION ((SOL_VERSION_MAJOR * 100000) + (SOL_VERSION_MINOR * 100) + (SOL_VERSION_PATCH)) + +#define SOL_TOKEN_TO_STRING_POST_EXPANSION_I_(_TOKEN) #_TOKEN +#define SOL_TOKEN_TO_STRING_I_(_TOKEN) SOL_TOKEN_TO_STRING_POST_EXPANSION_I_(_TOKEN) + +#define SOL_CONCAT_TOKENS_POST_EXPANSION_I_(_LEFT, _RIGHT) _LEFT##_RIGHT +#define SOL_CONCAT_TOKENS_I_(_LEFT, _RIGHT) SOL_CONCAT_TOKENS_POST_EXPANSION_I_(_LEFT, _RIGHT) + +#define SOL_RAW_IS_ON(OP_SYMBOL) ((3 OP_SYMBOL 3) != 0) +#define SOL_RAW_IS_OFF(OP_SYMBOL) ((3 OP_SYMBOL 3) == 0) +#define SOL_RAW_IS_DEFAULT_ON(OP_SYMBOL) ((3 OP_SYMBOL 3) > 3) +#define SOL_RAW_IS_DEFAULT_OFF(OP_SYMBOL) ((3 OP_SYMBOL 3 OP_SYMBOL 3) < 0) + +#define SOL_IS_ON(OP_SYMBOL) SOL_RAW_IS_ON(OP_SYMBOL ## _I_) +#define SOL_IS_OFF(OP_SYMBOL) SOL_RAW_IS_OFF(OP_SYMBOL ## _I_) +#define SOL_IS_DEFAULT_ON(OP_SYMBOL) SOL_RAW_IS_DEFAULT_ON(OP_SYMBOL ## _I_) +#define SOL_IS_DEFAULT_OFF(OP_SYMBOL) SOL_RAW_IS_DEFAULT_OFF(OP_SYMBOL ## _I_) + +#define SOL_ON | +#define SOL_OFF ^ +#define SOL_DEFAULT_ON + +#define SOL_DEFAULT_OFF - + +#if defined(SOL_BUILD_CXX_MODE) + #if (SOL_BUILD_CXX_MODE != 0) + #define SOL_BUILD_CXX_MODE_I_ SOL_ON + #else + #define SOL_BUILD_CXX_MODE_I_ SOL_OFF + #endif +#elif defined(__cplusplus) + #define SOL_BUILD_CXX_MODE_I_ SOL_DEFAULT_ON +#else + #define SOL_BUILD_CXX_MODE_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_BUILD_C_MODE) + #if (SOL_BUILD_C_MODE != 0) + #define SOL_BUILD_C_MODE_I_ SOL_ON + #else + #define SOL_BUILD_C_MODE_I_ SOL_OFF + #endif +#elif defined(__STDC__) + #define SOL_BUILD_C_MODE_I_ SOL_DEFAULT_ON +#else + #define SOL_BUILD_C_MODE_I_ SOL_DEFAULT_OFF +#endif + +#if SOL_IS_ON(SOL_BUILD_C_MODE) + #include + #include + #include +#else + #include + #include + #include +#endif + +#if defined(SOL_COMPILER_VCXX) + #if defined(SOL_COMPILER_VCXX != 0) + #define SOL_COMPILER_VCXX_I_ SOL_ON + #else + #define SOL_COMPILER_VCXX_I_ SOL_OFF + #endif +#elif defined(_MSC_VER) + #define SOL_COMPILER_VCXX_I_ SOL_DEFAULT_ON +#else + #define SOL_COMPILER_VCXX_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_COMPILER_GCC) + #if defined(SOL_COMPILER_GCC != 0) + #define SOL_COMPILER_GCC_I_ SOL_ON + #else + #define SOL_COMPILER_GCC_I_ SOL_OFF + #endif +#elif defined(__GNUC__) + #define SOL_COMPILER_GCC_I_ SOL_DEFAULT_ON +#else + #define SOL_COMPILER_GCC_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_COMPILER_CLANG) + #if defined(SOL_COMPILER_CLANG != 0) + #define SOL_COMPILER_CLANG_I_ SOL_ON + #else + #define SOL_COMPILER_CLANG_I_ SOL_OFF + #endif +#elif defined(__clang__) + #define SOL_COMPILER_CLANG_I_ SOL_DEFAULT_ON +#else + #define SOL_COMPILER_CLANG_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_COMPILER_EDG) + #if defined(SOL_COMPILER_EDG != 0) + #define SOL_COMPILER_EDG_I_ SOL_ON + #else + #define SOL_COMPILER_EDG_I_ SOL_OFF + #endif +#else + #define SOL_COMPILER_EDG_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_COMPILER_MINGW) + #if (SOL_COMPILER_MINGW != 0) + #define SOL_COMPILER_MINGW_I_ SOL_ON + #else + #define SOL_COMPILER_MINGW_I_ SOL_OFF + #endif +#elif defined(__MINGW32__) + #define SOL_COMPILER_MINGW_I_ SOL_DEFAULT_ON +#else + #define SOL_COMPILER_MINGW_I_ SOL_DEFAULT_OFF +#endif + +#if SIZE_MAX <= 0xFFFFULL + #define SOL_PLATFORM_X16_I_ SOL_ON + #define SOL_PLATFORM_X86_I_ SOL_OFF + #define SOL_PLATFORM_X64_I_ SOL_OFF +#elif SIZE_MAX <= 0xFFFFFFFFULL + #define SOL_PLATFORM_X16_I_ SOL_OFF + #define SOL_PLATFORM_X86_I_ SOL_ON + #define SOL_PLATFORM_X64_I_ SOL_OFF +#else + #define SOL_PLATFORM_X16_I_ SOL_OFF + #define SOL_PLATFORM_X86_I_ SOL_OFF + #define SOL_PLATFORM_X64_I_ SOL_ON +#endif + +#define SOL_PLATFORM_ARM32_I_ SOL_OFF +#define SOL_PLATFORM_ARM64_I_ SOL_OFF + +#if defined(SOL_PLATFORM_WINDOWS) + #if (SOL_PLATFORM_WINDOWS != 0) + #define SOL_PLATFORM_WINDOWS_I_ SOL_ON + #else + #define SOL_PLATFORM_WINDOWS_I_ SOL_OFF + #endif +#elif defined(_WIN32) + #define SOL_PLATFORM_WINDOWS_I_ SOL_DEFAULT_ON +#else + #define SOL_PLATFORM_WINDOWS_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_PLATFORM_CYGWIN) + #if (SOL_PLATFORM_CYGWIN != 0) + #define SOL_PLATFORM_CYGWIN_I_ SOL_ON + #else + #define SOL_PLATFORM_CYGWIN_I_ SOL_ON + #endif +#elif defined(__CYGWIN__) + #define SOL_PLATFORM_CYGWIN_I_ SOL_DEFAULT_ON +#else + #define SOL_PLATFORM_CYGWIN_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_PLATFORM_APPLE) + #if (SOL_PLATFORM_APPLE != 0) + #define SOL_PLATFORM_APPLE_I_ SOL_ON + #else + #define SOL_PLATFORM_APPLE_I_ SOL_OFF + #endif +#elif defined(__APPLE__) + #define SOL_PLATFORM_APPLE_I_ SOL_DEFAULT_ON +#else + #define SOL_PLATFORM_APPLE_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_PLATFORM_UNIX) + #if (SOL_PLATFORM_UNIX != 0) + #define SOL_PLATFORM_UNIXLIKE_I_ SOL_ON + #else + #define SOL_PLATFORM_UNIXLIKE_I_ SOL_OFF + #endif +#elif defined(__unix__) + #define SOL_PLATFORM_UNIXLIKE_I_ SOL_DEFAUKT_ON +#else + #define SOL_PLATFORM_UNIXLIKE_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_PLATFORM_LINUX) + #if (SOL_PLATFORM_LINUX != 0) + #define SOL_PLATFORM_LINUXLIKE_I_ SOL_ON + #else + #define SOL_PLATFORM_LINUXLIKE_I_ SOL_OFF + #endif +#elif defined(__LINUX__) + #define SOL_PLATFORM_LINUXLIKE_I_ SOL_DEFAUKT_ON +#else + #define SOL_PLATFORM_LINUXLIKE_I_ SOL_DEFAULT_OFF +#endif + +#define SOL_PLATFORM_APPLE_IPHONE_I_ SOL_OFF +#define SOL_PLATFORM_BSDLIKE_I_ SOL_OFF + +#if defined(SOL_IN_DEBUG_DETECTED) + #if SOL_IN_DEBUG_DETECTED != 0 + #define SOL_DEBUG_BUILD_I_ SOL_ON + #else + #define SOL_DEBUG_BUILD_I_ SOL_OFF + #endif +#elif !defined(NDEBUG) + #if SOL_IS_ON(SOL_COMPILER_VCXX) && defined(_DEBUG) + #define SOL_DEBUG_BUILD_I_ SOL_ON + #elif (SOL_IS_ON(SOL_COMPILER_CLANG) || SOL_IS_ON(SOL_COMPILER_GCC)) && !defined(__OPTIMIZE__) + #define SOL_DEBUG_BUILD_I_ SOL_ON + #else + #define SOL_DEBUG_BUILD_I_ SOL_OFF + #endif +#else + #define SOL_DEBUG_BUILD_I_ SOL_DEFAULT_OFF +#endif // We are in a debug mode of some sort + +#if defined(SOL_NO_EXCEPTIONS) + #if (SOL_NO_EXCEPTIONS != 0) + #define SOL_EXCEPTIONS_I_ SOL_OFF + #else + #define SOL_EXCEPTIONS_I_ SOL_ON + #endif +#elif SOL_IS_ON(SOL_COMPILER_VCXX) + #if !defined(_CPPUNWIND) + #define SOL_EXCEPTIONS_I_ SOL_OFF + #else + #define SOL_EXCEPTIONS_I_ SOL_ON + #endif +#elif SOL_IS_ON(SOL_COMPILER_CLANG) || SOL_IS_ON(SOL_COMPILER_GCC) + #if !defined(__EXCEPTIONS) + #define SOL_EXCEPTIONS_I_ SOL_OFF + #else + #define SOL_EXCEPTIONS_I_ SOL_ON + #endif +#else + #define SOL_EXCEPTIONS_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_NO_RTTI) + #if (SOL_NO_RTTI != 0) + #define SOL_RTTI_I_ SOL_OFF + #else + #define SOL_RTTI_I_ SOL_ON + #endif +#elif SOL_IS_ON(SOL_COMPILER_VCXX) + #if !defined(_CPPRTTI) + #define SOL_RTTI_I_ SOL_OFF + #else + #define SOL_RTTI_I_ SOL_ON + #endif +#elif SOL_IS_ON(SOL_COMPILER_CLANG) || SOL_IS_ON(SOL_COMPILER_GCC) + #if !defined(__GXX_RTTI) + #define SOL_RTTI_I_ SOL_OFF + #else + #define SOL_RTTI_I_ SOL_ON + #endif +#else + #define SOL_RTTI_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_NO_THREAD_LOCAL) + #if SOL_NO_THREAD_LOCAL != 0 + #define SOL_USE_THREAD_LOCAL_I_ SOL_OFF + #else + #define SOL_USE_THREAD_LOCAL_I_ SOL_ON + #endif +#else + #define SOL_USE_THREAD_LOCAL_I_ SOL_DEFAULT_ON +#endif // thread_local keyword is bjorked on some platforms + +#if defined(SOL_ALL_SAFETIES_ON) + #if SOL_ALL_SAFETIES_ON != 0 + #define SOL_ALL_SAFETIES_ON_I_ SOL_ON + #else + #define SOL_ALL_SAFETIES_ON_I_ SOL_OFF + #endif +#else + #define SOL_ALL_SAFETIES_ON_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_SAFE_GETTER) + #if SOL_SAFE_GETTER != 0 + #define SOL_SAFE_GETTER_I_ SOL_ON + #else + #define SOL_SAFE_GETTER_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_GETTER_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_GETTER_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_GETTER_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_USERTYPE) + #if SOL_SAFE_USERTYPE != 0 + #define SOL_SAFE_USERTYPE_I_ SOL_ON + #else + #define SOL_SAFE_USERTYPE_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_USERTYPE_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_USERTYPE_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_USERTYPE_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_REFERENCES) + #if SOL_SAFE_REFERENCES != 0 + #define SOL_SAFE_REFERENCES_I_ SOL_ON + #else + #define SOL_SAFE_REFERENCES_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_REFERENCES_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_REFERENCES_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_REFERENCES_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_FUNCTIONS) + #if SOL_SAFE_FUNCTIONS != 0 + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_ON + #else + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_OFF + #endif +#elif defined (SOL_SAFE_FUNCTION_OBJECTS) + #if SOL_SAFE_FUNCTION_OBJECTS != 0 + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_ON + #else + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_FUNCTION_OBJECTS_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_FUNCTION_CALLS) + #if SOL_SAFE_FUNCTION_CALLS != 0 + #define SOL_SAFE_FUNCTION_CALLS_I_ SOL_ON + #else + #define SOL_SAFE_FUNCTION_CALLS_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_FUNCTION_CALLS_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_FUNCTION_CALLS_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_FUNCTION_CALLS_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_PROXIES) + #if SOL_SAFE_PROXIES != 0 + #define SOL_SAFE_PROXIES_I_ SOL_ON + #else + #define SOL_SAFE_PROXIES_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_PROXIES_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_PROXIES_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_PROXIES_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_SAFE_NUMERICS) + #if SOL_SAFE_NUMERICS != 0 + #define SOL_SAFE_NUMERICS_I_ SOL_ON + #else + #define SOL_SAFE_NUMERICS_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_NUMERICS_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_NUMERICS_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_NUMERICS_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_ALL_INTEGER_VALUES_FIT) + #if (SOL_ALL_INTEGER_VALUES_FIT != 0) + #define SOL_ALL_INTEGER_VALUES_FIT_I_ SOL_ON + #else + #define SOL_ALL_INTEGER_VALUES_FIT_I_ SOL_OFF + #endif +#elif !SOL_IS_DEFAULT_OFF(SOL_SAFE_NUMERICS) && SOL_IS_OFF(SOL_SAFE_NUMERICS) + // if numerics is intentionally turned off, flip this on + #define SOL_ALL_INTEGER_VALUES_FIT_I_ SOL_DEFAULT_ON +#else + // default to off + #define SOL_ALL_INTEGER_VALUES_FIT_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_SAFE_STACK_CHECK) + #if SOL_SAFE_STACK_CHECK != 0 + #define SOL_SAFE_STACK_CHECK_I_ SOL_ON + #else + #define SOL_SAFE_STACK_CHECK_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_SAFE_STACK_CHECK_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_SAFE_STACK_CHECK_I_ SOL_DEFAULT_ON + #else + #define SOL_SAFE_STACK_CHECK_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_NO_CHECK_NUMBER_PRECISION) + #if SOL_NO_CHECK_NUMBER_PRECISION != 0 + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_OFF + #else + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_ON + #endif +#elif defined(SOL_NO_CHECKING_NUMBER_PRECISION) + #if SOL_NO_CHECKING_NUMBER_PRECISION != 0 + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_OFF + #else + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_ON + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_ON + #elif SOL_IS_ON(SOL_SAFE_NUMERICS) + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_DEFAULT_ON + #else + #define SOL_NUMBER_PRECISION_CHECKS_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_STRINGS_ARE_NUMBERS) + #if (SOL_STRINGS_ARE_NUMBERS != 0) + #define SOL_STRINGS_ARE_NUMBERS_I_ SOL_ON + #else + #define SOL_STRINGS_ARE_NUMBERS_I_ SOL_OFF + #endif +#else + #define SOL_STRINGS_ARE_NUMBERS_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_ENABLE_INTEROP) + #if SOL_ENABLE_INTEROP != 0 + #define SOL_USE_INTEROP_I_ SOL_ON + #else + #define SOL_USE_INTEROP_I_ SOL_OFF + #endif +#elif defined(SOL_USE_INTEROP) + #if SOL_USE_INTEROP != 0 + #define SOL_USE_INTEROP_I_ SOL_ON + #else + #define SOL_USE_INTEROP_I_ SOL_OFF + #endif +#else + #define SOL_USE_INTEROP_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_NO_NIL) + #if (SOL_NO_NIL != 0) + #define SOL_NIL_I_ SOL_OFF + #else + #define SOL_NIL_I_ SOL_ON + #endif +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) || defined(__OBJC__) || defined(nil) + #define SOL_NIL_I_ SOL_DEFAULT_OFF +#else + #define SOL_NIL_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_USERTYPE_TYPE_BINDING_INFO) + #if (SOL_USERTYPE_TYPE_BINDING_INFO != 0) + #define SOL_USERTYPE_TYPE_BINDING_INFO_I_ SOL_ON + #else + #define SOL_USERTYPE_TYPE_BINDING_INFO_I_ SOL_OFF + #endif +#else + #define SOL_USERTYPE_TYPE_BINDING_INFO_I_ SOL_DEFAULT_ON +#endif // We should generate a my_type.__type table with lots of class information for usertypes + +#if defined(SOL_AUTOMAGICAL_TYPES_BY_DEFAULT) + #if (SOL_AUTOMAGICAL_TYPES_BY_DEFAULT != 0) + #define SOL_DEFAULT_AUTOMAGICAL_USERTYPES_I_ SOL_ON + #else + #define SOL_DEFAULT_AUTOMAGICAL_USERTYPES_I_ SOL_OFF + #endif +#elif defined(SOL_DEFAULT_AUTOMAGICAL_USERTYPES) + #if (SOL_DEFAULT_AUTOMAGICAL_USERTYPES != 0) + #define SOL_DEFAULT_AUTOMAGICAL_USERTYPES_I_ SOL_ON + #else + #define SOL_DEFAULT_AUTOMAGICAL_USERTYPES_I_ SOL_OFF + #endif +#else + #define SOL_DEFAULT_AUTOMAGICAL_USERTYPES_I_ SOL_DEFAULT_ON +#endif // make is_automagical on/off by default + +#if defined(SOL_STD_VARIANT) + #if (SOL_STD_VARIANT != 0) + #define SOL_STD_VARIANT_I_ SOL_ON + #else + #define SOL_STD_VARIANT_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_COMPILER_CLANG) && SOL_IS_ON(SOL_PLATFORM_APPLE) + #if defined(__has_include) + #if __has_include() + #define SOL_STD_VARIANT_I_ SOL_DEFAULT_ON + #else + #define SOL_STD_VARIANT_I_ SOL_DEFAULT_OFF + #endif + #else + #define SOL_STD_VARIANT_I_ SOL_DEFAULT_OFF + #endif + #else + #define SOL_STD_VARIANT_I_ SOL_DEFAULT_ON + #endif +#endif // make is_automagical on/off by default + +#if defined(SOL_NOEXCEPT_FUNCTION_TYPE) + #if (SOL_NOEXCEPT_FUNCTION_TYPE != 0) + #define SOL_USE_NOEXCEPT_FUNCTION_TYPE_I_ SOL_ON + #else + #define SOL_USE_NOEXCEPT_FUNCTION_TYPE_I_ SOL_OFF + #endif +#else + #if defined(__cpp_noexcept_function_type) + #define SOL_USE_NOEXCEPT_FUNCTION_TYPE_I_ SOL_ON + #elif SOL_IS_ON(SOL_COMPILER_VCXX) && (defined(_MSVC_LANG) && (_MSVC_LANG < 201403L)) + // There is a bug in the VC++ compiler?? + // on /std:c++latest under x86 conditions (VS 15.5.2), + // compiler errors are tossed for noexcept markings being on function types + // that are identical in every other way to their non-noexcept marked types function types... + // VS 2019: There is absolutely a bug. + #define SOL_USE_NOEXCEPT_FUNCTION_TYPE_I_ SOL_OFF + #else + #define SOL_USE_NOEXCEPT_FUNCTION_TYPE_I_ SOL_DEFAULT_ON + #endif +#endif // noexcept is part of a function's type + +#if defined(SOL_STACK_STRING_OPTIMIZATION_SIZE) && SOL_STACK_STRING_OPTIMIZATION_SIZE > 0 + #define SOL_OPTIMIZATION_STRING_CONVERSION_STACK_SIZE_I_ SOL_STACK_STRING_OPTIMIZATION_SIZE +#else + #define SOL_OPTIMIZATION_STRING_CONVERSION_STACK_SIZE_I_ 1024 +#endif + +#if defined(SOL_ID_SIZE) && SOL_ID_SIZE > 0 + #define SOL_ID_SIZE_I_ SOL_ID_SIZE +#else + #define SOL_ID_SIZE_I_ 512 +#endif + +#if defined(LUA_IDSIZE) && LUA_IDSIZE > 0 + #define SOL_FILE_ID_SIZE_I_ LUA_IDSIZE +#elif defined(SOL_ID_SIZE) && SOL_ID_SIZE > 0 + #define SOL_FILE_ID_SIZE_I_ SOL_FILE_ID_SIZE +#else + #define SOL_FILE_ID_SIZE_I_ 2048 +#endif + +#if defined(SOL_PRINT_ERRORS) + #if (SOL_PRINT_ERRORS != 0) + #define SOL_PRINT_ERRORS_I_ SOL_ON + #else + #define SOL_PRINT_ERRORS_I_ SOL_OFF + #endif +#else + #if SOL_IS_ON(SOL_ALL_SAFETIES_ON) + #define SOL_PRINT_ERRORS_I_ SOL_ON + #elif SOL_IS_ON(SOL_DEBUG_BUILD) + #define SOL_PRINT_ERRORS_I_ SOL_DEFAULT_ON + #else + #define SOL_PRINT_ERRORS_I_ SOL_OFF + #endif +#endif + +#if defined(SOL_DEFAULT_PASS_ON_ERROR) + #if (SOL_DEFAULT_PASS_ON_ERROR != 0) + #define SOL_DEFAULT_PASS_ON_ERROR_I_ SOL_ON + #else + #define SOL_DEFAULT_PASS_ON_ERROR_I_ SOL_OFF + #endif +#else + #define SOL_DEFAULT_PASS_ON_ERROR_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_USING_CXX_LUA) + #if (SOL_USING_CXX_LUA != 0) + #define SOL_USE_CXX_LUA_I_ SOL_ON + #else + #define SOL_USE_CXX_LUA_I_ SOL_OFF + #endif +#elif defined(SOL_USE_CXX_LUA) + #if (SOL_USE_CXX_LUA != 0) + #define SOL_USE_CXX_LUA_I_ SOL_ON + #else + #define SOL_USE_CXX_LUA_I_ SOL_OFF + #endif +#else + #define SOL_USE_CXX_LUA_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_USING_CXX_LUAJIT) + #if (SOL_USING_CXX_LUA != 0) + #define SOL_USE_CXX_LUAJIT_I_ SOL_ON + #else + #define SOL_USE_CXX_LUAJIT_I_ SOL_OFF + #endif +#elif defined(SOL_USE_CXX_LUAJIT) + #if (SOL_USE_CXX_LUA != 0) + #define SOL_USE_CXX_LUAJIT_I_ SOL_ON + #else + #define SOL_USE_CXX_LUAJIT_I_ SOL_OFF + #endif +#else + #define SOL_USE_CXX_LUAJIT_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_NO_LUA_HPP) + #if (SOL_NO_LUA_HPP != 0) + #define SOL_USE_LUA_HPP_I_ SOL_OFF + #else + #define SOL_USE_LUA_HPP_I_ SOL_ON + #endif +#elif defined(SOL_USING_CXX_LUA) + #define SOL_USE_LUA_HPP_I_ SOL_OFF +#elif defined(__has_include) + #if __has_include() + #define SOL_USE_LUA_HPP_I_ SOL_ON + #else + #define SOL_USE_LUA_HPP_I_ SOL_OFF + #endif +#else + #define SOL_USE_LUA_HPP_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_CONTAINERS_START) + #define SOL_CONTAINER_START_INDEX_I_ SOL_CONTAINERS_START +#elif defined(SOL_CONTAINERS_START_INDEX) + #define SOL_CONTAINER_START_INDEX_I_ SOL_CONTAINERS_START_INDEX +#elif defined(SOL_CONTAINER_START_INDEX) + #define SOL_CONTAINER_START_INDEX_I_ SOL_CONTAINER_START_INDEX +#else + #define SOL_CONTAINER_START_INDEX_I_ 1 +#endif + +#if defined (SOL_NO_MEMORY_ALIGNMENT) + #if (SOL_NO_MEMORY_ALIGNMENT != 0) + #define SOL_ALIGN_MEMORY_I_ SOL_OFF + #else + #define SOL_ALIGN_MEMORY_I_ SOL_ON + #endif +#else + #define SOL_ALIGN_MEMORY_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_USE_BOOST) + #if (SOL_USE_BOOST != 0) + #define SOL_USE_BOOST_I_ SOL_ON + #else + #define SOL_USE_BOOST_I_ SOL_OFF + #endif +#else + #define SOL_USE_BOOST_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_USE_UNSAFE_BASE_LOOKUP) + #if (SOL_USE_UNSAFE_BASE_LOOKUP != 0) + #define SOL_USE_UNSAFE_BASE_LOOKUP_I_ SOL_ON + #else + #define SOL_USE_UNSAFE_BASE_LOOKUP_I_ SOL_OFF + #endif +#else + #define SOL_USE_UNSAFE_BASE_LOOKUP_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_INSIDE_UNREAL) + #if (SOL_INSIDE_UNREAL != 0) + #define SOL_INSIDE_UNREAL_ENGINE_I_ SOL_ON + #else + #define SOL_INSIDE_UNREAL_ENGINE_I_ SOL_OFF + #endif +#else + #if defined(UE_BUILD_DEBUG) || defined(UE_BUILD_DEVELOPMENT) || defined(UE_BUILD_TEST) || defined(UE_BUILD_SHIPPING) || defined(UE_SERVER) + #define SOL_INSIDE_UNREAL_ENGINE_I_ SOL_DEFAULT_ON + #else + #define SOL_INSIDE_UNREAL_ENGINE_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if defined(SOL_NO_COMPAT) + #if (SOL_NO_COMPAT != 0) + #define SOL_USE_COMPATIBILITY_LAYER_I_ SOL_OFF + #else + #define SOL_USE_COMPATIBILITY_LAYER_I_ SOL_ON + #endif +#else + #define SOL_USE_COMPATIBILITY_LAYER_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_GET_FUNCTION_POINTER_UNSAFE) + #if (SOL_GET_FUNCTION_POINTER_UNSAFE != 0) + #define SOL_GET_FUNCTION_POINTER_UNSAFE_I_ SOL_ON + #else + #define SOL_GET_FUNCTION_POINTER_UNSAFE_I_ SOL_OFF + #endif +#else + #define SOL_GET_FUNCTION_POINTER_UNSAFE_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_FUNCTION_CALL_VALUE_SEMANTICS) + #if (SOL_FUNCTION_CALL_VALUE_SEMANTICS != 0) + #define SOL_FUNCTION_CALL_VALUE_SEMANTICS_I_ SOL_ON + #else + #define SOL_FUNCTION_CALL_VALUE_SEMANTICS_I_ SOL_OFF + #endif +#else + #define SOL_FUNCTION_CALL_VALUE_SEMANTICS_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_MINGW_CCTYPE_IS_POISONED) + #if (SOL_MINGW_CCTYPE_IS_POISONED != 0) + #define SOL_MINGW_CCTYPE_IS_POISONED_I_ SOL_ON + #else + #define SOL_MINGW_CCTYPE_IS_POISONED_I_ SOL_OFF + #endif +#elif SOL_IS_ON(SOL_COMPILER_MINGW) && defined(__GNUC__) && (__GNUC__ < 6) + // MinGW is off its rocker in some places... + #define SOL_MINGW_CCTYPE_IS_POISONED_I_ SOL_DEFAULT_ON +#else + #define SOL_MINGW_CCTYPE_IS_POISONED_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_CHAR8_T) + #if (SOL_CHAR8_T != 0) + #define SOL_CHAR8_T_I_ SOL_ON + #else + #define SOL_CHAR8_T_I_ SOL_OFF + #endif +#else + #if defined(__cpp_char8_t) + #define SOL_CHAR8_T_I_ SOL_DEFAULT_ON + #else + #define SOL_CHAR8_T_I_ SOL_DEFAULT_OFF + #endif +#endif + +#if SOL_IS_ON(SOL_USE_BOOST) + #include + + #if BOOST_VERSION >= 107500 // Since Boost 1.75.0 boost::none is constexpr + #define SOL_BOOST_NONE_CONSTEXPR_I_ constexpr + #else + #define SOL_BOOST_NONE_CONSTEXPR_I_ const + #endif // BOOST_VERSION +#else + // assume boost isn't using a garbage version + #define SOL_BOOST_NONE_CONSTEXPR_I_ constexpr +#endif + +#if defined(SOL2_CI) + #if (SOL2_CI != 0) + #define SOL2_CI_I_ SOL_ON + #else + #define SOL2_CI_I_ SOL_OFF + #endif +#else + #define SOL2_CI_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_C_ASSERT) + #define SOL_USER_C_ASSERT_I_ SOL_ON +#else + #define SOL_USER_C_ASSERT_I_ SOL_DEFAULT_OFF +#endif + +#if defined(SOL_M_ASSERT) + #define SOL_USER_M_ASSERT_I_ SOL_ON +#else + #define SOL_USER_M_ASSERT_I_ SOL_DEFAULT_OFF +#endif + +// beginning of sol/prologue.hpp + +#if defined(SOL_PROLOGUE_I_) + #error "[sol2] Library Prologue was already included in translation unit and not properly ended with an epilogue." +#endif + +#define SOL_PROLOGUE_I_ 1 + +#if SOL_IS_ON(SOL_BUILD_CXX_MODE) + #define _FWD(...) static_cast( __VA_ARGS__ ) + + #if SOL_IS_ON(SOL_COMPILER_GCC) || SOL_IS_ON(SOL_COMPILER_CLANG) + #define _MOVE(...) static_cast<__typeof( __VA_ARGS__ )&&>( __VA_ARGS__ ) + #else + #include + + #define _MOVE(...) static_cast<::std::remove_reference_t<( __VA_ARGS__ )>&&>( __VA_OPT__(,) ) + #endif +#endif + +// end of sol/prologue.hpp + +// beginning of sol/epilogue.hpp + +#if !defined(SOL_PROLOGUE_I_) + #error "[sol2] Library Prologue is missing from this translation unit." +#else + #undef SOL_PROLOGUE_I_ +#endif + +#if SOL_IS_ON(SOL_BUILD_CXX_MODE) + #undef _FWD + #undef _MOVE +#endif + +// end of sol/epilogue.hpp + +// beginning of sol/detail/build_version.hpp + +#if defined(SOL_DLL) + #if (SOL_DLL != 0) + #define SOL_DLL_I_ SOL_ON + #else + #define SOL_DLL_I_ SOL_OFF + #endif +#elif SOL_IS_ON(SOL_COMPILER_VCXX) && (defined(DLL_) || defined(_DLL)) + #define SOL_DLL_I_ SOL_DEFAULT_ON +#else + #define SOL_DLL_I_ SOL_DEFAULT_OFF +#endif // DLL definition + +#if defined(SOL_HEADER_ONLY) + #if (SOL_HEADER_ONLY != 0) + #define SOL_HEADER_ONLY_I_ SOL_ON + #else + #define SOL_HEADER_ONLY_I_ SOL_OFF + #endif +#else + #define SOL_HEADER_ONLY_I_ SOL_DEFAULT_OFF +#endif // Header only library + +#if defined(SOL_BUILD) + #if (SOL_BUILD != 0) + #define SOL_BUILD_I_ SOL_ON + #else + #define SOL_BUILD_I_ SOL_OFF + #endif +#elif SOL_IS_ON(SOL_HEADER_ONLY) + #define SOL_BUILD_I_ SOL_DEFAULT_OFF +#else + #define SOL_BUILD_I_ SOL_DEFAULT_ON +#endif + +#if defined(SOL_UNITY_BUILD) + #if (SOL_UNITY_BUILD != 0) + #define SOL_UNITY_BUILD_I_ SOL_ON + #else + #define SOL_UNITY_BUILD_I_ SOL_OFF + #endif +#else + #define SOL_UNITY_BUILD_I_ SOL_DEFAULT_OFF +#endif // Header only library + +#if defined(SOL_C_FUNCTION_LINKAGE) + #define SOL_C_FUNCTION_LINKAGE_I_ SOL_C_FUNCTION_LINKAGE +#else + #if SOL_IS_ON(SOL_BUILD_CXX_MODE) + // C++ + #define SOL_C_FUNCTION_LINKAGE_I_ extern "C" + #else + // normal + #define SOL_C_FUNCTION_LINKAGE_I_ + #endif // C++ or not +#endif // Linkage specification for C functions + +#if defined(SOL_API_LINKAGE) + #define SOL_API_LINKAGE_I_ SOL_API_LINKAGE +#else + #if SOL_IS_ON(SOL_DLL) + #if SOL_IS_ON(SOL_COMPILER_VCXX) || SOL_IS_ON(SOL_PLATFORM_WINDOWS) || SOL_IS_ON(SOL_PLATFORM_CYGWIN) + // MSVC Compiler; or, Windows, or Cygwin platforms + #if SOL_IS_ON(SOL_BUILD) + // Building the library + #if SOL_IS_ON(SOL_COMPILER_GCC) + // Using GCC + #define SOL_API_LINKAGE_I_ __attribute__((dllexport)) + #else + // Using Clang, MSVC, etc... + #define SOL_API_LINKAGE_I_ __declspec(dllexport) + #endif + #else + #if SOL_IS_ON(SOL_COMPILER_GCC) + #define SOL_API_LINKAGE_I_ __attribute__((dllimport)) + #else + #define SOL_API_LINKAGE_I_ __declspec(dllimport) + #endif + #endif + #else + // extern if building normally on non-MSVC + #define SOL_API_LINKAGE_I_ extern + #endif + #elif SOL_IS_ON(SOL_UNITY_BUILD) + // Built-in library, like how stb typical works + #if SOL_IS_ON(SOL_HEADER_ONLY) + // Header only, so functions are defined "inline" + #define SOL_API_LINKAGE_I_ inline + #else + // Not header only, so seperately compiled files + #define SOL_API_LINKAGE_I_ extern + #endif + #else + // Normal static library + #if SOL_IS_ON(SOL_BUILD_CXX_MODE) + #define SOL_API_LINKAGE_I_ + #else + #define SOL_API_LINKAGE_I_ extern + #endif + #endif // DLL or not +#endif // Build definitions + +#if defined(SOL_PUBLIC_FUNC_DECL) + #define SOL_PUBLIC_FUNC_DECL_I_ SOL_PUBLIC_FUNC_DECL +#else + #define SOL_PUBLIC_FUNC_DECL_I_ SOL_API_LINKAGE_I_ +#endif + +#if defined(SOL_INTERNAL_FUNC_DECL_) + #define SOL_INTERNAL_FUNC_DECL_I_ SOL_INTERNAL_FUNC_DECL_ +#else + #define SOL_INTERNAL_FUNC_DECL_I_ SOL_API_LINKAGE_I_ +#endif + +#if defined(SOL_PUBLIC_FUNC_DEF) + #define SOL_PUBLIC_FUNC_DEF_I_ SOL_PUBLIC_FUNC_DEF +#else + #define SOL_PUBLIC_FUNC_DEF_I_ SOL_API_LINKAGE_I_ +#endif + +#if defined(SOL_INTERNAL_FUNC_DEF) + #define SOL_INTERNAL_FUNC_DEF_I_ SOL_INTERNAL_FUNC_DEF +#else + #define SOL_INTERNAL_FUNC_DEF_I_ SOL_API_LINKAGE_I_ +#endif + +#if defined(SOL_FUNC_DECL) + #define SOL_FUNC_DECL_I_ SOL_FUNC_DECL +#elif SOL_IS_ON(SOL_HEADER_ONLY) + #define SOL_FUNC_DECL_I_ +#elif SOL_IS_ON(SOL_DLL) + #if SOL_IS_ON(SOL_COMPILER_VCXX) + #if SOL_IS_ON(SOL_BUILD) + #define SOL_FUNC_DECL_I_ extern __declspec(dllexport) + #else + #define SOL_FUNC_DECL_I_ extern __declspec(dllimport) + #endif + #elif SOL_IS_ON(SOL_COMPILER_GCC) || SOL_IS_ON(SOL_COMPILER_CLANG) + #define SOL_FUNC_DECL_I_ extern __attribute__((visibility("default"))) + #else + #define SOL_FUNC_DECL_I_ extern + #endif +#endif + +#if defined(SOL_FUNC_DEFN) + #define SOL_FUNC_DEFN_I_ SOL_FUNC_DEFN +#elif SOL_IS_ON(SOL_HEADER_ONLY) + #define SOL_FUNC_DEFN_I_ inline +#elif SOL_IS_ON(SOL_DLL) + #if SOL_IS_ON(SOL_COMPILER_VCXX) + #if SOL_IS_ON(SOL_BUILD) + #define SOL_FUNC_DEFN_I_ __declspec(dllexport) + #else + #define SOL_FUNC_DEFN_I_ __declspec(dllimport) + #endif + #elif SOL_IS_ON(SOL_COMPILER_GCC) || SOL_IS_ON(SOL_COMPILER_CLANG) + #define SOL_FUNC_DEFN_I_ __attribute__((visibility("default"))) + #else + #define SOL_FUNC_DEFN_I_ + #endif +#endif + +#if defined(SOL_HIDDEN_FUNC_DECL) + #define SOL_HIDDEN_FUNC_DECL_I_ SOL_HIDDEN_FUNC_DECL +#elif SOL_IS_ON(SOL_HEADER_ONLY) + #define SOL_HIDDEN_FUNC_DECL_I_ +#elif SOL_IS_ON(SOL_DLL) + #if SOL_IS_ON(SOL_COMPILER_VCXX) + #if SOL_IS_ON(SOL_BUILD) + #define SOL_HIDDEN_FUNC_DECL_I_ extern __declspec(dllexport) + #else + #define SOL_HIDDEN_FUNC_DECL_I_ extern __declspec(dllimport) + #endif + #elif SOL_IS_ON(SOL_COMPILER_GCC) || SOL_IS_ON(SOL_COMPILER_CLANG) + #define SOL_HIDDEN_FUNC_DECL_I_ extern __attribute__((visibility("default"))) + #else + #define SOL_HIDDEN_FUNC_DECL_I_ extern + #endif +#endif + +#if defined(SOL_HIDDEN_FUNC_DEFN) + #define SOL_HIDDEN_FUNC_DEFN_I_ SOL_HIDDEN_FUNC_DEFN +#elif SOL_IS_ON(SOL_HEADER_ONLY) + #define SOL_HIDDEN_FUNC_DEFN_I_ inline +#elif SOL_IS_ON(SOL_DLL) + #if SOL_IS_ON(SOL_COMPILER_VCXX) + #if SOL_IS_ON(SOL_BUILD) + #define SOL_HIDDEN_FUNC_DEFN_I_ + #else + #define SOL_HIDDEN_FUNC_DEFN_I_ + #endif + #elif SOL_IS_ON(SOL_COMPILER_GCC) || SOL_IS_ON(SOL_COMPILER_CLANG) + #define SOL_HIDDEN_FUNC_DEFN_I_ __attribute__((visibility("hidden"))) + #else + #define SOL_HIDDEN_FUNC_DEFN_I_ + #endif +#endif + +// end of sol/detail/build_version.hpp + +// end of sol/version.hpp + +#if SOL_IS_ON(SOL_INSIDE_UNREAL_ENGINE) +#ifdef check +#pragma push_macro("check") +#undef check +#endif +#endif // Unreal Engine 4 Bullshit + +#if SOL_IS_ON(SOL_COMPILER_GCC) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#if __GNUC__ > 6 +#pragma GCC diagnostic ignored "-Wnoexcept-type" +#endif +#elif SOL_IS_ON(SOL_COMPILER_CLANG) +#elif SOL_IS_ON(SOL_COMPILER_VCXX) +#pragma warning(push) +#pragma warning(disable : 4505) // unreferenced local function has been removed GEE THANKS +#endif // clang++ vs. g++ vs. VC++ + +// beginning of sol/forward.hpp + +#ifndef SOL_FORWARD_HPP +#define SOL_FORWARD_HPP + +#include +#include +#include + +#if SOL_IS_ON(SOL_USE_CXX_LUA) || SOL_IS_ON(SOL_USE_CXX_LUAJIT) +struct lua_State; +#else +extern "C" { +struct lua_State; +} +#endif // C++ Mangling for Lua vs. Not + +namespace sol { + + enum class type; + + class stateless_reference; + template + class basic_reference; + using reference = basic_reference; + using main_reference = basic_reference; + class stateless_stack_reference; + class stack_reference; + + template + class basic_bytecode; + + struct lua_value; + + struct proxy_base_tag; + template + struct proxy_base; + template + struct table_proxy; + + template + class basic_table_core; + template + using table_core = basic_table_core; + template + using main_table_core = basic_table_core; + template + using stack_table_core = basic_table_core; + template + using basic_table = basic_table_core; + using table = table_core; + using global_table = table_core; + using main_table = main_table_core; + using main_global_table = main_table_core; + using stack_table = stack_table_core; + using stack_global_table = stack_table_core; + + template + struct basic_lua_table; + using lua_table = basic_lua_table; + using stack_lua_table = basic_lua_table; + + template + class basic_usertype; + template + using usertype = basic_usertype; + template + using stack_usertype = basic_usertype; + + template + class basic_metatable; + using metatable = basic_metatable; + using stack_metatable = basic_metatable; + + template + struct basic_environment; + using environment = basic_environment; + using main_environment = basic_environment; + using stack_environment = basic_environment; + + template + class basic_function; + template + class basic_protected_function; + using unsafe_function = basic_function; + using safe_function = basic_protected_function; + using main_unsafe_function = basic_function; + using main_safe_function = basic_protected_function; + using stack_unsafe_function = basic_function; + using stack_safe_function = basic_protected_function; + using stack_aligned_unsafe_function = basic_function; + using stack_aligned_safe_function = basic_protected_function; + using protected_function = safe_function; + using main_protected_function = main_safe_function; + using stack_protected_function = stack_safe_function; + using stack_aligned_protected_function = stack_aligned_safe_function; +#if SOL_IS_ON(SOL_SAFE_FUNCTION_OBJECTS) + using function = protected_function; + using main_function = main_protected_function; + using stack_function = stack_protected_function; + using stack_aligned_function = stack_aligned_safe_function; +#else + using function = unsafe_function; + using main_function = main_unsafe_function; + using stack_function = stack_unsafe_function; + using stack_aligned_function = stack_aligned_unsafe_function; +#endif + using stack_aligned_stack_handler_function = basic_protected_function; + + struct unsafe_function_result; + struct protected_function_result; + using safe_function_result = protected_function_result; +#if SOL_IS_ON(SOL_SAFE_FUNCTION_OBJECTS) + using function_result = safe_function_result; +#else + using function_result = unsafe_function_result; +#endif + + template + class basic_object_base; + template + class basic_object; + template + class basic_userdata; + template + class basic_lightuserdata; + template + class basic_coroutine; + template + class basic_packaged_coroutine; + template + class basic_thread; + + using object = basic_object; + using userdata = basic_userdata; + using lightuserdata = basic_lightuserdata; + using thread = basic_thread; + using coroutine = basic_coroutine; + using packaged_coroutine = basic_packaged_coroutine; + using main_object = basic_object; + using main_userdata = basic_userdata; + using main_lightuserdata = basic_lightuserdata; + using main_coroutine = basic_coroutine; + using stack_object = basic_object; + using stack_userdata = basic_userdata; + using stack_lightuserdata = basic_lightuserdata; + using stack_thread = basic_thread; + using stack_coroutine = basic_coroutine; + + struct stack_proxy_base; + struct stack_proxy; + struct variadic_args; + struct variadic_results; + struct stack_count; + struct this_state; + struct this_main_state; + struct this_environment; + + class state_view; + class state; + + template + struct as_table_t; + template + struct as_container_t; + template + struct nested; + template + struct light; + template + struct user; + template + struct as_args_t; + template + struct protect_t; + template + struct policy_wrapper; + + template + struct usertype_traits; + template + struct unique_usertype_traits; + + template + struct types { + typedef std::make_index_sequence indices; + static constexpr std::size_t size() { + return sizeof...(Args); + } + }; + + template + struct derive : std::false_type { + typedef types<> type; + }; + + template + struct base : std::false_type { + typedef types<> type; + }; + + template + struct weak_derive { + static bool value; + }; + + template + bool weak_derive::value = false; + + namespace stack { + struct record; + } + +#if SOL_IS_OFF(SOL_USE_BOOST) + template + class optional; + + template + class optional; +#endif + + using check_handler_type = int(lua_State*, int, type, type, const char*); + +} // namespace sol + +#define SOL_BASE_CLASSES(T, ...) \ + namespace sol { \ + template <> \ + struct base : std::true_type { \ + typedef ::sol::types<__VA_ARGS__> type; \ + }; \ + } \ + void a_sol3_detail_function_decl_please_no_collide() +#define SOL_DERIVED_CLASSES(T, ...) \ + namespace sol { \ + template <> \ + struct derive : std::true_type { \ + typedef ::sol::types<__VA_ARGS__> type; \ + }; \ + } \ + void a_sol3_detail_function_decl_please_no_collide() + +#endif // SOL_FORWARD_HPP +// end of sol/forward.hpp + +// beginning of sol/forward_detail.hpp + +#ifndef SOL_FORWARD_DETAIL_HPP +#define SOL_FORWARD_DETAIL_HPP + +// beginning of sol/traits.hpp + +// beginning of sol/tuple.hpp + +// beginning of sol/base_traits.hpp + +#include + +namespace sol { + namespace detail { + struct unchecked_t { }; + const unchecked_t unchecked = unchecked_t {}; + } // namespace detail + + namespace meta { + using sfinae_yes_t = std::true_type; + using sfinae_no_t = std::false_type; + + template + using void_t = void; + + template + using unqualified = std::remove_cv>; + + template + using unqualified_t = typename unqualified::type; + + namespace meta_detail { + template + struct unqualified_non_alias : unqualified { }; + + template
" << aStr << "" << ((aBitsMemory != 0) ? "mBits
" + CommaSeperate(aBitsMemory) : " ") << "
" - << ((aBitsMemory != 0) ? "mBits
" + CommaSeperate(aBitsMemory) : " ") - << "
" - << ((aPalletizedMemory != 0) - ? "Palletized
" + CommaSeperate(aPalletizedMemory) - : " ") + << ((aPalletizedMemory != 0) ? "Palletized
" + CommaSeperate(aPalletizedMemory) : " ") << "
" - << ((aSurfaceMemory != 0) - ? "DDSurface
" + CommaSeperate(aSurfaceMemory) - : " ") + aDumpStream << "
" << ((aSurfaceMemory != 0) ? "DDSurface
" + CommaSeperate(aSurfaceMemory) : " ") << "
" - << ((aMemoryImage->mD3DData != nullptr) + << ((aGPUImage->mGPUData != nullptr) ? "Texture
" + aTextureFormatName + "
" + CommaSeperate(aTextureMemory) : " ") << "
" - << (aMemoryImage->mIsVolatile ? "Volatile" : " ") - << "" << (aGPUImage->mIsVolatile ? "Volatile" : " ") << "" - << (aMemoryImage->mForcedMode ? "Forced" : " ") - << "" << (aGPUImage->mForcedMode ? "Forced" : " ") << "" - << (aMemoryImage->mHasAlpha ? "HasAlpha" : " ") - << "" << (aGPUImage->mHasAlpha ? "HasAlpha" : " ") << "" << (aGPUImage->mHasTrans ? "HasTrans" : " ") << "" - << (aMemoryImage->mHasTrans ? "HasTrans" : " ") + << ((aNativeAlphaMemory != 0) ? "NativeAlpha
" + CommaSeperate(aNativeAlphaMemory) : " ") << "
" - << ((aNativeAlphaMemory != 0) ? "NativeAlpha
" + CommaSeperate(aNativeAlphaMemory) :" ") + aDumpStream << "
" << ((aRLAlphaMemory != 0) ? "RLAlpha
" + CommaSeperate(aRLAlphaMemory) : " ") << "
" - <<((aRLAlphaMemory != 0) ? "RLAlpha
" + CommaSeperate(aRLAlphaMemory) : " ") + << ((aRLAdditiveMemory != 0) ? "RLAdditive
" + CommaSeperate(aRLAdditiveMemory) : " ") << "
" - << ((aRLAdditiveMemory != 0) - ? "RLAdditive
" + CommaSeperate(aRLAdditiveMemory) - : " ") - << "
" << (aMemoryImage->mFilePath.empty() ? " " : aMemoryImage->mFilePath) << "" << (aGPUImage->mFilePath.empty() ? " " : aGPUImage->mFilePath) << "