diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 141ec6c47..c4dd619c3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -180,6 +180,50 @@ jobs: - name: Pack cache run: cd ~ && tar cJf ccache.tar.xz .cache/ccache workspacecache.tar + build-ios: + name: Build (iOS) + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Get Time + id: get-time + run: echo "time=$(date -u '+%Y-%m-%d-%H:%M:%S')" >> $GITHUB_OUTPUT + - uses: actions/cache@v5 + with: + path: ~/ccache.tar.xz + key: ios-artifact-${{ steps.get-time.outputs.time }} + restore-keys: ios-artifact- + - name: Unpack cache + run: | + cd ~ + [ -f ccache.tar.xz ] && tar xf ccache.tar.xz || true + [ -f workspacecache.tar ] && mv workspacecache.tar ${{ github.workspace }} || true + - name: Install dependencies + run: | + wget https://apt.llvm.org/llvm.sh + sudo bash llvm.sh 22 + sudo apt-get update + sudo apt-get install -y ccache libplist-dev libplist-utils clang-22 + [ -f workspacecache.tar ] && tar xf workspacecache.tar + ./artifacts/ios/build-toolchain.sh + tar cf workspacecache.tar artifacts/ios/build/toolchain + mv workspacecache.tar ~ + - name: Build + run: | + PATH="$PWD/artifacts/ios/build/toolchain/bin:$PATH" make \ + NO_COLOR=1 \ + VERBOSE=1 \ + CC='ccache ios-cc' + env: + CLANG: clang-22 + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: butterscotch-ios + path: build/butterscotch.ipa + - name: Pack cache + run: cd ~ && tar cJf ccache.tar.xz .cache/ccache workspacecache.tar + build-ps2: name: Build (PS2)${{ matrix.label }} runs-on: ubuntu-24.04 diff --git a/CMakeLists.txt b/CMakeLists.txt index 924d470fa..398d07370 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ option(ENABLE_WAD16 "Enable support for WAD Version 15/16" ON) option(ENABLE_WAD17 "Enable support for WAD Version 17" ON) option(ENABLE_LEGACY_GL "Enable the legacy OpenGL renderer" ON) option(ENABLE_MODERN_GL "Enable the modern OpenGL renderer" ON) +option(ENABLE_SW_RENDERER "Enable the software renderer" ON) if(NOT ENABLE_WAD14 AND NOT ENABLE_WAD16 AND NOT ENABLE_WAD17) message(FATAL_ERROR "You need to build Butterscotch with at least one WAD version enabled!") endif() @@ -89,6 +90,9 @@ endif() if(ENABLE_MODERN_GL) add_compile_definitions(ENABLE_MODERN_GL) endif() +if(ENABLE_SW_RENDERER) + add_compile_definitions(ENABLE_SW_RENDERER) +endif() if(PLATFORM STREQUAL "web-meta") # Metadata-only build: just the data.win parser and its direct deps. No VM, no runner, no I/O subsystems. @@ -186,10 +190,15 @@ if(PLATFORM STREQUAL "desktop") # Matches the GitHub Actions container flags (FORTIFY requires building with optimizations enabled!) target_compile_options(butterscotch PRIVATE "$<$:-U_FORTIFY_SOURCE;-D_FORTIFY_SOURCE=2>") - if(NOT ENABLE_LEGACY_GL AND NOT ENABLE_MODERN_GL) + if(NOT ENABLE_LEGACY_GL AND NOT ENABLE_MODERN_GL AND NOT ENABLE_SW_RENDERER) message(FATAL_ERROR "You must enable at least one renderer!") endif() - file(GLOB GL_SOURCES src/image/*.c src/gl_common/*.c) + file(GLOB GL_SOURCES src/image/*.c) + if(ENABLE_LEGACY_GL OR ENABLE_MODERN_GL) + file(GLOB GL_SOURCES ${GL_SOURCES} src/gl_common/*.c) + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl) + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_common) + endif() if(ENABLE_LEGACY_GL) file(GLOB GL_SOURCES ${GL_SOURCES} src/gl_legacy/*.c) target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_legacy) @@ -197,9 +206,11 @@ if(PLATFORM STREQUAL "desktop") if(ENABLE_MODERN_GL) file(GLOB GL_SOURCES ${GL_SOURCES} src/gl/*.c) endif() + if(ENABLE_SW_RENDERER) + file(GLOB GL_SOURCES ${GL_SOURCES} src/sw/*.c) + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/sw) + endif() target_sources(butterscotch PRIVATE ${GL_SOURCES}) - target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl) - target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_common) target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/image) if (MSVC) target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/compat/getopt) @@ -231,13 +242,16 @@ if(PLATFORM STREQUAL "desktop") endif() # GLAD - add_library(glad STATIC vendor/glad/src/glad.c) - target_include_directories(glad PUBLIC vendor/glad/include) + if(ENABLE_LEGACY_GL OR ENABLE_MODERN_GL OR DESKTOP_BACKEND STREQUAL "glfw3" OR DESKTOP_BACKEND STREQUAL "glfw2") + add_library(glad STATIC vendor/glad/src/glad.c) + target_include_directories(glad PUBLIC vendor/glad/include) + set(PLATFORM_LIBRARIES glad) + endif() # stb_image target_include_directories(butterscotch PUBLIC vendor/stb/image) - set(PLATFORM_LIBRARIES glad bzip2 stb_ds sha1 stb_vorbis) + set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} bzip2 stb_ds sha1 stb_vorbis) if(NOT MSVC) set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} m) endif() diff --git a/Makefile b/Makefile index 15159842c..c4bd44b1f 100644 --- a/Makefile +++ b/Makefile @@ -39,8 +39,14 @@ INCLUDES += $(INCLUDE). \ HEADERS := $(wildcard src/*.h) $(shell find vendor -name '*.h') SRCS := $(wildcard src/*.c) $(wildcard src/image/*.c) $(wildcard vendor/bzip2/*.c) vendor/md5/md5.c vendor/sha1/sha1.c vendor/base64/base64.c +ifeq ($(OS),iOS) +DESKTOP_BACKEND := ios +AUDIO_BACKEND := openal +DISABLE_LEGACY_GL := 1 +else DESKTOP_BACKEND := glfw3 AUDIO_BACKEND := miniaudio +endif ifdef BUTTERSCOTCH_COMMIT_DATE DEFINES += $(DEFINE)BUTTERSCOTCH_COMMIT_DATE=\"$(BUTTERSCOTCH_COMMIT_DATE)\" @@ -98,7 +104,10 @@ SDL3_LIBS += $(shell pkg-config $(PKG_CONFIG_FLAGS) --libs sdl3) LIBS += $(SDL3_LIBS) DEFINES += $(DEFINE)USE_SDL3 endif - +ifeq ($(DESKTOP_BACKEND),ios) +LIBS += -framework Foundation -framework UIKit -framework OpenGLES -framework QuartzCore -framework CoreGraphics +DEFINES += -DUSE_IOS +endif # GNU make doesn't have a way to do OR in conditionals, stupid language for clowns ifndef DISABLE_LEGACY_GL @@ -128,6 +137,13 @@ SRCS += $(wildcard src/gl/*.c) HEADERS += $(wildcard src/gl/*.h) endif +ifndef DISABLE_SW_RENDERER +DEFINES += -DENABLE_SW_RENDERER +SRCS += $(wildcard src/sw/*.c) +HEADERS += $(wildcard src/sw/*.h) +INCLUDES += -Isrc/sw +endif + ifdef DISABLE_WAD14 ifdef DISABLE_WAD16 ifdef DISABLE_WAD17 @@ -138,9 +154,11 @@ endif ifdef DISABLE_LEGACY_GL ifdef DISABLE_MODERN_GL +ifdef DISABLE_SW_RENDERER $(error must enable at least 1 renderer) endif endif +endif ifeq ($(AUDIO_BACKEND),miniaudio) INCLUDES += $(INCLUDE)src/audio/miniaudio $(INCLUDE)vendor/miniaudio @@ -148,15 +166,19 @@ DEFINES += $(DEFINE)USE_MINIAUDIO SRCS += $(wildcard src/audio/miniaudio/*.c) HEADERS += $(wildcard src/audio/miniaudio/*.h) ifneq ($(OS),Windows) +ifeq ($(OS),iOS) +LIBS += -framework AVFoundation -framework AudioToolbox +else LIBS += -pthread endif endif +endif ifeq ($(AUDIO_BACKEND),openal) INCLUDES += $(INCLUDE)src/audio/openal DEFINES += $(DEFINE)USE_OPENAL SRCS += $(wildcard src/audio/openal/*.c) HEADERS += $(wildcard src/audio/openal/*.h) -ifeq ($(OS),Darwin) +ifneq ($(filter Darwin iOS,$(OS)),) # OS is 'Darwin' or 'iOS' LIBS += -framework OpenAL else LIBS += -lopenal @@ -178,7 +200,7 @@ DEFINES += $(DEFINE)_CRT_SECURE_NO_WARNINGS endif DEFINES += $(DEFINE)WIN32_LEAN_AND_MEAN else -ifeq ($(OS),Darwin) +ifneq ($(filter Darwin iOS,$(OS)),) # OS is 'Darwin' or 'iOS' LIBS += -lobjc else LIBS += -lm @@ -202,10 +224,11 @@ compat/config.mk: compat/configure.sh compat/tmp/cc endif -build/butterscotch: $(OBJS) +build/butterscotch: $(OBJS) $(if $(filter iOS,$(OS)),artifacts/ios/build-ipa.sh $(wildcard artifacts/ios/assets/*)) @{ [ -z "$(NO_COLOR)" ] && [ -t 1 ]; } && printf " \033[1;34mLD\033[0m butterscotch\n" || printf " LD butterscotch\n" $(V)$(_CC) $(LDFLAGS) $(OBJS) $(LIBS) $(EXTRALIBS) $(OUTPUT_EXE)$@ @[ -f $@.exe ] && chmod +x $@.exe || true + @if [ "$(OS)" = 'iOS' ]; then artifacts/ios/build-ipa.sh; fi build/%.c.$(OBJ_EXT): %.c compat/config.mk $(if $(DISABLE_MMD),$(HEADERS)) @mkdir -p $(dir $@) diff --git a/artifacts/ios/SDKSettings.json b/artifacts/ios/SDKSettings.json new file mode 100644 index 000000000..03bca4505 --- /dev/null +++ b/artifacts/ios/SDKSettings.json @@ -0,0 +1,28 @@ +{ + "DisplayName": "iOS 8.0", + "DocSetFeedURL": "http://developer.apple.com/rss/com.apple.adc.documentation.AppleiPhone8_0.atom", + "MinimalDisplayName": "8.0", + "DefaultProperties": { + "AD_HOC_CODE_SIGNING_ALLOWED": "NO", + "SUPPORTED_DEVICE_FAMILIES": "1,2", + "DEFAULT_COMPILER": "com.apple.compilers.llvm.clang.1_0", + "IPHONEOS_DEPLOYMENT_TARGET": "8.0", + "DEAD_CODE_STRIPPING": "YES", + "CODE_SIGN_ENTITLEMENTS": "", + "ENTITLEMENTS_REQUIRED": "YES", + "CODE_SIGNING_REQUIRED": "YES", + "PLATFORM_NAME": "iphoneos", + "GCC_THUMB_SUPPORT": "YES" + }, + "Version": "8.0", + "Toolchains": [ + "com.apple.dt.toolchain.iOS8_0" + ], + "MinimumSupportedToolsVersion": "3.2.3", + "IsBaseSDK": "YES", + "AlternateSDK": "iphonesimulator8.0", + "MaximumDeploymentTarget": "8.0.99", + "CanonicalName": "iphoneos8.0", + "DocSetFeedName": "Apple iPhone OS 8.0", + "CustomProperties": {} +} diff --git a/artifacts/ios/assets/Default-568h@2x.png b/artifacts/ios/assets/Default-568h@2x.png new file mode 100644 index 000000000..bba25c99d Binary files /dev/null and b/artifacts/ios/assets/Default-568h@2x.png differ diff --git a/artifacts/ios/assets/Default-667h@2x.png b/artifacts/ios/assets/Default-667h@2x.png new file mode 100644 index 000000000..af0cb4604 Binary files /dev/null and b/artifacts/ios/assets/Default-667h@2x.png differ diff --git a/artifacts/ios/assets/Default-736h@3x.png b/artifacts/ios/assets/Default-736h@3x.png new file mode 100644 index 000000000..91bd6e3e1 Binary files /dev/null and b/artifacts/ios/assets/Default-736h@3x.png differ diff --git a/artifacts/ios/assets/Default-812h@3x.png b/artifacts/ios/assets/Default-812h@3x.png new file mode 100644 index 000000000..d568763fe Binary files /dev/null and b/artifacts/ios/assets/Default-812h@3x.png differ diff --git a/artifacts/ios/assets/Default.png b/artifacts/ios/assets/Default.png new file mode 100644 index 000000000..a0dfda5aa Binary files /dev/null and b/artifacts/ios/assets/Default.png differ diff --git a/artifacts/ios/assets/Default@2x.png b/artifacts/ios/assets/Default@2x.png new file mode 100644 index 000000000..b8fa5d47a Binary files /dev/null and b/artifacts/ios/assets/Default@2x.png differ diff --git a/artifacts/ios/assets/Icon-20.png b/artifacts/ios/assets/Icon-20.png new file mode 100644 index 000000000..c4bceae2b Binary files /dev/null and b/artifacts/ios/assets/Icon-20.png differ diff --git a/artifacts/ios/assets/Icon-20@2x.png b/artifacts/ios/assets/Icon-20@2x.png new file mode 100644 index 000000000..46ebaebab Binary files /dev/null and b/artifacts/ios/assets/Icon-20@2x.png differ diff --git a/artifacts/ios/assets/Icon-20@3x.png b/artifacts/ios/assets/Icon-20@3x.png new file mode 100644 index 000000000..f91c9793d Binary files /dev/null and b/artifacts/ios/assets/Icon-20@3x.png differ diff --git a/artifacts/ios/assets/Icon-40.png b/artifacts/ios/assets/Icon-40.png new file mode 100644 index 000000000..46ebaebab Binary files /dev/null and b/artifacts/ios/assets/Icon-40.png differ diff --git a/artifacts/ios/assets/Icon-40@2x.png b/artifacts/ios/assets/Icon-40@2x.png new file mode 100644 index 000000000..59efc7775 Binary files /dev/null and b/artifacts/ios/assets/Icon-40@2x.png differ diff --git a/artifacts/ios/assets/Icon-40@3x.png b/artifacts/ios/assets/Icon-40@3x.png new file mode 100644 index 000000000..df60e45a2 Binary files /dev/null and b/artifacts/ios/assets/Icon-40@3x.png differ diff --git a/artifacts/ios/assets/Icon-60@2x.png b/artifacts/ios/assets/Icon-60@2x.png new file mode 120000 index 000000000..88f3dd0ff --- /dev/null +++ b/artifacts/ios/assets/Icon-60@2x.png @@ -0,0 +1 @@ +Icon-40@3x.png \ No newline at end of file diff --git a/artifacts/ios/assets/Icon-60@3x.png b/artifacts/ios/assets/Icon-60@3x.png new file mode 100644 index 000000000..99f841187 Binary files /dev/null and b/artifacts/ios/assets/Icon-60@3x.png differ diff --git a/artifacts/ios/assets/Icon-72.png b/artifacts/ios/assets/Icon-72.png new file mode 100644 index 000000000..b16c30048 Binary files /dev/null and b/artifacts/ios/assets/Icon-72.png differ diff --git a/artifacts/ios/assets/Icon-72@2x.png b/artifacts/ios/assets/Icon-72@2x.png new file mode 100644 index 000000000..08044b6e0 Binary files /dev/null and b/artifacts/ios/assets/Icon-72@2x.png differ diff --git a/artifacts/ios/assets/Icon-76.png b/artifacts/ios/assets/Icon-76.png new file mode 100644 index 000000000..4eca029be Binary files /dev/null and b/artifacts/ios/assets/Icon-76.png differ diff --git a/artifacts/ios/assets/Icon-76@2x.png b/artifacts/ios/assets/Icon-76@2x.png new file mode 100644 index 000000000..5e65aa54a Binary files /dev/null and b/artifacts/ios/assets/Icon-76@2x.png differ diff --git a/artifacts/ios/assets/Icon-83.5@2x.png b/artifacts/ios/assets/Icon-83.5@2x.png new file mode 100644 index 000000000..f105f10e1 Binary files /dev/null and b/artifacts/ios/assets/Icon-83.5@2x.png differ diff --git a/artifacts/ios/assets/Icon-Small.png b/artifacts/ios/assets/Icon-Small.png new file mode 100644 index 000000000..826b6e7fb Binary files /dev/null and b/artifacts/ios/assets/Icon-Small.png differ diff --git a/artifacts/ios/assets/Icon-Small@2x.png b/artifacts/ios/assets/Icon-Small@2x.png new file mode 100644 index 000000000..f040f1046 Binary files /dev/null and b/artifacts/ios/assets/Icon-Small@2x.png differ diff --git a/artifacts/ios/assets/Icon-Small@3x.png b/artifacts/ios/assets/Icon-Small@3x.png new file mode 100644 index 000000000..e72d9c785 Binary files /dev/null and b/artifacts/ios/assets/Icon-Small@3x.png differ diff --git a/artifacts/ios/assets/Icon.png b/artifacts/ios/assets/Icon.png new file mode 100644 index 000000000..723a3c636 Binary files /dev/null and b/artifacts/ios/assets/Icon.png differ diff --git a/artifacts/ios/assets/Icon@2x.png b/artifacts/ios/assets/Icon@2x.png new file mode 100644 index 000000000..71be8446c Binary files /dev/null and b/artifacts/ios/assets/Icon@2x.png differ diff --git a/artifacts/ios/assets/Info.plist b/artifacts/ios/assets/Info.plist new file mode 100755 index 000000000..fe149fdff --- /dev/null +++ b/artifacts/ios/assets/Info.plist @@ -0,0 +1,146 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Butterscotch + CFBundleExecutable + butterscotch + CFBundleIdentifier + com.mrpowergamerbr.butterscotch + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Butterscotch + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 0.7.6 + CFBundleShortVersionString + 0.7.6 + CFBundleSupportedPlatforms + + iPhoneOS + + MinimumOSVersion + 2.0 + UIDeviceFamily + + 1 + 2 + + UIStatusBarHidden + + UIFileSharingEnabled + + LSSupportsOpeningDocumentsInPlace + + UIViewControllerBasedStatusBarAppearance + + CFBundleIconFiles + + Icon.png + Icon@2x.png + Icon-72.png + Icon-72@2x.png + Icon-Small.png + Icon-Small@2x.png + + CFBundleIcons + + CFBundlePrimaryIcon + + CFBundleIconFiles + + Icon-20.png + Icon-20@2x.png + Icon-20@3x.png + Icon-40.png + Icon-40@2x.png + Icon-40@3x.png + Icon-60@2x.png + Icon-60@3x.png + + + + CFBundleIcons~ipad + + CFBundlePrimaryIcon + + CFBundleIconFiles + + Icon-20.png + Icon-20@2x.png + Icon-40.png + Icon-40@2x.png + Icon-72.png + Icon-72@2x.png + Icon-76.png + Icon-76@2x.png + Icon-83.5@2x.png + + + + UILaunchImages + + + UILaunchImageName + Default + UILaunchImageMinimumOSVersion + 2.0 + UILaunchImageOrientation + Portrait + UILaunchImageSize + {320, 480} + + + UILaunchImageName + Default-568h + UILaunchImageMinimumOSVersion + 7.0 + UILaunchImageOrientation + Portrait + UILaunchImageSize + {320, 568} + + + UILaunchImageName + Default-667h + UILaunchImageMinimumOSVersion + 8.0 + UILaunchImageOrientation + Portrait + UILaunchImageSize + {375, 667} + + + UILaunchImageName + Default-736h + UILaunchImageMinimumOSVersion + 8.0 + UILaunchImageOrientation + Portrait + UILaunchImageSize + {414, 736} + + + UILaunchImageName + Default-812h + UILaunchImageMinimumOSVersion + 11.0 + UILaunchImageOrientation + Portrait + UILaunchImageSize + {375, 812} + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + diff --git a/artifacts/ios/assets/iTunesArtwork b/artifacts/ios/assets/iTunesArtwork new file mode 100644 index 000000000..82acdc50f Binary files /dev/null and b/artifacts/ios/assets/iTunesArtwork differ diff --git a/artifacts/ios/assets/iTunesArtwork@2x b/artifacts/ios/assets/iTunesArtwork@2x new file mode 100644 index 000000000..3bc00b1e1 Binary files /dev/null and b/artifacts/ios/assets/iTunesArtwork@2x differ diff --git a/artifacts/ios/build-ipa.sh b/artifacts/ios/build-ipa.sh new file mode 100755 index 000000000..45a80cc41 --- /dev/null +++ b/artifacts/ios/build-ipa.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -e + +apppath="build/Payload/butterscotch.app" + +if ! [ -f build/butterscotch ]; then + printf 'Expected a binary at build/butterscotch\n' + exit 1 +fi + +printf 'Creating butterscotch.ipa\n' + +rm -rf "$apppath" +mkdir -p "$apppath" +if command -v ldid >/dev/null; then + ldid -Sartifacts/ios/butterscotch.entitlements build/butterscotch +else + codesign -f -s - --entitlements artifacts/ios/butterscotch.entitlements build/butterscotch +fi +cp build/butterscotch "$apppath/butterscotch" +cp -a artifacts/ios/assets/* "$apppath" +command -v plistutil >/dev/null && plistutil -i artifacts/ios/assets/Info.plist -o "$apppath/Info.plist" -f bin + +cd build +rm -f butterscotch.ipa +zip -qr butterscotch.ipa Payload diff --git a/artifacts/ios/build-toolchain.sh b/artifacts/ios/build-toolchain.sh new file mode 100755 index 000000000..5531a23f7 --- /dev/null +++ b/artifacts/ios/build-toolchain.sh @@ -0,0 +1,127 @@ +#!/bin/sh +# shellcheck disable=2086 +set -e + +[ "${0%/*}" = "$0" ] && scriptroot="." || scriptroot="${0%/*}" +cd "$scriptroot" + +workdir="$PWD/build" +mkdir -p "$workdir" +cd "$workdir" + +# Increase this if we ever make a change to the SDK, for example +# using a newer SDK version, and we need to invalidate the cache. +sdkver=1 +mkdir -p sdks +if ! [ -d sdks/ios8-sdk ] || [ "$(cat sdks/sdkver 2>/dev/null)" != "$sdkver" ]; then + # The iOS 8 SDK supports arm64, armv7s, and armv7 and is small. + # It also doesn't use tbd stubs so we don't need to link ld64 with libtapi. + printf '\nDownloading iOS SDK...\n\n' + [ -d sdks/ios8-sdk ] && rm -rf sdks/ios8-sdk + rm -f iPhoneOS8.0.sdk.tar.lzma + wget https://invoxiplaygames.uk/sdks/iPhoneOS8.0.sdk.tar.lzma + tar -x --lzma -f iPhoneOS8.0.sdk.tar.lzma + mv iPhoneOS8.0.sdk sdks/ios8-sdk + rm iPhoneOS8.0.sdk.tar.lzma + printf '%s' "$sdkver" > sdks/sdkver + cp ../SDKSettings.json sdks/ios8-sdk +fi + +if command -v nproc >/dev/null; then + ncpus="$(nproc)" +else + ncpus="$(sysctl -n hw.ncpu)" +fi + +if command -v gmake > /dev/null; then + _MAKE="gmake" +elif command -v make > /dev/null; then + _make_version="$(command make --version 2>/dev/null)" + case "$_make_version" in + (*GNU*) _MAKE="make" ;; + (*) + printf 'Missing dependency: GNU make\n' + exit 1 + ;; + esac +else + printf 'Missing dependency: GNU make\n' + exit 1 +fi + +make() { + command "$_MAKE" "$@" +} + +if [ -z "$LLVM_CONFIG" ]; then + if command -v llvm-config >/dev/null; then + export LLVM_CONFIG=llvm-config + else + export LLVM_CONFIG=false + fi +fi + +# toolchainver should be increased if we ever make a change to the toolchain, +# for example using a newer cctools version, and we need to invalidate the cache. +toolchainver=2 +if [ "$(cat toolchain/toolchainver 2>/dev/null)" != "$toolchainver" ]; then + outdated_toolchain=1 +fi + +# invalidate toolchain cache if settings change +"$LLVM_CONFIG" --version > toolchainsettings || true +if ! cmp -s toolchainsettings toolchain/lasttoolchainsettings; then + outdated_toolchain=1 +fi + +if [ -z "$outdated_toolchain" ]; then + printf 'Toolchain already built! :)\n' + exit 0 +fi + +rm -rf toolchain +mkdir -p toolchain/bin +mv toolchainsettings toolchain/lasttoolchainsettings + +printf '\nBuilding toolchain...\n\n' + +# this step is needed even on macOS since newer versions of Xcode will straight up not let you link for old iOS versions anymore +cctools_commit=fee8115127bb849d7481ea0015f181d3ebbd33cf +rm -rf cctools-port-* +wget -O- "https://github.com/Un1q32/cctools-port/archive/$cctools_commit.tar.gz" | tar -xz + +cd "cctools-port-$cctools_commit/cctools" +./configure \ + --enable-silent-rules \ + --with-llvm-config="$LLVM_CONFIG" +make -C ld64 -j"$ncpus" +strip ld64/src/ld/ld +mv ld64/src/ld/ld ../../toolchain/bin/ld64.ld64 +make -C libmacho -j"$ncpus" +make -C libstuff -j"$ncpus" +make -C misc strip lipo -j"$ncpus" +strip misc/strip misc/lipo +mv misc/strip ../../toolchain/bin/cctools-strip +mv misc/lipo ../../toolchain/bin/lipo +cd ../.. +rm -rf "cctools-port-$cctools_commit" & + +if [ "$(uname -s)" != "Darwin" ] && ! command -v ldid >/dev/null; then + printf '\nBuilding ldid...\n\n' + + ldid_commit=ef330422ef001ef2aa5792f4c6970d69f3c1f478 + rm -rf ldid-* + wget -O- "https://github.com/ProcursusTeam/ldid/archive/$ldid_commit.tar.gz" | tar -xz + + cd "ldid-$ldid_commit" + make + strip ldid + mv ldid ../toolchain/bin + cd .. + rm -rf "ldid-$ldid_commit" & +fi + +ln -s ../../../ios-cc toolchain/bin + +printf '%s' "$toolchainver" > toolchain/toolchainver +wait diff --git a/artifacts/ios/butterscotch.entitlements b/artifacts/ios/butterscotch.entitlements new file mode 100644 index 000000000..42cfb9cdc --- /dev/null +++ b/artifacts/ios/butterscotch.entitlements @@ -0,0 +1,8 @@ + + + + + get-task-allow + + + diff --git a/artifacts/ios/ios-cc b/artifacts/ios/ios-cc new file mode 100755 index 000000000..efacb52a7 --- /dev/null +++ b/artifacts/ios/ios-cc @@ -0,0 +1,13 @@ +#!/bin/sh + +[ "${0%/*}" = "$0" ] && scriptroot="." || scriptroot="${0%/*}" + +exec "${CLANG:-clang}" \ + -mlinker-version=762 \ + "$@" \ + -isysroot "$scriptroot/../../sdks/ios8-sdk" \ + -target unknown-apple-ios \ + -arch arm64 -Xarch_arm64 -mios-version-min=7.0 \ + -arch armv7 -Xarch_armv7 -mios-version-min=3.0 \ + -fuse-ld=ld64 \ + -Wno-unused-command-line-argument diff --git a/compat/configure.sh b/compat/configure.sh index c5493910c..61673f9e2 100644 --- a/compat/configure.sh +++ b/compat/configure.sh @@ -135,7 +135,20 @@ else fi configlog 'checking the target OS' -if checkdefine '_WIN32' > /dev/null; then + +printf '%s' "\ +#include +#if !TARGET_OS_IPHONE +#error not iOS +#endif +int main(void){return 0;} +" > tmp/test.c + +if check 'if we are targetting iOS' > /dev/null; then + printgreen 'ios' + config 'OS := iOS' + config "_CC := \$(CC) -ObjC" +elif checkdefine '_WIN32' > /dev/null; then printgreen 'windows' config 'OS := Windows' elif checkdefine '__APPLE__' > /dev/null; then diff --git a/src/desktop/backends/ios.c b/src/desktop/backends/ios.c new file mode 100755 index 000000000..20ff05f95 --- /dev/null +++ b/src/desktop/backends/ios.c @@ -0,0 +1,1880 @@ +#include +#include +#include +#include +#include +#include +#include "math_compat.h" + +#include +#include +#include +#include +#include +#include + +/* Undefine macros that conflict with glad headers */ +#undef GL_UNSIGNED_SHORT_1_5_5_5_REV + +#include + +#include "common.h" +#include "input_recording.h" +#include "desktop/platformdefs.h" +#include "gettime.h" +#include "runner_mouse.h" +#ifdef ENABLE_MODERN_GL +#include "gl_renderer.h" +#endif + +/* + * TODO: look into implementing platformSetCursor and platformGetWindowFocus + * They might actually be meaningful on newer iPadOS versions. + */ + +static atomic_bool needsResize = false; +static atomic_bool quitRequested = false; + +static Runner *g_runner; + +static EAGLContext *glcontext; +static GLuint framebuffer; +static GLuint renderbuffer; +static bool glInited = false; +static GLint fbWidth = 0; +static GLint fbHeight = 0; +static CAEAGLLayer *layer; + +#ifdef ENABLE_SW_RENDERER +static uint32_t* nextFb = NULL; +static GLuint swTexture = 0; +static uint32_t* swFbCopy = NULL; +static int swFbCopyWidth = 0, swFbCopyHeight = 0; +#endif + +/* Requested render resolution, as set via platformSetWindowSize() (and + * seeded from platformInit()'s reqW/reqH before that's ever called). Used + * to pick a CAEAGLLayer contentsScale that renders at roughly this pixel + * resolution instead of unconditionally rendering at the device's native + * scale -- see applyRenderScale() below. */ +static int32_t g_reqRenderWidth = 0; +static int32_t g_reqRenderHeight = 0; + +/* w/h of the game's requested resolution. Used only for layout decisions; + * the renderer itself already letterboxes/pillarboxes to this ratio + * regardless of the actual framebuffer size we hand it. */ +static float g_aspectRatio = 1.0f; + +/* The window/overlay are created on the main thread before platformInit() + * (which runs on the game thread) knows the real aspect ratio, so the + * initial layout uses the g_aspectRatio default of 1.0. These let us force + * a relayout once the real value is known, instead of waiting for the + * first rotation to happen to trigger one. */ +static UIView *g_glView = nil; +static UIView *g_overlayView = nil; + +/* Last UIDeviceOrientation actually applied to rootView by + * applyDeviceOrientation:, used to no-op duplicate/redundant notifications. + * Reset to Unknown in startGameWithPath: so each new game session always + * re-syncs to the device's current orientation, even if it's the same + * orientation the previous session last settled into. */ +static UIDeviceOrientation g_lastAppliedOrientation = UIDeviceOrientationUnknown; + +/* Path to the selected game's data.win, filled in by the game list + * controller before the game thread is started. */ +static char g_gamePath[PATH_MAX]; + +/* Built from the persisted fast-forward speed setting right before each + * game launch -- see AppDelegate startGameWithPath:. Sized generously + * for any reasonable floating-point value typed into the settings field. */ +static char g_ffSpeedArg[64] = "--fast-forward-speed=4"; + +/* Cached copy of the "high resolution" setting (see BS_HIGH_RES_DEFAULTS_KEY + * below), refreshed from NSUserDefaults once per game launch in + * startGameWithPath: rather than read on every applyRenderScale() call -- + * the setting can only be changed from the menu, never mid-game. */ +static atomic_bool g_highResEnabled = false; + +/* Cached copy of the renderer setting, refreshed from NSUserDefaults once per + * game launch in startGameWithPath:. */ +static char g_rendererArg[32] = "software"; + +/* Save folder path, derived from the selected game's directory in + * startGameWithPath: and used in gameThread to pass --save-folder. */ +static char g_saveFolderPath[PATH_MAX]; + +/* Games root(s) to scan. NSSearchPathForDirectoriesInDomains( + * NSDocumentDirectory, ...) resolves correctly on every SDK back to iOS + * 2, but what it resolves *to* -- and therefore what else is worth + * checking -- depends on how the app is installed: + * + * - Unsandboxed system app (installed to /Applications, classic + * jailbreak-style): NSHomeDirectory() has no per-app container and + * just resolves to /var/mobile, so NSDocumentDirectory alone gives + * the literal /var/mobile/Documents -- the same shared folder every + * other unsandboxed app on the device also gets pointed at. Append + * "Butterscotch" to keep this install path isolated, matching the + * original hardcoded behavior. There's only one root to scan here. + * + * - Sandboxed install (App Store, or a sandboxed jailbreak profile): + * resolves to this app's own container, e.g. + * /var/mobile/Containers/Data/Application//Documents. Used + * as-is as the primary root. But since some file managers / tweaks + * write into /var/mobile/Documents/Butterscotch regardless of a + * particular app's sandboxing, also scan that as a secondary root + * so games dropped there are still picked up. We never create it + * ourselves in this case (a sandboxed app creating files outside + * its own container is a sandbox violation waiting to happen), and + * if it's unreadable -- doesn't exist, permission denied, sandbox + * blocks the access outright -- that's fine, reloadGames() below + * just silently skips it. + * + * Distinguish sandboxed vs not by comparing the resolved Documents path + * against the known shared path literally, rather than e.g. checking + * for "Containers" in the path -- the sandboxed container layout has + * shifted before and isn't ours to assume; /var/mobile/Documents as the + * unsandboxed shared docs folder has been stable since early iOS and is + * the one thing we can rely on. + * + * Computed and cached once, since neither the container path nor the + * sandboxed-vs-not status changes over the process's lifetime. Index 0 + * is always the primary root (the only one whose read errors get + * surfaced to the user -- see reloadGames()). */ +static NSArray *bsGamesRootsToScan(void) { + static NSArray *cached = nil; + if (!cached) { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *docs = [paths objectAtIndex:0]; + NSMutableArray *roots = [NSMutableArray array]; + + if ([docs isEqualToString:@"/var/mobile/Documents"]) { + NSString *root = [docs stringByAppendingPathComponent:@"Butterscotch"]; + NSError *error = nil; + [[NSFileManager defaultManager] createDirectoryAtPath:root + withIntermediateDirectories:YES + attributes:nil + error:&error]; + /* If creation failed (e.g. read-only filesystem in some exotic + * install), fall back to docs itself rather than a path that + * may not exist -- reloadGames' contentsOfDirectoryAtPath: + * will just report the real error either way. */ + [roots addObject:(error ? docs : root)]; + } else { + [roots addObject:docs]; + [roots addObject:@"/var/mobile/Documents/Butterscotch"]; + } + + cached = [roots retain]; + } + return cached; +} + +static void bsRequestRelayout(void) { + if (g_glView) { + [g_glView performSelectorOnMainThread:@selector(setNeedsLayout) withObject:nil waitUntilDone:YES]; + [g_glView performSelectorOnMainThread:@selector(layoutIfNeeded) withObject:nil waitUntilDone:YES]; + } + if (g_overlayView) { + [g_overlayView performSelectorOnMainThread:@selector(setNeedsLayout) withObject:nil waitUntilDone:YES]; + [g_overlayView performSelectorOnMainThread:@selector(layoutIfNeeded) withObject:nil waitUntilDone:YES]; + [g_overlayView performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:YES]; + } +} + +/* --------------------------------------------------------------------- + * Deferred keyboard event queue. + * + * Touch handling runs on the main UI thread, but RunnerKeyboard_onKeyDown/ + * onKeyUp mutate keyPressed/keyReleased arrays that are also cleared once + * per frame by RunnerKeyboard_beginFrame() on the game thread. Calling + * onKeyDown/onKeyUp directly from touchesBegan/touchesEnded races against + * that clear -- an event set here can be wiped by beginFrame() before + * main.c's next poll ever observes it, more easily on fast devices since + * frames (and beginFrame() calls) come more often, shrinking the window + * in which an async touch event is safe from being clobbered. + * + * Rather than touch runner_keyboard.c or main.c (both intentionally + * platform-independent), buffer transitions here and only apply them + * from platformHandleEvents() -- already polled once per frame on the + * game thread, in the same place needsResize/quitRequested are handled. + * That makes the actual RunnerKeyboard_onKeyDown/onKeyUp calls happen on + * the correct thread, safely ordered relative to that frame's + * beginFrame(), exactly as if a real keyboard event arrived there. + * + * Single-producer (UI thread only ever advances head)/single-consumer + * (game thread only ever advances tail), so the plain atomic_int + * load/store already used elsewhere in this file is sufficient -- no + * locks needed. + * ------------------------------------------------------------------- */ +#define BS_KEY_QUEUE_SIZE 32 +typedef struct { int32_t key; bool isDown; } BSKeyEvent; +static BSKeyEvent bsKeyQueue[BS_KEY_QUEUE_SIZE]; +static atomic_int bsKeyQueueHead = 0; /* next slot to write -- producer (UI thread) owned */ +static atomic_int bsKeyQueueTail = 0; /* next slot to read -- consumer (game thread) owned */ + +static void bsEnqueueKeyEvent(int32_t key, bool isDown) { + int head = atomic_load(&bsKeyQueueHead); + int next = (head + 1) % BS_KEY_QUEUE_SIZE; + if (next == atomic_load(&bsKeyQueueTail)) return; /* full -- drop rather than corrupt, shouldn't happen at touch event rates */ + bsKeyQueue[head].key = key; + bsKeyQueue[head].isDown = isDown; + atomic_store(&bsKeyQueueHead, next); +} + +static void bsDrainKeyEvents(void) { + for (;;) { + int tail = atomic_load(&bsKeyQueueTail); + if (tail == atomic_load(&bsKeyQueueHead)) break; /* empty */ + BSKeyEvent ev = bsKeyQueue[tail]; + atomic_store(&bsKeyQueueTail, (tail + 1) % BS_KEY_QUEUE_SIZE); + if (g_runner) { + if (ev.isDown) RunnerKeyboard_onKeyDown(g_runner->keyboard, ev.key); + else RunnerKeyboard_onKeyUp(g_runner->keyboard, ev.key); + } + } +} + +/* --------------------------------------------------------------------- + * Touch control layout + * + * Portrait: game view anchored to the top of the screen, dpad + z/x/c + * strip along the bottom (gameboy-ish). Landscape: game view centered, + * dpad on the left, z/x/c on the right. If the requested aspect ratio + * needs more space than what's left after reserving the control strip, + * the game view is allowed to grow into the control area rather than + * get squashed -- the controls are translucent, so this is fine. + * ------------------------------------------------------------------- */ + +#define BS_CONTROL_STRIP_PORTRAIT_H 160.0f +#define BS_CONTROL_STRIP_LANDSCAPE_W 120.0f +#define BS_QUIT_BUTTON_SIZE 22.0f +#define BS_QUIT_BUTTON_MARGIN 8.0f +#define BS_FF_BUTTON_SIZE 32.0f /* a bit bigger than the quit button */ +/* padded off the taller of the two top-corner buttons so neither gets clipped */ +#define BS_GAME_TOP_PADDING (BS_QUIT_BUTTON_MARGIN + BS_FF_BUTTON_SIZE + 4.0f) + +typedef struct { + CGRect gameFrame; + CGRect dpadFrame; + CGRect buttonsFrame; + CGRect quitFrame; + CGRect ffFrame; + bool portrait; +} BSLayout; + +/* All notched iPhones have an aspect ratio > 2.0 (375x812, 414x896, + * 390x844, 428x926, etc). Older/non-notched devices are <= 1.78. */ +static bool bsDeviceHasNotch(void) { + CGRect bounds = [[UIScreen mainScreen] bounds]; + CGFloat maxDim = fmaxf(bounds.size.width, bounds.size.height); + CGFloat minDim = fminf(bounds.size.width, bounds.size.height); + return (maxDim / minDim > 2.0f); +} + +static BSLayout computeLayout(CGSize screen) { + BSLayout layout; + layout.portrait = screen.height >= screen.width; + + if (layout.portrait) { + CGFloat neededH = screen.width / g_aspectRatio; + CGFloat gameH = fminf(neededH, screen.height - BS_GAME_TOP_PADDING); + layout.gameFrame = CGRectMake(0, BS_GAME_TOP_PADDING, screen.width, gameH); + + CGFloat stripY = screen.height - BS_CONTROL_STRIP_PORTRAIT_H; + layout.dpadFrame = CGRectMake(16, stripY + 10, 130, 130); + layout.buttonsFrame = CGRectMake(screen.width - 16 - 150, stripY + 30, 150, 50); + } else { + CGFloat neededW = screen.height * g_aspectRatio; + CGFloat gameW = fminf(neededW, screen.width); + CGFloat gameX = (screen.width - gameW) / 2.0f; + layout.gameFrame = CGRectMake(gameX, 0, gameW, screen.height); + + /* On notched devices both landscape orientations have ~44pt of + * safe-area inset on each side (notch cutout zone). Shift the + * controls in so they're not behind the notch. */ + CGFloat notchInset = bsDeviceHasNotch() ? 44.0f : 0.0f; + layout.dpadFrame = CGRectMake(10 + notchInset, (screen.height - 130) / 2.0f + 35, 110, 130); + layout.buttonsFrame = CGRectMake(screen.width - notchInset - 10 - 170, (screen.height - 130) / 2.0f + 60, 170, 60); + } + + layout.quitFrame = CGRectMake(screen.width - BS_QUIT_BUTTON_MARGIN - BS_QUIT_BUTTON_SIZE, + BS_QUIT_BUTTON_MARGIN, BS_QUIT_BUTTON_SIZE, BS_QUIT_BUTTON_SIZE); + + /* Fast-forward: top-left, mirrors the quit button on the top-right. */ + layout.ffFrame = CGRectMake(BS_QUIT_BUTTON_MARGIN, BS_QUIT_BUTTON_MARGIN, + BS_FF_BUTTON_SIZE, BS_FF_BUTTON_SIZE); + return layout; +} + +static void dpadArmRects(CGRect dpad, CGRect *up, CGRect *down, CGRect *left, CGRect *right) { + CGFloat cx = dpad.origin.x + dpad.size.width / 2.0f; + CGFloat cy = dpad.origin.y + dpad.size.height / 2.0f; + CGFloat armW = dpad.size.width / 3.0f; + CGFloat armH = dpad.size.height / 3.0f; + *up = CGRectMake(cx - armW / 2.0f, dpad.origin.y, armW, armH); + *down = CGRectMake(cx - armW / 2.0f, dpad.origin.y + dpad.size.height - armH, armW, armH); + *left = CGRectMake(dpad.origin.x, cy - armH / 2.0f, armW, armH); + *right = CGRectMake(dpad.origin.x + dpad.size.width - armW, cy - armH / 2.0f, armW, armH); +} + +/* 8-way split around the dpad's center so a single touch can express + * diagonals (e.g. up+left) without needing two fingers. */ +static void dpadDirectionsForPoint(CGRect dpad, CGPoint p, bool *up, bool *down, bool *left, bool *right) { + CGFloat cx = dpad.origin.x + dpad.size.width / 2.0f; + CGFloat cy = dpad.origin.y + dpad.size.height / 2.0f; + CGFloat dx = p.x - cx; + CGFloat dy = p.y - cy; + CGFloat deadzone = fminf(dpad.size.width, dpad.size.height) * 0.15f; + + *up = *down = *left = *right = false; + if (fabs(dx) < deadzone && fabs(dy) < deadzone) return; + + CGFloat deg = atan2f(dy, dx) * 180.0f / (CGFloat)M_PI; /* 0 = right, 90 = down (screen coords) */ + if (deg < 0) deg += 360.0f; + + if (deg >= 337.5f || deg < 22.5f) { *right = true; } + else if (deg < 67.5f) { *right = true; *down = true; } + else if (deg < 112.5f) { *down = true; } + else if (deg < 157.5f) { *down = true; *left = true; } + else if (deg < 202.5f) { *left = true; } + else if (deg < 247.5f) { *left = true; *up = true; } + else if (deg < 292.5f) { *up = true; } + else { *up = true; *right = true; } +} + +static void actionButtonRects(CGRect bf, CGRect out[3]) { + CGFloat btnSize = fminf(bf.size.height, bf.size.width / 3.0f) - 8.0f; + for (int i = 0; i < 3; i++) { + out[i] = CGRectMake(bf.origin.x + i * (bf.size.width / 3.0f) + 4.0f, + bf.origin.y + (bf.size.height - btnSize) / 2.0f, + btnSize, btnSize); + } +} + +void platformSetWindowTitle(const char* title) { + (void)title; +} + +bool platformGetWindowSize(int32_t* outW, int32_t* outH) { + if (!outW || !outH) return false; + if (fbWidth <= 0 || fbHeight <= 0) return false; + *outW = fbWidth; + *outH = fbHeight; + return true; +} + +bool platformGetScaledWindowSize(int32_t* outW, int32_t* outH) { + if (!outW || !outH) return false; + CGRect bounds = [[UIScreen mainScreen] bounds]; + if (bounds.size.width <= 0 || bounds.size.height <= 0) return false; + *outW = bounds.size.width; + *outH = bounds.size.height; + return true; +} + +/* + * Stores the game's requested render resolution; applyRenderScale() (run + * from resizeFramebuffer(), triggered here via needsResize) derives the + * actual CAEAGLLayer contentsScale from this plus the game view's current + * point size -- see applyRenderScale() for the up-vs-down-clamp logic. + * We don't recompute the scale directly here because the frame's point + * size can also change independently of this call (rotation), so both + * paths just flag needsResize and let resizeFramebuffer() re-derive it + * from whatever's current on the next frame. + * + * The requested width/height also determine g_aspectRatio, which drives + * computeLayout()'s letterboxing -- previously only set once in + * platformInit(), so a game changing its resolution mid-run (e.g. a + * GameMaker room with a different size) kept the *old* aspect ratio's + * gameFrame even though the framebuffer underneath it got resized to + * match the new one. Recompute it here too, and force a relayout the + * same way platformInit() does for the initial value. + */ +void platformSetWindowSize(int32_t width, int32_t height) { + if (width <= 0 || height <= 0) return; + + g_reqRenderWidth = width; + g_reqRenderHeight = height; + g_aspectRatio = (float)width / (float)height; + bsRequestRelayout(); + atomic_store(&needsResize, true); +} + +/* TODO: touchscreen mouse support */ +void platformGetMousePos(double *xPos, double *yPos) { + *xPos = 0.0; + *yPos = 0.0; +} + +bool platformInit(int32_t reqW, int32_t reqH, const char *title, bool headless) { + (void)title; (void)headless; + + g_aspectRatio = (reqH > 0) ? ((float)reqW / (float)reqH) : 1.0f; + g_reqRenderWidth = reqW; + g_reqRenderHeight = reqH; + bsRequestRelayout(); + +#ifdef ENABLE_MODERN_GL + if (gfx == MODERN_GL) { + glcontext = [[EAGLContext alloc] initWithAPI:3]; + if (!glcontext) + glcontext = [[EAGLContext alloc] initWithAPI:2]; + } +#endif +#ifdef ENABLE_SW_RENDERER + if (gfx == SOFTWARE) + glcontext = [[EAGLContext alloc] initWithAPI:1]; +#endif + + if (!glcontext) { + fprintf(stderr, "Failed to create an OpenGLES context\n"); + return false; + } + + if (![EAGLContext setCurrentContext:glcontext]) { + [glcontext release]; + glcontext = nil; + fprintf(stderr, "Failed to set the OpenGLES context\n"); + return false; + } + + return true; +} + +void platformExit(void) { + if (framebuffer) { + glDeleteFramebuffers(1, &framebuffer); + framebuffer = 0; + } + if (renderbuffer) { + glDeleteRenderbuffers(1, &renderbuffer); + renderbuffer = 0; + } +#ifdef ENABLE_SW_RENDERER + if (swTexture) { + glDeleteTextures(1, &swTexture); + swTexture = 0; + } + if (swFbCopy) { + free(swFbCopy); + swFbCopy = NULL; + } +#endif + [glcontext release]; + glcontext = nil; + glInited = false; +} + +/* [screen respondsToSelector:@selector(scale)] guard is for pre-iOS-4, + * where UIScreen has no notion of a Retina content scale and 1x is + * correct. */ +static CGFloat nativeScreenScale(void) { + UIScreen *screen = [UIScreen mainScreen]; + CGFloat scale = 1.0f; + if ([screen respondsToSelector:@selector(scale)]) { + CGFloat (*getScale)(id, SEL) = (CGFloat (*)(id, SEL))objc_msgSend; + scale = getScale(screen, @selector(scale)); + } + return scale; +} + +/* + * Picks the CAEAGLLayer's contentsScale -- and therefore the framebuffer's + * pixel resolution, since the drawable size handed to + * renderbufferStorage:fromDrawable: is just (layer bounds in points) * + * contentsScale -- based on the game's requested render resolution vs. + * the device's native scale: + * + * - requested < native: render at the requested (lower) resolution. + * Saves fill-rate, which matters a lot on early Retina devices that + * got a 4x pixel-count jump without a matching GPU jump. + * - requested > native: clamp to native. Upsampling past native just + * produces blur with no visible benefit, and burns performance that + * already-underpowered low-res devices can't spare. + * + * Recomputed from scratch every call (cheap) rather than cached, since + * the inputs -- the requested resolution, the "high resolution" setting, + * or the frame's current point size -- can each change independently + * (platformSetWindowSize, the settings menu, or rotation respectively). + */ +static void applyRenderScale(void) { + if (!layer) return; + + CGSize sz = layer.bounds.size; + if (sz.width <= 0.0f || sz.height <= 0.0f) return; + + CGFloat nativeScale = nativeScreenScale(); + CGFloat targetScale = nativeScale; + + int32_t effReqWidth = g_reqRenderWidth; + int32_t effReqHeight = g_reqRenderHeight; + + /* "High resolution" setting (see BS_HIGH_RES_DEFAULTS_KEY): instead of + * clamping down to the game's requested render resolution (e.g. + * 640x480), target the full available logical screen size at native + * pixel density instead -- most noticeable in landscape, where the + * requested resolution would otherwise get scaled down well below + * what the device can actually display. g_glView's superview + * (rootView) already reflects the current device orientation (see + * AppDelegate applyDeviceOrientation:), so its bounds are exactly the + * logical on-screen size for however the device is currently held. */ + if (atomic_load(&g_highResEnabled) && g_glView && g_glView.superview) { + CGSize logicalSize = g_glView.superview.bounds.size; + effReqWidth = (int32_t)(logicalSize.width * nativeScale); + effReqHeight = (int32_t)(logicalSize.height * nativeScale); + } + + if (effReqWidth > 0 && effReqHeight > 0) { + CGFloat sW = (CGFloat)effReqWidth / sz.width; + CGFloat sH = (CGFloat)effReqHeight / sz.height; + /* Use whichever axis wants the smaller scale, so we never exceed + * the requested pixel count on either dimension -- then clamp to + * native so we never exceed the device's real resolution either. */ + targetScale = fminf(nativeScale, fminf(sW, sH)); + } + + if (targetScale <= 0.0f) targetScale = nativeScale; /* safety net */ + + /* CAEAGLLayer derives its backing pixel size as bounds.size (points) * + * contentsScale, and truncates rather than rounds that product down + * to an integer. The division/multiplication chain above -- aspect + * ratio, then layout, then the division just above -- can leave + * targetScale a hair below the value that would exactly reproduce + * the intended pixel count (e.g. 1.499998 instead of 1.5), which + * then silently truncates a whole pixel off the framebuffer on each + * axis (observed as 639x479 instead of 640x480 on an iPhone 5S in + * landscape). Nudge just past that truncation boundary -- this is + * far too small (a fraction of a point in scale) to visibly affect + * the actual rendered resolution, but reliably avoids the off-by-one. */ + targetScale += 0.0005f; + if (targetScale > nativeScale) targetScale = nativeScale; + + if ([layer respondsToSelector:@selector(setContentsScale:)]) { + void (*setScale)(id, SEL, CGFloat) = (void (*)(id, SEL, CGFloat))objc_msgSend; + setScale(layer, @selector(setContentsScale:), targetScale); + } +} + +static void resizeFramebuffer(void) { + applyRenderScale(); + + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + [glcontext renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer]; + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &fbWidth); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &fbHeight); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); + + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + +#ifdef ENABLE_MODERN_GL + if (g_runner && g_runner->renderer) + ((GLRenderer *)g_runner->renderer)->hostFramebuffer = framebuffer; +#endif + + glViewport(0, 0, fbWidth, fbHeight); +} + +void platformInitFunctions(Runner *runner) { + g_runner = runner; + + /* this can't be in platformInit because glad hasn't initialized yet */ + if (!glInited) { +#ifdef ENABLE_SW_RENDERER + if (gfx == SOFTWARE) { + glGenFramebuffers = glGenFramebuffersOES; + glGenRenderbuffers = glGenRenderbuffersOES; + glBindFramebuffer = glBindFramebufferOES; + glBindRenderbuffer = glBindRenderbufferOES; + glGetRenderbufferParameteriv = glGetRenderbufferParameterivOES; + glFramebufferRenderbuffer = glFramebufferRenderbufferOES; + glDeleteFramebuffers = glDeleteFramebuffersOES; + glDeleteRenderbuffers = glDeleteRenderbuffersOES; + + glGenTextures(1, &swTexture); + glBindTexture(GL_TEXTURE_2D, swTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 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); + } +#endif + glGenFramebuffers(1, &framebuffer); + glGenRenderbuffers(1, &renderbuffer); + glInited = true; + } + + resizeFramebuffer(); + atomic_store(&needsResize, false); +} + +#ifdef ENABLE_SW_RENDERER + +static GLint NearestPO2(GLint i) { + for (GLint j = 1; j < 1024 * 1024; j *= 2) + if (i < j) + return j; + assert(!"this shouldn't happen"); + return i; /* fallback */ +} + +void Runner_setNextFrame(uint32_t* framebuffer, int width, int height) { + nextFb = framebuffer; + fbWidth = width; + fbHeight = height; + + /* Allocate power-of-2 sized buffer for texture upload */ + int glWidth = NearestPO2(fbWidth), glHeight = NearestPO2(fbHeight); + if (swFbCopyWidth != glWidth || swFbCopyHeight != glHeight) { + if (swFbCopy) + free(swFbCopy); + size_t rfbSize = sizeof(uint32_t) * glWidth * glHeight; + swFbCopy = safeMalloc(rfbSize); + swFbCopyWidth = glWidth; + swFbCopyHeight = glHeight; + } +} + +#endif + +void platformSwapBuffers(void) { +#ifdef ENABLE_SW_RENDERER + if (gfx == SOFTWARE && nextFb && swFbCopy) { + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, swTexture); + + float xs = (float)swFbCopyWidth / (float)fbWidth; + float ys = (float)swFbCopyHeight / (float)fbHeight; + + /* Copy to power-of-2 buffer */ + for (int y = 0; y < fbHeight; ++y) { + uint32_t* dstline = swFbCopy + y * swFbCopyWidth; + const uint32_t* srcline = nextFb + y * fbWidth; + memcpy(dstline, srcline, fbWidth * sizeof(uint32_t)); + } + nextFb = NULL; + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, swFbCopyWidth, swFbCopyHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, swFbCopy); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + GLfloat vertices[] = { + // tri 1 + -1, -1, 0, 1.0f / ys, + -1, 1, 0, 0, + 1, 1, 1.0f / xs, 0, + // tri 2 + -1, -1, 0, 1.0f / ys, + 1, 1, 1.0f / xs, 0, + 1, -1, 1.0f / xs, 1.0f / ys, + }; + + // count, type, stride, pointer + glVertexPointer(2, GL_FLOAT, 4 * sizeof(float), vertices); + glTexCoordPointer(2, GL_FLOAT, 4 * sizeof(float), vertices + 2); + glDrawArrays(GL_TRIANGLES, 0, 6); + + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } +#endif + [glcontext presentRenderbuffer:GL_RENDERBUFFER]; +} + +void *platformGetProcAddress(const char *name) { + return dlsym(RTLD_NEXT, name); +} + +bool platformHandleEvents(void) { + bsDrainKeyEvents(); + if (atomic_exchange(&needsResize, false)) + resizeFramebuffer(); + if (atomic_load(&quitRequested)) + return true; + return false; +} + +void platformSleepUntil(uint64_t time) { + int64_t remaining = time - nowNanos(); + if (remaining > 2000000) { + remaining -= 1000000; + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = remaining; + nanosleep(&ts, NULL); + } + while (nowNanos() < time) { + // Spin-wait for the remaining sub-millisecond + YIELD(); + } +} + +@interface GLView : UIView +@end + +@implementation GLView + ++ (Class)layerClass { + return [CAEAGLLayer class]; +} + +- (id)initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame])) { + layer = (CAEAGLLayer *)self.layer; + layer.opaque = YES; + /* Frame is fully recomputed in -layoutSubviews on every layout pass + * (rotation, first layout, etc), so we don't rely on autoresizing. */ + self.autoresizingMask = UIViewAutoresizingNone; + + /* Seed with the native scale so the layer is sane for the brief + * window between view creation and the first resizeFramebuffer() + * call; applyRenderScale() will correct this to the requested + * render resolution's scale once sizing kicks in. */ + CGFloat scale = nativeScreenScale(); + if ([layer respondsToSelector:@selector(setContentsScale:)]) { + void (*setScale)(id, SEL, CGFloat) = (void (*)(id, SEL, CGFloat))objc_msgSend; + setScale(layer, @selector(setContentsScale:), scale); + } + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + if (self.superview) { + BSLayout bsLayout = computeLayout(self.superview.bounds.size); + self.frame = bsLayout.gameFrame; + } + atomic_store(&needsResize, true); +} + +- (void)dealloc { + [super dealloc]; +} + +@end + +/* --------------------------------------------------------------------- + * On-screen touch controls: dpad + z/x/c + quit. Sits as a sibling of + * GLView, sized to the full screen (so it can draw controls in the area + * outside the game view, and translucently over it when the game view + * grows into that area). + * ------------------------------------------------------------------- */ + +@interface BSTouchOverlay : UIView { + UITouch *dpadTouch; + int32_t dpadKeysDown[4]; /* up, down, left, right */ + UITouch *buttonTouches[3]; /* z, x, c */ + UITouch *quitTouch; + UITouch *ffTouch; +} +@end + +@implementation BSTouchOverlay + +- (id)initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame])) { + self.backgroundColor = [UIColor clearColor]; + self.opaque = NO; + self.multipleTouchEnabled = YES; + self.userInteractionEnabled = YES; + self.autoresizingMask = UIViewAutoresizingNone; + dpadTouch = nil; + quitTouch = nil; + ffTouch = nil; + for (int i = 0; i < 4; i++) dpadKeysDown[i] = 0; + for (int i = 0; i < 3; i++) buttonTouches[i] = nil; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + if (self.superview) { + self.frame = self.superview.bounds; + } + [self setNeedsDisplay]; +} + +static void drawTranslucentCircle(CGContextRef ctx, CGRect frame, BOOL highlighted) { + CGFloat alpha = highlighted ? 0.55f : 0.30f; + CGContextSetRGBFillColor(ctx, 1.0f, 1.0f, 1.0f, alpha); + CGContextFillEllipseInRect(ctx, frame); + CGContextSetRGBStrokeColor(ctx, 1.0f, 1.0f, 1.0f, 0.6f); + CGContextStrokeEllipseInRect(ctx, frame); +} + +static void drawCenteredLabel(NSString *text, CGRect rect, UIFont *font) { +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 + CGSize size = [text sizeWithAttributes:@{NSFontAttributeName: font}]; +#else + CGSize size = [text sizeWithFont:font]; +#endif + CGPoint pt = CGPointMake(rect.origin.x + (rect.size.width - size.width) / 2.0f, + rect.origin.y + (rect.size.height - size.height) / 2.0f); + [[UIColor whiteColor] set]; +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 + [text drawAtPoint:pt withAttributes:@{NSFontAttributeName: font}]; +#else + [text drawAtPoint:pt withFont:font]; +#endif +} + +- (void)drawRect:(CGRect)rect { + (void)rect; + CGContextRef ctx = UIGraphicsGetCurrentContext(); + BSLayout bsLayout = computeLayout(self.bounds.size); + + CGRect up, down, left, right; + dpadArmRects(bsLayout.dpadFrame, &up, &down, &left, &right); + + CGContextSetRGBFillColor(ctx, 1.0f, 1.0f, 1.0f, dpadKeysDown[0] ? 0.55f : 0.28f); + CGContextFillRect(ctx, up); + CGContextSetRGBFillColor(ctx, 1.0f, 1.0f, 1.0f, dpadKeysDown[1] ? 0.55f : 0.28f); + CGContextFillRect(ctx, down); + CGContextSetRGBFillColor(ctx, 1.0f, 1.0f, 1.0f, dpadKeysDown[2] ? 0.55f : 0.28f); + CGContextFillRect(ctx, left); + CGContextSetRGBFillColor(ctx, 1.0f, 1.0f, 1.0f, dpadKeysDown[3] ? 0.55f : 0.28f); + CGContextFillRect(ctx, right); + + CGRect actionRects[3]; + actionButtonRects(bsLayout.buttonsFrame, actionRects); + NSString *actionLabels[3] = { @"Z", @"X", @"C" }; + UIFont *actionFont = [UIFont boldSystemFontOfSize:18.0f]; + for (int i = 0; i < 3; i++) { + drawTranslucentCircle(ctx, actionRects[i], buttonTouches[i] != nil); + drawCenteredLabel(actionLabels[i], actionRects[i], actionFont); + } + + drawTranslucentCircle(ctx, bsLayout.quitFrame, quitTouch != nil); + drawCenteredLabel(@"X", bsLayout.quitFrame, [UIFont boldSystemFontOfSize:14.0f]); + + drawTranslucentCircle(ctx, bsLayout.ffFrame, ffTouch != nil); + drawCenteredLabel(@">>", bsLayout.ffFrame, [UIFont boldSystemFontOfSize:16.0f]); +} + +- (void)updateDpadUp:(bool)up down:(bool)down left:(bool)left right:(bool)right { + bool newState[4] = { up, down, left, right }; + int32_t keys[4] = { VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT }; + for (int i = 0; i < 4; i++) { + if (newState[i] && !dpadKeysDown[i]) { + bsEnqueueKeyEvent(keys[i], true); + } else if (!newState[i] && dpadKeysDown[i]) { + bsEnqueueKeyEvent(keys[i], false); + } + dpadKeysDown[i] = newState[i] ? 1 : 0; + } +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + BSLayout bsLayout = computeLayout(self.bounds.size); + CGRect actionRects[3]; + actionButtonRects(bsLayout.buttonsFrame, actionRects); + + for (UITouch *touch in touches) { + CGPoint p = [touch locationInView:self]; + + if (!quitTouch && CGRectContainsPoint(CGRectInset(bsLayout.quitFrame, -6, -6), p)) { + quitTouch = [touch retain]; + [self setNeedsDisplay]; + continue; + } + + if (!ffTouch && CGRectContainsPoint(CGRectInset(bsLayout.ffFrame, -6, -6), p)) { + ffTouch = [touch retain]; + bsEnqueueKeyEvent(VK_TAB, true); + [self setNeedsDisplay]; + continue; + } + + if (!dpadTouch && CGRectContainsPoint(CGRectInset(bsLayout.dpadFrame, -20, -20), p)) { + dpadTouch = [touch retain]; + bool up, down, left, right; + dpadDirectionsForPoint(bsLayout.dpadFrame, p, &up, &down, &left, &right); + [self updateDpadUp:up down:down left:left right:right]; + [self setNeedsDisplay]; + continue; + } + + for (int i = 0; i < 3; i++) { + if (!buttonTouches[i] && CGRectContainsPoint(CGRectInset(actionRects[i], -6, -6), p)) { + buttonTouches[i] = [touch retain]; + int32_t vk = (i == 0) ? 'Z' : (i == 1) ? 'X' : 'C'; + bsEnqueueKeyEvent(vk, true); + [self setNeedsDisplay]; + break; + } + } + } +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + if (!dpadTouch) return; + BSLayout bsLayout = computeLayout(self.bounds.size); + for (UITouch *touch in touches) { + if (touch == dpadTouch) { + CGPoint p = [touch locationInView:self]; + bool up, down, left, right; + dpadDirectionsForPoint(bsLayout.dpadFrame, p, &up, &down, &left, &right); + [self updateDpadUp:up down:down left:left right:right]; + [self setNeedsDisplay]; + break; + } + } +} + +- (void)handleTouchEnd:(NSSet *)touches { + for (UITouch *touch in touches) { + if (touch == dpadTouch) { + [self updateDpadUp:false down:false left:false right:false]; + [dpadTouch release]; + dpadTouch = nil; + [self setNeedsDisplay]; + continue; + } + if (touch == quitTouch) { + [quitTouch release]; + quitTouch = nil; + atomic_store(&quitRequested, true); + [self setNeedsDisplay]; + continue; + } + if (touch == ffTouch) { + bsEnqueueKeyEvent(VK_TAB, false); + [ffTouch release]; + ffTouch = nil; + [self setNeedsDisplay]; + continue; + } + for (int i = 0; i < 3; i++) { + if (touch == buttonTouches[i]) { + int32_t vk = (i == 0) ? 'Z' : (i == 1) ? 'X' : 'C'; + bsEnqueueKeyEvent(vk, false); + [buttonTouches[i] release]; + buttonTouches[i] = nil; + [self setNeedsDisplay]; + break; + } + } + } +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + [self handleTouchEnd:touches]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + [self handleTouchEnd:touches]; +} + +- (void)dealloc { + if (dpadTouch) [dpadTouch release]; + if (quitTouch) [quitTouch release]; + if (ffTouch) [ffTouch release]; + for (int i = 0; i < 3; i++) if (buttonTouches[i]) [buttonTouches[i] release]; + [super dealloc]; +} + +@end + +@interface BSViewController : UIViewController +@end + +@implementation BSViewController + +/* + * Apple keeps changing how you're supposed to tell UIKit to handle shit, + * so we just pretend we're portrait only and then rotate manually. + */ +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + return interfaceOrientation == UIInterfaceOrientationPortrait; +} + +/* iOS 6+ query this pair instead of the method above. NSUInteger (rather + * than UIInterfaceOrientationMask) as the return type keeps this + * compiling against SDKs that predate that type -- it's still called + * correctly at runtime on OS versions that support it. */ +- (BOOL)shouldAutorotate { + return NO; +} + +- (NSUInteger)supportedInterfaceOrientations { + return 1; /* UIInterfaceOrientationMaskPortrait's raw value (1 << UIInterfaceOrientationPortrait) */ +} + +/* Pre-iOS-7: without this, the view is offset 20pt down to make room for + * the status bar, even though we hide the status bar at launch. */ +- (BOOL)wantsFullScreenLayout { + return YES; +} + +@end + +/* --------------------------------------------------------------------- + * Settings: the fast-forward speed multiplier and the "high resolution" + * toggle, both persisted via NSUserDefaults. The fast-forward speed is + * applied as a --fast-forward-speed= argv entry, and the high-res flag + * is cached into g_highResEnabled, the next time a game is launched (see + * AppDelegate startGameWithPath:). + * ------------------------------------------------------------------- */ + +#define BS_FF_SPEED_DEFAULTS_KEY @"BSFastForwardSpeed" +#define BS_DEFAULT_FF_SPEED 4.0 + +#define BS_HIGH_RES_DEFAULTS_KEY @"BSHighResolution" + +#define BS_RENDERER_DEFAULTS_KEY @"BSRenderer" +#define BS_RENDERER_SOFTWARE 0 +#define BS_RENDERER_MODERN_GL 1 + +/* Falls back to the default any time the stored value is missing or + * non-positive (e.g. first launch, or a corrupted/edited defaults plist). */ +static double bsLoadFastForwardSpeed(void) { + double v = [[NSUserDefaults standardUserDefaults] doubleForKey:BS_FF_SPEED_DEFAULTS_KEY]; + return (v > 0.0) ? v : BS_DEFAULT_FF_SPEED; +} + +/* boolForKey: returns NO when the key has never been set, so this is off + * by default with no separate first-launch handling needed. */ +static bool bsLoadHighResEnabled(void) { + return [[NSUserDefaults standardUserDefaults] boolForKey:BS_HIGH_RES_DEFAULTS_KEY]; +} + +/* Returns the saved renderer preference, defaulting to software if never set. */ +static int bsLoadRendererPreference(void) { + NSInteger v = [[NSUserDefaults standardUserDefaults] integerForKey:BS_RENDERER_DEFAULTS_KEY]; + if (v != BS_RENDERER_SOFTWARE && v != BS_RENDERER_MODERN_GL) + return BS_RENDERER_SOFTWARE; + return (int)v; +} + +/* Checks if the device supports OpenGL ES 2.0. On iOS 2.0+, this can be + * checked by trying to create an EAGLContext with API=2. */ +static bool bsSupportsGLES2(void) { + EAGLContext *testContext = [[EAGLContext alloc] initWithAPI:2]; + if (testContext) { + [testContext release]; + return true; + } + return false; +} + +/* Renders a simple gear glyph into a UIImage via Core Graphics, rather than + * relying on a Unicode gear character rendering correctly on very old + * font/rendering stacks. Uses UIGraphicsBeginImageContext (iOS 2+) rather + * than the *WithOptions variant (iOS 4+) to stay compatible with the low + * end of the SDK matrix. */ +static UIImage *createGearIconImage(CGFloat size, UIColor *color) { + UIGraphicsBeginImageContext(CGSizeMake(size, size)); + CGContextRef ctx = UIGraphicsGetCurrentContext(); + + CGPoint center = CGPointMake(size / 2.0f, size / 2.0f); + CGFloat outerR = size * 0.46f; + CGFloat toothR = size * 0.12f; + CGFloat innerR = size * 0.28f; + const int teeth = 8; + + CGContextSetFillColorWithColor(ctx, color.CGColor); + + CGMutablePathRef path = CGPathCreateMutable(); + for (int i = 0; i < teeth * 2; i++) { + CGFloat angle = (CGFloat)i * (CGFloat)M_PI / (CGFloat)teeth; + CGFloat r = (i % 2 == 0) ? (outerR + toothR) : outerR; + CGFloat x = center.x + r * cosf(angle); + CGFloat y = center.y + r * sinf(angle); + if (i == 0) CGPathMoveToPoint(path, NULL, x, y); + else CGPathAddLineToPoint(path, NULL, x, y); + } + CGPathCloseSubpath(path); + CGContextAddPath(ctx, path); + CGContextFillPath(ctx); + CGPathRelease(path); + + /* Punch the center hole so it reads as a gear rather than a spiky disc. */ + CGContextSetBlendMode(ctx, kCGBlendModeClear); + CGContextFillEllipseInRect(ctx, CGRectMake(center.x - innerR, center.y - innerR, + innerR * 2.0f, innerR * 2.0f)); + + UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return img; +} + +@interface BSSettingsViewController : UIViewController { + UITextField *speedField; + UISwitch *highResSwitch; + UISegmentedControl *rendererControl; +} +@end + +/* UIKeyboardTypeDecimalPad was added in iOS 4.1. Rather than gate on the + * SDK this file happens to be compiled against (which says nothing about + * whether the *running* device actually supports it, and may not even + * declare the symbol), reference its known raw enum value directly and + * decide whether to use it purely from the device's reported OS version + * at runtime. Falls back to NumbersAndPunctuation (available since iOS + * 2.0) on anything older -- still exposes a decimal point. */ +static UIKeyboardType bsNumericKeyboardType(void) { + NSString *sysVersion = [[UIDevice currentDevice] systemVersion]; + if ([sysVersion compare:@"4.1" options:NSNumericSearch] != NSOrderedAscending) + return (UIKeyboardType)8; /* UIKeyboardTypeDecimalPad's raw value */ + return UIKeyboardTypeNumbersAndPunctuation; +} + +@implementation BSSettingsViewController + +- (void)loadView { + CGRect bounds = [[UIScreen mainScreen] bounds]; + UIView *root = [[[UIView alloc] initWithFrame:bounds] autorelease]; + root.backgroundColor = [UIColor whiteColor]; + root.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + UILabel *fieldLabel = [[[UILabel alloc] initWithFrame:CGRectMake(20, 20, bounds.size.width - 40, 24)] autorelease]; + fieldLabel.text = @"Fast forward speed (multiplier):"; + fieldLabel.font = [UIFont systemFontOfSize:15.0f]; + fieldLabel.backgroundColor = [UIColor clearColor]; + [root addSubview:fieldLabel]; + + speedField = [[UITextField alloc] initWithFrame:CGRectMake(20, 48, bounds.size.width - 40, 36)]; + speedField.borderStyle = UITextBorderStyleRoundedRect; + speedField.keyboardType = bsNumericKeyboardType(); + speedField.delegate = self; + speedField.text = [NSString stringWithFormat:@"%g", bsLoadFastForwardSpeed()]; + [root addSubview:speedField]; + + /* sizeToFit rather than a hardcoded frame, since UISwitch's on-screen + * size has drifted slightly across OS versions. */ + highResSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; + [highResSwitch sizeToFit]; + CGRect swFrame = highResSwitch.frame; + swFrame.origin = CGPointMake(bounds.size.width - 20 - swFrame.size.width, 100); + highResSwitch.frame = swFrame; + highResSwitch.on = bsLoadHighResEnabled(); + [root addSubview:highResSwitch]; + + UILabel *highResLabel = [[[UILabel alloc] initWithFrame:CGRectMake(20, 100, swFrame.origin.x - 30, swFrame.size.height)] autorelease]; + highResLabel.text = @"High resolution (landscape):"; + highResLabel.font = [UIFont systemFontOfSize:15.0f]; + highResLabel.backgroundColor = [UIColor clearColor]; + [root addSubview:highResLabel]; + + /* Renderer selection: segmented control with Software and Modern GL options. + * Only show this if both renderers are available, otherwise force the appropriate one. */ +#if !defined(ENABLE_MODERN_GL) || !defined(ENABLE_SW_RENDERER) + rendererControl = nil; +#else + bool supportsGLES2 = bsSupportsGLES2(); + rendererControl = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:@"Software", @"Modern GL", nil]]; + [rendererControl sizeToFit]; + CGRect rendererFrame = rendererControl.frame; + rendererFrame.origin = CGPointMake(20, 150); + rendererFrame.size.width = bounds.size.width - 40; + rendererControl.frame = rendererFrame; + rendererControl.selectedSegmentIndex = bsLoadRendererPreference(); + rendererControl.enabled = supportsGLES2; + if (!supportsGLES2) { + rendererControl.selectedSegmentIndex = BS_RENDERER_SOFTWARE; + } + [root addSubview:rendererControl]; + + UILabel *rendererLabel = [[[UILabel alloc] initWithFrame:CGRectMake(20, 130, bounds.size.width - 40, 20)] autorelease]; + rendererLabel.text = @"Renderer:"; + rendererLabel.font = [UIFont systemFontOfSize:15.0f]; + rendererLabel.backgroundColor = [UIColor clearColor]; + [root addSubview:rendererLabel]; +#endif + + self.view = root; +} + +- (id)init { + if ((self = [super init])) { + self.title = @"Settings"; + if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)]) + ((void(*)(id, SEL, NSUInteger))objc_msgSend)(self, @selector(setEdgesForExtendedLayout:), 0); + UIBarButtonItem *saveItem = [[UIBarButtonItem alloc] initWithTitle:@"Save" + style:UIBarButtonItemStyleDone + target:self action:@selector(saveTapped)]; + self.navigationItem.rightBarButtonItem = saveItem; + [saveItem release]; + } + return self; +} + +- (void)saveTapped { + [speedField resignFirstResponder]; + double v = [speedField.text doubleValue]; + if (v > 0.0) { + [[NSUserDefaults standardUserDefaults] setDouble:v forKey:BS_FF_SPEED_DEFAULTS_KEY]; + } + /* Invalid/non-positive input is silently ignored -- previously saved + * value (or the default) is left untouched. */ + + [[NSUserDefaults standardUserDefaults] setBool:highResSwitch.isOn forKey:BS_HIGH_RES_DEFAULTS_KEY]; +#if defined(ENABLE_MODERN_GL) && defined(ENABLE_SW_RENDERER) + if (rendererControl) + [[NSUserDefaults standardUserDefaults] setInteger:rendererControl.selectedSegmentIndex forKey:BS_RENDERER_DEFAULTS_KEY]; +#endif + [[NSUserDefaults standardUserDefaults] synchronize]; + + [[[UIApplication sharedApplication] delegate] performSelector:@selector(settingsDone)]; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + return YES; +} + +- (void)dealloc { + [speedField release]; + [highResSwitch release]; + if (rendererControl) [rendererControl release]; + [super dealloc]; +} + +@end + +/* --------------------------------------------------------------------- + * Game selection menu. Scans the games roots (see bsGamesRootsToScan() + * above -- a "Butterscotch" folder under Documents, one way or another) + * for subfolders that contain a data.win, and lets the user pick one. + * Re-scans every time the view (re)appears so games dropped in via file + * transfer while the app is running (or after returning from a game) + * show up. + * ------------------------------------------------------------------- */ + +/* Forward-declare viewDidLayoutSubviews on UITableViewController so the + * compiler doesn't warn when we call super on it -- the method is only + * called at runtime when the runtime supports it (iOS 5+), guarded by + * instancesRespondToSelector:. */ +@interface UITableViewController (BSIOS5Compat) +- (void)viewDidLayoutSubviews; +@end + +@interface BSGameListViewController : UITableViewController { + NSMutableArray *games; + NSIndexPath *pendingDeleteIndexPath; + NSIndexPath *longPressIndexPath; + UIActivityIndicatorView *refreshIndicator; + UIView *refreshOverlay; + BOOL isRefreshing; +} +- (void)reloadGames; +- (void)handleRefresh:(id)sender; +@end + +@implementation BSGameListViewController + +- (id)init { +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 30000 + if ((self = [super initWithStyle:UITableViewStylePlain])) { +#else + if ((self = [super init])) { +#endif + games = [[NSMutableArray alloc] init]; + self.title = @"Butterscotch"; + + UIImage *gearImg = createGearIconImage(22.0f, [UIColor darkGrayColor]); + UIBarButtonItem *gearItem = [[UIBarButtonItem alloc] initWithImage:gearImg + style:UIBarButtonItemStylePlain + target:self action:@selector(settingsTapped)]; + self.navigationItem.leftBarButtonItem = gearItem; + [gearItem release]; + + [self reloadGames]; + + /* Add pull-to-refresh support. UIRefreshControl is available since iOS 6, + * so use it if present, otherwise fall back to a custom implementation. */ + Class refreshControlClass = NSClassFromString(@"UIRefreshControl"); + if (refreshControlClass) { + id refreshControl = [[refreshControlClass alloc] init]; + [refreshControl addTarget:self action:@selector(handleRefresh:) forControlEvents:UIControlEventValueChanged]; + [self setValue:refreshControl forKey:@"refreshControl"]; + [refreshControl release]; + } else { + /* Custom refresh for iOS < 6: use centered activity indicator overlay */ + refreshIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + refreshIndicator.hidesWhenStopped = YES; + refreshIndicator.hidden = YES; + + refreshOverlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + refreshOverlay.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.7]; + refreshOverlay.hidden = YES; + refreshIndicator.center = CGPointMake(50, 50); + [refreshOverlay addSubview:refreshIndicator]; + } + + /* Add long-press gesture for game context menu (iOS 3.2+) */ + Class lpClass = NSClassFromString(@"UILongPressGestureRecognizer"); + if (lpClass) { + id lp = [lpClass alloc]; + lp = ((id(*)(id, SEL, id, SEL))objc_msgSend)(lp, @selector(initWithTarget:action:), self, @selector(handleLongPress:)); + ((void(*)(id, SEL, id))objc_msgSend)(self.tableView, @selector(addGestureRecognizer:), lp); + [lp release]; + } + + } + return self; +} + +- (void)settingsTapped { + [[[UIApplication sharedApplication] delegate] performSelector:@selector(showSettings)]; +} + +/* Each entry in `games` is an NSDictionary with: + * "name" -- the folder's display name (may repeat across roots if the + * same name exists in more than one scanned root -- we don't + * dedupe, since they could be entirely different games that + * just happen to share a folder name). + * "path" -- the full path to that folder, already resolved against + * whichever root it was found under. Kept in full rather + * than re-derived from bsGamesRootsToScan() + name at + * use-time, since a name alone is now ambiguous as to which + * root it came from. */ +- (void)reloadGames { + [games removeAllObjects]; + + NSFileManager *fm = [NSFileManager defaultManager]; + NSArray *roots = bsGamesRootsToScan(); + + for (NSUInteger i = 0; i < [roots count]; i++) { + NSString *root = [roots objectAtIndex:i]; + NSString *gamesDir = [root stringByAppendingPathComponent:@"games"]; + + /* Ensure the games directory exists for the primary root */ + if (i == 0) { + [[NSFileManager defaultManager] createDirectoryAtPath:gamesDir + withIntermediateDirectories:YES + attributes:nil + error:nil]; + } + + NSError *error = nil; + NSArray *entries = [fm contentsOfDirectoryAtPath:gamesDir error:&error]; + if (error) { + /* Only the primary root (index 0) is expected to always be + * readable -- it's either our own sandboxed container or a + * freshly-created directory. Secondary roots (currently just + * the shared /var/mobile/Documents/Butterscotch courtesy + * scan when sandboxed) are allowed to be missing/unreadable; + * silently skip those rather than alerting the user to + * something they can't do anything about. */ + if (i == 0) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" + message:[error localizedDescription] + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + [alert release]; + } + continue; + } + + for (NSString *name in entries) { + NSString *dir = [gamesDir stringByAppendingPathComponent:name]; + BOOL isDir = NO; + if (![fm fileExistsAtPath:dir isDirectory:&isDir] || !isDir) continue; + + NSString *dataWin = [dir stringByAppendingPathComponent:@"data.win"]; + if ([fm fileExistsAtPath:dataWin]) { + [games addObject:[NSDictionary dictionaryWithObjectsAndKeys: + name, @"name", dir, @"path", nil]]; + } + } + } + + [self.tableView reloadData]; +} + +- (void)handleRefresh:(id)sender { + [self reloadGames]; + + /* End the refresh animation for UIRefreshControl (iOS 6+) */ + Class refreshControlClass = NSClassFromString(@"UIRefreshControl"); + if (refreshControlClass && [sender isKindOfClass:refreshControlClass]) { + [sender performSelector:@selector(endRefreshing)]; + } else { + /* End custom refresh for iOS < 6 */ + isRefreshing = NO; + [refreshIndicator stopAnimating]; + refreshOverlay.hidden = YES; + [refreshOverlay removeFromSuperview]; + } +} + +- (void)viewDidLayoutSubviews { + if ([UIViewController instancesRespondToSelector:@selector(viewDidLayoutSubviews)]) + [super viewDidLayoutSubviews]; + /* The 7.0 SDK's automaticallyAdjustsScrollViewInsets only insets 64pt + * (44 nav + 20 old-style status). On a notched device the status area + * is 44pt, so we need 24pt more for the first row to be fully visible. */ + if (bsDeviceHasNotch() && self.tableView.contentInset.top < 66.0f) { + UIEdgeInsets inset = self.tableView.contentInset; + inset.top = 88.0f; + self.tableView.contentInset = inset; + self.tableView.scrollIndicatorInsets = inset; + } +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [self reloadGames]; +} + +/* Custom pull-to-refresh implementation for iOS < 6 */ +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { + (void)decelerate; + if (!refreshIndicator) return; /* Using UIRefreshControl on iOS 6+ */ + + CGFloat offset = scrollView.contentOffset.y; + if (offset < -60 && !isRefreshing) { + isRefreshing = YES; + refreshOverlay.hidden = NO; + refreshOverlay.center = CGPointMake(self.tableView.bounds.size.width / 2.0f, + self.tableView.bounds.size.height / 2.0f); + [self.tableView addSubview:refreshOverlay]; + [refreshIndicator startAnimating]; + [self performSelector:@selector(handleRefresh:) withObject:nil afterDelay:0.5]; + } +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + (void)tableView; (void)section; + return [games count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *reuseId = @"BSGameCell"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId]; + if (!cell) { +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 30000 + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId] autorelease]; +#else + cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseId] autorelease]; +#endif + } +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 30000 + cell.textLabel.text = [[games objectAtIndex:indexPath.row] objectForKey:@"name"]; +#else + cell.text = [[games objectAtIndex:indexPath.row] objectForKey:@"name"]; +#endif + return cell; +} + +/* Enables the standard swipe-left-to-delete gesture for every row; UIKit + * reveals its built-in red "Delete" button automatically once this + * returns YES, no custom gesture recognizer needed. */ +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { + (void)tableView; (void)indexPath; + return YES; +} + +/* Called when the user actually taps the revealed Delete button. We don't + * touch the filesystem here -- removing a game folder also wipes any save + * data living alongside data.win, so we confirm first via UIAlertView + * (rather than UIAlertController, which needs iOS 8+) and do the real + * removal from the alert's delegate callback below. */ +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle + forRowAtIndexPath:(NSIndexPath *)indexPath { + (void)tableView; + if (editingStyle != UITableViewCellEditingStyleDelete) return; + + NSString *name = [[games objectAtIndex:indexPath.row] objectForKey:@"name"]; + + [pendingDeleteIndexPath release]; + pendingDeleteIndexPath = [indexPath retain]; + + NSString *msg = [NSString stringWithFormat: + @"Delete \"%@\" and all of its save data? This cannot be undone.", name]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Delete Game" + message:msg + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:@"Delete", nil]; + [alert show]; + [alert release]; +} + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { + if (longPressIndexPath) { + if (buttonIndex == alertView.cancelButtonIndex) { + [longPressIndexPath release]; + longPressIndexPath = nil; + return; + } + + NSDictionary *entry = [games objectAtIndex:longPressIndexPath.row]; + NSString *dir = [entry objectForKey:@"path"]; + NSString *name = [entry objectForKey:@"name"]; + + if (buttonIndex == 2) { + /* "Delete Game" — reuse the existing confirmation flow */ + NSString *msg = [NSString stringWithFormat: + @"Delete \"%@\" and all of its save data? This cannot be undone.", name]; + pendingDeleteIndexPath = longPressIndexPath; + longPressIndexPath = nil; + UIAlertView *confirm = [[UIAlertView alloc] initWithTitle:@"Delete Game" + message:msg + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:@"Delete", nil]; + [confirm show]; + [confirm release]; + } else { + /* "Delete Save Data" — remove only the saves folder */ + NSString *gamesRoot = [dir stringByDeletingLastPathComponent]; + NSString *butterscotchDir = [gamesRoot stringByDeletingLastPathComponent]; + NSString *saveDir = [[butterscotchDir stringByAppendingPathComponent:@"saves"] stringByAppendingPathComponent:name]; + [[NSFileManager defaultManager] removeItemAtPath:saveDir error:nil]; + [longPressIndexPath release]; + longPressIndexPath = nil; + } + return; + } + + NSIndexPath *indexPath = pendingDeleteIndexPath; + pendingDeleteIndexPath = nil; + + if (!indexPath) return; + if (buttonIndex == alertView.cancelButtonIndex) { + [indexPath release]; + return; + } + + NSDictionary *entry = [games objectAtIndex:indexPath.row]; + NSString *dir = [entry objectForKey:@"path"]; + + /* Derive the corresponding saves folder and remove it too. */ + NSString *gameName = [dir lastPathComponent]; + NSString *gamesRoot = [dir stringByDeletingLastPathComponent]; + NSString *butterscotchDir = [gamesRoot stringByDeletingLastPathComponent]; + NSString *saveDir = [[butterscotchDir stringByAppendingPathComponent:@"saves"] stringByAppendingPathComponent:gameName]; + [[NSFileManager defaultManager] removeItemAtPath:saveDir error:nil]; + + /* removeItemAtPath: recursively removes directory contents. */ + NSError *error = nil; + if ([[NSFileManager defaultManager] removeItemAtPath:dir error:&error]) { + [games removeObjectAtIndex:indexPath.row]; + [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] + withRowAnimation:UITableViewRowAnimationFade]; + } else { + NSLog(@"Butterscotch: failed to delete game directory %@: %@", dir, error); + /* Directory listing may now be stale (partial delete, permissions + * error, etc) -- resync from disk rather than trust our cache. */ + [self reloadGames]; + } + + [indexPath release]; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + NSString *gameDir = [[games objectAtIndex:indexPath.row] objectForKey:@"path"]; + id delegate = [[UIApplication sharedApplication] delegate]; + [delegate performSelector:@selector(startGameWithPath:) withObject:gameDir]; +} + +- (void)handleLongPress:(id)gesture { + if ([gesture state] != 1) return; /* UIGestureRecognizerStateBegan */ + + CGPoint p = [gesture locationInView:self.tableView]; + NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:p]; + if (!indexPath) return; + + [longPressIndexPath release]; + longPressIndexPath = [indexPath retain]; + + NSString *name = [[games objectAtIndex:indexPath.row] objectForKey:@"name"]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:name + message:nil + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:@"Delete Save Data", @"Delete Game", nil]; + [alert show]; + [alert release]; +} + +- (void)dealloc { + [longPressIndexPath release]; + [pendingDeleteIndexPath release]; + [games release]; + [refreshIndicator release]; + [refreshOverlay release]; + [super dealloc]; +} + +@end + +extern int game_main(int argc, char *argv[]); + +@interface AppDelegate : NSObject { + UIWindow *window; + GLView *view; + BSTouchOverlay *overlay; + UIView *rootView; + BSGameListViewController *gameListVC; + BSSettingsViewController *settingsVC; + UINavigationController *navController; + BOOL usingRootViewController; +} +- (void)startGameWithPath:(NSString *)gamePath; +- (void)returnToMenu; +- (void)showSettings; +- (void)settingsDone; +- (void)orientationChanged:(NSNotification *)note; +- (void)applyDeviceOrientation:(UIDeviceOrientation)devOrientation; +@end + +@implementation AppDelegate + +- (void)gameThread { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + optind = 1; + optreset = 1; + + static char arg0[] = "butterscotch"; + static char arg1[] = "--lazy-textures"; + static char arg2[] = "--lazy-rooms"; + static char arg3[] = "--renderer"; + static char arg4[] = "--save-folder"; + char *argv[] = { arg0, arg1, arg2, g_ffSpeedArg, arg3, g_rendererArg, arg4, g_saveFolderPath, g_gamePath, NULL }; + game_main(9, argv); + + [self performSelectorOnMainThread:@selector(returnToMenu) withObject:nil waitUntilDone:NO]; + + [pool release]; +} + +- (void)startGameWithPath:(NSString *)gamePath { + NSString *dataWin = [gamePath stringByAppendingPathComponent:@"data.win"]; + strlcpy(g_gamePath, [dataWin fileSystemRepresentation], sizeof(g_gamePath)); + snprintf(g_ffSpeedArg, sizeof(g_ffSpeedArg), "--fast-forward-speed=%g", bsLoadFastForwardSpeed()); + atomic_store(&g_highResEnabled, bsLoadHighResEnabled()); + + /* Derive the save folder from the game path: strip "games/" to + * get the Butterscotch base, then append "saves/". */ + NSString *gameName = [gamePath lastPathComponent]; + NSString *gamesDir = [gamePath stringByDeletingLastPathComponent]; + NSString *butterscotchDir = [gamesDir stringByDeletingLastPathComponent]; + NSString *gameSaveDir = [[butterscotchDir stringByAppendingPathComponent:@"saves"] stringByAppendingPathComponent:gameName]; + [[NSFileManager defaultManager] createDirectoryAtPath:gameSaveDir + withIntermediateDirectories:YES + attributes:nil + error:nil]; + strlcpy(g_saveFolderPath, [gameSaveDir fileSystemRepresentation], sizeof(g_saveFolderPath)); + +#if defined(ENABLE_MODERN_GL) && defined(ENABLE_SW_RENDERER) + int rendererPref = bsLoadRendererPreference(); + if (rendererPref == BS_RENDERER_MODERN_GL && bsSupportsGLES2()) { + snprintf(g_rendererArg, sizeof(g_rendererArg), "modern-gl"); + } else { + snprintf(g_rendererArg, sizeof(g_rendererArg), "software"); + } +#elif defined(ENABLE_MODERN_GL) + snprintf(g_rendererArg, sizeof(g_rendererArg), "modern-gl"); +#else + snprintf(g_rendererArg, sizeof(g_rendererArg), "software"); +#endif + + atomic_store(&quitRequested, false); + + CGRect bounds = [[UIScreen mainScreen] bounds]; + BSLayout bsLayout = computeLayout(bounds.size); + + view = [[GLView alloc] initWithFrame:bsLayout.gameFrame]; + overlay = [[BSTouchOverlay alloc] initWithFrame:bounds]; + g_glView = view; + g_overlayView = overlay; + + rootView = [[UIView alloc] initWithFrame:bounds]; + rootView.autoresizingMask = UIViewAutoresizingNone; /* we drive rootView's frame manually now -- autoresizing masks are unreliable once a transform is applied */ + [rootView addSubview:view]; + [rootView addSubview:overlay]; /* on top, so controls are visible over the game */ + + /* Force a fresh sync even if the device is in the same physical + * orientation the last game session ended in -- g_lastAppliedOrientation + * is stale for this brand-new rootView. */ + g_lastAppliedOrientation = UIDeviceOrientationUnknown; + [self applyDeviceOrientation:[[UIDevice currentDevice] orientation]]; + + if (usingRootViewController) { + UIViewController *vc = [[BSViewController alloc] init]; + vc.view = rootView; + [window performSelector:@selector(setRootViewController:) withObject:vc]; + [vc release]; + } else { + NSArray *subs = [[window.subviews copy] autorelease]; + for (UIView *sub in subs) [sub removeFromSuperview]; + [window addSubview:rootView]; + } + + [NSThread detachNewThreadSelector:@selector(gameThread) toTarget:self withObject:nil]; +} + +- (void)returnToMenu { + g_glView = nil; + g_overlayView = nil; + + if (usingRootViewController) { + [window performSelector:@selector(setRootViewController:) withObject:navController]; + } else { + NSArray *subs = [[window.subviews copy] autorelease]; + for (UIView *sub in subs) [sub removeFromSuperview]; + [window addSubview:navController.view]; + } + + [overlay release]; + overlay = nil; + [rootView release]; + rootView = nil; + [view release]; + view = nil; +} + +- (void)showSettings { + if (!settingsVC) settingsVC = [[BSSettingsViewController alloc] init]; + [navController pushViewController:settingsVC animated:YES]; +} + +- (void)settingsDone { + [navController popViewControllerAnimated:YES]; +} + +- (void)orientationChanged:(NSNotification *)note { + (void)note; + [self applyDeviceOrientation:[[UIDevice currentDevice] orientation]]; +} + +/* --------------------------------------------------------------------- + * Manual rotation for the game screen. Locks the interface orientation + * (see BSViewController) and instead rotates rootView ourselves off raw + * UIDeviceOrientation notifications, which have been stable since iOS + * 2.0 -- unlike the view-controller rotation callback chain, which isn't. + * + * rootView's children (GLView, BSTouchOverlay) are untouched by this; + * they just read superview.bounds.size on layout, same as always, so + * computeLayout()'s existing portrait/landscape logic keeps working + * without any changes on that side. + * + * Only applies when a game is running (rootView != nil). The menu / + * settings navigation stack is left in the OS's default fixed + * orientation and does not rotate. + * ------------------------------------------------------------------- */ +- (void)applyDeviceOrientation:(UIDeviceOrientation)devOrientation { + if (!rootView) return; + + CGFloat angle; + BOOL swapped; + switch (devOrientation) { + case UIDeviceOrientationPortrait: angle = 0.0f; swapped = NO; break; + case UIDeviceOrientationPortraitUpsideDown: angle = (CGFloat)M_PI; swapped = NO; break; + /* Device rotated so its left edge points "up" corresponds to + * interface orientation LandscapeRight, i.e. a +90 degree + * compensating rotation; the other case is the mirror image. */ + case UIDeviceOrientationLandscapeLeft: angle = (CGFloat)M_PI_2; swapped = YES; break; + case UIDeviceOrientationLandscapeRight: angle = -(CGFloat)M_PI_2; swapped = YES; break; + default: + /* FaceUp / FaceDown / Unknown: not a usable orientation -- + * keep whatever we last applied. */ + return; + } + + if (devOrientation == g_lastAppliedOrientation) return; + g_lastAppliedOrientation = devOrientation; + + CGRect nativeBounds = [[UIScreen mainScreen] bounds]; + CGSize logicalSize = swapped ? CGSizeMake(nativeBounds.size.height, nativeBounds.size.width) + : nativeBounds.size; + + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDuration:0.3]; + rootView.transform = CGAffineTransformMakeRotation(angle); + rootView.bounds = CGRectMake(0, 0, logicalSize.width, logicalSize.height); + rootView.center = CGPointMake(CGRectGetMidX(nativeBounds), CGRectGetMidY(nativeBounds)); + [UIView commitAnimations]; + + bsRequestRelayout(); +} + +- (void)applicationDidFinishLaunching:(UIApplication *)application { + [application setStatusBarHidden:YES]; + + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(orientationChanged:) + name:UIDeviceOrientationDidChangeNotification + object:nil]; + + CGRect bounds = [[UIScreen mainScreen] bounds]; + window = [[UIWindow alloc] initWithFrame:bounds]; + usingRootViewController = [window respondsToSelector:@selector(setRootViewController:)]; + + gameListVC = [[BSGameListViewController alloc] init]; + navController = [[UINavigationController alloc] initWithRootViewController:gameListVC]; + + if (usingRootViewController) { + [window performSelector:@selector(setRootViewController:) withObject:navController]; + } else { + [window addSubview:navController.view]; + } + [window makeKeyAndVisible]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; + [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; + + g_glView = nil; + g_overlayView = nil; + [overlay release]; + [rootView release]; + [view release]; + [gameListVC release]; + [settingsVC release]; + [navController release]; + [window release]; + [super dealloc]; +} + +@end + +int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *docs = [paths objectAtIndex:0]; + NSString *logPath; + if ([docs isEqualToString:@"/var/mobile/Documents"]) { + NSString *butterscotch = [docs stringByAppendingPathComponent:@"Butterscotch"]; + [[NSFileManager defaultManager] createDirectoryAtPath:butterscotch + withIntermediateDirectories:YES + attributes:nil + error:nil]; + logPath = [butterscotch stringByAppendingPathComponent:@"latest_log.txt"]; + } else { + logPath = [docs stringByAppendingPathComponent:@"latest_log.txt"]; + } + + FILE *f = fopen([logPath fileSystemRepresentation], "w"); + if (f) { + dup2(fileno(f), STDOUT_FILENO); + dup2(fileno(f), STDERR_FILENO); + setbuf(stdout, NULL); + setbuf(stderr, NULL); + } + + int ret = UIApplicationMain(argc, argv, nil, @"AppDelegate"); + [pool release]; + + return ret; +} diff --git a/src/desktop/main.c b/src/desktop/main.c index bf2cfc7ea..13f0b0816 100644 --- a/src/desktop/main.c +++ b/src/desktop/main.c @@ -54,13 +54,15 @@ #include "profiler.h" #include "gettime.h" -/* For SDL_main */ +/* For main function overrides */ #if defined(USE_SDL1) #include #elif defined(USE_SDL2) #include #elif defined(USE_SDL3) #include +#elif defined(USE_IOS) +#define main game_main #endif enum GraphicsAPI gfx; @@ -116,10 +118,10 @@ static int platformInitGlad(GLADloadproc load) { } else return 0; // Load OpenGL function pointers via GLAD - // This will need to be modified if we ever want to support GLES 1.x if (version && strstr(version, "OpenGL ES")) { + bool ret = gladLoadGLES1Loader(platformGetProcAddress); if (!gladLoadGLES2Loader(platformGetProcAddress)) - return 0; + return ret ? 3 : 0; return 2; } else { if (!gladLoadGLLoader(platformGetProcAddress)) @@ -1327,8 +1329,8 @@ int main(int argc, char* argv[]) { return 1; } -#if defined(ENABLE_LEGACY_GL) || defined(ENABLE_MODERN_GL) || ((defined(USE_GLFW3) || defined(USE_GLFW2)) && defined(ENABLE_SW_RENDERER) ) -#if defined(USE_GLFW3) || defined(USE_GLFW2) +#if defined(ENABLE_LEGACY_GL) || defined(ENABLE_MODERN_GL) || ((defined(USE_GLFW3) || defined(USE_GLFW2) || defined(USE_IOS)) && defined(ENABLE_SW_RENDERER) ) +#if defined(USE_GLFW3) || defined(USE_GLFW2) || defined(USE_IOS) if (gfx == LEGACY_GL || gfx == MODERN_GL || gfx == SOFTWARE) { #else if (gfx == LEGACY_GL || gfx == MODERN_GL) { diff --git a/src/sw/defines.h b/src/sw/defines.h new file mode 100644 index 000000000..1cfd49d88 --- /dev/null +++ b/src/sw/defines.h @@ -0,0 +1,24 @@ +#pragma once + +#include "common.h" + +// CONFIG: Change the size of a pixel. +// +// 32-bit: 0xAARRGGBB +// 16-bit: 0b0RRRRRGGGGGBBBBB +// 8-bit: 0bBBGGGRRR +#define PIXEL_SIZE 32 +//#define PIXEL_SIZE 16 +//#define PIXEL_SIZE 8 + +#if defined(__GNUC__) && (__GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96)) +#define LIKELY(cond) __builtin_expect(!!(cond), 1) +#define UNLIKELY(cond) __builtin_expect(!!(cond), 0) +#else +#define LIKELY(cond) (cond) +#define UNLIKELY(cond) (cond) +#endif + +#define UNUSED __attribute__ ((unused)) +#define FORCE_INLINE static inline __attribute__((always_inline)) + diff --git a/src/sw/pixel_convert.h b/src/sw/pixel_convert.h new file mode 100644 index 000000000..7eee4c2f9 --- /dev/null +++ b/src/sw/pixel_convert.h @@ -0,0 +1,105 @@ +#pragma once + +#include "defines.h" +#include "binary_utils.h" + +#if PIXEL_SIZE == 32 +typedef uint32_t uintpixel_t; +#elif PIXEL_SIZE == 16 +typedef uint16_t uintpixel_t; +#elif PIXEL_SIZE == 8 +typedef uint8_t uintpixel_t; +#define PXL_TRANSPARENT (0xAA) +#else +#error "Unknown pixel size!" +#endif + +// The native format coming out of GameMaker Studio's assets. +typedef union +{ + struct { + uint8_t r, g, b, a; + } p; + uint32_t l; +} +Pixel32ABGR; + +// This is the format the renderer knows how to work with. +typedef union +{ + struct { +#ifdef IS_BIG_ENDIAN + uint8_t a, r, g, b; +#else + uint8_t b, g, r, a; +#endif + } p; + uint32_t l; +} +Pixel32ARGB; + +FORCE_INLINE UNUSED +uint16_t abgr8888_to_rgb1555(uint32_t xl) +{ + Pixel32ABGR x; + x.l = xl; + return (x.p.b >> 3) | ((x.p.g >> 3) << 5) | ((x.p.r >> 3) << 10) | ((x.p.a >> 7) << 15); +} + +FORCE_INLINE UNUSED +uint8_t abgr8888_to_rgb332(uint32_t xl) +{ + Pixel32ABGR x; + x.l = xl; + +#if PIXEL_SIZE == 8 + //check if transparent + if (x.p.a < 128) + return PXL_TRANSPARENT; +#endif + + uint8_t pxl = (x.p.r >> 5) | ((x.p.g >> 5) << 3) | ((x.p.b >> 6) << 6); + +#if PIXEL_SIZE == 8 + //hacky fixup + if (pxl == PXL_TRANSPARENT) + pxl++; +#endif + + return pxl; +} + +FORCE_INLINE UNUSED +uintpixel_t swrConvertPixelBase(uint32_t gmPixel) +{ +#if PIXEL_SIZE == 32 + return (gmPixel & 0xFF00FF00) | ((gmPixel & 0xFF) << 16) | ((gmPixel >> 16) & 0xFF); +#elif PIXEL_SIZE == 16 + return abgr8888_to_rgb1555(gmPixel); +#elif PIXEL_SIZE == 8 + return abgr8888_to_rgb332(gmPixel); +#endif +} + +#if PIXEL_SIZE == 32 +#define TRANSPARENT_MASK 0xFF000000 +#elif PIXEL_SIZE == 16 +#define TRANSPARENT_MASK 0x8000 +#endif + +#if PIXEL_SIZE == 8 + +#define swrConvertPixel(x) swrConvertPixelBase((x) | 0xFF000000) +#define swrConvertPixelTexture(x) swrConvertPixelBase(x) + +#elif defined IS_BIG_ENDIAN + +#define swrConvertPixel(x) swrConvertPixelBase(x) +#define swrConvertPixelTexture(x) swrConvertPixelBase(BinaryUtils_bswap32(x)) + +#else + +#define swrConvertPixel swrConvertPixelBase +#define swrConvertPixelTexture swrConvertPixelBase + +#endif diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c new file mode 100644 index 000000000..4490355a3 --- /dev/null +++ b/src/sw/sw_renderer.c @@ -0,0 +1,2346 @@ +#include +#include +#include +#include +#include "defines.h" +#include "sw_renderer.h" +#include "text_utils.h" +#include "pixel_convert.h" +#include "image/image_decoder.h" + +#define UNIMP() do { fprintf(stderr, "NYI %s\n", __func__); } while (0) +//#define UNIMP() do { } while (0) +#define UNIMP2() do { } while (0) + +#ifndef M_PI +#define M_PI 3.1415926535897932384626 +#endif + +#define TEXTURE_LRU_LENGTH 64 + +#define SURFACE_MAX_COUNT 64 + +typedef struct +{ + uintpixel_t* buffer; + uint16_t width, height; +} +SWTexture; + +typedef struct +{ + Renderer base; + + // Window Properties + uint16_t width; + uint16_t height; + // Framebuffer + uintpixel_t* fb; + uint16_t fbPitch; // in sizeof(uintpixel_t) units, NOT in bytes! + + uintpixel_t* mainFb; + uint16_t mainWidth; + uint16_t mainHeight; + uint16_t mainPitch; + bool drawingToSurface; + + SWTexture** textures; + uint32_t* textureIndexLRU; + uint32_t textureIndexLRUHead; + uint32_t textureIndexLRUTail; + size_t textureCount; + size_t surfaceCount; + size_t totalTextureCount; + size_t originalTPagCount; + size_t originalSpriteCount; + + bool viewActive; + int viewX, viewY, viewW, viewH; + int portX, portY, portW, portH; + int gameW, gameH, maxX, maxY; +} +SWRenderer; + +void Runner_setNextFrame(uintpixel_t* framebuffer, int width, int height); + +FORCE_INLINE int swrMin(int a, int b) { return a < b ? a : b; } +FORCE_INLINE int swrMax(int a, int b) { return a > b ? a : b; } +FORCE_INLINE int swrAbs(int x) { return x < 0 ? -x : x; } + +FORCE_INLINE bool opaque(uintpixel_t color) +{ +#if PIXEL_SIZE == 8 + return (color != PXL_TRANSPARENT); +#else + return (color & TRANSPARENT_MASK) != 0; +#endif +} + +FORCE_INLINE uintpixel_t tint(uintpixel_t tintColor, uintpixel_t color) +{ +#if PIXEL_SIZE == 32 + Pixel32ARGB x, y; + + if ((tintColor & 0xFFFFFF) == 0xFFFFFF) + return color; + + x.l = color; + y.l = tintColor; + + x.p.b = (int)x.p.b * y.p.b / 255; + x.p.g = (int)x.p.g * y.p.g / 255; + x.p.r = (int)x.p.r * y.p.r / 255; + return x.l; +#elif PIXEL_SIZE == 16 + if ((tintColor & 0x7FFF) == 0x7FFF) + return color; + + int tcb = tintColor & 0x1F; + int tcg = (tintColor >> 5) & 0x1F; + int tcr = (tintColor >> 10) & 0x1F; + + int cb = color & 0x1F; + int cg = (color >> 5) & 0x1F; + int cr = (color >> 10) & 0x1F; + int ca = color & 0x8000; + + cb = (cb * tcb) / 32; + cg = (cg * tcg) / 32; + cr = (cr * tcr) / 32; + return ca | cb | (cg << 5) | (cr << 10); +#elif PIXEL_SIZE == 8 + // fast but hacky + if (tintColor == 0xFF || tintColor == PXL_TRANSPARENT) + return color; + + return color & tintColor; +#endif +} + +// NOTE: alpha is between 0 and 256, NOT between 0 and 255! +FORCE_INLINE void alphaBlend(uintpixel_t* dcolor, uintpixel_t scolor, int alpha) +{ +#if PIXEL_SIZE == 32 || PIXEL_SIZE == 16 + // it's so insignificant here nobody will notice if we just don't... + if (alpha < 3) + return; + + // it's so significant here we might as well fill in the whole color + if (alpha > 253) + { + *dcolor = scolor; + return; + } + + int inval = 256 - alpha; +#endif + +#if PIXEL_SIZE == 32 + Pixel32ARGB dc, sc; + dc.l = *dcolor; + sc.l = scolor; + + dc.p.r = (dc.p.r * inval + sc.p.r * alpha) >> 8; + dc.p.g = (dc.p.g * inval + sc.p.g * alpha) >> 8; + dc.p.b = (dc.p.b * inval + sc.p.b * alpha) >> 8; + + *dcolor = dc.l; +#elif PIXEL_SIZE == 16 + int scb = scolor & 0x1F; + int scg = (scolor >> 5) & 0x1F; + int scr = (scolor >> 10) & 0x1F; + + uintpixel_t _dcolor = *dcolor; + int dcb = _dcolor & 0x1F; + int dcg = (_dcolor >> 5) & 0x1F; + int dcr = (_dcolor >> 10) & 0x1F; + int dca = _dcolor & 0x8000; + + dcr = (dcr * inval + scr * alpha) >> 8; + dcg = (dcg * inval + scg * alpha) >> 8; + dcb = (dcb * inval + scb * alpha) >> 8; + + *dcolor = dca | dcb | (dcg << 5) | (dcr << 10); +#else + if (alpha < 240) { + static int alphaApproximationThingy = 0; + alphaApproximationThingy += 1339; + if (alphaApproximationThingy > 601000) + alphaApproximationThingy = 0; + + //gotta love that RNG + if ((alphaApproximationThingy & 0xFF) >= alpha) + return; + } + + *dcolor = scolor; +#endif +} + +FORCE_INLINE int swrIntAlpha(float alphaf) +{ + return (int)(alphaf * 256); +} + +FORCE_INLINE bool swrMustRotate(float angleDeg) +{ + int angleDegInt = (int)(angleDeg * 4); + angleDegInt %= 360*4; + + if (angleDegInt > 180*4) + angleDegInt -= 360*4; + + return swrAbs(angleDegInt) >= 1; // 0.25 degrees +} + +FORCE_INLINE bool swrMustRotateTolerant(float angleDeg) +{ + int angleDegInt = (int)(angleDeg * 16); + angleDegInt %= 360*16; + + if (angleDegInt > 180*16) + angleDegInt -= 360*16; + + return swrAbs(angleDegInt) >= 1; // 1/16 of a degree +} + +FORCE_INLINE int swrSgn(float x) +{ + if (x < 0) return -1; + return 1; +} + +FORCE_INLINE int swrFloor(float x) +{ + int i = (int) x; + return i - (x < (float) i); +} + +FORCE_INLINE int swrCeiling(float x) +{ + int i = (int) x; + return i + (x > (float) i); +} + +static SWTexture* swrCreateTexture(const uint8_t* srcBuffer, int width, int height) +{ + SWTexture* txt = (SWTexture*) safeMalloc(sizeof(SWTexture)); + txt->buffer = (uintpixel_t*) safeMalloc(width * height * sizeof(uintpixel_t)); + + const uint32_t* rgbaSrc = (const uint32_t*) srcBuffer; + + size_t sz = width * height; + + if (srcBuffer) + { + for (size_t i = 0; i < sz; i++) + txt->buffer[i] = swrConvertPixelTexture(rgbaSrc[i]); + } + + txt->width = (uint16_t) width; + txt->height = (uint16_t) height; + + return txt; +} + +static void swrFreeTexture(SWTexture* texture) +{ + free(texture->buffer); + free(texture); +} + +static bool swrAddTextureIndexToLRU(SWRenderer* swr, int textureIndex) +{ + uint32_t newIndex = (swr->textureIndexLRUHead + 1) % TEXTURE_LRU_LENGTH; + if (newIndex == swr->textureIndexLRUTail) { + // about to collide with tail from the other side -- nope. + return false; + } + + swr->textureIndexLRU[swr->textureIndexLRUHead] = textureIndex; + swr->textureIndexLRUHead = newIndex; + return true; +} + +static int swrTailTextureIndexLRU(SWRenderer* swr, bool remove) +{ + if (swr->textureIndexLRUHead == swr->textureIndexLRUTail) + return -1; + + uint32_t textureIndex = swr->textureIndexLRU[swr->textureIndexLRUTail]; + + if (remove) + swr->textureIndexLRUTail = (swr->textureIndexLRUTail + 1) % TEXTURE_LRU_LENGTH; + + return textureIndex; +} + +static void swrEvictTextureFromCache(SWRenderer* swr, int textureIndex) +{ + SWTexture* texture = swr->textures[textureIndex]; + swr->textures[textureIndex] = NULL; + + swrFreeTexture(texture); +} + +// Lazily decodes and uploads a TXTR page on first access. +// Returns true if the texture is ready, false if it failed to decode. +static bool swrEnsureTextureIsLoaded(SWRenderer* swr, uint32_t pageId) +{ + if (swr->textures[pageId]) + return true; + + DataWin* dw = swr->base.dataWin; + Texture* txtr = &dw->txtr.textures[pageId]; + + int w, h; + bool gm2022_5 = DataWin_isVersionAtLeast(dw, 2022, 5, 0, 0); + + uint8_t* pixels = NULL; + + do + { + pixels = ImageDecoder_decodeToRgba(txtr->blobData, (size_t) txtr->blobSize, gm2022_5, &w, &h); + if (pixels) + break; + + fprintf(stderr, "swr: Failed to decode TXTR page %u. This is likely because we're out of memory, so evicting a texture.\n", pageId); + + int tail = swrTailTextureIndexLRU(swr, true); + if (tail == -1) { + fprintf(stderr, "swr: Looks like we can't fit this texture in memory at all. Bummer.\n"); + break; + } + + swrEvictTextureFromCache(swr, tail); + fprintf(stderr, "swr: Evicted texture %d, trying again.\n", tail); + } + while (!pixels); + + if (pixels == nullptr) { + fprintf(stderr, "swr: Failed to decode TXTR page %u.\n", pageId); + return false; + } + + swr->textures[pageId] = swrCreateTexture(pixels, w, h); + free(pixels); + + fprintf(stderr, "SWR: Loaded TXTR page %u (%dx%d)\n", pageId, w, h); + + // add it to the LRU + do + { + bool added = swrAddTextureIndexToLRU(swr, pageId); + if (added) + break; + + int tail = swrTailTextureIndexLRU(swr, true); + if (tail == -1) { + fprintf(stderr, "swr: Come on now.\n"); + assert(tail != -1); + } + + swrEvictTextureFromCache(swr, tail); + } + while (true); + + return true; +} + +static void SWRenderer_init(Renderer* renderer, DataWin* dataWin) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + renderer->dataWin = dataWin; + + //allocate texture buffer + swr->textureCount = dataWin->txtr.count; + swr->surfaceCount = SURFACE_MAX_COUNT; + swr->totalTextureCount = swr->textureCount + swr->surfaceCount; + swr->textures = (SWTexture**) safeCalloc(swr->totalTextureCount, sizeof(SWTexture*)); + + //allocate texture LRU cache to allow for dynamic unloading of textures + swr->textureIndexLRU = (uint32_t*) safeCalloc(TEXTURE_LRU_LENGTH, sizeof(uint32_t)); + swr->textureIndexLRUHead = 0; + swr->textureIndexLRUTail = 0; + + //HACK: this isn't good, really. This should seriously be refactored. + //expand datawin's tpag items list to include our surface count. + swr->originalTPagCount = dataWin->tpag.count; + dataWin->tpag.items = (TexturePageItem*) safeRealloc(dataWin->tpag.items, sizeof(TexturePageItem) * (dataWin->tpag.count + swr->surfaceCount)); + dataWin->tpag.count += swr->surfaceCount; + + swr->originalSpriteCount = dataWin->sprt.count; + + for (size_t i = swr->originalTPagCount; i < dataWin->tpag.count; i++) + { + memset(&dataWin->tpag.items[i], 0, sizeof(TexturePageItem)); + dataWin->tpag.items[i].texturePageId = -1; + } + + fprintf(stderr, "SWRenderer initialized.\n"); +} + +static void SWRenderer_destroy(Renderer* renderer) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + (void) swr; + + fprintf(stderr, "SWRenderer destroyed.\n"); +} + +static void SWRenderer_beginFrame(Renderer* renderer, int32_t gameW, int32_t gameH, int32_t windowW, int32_t windowH) +{ + SWRenderer* swr = (SWRenderer*) renderer; + swr->gameW = gameW; + swr->gameH = gameH; + swr->drawingToSurface = false; + if (swr->width != windowW || swr->height != windowH) + { + //allocate frame buffer + free(swr->fb); + swr->fb = (uintpixel_t*) safeMalloc(windowW * windowH * sizeof(uintpixel_t)); + swr->fbPitch = windowW; + swr->width = windowW; + swr->height = windowH; + } +} + +// This used to be just one, "endFrame". Not sure what the different is. +static void SWRenderer_endFrameInit(Renderer* renderer) +{ + (void) renderer; + + //this is kinda useless to do twice isn't it? +} + +static void SWRenderer_endFrameEnd(Renderer* renderer) +{ + SWRenderer* swr = (SWRenderer*) renderer; + assert(!swr->drawingToSurface); + Runner_setNextFrame(swr->fb, swr->width, swr->height); +} + +static void SWRenderer_beginView(Renderer* renderer, int32_t viewX, int32_t viewY, int32_t viewW, int32_t viewH, + int32_t portX, int32_t portY, int32_t portW, int32_t portH, float viewAngle) +{ + (void)renderer; (void)viewX; (void)viewY; (void)viewW; (void)viewH; + (void)portX; (void)portY; (void)portW; (void)portH; (void)viewAngle; + UNIMP2(); + + SWRenderer* swr = (SWRenderer*) renderer; + + float xratio, yratio; + + if (swr->drawingToSurface) { + UNIMP(); + xratio = 1.0f; + yratio = 1.0f; + portX = (int)(portX * xratio); + portY = (int)(portY * yratio); + } + else { + float scaleX = (float) swr->width / swr->gameW; + float scaleY = (float) swr->height / swr->gameH; + float scale = (scaleX < scaleY) ? scaleX : scaleY; + + int32_t scaledW = (int32_t)(swr->gameW * scale); + int32_t scaledH = (int32_t)(swr->gameH * scale); + int32_t offsetX = (swr->width - scaledW) / 2; + int32_t offsetY = (swr->height - scaledH) / 2; + + xratio = scale; + yratio = scale; + + portX = (int)(portX * xratio) + offsetX; + portY = (int)(portY * yratio) + offsetY; + } + portW = (int)(portW * xratio); + portH = (int)(portH * yratio); + + swr->viewActive = true; + swr->viewX = viewX; + swr->viewY = viewY; + swr->viewW = viewW; + swr->viewH = viewH; + swr->portX = portX; + swr->portY = portY; + swr->portW = portW; + swr->portH = portH; + swr->maxX = portX + portW; + swr->maxY = portY + portH; +} + +static void SWRenderer_endView(Renderer* renderer) +{ + (void)renderer; + UNIMP2(); + + SWRenderer* swr = (SWRenderer*) renderer; + swr->viewActive = false; + + swr->portX = swr->viewX = 0; + swr->portY = swr->viewY = 0; + swr->portW = swr->viewW = swr->width; + swr->portH = swr->viewH = swr->height; +} + +static void SWRenderer_beginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, + int32_t portX, int32_t portY, int32_t portW, int32_t portH, int32_t targetSurfaceId) +{ + (void)renderer; (void)guiW; (void)guiH; + (void)portX; (void)portY; (void)portW; (void)portH; + (void)targetSurfaceId; + UNIMP2(); +} + +static void SWRenderer_setGuiProjection(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t portW, int32_t portH, bool renderingToUserSurface) +{ + (void) renderer; + (void) guiW; (void) guiH; + (void) portW; (void) portH; + (void) renderingToUserSurface; + UNIMP(); +} + +static void SWRenderer_endGUI(Renderer* renderer) +{ + (void)renderer; + UNIMP2(); +} + +// TODO[MrPowerGamerBR]: This is supposed to be refactored, not to modify data.win structs directly. +static int32_t swrFindSurfaceTextureSlot(SWRenderer* swr) +{ + // NOTE: dynamic textures are not enrolled into the eviction cache for + // hopefully obvious reasons ... + for (size_t i = swr->textureCount; i != swr->totalTextureCount; i++) + { + if (swr->textures[i] == NULL) { + return (int32_t) i; + } + } + + return -1; +} + +static int32_t swrFindSurfaceTPagSlot(SWRenderer* swr) +{ + DataWin* dw = swr->base.dataWin; + for (size_t i = swr->originalTPagCount; i != dw->tpag.count; i++) + { + if (dw->tpag.items[i].texturePageId == -1) { + return (int32_t) i; + } + } + + return -1; +} + +static void swrTransformPosIfNeeded(SWRenderer* swr, float* dx, float* dy) +{ + if (!swr->viewActive) return; + + if (dx) { + float xscale = ((float)swr->portW / swr->viewW); + *dx -= swr->viewX; + *dx *= xscale; + *dx += swr->portX; + } + if (dy) { + float yscale = ((float)swr->portH / swr->viewH); + *dy -= swr->viewY; + *dy *= yscale; + *dy += swr->portY; + } +} + +static void swrTransformSizeIfNeeded(SWRenderer* swr, float* dx, float* dy) +{ + if (!swr->viewActive || !swr->viewW || !swr->viewH) return; + + if (dx) *dx *= ((float)swr->portW / swr->viewW); + if (dy) *dy *= ((float)swr->portH / swr->viewH); +} + +static void swrTransformPosIntIfNeeded(SWRenderer* swr, int32_t* dx, int32_t* dy) +{ + if (!swr->viewActive) return; + + if (dx) { + *dx -= swr->viewX; + *dx = *dx * swr->portW / swr->viewW; + *dx += swr->portX; + } + if (dy) { + *dy -= swr->viewY; + *dy = *dy * swr->portH / swr->viewH; + *dy += swr->portY; + } +} + +static void swrTransformSizeIntIfNeeded(SWRenderer* swr, int32_t* dx, int32_t* dy) +{ + if (!swr->viewActive || !swr->viewW || !swr->viewH) return; + + if (dx) *dx = *dx * swr->portW / swr->viewW; + if (dy) *dy = *dy * swr->portH / swr->viewH; +} + +static void swrReverseTransformSizeIntIfNeeded(SWRenderer* swr, int32_t* dx, int32_t* dy) +{ + if (!swr->viewActive) return; + + if (dx) *dx = *dx * swr->viewW / swr->portW; + if (dy) *dy = *dy * swr->viewH / swr->portH; +} + +FORCE_INLINE void swrPlotPixel(Renderer* renderer, int x, int y, uintpixel_t color, int alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + if (x < swr->portX || y < swr->portY) return; + if (x >= swr->maxX || y >= swr->maxY) return; + + alphaBlend(&swr->fb[y * swr->fbPitch + x], color, alpha); +} + +static void swrDrawHLineInt(Renderer* renderer, int dx, int dy, int dw, uintpixel_t color, UNUSED uintpixel_t color2, int alpha) +{ + SWRenderer *swr = (SWRenderer*) renderer; + + if (dy < swr->portY) return; + if (dy >= swr->maxY) return; + if (dx < swr->portX) { dw += dx - swr->portX; dx = swr->portX; } + if (dx + dw >= swr->maxX) dw = swr->maxX - dx; + if (dw <= 0) return; + +#if PIXEL_SIZE == 32 + if (color == color2) +#endif + { + uintpixel_t *line = &swr->fb[dy * swr->fbPitch + dx]; + for (int i = 0; i < dw; i++) + alphaBlend(&line[i], color, alpha); + } +#if PIXEL_SIZE == 32 + else + { + Pixel32ARGB clr1, clr2; + clr1.l = color; + clr2.l = color2; + + uint32_t rinit = clr1.p.r << 20; + uint32_t ginit = clr1.p.g << 20; + uint32_t binit = clr1.p.b << 20; + int32_t rstep = ((int)clr2.p.r - clr1.p.r) << 20; + int32_t gstep = ((int)clr2.p.g - clr1.p.g) << 20; + int32_t bstep = ((int)clr2.p.b - clr1.p.b) << 20; + rstep /= dw; + gstep /= dw; + bstep /= dw; + + uintpixel_t *line = &swr->fb[dy * swr->fbPitch + dx]; + for (int i = 0; i < dw; i++) + { + Pixel32ARGB resultPixel; + resultPixel.p.r = rinit >> 20; + resultPixel.p.g = ginit >> 20; + resultPixel.p.b = binit >> 20; + rinit += rstep; + ginit += gstep; + binit += bstep; + alphaBlend(&line[i], resultPixel.l, alpha); + } + } +#endif +} + +static void swrDrawHLine(Renderer* renderer, float dx, float dy, float dw, uintpixel_t color, uintpixel_t color2, float alpha) +{ + SWRenderer *swr = (SWRenderer*) renderer; + float thickness = 1; + + swrTransformPosIfNeeded(swr, &dx, &dy); + swrTransformSizeIfNeeded(swr, &dw, &thickness); + + // TODO: use thickness + swrDrawHLineInt(renderer, swrFloor(dx), swrFloor(dy), swrCeiling(dw), color, color2, swrIntAlpha(alpha)); +} + +static void swrDrawVLineInt(Renderer* renderer, int dx, int dy, int dh, uintpixel_t color, UNUSED uintpixel_t color2, int alpha) +{ + SWRenderer *swr = (SWRenderer*) renderer; + + if (dx < swr->portX) return; + if (dx >= swr->maxX) return; + if (dy < swr->portY) { dh += dy - swr->portY; dy = swr->portY; } + if (dy + dh >= swr->maxY) dh = swr->maxY - dy; + if (dh <= 0) return; + +#if PIXEL_SIZE == 32 + if (color == color2) +#endif + { + for (int i = 0; i < dh; i++) + { + uintpixel_t *line = &swr->fb[(dy + i) * swr->fbPitch + dx]; + alphaBlend(&line[0], color, alpha); + } + } +#if PIXEL_SIZE == 32 + else + { + Pixel32ARGB clr1, clr2; + clr1.l = color; + clr2.l = color2; + + uint32_t rinit = clr1.p.r << 20; + uint32_t ginit = clr1.p.g << 20; + uint32_t binit = clr1.p.b << 20; + int32_t rstep = ((int)clr2.p.r - clr1.p.r) << 20; + int32_t gstep = ((int)clr2.p.g - clr1.p.g) << 20; + int32_t bstep = ((int)clr2.p.b - clr1.p.b) << 20; + rstep /= dh; + gstep /= dh; + bstep /= dh; + + for (int i = 0; i < dh; i++) + { + uintpixel_t *line = &swr->fb[(dy + i) * swr->fbPitch + dx]; + Pixel32ARGB resultPixel; + resultPixel.p.r = rinit >> 20; + resultPixel.p.g = ginit >> 20; + resultPixel.p.b = binit >> 20; + rinit += rstep; + ginit += gstep; + binit += bstep; + alphaBlend(&line[0], resultPixel.l, alpha); + } + } +#endif +} + +static void swrDrawVLine(Renderer* renderer, float dx, float dy, float dh, uintpixel_t color, uintpixel_t color2, float alpha) +{ + SWRenderer *swr = (SWRenderer*) renderer; + float thickness = 1; + + swrTransformPosIfNeeded(swr, &dx, &dy); + swrTransformSizeIfNeeded(swr, &thickness, &dh); + + // TODO: use thickness + swrDrawVLineInt(renderer, swrFloor(dx), swrFloor(dy), swrCeiling(dh), color, color2, swrIntAlpha(alpha)); +} + +static void swrDrawRectangle(Renderer* renderer, float x1, float y1, float x2, float y2, uintpixel_t color, float alpha) +{ + swrDrawHLine(renderer, x1, y1, (x2 - x1) + 1, color, color, alpha); + swrDrawHLine(renderer, x1, y2, (x2 - x1) + 1, color, color, alpha); + swrDrawVLine(renderer, x1, y1, (y2 - y1) + 1, color, color, alpha); + swrDrawVLine(renderer, x2, y1, (y2 - y1) + 1, color, color, alpha); +} + +static void swrDrawRectangleColor(Renderer* renderer, float x1, float y1, float x2, float y2, uintpixel_t color1, uintpixel_t color2, uintpixel_t color3, uintpixel_t color4, float alpha) +{ + swrDrawHLine(renderer, x1, y1, (x2 - x1) + 1, color1, color2, alpha); + swrDrawHLine(renderer, x1, y2, (x2 - x1) + 1, color3, color4, alpha); + swrDrawVLine(renderer, x1, y1, (y2 - y1) + 1, color1, color3, alpha); + swrDrawVLine(renderer, x2, y1, (y2 - y1) + 1, color2, color4, alpha); +} + +static void swrDrawLineInt(Renderer* renderer, int x1, int y1, int x2, int y2, MAYBE_UNUSED int width, uintpixel_t color1, uintpixel_t color2, int alpha) +{ + if (x1 == x2) + { + swrDrawVLineInt(renderer, x1, swrMin(y1, y2), swrAbs(y1 - y2), color1, color2, alpha); + return; + } + if (y1 == y2) + { + swrDrawHLineInt(renderer, swrMin(x1, x2), y1, swrAbs(x1 - x2), color1, color2, alpha); + return; + } + + int dx = x2 - x1, dy = y2 - y1; + int dx1 = swrAbs(dx), dy1 = swrAbs(dy), xe, ye, x, y; + int px = 2 * dy1 - dx1, py = 2 * dx1 - dy1; + + uintpixel_t color = color1; +#if PIXEL_SIZE == 32 + Pixel32ARGB clr1, clr2; + clr1.l = color1; + clr2.l = color2; + + uint32_t rinit = clr1.p.r << 20; + uint32_t ginit = clr1.p.g << 20; + uint32_t binit = clr1.p.b << 20; + int32_t rstep = ((int)clr2.p.r - clr1.p.r) << 20; + int32_t gstep = ((int)clr2.p.g - clr1.p.g) << 20; + int32_t bstep = ((int)clr2.p.b - clr1.p.b) << 20; +#endif + + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1, y = y1, xe = x2; + } + else + { + x = x2, y = y2, xe = x1; + } + +#if PIXEL_SIZE == 32 + if (dx1 > 0) { + rstep /= dx1; + gstep /= dx1; + bstep /= dx1; + } else { + rstep = gstep = bstep = 0; + } +#endif + + swrPlotPixel(renderer, x, y, color, alpha); + + for (int i = 0; x < xe; i++) + { + x++; + if (px < 0) + { + px += 2 * dy1; + } + else + { + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) y++; else y--; + px += 2 * (dy1 - dx1); + } + +#if PIXEL_SIZE == 32 + Pixel32ARGB resultPixel; + resultPixel.p.r = rinit >> 20; + resultPixel.p.g = ginit >> 20; + resultPixel.p.b = binit >> 20; + rinit += rstep; + ginit += gstep; + binit += bstep; + color = resultPixel.l; +#endif + + swrPlotPixel(renderer, x, y, color, alpha); + } + } + else + { + if (dy >= 0) + { + x = x1, y = y1, ye = y2; + } + else + { + x = x2, y = y2, ye = y1; + } + +#if PIXEL_SIZE == 32 + if (dy1 > 0) { + rstep /= dy1; + gstep /= dy1; + bstep /= dy1; + } else { + rstep = gstep = bstep = 0; + } +#endif + + swrPlotPixel(renderer, x, y, color, alpha); + + for (int i = 0; y < ye; i++) + { + y++; + if (py <= 0) + { + py += 2 * dx1; + } + else + { + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) x++; else x--; + py += 2 * (dx1 - dy1); + } + +#if PIXEL_SIZE == 32 + Pixel32ARGB resultPixel; + resultPixel.p.r = rinit >> 20; + resultPixel.p.g = ginit >> 20; + resultPixel.p.b = binit >> 20; + rinit += rstep; + ginit += gstep; + binit += bstep; + color = resultPixel.l; +#endif + + swrPlotPixel(renderer, x, y, color, alpha); + } + } +} + +static void swrDrawLine(Renderer* renderer, float x1, float y1, float x2, float y2, float width, uintpixel_t color, uintpixel_t color2, float alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + swrTransformPosIfNeeded(swr, &x1, &y1); + swrTransformPosIfNeeded(swr, &x2, &y2); + swrTransformSizeIfNeeded(swr, &width, NULL); + swrDrawLineInt(renderer, swrFloor(x1), swrFloor(y1), swrCeiling(x2), swrCeiling(y2), swrCeiling(width), color, color2, swrIntAlpha(alpha)); +} + +static void swrDrawSpriteInternal( + Renderer* renderer, int dx, int dy, int dw, int dh, + SWTexture* texture, int sx, int sy, int sw, int sh, + uintpixel_t tintColor, int alpha +) +{ + SWRenderer *swr = (SWRenderer*) renderer; + + bool flipX = false, flipY = false; + if (dw < 0) { dx += dw; dw = -dw; flipX = true; } + if (dh < 0) { dy += dh; dh = -dh; flipY = true; } + + //basic out of bound checks + if (dw == 0 || dh == 0) return; + if (sw == 0) sw = 1; + if (sh == 0) sh = 1; + if (dx + dw <= swr->portX) return; + if (dy + dh <= swr->portY) return; + if (dx >= swr->maxX) return; + if (dy >= swr->maxY) return; + + int odw = dw, odh = dh; + int osw = sw, osh = sh; + + int minx = swr->portX, miny = swr->portY, maxx = swr->portX + swr->portW, maxy = swr->portY + swr->portH; + + //out of bounds adjustment checks + int diffxl = 0, diffyl = 0, diffxu = 0, diffyu = 0; + if (dx < minx) { diffxl = minx - dx; dx = minx; dw -= diffxl; } + if (dy < miny) { diffyl = miny - dy; dy = miny; dh -= diffyl; } + if (dx + dw > maxx) { diffxu = dx + dw - maxx; dw -= diffxu; } + if (dy + dh > maxy) { diffyu = dy + dh - maxy; dh -= diffyu; } + + if (diffxl != 0 || diffyl != 0 || diffxu != 0 || diffyu != 0) + { + //adjust source coordinates too + diffxl = (int)((long)diffxl * osw / odw); + diffyl = (int)((long)diffyl * osh / odh); + diffxu = (int)((long)(diffxu + 1) * osw / odw); + diffyu = (int)((long)(diffyu + 1) * osh / odh); + sx += flipX ? diffxu : diffxl; + sy += flipY ? diffyu : diffyl; + sw -= diffxl + diffxu; + sh -= diffyl + diffyu; + if (sw <= 0 || sh <= 0) return; + } + + //clip the source coords into bounds too + if (sx < 0) { sw += sx; sx = 0; } + if (sy < 0) { sh += sy; sy = 0; } + if (sx + sw >= texture->width) { sw = texture->width - sx; } + if (sy + sh >= texture->height) { sh = texture->height - sy; } + + //okay, now we can finally get on with rendering + + int ixs = 0, oxs = 1, iys = 0, oys = 1; + if (flipX) ixs = dw - 1, oxs = -1; + if (flipY) iys = dh - 1, oys = -1; + + // tweak these if stuff doesn't look right + typedef int32_t fixedp_t; + const int fp_prec = 14; + + fixedp_t ystep = (sh == dh) ? (1 << fp_prec) : ((fixedp_t) osh << fp_prec) / odh; + fixedp_t xstep = (sw == dw) ? (1 << fp_prec) : ((fixedp_t) osw << fp_prec) / odw; + fixedp_t oxs2 = oxs * xstep; + fixedp_t oys2 = oys * ystep; + fixedp_t ixs2 = ixs * xstep; + fixedp_t iys2 = iys * ystep; + + if (sw == dw) + { + fixedp_t ys2 = (fixedp_t) iys2; + for (int y = 0, ys = iys; y < dh; y++, ys += oys, ys2 += oys2) + { + uintpixel_t* dstline; + const uintpixel_t* srcline; + dstline = &swr->fb[(dy + y) * swr->fbPitch + dx]; + if (dh == sh) + srcline = &texture->buffer[(sy + ys) * texture->width + sx]; + else + srcline = &texture->buffer[(sy + (int)(ys2 >> fp_prec)) * texture->width + sx]; + + for (int x = 0, xs = ixs; x < dw; x++, xs += oxs) + { + uintpixel_t pixel = srcline[xs]; + if (opaque(pixel)) + alphaBlend(&dstline[x], tint(tintColor, pixel), alpha); + } + } + } + else + { + fixedp_t ys2 = iys2; + for (int y = 0, ys = iys; y < dh; y++, ys += oys, ys2 += oys2) + { + uintpixel_t* dstline; + const uintpixel_t* srcline; + dstline = &swr->fb[(dy + y) * swr->fbPitch + dx]; + if (dh == sh) + srcline = &texture->buffer[(sy + ys) * texture->width + sx]; + else + srcline = &texture->buffer[(sy + (int)(ys2 >> fp_prec)) * texture->width + sx]; + + fixedp_t xs2 = ixs2; + for (int x = 0, xs = ixs; x < dw; x++, xs += oxs, xs2 += oxs2) + { + uintpixel_t pixel = srcline[(int)(xs2 >> fp_prec)]; + if (opaque(pixel)) + alphaBlend(&dstline[x], tint(tintColor, pixel), alpha); + } + } + } +} + +static void swrDrawSprite( + Renderer* renderer, float dx, float dy, float dw, float dh, + SWTexture* texture, int sx, int sy, int sw, int sh, + uint32_t tintColor, float alpha +) +{ + SWRenderer *swr = (SWRenderer*) renderer; + + swrTransformPosIfNeeded(swr, &dx, &dy); + swrTransformSizeIfNeeded(swr, &dw, &dh); + + swrDrawSpriteInternal( + renderer, + swrFloor(dx), + swrFloor(dy), + swrCeiling(dw), + swrCeiling(dh), + texture, + sx, sy, + sw, sh, + swrConvertPixel(tintColor), + swrIntAlpha(alpha) + ); +} + +static void swrDrawSpriteRotatedInternal( + Renderer* renderer, int dx, int dy, int dw, int dh, + SWTexture* texture, int sx, int sy, int sw, int sh, + uintpixel_t tintColor, int alpha, + float angleDeg, + float pivotX, + float pivotY +) +{ + SWRenderer* swr = (SWRenderer*) renderer; + float angleRad = -angleDeg * M_PI / 180.0f; + + bool flipX = false, flipY = false; + if (dw < 0) { dw = -dw; dx -= dw; pivotX = dw - pivotX; flipX = true; } + if (dh < 0) { dh = -dh; dy -= dh; pivotY = dh - pivotY; flipY = true; } + + float cosA = cosf(angleRad); + float sinA = sinf(angleRad); + + float cnrx[4], cnry[4]; + cnrx[0] = cnrx[3] = dx; + cnry[0] = cnry[1] = dy; + cnrx[1] = cnrx[2] = dx + dw; + cnry[2] = cnry[3] = dy + dh; + + float pxa = pivotX + dx; + float pya = pivotY + dy; + + float minXf = FLT_MAX, minYf = FLT_MAX, maxXf = -FLT_MAX, maxYf = -FLT_MAX; + for (int i = 0; i < 4; i++) + { + float cxi = cnrx[i] - pxa; + float cyi = cnry[i] - pya; + float rx = cosA * cxi - sinA * cyi + pxa; + float ry = sinA * cxi + cosA * cyi + pya; + if (minXf > rx) minXf = rx; + if (maxXf < rx) maxXf = rx; + if (minYf > ry) minYf = ry; + if (maxYf < ry) maxYf = ry; + } + + // minX, minY, maxX, maxY now represent an AABB of pixels we should loop over + int minX = swrFloor(minXf); + int minY = swrFloor(minYf); + int maxX = swrCeiling(maxXf); + int maxY = swrCeiling(maxYf); + + // basic out-of-bound checks + if (maxX < swr->portX) return; + if (maxY < swr->portY) return; + if (minX >= swr->maxX) return; + if (minY >= swr->maxY) return; + + // however, we'll need to clip it against out of bounds first + int minXc = minX, minYc = minY, maxXc = maxX, maxYc = maxY; + int minx = swr->portX, miny = swr->portY, maxx = swr->portX + swr->portW, maxy = swr->portY + swr->portH; + + if (minXc < minx) minXc = minx; + if (minYc < miny) minYc = miny; + if (maxXc >= maxx) maxXc = maxx; + if (maxYc >= maxy) maxYc = maxy; + + // some final clip checks + if (minXc >= maxXc || minYc >= maxYc) return; + + int sox = flipX ? sw - 1 : 0; + int soy = flipY ? sh - 1 : 0; + int six = flipX ? -1 : 1; + int siy = flipY ? -1 : 1; + + float sw_dw = (float) sw / dw; + float sh_dh = (float) sh / dh; + + for (int cy = minYc; cy < maxYc; cy++) + { + uintpixel_t *dstline = &swr->fb[cy * swr->fbPitch]; + for (int cx = minXc; cx < maxXc; cx++) + { + // we need to determine the texture-space coordinate of cx/cy + float ox = (float) cx + 0.5f - pxa; + float oy = (float) cy + 0.5f - pya; + + // "undo" the rotation + float lx = cosA * ox + sinA * oy; + float ly = -sinA * ox + cosA * oy; + + // turn it into a texture-local coordinate + lx += pxa - dx; + ly += pya - dy; + + if (lx < 0 || ly < 0 || lx >= (float) dw || ly >= (float) dh) continue; + + lx = lx * sw_dw; + ly = ly * sh_dh; + + int tx = (int)(sox + lx * six); + int ty = (int)(soy + ly * siy); + + if (tx < 0) tx = 0; + if (ty < 0) ty = 0; + if (tx >= sw) tx = sw - 1; + if (ty >= sh) ty = sh - 1; + + tx += sx; + ty += sy; + + uintpixel_t src = texture->buffer[ty * texture->width + tx]; + + if (opaque(src)) + alphaBlend(&dstline[cx], tint(tintColor, src), alpha); + } + } +} + +static void swrDrawSpriteRotated( + Renderer* renderer, float dx, float dy, float dw, float dh, + SWTexture* texture, int sx, int sy, int sw, int sh, + uint32_t tintColor, float alpha, + float angleDeg, + float pivotX, + float pivotY +) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + swrTransformPosIfNeeded(swr, &dx, &dy); + swrTransformSizeIfNeeded(swr, &pivotX, &pivotY); + swrTransformSizeIfNeeded(swr, &dw, &dh); + + swrDrawSpriteRotatedInternal( + renderer, + swrFloor(dx), + swrFloor(dy), + swrCeiling(dw), + swrCeiling(dh), + texture, + sx, sy, + sw, sh, + swrConvertPixel(tintColor), + swrIntAlpha(alpha), + angleDeg, + pivotX, + pivotY + ); +} + +static void SWRenderer_drawSprite(Renderer* renderer, int32_t tpagIndex, float x, float y, + float originX, float originY, float xscale, float yscale, + float angleDeg, uint32_t color, float alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + DataWin* dwin = renderer->dataWin; + + if (tpagIndex < 0 || (uint32_t) tpagIndex >= dwin->tpag.count) { + fprintf(stderr, "%s: tpagIndex of %d is invalid\n", __func__, tpagIndex); + return; + } + + TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; + int16_t pageId = tpag->texturePageId; + if (0 > pageId || swr->totalTextureCount <= (uint32_t) pageId) { + fprintf(stderr, "%s: tpagIndex of %d is invalid, as pageId of %d is invalid\n", __func__, tpagIndex, pageId); + return; + } + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) { + fprintf(stderr, "%s: could not ensure texture is loaded, tpagIndex: %d, pageId: %d\n", __func__, tpagIndex, pageId); + return; + } + + int sx = tpag->sourceX; + int sy = tpag->sourceY; + int sw = tpag->sourceWidth; + int sh = tpag->sourceHeight; + + float dx = (float)(tpag->targetX - originX); + float dy = (float)(tpag->targetY - originY); + int dw = (int)(xscale * tpag->targetWidth); + int dh = (int)(yscale * tpag->targetHeight); + dx *= xscale; + dy *= yscale; + dx += x; + dy += y; + + SWTexture* texture = swr->textures[pageId]; + + if (UNLIKELY(swrMustRotate(angleDeg))) + { + float pivotX = (x - dx) * swrSgn(xscale); + float pivotY = (y - dy) * swrSgn(yscale); + + if (tpag->targetWidth != tpag->sourceWidth) + pivotX *= (float)tpag->targetWidth / tpag->sourceWidth; + if (tpag->targetHeight != tpag->sourceHeight) + pivotY *= (float)tpag->targetHeight/ tpag->sourceHeight; + + swrDrawSpriteRotated(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha, angleDeg, pivotX, pivotY); + } + else + { + swrDrawSprite(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha); + } +} + +static void SWRenderer_drawSpritePart(Renderer* renderer, int32_t tpagIndex, + int32_t srcOffX, int32_t srcOffY, int32_t srcW, int32_t srcH, + float x, float y, float xscale, float yscale, float angleDeg, + float pivotX, float pivotY, uint32_t color, float alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + DataWin* dwin = renderer->dataWin; + + if (tpagIndex < 0 || (uint32_t) tpagIndex >= dwin->tpag.count) return; + + TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; + int16_t pageId = tpag->texturePageId; + if (0 > pageId || swr->totalTextureCount <= (uint32_t) pageId) return; + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return; + + int sx = tpag->sourceX + srcOffX; + int sy = tpag->sourceY + srcOffY; + int sw = srcW; + int sh = srcH; + + float dx = x; + float dy = y; + int dw = swrCeiling(xscale * sw); + int dh = swrCeiling(yscale * sh); + + SWTexture* texture = swr->textures[pageId]; + + if (UNLIKELY(swrMustRotate(angleDeg))) + { + swrDrawSpriteRotated(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha, angleDeg, pivotX * dw, pivotY * dh); + } + else + { + swrDrawSprite(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha); + } +} + +static void SWRenderer_drawSpritePos(Renderer* renderer, int32_t tpagIndex, + float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4, float alpha) +{ + (void)renderer; (void)tpagIndex; + (void)x1; (void)y1; (void)x2; (void)y2; + (void)x3; (void)y3; (void)x4; (void)y4; (void)alpha; + UNIMP(); +} + +static void SWRenderer_drawRectangle(Renderer* renderer, float x1, float y1, float x2, float y2, + uint32_t color, float alpha, bool outline) +{ + uintpixel_t pxcolor = swrConvertPixel(color); + + SWRenderer* swr = (SWRenderer*) renderer; + + if (outline) + { + swrDrawRectangle(renderer, x1, y1, x2, y2, pxcolor, alpha); + } + else + { + swrTransformPosIfNeeded(swr, &x1, &y1); + swrTransformPosIfNeeded(swr, &x2, &y2); + + int alphaInt = swrIntAlpha(alpha); + int x1i = swrFloor(x1), x2i = swrCeiling(x2), y1i = swrFloor(y1), y2i = swrCeiling(y2); + int xd = x2i - x1i; + int yd = y2i - y1i; + if (xd <= 0 || yd <= 0) return; + + for (int y = 0; y <= yd; y++) { + swrDrawHLineInt(renderer, x1i, y1i + y, xd, pxcolor, pxcolor, alphaInt); + } + } +} + +static void SWRenderer_drawRectangleColor(Renderer* renderer, float x1, float y1, float x2, float y2, + uint32_t color1, uint32_t color2, uint32_t color3, uint32_t color4, + float alpha, bool outline) +{ + uintpixel_t pxcolor1 = swrConvertPixel(color1); + uintpixel_t pxcolor2 = swrConvertPixel(color2); + uintpixel_t pxcolor3 = swrConvertPixel(color3); + uintpixel_t pxcolor4 = swrConvertPixel(color4); + + SWRenderer* swr = (SWRenderer*) renderer; + + if (outline) + { + swrDrawRectangleColor(renderer, x1, y1, x2, y2, pxcolor1, pxcolor2, pxcolor3, pxcolor4, alpha); + } + else + { + swrTransformPosIfNeeded(swr, &x1, &y1); + swrTransformPosIfNeeded(swr, &x2, &y2); + + int alphaInt = swrIntAlpha(alpha); + int x1i = swrFloor(x1), x2i = swrCeiling(x2), y1i = swrFloor(y1), y2i = swrCeiling(y2); + int xd = x2i - x1i; + int yd = y2i - y1i; + if (xd <= 0 || yd <= 0) return; + + // TODO: blending vertically + for (int y = 0; y <= yd; y++) { + swrDrawHLineInt(renderer, x1i, y1i + y, xd, pxcolor1, pxcolor2, alphaInt); + } + } +} + +static void SWRenderer_drawLine(Renderer* renderer, float x1, float y1, float x2, float y2, + float width, uint32_t color, float alpha) +{ + (void)renderer; (void)x1; (void)y1; (void)x2; (void)y2; + (void)width; (void)color; (void)alpha; + + uintpixel_t colorCvt = swrConvertPixel(color); + swrDrawLine(renderer, x1, y1, x2, y2, width, colorCvt, colorCvt, alpha); +} + +static void swrDrawTriangleInternal(SWRenderer* swr, int xup, int yup, int xleft, int yleft, int xright, int yright, uint32_t color1, uint32_t color2, uint32_t color3, int alpha) +{ + // TODO: update this + (void) color2; + (void) color3; + + // Figure out the maximum Y extent of the triangle. + // (Note that we know yup is the minimum.) + int xmid, ymid, xmid2 = xup, xmax, ymax; + if (yleft < yright) { + xmax = xright, ymax = yright; + xmid = xleft, ymid = yleft; + if (yright != yup) + xmid2 = xup + (xright - xup) * (ymid - yup) / (yright - yup); + } else { + xmax = xleft, ymax = yleft; + xmid = xright, ymid = yright; + if (yleft != yup) + xmid2 = xup + (xleft - xup) * (ymid - yup) / (yleft - yup); + } + + for (int y = yup; y < ymax; y++) + { + if (y < 0) continue; + if (y >= swr->height) break; + + int x1 = xup, x2 = xup; + if (y <= ymid) + { + // Lines: between up and mid, and between up and max + if (ymid != yup) + x1 = xup + (xmid - xup) * (y - yup) / (ymid - yup); + + if (ymid != yup) + x2 = xup + (xmid2 - xup) * (y - yup) / (ymid - yup); + } + else + { + // Lines: between mid and max, and between up and max + if (ymax != yup) + x1 = xup + (xmax - xup) * (y - yup) / (ymax - yup); + + if (ymax != ymid) + x2 = xmid + (xmax - xmid) * (y - ymid) / (ymax - ymid); + } + + if (x1 >= x2) { + int tmp = x1; + x1 = x2; + x2 = tmp; + } + + if (x1 < swr->portX) x1 = swr->portX; + if (x1 >= swr->maxX) continue; + if (x2 < swr->portX) continue; + if (x2 >= swr->maxX) x2 = swr->maxX - 1; + if (x1 > x2) continue; + + uintpixel_t* line = &swr->fb[y * swr->width]; + for (int x = x1; x < x2; x++) { + alphaBlend(&line[x], color1, alpha); + } + } +} + +static void swrDrawTriangle(Renderer* renderer, float x1, float y1, float x2, float y2, float x3, float y3, uint32_t color1, uint32_t color2, uint32_t color3, float alpha) +{ + float xup, yup, xleft, yleft, xright, yright; + uint32_t colorup, colorleft, colorright; + + SWRenderer* swr = (SWRenderer*) renderer; + swrTransformPosIfNeeded(swr, &x1, &y1); + swrTransformPosIfNeeded(swr, &x2, &y2); + swrTransformPosIfNeeded(swr, &x3, &y3); + + //which vertex is higher? + xup = x1, yup = y1; colorup = color1; + xleft = x2, yleft = y2; colorleft = color2; + xright = x3, yright = y3; colorright = color3; + if (yup > y2) { + xup = x2, yup = y2, colorup = color2; + xleft = x1, yleft = y1, colorleft = color1; + //xright = x3, yright = y3; + } + if (yup > y3) { + xup = x3, yup = y3, colorup = color3; + xleft = x1, yleft = y1, colorleft = color1; + xright = x2, yright = y2, colorright = color2; + } + + if (xleft > xright) { + float tmp = xleft; + xleft = xright; + xright = tmp; + tmp = yleft; + yleft = yright; + yright = tmp; + uint32_t tmp2 = colorleft; + colorleft = colorright; + colorright = tmp2; + } + + swrDrawTriangleInternal( + swr, + swrFloor(xup), swrFloor(yup), + swrFloor(xleft), swrCeiling(yleft), + swrFloor(xright), swrCeiling(yright), + swrConvertPixel(colorup), + swrConvertPixel(colorleft), + swrConvertPixel(colorright), + swrIntAlpha(alpha) + ); +} + +static void SWRenderer_drawTriangle(Renderer* renderer, + float x1, float y1, float x2, float y2, float x3, float y3, + uint32_t color1, uint32_t color2, uint32_t color3, + float alpha, bool outline) +{ + if (outline) + { + uintpixel_t color1cvt = swrConvertPixel(color1); + uintpixel_t color2cvt = swrConvertPixel(color2); + uintpixel_t color3cvt = swrConvertPixel(color3); + swrDrawLine(renderer, x1, y1, x2, y2, 1, color1cvt, color2cvt, renderer->drawAlpha); + swrDrawLine(renderer, x1, y1, x3, y3, 1, color1cvt, color3cvt, renderer->drawAlpha); + swrDrawLine(renderer, x2, y2, x3, y3, 1, color3cvt, color3cvt, renderer->drawAlpha); + } + else + { + swrDrawTriangle(renderer, x1, y1, x2, y2, x3, y3, color1, color2, color3, alpha); + } +} + +static void SWRenderer_drawLineColor(Renderer* renderer, float x1, float y1, float x2, float y2, + float width, uint32_t color1, uint32_t color2, float alpha) +{ + swrDrawLine(renderer, x1, y1, x2, y2, width, swrConvertPixel(color1), swrConvertPixel(color2), alpha); +} + +typedef struct +{ + Font* font; + TexturePageItem* fontTpag; // single TPAG for regular fonts (NULL for sprite fonts) + int fontTpagIndex; + int fontPageId; + Sprite* spriteFontSprite; // source sprite for sprite fonts (NULL for regular fonts) +} +SwrFontState; + +static bool swrResolveFontState(SWRenderer* swr, DataWin* dw, Font* font, SwrFontState* state) +{ + state->font = font; + state->fontTpag = NULL; + state->fontTpagIndex = 0; + state->spriteFontSprite = NULL; + + if (font->isSpriteFont) + { + state->spriteFontSprite = &dw->sprt.sprites[font->spriteIndex]; + } + else + { + state->fontTpagIndex = font->tpagIndex; + if (state->fontTpagIndex < 0) return false; + + state->fontTpag = &dw->tpag.items[state->fontTpagIndex]; + int16_t pageId = state->fontTpag->texturePageId; + if (0 > pageId || (uint32_t) pageId >= swr->totalTextureCount) return false; + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return false; + + state->fontPageId = pageId; + } + + return true; +} + +static bool swrResolveGlyph( + SWRenderer* swr, DataWin* dw, SwrFontState* state, FontGlyph* glyph, float cursorX, float cursorY, + int* tpagIndex, int* pageId, int* sx, int* sy, int* sw, int* sh, float* dx, float* dy +) +{ + Font* font = state->font; + if (font->isSpriteFont && state->spriteFontSprite != NULL) + { + Sprite* sprite = state->spriteFontSprite; + int32_t glyphIndex = (int32_t) (glyph - font->glyphs); + if (0 > glyphIndex || glyphIndex >= (int32_t) sprite->textureCount) return false; + + int32_t tpagIdx = sprite->tpagIndices[glyphIndex]; + if (0 > tpagIdx) return false; + + TexturePageItem* glyphTpag = &dw->tpag.items[tpagIdx]; + int16_t pid = glyphTpag->texturePageId; + if (0 > pid || (uint32_t) pid >= swr->totalTextureCount) return false; + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pid)) return false; + + *tpagIndex = tpagIdx; + *pageId = glyphTpag->texturePageId; + + *sx = glyphTpag->sourceX; + *sy = glyphTpag->sourceY; + *sw = glyphTpag->sourceWidth; + *sh = glyphTpag->sourceHeight; + + *dx = cursorX + glyph->offset; + *dy = cursorY + glyphTpag->targetY - sprite->originY; + } + else + { + *tpagIndex = state->fontTpagIndex; + *pageId = state->fontPageId; + + *sx = state->fontTpag->sourceX + glyph->sourceX; + *sy = state->fontTpag->sourceY + glyph->sourceY; + *sw = glyph->sourceWidth; + *sh = glyph->sourceHeight; + + *dx = cursorX + glyph->offset; + *dy = cursorY; + } + + return true; +} + +static void swrDrawText(SWRenderer* swr, const char* text, float x, float y, float xscale, float yscale, float angleDeg, int32_t color, float alpha, float lineSeparation) +{ + Renderer* renderer = &swr->base; + DataWin* dwin = renderer->dataWin; + + int32_t fontIndex = renderer->drawFont; + if (0 > fontIndex || dwin->font.count <= (uint32_t) fontIndex) return; + + Font* font = &dwin->font.fonts[fontIndex]; + + SwrFontState fontState; + memset(&fontState, 0, sizeof fontState); // silence warning treated as error + + if (!swrResolveFontState(swr, dwin, font, &fontState)) return; + + // TODO: do we need to mirror the way the text scrolls too?! + float cosA = 1.0f, sinA = 0.0f, angleRad = 0.0f; + bool mustRotate = swrMustRotateTolerant(angleDeg); + if (UNLIKELY(mustRotate)) + { + angleRad = -angleDeg * M_PI / 180.0f; + cosA = cosf(angleRad); + sinA = sinf(angleRad); + } + + int textLen = (int) strlen(text); + int lineCount = TextUtils_countLines(text, textLen); + float lineStride = (0.0f > lineSeparation) ? TextUtils_lineStride(font) : (lineSeparation / (font->scaleY != 0.0f ? font->scaleY : 1.0f)); + + // Vertical alignment offset + float totalHeight = (float) lineCount * lineStride; + float valignOffset = 0; + if (renderer->drawValign == 1) valignOffset = -totalHeight / 2.0f; + else if (renderer->drawValign == 2) valignOffset = -totalHeight; + + xscale *= font->scaleX; + yscale *= font->scaleY; + + // Iterate through lines. HTML5 subtracts ascenderOffset from the per-line y offset + // (see yyFont.GR_Text_Draw), shifting glyphs up so the baseline aligns with the drawn y. + float cursorY = valignOffset - (float) font->ascenderOffset; + int32_t lineStart = 0; + + for (int32_t lineIdx = 0; lineCount > lineIdx; lineIdx++) { + // Find end of current line + int32_t lineEnd = lineStart; + while (textLen > lineEnd && !TextUtils_isNewlineChar(text[lineEnd])) { + lineEnd++; + } + int32_t lineLen = lineEnd - lineStart; + + // Horizontal alignment offset for this line + float lineWidth = TextUtils_measureLineWidth(font, text + lineStart, lineLen); + float halignOffset = 0; + if (renderer->drawHalign == 1) halignOffset = -lineWidth / 2.0f; + else if (renderer->drawHalign == 2) halignOffset = -lineWidth; + + float cursorX = halignOffset; + + // Render each glyph in the line - decode each codepoint once and carry it forward as next iteration's ch (also used for kerning) + int32_t pos = 0; + uint16_t ch = 0; + bool hasCh = false; + if (lineLen > pos) { + ch = TextUtils_decodeUtf8(text + lineStart, lineLen, &pos); + hasCh = true; + } + + while (hasCh) { + FontGlyph* glyph = TextUtils_findGlyph(font, ch); + + uint16_t nextCh = 0; + bool hasNext = lineLen > pos; + if (hasNext) nextCh = TextUtils_decodeUtf8(text + lineStart, lineLen, &pos); + + if (glyph != nullptr) { + bool drewSuccessfully = false; + if (glyph->sourceWidth != 0 && glyph->sourceHeight != 0) { + int fontTpagIndex = 0, pageId = 0; + int sx, sy, sw, sh, dw, dh; + float dx, dy; + if (swrResolveGlyph(swr, dwin, &fontState, glyph, cursorX, cursorY, + &fontTpagIndex, &pageId, &sx, &sy, &sw, &sh, &dx, &dy)) + { + dx *= xscale; dx += x; + dy *= xscale; dy += y; + dw = swrCeiling(xscale * glyph->sourceWidth); + dh = swrCeiling(yscale * glyph->sourceHeight); + + // TODO: at 640x480, for some reason, without this fixup the + // letters in the "Name the fallen human." screen don't shake + dx = roundf(dx * 2) / 2; + dy = roundf(dy * 2) / 2; + + SWTexture* texture = swr->textures[pageId]; + + if (UNLIKELY(mustRotate)) + { + dx -= x; + dy -= y; + float ndx = cosA * dx - sinA * dy; + float ndy = sinA * dx + cosA * dy; + ndx += x; + ndy += y; + swrDrawSpriteRotated(renderer, ndx, ndy, dw, dh, texture, sx, sy, sw, sh, color, alpha, angleDeg, 0.0f, 0.0f); + } + else + { + swrDrawSprite(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha); + } + + drewSuccessfully = true; + } + } + + cursorX += glyph->shift; + if (drewSuccessfully && hasNext) { + cursorX += TextUtils_getKerningOffset(glyph, nextCh); + } + } + + ch = nextCh; + hasCh = hasNext; + } + + cursorY += lineStride; + // Skip past the newline, treating \r\n and \n\r as single breaks + if (textLen > lineEnd) { + lineStart = TextUtils_skipNewline(text, lineEnd, textLen); + } else { + lineStart = lineEnd; + } + } +} + +static void SWRenderer_drawText(Renderer* renderer, const char* text, float x, float y, + float xscale, float yscale, float angleDeg, float lineSeparation) +{ + SWRenderer* swr = (SWRenderer*) renderer; + swrDrawText(swr, text, x, y, xscale, yscale, angleDeg, renderer->drawColor, renderer->drawAlpha, lineSeparation); +} + +static void SWRenderer_drawTextColor(Renderer* renderer, const char* text, float x, float y, + float xscale, float yscale, float angleDeg, + int32_t c1, int32_t c2, int32_t c3, int32_t c4, MAYBE_UNUSED float alpha, + float lineSeparation) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + // TODO: allow c2, c3, c4 + (void) c2; + (void) c3; + (void) c4; + + swrDrawText(swr, text, x, y, xscale, yscale, angleDeg, c1, renderer->drawAlpha, lineSeparation); +} + +static void SWRenderer_drawSpriteTiled(Renderer* renderer, int32_t tpagIndex, + float originX, float originY, float x, float y, + float xscale, float yscale, bool tileX, bool tileY, + float roomW, float roomH, uint32_t color, float alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + DataWin* dwin = renderer->dataWin; + + if (0 > tpagIndex || dwin->tpag.count <= (uint32_t) tpagIndex) return; + + TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; + int16_t pageId = tpag->texturePageId; + if (0 > pageId || swr->totalTextureCount <= (uint32_t) pageId) return; + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return; + + float axScale = fabsf(xscale); + float ayScale = fabsf(yscale); + float tileW = (float) tpag->boundingWidth * axScale; + float tileH = (float) tpag->boundingHeight * ayScale; + if (0 >= tileW || 0 >= tileH) return; + + float startX, endX, startY, endY; + if (tileX) { + startX = fmodf(x - originX * axScale, tileW); + if (startX > 0) startX -= tileW; + endX = roomW; + } else { + startX = x - originX * axScale; + endX = startX + tileW; + } + if (tileY) { + startY = fmodf(y - originY * ayScale, tileH); + if (startY > 0) startY -= tileH; + endY = roomH; + } else { + startY = y - originY * ayScale; + endY = startY + tileH; + } + + int sx = tpag->sourceX; + int sy = tpag->sourceY; + int sw = tpag->sourceWidth; + int sh = tpag->sourceHeight; + + int localX0 = tpag->targetX - originX; + int localY0 = tpag->targetY - originY; + int localX1 = localX0 + tpag->sourceWidth; + int localY1 = localY0 + tpag->sourceHeight; + int sx0 = xscale * localX0; + int sy0 = yscale * localY0; + int sx1 = xscale * localX1; + int sy1 = yscale * localY1; + + for (int dy = startY; endY > dy; dy += tileH) { + int cy = dy + (int)(originY * ayScale); + int vy0 = cy + sy0; + int vy1 = cy + sy1; + int dh = vy1 - vy0; + + for (int dx = startX; endX > dx; dx += tileW) { + int cx = dx + (int)(originX * axScale); + int vx0 = cx + sx0; + int vx1 = cx + sx1; + int dw = vx1 - vx0; + + swrDrawSprite(renderer, vx0, vy0, dw, dh, swr->textures[pageId], sx, sy, sw, sh, color, alpha); + } + } +} + +static void SWRenderer_drawSurfaceTiled(Renderer* renderer, int32_t surfaceID, float x, float y, float xscale, float yscale, float roomW, float roomH, uint32_t color, float alpha) +{ + (void) renderer; + (void) surfaceID; + (void) x; (void) y; + (void) xscale; (void) yscale; + (void) roomW; (void) roomH; + (void) color; (void) alpha; + + UNIMP2(); +} + +static void SWRenderer_flush(Renderer* renderer) +{ + (void)renderer; + UNIMP(); +} + +static void SWRenderer_clearScreen(Renderer* renderer, uint32_t color, float alpha) +{ + (void)renderer; (void)color; (void)alpha; + UNIMP(); +} + +static void SWRenderer_gpuSetBlendMode(Renderer* renderer, int32_t mode) +{ + UNIMP(); + (void)renderer; (void)mode; +} + +static void SWRenderer_gpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t dfactor, int32_t sfactor_alpha, int32_t dfactor_alpha) +{ + UNIMP(); + (void)renderer; (void)sfactor; (void)dfactor; (void)sfactor_alpha; (void)dfactor_alpha; +} + +static void SWRenderer_gpuSetBlendEnable(Renderer* renderer, bool enable) +{ + UNIMP(); + (void)renderer; (void)enable; +} + +static void SWRenderer_gpuSetAlphaTestEnable(Renderer* renderer, bool enable) +{ + UNIMP(); + (void)renderer; (void)enable; +} + +static void SWRenderer_gpuSetAlphaTestRef(Renderer* renderer, uint8_t ref) +{ + UNIMP(); + (void)renderer; (void)ref; +} + +static void SWRenderer_gpuSetColorWriteEnable(Renderer* renderer, bool red, bool green, bool blue, bool alpha) +{ + UNIMP(); + (void)renderer; (void)red; (void)green; (void)blue; (void)alpha; +} + +static void SWRenderer_gpuGetColorWriteEnable(Renderer* renderer, bool* red, bool* green, bool* blue, bool* alpha) +{ + UNIMP(); + *red = false; + *green = false; + *blue = false; + *alpha = false; + (void)renderer; (void)red; (void)green; (void)blue; (void)alpha; +} + +static bool SWRenderer_gpuGetBlendEnable(Renderer* renderer) +{ + UNIMP(); + (void)renderer; + return false; +} + +static void SWRenderer_gpuSetFog(Renderer* renderer, bool enable, uint32_t color) +{ + UNIMP(); + (void)renderer; (void)enable; (void)color; +} + +static int32_t SWRenderer_createSurface(Renderer* renderer, int32_t width, int32_t height) +{ + UNIMP(); + (void)renderer; (void)width; (void)height; + return 0; +} + +static bool SWRenderer_surfaceExists(Renderer* renderer, int32_t surfaceID) +{ + UNIMP(); + (void)renderer; (void)surfaceID; + return false; +} + +static bool SWRenderer_setRenderTarget(Renderer* renderer, int32_t surfaceID, bool implicitApplicationSurface) +{ + UNIMP(); + (void)renderer; (void)surfaceID; + (void)implicitApplicationSurface; + return false; +} + +static float SWRenderer_getSurfaceWidth(Renderer* renderer, int32_t surfaceID) +{ + SWRenderer* swr = (SWRenderer*) renderer; + if (surfaceID == APPLICATION_SURFACE_ID) { + return (float) swr->width; + } + + UNIMP(); + (void)renderer; (void)surfaceID; + return 0.0f; +} + +static float SWRenderer_getSurfaceHeight(Renderer* renderer, int32_t surfaceID) +{ + SWRenderer* swr = (SWRenderer*) renderer; + if (surfaceID == APPLICATION_SURFACE_ID) { + return (float) swr->height; + } + + UNIMP(); + (void)renderer; (void)surfaceID; + return 0.0f; +} + +static void SWRenderer_drawSurface(Renderer* renderer, int32_t surfaceID, + int32_t srcLeft, int32_t srcTop, int32_t srcWidth, int32_t srcHeight, + float x, float y, float xscale, float yscale, float angleDeg, + uint32_t color, float alpha) +{ + UNIMP(); + (void)renderer; (void)surfaceID; + (void)srcLeft; (void)srcTop; (void)srcWidth; (void)srcHeight; + (void)x; (void)y; (void)xscale; (void)yscale; (void)angleDeg; + (void)color; (void)alpha; +} + +static void SWRenderer_surfaceResize(Renderer* renderer, int32_t surfaceID, int32_t width, int32_t height) +{ + UNIMP(); + (void)renderer; (void)surfaceID; (void)width; (void)height; +} + +static void SWRenderer_surfaceFree(Renderer* renderer, int32_t surfaceID) +{ + UNIMP(); + (void)renderer; (void)surfaceID; +} + +static void SWRenderer_surfaceCopy(Renderer* renderer, + int32_t DestSurfaceID, int32_t DestX, int32_t DestY, + int32_t SrcSurfaceID, int32_t SrcX, int32_t SrcY, + int32_t SrcW, int32_t SrcH, bool part) +{ + UNIMP(); + (void)renderer; + (void)DestSurfaceID; (void)DestX; (void)DestY; + (void)SrcSurfaceID; (void)SrcX; (void)SrcY; + (void)SrcW; (void)SrcH; (void)part; +} + +static bool SWRenderer_surfaceGetPixels(Renderer* renderer, int32_t surfaceID, uint8_t* outRGBA) +{ + UNIMP(); + (void)renderer; (void)surfaceID; (void)outRGBA; + return false; +} + +static int32_t SWRenderer_createSpriteFromSurface(Renderer* renderer, int32_t surfaceID, + int32_t x, int32_t y, int32_t w, int32_t h, + bool removeback, bool smooth, + int32_t xorig, int32_t yorig) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + swrTransformPosIntIfNeeded(swr, &x, &y); + swrTransformSizeIntIfNeeded(swr, &w, &h); + swrTransformSizeIntIfNeeded(swr, &xorig, &yorig); + + (void) removeback; + (void) smooth; + + if (surfaceID != -1) { + fprintf(stderr, "%s: Surfaces other than application_surface aren't supported yet!\n", __func__); + return 0; + } + + int32_t texturePageId = swrFindSurfaceTextureSlot(swr); + int32_t tpagIndex = swrFindSurfaceTPagSlot(swr); + if (texturePageId == -1 || tpagIndex == -1) { + fprintf(stderr, "%s: Surface overflow!!\n", __func__); + return 0; + } + + SWTexture* tex = swrCreateTexture(NULL, w, h); + + // grab the pixels. + for (int iy = 0; iy < h; iy++) + { + uintpixel_t* dstline = &tex->buffer[iy * tex->width]; + if ((iy + y) < 0 || (iy + y) >= swr->height) + { + for (int ix = 0; ix < w; ix++) + dstline[ix] = 0; + + continue; + } + + uintpixel_t* srcline = &swr->fb[(iy + y) * swr->width + x]; + + int ix = 0, sx = x; + // left edge + for (; sx < 0 && ix < w; sx++, ix++) + dstline[ix] = 0; + + // in-bounds + for (; sx < swr->width && ix < w; sx++, ix++) +#if PIXEL_SIZE == 8 + dstline[ix] = srcline[ix]; +#else + dstline[ix] = srcline[ix] | TRANSPARENT_MASK; +#endif + + // right edge + for (; ix < w; ix++) + dstline[ix] = 0; + } + + int32_t spriteW = w; + int32_t spriteH = h; + + int32_t targetW = spriteW; + int32_t targetH = spriteH; + swrReverseTransformSizeIntIfNeeded(swr, &targetW, &targetH); + + swr->textures[texturePageId] = tex; + + // TODO[MrPowerGamerBR]: This is supposed to be refactored, not to modify data.win structs directly. + DataWin* dw = swr->base.dataWin; + TexturePageItem* tpag = &dw->tpag.items[tpagIndex]; + tpag->sourceX = 0; + tpag->sourceY = 0; + tpag->sourceWidth = (uint16_t) spriteW; + tpag->sourceHeight = (uint16_t) spriteH; + tpag->targetX = 0; + tpag->targetY = 0; + tpag->targetWidth = (uint16_t) (spriteW * swr->viewW / swr->portW); + tpag->targetHeight = (uint16_t) (spriteH * swr->viewH / swr->portH); + tpag->boundingWidth = (uint16_t) spriteW; + tpag->boundingHeight = (uint16_t) spriteH; + tpag->texturePageId = texturePageId; + + uint32_t spriteIndex = DataWin_allocSpriteSlot(dw, swr->originalSpriteCount); + Sprite* sprite = &dw->sprt.sprites[spriteIndex]; + // name was set by DataWin_allocSpriteSlot ("__newsprite"); don't overwrite it here + sprite->width = (uint32_t) w; + sprite->height = (uint32_t) h; + sprite->originX = xorig; + sprite->originY = yorig; + sprite->textureCount = 1; + sprite->tpagIndices = (int32_t*) safeMalloc(sizeof(int32_t)); + sprite->tpagIndices[0] = (int32_t) tpagIndex; + sprite->maskCount = 0; + sprite->masks = nullptr; + + fprintf(stderr, "%s: Allocated surface sprite with ID %d\n", __func__, spriteIndex); + return spriteIndex; +} + +static void SWRenderer_deleteSprite(Renderer* renderer, int32_t spriteIndex) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + DataWin* dw = renderer->dataWin; + if (0 > spriteIndex || dw->sprt.count <= (uint32_t) spriteIndex) return; + + // Refuse to delete original data.win sprites + if (swr->originalSpriteCount > (uint32_t) spriteIndex) { + fprintf(stderr, "%s: Sprite index %d is invalid.\n", __func__, spriteIndex); + return; + } + + Sprite* sprite = &dw->sprt.sprites[spriteIndex]; + if (sprite->textureCount == 0) return; // already deleted + + for (uint32_t i = 0; i < sprite->textureCount; i++) + { + int32_t tpagIdx = sprite->tpagIndices[i]; + if (tpagIdx >= 0 && (uint32_t) tpagIdx >= swr->originalTPagCount) { + TexturePageItem* tpag = &dw->tpag.items[tpagIdx]; + int16_t pageId = tpag->texturePageId; + if (pageId >= 0 && swr->totalTextureCount > (uint32_t) pageId) { + swrFreeTexture(swr->textures[pageId]); + swr->textures[pageId] = NULL; + } + // Mark TPAG slot as free for reuse + tpag->texturePageId = -1; + } + } + + free(sprite->tpagIndices); + sprite->tpagIndices = NULL; + + const char* keepName = sprite->name; + memset(sprite, 0, sizeof(Sprite)); + sprite->name = keepName; + + fprintf(stderr, "SWR: Deleted sprite %d\n", spriteIndex); +} + +static void SWRenderer_drawTiledPart(Renderer* renderer, int32_t tpagIndex, + int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH, + float dstX, float dstY, float dstW, float dstH, + uint32_t color, float alpha) +{ + UNIMP(); + (void)renderer; (void)tpagIndex; + (void)srcX; (void)srcY; (void)srcW; (void)srcH; + (void)dstX; (void)dstY; (void)dstW; (void)dstH; + (void)color; (void)alpha; +} + +static int32_t SWRenderer_ensureApplicationSurface(Renderer* renderer, int32_t width, int32_t height) +{ + UNIMP(); + (void) renderer; + (void) width; + (void) height; + return -1; +} + +static RendererVtable swrVtable; + +void SWRenderer_clearFrameBuffer(Renderer* renderer, uint32_t color) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + uintpixel_t pxcolor = swrConvertPixel(color); + + size_t fbSize = swr->fbPitch; + fbSize *= swr->height; + for (size_t i = 0; i < fbSize; i++) + { + swr->fb[i] = pxcolor; + } +} + +static uint32_t SWRenderer_spriteGetTexture(Renderer* renderer, int32_t tpagIndex) +{ + (void) renderer; + + return (uint32_t) tpagIndex + 1; +} + +static uint32_t SWRenderer_surfaceGetTexture(Renderer* renderer, int32_t surfaceID) +{ + (void) renderer; + (void) surfaceID; + + return (uint32_t) -1; +} + +static float SWRenderer_textureGetTexelWidth(Renderer* renderer, uint32_t texID) +{ + (void) renderer; + (void) texID; + + return 1.0f; +} + +static float SWRenderer_textureGetTexelHeight(Renderer* renderer, uint32_t texID) +{ + (void) renderer; + (void) texID; + + return 1.0f; +} + +static bool SWRenderer_textureGetUVs(Renderer* renderer, uint32_t texID, float* outUVs) +{ + (void) renderer; + (void) texID; + (void) outUVs; + + return false; +} + +static void SWRenderer_textureSetStage(Renderer* renderer, int32_t slot, uint32_t texID) +{ + (void) renderer; + (void) slot; + (void) texID; +} + +static bool SWRenderer_shaderIsCompiled(Renderer* renderer, int32_t shader) +{ + (void) renderer; + (void) shader; + + return false; +} + +static bool SWRenderer_shadersSupported(void) +{ + return false; +} + +static void SWRenderer_gpuSetShader(Renderer* renderer, int32_t shaderIndex) +{ + (void) renderer; + (void) shaderIndex; +} + +static void SWRenderer_gpuResetShader(Renderer* renderer) +{ + (void) renderer; +} + +static int32_t SWRenderer_shaderGetUniform(Renderer* renderer, int32_t shaderIndex, char* uniform) +{ + (void) renderer; + (void) shaderIndex; + (void) uniform; + + return 0; +} + +static int32_t SWRenderer_shaderGetSamplerIndex(Renderer* renderer, int32_t shaderIndex, char* uniform) +{ + (void) renderer; + (void) shaderIndex; + (void) uniform; + + return 0; +} + +static void SWRenderer_shaderSetUniformF(Renderer* renderer, int32_t handle, int32_t count, float value1, float value2, float value3, float value4) +{ + (void) renderer; + (void) handle; + (void) count; + (void) value1; + (void) value2; + (void) value3; + (void) value4; +} + +static void SWRenderer_shaderSetUniformI(Renderer* renderer, int32_t handle, int32_t count, int32_t value1, int32_t value2, int32_t value3, int32_t value4) +{ + (void) renderer; + (void) handle; + (void) count; + (void) value1; + (void) value2; + (void) value3; + (void) value4; +} + +Renderer* SWRenderer_create(void) +{ + SWRenderer* swr = (SWRenderer*) safeCalloc(1, sizeof(SWRenderer)); + swr->base.vtable = &swrVtable; + swrVtable.init = SWRenderer_init; + swrVtable.destroy = SWRenderer_destroy; + swrVtable.beginFrame = SWRenderer_beginFrame; + swrVtable.endFrameInit = SWRenderer_endFrameInit; + swrVtable.endFrameEnd = SWRenderer_endFrameEnd; + swrVtable.beginView = SWRenderer_beginView; + swrVtable.endView = SWRenderer_endView; + swrVtable.beginGUI = SWRenderer_beginGUI; + swrVtable.setGuiProjection = SWRenderer_setGuiProjection; + swrVtable.endGUI = SWRenderer_endGUI; + swrVtable.drawSprite = SWRenderer_drawSprite; + swrVtable.drawSpritePart = SWRenderer_drawSpritePart; + swrVtable.drawSpritePos = SWRenderer_drawSpritePos; + swrVtable.drawRectangle = SWRenderer_drawRectangle; + swrVtable.drawRectangleColor = SWRenderer_drawRectangleColor; + swrVtable.drawLine = SWRenderer_drawLine; + swrVtable.drawTriangle = SWRenderer_drawTriangle; + swrVtable.drawLineColor = SWRenderer_drawLineColor; + swrVtable.drawText = SWRenderer_drawText; + swrVtable.drawTextColor = SWRenderer_drawTextColor; + swrVtable.flush = SWRenderer_flush; + swrVtable.clearScreen = SWRenderer_clearScreen; + swrVtable.createSpriteFromSurface = SWRenderer_createSpriteFromSurface; + swrVtable.deleteSprite = SWRenderer_deleteSprite; + swrVtable.gpuSetBlendMode = SWRenderer_gpuSetBlendMode; + swrVtable.gpuSetBlendModeExt = SWRenderer_gpuSetBlendModeExt; + swrVtable.gpuSetBlendEnable = SWRenderer_gpuSetBlendEnable; + swrVtable.gpuSetAlphaTestEnable = SWRenderer_gpuSetAlphaTestEnable; + swrVtable.gpuSetAlphaTestRef = SWRenderer_gpuSetAlphaTestRef; + swrVtable.gpuSetColorWriteEnable = SWRenderer_gpuSetColorWriteEnable; + swrVtable.gpuGetColorWriteEnable = SWRenderer_gpuGetColorWriteEnable; + swrVtable.gpuGetBlendEnable = SWRenderer_gpuGetBlendEnable; + swrVtable.gpuSetFog = SWRenderer_gpuSetFog; + swrVtable.drawSpriteTiled = SWRenderer_drawSpriteTiled; + swrVtable.drawSurfaceTiled = SWRenderer_drawSurfaceTiled; + swrVtable.createSurface = SWRenderer_createSurface; + swrVtable.surfaceExists = SWRenderer_surfaceExists; + swrVtable.setRenderTarget = SWRenderer_setRenderTarget; + swrVtable.ensureApplicationSurface = SWRenderer_ensureApplicationSurface; + swrVtable.getSurfaceWidth = SWRenderer_getSurfaceWidth; + swrVtable.getSurfaceHeight = SWRenderer_getSurfaceHeight; + swrVtable.drawSurface = SWRenderer_drawSurface; + swrVtable.surfaceResize = SWRenderer_surfaceResize; + swrVtable.surfaceFree = SWRenderer_surfaceFree; + swrVtable.surfaceCopy = SWRenderer_surfaceCopy; + swrVtable.surfaceGetPixels = SWRenderer_surfaceGetPixels; + swrVtable.drawTiledPart = SWRenderer_drawTiledPart; + swrVtable.spriteGetTexture = SWRenderer_spriteGetTexture; + swrVtable.surfaceGetTexture = SWRenderer_surfaceGetTexture; + swrVtable.textureGetTexelWidth = SWRenderer_textureGetTexelWidth; + swrVtable.textureGetTexelHeight = SWRenderer_textureGetTexelHeight; + swrVtable.textureGetUVs = SWRenderer_textureGetUVs; + swrVtable.textureSetStage = SWRenderer_textureSetStage; + swrVtable.gpuSetShader = SWRenderer_gpuSetShader; + swrVtable.gpuResetShader = SWRenderer_gpuResetShader; + swrVtable.shaderGetUniform = SWRenderer_shaderGetUniform; + swrVtable.shaderGetSamplerIndex = SWRenderer_shaderGetSamplerIndex; + swrVtable.shaderSetUniformF = SWRenderer_shaderSetUniformF; + swrVtable.shaderSetUniformI = SWRenderer_shaderSetUniformI; + swrVtable.shaderIsCompiled = SWRenderer_shaderIsCompiled; + swrVtable.shadersSupported = SWRenderer_shadersSupported; + + swrVtable.drawTile = NULL; + + swr->base.drawColor = 0xFFFFFF; + swr->base.drawAlpha = 1.0f; + swr->base.drawFont = -1; + swr->base.drawHalign = 0; + swr->base.drawValign = 0; + swr->base.circlePrecision = 24; + + return (Renderer*) swr; +} diff --git a/src/sw/sw_renderer.h b/src/sw/sw_renderer.h new file mode 100644 index 000000000..1b2618779 --- /dev/null +++ b/src/sw/sw_renderer.h @@ -0,0 +1,7 @@ +#pragma once + +#include "renderer.h" + +Renderer* SWRenderer_create(void); + +void SWRenderer_clearFrameBuffer(Renderer* renderer, uint32_t color); diff --git a/vendor/glad/include/glad/glad.h b/vendor/glad/include/glad/glad.h index 01d618327..e7a5a55df 100644 --- a/vendor/glad/include/glad/glad.h +++ b/vendor/glad/include/glad/glad.h @@ -1,10 +1,10 @@ /* - OpenGL, OpenGL ES loader generated by glad 0.1.36 on Thu Jul 2 22:44:49 2026. + OpenGL, OpenGL ES loader generated by glad 0.1.36 on Tue Jul 7 08:27:13 2026. Language/Generator: C/C++ Specification: gl - APIs: gl=4.1, gles2=3.0 + APIs: gl=4.1, gles1=1.0, gles2=3.0 Profile: compatibility Extensions: GL_ARB_debug_output, @@ -13,6 +13,7 @@ GL_EXT_framebuffer_blit, GL_EXT_framebuffer_object, GL_KHR_debug, + GL_OES_framebuffer_object, GL_OES_vertex_array_object Loader: True Local files: False @@ -20,9 +21,9 @@ Reproducible: False Commandline: - --profile="compatibility" --api="gl=4.1,gles2=3.0" --generator="c" --spec="gl" --extensions="GL_ARB_debug_output,GL_ARB_framebuffer_object,GL_ARB_vertex_array_object,GL_EXT_framebuffer_blit,GL_EXT_framebuffer_object,GL_KHR_debug,GL_OES_vertex_array_object" + --profile="compatibility" --api="gl=4.1,gles1=1.0,gles2=3.0" --generator="c" --spec="gl" --extensions="GL_ARB_debug_output,GL_ARB_framebuffer_object,GL_ARB_vertex_array_object,GL_EXT_framebuffer_blit,GL_EXT_framebuffer_object,GL_KHR_debug,GL_OES_framebuffer_object,GL_OES_vertex_array_object" Online: - https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D4.1&api=gles2%3D3.0&extensions=GL_ARB_debug_output&extensions=GL_ARB_framebuffer_object&extensions=GL_ARB_vertex_array_object&extensions=GL_EXT_framebuffer_blit&extensions=GL_EXT_framebuffer_object&extensions=GL_KHR_debug&extensions=GL_OES_vertex_array_object + https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D4.1&api=gles1%3D1.0&api=gles2%3D3.0&extensions=GL_ARB_debug_output&extensions=GL_ARB_framebuffer_object&extensions=GL_ARB_vertex_array_object&extensions=GL_EXT_framebuffer_blit&extensions=GL_EXT_framebuffer_object&extensions=GL_KHR_debug&extensions=GL_OES_framebuffer_object&extensions=GL_OES_vertex_array_object */ @@ -102,6 +103,8 @@ GLAPI int gladLoadGL(void); GLAPI int gladLoadGLLoader(GLADloadproc); +GLAPI int gladLoadGLES1Loader(GLADloadproc); + GLAPI int gladLoadGLES2Loader(GLADloadproc); #include @@ -1513,6 +1516,9 @@ typedef void (APIENTRY *GLVULKANPROCNV)(void); #define GL_LAYER_PROVOKING_VERTEX 0x825E #define GL_VIEWPORT_INDEX_PROVOKING_VERTEX 0x825F #define GL_UNDEFINED_VERTEX 0x8260 +#define GL_VERSION_ES_CL_1_0 1 +#define GL_VERSION_ES_CM_1_1 1 +#define GL_VERSION_ES_CL_1_1 1 #define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9 #define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69 #define GL_COPY_READ_BUFFER_BINDING 0x8F36 @@ -4164,6 +4170,139 @@ typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC)(GLenum target, GLuint index, GLdo GLAPI PFNGLGETDOUBLEI_VPROC glad_glGetDoublei_v; #define glGetDoublei_v glad_glGetDoublei_v #endif +#ifndef GL_VERSION_ES_CM_1_0 +#define GL_VERSION_ES_CM_1_0 1 +GLAPI int GLAD_GL_VERSION_ES_CM_1_0; +typedef void (APIENTRYP PFNGLCLIPPLANEFPROC)(GLenum p, const GLfloat *eqn); +GLAPI PFNGLCLIPPLANEFPROC glad_glClipPlanef; +#define glClipPlanef glad_glClipPlanef +typedef void (APIENTRYP PFNGLFRUSTUMFPROC)(GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +GLAPI PFNGLFRUSTUMFPROC glad_glFrustumf; +#define glFrustumf glad_glFrustumf +typedef void (APIENTRYP PFNGLGETCLIPPLANEFPROC)(GLenum plane, GLfloat *equation); +GLAPI PFNGLGETCLIPPLANEFPROC glad_glGetClipPlanef; +#define glGetClipPlanef glad_glGetClipPlanef +typedef void (APIENTRYP PFNGLORTHOFPROC)(GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +GLAPI PFNGLORTHOFPROC glad_glOrthof; +#define glOrthof glad_glOrthof +typedef void (APIENTRYP PFNGLALPHAFUNCXPROC)(GLenum func, GLfixed ref); +GLAPI PFNGLALPHAFUNCXPROC glad_glAlphaFuncx; +#define glAlphaFuncx glad_glAlphaFuncx +typedef void (APIENTRYP PFNGLCLEARCOLORXPROC)(GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI PFNGLCLEARCOLORXPROC glad_glClearColorx; +#define glClearColorx glad_glClearColorx +typedef void (APIENTRYP PFNGLCLEARDEPTHXPROC)(GLfixed depth); +GLAPI PFNGLCLEARDEPTHXPROC glad_glClearDepthx; +#define glClearDepthx glad_glClearDepthx +typedef void (APIENTRYP PFNGLCLIPPLANEXPROC)(GLenum plane, const GLfixed *equation); +GLAPI PFNGLCLIPPLANEXPROC glad_glClipPlanex; +#define glClipPlanex glad_glClipPlanex +typedef void (APIENTRYP PFNGLCOLOR4XPROC)(GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI PFNGLCOLOR4XPROC glad_glColor4x; +#define glColor4x glad_glColor4x +typedef void (APIENTRYP PFNGLDEPTHRANGEXPROC)(GLfixed n, GLfixed f); +GLAPI PFNGLDEPTHRANGEXPROC glad_glDepthRangex; +#define glDepthRangex glad_glDepthRangex +typedef void (APIENTRYP PFNGLFOGXPROC)(GLenum pname, GLfixed param); +GLAPI PFNGLFOGXPROC glad_glFogx; +#define glFogx glad_glFogx +typedef void (APIENTRYP PFNGLFOGXVPROC)(GLenum pname, const GLfixed *param); +GLAPI PFNGLFOGXVPROC glad_glFogxv; +#define glFogxv glad_glFogxv +typedef void (APIENTRYP PFNGLFRUSTUMXPROC)(GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +GLAPI PFNGLFRUSTUMXPROC glad_glFrustumx; +#define glFrustumx glad_glFrustumx +typedef void (APIENTRYP PFNGLGETCLIPPLANEXPROC)(GLenum plane, GLfixed *equation); +GLAPI PFNGLGETCLIPPLANEXPROC glad_glGetClipPlanex; +#define glGetClipPlanex glad_glGetClipPlanex +typedef void (APIENTRYP PFNGLGETFIXEDVPROC)(GLenum pname, GLfixed *data); +GLAPI PFNGLGETFIXEDVPROC glad_glGetFixedv; +#define glGetFixedv glad_glGetFixedv +typedef void (APIENTRYP PFNGLGETLIGHTXVPROC)(GLenum light, GLenum pname, GLfixed *params); +GLAPI PFNGLGETLIGHTXVPROC glad_glGetLightxv; +#define glGetLightxv glad_glGetLightxv +typedef void (APIENTRYP PFNGLGETMATERIALXVPROC)(GLenum face, GLenum pname, GLfixed *params); +GLAPI PFNGLGETMATERIALXVPROC glad_glGetMaterialxv; +#define glGetMaterialxv glad_glGetMaterialxv +typedef void (APIENTRYP PFNGLGETTEXENVXVPROC)(GLenum target, GLenum pname, GLfixed *params); +GLAPI PFNGLGETTEXENVXVPROC glad_glGetTexEnvxv; +#define glGetTexEnvxv glad_glGetTexEnvxv +typedef void (APIENTRYP PFNGLGETTEXPARAMETERXVPROC)(GLenum target, GLenum pname, GLfixed *params); +GLAPI PFNGLGETTEXPARAMETERXVPROC glad_glGetTexParameterxv; +#define glGetTexParameterxv glad_glGetTexParameterxv +typedef void (APIENTRYP PFNGLLIGHTMODELXPROC)(GLenum pname, GLfixed param); +GLAPI PFNGLLIGHTMODELXPROC glad_glLightModelx; +#define glLightModelx glad_glLightModelx +typedef void (APIENTRYP PFNGLLIGHTMODELXVPROC)(GLenum pname, const GLfixed *param); +GLAPI PFNGLLIGHTMODELXVPROC glad_glLightModelxv; +#define glLightModelxv glad_glLightModelxv +typedef void (APIENTRYP PFNGLLIGHTXPROC)(GLenum light, GLenum pname, GLfixed param); +GLAPI PFNGLLIGHTXPROC glad_glLightx; +#define glLightx glad_glLightx +typedef void (APIENTRYP PFNGLLIGHTXVPROC)(GLenum light, GLenum pname, const GLfixed *params); +GLAPI PFNGLLIGHTXVPROC glad_glLightxv; +#define glLightxv glad_glLightxv +typedef void (APIENTRYP PFNGLLINEWIDTHXPROC)(GLfixed width); +GLAPI PFNGLLINEWIDTHXPROC glad_glLineWidthx; +#define glLineWidthx glad_glLineWidthx +typedef void (APIENTRYP PFNGLLOADMATRIXXPROC)(const GLfixed *m); +GLAPI PFNGLLOADMATRIXXPROC glad_glLoadMatrixx; +#define glLoadMatrixx glad_glLoadMatrixx +typedef void (APIENTRYP PFNGLMATERIALXPROC)(GLenum face, GLenum pname, GLfixed param); +GLAPI PFNGLMATERIALXPROC glad_glMaterialx; +#define glMaterialx glad_glMaterialx +typedef void (APIENTRYP PFNGLMATERIALXVPROC)(GLenum face, GLenum pname, const GLfixed *param); +GLAPI PFNGLMATERIALXVPROC glad_glMaterialxv; +#define glMaterialxv glad_glMaterialxv +typedef void (APIENTRYP PFNGLMULTMATRIXXPROC)(const GLfixed *m); +GLAPI PFNGLMULTMATRIXXPROC glad_glMultMatrixx; +#define glMultMatrixx glad_glMultMatrixx +typedef void (APIENTRYP PFNGLMULTITEXCOORD4XPROC)(GLenum texture, GLfixed s, GLfixed t, GLfixed r, GLfixed q); +GLAPI PFNGLMULTITEXCOORD4XPROC glad_glMultiTexCoord4x; +#define glMultiTexCoord4x glad_glMultiTexCoord4x +typedef void (APIENTRYP PFNGLNORMAL3XPROC)(GLfixed nx, GLfixed ny, GLfixed nz); +GLAPI PFNGLNORMAL3XPROC glad_glNormal3x; +#define glNormal3x glad_glNormal3x +typedef void (APIENTRYP PFNGLORTHOXPROC)(GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +GLAPI PFNGLORTHOXPROC glad_glOrthox; +#define glOrthox glad_glOrthox +typedef void (APIENTRYP PFNGLPOINTPARAMETERXPROC)(GLenum pname, GLfixed param); +GLAPI PFNGLPOINTPARAMETERXPROC glad_glPointParameterx; +#define glPointParameterx glad_glPointParameterx +typedef void (APIENTRYP PFNGLPOINTPARAMETERXVPROC)(GLenum pname, const GLfixed *params); +GLAPI PFNGLPOINTPARAMETERXVPROC glad_glPointParameterxv; +#define glPointParameterxv glad_glPointParameterxv +typedef void (APIENTRYP PFNGLPOINTSIZEXPROC)(GLfixed size); +GLAPI PFNGLPOINTSIZEXPROC glad_glPointSizex; +#define glPointSizex glad_glPointSizex +typedef void (APIENTRYP PFNGLPOLYGONOFFSETXPROC)(GLfixed factor, GLfixed units); +GLAPI PFNGLPOLYGONOFFSETXPROC glad_glPolygonOffsetx; +#define glPolygonOffsetx glad_glPolygonOffsetx +typedef void (APIENTRYP PFNGLROTATEXPROC)(GLfixed angle, GLfixed x, GLfixed y, GLfixed z); +GLAPI PFNGLROTATEXPROC glad_glRotatex; +#define glRotatex glad_glRotatex +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEXPROC)(GLclampx value, GLboolean invert); +GLAPI PFNGLSAMPLECOVERAGEXPROC glad_glSampleCoveragex; +#define glSampleCoveragex glad_glSampleCoveragex +typedef void (APIENTRYP PFNGLSCALEXPROC)(GLfixed x, GLfixed y, GLfixed z); +GLAPI PFNGLSCALEXPROC glad_glScalex; +#define glScalex glad_glScalex +typedef void (APIENTRYP PFNGLTEXENVXPROC)(GLenum target, GLenum pname, GLfixed param); +GLAPI PFNGLTEXENVXPROC glad_glTexEnvx; +#define glTexEnvx glad_glTexEnvx +typedef void (APIENTRYP PFNGLTEXENVXVPROC)(GLenum target, GLenum pname, const GLfixed *params); +GLAPI PFNGLTEXENVXVPROC glad_glTexEnvxv; +#define glTexEnvxv glad_glTexEnvxv +typedef void (APIENTRYP PFNGLTEXPARAMETERXPROC)(GLenum target, GLenum pname, GLfixed param); +GLAPI PFNGLTEXPARAMETERXPROC glad_glTexParameterx; +#define glTexParameterx glad_glTexParameterx +typedef void (APIENTRYP PFNGLTEXPARAMETERXVPROC)(GLenum target, GLenum pname, const GLfixed *params); +GLAPI PFNGLTEXPARAMETERXVPROC glad_glTexParameterxv; +#define glTexParameterxv glad_glTexParameterxv +typedef void (APIENTRYP PFNGLTRANSLATEXPROC)(GLfixed x, GLfixed y, GLfixed z); +GLAPI PFNGLTRANSLATEXPROC glad_glTranslatex; +#define glTranslatex glad_glTranslatex +#endif #ifndef GL_ES_VERSION_2_0 #define GL_ES_VERSION_2_0 1 GLAPI int GLAD_GL_ES_VERSION_2_0; @@ -4342,6 +4481,39 @@ GLAPI PFNGLGETINTERNALFORMATIVPROC glad_glGetInternalformativ; #define GL_STACK_OVERFLOW_KHR 0x0503 #define GL_STACK_UNDERFLOW_KHR 0x0504 #define GL_DISPLAY_LIST 0x82E7 +#define GL_NONE_OES 0 +#define GL_FRAMEBUFFER_OES 0x8D40 +#define GL_RENDERBUFFER_OES 0x8D41 +#define GL_RGBA4_OES 0x8056 +#define GL_RGB5_A1_OES 0x8057 +#define GL_RGB565_OES 0x8D62 +#define GL_DEPTH_COMPONENT16_OES 0x81A5 +#define GL_RENDERBUFFER_WIDTH_OES 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_OES 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_OES 0x8D44 +#define GL_RENDERBUFFER_RED_SIZE_OES 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_OES 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_OES 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_OES 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_OES 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_OES 0x8D55 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_OES 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_OES 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_OES 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_OES 0x8CD3 +#define GL_COLOR_ATTACHMENT0_OES 0x8CE0 +#define GL_DEPTH_ATTACHMENT_OES 0x8D00 +#define GL_STENCIL_ATTACHMENT_OES 0x8D20 +#define GL_FRAMEBUFFER_COMPLETE_OES 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES 0x8CDA +#define GL_FRAMEBUFFER_UNSUPPORTED_OES 0x8CDD +#define GL_FRAMEBUFFER_BINDING_OES 0x8CA6 +#define GL_RENDERBUFFER_BINDING_OES 0x8CA7 +#define GL_MAX_RENDERBUFFER_SIZE_OES 0x84E8 +#define GL_INVALID_FRAMEBUFFER_OPERATION_OES 0x0506 #define GL_VERTEX_ARRAY_BINDING_OES 0x85B5 #ifndef GL_ARB_debug_output #define GL_ARB_debug_output 1 @@ -4500,6 +4672,55 @@ GLAPI PFNGLGETPOINTERVKHRPROC glad_glGetPointervKHR; #define GL_KHR_debug 1 GLAPI int GLAD_GL_KHR_debug; #endif +#ifndef GL_OES_framebuffer_object +#define GL_OES_framebuffer_object 1 +GLAPI int GLAD_GL_OES_framebuffer_object; +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFEROESPROC)(GLuint renderbuffer); +GLAPI PFNGLISRENDERBUFFEROESPROC glad_glIsRenderbufferOES; +#define glIsRenderbufferOES glad_glIsRenderbufferOES +typedef void (APIENTRYP PFNGLBINDRENDERBUFFEROESPROC)(GLenum target, GLuint renderbuffer); +GLAPI PFNGLBINDRENDERBUFFEROESPROC glad_glBindRenderbufferOES; +#define glBindRenderbufferOES glad_glBindRenderbufferOES +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSOESPROC)(GLsizei n, const GLuint *renderbuffers); +GLAPI PFNGLDELETERENDERBUFFERSOESPROC glad_glDeleteRenderbuffersOES; +#define glDeleteRenderbuffersOES glad_glDeleteRenderbuffersOES +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSOESPROC)(GLsizei n, GLuint *renderbuffers); +GLAPI PFNGLGENRENDERBUFFERSOESPROC glad_glGenRenderbuffersOES; +#define glGenRenderbuffersOES glad_glGenRenderbuffersOES +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEOESPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI PFNGLRENDERBUFFERSTORAGEOESPROC glad_glRenderbufferStorageOES; +#define glRenderbufferStorageOES glad_glRenderbufferStorageOES +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVOESPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETRENDERBUFFERPARAMETERIVOESPROC glad_glGetRenderbufferParameterivOES; +#define glGetRenderbufferParameterivOES glad_glGetRenderbufferParameterivOES +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFEROESPROC)(GLuint framebuffer); +GLAPI PFNGLISFRAMEBUFFEROESPROC glad_glIsFramebufferOES; +#define glIsFramebufferOES glad_glIsFramebufferOES +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFEROESPROC)(GLenum target, GLuint framebuffer); +GLAPI PFNGLBINDFRAMEBUFFEROESPROC glad_glBindFramebufferOES; +#define glBindFramebufferOES glad_glBindFramebufferOES +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSOESPROC)(GLsizei n, const GLuint *framebuffers); +GLAPI PFNGLDELETEFRAMEBUFFERSOESPROC glad_glDeleteFramebuffersOES; +#define glDeleteFramebuffersOES glad_glDeleteFramebuffersOES +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSOESPROC)(GLsizei n, GLuint *framebuffers); +GLAPI PFNGLGENFRAMEBUFFERSOESPROC glad_glGenFramebuffersOES; +#define glGenFramebuffersOES glad_glGenFramebuffersOES +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSOESPROC)(GLenum target); +GLAPI PFNGLCHECKFRAMEBUFFERSTATUSOESPROC glad_glCheckFramebufferStatusOES; +#define glCheckFramebufferStatusOES glad_glCheckFramebufferStatusOES +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFEROESPROC)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI PFNGLFRAMEBUFFERRENDERBUFFEROESPROC glad_glFramebufferRenderbufferOES; +#define glFramebufferRenderbufferOES glad_glFramebufferRenderbufferOES +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DOESPROC)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI PFNGLFRAMEBUFFERTEXTURE2DOESPROC glad_glFramebufferTexture2DOES; +#define glFramebufferTexture2DOES glad_glFramebufferTexture2DOES +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVOESPROC)(GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVOESPROC glad_glGetFramebufferAttachmentParameterivOES; +#define glGetFramebufferAttachmentParameterivOES glad_glGetFramebufferAttachmentParameterivOES +typedef void (APIENTRYP PFNGLGENERATEMIPMAPOESPROC)(GLenum target); +GLAPI PFNGLGENERATEMIPMAPOESPROC glad_glGenerateMipmapOES; +#define glGenerateMipmapOES glad_glGenerateMipmapOES +#endif #ifndef GL_OES_vertex_array_object #define GL_OES_vertex_array_object 1 GLAPI int GLAD_GL_OES_vertex_array_object; @@ -4516,6 +4737,14 @@ typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYOESPROC)(GLuint array); GLAPI PFNGLISVERTEXARRAYOESPROC glad_glIsVertexArrayOES; #define glIsVertexArrayOES glad_glIsVertexArrayOES #endif +#ifndef GL_KHR_debug +#define GL_KHR_debug 1 +GLAPI int GLAD_GL_KHR_debug; +#endif +#ifndef GL_OES_vertex_array_object +#define GL_OES_vertex_array_object 1 +GLAPI int GLAD_GL_OES_vertex_array_object; +#endif #ifdef __cplusplus } diff --git a/vendor/glad/src/glad.c b/vendor/glad/src/glad.c index 7b968cf82..7c8b488ba 100644 --- a/vendor/glad/src/glad.c +++ b/vendor/glad/src/glad.c @@ -1,10 +1,10 @@ /* - OpenGL, OpenGL ES loader generated by glad 0.1.36 on Thu Jul 2 22:44:49 2026. + OpenGL, OpenGL ES loader generated by glad 0.1.36 on Tue Jul 7 08:27:13 2026. Language/Generator: C/C++ Specification: gl - APIs: gl=4.1, gles2=3.0 + APIs: gl=4.1, gles1=1.0, gles2=3.0 Profile: compatibility Extensions: GL_ARB_debug_output, @@ -13,6 +13,7 @@ GL_EXT_framebuffer_blit, GL_EXT_framebuffer_object, GL_KHR_debug, + GL_OES_framebuffer_object, GL_OES_vertex_array_object Loader: True Local files: False @@ -20,9 +21,9 @@ Reproducible: False Commandline: - --profile="compatibility" --api="gl=4.1,gles2=3.0" --generator="c" --spec="gl" --extensions="GL_ARB_debug_output,GL_ARB_framebuffer_object,GL_ARB_vertex_array_object,GL_EXT_framebuffer_blit,GL_EXT_framebuffer_object,GL_KHR_debug,GL_OES_vertex_array_object" + --profile="compatibility" --api="gl=4.1,gles1=1.0,gles2=3.0" --generator="c" --spec="gl" --extensions="GL_ARB_debug_output,GL_ARB_framebuffer_object,GL_ARB_vertex_array_object,GL_EXT_framebuffer_blit,GL_EXT_framebuffer_object,GL_KHR_debug,GL_OES_framebuffer_object,GL_OES_vertex_array_object" Online: - https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D4.1&api=gles2%3D3.0&extensions=GL_ARB_debug_output&extensions=GL_ARB_framebuffer_object&extensions=GL_ARB_vertex_array_object&extensions=GL_EXT_framebuffer_blit&extensions=GL_EXT_framebuffer_object&extensions=GL_KHR_debug&extensions=GL_OES_vertex_array_object + https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D4.1&api=gles1%3D1.0&api=gles2%3D3.0&extensions=GL_ARB_debug_output&extensions=GL_ARB_framebuffer_object&extensions=GL_ARB_vertex_array_object&extensions=GL_EXT_framebuffer_blit&extensions=GL_EXT_framebuffer_object&extensions=GL_KHR_debug&extensions=GL_OES_framebuffer_object&extensions=GL_OES_vertex_array_object */ #include @@ -275,12 +276,14 @@ int GLAD_GL_VERSION_3_2 = 0; int GLAD_GL_VERSION_3_3 = 0; int GLAD_GL_VERSION_4_0 = 0; int GLAD_GL_VERSION_4_1 = 0; +int GLAD_GL_VERSION_ES_CM_1_0 = 0; int GLAD_GL_ES_VERSION_2_0 = 0; int GLAD_GL_ES_VERSION_3_0 = 0; PFNGLACCUMPROC glad_glAccum = NULL; PFNGLACTIVESHADERPROGRAMPROC glad_glActiveShaderProgram = NULL; PFNGLACTIVETEXTUREPROC glad_glActiveTexture = NULL; PFNGLALPHAFUNCPROC glad_glAlphaFunc = NULL; +PFNGLALPHAFUNCXPROC glad_glAlphaFuncx = NULL; PFNGLARETEXTURESRESIDENTPROC glad_glAreTexturesResident = NULL; PFNGLARRAYELEMENTPROC glad_glArrayElement = NULL; PFNGLATTACHSHADERPROC glad_glAttachShader = NULL; @@ -326,13 +329,17 @@ PFNGLCLEARBUFFERFVPROC glad_glClearBufferfv = NULL; PFNGLCLEARBUFFERIVPROC glad_glClearBufferiv = NULL; PFNGLCLEARBUFFERUIVPROC glad_glClearBufferuiv = NULL; PFNGLCLEARCOLORPROC glad_glClearColor = NULL; +PFNGLCLEARCOLORXPROC glad_glClearColorx = NULL; PFNGLCLEARDEPTHPROC glad_glClearDepth = NULL; PFNGLCLEARDEPTHFPROC glad_glClearDepthf = NULL; +PFNGLCLEARDEPTHXPROC glad_glClearDepthx = NULL; PFNGLCLEARINDEXPROC glad_glClearIndex = NULL; PFNGLCLEARSTENCILPROC glad_glClearStencil = NULL; PFNGLCLIENTACTIVETEXTUREPROC glad_glClientActiveTexture = NULL; PFNGLCLIENTWAITSYNCPROC glad_glClientWaitSync = NULL; PFNGLCLIPPLANEPROC glad_glClipPlane = NULL; +PFNGLCLIPPLANEFPROC glad_glClipPlanef = NULL; +PFNGLCLIPPLANEXPROC glad_glClipPlanex = NULL; PFNGLCOLOR3BPROC glad_glColor3b = NULL; PFNGLCOLOR3BVPROC glad_glColor3bv = NULL; PFNGLCOLOR3DPROC glad_glColor3d = NULL; @@ -365,6 +372,7 @@ PFNGLCOLOR4UIPROC glad_glColor4ui = NULL; PFNGLCOLOR4UIVPROC glad_glColor4uiv = NULL; PFNGLCOLOR4USPROC glad_glColor4us = NULL; PFNGLCOLOR4USVPROC glad_glColor4usv = NULL; +PFNGLCOLOR4XPROC glad_glColor4x = NULL; PFNGLCOLORMASKPROC glad_glColorMask = NULL; PFNGLCOLORMASKIPROC glad_glColorMaski = NULL; PFNGLCOLORMATERIALPROC glad_glColorMaterial = NULL; @@ -410,6 +418,7 @@ PFNGLDEPTHRANGEPROC glad_glDepthRange = NULL; PFNGLDEPTHRANGEARRAYVPROC glad_glDepthRangeArrayv = NULL; PFNGLDEPTHRANGEINDEXEDPROC glad_glDepthRangeIndexed = NULL; PFNGLDEPTHRANGEFPROC glad_glDepthRangef = NULL; +PFNGLDEPTHRANGEXPROC glad_glDepthRangex = NULL; PFNGLDETACHSHADERPROC glad_glDetachShader = NULL; PFNGLDISABLEPROC glad_glDisable = NULL; PFNGLDISABLECLIENTSTATEPROC glad_glDisableClientState = NULL; @@ -469,6 +478,8 @@ PFNGLFOGFPROC glad_glFogf = NULL; PFNGLFOGFVPROC glad_glFogfv = NULL; PFNGLFOGIPROC glad_glFogi = NULL; PFNGLFOGIVPROC glad_glFogiv = NULL; +PFNGLFOGXPROC glad_glFogx = NULL; +PFNGLFOGXVPROC glad_glFogxv = NULL; PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer = NULL; PFNGLFRAMEBUFFERTEXTUREPROC glad_glFramebufferTexture = NULL; PFNGLFRAMEBUFFERTEXTURE1DPROC glad_glFramebufferTexture1D = NULL; @@ -477,6 +488,8 @@ PFNGLFRAMEBUFFERTEXTURE3DPROC glad_glFramebufferTexture3D = NULL; PFNGLFRAMEBUFFERTEXTURELAYERPROC glad_glFramebufferTextureLayer = NULL; PFNGLFRONTFACEPROC glad_glFrontFace = NULL; PFNGLFRUSTUMPROC glad_glFrustum = NULL; +PFNGLFRUSTUMFPROC glad_glFrustumf = NULL; +PFNGLFRUSTUMXPROC glad_glFrustumx = NULL; PFNGLGENBUFFERSPROC glad_glGenBuffers = NULL; PFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers = NULL; PFNGLGENLISTSPROC glad_glGenLists = NULL; @@ -506,10 +519,13 @@ PFNGLGETBUFFERPARAMETERIVPROC glad_glGetBufferParameteriv = NULL; PFNGLGETBUFFERPOINTERVPROC glad_glGetBufferPointerv = NULL; PFNGLGETBUFFERSUBDATAPROC glad_glGetBufferSubData = NULL; PFNGLGETCLIPPLANEPROC glad_glGetClipPlane = NULL; +PFNGLGETCLIPPLANEFPROC glad_glGetClipPlanef = NULL; +PFNGLGETCLIPPLANEXPROC glad_glGetClipPlanex = NULL; PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_glGetCompressedTexImage = NULL; PFNGLGETDOUBLEI_VPROC glad_glGetDoublei_v = NULL; PFNGLGETDOUBLEVPROC glad_glGetDoublev = NULL; PFNGLGETERRORPROC glad_glGetError = NULL; +PFNGLGETFIXEDVPROC glad_glGetFixedv = NULL; PFNGLGETFLOATI_VPROC glad_glGetFloati_v = NULL; PFNGLGETFLOATVPROC glad_glGetFloatv = NULL; PFNGLGETFRAGDATAINDEXPROC glad_glGetFragDataIndex = NULL; @@ -522,11 +538,13 @@ PFNGLGETINTEGERVPROC glad_glGetIntegerv = NULL; PFNGLGETINTERNALFORMATIVPROC glad_glGetInternalformativ = NULL; PFNGLGETLIGHTFVPROC glad_glGetLightfv = NULL; PFNGLGETLIGHTIVPROC glad_glGetLightiv = NULL; +PFNGLGETLIGHTXVPROC glad_glGetLightxv = NULL; PFNGLGETMAPDVPROC glad_glGetMapdv = NULL; PFNGLGETMAPFVPROC glad_glGetMapfv = NULL; PFNGLGETMAPIVPROC glad_glGetMapiv = NULL; PFNGLGETMATERIALFVPROC glad_glGetMaterialfv = NULL; PFNGLGETMATERIALIVPROC glad_glGetMaterialiv = NULL; +PFNGLGETMATERIALXVPROC glad_glGetMaterialxv = NULL; PFNGLGETMULTISAMPLEFVPROC glad_glGetMultisamplefv = NULL; PFNGLGETPIXELMAPFVPROC glad_glGetPixelMapfv = NULL; PFNGLGETPIXELMAPUIVPROC glad_glGetPixelMapuiv = NULL; @@ -561,6 +579,7 @@ PFNGLGETSUBROUTINEUNIFORMLOCATIONPROC glad_glGetSubroutineUniformLocation = NULL PFNGLGETSYNCIVPROC glad_glGetSynciv = NULL; PFNGLGETTEXENVFVPROC glad_glGetTexEnvfv = NULL; PFNGLGETTEXENVIVPROC glad_glGetTexEnviv = NULL; +PFNGLGETTEXENVXVPROC glad_glGetTexEnvxv = NULL; PFNGLGETTEXGENDVPROC glad_glGetTexGendv = NULL; PFNGLGETTEXGENFVPROC glad_glGetTexGenfv = NULL; PFNGLGETTEXGENIVPROC glad_glGetTexGeniv = NULL; @@ -571,6 +590,7 @@ PFNGLGETTEXPARAMETERIIVPROC glad_glGetTexParameterIiv = NULL; PFNGLGETTEXPARAMETERIUIVPROC glad_glGetTexParameterIuiv = NULL; PFNGLGETTEXPARAMETERFVPROC glad_glGetTexParameterfv = NULL; PFNGLGETTEXPARAMETERIVPROC glad_glGetTexParameteriv = NULL; +PFNGLGETTEXPARAMETERXVPROC glad_glGetTexParameterxv = NULL; PFNGLGETTRANSFORMFEEDBACKVARYINGPROC glad_glGetTransformFeedbackVarying = NULL; PFNGLGETUNIFORMBLOCKINDEXPROC glad_glGetUniformBlockIndex = NULL; PFNGLGETUNIFORMINDICESPROC glad_glGetUniformIndices = NULL; @@ -623,17 +643,23 @@ PFNGLLIGHTMODELFPROC glad_glLightModelf = NULL; PFNGLLIGHTMODELFVPROC glad_glLightModelfv = NULL; PFNGLLIGHTMODELIPROC glad_glLightModeli = NULL; PFNGLLIGHTMODELIVPROC glad_glLightModeliv = NULL; +PFNGLLIGHTMODELXPROC glad_glLightModelx = NULL; +PFNGLLIGHTMODELXVPROC glad_glLightModelxv = NULL; PFNGLLIGHTFPROC glad_glLightf = NULL; PFNGLLIGHTFVPROC glad_glLightfv = NULL; PFNGLLIGHTIPROC glad_glLighti = NULL; PFNGLLIGHTIVPROC glad_glLightiv = NULL; +PFNGLLIGHTXPROC glad_glLightx = NULL; +PFNGLLIGHTXVPROC glad_glLightxv = NULL; PFNGLLINESTIPPLEPROC glad_glLineStipple = NULL; PFNGLLINEWIDTHPROC glad_glLineWidth = NULL; +PFNGLLINEWIDTHXPROC glad_glLineWidthx = NULL; PFNGLLINKPROGRAMPROC glad_glLinkProgram = NULL; PFNGLLISTBASEPROC glad_glListBase = NULL; PFNGLLOADIDENTITYPROC glad_glLoadIdentity = NULL; PFNGLLOADMATRIXDPROC glad_glLoadMatrixd = NULL; PFNGLLOADMATRIXFPROC glad_glLoadMatrixf = NULL; +PFNGLLOADMATRIXXPROC glad_glLoadMatrixx = NULL; PFNGLLOADNAMEPROC glad_glLoadName = NULL; PFNGLLOADTRANSPOSEMATRIXDPROC glad_glLoadTransposeMatrixd = NULL; PFNGLLOADTRANSPOSEMATRIXFPROC glad_glLoadTransposeMatrixf = NULL; @@ -652,10 +678,13 @@ PFNGLMATERIALFPROC glad_glMaterialf = NULL; PFNGLMATERIALFVPROC glad_glMaterialfv = NULL; PFNGLMATERIALIPROC glad_glMateriali = NULL; PFNGLMATERIALIVPROC glad_glMaterialiv = NULL; +PFNGLMATERIALXPROC glad_glMaterialx = NULL; +PFNGLMATERIALXVPROC glad_glMaterialxv = NULL; PFNGLMATRIXMODEPROC glad_glMatrixMode = NULL; PFNGLMINSAMPLESHADINGPROC glad_glMinSampleShading = NULL; PFNGLMULTMATRIXDPROC glad_glMultMatrixd = NULL; PFNGLMULTMATRIXFPROC glad_glMultMatrixf = NULL; +PFNGLMULTMATRIXXPROC glad_glMultMatrixx = NULL; PFNGLMULTTRANSPOSEMATRIXDPROC glad_glMultTransposeMatrixd = NULL; PFNGLMULTTRANSPOSEMATRIXFPROC glad_glMultTransposeMatrixf = NULL; PFNGLMULTIDRAWARRAYSPROC glad_glMultiDrawArrays = NULL; @@ -693,6 +722,7 @@ PFNGLMULTITEXCOORD4IPROC glad_glMultiTexCoord4i = NULL; PFNGLMULTITEXCOORD4IVPROC glad_glMultiTexCoord4iv = NULL; PFNGLMULTITEXCOORD4SPROC glad_glMultiTexCoord4s = NULL; PFNGLMULTITEXCOORD4SVPROC glad_glMultiTexCoord4sv = NULL; +PFNGLMULTITEXCOORD4XPROC glad_glMultiTexCoord4x = NULL; PFNGLMULTITEXCOORDP1UIPROC glad_glMultiTexCoordP1ui = NULL; PFNGLMULTITEXCOORDP1UIVPROC glad_glMultiTexCoordP1uiv = NULL; PFNGLMULTITEXCOORDP2UIPROC glad_glMultiTexCoordP2ui = NULL; @@ -712,10 +742,13 @@ PFNGLNORMAL3IPROC glad_glNormal3i = NULL; PFNGLNORMAL3IVPROC glad_glNormal3iv = NULL; PFNGLNORMAL3SPROC glad_glNormal3s = NULL; PFNGLNORMAL3SVPROC glad_glNormal3sv = NULL; +PFNGLNORMAL3XPROC glad_glNormal3x = NULL; PFNGLNORMALP3UIPROC glad_glNormalP3ui = NULL; PFNGLNORMALP3UIVPROC glad_glNormalP3uiv = NULL; PFNGLNORMALPOINTERPROC glad_glNormalPointer = NULL; PFNGLORTHOPROC glad_glOrtho = NULL; +PFNGLORTHOFPROC glad_glOrthof = NULL; +PFNGLORTHOXPROC glad_glOrthox = NULL; PFNGLPASSTHROUGHPROC glad_glPassThrough = NULL; PFNGLPATCHPARAMETERFVPROC glad_glPatchParameterfv = NULL; PFNGLPATCHPARAMETERIPROC glad_glPatchParameteri = NULL; @@ -732,9 +765,13 @@ PFNGLPOINTPARAMETERFPROC glad_glPointParameterf = NULL; PFNGLPOINTPARAMETERFVPROC glad_glPointParameterfv = NULL; PFNGLPOINTPARAMETERIPROC glad_glPointParameteri = NULL; PFNGLPOINTPARAMETERIVPROC glad_glPointParameteriv = NULL; +PFNGLPOINTPARAMETERXPROC glad_glPointParameterx = NULL; +PFNGLPOINTPARAMETERXVPROC glad_glPointParameterxv = NULL; PFNGLPOINTSIZEPROC glad_glPointSize = NULL; +PFNGLPOINTSIZEXPROC glad_glPointSizex = NULL; PFNGLPOLYGONMODEPROC glad_glPolygonMode = NULL; PFNGLPOLYGONOFFSETPROC glad_glPolygonOffset = NULL; +PFNGLPOLYGONOFFSETXPROC glad_glPolygonOffsetx = NULL; PFNGLPOLYGONSTIPPLEPROC glad_glPolygonStipple = NULL; PFNGLPOPATTRIBPROC glad_glPopAttrib = NULL; PFNGLPOPCLIENTATTRIBPROC glad_glPopClientAttrib = NULL; @@ -841,7 +878,9 @@ PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample = PFNGLRESUMETRANSFORMFEEDBACKPROC glad_glResumeTransformFeedback = NULL; PFNGLROTATEDPROC glad_glRotated = NULL; PFNGLROTATEFPROC glad_glRotatef = NULL; +PFNGLROTATEXPROC glad_glRotatex = NULL; PFNGLSAMPLECOVERAGEPROC glad_glSampleCoverage = NULL; +PFNGLSAMPLECOVERAGEXPROC glad_glSampleCoveragex = NULL; PFNGLSAMPLEMASKIPROC glad_glSampleMaski = NULL; PFNGLSAMPLERPARAMETERIIVPROC glad_glSamplerParameterIiv = NULL; PFNGLSAMPLERPARAMETERIUIVPROC glad_glSamplerParameterIuiv = NULL; @@ -851,6 +890,7 @@ PFNGLSAMPLERPARAMETERIPROC glad_glSamplerParameteri = NULL; PFNGLSAMPLERPARAMETERIVPROC glad_glSamplerParameteriv = NULL; PFNGLSCALEDPROC glad_glScaled = NULL; PFNGLSCALEFPROC glad_glScalef = NULL; +PFNGLSCALEXPROC glad_glScalex = NULL; PFNGLSCISSORPROC glad_glScissor = NULL; PFNGLSCISSORARRAYVPROC glad_glScissorArrayv = NULL; PFNGLSCISSORINDEXEDPROC glad_glScissorIndexed = NULL; @@ -930,6 +970,8 @@ PFNGLTEXENVFPROC glad_glTexEnvf = NULL; PFNGLTEXENVFVPROC glad_glTexEnvfv = NULL; PFNGLTEXENVIPROC glad_glTexEnvi = NULL; PFNGLTEXENVIVPROC glad_glTexEnviv = NULL; +PFNGLTEXENVXPROC glad_glTexEnvx = NULL; +PFNGLTEXENVXVPROC glad_glTexEnvxv = NULL; PFNGLTEXGENDPROC glad_glTexGend = NULL; PFNGLTEXGENDVPROC glad_glTexGendv = NULL; PFNGLTEXGENFPROC glad_glTexGenf = NULL; @@ -947,6 +989,8 @@ PFNGLTEXPARAMETERFPROC glad_glTexParameterf = NULL; PFNGLTEXPARAMETERFVPROC glad_glTexParameterfv = NULL; PFNGLTEXPARAMETERIPROC glad_glTexParameteri = NULL; PFNGLTEXPARAMETERIVPROC glad_glTexParameteriv = NULL; +PFNGLTEXPARAMETERXPROC glad_glTexParameterx = NULL; +PFNGLTEXPARAMETERXVPROC glad_glTexParameterxv = NULL; PFNGLTEXSTORAGE2DPROC glad_glTexStorage2D = NULL; PFNGLTEXSTORAGE3DPROC glad_glTexStorage3D = NULL; PFNGLTEXSUBIMAGE1DPROC glad_glTexSubImage1D = NULL; @@ -955,6 +999,7 @@ PFNGLTEXSUBIMAGE3DPROC glad_glTexSubImage3D = NULL; PFNGLTRANSFORMFEEDBACKVARYINGSPROC glad_glTransformFeedbackVaryings = NULL; PFNGLTRANSLATEDPROC glad_glTranslated = NULL; PFNGLTRANSLATEFPROC glad_glTranslatef = NULL; +PFNGLTRANSLATEXPROC glad_glTranslatex = NULL; PFNGLUNIFORM1DPROC glad_glUniform1d = NULL; PFNGLUNIFORM1DVPROC glad_glUniform1dv = NULL; PFNGLUNIFORM1FPROC glad_glUniform1f = NULL; @@ -1146,6 +1191,7 @@ int GLAD_GL_ARB_vertex_array_object = 0; int GLAD_GL_EXT_framebuffer_blit = 0; int GLAD_GL_EXT_framebuffer_object = 0; int GLAD_GL_KHR_debug = 0; +int GLAD_GL_OES_framebuffer_object = 0; int GLAD_GL_OES_vertex_array_object = 0; PFNGLDEBUGMESSAGECONTROLARBPROC glad_glDebugMessageControlARB = NULL; PFNGLDEBUGMESSAGEINSERTARBPROC glad_glDebugMessageInsertARB = NULL; @@ -1190,6 +1236,21 @@ PFNGLGETOBJECTLABELKHRPROC glad_glGetObjectLabelKHR = NULL; PFNGLOBJECTPTRLABELKHRPROC glad_glObjectPtrLabelKHR = NULL; PFNGLGETOBJECTPTRLABELKHRPROC glad_glGetObjectPtrLabelKHR = NULL; PFNGLGETPOINTERVKHRPROC glad_glGetPointervKHR = NULL; +PFNGLISRENDERBUFFEROESPROC glad_glIsRenderbufferOES = NULL; +PFNGLBINDRENDERBUFFEROESPROC glad_glBindRenderbufferOES = NULL; +PFNGLDELETERENDERBUFFERSOESPROC glad_glDeleteRenderbuffersOES = NULL; +PFNGLGENRENDERBUFFERSOESPROC glad_glGenRenderbuffersOES = NULL; +PFNGLRENDERBUFFERSTORAGEOESPROC glad_glRenderbufferStorageOES = NULL; +PFNGLGETRENDERBUFFERPARAMETERIVOESPROC glad_glGetRenderbufferParameterivOES = NULL; +PFNGLISFRAMEBUFFEROESPROC glad_glIsFramebufferOES = NULL; +PFNGLBINDFRAMEBUFFEROESPROC glad_glBindFramebufferOES = NULL; +PFNGLDELETEFRAMEBUFFERSOESPROC glad_glDeleteFramebuffersOES = NULL; +PFNGLGENFRAMEBUFFERSOESPROC glad_glGenFramebuffersOES = NULL; +PFNGLCHECKFRAMEBUFFERSTATUSOESPROC glad_glCheckFramebufferStatusOES = NULL; +PFNGLFRAMEBUFFERRENDERBUFFEROESPROC glad_glFramebufferRenderbufferOES = NULL; +PFNGLFRAMEBUFFERTEXTURE2DOESPROC glad_glFramebufferTexture2DOES = NULL; +PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVOESPROC glad_glGetFramebufferAttachmentParameterivOES = NULL; +PFNGLGENERATEMIPMAPOESPROC glad_glGenerateMipmapOES = NULL; PFNGLBINDVERTEXARRAYOESPROC glad_glBindVertexArrayOES = NULL; PFNGLDELETEVERTEXARRAYSOESPROC glad_glDeleteVertexArraysOES = NULL; PFNGLGENVERTEXARRAYSOESPROC glad_glGenVertexArraysOES = NULL; @@ -2283,6 +2344,245 @@ int gladLoadGLLoader(GLADloadproc load) { return GLVersion.major != 0 || GLVersion.minor != 0; } +static void load_GL_VERSION_ES_CM_1_0(GLADloadproc load) { + if(!GLAD_GL_VERSION_ES_CM_1_0) return; + glad_glAlphaFunc = (PFNGLALPHAFUNCPROC)load("glAlphaFunc"); + glad_glClearColor = (PFNGLCLEARCOLORPROC)load("glClearColor"); + glad_glClearDepthf = (PFNGLCLEARDEPTHFPROC)load("glClearDepthf"); + glad_glClipPlanef = (PFNGLCLIPPLANEFPROC)load("glClipPlanef"); + glad_glColor4f = (PFNGLCOLOR4FPROC)load("glColor4f"); + glad_glDepthRangef = (PFNGLDEPTHRANGEFPROC)load("glDepthRangef"); + glad_glFogf = (PFNGLFOGFPROC)load("glFogf"); + glad_glFogfv = (PFNGLFOGFVPROC)load("glFogfv"); + glad_glFrustumf = (PFNGLFRUSTUMFPROC)load("glFrustumf"); + glad_glGetClipPlanef = (PFNGLGETCLIPPLANEFPROC)load("glGetClipPlanef"); + glad_glGetFloatv = (PFNGLGETFLOATVPROC)load("glGetFloatv"); + glad_glGetLightfv = (PFNGLGETLIGHTFVPROC)load("glGetLightfv"); + glad_glGetMaterialfv = (PFNGLGETMATERIALFVPROC)load("glGetMaterialfv"); + glad_glGetTexEnvfv = (PFNGLGETTEXENVFVPROC)load("glGetTexEnvfv"); + glad_glGetTexParameterfv = (PFNGLGETTEXPARAMETERFVPROC)load("glGetTexParameterfv"); + glad_glLightModelf = (PFNGLLIGHTMODELFPROC)load("glLightModelf"); + glad_glLightModelfv = (PFNGLLIGHTMODELFVPROC)load("glLightModelfv"); + glad_glLightf = (PFNGLLIGHTFPROC)load("glLightf"); + glad_glLightfv = (PFNGLLIGHTFVPROC)load("glLightfv"); + glad_glLineWidth = (PFNGLLINEWIDTHPROC)load("glLineWidth"); + glad_glLoadMatrixf = (PFNGLLOADMATRIXFPROC)load("glLoadMatrixf"); + glad_glMaterialf = (PFNGLMATERIALFPROC)load("glMaterialf"); + glad_glMaterialfv = (PFNGLMATERIALFVPROC)load("glMaterialfv"); + glad_glMultMatrixf = (PFNGLMULTMATRIXFPROC)load("glMultMatrixf"); + glad_glMultiTexCoord4f = (PFNGLMULTITEXCOORD4FPROC)load("glMultiTexCoord4f"); + glad_glNormal3f = (PFNGLNORMAL3FPROC)load("glNormal3f"); + glad_glOrthof = (PFNGLORTHOFPROC)load("glOrthof"); + glad_glPointParameterf = (PFNGLPOINTPARAMETERFPROC)load("glPointParameterf"); + glad_glPointParameterfv = (PFNGLPOINTPARAMETERFVPROC)load("glPointParameterfv"); + glad_glPointSize = (PFNGLPOINTSIZEPROC)load("glPointSize"); + glad_glPolygonOffset = (PFNGLPOLYGONOFFSETPROC)load("glPolygonOffset"); + glad_glRotatef = (PFNGLROTATEFPROC)load("glRotatef"); + glad_glScalef = (PFNGLSCALEFPROC)load("glScalef"); + glad_glTexEnvf = (PFNGLTEXENVFPROC)load("glTexEnvf"); + glad_glTexEnvfv = (PFNGLTEXENVFVPROC)load("glTexEnvfv"); + glad_glTexParameterf = (PFNGLTEXPARAMETERFPROC)load("glTexParameterf"); + glad_glTexParameterfv = (PFNGLTEXPARAMETERFVPROC)load("glTexParameterfv"); + glad_glTranslatef = (PFNGLTRANSLATEFPROC)load("glTranslatef"); + glad_glActiveTexture = (PFNGLACTIVETEXTUREPROC)load("glActiveTexture"); + glad_glAlphaFuncx = (PFNGLALPHAFUNCXPROC)load("glAlphaFuncx"); + glad_glBindBuffer = (PFNGLBINDBUFFERPROC)load("glBindBuffer"); + glad_glBindTexture = (PFNGLBINDTEXTUREPROC)load("glBindTexture"); + glad_glBlendFunc = (PFNGLBLENDFUNCPROC)load("glBlendFunc"); + glad_glBufferData = (PFNGLBUFFERDATAPROC)load("glBufferData"); + glad_glBufferSubData = (PFNGLBUFFERSUBDATAPROC)load("glBufferSubData"); + glad_glClear = (PFNGLCLEARPROC)load("glClear"); + glad_glClearColorx = (PFNGLCLEARCOLORXPROC)load("glClearColorx"); + glad_glClearDepthx = (PFNGLCLEARDEPTHXPROC)load("glClearDepthx"); + glad_glClearStencil = (PFNGLCLEARSTENCILPROC)load("glClearStencil"); + glad_glClientActiveTexture = (PFNGLCLIENTACTIVETEXTUREPROC)load("glClientActiveTexture"); + glad_glClipPlanex = (PFNGLCLIPPLANEXPROC)load("glClipPlanex"); + glad_glColor4ub = (PFNGLCOLOR4UBPROC)load("glColor4ub"); + glad_glColor4x = (PFNGLCOLOR4XPROC)load("glColor4x"); + glad_glColorMask = (PFNGLCOLORMASKPROC)load("glColorMask"); + glad_glColorPointer = (PFNGLCOLORPOINTERPROC)load("glColorPointer"); + glad_glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)load("glCompressedTexImage2D"); + glad_glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)load("glCompressedTexSubImage2D"); + glad_glCopyTexImage2D = (PFNGLCOPYTEXIMAGE2DPROC)load("glCopyTexImage2D"); + glad_glCopyTexSubImage2D = (PFNGLCOPYTEXSUBIMAGE2DPROC)load("glCopyTexSubImage2D"); + glad_glCullFace = (PFNGLCULLFACEPROC)load("glCullFace"); + glad_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)load("glDeleteBuffers"); + glad_glDeleteTextures = (PFNGLDELETETEXTURESPROC)load("glDeleteTextures"); + glad_glDepthFunc = (PFNGLDEPTHFUNCPROC)load("glDepthFunc"); + glad_glDepthMask = (PFNGLDEPTHMASKPROC)load("glDepthMask"); + glad_glDepthRangex = (PFNGLDEPTHRANGEXPROC)load("glDepthRangex"); + glad_glDisable = (PFNGLDISABLEPROC)load("glDisable"); + glad_glDisableClientState = (PFNGLDISABLECLIENTSTATEPROC)load("glDisableClientState"); + glad_glDrawArrays = (PFNGLDRAWARRAYSPROC)load("glDrawArrays"); + glad_glDrawElements = (PFNGLDRAWELEMENTSPROC)load("glDrawElements"); + glad_glEnable = (PFNGLENABLEPROC)load("glEnable"); + glad_glEnableClientState = (PFNGLENABLECLIENTSTATEPROC)load("glEnableClientState"); + glad_glFinish = (PFNGLFINISHPROC)load("glFinish"); + glad_glFlush = (PFNGLFLUSHPROC)load("glFlush"); + glad_glFogx = (PFNGLFOGXPROC)load("glFogx"); + glad_glFogxv = (PFNGLFOGXVPROC)load("glFogxv"); + glad_glFrontFace = (PFNGLFRONTFACEPROC)load("glFrontFace"); + glad_glFrustumx = (PFNGLFRUSTUMXPROC)load("glFrustumx"); + glad_glGetBooleanv = (PFNGLGETBOOLEANVPROC)load("glGetBooleanv"); + glad_glGetBufferParameteriv = (PFNGLGETBUFFERPARAMETERIVPROC)load("glGetBufferParameteriv"); + glad_glGetClipPlanex = (PFNGLGETCLIPPLANEXPROC)load("glGetClipPlanex"); + glad_glGenBuffers = (PFNGLGENBUFFERSPROC)load("glGenBuffers"); + glad_glGenTextures = (PFNGLGENTEXTURESPROC)load("glGenTextures"); + glad_glGetError = (PFNGLGETERRORPROC)load("glGetError"); + glad_glGetFixedv = (PFNGLGETFIXEDVPROC)load("glGetFixedv"); + glad_glGetIntegerv = (PFNGLGETINTEGERVPROC)load("glGetIntegerv"); + glad_glGetLightxv = (PFNGLGETLIGHTXVPROC)load("glGetLightxv"); + glad_glGetMaterialxv = (PFNGLGETMATERIALXVPROC)load("glGetMaterialxv"); + glad_glGetPointerv = (PFNGLGETPOINTERVPROC)load("glGetPointerv"); + glad_glGetString = (PFNGLGETSTRINGPROC)load("glGetString"); + glad_glGetTexEnviv = (PFNGLGETTEXENVIVPROC)load("glGetTexEnviv"); + glad_glGetTexEnvxv = (PFNGLGETTEXENVXVPROC)load("glGetTexEnvxv"); + glad_glGetTexParameteriv = (PFNGLGETTEXPARAMETERIVPROC)load("glGetTexParameteriv"); + glad_glGetTexParameterxv = (PFNGLGETTEXPARAMETERXVPROC)load("glGetTexParameterxv"); + glad_glHint = (PFNGLHINTPROC)load("glHint"); + glad_glIsBuffer = (PFNGLISBUFFERPROC)load("glIsBuffer"); + glad_glIsEnabled = (PFNGLISENABLEDPROC)load("glIsEnabled"); + glad_glIsTexture = (PFNGLISTEXTUREPROC)load("glIsTexture"); + glad_glLightModelx = (PFNGLLIGHTMODELXPROC)load("glLightModelx"); + glad_glLightModelxv = (PFNGLLIGHTMODELXVPROC)load("glLightModelxv"); + glad_glLightx = (PFNGLLIGHTXPROC)load("glLightx"); + glad_glLightxv = (PFNGLLIGHTXVPROC)load("glLightxv"); + glad_glLineWidthx = (PFNGLLINEWIDTHXPROC)load("glLineWidthx"); + glad_glLoadIdentity = (PFNGLLOADIDENTITYPROC)load("glLoadIdentity"); + glad_glLoadMatrixx = (PFNGLLOADMATRIXXPROC)load("glLoadMatrixx"); + glad_glLogicOp = (PFNGLLOGICOPPROC)load("glLogicOp"); + glad_glMaterialx = (PFNGLMATERIALXPROC)load("glMaterialx"); + glad_glMaterialxv = (PFNGLMATERIALXVPROC)load("glMaterialxv"); + glad_glMatrixMode = (PFNGLMATRIXMODEPROC)load("glMatrixMode"); + glad_glMultMatrixx = (PFNGLMULTMATRIXXPROC)load("glMultMatrixx"); + glad_glMultiTexCoord4x = (PFNGLMULTITEXCOORD4XPROC)load("glMultiTexCoord4x"); + glad_glNormal3x = (PFNGLNORMAL3XPROC)load("glNormal3x"); + glad_glNormalPointer = (PFNGLNORMALPOINTERPROC)load("glNormalPointer"); + glad_glOrthox = (PFNGLORTHOXPROC)load("glOrthox"); + glad_glPixelStorei = (PFNGLPIXELSTOREIPROC)load("glPixelStorei"); + glad_glPointParameterx = (PFNGLPOINTPARAMETERXPROC)load("glPointParameterx"); + glad_glPointParameterxv = (PFNGLPOINTPARAMETERXVPROC)load("glPointParameterxv"); + glad_glPointSizex = (PFNGLPOINTSIZEXPROC)load("glPointSizex"); + glad_glPolygonOffsetx = (PFNGLPOLYGONOFFSETXPROC)load("glPolygonOffsetx"); + glad_glPopMatrix = (PFNGLPOPMATRIXPROC)load("glPopMatrix"); + glad_glPushMatrix = (PFNGLPUSHMATRIXPROC)load("glPushMatrix"); + glad_glReadPixels = (PFNGLREADPIXELSPROC)load("glReadPixels"); + glad_glRotatex = (PFNGLROTATEXPROC)load("glRotatex"); + glad_glSampleCoverage = (PFNGLSAMPLECOVERAGEPROC)load("glSampleCoverage"); + glad_glSampleCoveragex = (PFNGLSAMPLECOVERAGEXPROC)load("glSampleCoveragex"); + glad_glScalex = (PFNGLSCALEXPROC)load("glScalex"); + glad_glScissor = (PFNGLSCISSORPROC)load("glScissor"); + glad_glShadeModel = (PFNGLSHADEMODELPROC)load("glShadeModel"); + glad_glStencilFunc = (PFNGLSTENCILFUNCPROC)load("glStencilFunc"); + glad_glStencilMask = (PFNGLSTENCILMASKPROC)load("glStencilMask"); + glad_glStencilOp = (PFNGLSTENCILOPPROC)load("glStencilOp"); + glad_glTexCoordPointer = (PFNGLTEXCOORDPOINTERPROC)load("glTexCoordPointer"); + glad_glTexEnvi = (PFNGLTEXENVIPROC)load("glTexEnvi"); + glad_glTexEnvx = (PFNGLTEXENVXPROC)load("glTexEnvx"); + glad_glTexEnviv = (PFNGLTEXENVIVPROC)load("glTexEnviv"); + glad_glTexEnvxv = (PFNGLTEXENVXVPROC)load("glTexEnvxv"); + glad_glTexImage2D = (PFNGLTEXIMAGE2DPROC)load("glTexImage2D"); + glad_glTexParameteri = (PFNGLTEXPARAMETERIPROC)load("glTexParameteri"); + glad_glTexParameterx = (PFNGLTEXPARAMETERXPROC)load("glTexParameterx"); + glad_glTexParameteriv = (PFNGLTEXPARAMETERIVPROC)load("glTexParameteriv"); + glad_glTexParameterxv = (PFNGLTEXPARAMETERXVPROC)load("glTexParameterxv"); + glad_glTexSubImage2D = (PFNGLTEXSUBIMAGE2DPROC)load("glTexSubImage2D"); + glad_glTranslatex = (PFNGLTRANSLATEXPROC)load("glTranslatex"); + glad_glVertexPointer = (PFNGLVERTEXPOINTERPROC)load("glVertexPointer"); + glad_glViewport = (PFNGLVIEWPORTPROC)load("glViewport"); +} +static void load_GL_OES_framebuffer_object(GLADloadproc load) { + if(!GLAD_GL_OES_framebuffer_object) return; + glad_glIsRenderbufferOES = (PFNGLISRENDERBUFFEROESPROC)load("glIsRenderbufferOES"); + glad_glBindRenderbufferOES = (PFNGLBINDRENDERBUFFEROESPROC)load("glBindRenderbufferOES"); + glad_glDeleteRenderbuffersOES = (PFNGLDELETERENDERBUFFERSOESPROC)load("glDeleteRenderbuffersOES"); + glad_glGenRenderbuffersOES = (PFNGLGENRENDERBUFFERSOESPROC)load("glGenRenderbuffersOES"); + glad_glRenderbufferStorageOES = (PFNGLRENDERBUFFERSTORAGEOESPROC)load("glRenderbufferStorageOES"); + glad_glGetRenderbufferParameterivOES = (PFNGLGETRENDERBUFFERPARAMETERIVOESPROC)load("glGetRenderbufferParameterivOES"); + glad_glIsFramebufferOES = (PFNGLISFRAMEBUFFEROESPROC)load("glIsFramebufferOES"); + glad_glBindFramebufferOES = (PFNGLBINDFRAMEBUFFEROESPROC)load("glBindFramebufferOES"); + glad_glDeleteFramebuffersOES = (PFNGLDELETEFRAMEBUFFERSOESPROC)load("glDeleteFramebuffersOES"); + glad_glGenFramebuffersOES = (PFNGLGENFRAMEBUFFERSOESPROC)load("glGenFramebuffersOES"); + glad_glCheckFramebufferStatusOES = (PFNGLCHECKFRAMEBUFFERSTATUSOESPROC)load("glCheckFramebufferStatusOES"); + glad_glFramebufferRenderbufferOES = (PFNGLFRAMEBUFFERRENDERBUFFEROESPROC)load("glFramebufferRenderbufferOES"); + glad_glFramebufferTexture2DOES = (PFNGLFRAMEBUFFERTEXTURE2DOESPROC)load("glFramebufferTexture2DOES"); + glad_glGetFramebufferAttachmentParameterivOES = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVOESPROC)load("glGetFramebufferAttachmentParameterivOES"); + glad_glGenerateMipmapOES = (PFNGLGENERATEMIPMAPOESPROC)load("glGenerateMipmapOES"); +} +static void load_GL_OES_vertex_array_object(GLADloadproc load) { + if(!GLAD_GL_OES_vertex_array_object) return; + glad_glBindVertexArrayOES = (PFNGLBINDVERTEXARRAYOESPROC)load("glBindVertexArrayOES"); + glad_glDeleteVertexArraysOES = (PFNGLDELETEVERTEXARRAYSOESPROC)load("glDeleteVertexArraysOES"); + glad_glGenVertexArraysOES = (PFNGLGENVERTEXARRAYSOESPROC)load("glGenVertexArraysOES"); + glad_glIsVertexArrayOES = (PFNGLISVERTEXARRAYOESPROC)load("glIsVertexArrayOES"); +} +static int find_extensionsGLES1(void) { + if (!get_exts()) return 0; + GLAD_GL_KHR_debug = has_ext("GL_KHR_debug"); + GLAD_GL_OES_framebuffer_object = has_ext("GL_OES_framebuffer_object"); + GLAD_GL_OES_vertex_array_object = has_ext("GL_OES_vertex_array_object"); + free_exts(); + return 1; +} + +static void find_coreGLES1(void) { + + /* Thank you @elmindreda + * https://github.com/elmindreda/greg/blob/master/templates/greg.c.in#L176 + * https://github.com/glfw/glfw/blob/master/src/context.c#L36 + */ + int i, major, minor; + + const char* version; + const char* prefixes[] = { + "OpenGL ES-CM ", + "OpenGL ES-CL ", + "OpenGL ES ", + NULL + }; + + version = (const char*) glGetString(GL_VERSION); + if (!version) return; + + for (i = 0; prefixes[i]; i++) { + const size_t length = strlen(prefixes[i]); + if (strncmp(version, prefixes[i], length) == 0) { + version += length; + break; + } + } + +/* PR #18 */ +#ifdef _MSC_VER + sscanf_s(version, "%d.%d", &major, &minor); +#else + sscanf(version, "%d.%d", &major, &minor); +#endif + + GLVersion.major = major; GLVersion.minor = minor; + max_loaded_major = major; max_loaded_minor = minor; + GLAD_GL_VERSION_ES_CM_1_0 = (major == 1 && minor >= 0) || major > 1; + if (GLVersion.major > 1 || (GLVersion.major >= 1 && GLVersion.minor >= 0)) { + max_loaded_major = 1; + max_loaded_minor = 0; + } +} + +int gladLoadGLES1Loader(GLADloadproc load) { + GLVersion.major = 0; GLVersion.minor = 0; + glGetString = (PFNGLGETSTRINGPROC)load("glGetString"); + if(glGetString == NULL) return 0; + if(glGetString(GL_VERSION) == NULL) return 0; + find_coreGLES1(); + load_GL_VERSION_ES_CM_1_0(load); + + if (!find_extensionsGLES1()) return 0; + load_GL_KHR_debug(load); + load_GL_OES_framebuffer_object(load); + load_GL_OES_vertex_array_object(load); + return GLVersion.major != 0 || GLVersion.minor != 0; +} + static void load_GL_ES_VERSION_2_0(GLADloadproc load) { if(!GLAD_GL_ES_VERSION_2_0) return; glad_glActiveTexture = (PFNGLACTIVETEXTUREPROC)load("glActiveTexture"); @@ -2535,13 +2835,6 @@ static void load_GL_ES_VERSION_3_0(GLADloadproc load) { glad_glTexStorage3D = (PFNGLTEXSTORAGE3DPROC)load("glTexStorage3D"); glad_glGetInternalformativ = (PFNGLGETINTERNALFORMATIVPROC)load("glGetInternalformativ"); } -static void load_GL_OES_vertex_array_object(GLADloadproc load) { - if(!GLAD_GL_OES_vertex_array_object) return; - glad_glBindVertexArrayOES = (PFNGLBINDVERTEXARRAYOESPROC)load("glBindVertexArrayOES"); - glad_glDeleteVertexArraysOES = (PFNGLDELETEVERTEXARRAYSOESPROC)load("glDeleteVertexArraysOES"); - glad_glGenVertexArraysOES = (PFNGLGENVERTEXARRAYSOESPROC)load("glGenVertexArraysOES"); - glad_glIsVertexArrayOES = (PFNGLISVERTEXARRAYOESPROC)load("glIsVertexArrayOES"); -} static int find_extensionsGLES2(void) { if (!get_exts()) return 0; GLAD_GL_KHR_debug = has_ext("GL_KHR_debug");