mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-10-03 20:27:58 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48458ac034 | ||
|
|
ffdd362f35 | ||
|
|
78519bb5ef | ||
|
|
5989fe72e2 | ||
|
|
66d0636197 | ||
|
|
6c8590f8d4 | ||
|
|
3cbe4c73d8 | ||
|
|
80da6cd782 | ||
|
|
2f73abd486 | ||
|
|
5844226b2f | ||
|
|
87596c58c5 | ||
|
|
212a7bf138 | ||
|
|
652724a9ab | ||
|
|
658debdcf3 | ||
|
|
ed4fbc1f19 | ||
|
|
c1910382f8 | ||
|
|
495d6146f3 | ||
|
|
6f01734215 | ||
|
|
b55574c162 | ||
|
|
9f92186905 | ||
|
|
d80d82912a | ||
|
|
eeb4953213 | ||
|
|
5abc9e8177 | ||
|
|
5d7ec418b1 | ||
|
|
e488123345 | ||
|
|
5eea90c9a8 | ||
|
|
677ecb5a0f | ||
|
|
1918350b19 | ||
|
|
9504ee7b65 | ||
|
|
f033a507d3 | ||
|
|
fbeb9a780e | ||
|
|
8c69f53073 | ||
|
|
cc96c85381 | ||
|
|
a2618319d9 | ||
|
|
30fd5768da | ||
|
|
15a769d83c | ||
|
|
253de6adc8 | ||
|
|
a0daa3261c | ||
|
|
98e9d8bcfb | ||
|
|
b7854aaec1 | ||
|
|
905eb34590 | ||
|
|
8f22566adb | ||
|
|
48ded43e2a | ||
|
|
571bec63a2 | ||
|
|
345c017c22 | ||
|
|
205004086b | ||
|
|
6820bcfcb8 | ||
|
|
5ce1494b59 | ||
|
|
dd45e566f6 | ||
|
|
1ccc7630c1 | ||
|
|
6456f59c37 | ||
|
|
f748e8f678 | ||
|
|
a9e23508b7 | ||
|
|
c3bd978f02 | ||
|
|
50a6b17c5e | ||
|
|
0d49619366 | ||
|
|
da3cc5d900 | ||
|
|
1785e93fcc |
@@ -24,5 +24,6 @@ jobs:
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: xsystem35-android-sdl3
|
||||
path: android/app/build/outputs/apk/release/app-release.apk
|
||||
archive: false
|
||||
|
||||
@@ -2,12 +2,17 @@ name: Emscripten Build
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
EM_VERSION: latest
|
||||
EM_VERSION: 6.0.10
|
||||
EM_CACHE_FOLDER: 'emsdk-cache'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
sdl-version: [2, 3]
|
||||
name: Emscripten SDL${{ matrix.sdl-version }}
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
@@ -17,7 +22,7 @@ jobs:
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{env.EM_CACHE_FOLDER}}
|
||||
key: ${{env.EM_VERSION}}-${{ runner.os }}
|
||||
key: ${{env.EM_VERSION}}-${{ runner.os }}-sdl${{ matrix.sdl-version }}
|
||||
|
||||
- name: Setup Emscripten toolchain
|
||||
uses: emscripten-core/setup-emsdk@v16
|
||||
@@ -30,15 +35,16 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p out/wasm
|
||||
cd out/wasm
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_COMPILE_WARNING_AS_ERROR=YES ../../
|
||||
make -j4
|
||||
mkdir xsystem35
|
||||
mv src/xsystem35.* xsystem35/
|
||||
emcmake cmake -S . -B out/wasm \
|
||||
-DCMAKE_BUILD_TYPE=MinSizeRel \
|
||||
-DCMAKE_COMPILE_WARNING_AS_ERROR=YES \
|
||||
-DXSYSTEM35_SDL_VERSION=${{ matrix.sdl-version }}
|
||||
cmake --build out/wasm -j4
|
||||
mkdir out/wasm/xsystem35
|
||||
mv out/wasm/src/xsystem35.* out/wasm/xsystem35/
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: xsystem35-wasm
|
||||
name: xsystem35-wasm-sdl${{ matrix.sdl-version }}
|
||||
path: out/wasm/xsystem35
|
||||
|
||||
+15
-10
@@ -3,33 +3,38 @@ on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build-type: ["Debug", "Release"]
|
||||
sdl-version: [2, 3]
|
||||
|
||||
name: Linux ${{ matrix.build-type }}
|
||||
name: Linux SDL${{ matrix.sdl-version }} ${{ matrix.build-type }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install Deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext
|
||||
sudo apt install libfreetype-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext
|
||||
if [ "${{ matrix.sdl-version }}" = 2 ]; then
|
||||
sudo apt install libsdl2-dev libsdl2-mixer-dev
|
||||
else
|
||||
sudo apt install libsdl3-dev
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p out/${{ matrix.build-type }}
|
||||
cd out/${{ matrix.build-type }}
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} ../../
|
||||
make -j4
|
||||
cmake -S . -B out/build -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
|
||||
-DXSYSTEM35_SDL_VERSION=${{ matrix.sdl-version }}
|
||||
cmake --build out/build -j4
|
||||
|
||||
- name: Test
|
||||
run: ctest --output-on-failure
|
||||
working-directory: out/${{ matrix.build-type }}
|
||||
run: ctest --test-dir out/build --output-on-failure
|
||||
|
||||
- name: Verify xsystem35.pot is up to date
|
||||
if: matrix.build-type == 'Debug'
|
||||
if: matrix.build-type == 'Debug' && matrix.sdl-version == 2
|
||||
run: |
|
||||
xgettext --default-domain=xsystem35 --directory=. \
|
||||
--keyword=_ --keyword=N_ \
|
||||
|
||||
@@ -9,11 +9,23 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- sys: mingw32
|
||||
package: "xsystem35-32bit"
|
||||
package: "xsystem35-sdl2-32bit"
|
||||
sdl-version: 2
|
||||
sdl-deps: "SDL2:p SDL2_mixer:p"
|
||||
cmake_args: "-DENABLE_DEBUGGER=no"
|
||||
deps: ""
|
||||
- sys: ucrt64
|
||||
package: "xsystem35-64bit"
|
||||
package: "xsystem35-sdl2-64bit"
|
||||
sdl-version: 2
|
||||
sdl-deps: "SDL2:p SDL2_mixer:p"
|
||||
cmake_args: ""
|
||||
deps: >-
|
||||
cjson:p
|
||||
portmidi:p
|
||||
- sys: ucrt64
|
||||
package: "xsystem35-sdl3-64bit"
|
||||
sdl-version: 3
|
||||
sdl-deps: "sdl3:p sdl3-mixer:p"
|
||||
cmake_args: ""
|
||||
deps: >-
|
||||
cjson:p
|
||||
@@ -21,7 +33,7 @@ jobs:
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
name: MSYS2 ${{ matrix.sys }}
|
||||
name: MSYS2 SDL${{ matrix.sdl-version }} ${{ matrix.sys }}
|
||||
steps:
|
||||
|
||||
- name: Set up MSYS2
|
||||
@@ -29,9 +41,8 @@ jobs:
|
||||
with:
|
||||
msystem: ${{ matrix.sys }}
|
||||
pacboy: >-
|
||||
SDL2:p
|
||||
SDL2_ttf:p
|
||||
SDL2_mixer:p
|
||||
${{ matrix.sdl-deps }}
|
||||
freetype:p
|
||||
libwebp:p
|
||||
${{ matrix.deps }}
|
||||
|
||||
@@ -50,10 +61,11 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p out/release
|
||||
cd out/release
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_COMPILE_WARNING_AS_ERROR=YES ${{ matrix.cmake_args }} ../../
|
||||
ninja
|
||||
cmake -S . -B out/release -GNinja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_COMPILE_WARNING_AS_ERROR=YES \
|
||||
-DXSYSTEM35_SDL_VERSION=${{ matrix.sdl-version }} \
|
||||
${{ matrix.cmake_args }}
|
||||
cmake --build out/release
|
||||
|
||||
- name: Test
|
||||
run: ctest --output-on-failure
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
out
|
||||
android/.gradle
|
||||
android/.idea
|
||||
android/app/aars
|
||||
android/app/.cxx
|
||||
android/app/.externalNativeBuild
|
||||
android/app/build
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## 2.20.0 - 2026-09-11
|
||||
- Improved font rendering to more closely match the original engine. On
|
||||
Windows, MS Gothic and MS Mincho are now used by default when available.
|
||||
- The experimental `enable_zb` option has been removed. (#44)
|
||||
- SACT: Message skipping now also skips text animation, effects, waits, and
|
||||
related input waits.
|
||||
- SACT: BGM now honors the loop count specified by the game.
|
||||
- SACT: Fixed timer handling in 楽園行.
|
||||
- The popup menu and dialogs can now be operated with the keyboard.
|
||||
- Fixed mouse coordinates changing unexpectedly when automatic mouse movement
|
||||
is disabled in System 3.8/3.9 games.
|
||||
- Android: The status bar now stays hidden after returning to the game. (#81)
|
||||
- Debugger: Added an `info cache` command for viewing cache usage and hit rates.
|
||||
|
||||
## 2.19.1 - 2026-08-02
|
||||
- Fixed an issue that prevented moving upward during battles in Rance 4.
|
||||
|
||||
## 2.19.0 - 2026-07-19
|
||||
- Replaced the GTK-based popup menu and dialogs with a pure-SDL implementation.
|
||||
- On Android, the popup menu opens with a three-finger tap.
|
||||
|
||||
+74
-19
@@ -5,7 +5,24 @@ set(CMAKE_C_STANDARD 99)
|
||||
enable_testing()
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
||||
|
||||
set(XSYSTEM35_VERSION "2.19.0")
|
||||
set(XSYSTEM35_VERSION "2.20.0")
|
||||
|
||||
if (ANDROID)
|
||||
set(XSYSTEM35_DEFAULT_SDL_VERSION 3)
|
||||
else()
|
||||
set(XSYSTEM35_DEFAULT_SDL_VERSION 2)
|
||||
endif()
|
||||
set(XSYSTEM35_SDL_VERSION "${XSYSTEM35_DEFAULT_SDL_VERSION}" CACHE STRING
|
||||
"SDL major version (2 or 3)")
|
||||
set_property(CACHE XSYSTEM35_SDL_VERSION PROPERTY STRINGS 2 3)
|
||||
if (NOT XSYSTEM35_SDL_VERSION STREQUAL "2" AND
|
||||
NOT XSYSTEM35_SDL_VERSION STREQUAL "3")
|
||||
message(FATAL_ERROR "XSYSTEM35_SDL_VERSION must be 2 or 3")
|
||||
endif()
|
||||
add_library(xsystem35_sdl INTERFACE)
|
||||
target_compile_definitions(xsystem35_sdl INTERFACE
|
||||
XSYSTEM35_SDL_VERSION=${XSYSTEM35_SDL_VERSION})
|
||||
add_library(xsystem35_sdl_mixer INTERFACE)
|
||||
|
||||
include(CheckSymbolExists)
|
||||
include(FetchContent)
|
||||
@@ -55,6 +72,8 @@ check_symbol_exists(mmap "sys/mman.h" HAVE_MMAP)
|
||||
check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION)
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
option(JSPI "Use JavaScript Promise Integration instead of Asyncify" OFF)
|
||||
|
||||
function(add_emscripten_library name option)
|
||||
add_library(${name} INTERFACE)
|
||||
set_target_properties(${name} PROPERTIES
|
||||
@@ -62,16 +81,23 @@ if (EMSCRIPTEN)
|
||||
INTERFACE_LINK_OPTIONS ${option})
|
||||
endfunction()
|
||||
add_emscripten_library(zlib -sUSE_ZLIB=1)
|
||||
add_emscripten_library(sdl2 -sUSE_SDL=2)
|
||||
add_emscripten_library(sdl2_ttf -sUSE_SDL_TTF=2)
|
||||
target_compile_options(xsystem35_sdl INTERFACE
|
||||
-sUSE_SDL=${XSYSTEM35_SDL_VERSION})
|
||||
target_link_options(xsystem35_sdl INTERFACE
|
||||
-sUSE_SDL=${XSYSTEM35_SDL_VERSION})
|
||||
add_emscripten_library(freetype2 -sUSE_FREETYPE=1)
|
||||
set(DEFAULT_FONT_PATH /fonts/)
|
||||
fetch_webp()
|
||||
set(HAVE_WEBP 1)
|
||||
|
||||
elseif (ANDROID)
|
||||
add_library(sdl2 ALIAS SDL2)
|
||||
add_library(sdl2_ttf ALIAS SDL2_ttf)
|
||||
add_library(sdl2_mixer ALIAS SDL2_mixer)
|
||||
if (NOT XSYSTEM35_SDL_VERSION STREQUAL "3")
|
||||
message(FATAL_ERROR "Android supports SDL3 only")
|
||||
endif()
|
||||
target_link_libraries(xsystem35_sdl INTERFACE SDL3::SDL3)
|
||||
target_link_libraries(xsystem35_sdl_mixer INTERFACE
|
||||
SDL3_mixer::SDL3_mixer)
|
||||
add_library(freetype2 ALIAS freetype)
|
||||
find_library(ndk_log log)
|
||||
find_library(ndk_zlib z)
|
||||
add_library(zlib INTERFACE)
|
||||
@@ -87,20 +113,48 @@ else() # non-emscripten, non-android
|
||||
add_library(zlib ALIAS ZLIB::ZLIB)
|
||||
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2>=2.18.0)
|
||||
pkg_check_modules(SDL2TTF REQUIRED IMPORTED_TARGET SDL2_ttf)
|
||||
pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer)
|
||||
if (WIN32)
|
||||
add_static_library(sdl2 SDL2)
|
||||
add_static_library(sdl2_ttf SDL2TTF)
|
||||
# Workaround for linking error
|
||||
set_property(TARGET sdl2_ttf PROPERTY INTERFACE_LINK_LIBRARIES
|
||||
$<LINK_GROUP:RESCAN,${SDL2TTF_STATIC_LIBRARIES} -lstdc++>)
|
||||
add_static_library(sdl2_mixer SDL2MIXER)
|
||||
pkg_check_modules(FREETYPE REQUIRED IMPORTED_TARGET freetype2)
|
||||
if (XSYSTEM35_SDL_VERSION STREQUAL "2")
|
||||
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2>=2.18.0)
|
||||
pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer)
|
||||
if (WIN32)
|
||||
add_static_library(sdl2 SDL2)
|
||||
add_static_library(sdl2_mixer SDL2MIXER)
|
||||
target_link_libraries(xsystem35_sdl INTERFACE sdl2)
|
||||
target_link_libraries(xsystem35_sdl_mixer INTERFACE sdl2_mixer)
|
||||
else()
|
||||
target_link_libraries(xsystem35_sdl INTERFACE PkgConfig::SDL2)
|
||||
target_link_libraries(xsystem35_sdl_mixer INTERFACE
|
||||
PkgConfig::SDL2MIXER)
|
||||
endif()
|
||||
else()
|
||||
add_library(sdl2 ALIAS PkgConfig::SDL2)
|
||||
add_library(sdl2_ttf ALIAS PkgConfig::SDL2TTF)
|
||||
add_library(sdl2_mixer ALIAS PkgConfig::SDL2MIXER)
|
||||
find_package(SDL3 3.4 REQUIRED CONFIG)
|
||||
find_package(SDL3_mixer 3.2 QUIET CONFIG)
|
||||
if (NOT SDL3_mixer_FOUND)
|
||||
message(STATUS "SDL3_mixer not found; building SDL3_mixer 3.2.4 from source")
|
||||
FetchContent_Declare(
|
||||
SDL3_mixer
|
||||
URL https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-3.2.4.tar.gz
|
||||
URL_HASH SHA256=f2ea848ccdf2f394cd4973ee0f6c482e04511044695cccfd46bab6dcd7f780aa
|
||||
)
|
||||
FetchContent_MakeAvailable(SDL3_mixer)
|
||||
endif()
|
||||
if (WIN32)
|
||||
target_link_libraries(xsystem35_sdl INTERFACE SDL3::SDL3-static)
|
||||
target_link_libraries(xsystem35_sdl_mixer INTERFACE
|
||||
SDL3_mixer::SDL3_mixer-static)
|
||||
else()
|
||||
target_link_libraries(xsystem35_sdl INTERFACE SDL3::SDL3)
|
||||
target_link_libraries(xsystem35_sdl_mixer INTERFACE
|
||||
SDL3_mixer::SDL3_mixer)
|
||||
endif()
|
||||
endif()
|
||||
if (WIN32)
|
||||
add_static_library(freetype2 FREETYPE)
|
||||
# harfbuzz and graphite2, which the static freetype depends on, are C++ libraries.
|
||||
target_link_libraries(freetype2 INTERFACE -lstdc++)
|
||||
else()
|
||||
add_library(freetype2 ALIAS PkgConfig::FREETYPE)
|
||||
set(DEFAULT_FONT_PATH ${CMAKE_INSTALL_PREFIX}/share/xsystem35/fonts/)
|
||||
endif()
|
||||
|
||||
@@ -211,6 +265,7 @@ endfunction()
|
||||
|
||||
message("xsystem35 summary:")
|
||||
message("------------------")
|
||||
print_summary(SDL ${XSYSTEM35_SDL_VERSION})
|
||||
print_summary(audio ${SUMMARY_AUDIO})
|
||||
print_summary(cdrom ${SUMMARY_CDROM})
|
||||
print_summary(midi ${SUMMARY_MIDI})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# xsytem35-sdl2
|
||||
# xsystem35 for SDL2/SDL3
|
||||
|
||||
This is a multi-platform port of `xsystem35`, a free implementation of
|
||||
AliceSoft's System 3.x game engine.
|
||||
@@ -102,53 +102,97 @@ $ xsystem35
|
||||
See [xsystem35 command manual](doc/xsystem35.6.adoc) for detailed usage.
|
||||
|
||||
## Building
|
||||
|
||||
SDL2 is used by default on desktop platforms and Emscripten. Pass
|
||||
`-DXSYSTEM35_SDL_VERSION=3` to CMake to select SDL3. Use a separate build
|
||||
directory for each SDL version.
|
||||
|
||||
The supported configurations are:
|
||||
|
||||
| Platform | SDL2 | SDL3 |
|
||||
| --- | --- | --- |
|
||||
| Linux and macOS | Yes | Yes |
|
||||
| Windows 64-bit | Yes | Yes |
|
||||
| Windows 32-bit | Yes | No |
|
||||
| Emscripten | Yes | Yes |
|
||||
| Android | No | Yes |
|
||||
|
||||
SDL2 builds require SDL 2.18 or later and SDL2_mixer. SDL3 builds require
|
||||
SDL 3.4 or later and SDL3_mixer 3.2 or later. The remaining common
|
||||
dependencies are FreeType, zlib, and optionally libwebp, PortMidi, and cJSON.
|
||||
|
||||
### Linux (Debian / Ubuntu)
|
||||
|
||||
```bash
|
||||
$ sudo apt install build-essential cmake libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ make && make install
|
||||
$ sudo apt install build-essential cmake libsdl2-dev libsdl2-mixer-dev \
|
||||
libfreetype-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
|
||||
$ cmake -S . -B out/sdl2 -DCMAKE_BUILD_TYPE=Debug \
|
||||
-DXSYSTEM35_SDL_VERSION=2
|
||||
$ cmake --build out/sdl2
|
||||
$ sudo cmake --install out/sdl2
|
||||
```
|
||||
|
||||
For SDL3 on a distribution that provides SDL 3.4 or later (for example,
|
||||
Ubuntu 26.04), install `libsdl3-dev` instead of the SDL2 packages and
|
||||
configure `out/sdl3` with `-DXSYSTEM35_SDL_VERSION=3`. If SDL3_mixer 3.2 or
|
||||
later is installed, CMake uses it; otherwise, CMake downloads and builds
|
||||
SDL3_mixer 3.2.4 during configuration.
|
||||
|
||||
### MacOS
|
||||
|
||||
[Homebrew](https://brew.sh/) is required.
|
||||
|
||||
```bash
|
||||
$ brew install cmake pkg-config sdl2 sdl2_mixer sdl2_ttf webp portmidi cjson asciidoctor
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ make && make install
|
||||
$ brew install cmake pkg-config sdl2 sdl2_mixer freetype webp portmidi \
|
||||
cjson asciidoctor
|
||||
$ cmake -S . -B out/sdl2 -DCMAKE_BUILD_TYPE=Debug \
|
||||
-DXSYSTEM35_SDL_VERSION=2
|
||||
$ cmake --build out/sdl2
|
||||
$ sudo cmake --install out/sdl2
|
||||
```
|
||||
|
||||
For SDL3, install `sdl3 sdl3_mixer` instead of `sdl2 sdl2_mixer` and
|
||||
configure `out/sdl3` with `-DXSYSTEM35_SDL_VERSION=3`.
|
||||
|
||||
### Windows
|
||||
|
||||
[MSYS2](https://www.msys2.org) is required.
|
||||
|
||||
```bash
|
||||
$ pacman -S cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-SDL2_ttf mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi mingw-w64-ucrt-x86_64-cjson
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
$ make
|
||||
$ pacman -S mingw-w64-ucrt-x86_64-gcc \
|
||||
mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 \
|
||||
mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-freetype \
|
||||
mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi \
|
||||
mingw-w64-ucrt-x86_64-cjson
|
||||
$ cmake -S . -B out/sdl2 -G"MSYS Makefiles" \
|
||||
-DCMAKE_BUILD_TYPE=Debug -DXSYSTEM35_SDL_VERSION=2
|
||||
$ cmake --build out/sdl2
|
||||
```
|
||||
|
||||
For a 64-bit SDL3 build, install `mingw-w64-ucrt-x86_64-sdl3` and
|
||||
`mingw-w64-ucrt-x86_64-sdl3-mixer` instead of the SDL2 packages, then
|
||||
configure `out/sdl3` with `-DXSYSTEM35_SDL_VERSION=3`. The MSYS2 mingw32
|
||||
repository does not provide SDL3, so 32-bit Windows builds remain on SDL2.
|
||||
|
||||
### Emscripten
|
||||
|
||||
```bash
|
||||
$ mkdir -p out/wasm
|
||||
$ cd out/wasm
|
||||
$ emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../../
|
||||
$ make
|
||||
$ emcmake cmake -S . -B out/wasm-sdl2 -DCMAKE_BUILD_TYPE=MinSizeRel \
|
||||
-DXSYSTEM35_SDL_VERSION=2
|
||||
$ cmake --build out/wasm-sdl2
|
||||
```
|
||||
|
||||
For SDL3, use a different build directory and
|
||||
`-DXSYSTEM35_SDL_VERSION=3`. Both versions use Emscripten's bundled SDL port;
|
||||
SDL_mixer is not required.
|
||||
|
||||
To use the generated binary, check out
|
||||
[Kichikuou on Web](https://github.com/kichikuou/web) and copy `out/xsystem35.*`
|
||||
into its `docs` directory.
|
||||
[Kichikuou on Web](https://github.com/kichikuou/web) and copy
|
||||
`out/wasm-sdl2/src/xsystem35.*` into its `docs` directory.
|
||||
|
||||
### Android
|
||||
|
||||
See [android/README.md](android/).
|
||||
|
||||
Android uses SDL3 exclusively. The Gradle build downloads fixed official SDL3
|
||||
3.4.16 and SDL3_mixer 3.2.4 AARs and exposes them to CMake through Prefab.
|
||||
|
||||
@@ -73,6 +73,11 @@ Note: This ZIP format is also compatible with
|
||||
|
||||
## Building from Source
|
||||
|
||||
Android builds use SDL3 exclusively. Gradle downloads the fixed official SDL3
|
||||
3.4.16 and SDL3_mixer 3.2.4 AARs, verifies their checksums, and supplies their
|
||||
native libraries to CMake through Prefab. No separate SDL installation is
|
||||
required.
|
||||
|
||||
### Using Android Studio
|
||||
Open this directory as an Android Studio project.
|
||||
|
||||
|
||||
@@ -8,19 +8,89 @@ else {
|
||||
}
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
def aarsDir = file('aars')
|
||||
def assetsLicensesDir = file('src/main/assets/licenses')
|
||||
|
||||
def sha256 = { File file ->
|
||||
def digest = java.security.MessageDigest.getInstance('SHA-256')
|
||||
file.withInputStream { input ->
|
||||
byte[] buffer = new byte[8192]
|
||||
for (int n = input.read(buffer); n >= 0; n = input.read(buffer)) {
|
||||
digest.update(buffer, 0, n)
|
||||
}
|
||||
}
|
||||
digest.digest().encodeHex().toString()
|
||||
}
|
||||
|
||||
def installAar = { String url, String archiveSha256, String aarName,
|
||||
String aarSha256, String licenseName ->
|
||||
def aarFile = new File(aarsDir, aarName)
|
||||
def licenseFile = new File(assetsLicensesDir, licenseName)
|
||||
if (!aarFile.exists() || sha256(aarFile) != aarSha256 || !licenseFile.exists()) {
|
||||
logger.lifecycle("Downloading ${aarName} ...")
|
||||
def archive = File.createTempFile('sdl-android', '.zip')
|
||||
try {
|
||||
new URL(url).withInputStream { input ->
|
||||
archive.withOutputStream { it << input }
|
||||
}
|
||||
if (sha256(archive) != archiveSha256) {
|
||||
throw new GradleException("Checksum mismatch for ${url}")
|
||||
}
|
||||
aarsDir.mkdirs()
|
||||
assetsLicensesDir.mkdirs()
|
||||
copy {
|
||||
from zipTree(archive)
|
||||
include aarName
|
||||
into aarsDir
|
||||
}
|
||||
copy {
|
||||
from zipTree(archive)
|
||||
include 'LICENSE.txt'
|
||||
into assetsLicensesDir
|
||||
rename 'LICENSE.txt', licenseName
|
||||
}
|
||||
} finally {
|
||||
archive.delete()
|
||||
}
|
||||
}
|
||||
if (!aarFile.exists() || sha256(aarFile) != aarSha256) {
|
||||
throw new GradleException("Checksum mismatch for ${aarFile}")
|
||||
}
|
||||
aarFile
|
||||
}
|
||||
|
||||
def sdlVersion = '3.4.16'
|
||||
def sdlAarFile = installAar(
|
||||
"https://github.com/libsdl-org/SDL/releases/download/release-${sdlVersion}/SDL3-devel-${sdlVersion}-android.zip",
|
||||
'e1da8d9298471ea4ab8c6d48a804c0ac5415f35f30fcf7eba61aa7357e165765',
|
||||
"SDL3-${sdlVersion}.aar",
|
||||
'03710fc7b49cc070551446841a843840fabf2aaaaa3043cd5739292a55e4e61c',
|
||||
'SDL')
|
||||
|
||||
def sdlMixerVersion = '3.2.4'
|
||||
def sdlMixerAarFile = installAar(
|
||||
"https://github.com/libsdl-org/SDL_mixer/releases/download/release-${sdlMixerVersion}/SDL3_mixer-devel-${sdlMixerVersion}-android.zip",
|
||||
'b7f9f1ee5162a0492e71941f2cbdc50ec256ef15c0e995ca72c7e519eebd810d',
|
||||
"SDL3_mixer-${sdlMixerVersion}.aar",
|
||||
'd13dc45d4a4d6a2db4132400e77629c466ed533641fc9e211dfe85fab025b792',
|
||||
'SDL_mixer')
|
||||
|
||||
android {
|
||||
if (buildAsApplication) {
|
||||
namespace "io.github.kichikuou.xsystem35"
|
||||
}
|
||||
buildFeatures {
|
||||
prefab true
|
||||
}
|
||||
compileSdkVersion 34
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode 36
|
||||
versionName "2.19.0" + gitRevision()
|
||||
versionName "2.20.0" + gitRevision()
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
|
||||
arguments "-DANDROID_STL=c++_static", "-DXSYSTEM35_SDL_VERSION=3"
|
||||
// abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
@@ -83,6 +153,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation files(sdlAarFile, sdlMixerAarFile)
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
include $(call all-subdir-makefiles)
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
# Uncomment this if you're using STL in your project
|
||||
# You can find more information here:
|
||||
# https://developer.android.com/ndk/guides/cpp-support
|
||||
# APP_STL := c++_shared
|
||||
|
||||
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
|
||||
|
||||
# Min runtime API level
|
||||
APP_PLATFORM=android-16
|
||||
@@ -3,37 +3,25 @@ cmake_minimum_required(VERSION 3.14)
|
||||
project(GAME)
|
||||
set(PROJECT_ROOT_DIR ../../..)
|
||||
|
||||
# Compilation of SDL and companion libraries
|
||||
# SDL3 and SDL3_mixer are provided by their official Android AARs through Prefab.
|
||||
find_package(SDL3 3.4 REQUIRED CONFIG)
|
||||
find_package(SDL3_mixer 3.2.4 REQUIRED CONFIG)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
SDL
|
||||
URL https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-2.32.10.tar.gz
|
||||
URL_HASH SHA1=ce98fa93e31836a751feca374ab28a0770b63c16
|
||||
)
|
||||
FetchContent_Declare(
|
||||
SDL_ttf
|
||||
URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.22.0/SDL2_ttf-2.22.0.tar.gz
|
||||
URL_HASH SHA1=da5e86b601ad299a697878fab1af6f3be47b529d
|
||||
)
|
||||
FetchContent_Declare(
|
||||
SDL_mixer
|
||||
URL https://github.com/libsdl-org/SDL_mixer/releases/download/release-2.8.0/SDL2_mixer-2.8.0.tar.gz
|
||||
URL_HASH SHA1=a58c69f9d00e44833b9e00e1adb58d85759ca499
|
||||
freetype
|
||||
URL https://downloads.sourceforge.net/project/freetype/freetype2/2.14.3/freetype-2.14.3.tar.gz
|
||||
https://download.savannah.gnu.org/releases/freetype/freetype-2.14.3.tar.gz
|
||||
URL_HASH SHA1=a911f65c6355ddcf7aaba46e250dbc00f67e1678
|
||||
)
|
||||
# Only FreeType's own rasterizer is needed, none of its optional dependencies.
|
||||
set(FT_DISABLE_ZLIB ON CACHE BOOL "Disable use of system zlib" FORCE)
|
||||
set(FT_DISABLE_BZIP2 ON CACHE BOOL "Disable use of system libbz2" FORCE)
|
||||
set(FT_DISABLE_PNG ON CACHE BOOL "Disable use of system libpng" FORCE)
|
||||
set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "Disable use of harfbuzz" FORCE)
|
||||
set(FT_DISABLE_BROTLI ON CACHE BOOL "Disable use of brotli" FORCE)
|
||||
|
||||
set(SDL2TTF_SAMPLES OFF CACHE BOOL "Build the SDL2_ttf sample program(s)" FORCE)
|
||||
set(SDL2TTF_INSTALL OFF CACHE BOOL "Enable SDL2_ttf install target" FORCE)
|
||||
set(SDL2TTF_VENDORED ON CACHE BOOL "Use vendored third-party libraries" FORCE)
|
||||
|
||||
set(SDL2MIXER_OPUS OFF CACHE BOOL "Enable Opus music" FORCE)
|
||||
set(SDL2MIXER_FLAC OFF CACHE BOOL "Enable FLAC music" FORCE)
|
||||
set(SDL2MIXER_MOD OFF CACHE BOOL "Support loading MOD music" FORCE)
|
||||
set(SDL2MIXER_MIDI OFF CACHE BOOL "Enable MIDI music" FORCE)
|
||||
set(SDL2MIXER_WAVPACK OFF CACHE BOOL "Enable WavPack music" FORCE)
|
||||
set(SDL2MIXER_SAMPLES OFF CACHE BOOL "Build the SDL2_mixer sample program(s)" FORCE)
|
||||
set(SDL2MIXER_INSTALL OFF CACHE BOOL "Enable SDL2_mixer install target" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(SDL SDL_ttf SDL_mixer)
|
||||
FetchContent_MakeAvailable(freetype)
|
||||
|
||||
# The main CMakeLists.txt of xsystem35
|
||||
add_subdirectory(${PROJECT_ROOT_DIR} xsystem35)
|
||||
@@ -48,8 +36,4 @@ file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/MTLc3m.txt ${ASSETS_DIR}/licenses/MT
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/mincho.txt ${ASSETS_DIR}/licenses/mincho)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/nanojpeg.txt ${ASSETS_DIR}/licenses/nanojpeg)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/microui.txt ${ASSETS_DIR}/licenses/microui)
|
||||
file(COPY_FILE ${sdl_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL)
|
||||
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_ttf)
|
||||
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/freetype/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
|
||||
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/harfbuzz/COPYING ${ASSETS_DIR}/licenses/harfbuzz)
|
||||
file(COPY_FILE ${sdl_mixer_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_mixer)
|
||||
file(COPY_FILE ${freetype_SOURCE_DIR}/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := main
|
||||
|
||||
SDL_PATH := ../SDL
|
||||
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
|
||||
|
||||
# Add your application source files here...
|
||||
LOCAL_SRC_FILES := YourSourceHere.c
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := SDL2
|
||||
|
||||
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -1,13 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
|
||||
project(MY_APP)
|
||||
|
||||
find_library(SDL2 SDL2)
|
||||
|
||||
add_library(main SHARED)
|
||||
|
||||
target_sources(main PRIVATE YourSourceHere.c)
|
||||
|
||||
target_link_libraries(main SDL2)
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
<!-- Example of setting SDL hints from AndroidManifest.xml:
|
||||
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
|
||||
-->
|
||||
<meta-data android:name="SDL_ENV.SDL_IOS_ORIENTATIONS" android:value="LandscapeLeft LandscapeRight"/>
|
||||
|
||||
<activity android:name=".LauncherActivity"
|
||||
android:exported="true">
|
||||
|
||||
@@ -45,6 +45,8 @@ class GameActivity : SDLActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
gameRoot = File(intent.getStringExtra(EXTRA_GAME_ROOT)!!)
|
||||
// Workaround for https://github.com/libsdl-org/SDL/issues/8995
|
||||
SDLActivity.setWindowStyle(true)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
@@ -70,7 +72,7 @@ class GameActivity : SDLActivity() {
|
||||
}
|
||||
|
||||
override fun getLibraries(): Array<String> {
|
||||
return arrayOf("SDL2", "xsystem35")
|
||||
return arrayOf("SDL3", "xsystem35")
|
||||
}
|
||||
|
||||
override fun getArguments(): Array<String> {
|
||||
|
||||
@@ -12,11 +12,9 @@ class LicensesMenuActivity : Activity() {
|
||||
Entry("xsystem35-sdl2", "xsystem35", "https://github.com/kichikuou/xsystem35-sdl2"),
|
||||
Entry("SDL", "SDL", "https://www.libsdl.org/"),
|
||||
Entry("SDL_mixer", "SDL_mixer", "https://github.com/libsdl-org/SDL_mixer"),
|
||||
Entry("SDL_ttf", "SDL_ttf", "https://github.com/libsdl-org/SDL_ttf"),
|
||||
Entry("NanoJPEG", "nanojpeg", "https://keyj.emphy.de/nanojpeg/"),
|
||||
Entry("microui", "microui", "https://github.com/rxi/microui"),
|
||||
Entry("FreeType", "freetype", "https://freetype.org/"),
|
||||
Entry("HarfBuzz", "harfbuzz", "https://harfbuzz.github.io/"),
|
||||
Entry("MotoyaLCedar W3 mono", "MTLc3m", "https://github.com/aosp-mirror/platform_frameworks_base/tree/lollipop-release/data/fonts"),
|
||||
Entry("IPA明朝", "mincho", "https://moji.or.jp/ipafont/"),
|
||||
)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
|
||||
interface HIDDevice
|
||||
{
|
||||
public int getId();
|
||||
public int getVendorId();
|
||||
public int getProductId();
|
||||
public String getSerialNumber();
|
||||
public int getVersion();
|
||||
public String getManufacturerName();
|
||||
public String getProductName();
|
||||
public UsbDevice getDevice();
|
||||
public boolean open();
|
||||
public int sendFeatureReport(byte[] report);
|
||||
public int sendOutputReport(byte[] report);
|
||||
public boolean getFeatureReport(byte[] report);
|
||||
public void setFrozen(boolean frozen);
|
||||
public void close();
|
||||
public void shutdown();
|
||||
}
|
||||
@@ -1,650 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.os.*;
|
||||
|
||||
//import com.android.internal.util.HexDump;
|
||||
|
||||
import java.lang.Runnable;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.UUID;
|
||||
|
||||
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
|
||||
|
||||
private static final String TAG = "hidapi";
|
||||
private HIDDeviceManager mManager;
|
||||
private BluetoothDevice mDevice;
|
||||
private int mDeviceId;
|
||||
private BluetoothGatt mGatt;
|
||||
private boolean mIsRegistered = false;
|
||||
private boolean mIsConnected = false;
|
||||
private boolean mIsChromebook = false;
|
||||
private boolean mIsReconnecting = false;
|
||||
private boolean mFrozen = false;
|
||||
private LinkedList<GattOperation> mOperations;
|
||||
GattOperation mCurrentOperation = null;
|
||||
private Handler mHandler;
|
||||
|
||||
private static final int TRANSPORT_AUTO = 0;
|
||||
private static final int TRANSPORT_BREDR = 1;
|
||||
private static final int TRANSPORT_LE = 2;
|
||||
|
||||
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
|
||||
|
||||
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
|
||||
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
|
||||
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
|
||||
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
|
||||
|
||||
static class GattOperation {
|
||||
private enum Operation {
|
||||
CHR_READ,
|
||||
CHR_WRITE,
|
||||
ENABLE_NOTIFICATION
|
||||
}
|
||||
|
||||
Operation mOp;
|
||||
UUID mUuid;
|
||||
byte[] mValue;
|
||||
BluetoothGatt mGatt;
|
||||
boolean mResult = true;
|
||||
|
||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
|
||||
mGatt = gatt;
|
||||
mOp = operation;
|
||||
mUuid = uuid;
|
||||
}
|
||||
|
||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
|
||||
mGatt = gatt;
|
||||
mOp = operation;
|
||||
mUuid = uuid;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// This is executed in main thread
|
||||
BluetoothGattCharacteristic chr;
|
||||
|
||||
switch (mOp) {
|
||||
case CHR_READ:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
|
||||
if (!mGatt.readCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case CHR_WRITE:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
|
||||
chr.setValue(mValue);
|
||||
if (!mGatt.writeCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case ENABLE_NOTIFICATION:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
|
||||
if (chr != null) {
|
||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||
if (cccd != null) {
|
||||
int properties = chr.getProperties();
|
||||
byte[] value;
|
||||
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
|
||||
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
|
||||
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
|
||||
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
|
||||
} else {
|
||||
Log.e(TAG, "Unable to start notifications on input characteristic");
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mGatt.setCharacteristicNotification(chr, true);
|
||||
cccd.setValue(value);
|
||||
if (!mGatt.writeDescriptor(cccd)) {
|
||||
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
mResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean finish() {
|
||||
return mResult;
|
||||
}
|
||||
|
||||
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
|
||||
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
||||
if (valveService == null)
|
||||
return null;
|
||||
return valveService.getCharacteristic(uuid);
|
||||
}
|
||||
|
||||
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
|
||||
return new GattOperation(gatt, Operation.CHR_READ, uuid);
|
||||
}
|
||||
|
||||
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
|
||||
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
|
||||
}
|
||||
|
||||
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
|
||||
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
|
||||
mManager = manager;
|
||||
mDevice = device;
|
||||
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
|
||||
mIsRegistered = false;
|
||||
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||
mOperations = new LinkedList<GattOperation>();
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
mGatt = connectGatt();
|
||||
// final HIDDeviceBLESteamController finalThis = this;
|
||||
// mHandler.postDelayed(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// finalThis.checkConnectionForChromebookIssue();
|
||||
// }
|
||||
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return String.format("SteamController.%s", mDevice.getAddress());
|
||||
}
|
||||
|
||||
public BluetoothGatt getGatt() {
|
||||
return mGatt;
|
||||
}
|
||||
|
||||
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
||||
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
||||
private BluetoothGatt connectGatt(boolean managed) {
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
try {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||
} catch (Exception e) {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
} else {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
}
|
||||
|
||||
private BluetoothGatt connectGatt() {
|
||||
return connectGatt(false);
|
||||
}
|
||||
|
||||
protected int getConnectionState() {
|
||||
|
||||
Context context = mManager.getContext();
|
||||
if (context == null) {
|
||||
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (btManager == null) {
|
||||
// This device doesn't support Bluetooth. We should never be here, because how did
|
||||
// we instantiate a device to start with?
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
|
||||
}
|
||||
|
||||
public void reconnect() {
|
||||
|
||||
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void checkConnectionForChromebookIssue() {
|
||||
if (!mIsChromebook) {
|
||||
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
|
||||
// over and over.
|
||||
return;
|
||||
}
|
||||
|
||||
int connectionState = getConnectionState();
|
||||
|
||||
switch (connectionState) {
|
||||
case BluetoothProfile.STATE_CONNECTED:
|
||||
if (!mIsConnected) {
|
||||
// We are in the Bad Chromebook Place. We can force a disconnect
|
||||
// to try to recover.
|
||||
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
else if (!isRegistered()) {
|
||||
if (mGatt.getServices().size() > 0) {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
|
||||
probeService(this);
|
||||
}
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_DISCONNECTED:
|
||||
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
|
||||
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_CONNECTING:
|
||||
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
|
||||
break;
|
||||
}
|
||||
|
||||
final HIDDeviceBLESteamController finalThis = this;
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finalThis.checkConnectionForChromebookIssue();
|
||||
}
|
||||
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
private boolean isRegistered() {
|
||||
return mIsRegistered;
|
||||
}
|
||||
|
||||
private void setRegistered() {
|
||||
mIsRegistered = true;
|
||||
}
|
||||
|
||||
private boolean probeService(HIDDeviceBLESteamController controller) {
|
||||
|
||||
if (isRegistered()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mIsConnected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.v(TAG, "probeService controller=" + controller);
|
||||
|
||||
for (BluetoothGattService service : mGatt.getServices()) {
|
||||
if (service.getUuid().equals(steamControllerService)) {
|
||||
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
|
||||
|
||||
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
|
||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||
Log.v(TAG, "Found input characteristic");
|
||||
// Start notifications
|
||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||
if (cccd != null) {
|
||||
enableNotification(chr.getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
|
||||
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
|
||||
mIsConnected = false;
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void finishCurrentGattOperation() {
|
||||
GattOperation op = null;
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation != null) {
|
||||
op = mCurrentOperation;
|
||||
mCurrentOperation = null;
|
||||
}
|
||||
}
|
||||
if (op != null) {
|
||||
boolean result = op.finish(); // TODO: Maybe in main thread as well?
|
||||
|
||||
// Our operation failed, let's add it back to the beginning of our queue.
|
||||
if (!result) {
|
||||
mOperations.addFirst(op);
|
||||
}
|
||||
}
|
||||
executeNextGattOperation();
|
||||
}
|
||||
|
||||
private void executeNextGattOperation() {
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation != null)
|
||||
return;
|
||||
|
||||
if (mOperations.isEmpty())
|
||||
return;
|
||||
|
||||
mCurrentOperation = mOperations.removeFirst();
|
||||
}
|
||||
|
||||
// Run in main thread
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation == null) {
|
||||
Log.e(TAG, "Current operation null in executor?");
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentOperation.run();
|
||||
// now wait for the GATT callback and when it comes, finish this operation
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void queueGattOperation(GattOperation op) {
|
||||
synchronized (mOperations) {
|
||||
mOperations.add(op);
|
||||
}
|
||||
executeNextGattOperation();
|
||||
}
|
||||
|
||||
private void enableNotification(UUID chrUuid) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
public void writeCharacteristic(UUID uuid, byte[] value) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
public void readCharacteristic(UUID uuid) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////// BluetoothGattCallback overridden methods
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
|
||||
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
|
||||
mIsReconnecting = false;
|
||||
if (newState == 2) {
|
||||
mIsConnected = true;
|
||||
// Run directly, without GattOperation
|
||||
if (!isRegistered()) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mGatt.discoverServices();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (newState == 0) {
|
||||
mIsConnected = false;
|
||||
}
|
||||
|
||||
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
|
||||
}
|
||||
|
||||
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
||||
//Log.v(TAG, "onServicesDiscovered status=" + status);
|
||||
if (status == 0) {
|
||||
if (gatt.getServices().size() == 0) {
|
||||
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
|
||||
mIsReconnecting = true;
|
||||
mIsConnected = false;
|
||||
gatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
else {
|
||||
probeService(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
|
||||
|
||||
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
|
||||
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
|
||||
|
||||
if (characteristic.getUuid().equals(reportCharacteristic)) {
|
||||
// Only register controller with the native side once it has been fully configured
|
||||
if (!isRegistered()) {
|
||||
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
|
||||
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
|
||||
setRegistered();
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
// Enable this for verbose logging of controller input reports
|
||||
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
|
||||
|
||||
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
|
||||
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
//Log.v(TAG, "onDescriptorRead status=" + status);
|
||||
}
|
||||
|
||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
||||
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
||||
|
||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||
boolean hasWrittenInputDescriptor = true;
|
||||
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
||||
if (reportChr != null) {
|
||||
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
||||
reportChr.setValue(enterValveMode);
|
||||
gatt.writeCharacteristic(reportChr);
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
|
||||
}
|
||||
|
||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||
//Log.v(TAG, "onReadRemoteRssi status=" + status);
|
||||
}
|
||||
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
//Log.v(TAG, "onMtuChanged status=" + status);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////// Public API
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId() {
|
||||
// Valve Corporation
|
||||
final int VALVE_USB_VID = 0x28DE;
|
||||
return VALVE_USB_VID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProductId() {
|
||||
// We don't have an easy way to query from the Bluetooth device, but we know what it is
|
||||
final int D0G_BLE2_PID = 0x1106;
|
||||
return D0G_BLE2_PID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
// This will be read later via feature report by Steam
|
||||
return "12345";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
return "Valve Corporation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductName() {
|
||||
return "Steam Controller";
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendFeatureReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We need to skip the first byte, as that doesn't go over the air
|
||||
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
|
||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
|
||||
writeCharacteristic(reportCharacteristic, actual_report);
|
||||
return report.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendOutputReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
|
||||
writeCharacteristic(reportCharacteristic, report);
|
||||
return report.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeatureReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Log.v(TAG, "getFeatureReport");
|
||||
readCharacteristic(reportCharacteristic);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrozen(boolean frozen) {
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
|
||||
BluetoothGatt g = mGatt;
|
||||
if (g != null) {
|
||||
g.disconnect();
|
||||
g.close();
|
||||
mGatt = null;
|
||||
}
|
||||
mManager = null;
|
||||
mIsRegistered = false;
|
||||
mIsConnected = false;
|
||||
mOperations.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,698 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class HIDDeviceManager {
|
||||
private static final String TAG = "hidapi";
|
||||
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
|
||||
|
||||
private static HIDDeviceManager sManager;
|
||||
private static int sManagerRefCount = 0;
|
||||
|
||||
public static HIDDeviceManager acquire(Context context) {
|
||||
if (sManagerRefCount == 0) {
|
||||
sManager = new HIDDeviceManager(context);
|
||||
}
|
||||
++sManagerRefCount;
|
||||
return sManager;
|
||||
}
|
||||
|
||||
public static void release(HIDDeviceManager manager) {
|
||||
if (manager == sManager) {
|
||||
--sManagerRefCount;
|
||||
if (sManagerRefCount == 0) {
|
||||
sManager.close();
|
||||
sManager = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
|
||||
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
|
||||
private int mNextDeviceId = 0;
|
||||
private SharedPreferences mSharedPreferences = null;
|
||||
private boolean mIsChromebook = false;
|
||||
private UsbManager mUsbManager;
|
||||
private Handler mHandler;
|
||||
private BluetoothManager mBluetoothManager;
|
||||
private List<BluetoothDevice> mLastBluetoothDevices;
|
||||
|
||||
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDeviceDetached(usbDevice);
|
||||
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
// Bluetooth device was connected. If it was a Steam Controller, handle it
|
||||
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
Log.d(TAG, "Bluetooth device connected: " + device);
|
||||
|
||||
if (isSteamController(device)) {
|
||||
connectBluetoothDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
// Bluetooth device was disconnected, remove from controller manager (if any)
|
||||
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
Log.d(TAG, "Bluetooth device disconnected: " + device);
|
||||
|
||||
disconnectBluetoothDevice(device);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private HIDDeviceManager(final Context context) {
|
||||
mContext = context;
|
||||
|
||||
HIDDeviceRegisterCallback();
|
||||
|
||||
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
|
||||
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||
|
||||
// if (shouldClear) {
|
||||
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||
// spedit.clear();
|
||||
// spedit.commit();
|
||||
// }
|
||||
// else
|
||||
{
|
||||
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
|
||||
}
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public int getDeviceIDForIdentifier(String identifier) {
|
||||
SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||
|
||||
int result = mSharedPreferences.getInt(identifier, 0);
|
||||
if (result == 0) {
|
||||
result = mNextDeviceId++;
|
||||
spedit.putInt("next_device_id", mNextDeviceId);
|
||||
}
|
||||
|
||||
spedit.putInt(identifier, result);
|
||||
spedit.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void initializeUSB() {
|
||||
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
|
||||
if (mUsbManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// Logging
|
||||
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
|
||||
Log.i(TAG,"Path: " + device.getDeviceName());
|
||||
Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
|
||||
Log.i(TAG,"Product: " + device.getProductName());
|
||||
Log.i(TAG,"ID: " + device.getDeviceId());
|
||||
Log.i(TAG,"Class: " + device.getDeviceClass());
|
||||
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
|
||||
Log.i(TAG,"Vendor ID " + device.getVendorId());
|
||||
Log.i(TAG,"Product ID: " + device.getProductId());
|
||||
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
|
||||
Log.i(TAG,"---------------------------------------");
|
||||
|
||||
// Get interface details
|
||||
for (int index = 0; index < device.getInterfaceCount(); index++) {
|
||||
UsbInterface mUsbInterface = device.getInterface(index);
|
||||
Log.i(TAG," ***** *****");
|
||||
Log.i(TAG," Interface index: " + index);
|
||||
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
|
||||
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
|
||||
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
|
||||
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
|
||||
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
|
||||
|
||||
// Get endpoint details
|
||||
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
|
||||
{
|
||||
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
|
||||
Log.i(TAG," ++++ ++++ ++++");
|
||||
Log.i(TAG," Endpoint index: " + epi);
|
||||
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
|
||||
Log.i(TAG," Direction: " + mEndpoint.getDirection());
|
||||
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
|
||||
Log.i(TAG," Interval: " + mEndpoint.getInterval());
|
||||
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
|
||||
Log.i(TAG," Type: " + mEndpoint.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.i(TAG," No more devices connected.");
|
||||
*/
|
||||
|
||||
// Register for USB broadcasts and permission completions
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
||||
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
mContext.registerReceiver(mUsbBroadcast, filter);
|
||||
|
||||
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
}
|
||||
}
|
||||
|
||||
UsbManager getUSBManager() {
|
||||
return mUsbManager;
|
||||
}
|
||||
|
||||
private void shutdownUSB() {
|
||||
try {
|
||||
mContext.unregisterReceiver(mUsbBroadcast);
|
||||
} catch (Exception e) {
|
||||
// We may not have registered, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
|
||||
return true;
|
||||
}
|
||||
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
final int XB360_IFACE_SUBCLASS = 93;
|
||||
final int XB360_IFACE_PROTOCOL = 1; // Wired
|
||||
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x0079, // GPD Win 2
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x046d, // Logitech
|
||||
0x056e, // Elecom
|
||||
0x06a3, // Saitek
|
||||
0x0738, // Mad Catz
|
||||
0x07ff, // Mad Catz
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x1038, // SteelSeries
|
||||
0x11c9, // Nacon
|
||||
0x12ab, // Unknown
|
||||
0x1430, // RedOctane
|
||||
0x146b, // BigBen
|
||||
0x1532, // Razer Sabertooth
|
||||
0x15e4, // Numark
|
||||
0x162e, // Joytech
|
||||
0x1689, // Razer Onza
|
||||
0x1949, // Lab126, Inc.
|
||||
0x1bad, // Harmonix
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2c22, // Qanba
|
||||
0x2dc8, // 8BitDo
|
||||
0x9886, // ASTRO Gaming
|
||||
};
|
||||
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
|
||||
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
|
||||
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
|
||||
int vendor_id = usbDevice.getVendorId();
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (vendor_id == supportedVid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
final int XB1_IFACE_SUBCLASS = 71;
|
||||
final int XB1_IFACE_PROTOCOL = 208;
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x03f0, // HP
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x0738, // Mad Catz
|
||||
0x0b05, // ASUS
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x10f5, // Turtle Beach
|
||||
0x1532, // Razer Wildcat
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2dc8, // 8BitDo
|
||||
0x2e24, // Hyperkin
|
||||
0x3537, // GameSir
|
||||
};
|
||||
|
||||
if (usbInterface.getId() == 0 &&
|
||||
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
|
||||
int vendor_id = usbDevice.getVendorId();
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (vendor_id == supportedVid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
|
||||
connectHIDDeviceUSB(usbDevice);
|
||||
}
|
||||
|
||||
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
|
||||
List<Integer> devices = new ArrayList<Integer>();
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
if (usbDevice.equals(device.getDevice())) {
|
||||
devices.add(device.getId());
|
||||
}
|
||||
}
|
||||
for (int id : devices) {
|
||||
HIDDevice device = mDevicesById.get(id);
|
||||
mDevicesById.remove(id);
|
||||
device.shutdown();
|
||||
HIDDeviceDisconnected(id);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
if (usbDevice.equals(device.getDevice())) {
|
||||
boolean opened = false;
|
||||
if (permission_granted) {
|
||||
opened = device.open();
|
||||
}
|
||||
HIDDeviceOpenResult(device.getId(), opened);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
|
||||
synchronized (this) {
|
||||
int interface_mask = 0;
|
||||
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
|
||||
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
|
||||
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
|
||||
// Check to see if we've already added this interface
|
||||
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
|
||||
int interface_id = usbInterface.getId();
|
||||
if ((interface_mask & (1 << interface_id)) != 0) {
|
||||
continue;
|
||||
}
|
||||
interface_mask |= (1 << interface_id);
|
||||
|
||||
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
|
||||
int id = device.getId();
|
||||
mDevicesById.put(id, device);
|
||||
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeBluetooth() {
|
||||
Log.d(TAG, "Initializing Bluetooth");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find bonded bluetooth controllers and create SteamControllers for them
|
||||
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (mBluetoothManager == null) {
|
||||
// This device doesn't support Bluetooth.
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
|
||||
if (btAdapter == null) {
|
||||
// This device has Bluetooth support in the codebase, but has no available adapters.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get our bonded devices.
|
||||
for (BluetoothDevice device : btAdapter.getBondedDevices()) {
|
||||
|
||||
Log.d(TAG, "Bluetooth device available: " + device);
|
||||
if (isSteamController(device)) {
|
||||
connectBluetoothDevice(device);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NOTE: These don't work on Chromebooks, to my undying dismay.
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
mContext.registerReceiver(mBluetoothBroadcast, filter);
|
||||
|
||||
if (mIsChromebook) {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
|
||||
|
||||
// final HIDDeviceManager finalThis = this;
|
||||
// mHandler.postDelayed(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// finalThis.chromebookConnectionHandler();
|
||||
// }
|
||||
// }, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownBluetooth() {
|
||||
try {
|
||||
mContext.unregisterReceiver(mBluetoothBroadcast);
|
||||
} catch (Exception e) {
|
||||
// We may not have registered, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
|
||||
// This function provides a sort of dummy version of that, watching for changes in the
|
||||
// connected devices and attempting to add controllers as things change.
|
||||
public void chromebookConnectionHandler() {
|
||||
if (!mIsChromebook) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
|
||||
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
|
||||
|
||||
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : currentConnected) {
|
||||
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
|
||||
connected.add(bluetoothDevice);
|
||||
}
|
||||
}
|
||||
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
|
||||
if (!currentConnected.contains(bluetoothDevice)) {
|
||||
disconnected.add(bluetoothDevice);
|
||||
}
|
||||
}
|
||||
|
||||
mLastBluetoothDevices = currentConnected;
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : disconnected) {
|
||||
disconnectBluetoothDevice(bluetoothDevice);
|
||||
}
|
||||
for (BluetoothDevice bluetoothDevice : connected) {
|
||||
connectBluetoothDevice(bluetoothDevice);
|
||||
}
|
||||
|
||||
final HIDDeviceManager finalThis = this;
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finalThis.chromebookConnectionHandler();
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
|
||||
synchronized (this) {
|
||||
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
|
||||
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
|
||||
|
||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||
device.reconnect();
|
||||
|
||||
return false;
|
||||
}
|
||||
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
|
||||
int id = device.getId();
|
||||
mBluetoothDevices.put(bluetoothDevice, device);
|
||||
mDevicesById.put(id, device);
|
||||
|
||||
// The Steam Controller will mark itself connected once initialization is complete
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||
synchronized (this) {
|
||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||
if (device == null)
|
||||
return;
|
||||
|
||||
int id = device.getId();
|
||||
mBluetoothDevices.remove(bluetoothDevice);
|
||||
mDevicesById.remove(id);
|
||||
device.shutdown();
|
||||
HIDDeviceDisconnected(id);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
|
||||
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
|
||||
if (bluetoothDevice == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the device has no local name, we really don't want to try an equality check against it.
|
||||
if (bluetoothDevice.getName() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
||||
}
|
||||
|
||||
private void close() {
|
||||
shutdownUSB();
|
||||
shutdownBluetooth();
|
||||
synchronized (this) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.shutdown();
|
||||
}
|
||||
mDevicesById.clear();
|
||||
mBluetoothDevices.clear();
|
||||
HIDDeviceReleaseCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public void setFrozen(boolean frozen) {
|
||||
synchronized (this) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.setFrozen(frozen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private HIDDevice getDevice(int id) {
|
||||
synchronized (this) {
|
||||
HIDDevice result = mDevicesById.get(id);
|
||||
if (result == null) {
|
||||
Log.v(TAG, "No device for id: " + id);
|
||||
Log.v(TAG, "Available devices: " + mDevicesById.keySet());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////// JNI interface functions
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public boolean initialize(boolean usb, boolean bluetooth) {
|
||||
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
|
||||
|
||||
if (usb) {
|
||||
initializeUSB();
|
||||
}
|
||||
if (bluetooth) {
|
||||
initializeBluetooth();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean openDevice(int deviceID) {
|
||||
Log.v(TAG, "openDevice deviceID=" + deviceID);
|
||||
HIDDevice device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look to see if this is a USB device and we have permission to access it
|
||||
UsbDevice usbDevice = device.getDevice();
|
||||
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
|
||||
HIDDeviceOpenPending(deviceID);
|
||||
try {
|
||||
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
||||
int flags;
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
|
||||
flags = FLAG_MUTABLE;
|
||||
} else {
|
||||
flags = 0;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {
|
||||
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
intent.setPackage(mContext.getPackageName());
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));
|
||||
} else {
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
|
||||
HIDDeviceOpenResult(deviceID, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return device.open();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int sendOutputReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return device.sendOutputReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int sendFeatureReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return device.sendFeatureReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean getFeatureReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
return device.getFeatureReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void closeDevice(int deviceID) {
|
||||
try {
|
||||
Log.v(TAG, "closeDevice deviceID=" + deviceID);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return;
|
||||
}
|
||||
|
||||
device.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////// Native methods
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private native void HIDDeviceRegisterCallback();
|
||||
private native void HIDDeviceReleaseCallback();
|
||||
|
||||
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
|
||||
native void HIDDeviceOpenPending(int deviceID);
|
||||
native void HIDDeviceOpenResult(int deviceID, boolean opened);
|
||||
native void HIDDeviceDisconnected(int deviceID);
|
||||
|
||||
native void HIDDeviceInputReport(int deviceID, byte[] report);
|
||||
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import java.util.Arrays;
|
||||
|
||||
class HIDDeviceUSB implements HIDDevice {
|
||||
|
||||
private static final String TAG = "hidapi";
|
||||
|
||||
protected HIDDeviceManager mManager;
|
||||
protected UsbDevice mDevice;
|
||||
protected int mInterfaceIndex;
|
||||
protected int mInterface;
|
||||
protected int mDeviceId;
|
||||
protected UsbDeviceConnection mConnection;
|
||||
protected UsbEndpoint mInputEndpoint;
|
||||
protected UsbEndpoint mOutputEndpoint;
|
||||
protected InputThread mInputThread;
|
||||
protected boolean mRunning;
|
||||
protected boolean mFrozen;
|
||||
|
||||
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
|
||||
mManager = manager;
|
||||
mDevice = usbDevice;
|
||||
mInterfaceIndex = interface_index;
|
||||
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
|
||||
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
|
||||
mRunning = false;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId() {
|
||||
return mDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProductId() {
|
||||
return mDevice.getProductId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
try {
|
||||
result = mDevice.getSerialNumber();
|
||||
}
|
||||
catch (SecurityException exception) {
|
||||
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = "";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getManufacturerName();
|
||||
}
|
||||
if (result == null) {
|
||||
result = String.format("%x", getVendorId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getProductName();
|
||||
}
|
||||
if (result == null) {
|
||||
result = String.format("%x", getProductId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
mConnection = mManager.getUSBManager().openDevice(mDevice);
|
||||
if (mConnection == null) {
|
||||
Log.w(TAG, "Unable to open USB device " + getDeviceName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Force claim our interface
|
||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||
if (!mConnection.claimInterface(iface, true)) {
|
||||
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the endpoints
|
||||
for (int j = 0; j < iface.getEndpointCount(); j++) {
|
||||
UsbEndpoint endpt = iface.getEndpoint(j);
|
||||
switch (endpt.getDirection()) {
|
||||
case UsbConstants.USB_DIR_IN:
|
||||
if (mInputEndpoint == null) {
|
||||
mInputEndpoint = endpt;
|
||||
}
|
||||
break;
|
||||
case UsbConstants.USB_DIR_OUT:
|
||||
if (mOutputEndpoint == null) {
|
||||
mOutputEndpoint = endpt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the required endpoints were present
|
||||
if (mInputEndpoint == null || mOutputEndpoint == null) {
|
||||
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start listening for input
|
||||
mRunning = true;
|
||||
mInputThread = new InputThread();
|
||||
mInputThread.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendFeatureReport(byte[] report) {
|
||||
int res = -1;
|
||||
int offset = 0;
|
||||
int length = report.length;
|
||||
boolean skipped_report_id = false;
|
||||
byte report_number = report[0];
|
||||
|
||||
if (report_number == 0x0) {
|
||||
++offset;
|
||||
--length;
|
||||
skipped_report_id = true;
|
||||
}
|
||||
|
||||
res = mConnection.controlTransfer(
|
||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
|
||||
0x09/*HID set_report*/,
|
||||
(3/*HID feature*/ << 8) | report_number,
|
||||
mInterface,
|
||||
report, offset, length,
|
||||
1000/*timeout millis*/);
|
||||
|
||||
if (res < 0) {
|
||||
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (skipped_report_id) {
|
||||
++length;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendOutputReport(byte[] report) {
|
||||
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
|
||||
if (r != report.length) {
|
||||
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeatureReport(byte[] report) {
|
||||
int res = -1;
|
||||
int offset = 0;
|
||||
int length = report.length;
|
||||
boolean skipped_report_id = false;
|
||||
byte report_number = report[0];
|
||||
|
||||
if (report_number == 0x0) {
|
||||
/* Offset the return buffer by 1, so that the report ID
|
||||
will remain in byte 0. */
|
||||
++offset;
|
||||
--length;
|
||||
skipped_report_id = true;
|
||||
}
|
||||
|
||||
res = mConnection.controlTransfer(
|
||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
|
||||
0x01/*HID get_report*/,
|
||||
(3/*HID feature*/ << 8) | report_number,
|
||||
mInterface,
|
||||
report, offset, length,
|
||||
1000/*timeout millis*/);
|
||||
|
||||
if (res < 0) {
|
||||
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (skipped_report_id) {
|
||||
++res;
|
||||
++length;
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
if (res == length) {
|
||||
data = report;
|
||||
} else {
|
||||
data = Arrays.copyOfRange(report, 0, res);
|
||||
}
|
||||
mManager.HIDDeviceFeatureReport(mDeviceId, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mRunning = false;
|
||||
if (mInputThread != null) {
|
||||
while (mInputThread.isAlive()) {
|
||||
mInputThread.interrupt();
|
||||
try {
|
||||
mInputThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// Keep trying until we're done
|
||||
}
|
||||
}
|
||||
mInputThread = null;
|
||||
}
|
||||
if (mConnection != null) {
|
||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||
mConnection.releaseInterface(iface);
|
||||
mConnection.close();
|
||||
mConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
mManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrozen(boolean frozen) {
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
protected class InputThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
int packetSize = mInputEndpoint.getMaxPacketSize();
|
||||
byte[] packet = new byte[packetSize];
|
||||
while (mRunning) {
|
||||
int r;
|
||||
try
|
||||
{
|
||||
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
|
||||
break;
|
||||
}
|
||||
if (r < 0) {
|
||||
// Could be a timeout or an I/O error
|
||||
}
|
||||
if (r > 0) {
|
||||
byte[] data;
|
||||
if (r == packetSize) {
|
||||
data = packet;
|
||||
} else {
|
||||
data = Arrays.copyOfRange(packet, 0, r);
|
||||
}
|
||||
|
||||
if (!mFrozen) {
|
||||
mManager.HIDDeviceInputReport(mDeviceId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
SDL library initialization
|
||||
*/
|
||||
public class SDL {
|
||||
|
||||
// This function should be called first and sets up the native code
|
||||
// so it can call into the Java classes
|
||||
public static void setupJNI() {
|
||||
SDLActivity.nativeSetupJNI();
|
||||
SDLAudioManager.nativeSetupJNI();
|
||||
SDLControllerManager.nativeSetupJNI();
|
||||
}
|
||||
|
||||
// This function should be called each time the activity is started
|
||||
public static void initialize() {
|
||||
setContext(null);
|
||||
|
||||
SDLActivity.initialize();
|
||||
SDLAudioManager.initialize();
|
||||
SDLControllerManager.initialize();
|
||||
}
|
||||
|
||||
// This function stores the current activity (SDL or not)
|
||||
public static void setContext(Context context) {
|
||||
SDLAudioManager.setContext(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
loadLibrary(libraryName, mContext);
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
|
||||
if (libraryName == null) {
|
||||
throw new NullPointerException("No library name provided.");
|
||||
}
|
||||
|
||||
try {
|
||||
// Let's see if we have ReLinker available in the project. This is necessary for
|
||||
// some projects that have huge numbers of local libraries bundled, and thus may
|
||||
// trip a bug in Android's native library loader which ReLinker works around. (If
|
||||
// loadLibrary works properly, ReLinker will simply use the normal Android method
|
||||
// internally.)
|
||||
//
|
||||
// To use ReLinker, just add it as a dependency. For more information, see
|
||||
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
|
||||
//
|
||||
Class<?> relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||
Class<?> relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||
Class<?> contextClass = context.getClassLoader().loadClass("android.content.Context");
|
||||
Class<?> stringClass = context.getClassLoader().loadClass("java.lang.String");
|
||||
|
||||
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
|
||||
// they've changed during updates.
|
||||
Method forceMethod = relinkClass.getDeclaredMethod("force");
|
||||
Object relinkInstance = forceMethod.invoke(null);
|
||||
Class<?> relinkInstanceClass = relinkInstance.getClass();
|
||||
|
||||
// Actually load the library!
|
||||
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
|
||||
loadMethod.invoke(relinkInstance, context, libraryName, null, null);
|
||||
}
|
||||
catch (final Throwable e) {
|
||||
// Fall back
|
||||
try {
|
||||
System.loadLibrary(libraryName);
|
||||
}
|
||||
catch (final UnsatisfiedLinkError ule) {
|
||||
throw ule;
|
||||
}
|
||||
catch (final SecurityException se) {
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static Context mContext;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,514 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioDeviceCallback;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.AudioTrack;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SDLAudioManager {
|
||||
protected static final String TAG = "SDLAudio";
|
||||
|
||||
protected static AudioTrack mAudioTrack;
|
||||
protected static AudioRecord mAudioRecord;
|
||||
protected static Context mContext;
|
||||
|
||||
private static final int[] NO_DEVICES = {};
|
||||
|
||||
private static AudioDeviceCallback mAudioDeviceCallback;
|
||||
|
||||
public static void initialize() {
|
||||
mAudioTrack = null;
|
||||
mAudioRecord = null;
|
||||
mAudioDeviceCallback = null;
|
||||
|
||||
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
|
||||
{
|
||||
mAudioDeviceCallback = new AudioDeviceCallback() {
|
||||
@Override
|
||||
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
|
||||
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
|
||||
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void setContext(Context context) {
|
||||
mContext = context;
|
||||
if (context != null) {
|
||||
registerAudioDeviceCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public static void release(Context context) {
|
||||
unregisterAudioDeviceCallback(context);
|
||||
}
|
||||
|
||||
// Audio
|
||||
|
||||
protected static String getAudioFormatString(int audioFormat) {
|
||||
switch (audioFormat) {
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
return "8-bit";
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
return "16-bit";
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
return "float";
|
||||
default:
|
||||
return Integer.toString(audioFormat);
|
||||
}
|
||||
}
|
||||
|
||||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
int channelConfig;
|
||||
int sampleSize;
|
||||
int frameSize;
|
||||
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
|
||||
|
||||
/* On older devices let's use known good settings */
|
||||
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
if (desiredChannels > 2) {
|
||||
desiredChannels = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
||||
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
|
||||
if (sampleRate < 8000) {
|
||||
sampleRate = 8000;
|
||||
} else if (sampleRate > 48000) {
|
||||
sampleRate = 48000;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
|
||||
if (Build.VERSION.SDK_INT < minSDKVersion) {
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
}
|
||||
}
|
||||
switch (audioFormat)
|
||||
{
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
sampleSize = 1;
|
||||
break;
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
sampleSize = 2;
|
||||
break;
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
sampleSize = 4;
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
sampleSize = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isCapture) {
|
||||
switch (desiredChannels) {
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_IN_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||
desiredChannels = 2;
|
||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (desiredChannels) {
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
case 3:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 4:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
|
||||
break;
|
||||
case 5:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 6:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
break;
|
||||
case 7:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||
} else {
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
|
||||
desiredChannels = 6;
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||
desiredChannels = 2;
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Log.v(TAG, "Speaker configuration (and order of channels):");
|
||||
|
||||
if ((channelConfig & 0x00000004) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00000008) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
|
||||
}
|
||||
if ((channelConfig & 0x00000010) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000020) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
|
||||
}
|
||||
if ((channelConfig & 0x00000040) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00000080) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
|
||||
}
|
||||
if ((channelConfig & 0x00000100) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000200) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000400) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000800) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00001000) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
|
||||
}
|
||||
*/
|
||||
}
|
||||
frameSize = (sampleSize * desiredChannels);
|
||||
|
||||
// Let the user pick a larger buffer if they really want -- but ye
|
||||
// gods they probably shouldn't, the minimums are horrifyingly high
|
||||
// latency already
|
||||
int minBufferSize;
|
||||
if (isCapture) {
|
||||
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
} else {
|
||||
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
}
|
||||
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
|
||||
|
||||
int[] results = new int[4];
|
||||
|
||||
if (isCapture) {
|
||||
if (mAudioRecord == null) {
|
||||
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
|
||||
channelConfig, audioFormat, desiredFrames * frameSize);
|
||||
|
||||
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
|
||||
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||
Log.e(TAG, "Failed during initialization of AudioRecord");
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioRecord.startRecording();
|
||||
}
|
||||
|
||||
results[0] = mAudioRecord.getSampleRate();
|
||||
results[1] = mAudioRecord.getAudioFormat();
|
||||
results[2] = mAudioRecord.getChannelCount();
|
||||
|
||||
} else {
|
||||
if (mAudioTrack == null) {
|
||||
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
|
||||
|
||||
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
|
||||
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
|
||||
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
|
||||
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
||||
/* Try again, with safer values */
|
||||
|
||||
Log.e(TAG, "Failed during initialization of Audio Track");
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioTrack.play();
|
||||
}
|
||||
|
||||
results[0] = mAudioTrack.getSampleRate();
|
||||
results[1] = mAudioTrack.getAudioFormat();
|
||||
results[2] = mAudioTrack.getChannelCount();
|
||||
}
|
||||
results[3] = desiredFrames;
|
||||
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerAudioDeviceCallback() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void unregisterAudioDeviceCallback(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioOutputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioInputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteFloatBuffer(float[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(float)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteShortBuffer(short[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(short)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteByteBuffer(byte[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length; ) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(byte)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return 0;
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void audioClose() {
|
||||
if (mAudioTrack != null) {
|
||||
mAudioTrack.stop();
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void captureClose() {
|
||||
if (mAudioRecord != null) {
|
||||
mAudioRecord.stop();
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
|
||||
try {
|
||||
|
||||
/* Set thread name */
|
||||
if (iscapture) {
|
||||
Thread.currentThread().setName("SDLAudioC" + device_id);
|
||||
} else {
|
||||
Thread.currentThread().setName("SDLAudioP" + device_id);
|
||||
}
|
||||
|
||||
/* Set thread priority */
|
||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "modify thread properties failed " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native void removeAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
public static native void addAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
}
|
||||
@@ -1,856 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
|
||||
public class SDLControllerManager
|
||||
{
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native int nativeAddJoystick(int device_id, String name, String desc,
|
||||
int vendor_id, int product_id,
|
||||
boolean is_accelerometer, int button_mask,
|
||||
int naxes, int axis_mask, int nhats, int nballs);
|
||||
public static native int nativeRemoveJoystick(int device_id);
|
||||
public static native int nativeAddHaptic(int device_id, String name);
|
||||
public static native int nativeRemoveHaptic(int device_id);
|
||||
public static native int onNativePadDown(int device_id, int keycode);
|
||||
public static native int onNativePadUp(int device_id, int keycode);
|
||||
public static native void onNativeJoy(int device_id, int axis,
|
||||
float value);
|
||||
public static native void onNativeHat(int device_id, int hat_id,
|
||||
int x, int y);
|
||||
|
||||
protected static SDLJoystickHandler mJoystickHandler;
|
||||
protected static SDLHapticHandler mHapticHandler;
|
||||
|
||||
private static final String TAG = "SDLControllerManager";
|
||||
|
||||
public static void initialize() {
|
||||
if (mJoystickHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
||||
mJoystickHandler = new SDLJoystickHandler_API19();
|
||||
} else {
|
||||
mJoystickHandler = new SDLJoystickHandler_API16();
|
||||
}
|
||||
}
|
||||
|
||||
if (mHapticHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
|
||||
mHapticHandler = new SDLHapticHandler_API26();
|
||||
} else {
|
||||
mHapticHandler = new SDLHapticHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
|
||||
public static boolean handleJoystickMotionEvent(MotionEvent event) {
|
||||
return mJoystickHandler.handleMotionEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void pollInputDevices() {
|
||||
mJoystickHandler.pollInputDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void pollHapticDevices() {
|
||||
mHapticHandler.pollHapticDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void hapticRun(int device_id, float intensity, int length) {
|
||||
mHapticHandler.run(device_id, intensity, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void hapticStop(int device_id)
|
||||
{
|
||||
mHapticHandler.stop(device_id);
|
||||
}
|
||||
|
||||
// Check if a given device is considered a possible SDL joystick
|
||||
public static boolean isDeviceSDLJoystick(int deviceId) {
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
// We cannot use InputDevice.isVirtual before API 16, so let's accept
|
||||
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
|
||||
if ((device == null) || (deviceId < 0)) {
|
||||
return false;
|
||||
}
|
||||
int sources = device.getSources();
|
||||
|
||||
/* This is called for every button press, so let's not spam the logs */
|
||||
/*
|
||||
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
|
||||
}
|
||||
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
|
||||
}
|
||||
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
|
||||
}
|
||||
*/
|
||||
|
||||
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
|
||||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
|
||||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SDLJoystickHandler {
|
||||
|
||||
/**
|
||||
* Handles given MotionEvent.
|
||||
* @param event the event to be handled.
|
||||
* @return if given event was processed.
|
||||
*/
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles adding and removing of input devices.
|
||||
*/
|
||||
public void pollInputDevices() {
|
||||
}
|
||||
}
|
||||
|
||||
/* Actual joystick functionality available for API >= 12 devices */
|
||||
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
|
||||
static class SDLJoystick {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public String desc;
|
||||
public ArrayList<InputDevice.MotionRange> axes;
|
||||
public ArrayList<InputDevice.MotionRange> hats;
|
||||
}
|
||||
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
|
||||
@Override
|
||||
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
|
||||
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
|
||||
int arg0Axis = arg0.getAxis();
|
||||
int arg1Axis = arg1.getAxis();
|
||||
if (arg0Axis == MotionEvent.AXIS_GAS) {
|
||||
arg0Axis = MotionEvent.AXIS_BRAKE;
|
||||
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
|
||||
arg0Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_GAS) {
|
||||
arg1Axis = MotionEvent.AXIS_BRAKE;
|
||||
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
|
||||
arg1Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
|
||||
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
|
||||
// This is because the usual pairing are:
|
||||
// - AXIS_X + AXIS_Y (left stick).
|
||||
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
|
||||
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
|
||||
// This sorts the axes in the above order, which tends to be correct
|
||||
// for Xbox-ish game pads that have the right stick on RX/RY and the
|
||||
// triggers on Z/RZ.
|
||||
//
|
||||
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
|
||||
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
|
||||
//
|
||||
// References:
|
||||
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
|
||||
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
|
||||
if (arg0Axis == MotionEvent.AXIS_Z) {
|
||||
arg0Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg0Axis;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_Z) {
|
||||
arg1Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg1Axis;
|
||||
}
|
||||
|
||||
return arg0Axis - arg1Axis;
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayList<SDLJoystick> mJoysticks;
|
||||
|
||||
public SDLJoystickHandler_API16() {
|
||||
|
||||
mJoysticks = new ArrayList<SDLJoystick>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pollInputDevices() {
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
|
||||
for (int device_id : deviceIds) {
|
||||
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
|
||||
SDLJoystick joystick = getJoystick(device_id);
|
||||
if (joystick == null) {
|
||||
InputDevice joystickDevice = InputDevice.getDevice(device_id);
|
||||
joystick = new SDLJoystick();
|
||||
joystick.device_id = device_id;
|
||||
joystick.name = joystickDevice.getName();
|
||||
joystick.desc = getJoystickDescriptor(joystickDevice);
|
||||
joystick.axes = new ArrayList<InputDevice.MotionRange>();
|
||||
joystick.hats = new ArrayList<InputDevice.MotionRange>();
|
||||
|
||||
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
|
||||
Collections.sort(ranges, new RangeComparator());
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
||||
joystick.hats.add(range);
|
||||
} else {
|
||||
joystick.axes.add(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mJoysticks.add(joystick);
|
||||
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
|
||||
getVendorId(joystickDevice), getProductId(joystickDevice), false,
|
||||
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
int device_id = joystick.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveJoystick(device_id);
|
||||
for (int i = 0; i < mJoysticks.size(); i++) {
|
||||
if (mJoysticks.get(i).device_id == device_id) {
|
||||
mJoysticks.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLJoystick getJoystick(int device_id) {
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
if (joystick.device_id == device_id) {
|
||||
return joystick;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
int actionPointerIndex = event.getActionIndex();
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
SDLJoystick joystick = getJoystick(event.getDeviceId());
|
||||
if (joystick != null) {
|
||||
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||
InputDevice.MotionRange range = joystick.axes.get(i);
|
||||
/* Normalize the value to -1...1 */
|
||||
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
|
||||
}
|
||||
for (int i = 0; i < joystick.hats.size() / 2; i++) {
|
||||
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
||||
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
||||
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getJoystickDescriptor(InputDevice joystickDevice) {
|
||||
String desc = joystickDevice.getDescriptor();
|
||||
|
||||
if (desc != null && !desc.isEmpty()) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
return joystickDevice.getName();
|
||||
}
|
||||
public int getProductId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
return -1;
|
||||
}
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||
|
||||
@Override
|
||||
public int getProductId(InputDevice joystickDevice) {
|
||||
return joystickDevice.getProductId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return joystickDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
// For compatibility, keep computing the axis mask like before,
|
||||
// only really distinguishing 2, 4 and 6 axes.
|
||||
int axis_mask = 0;
|
||||
if (ranges.size() >= 2) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
|
||||
axis_mask |= 0x0003;
|
||||
}
|
||||
if (ranges.size() >= 4) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
|
||||
axis_mask |= 0x000c;
|
||||
}
|
||||
if (ranges.size() >= 6) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
||||
axis_mask |= 0x0030;
|
||||
}
|
||||
// Also add an indicator bit for whether the sorting order has changed.
|
||||
// This serves to disable outdated gamecontrollerdb.txt mappings.
|
||||
boolean have_z = false;
|
||||
boolean have_past_z_before_rz = false;
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
int axis = range.getAxis();
|
||||
if (axis == MotionEvent.AXIS_Z) {
|
||||
have_z = true;
|
||||
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
|
||||
have_past_z_before_rz = true;
|
||||
}
|
||||
}
|
||||
if (have_z && have_past_z_before_rz) {
|
||||
// If both these exist, the compare() function changed sorting order.
|
||||
// Set a bit to indicate this fact.
|
||||
axis_mask |= 0x8000;
|
||||
}
|
||||
return axis_mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
int button_mask = 0;
|
||||
int[] keys = new int[] {
|
||||
KeyEvent.KEYCODE_BUTTON_A,
|
||||
KeyEvent.KEYCODE_BUTTON_B,
|
||||
KeyEvent.KEYCODE_BUTTON_X,
|
||||
KeyEvent.KEYCODE_BUTTON_Y,
|
||||
KeyEvent.KEYCODE_BACK,
|
||||
KeyEvent.KEYCODE_MENU,
|
||||
KeyEvent.KEYCODE_BUTTON_MODE,
|
||||
KeyEvent.KEYCODE_BUTTON_START,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBR,
|
||||
KeyEvent.KEYCODE_BUTTON_L1,
|
||||
KeyEvent.KEYCODE_BUTTON_R1,
|
||||
KeyEvent.KEYCODE_DPAD_UP,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||
KeyEvent.KEYCODE_BUTTON_SELECT,
|
||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||
|
||||
// These don't map into any SDL controller buttons directly
|
||||
KeyEvent.KEYCODE_BUTTON_L2,
|
||||
KeyEvent.KEYCODE_BUTTON_R2,
|
||||
KeyEvent.KEYCODE_BUTTON_C,
|
||||
KeyEvent.KEYCODE_BUTTON_Z,
|
||||
KeyEvent.KEYCODE_BUTTON_1,
|
||||
KeyEvent.KEYCODE_BUTTON_2,
|
||||
KeyEvent.KEYCODE_BUTTON_3,
|
||||
KeyEvent.KEYCODE_BUTTON_4,
|
||||
KeyEvent.KEYCODE_BUTTON_5,
|
||||
KeyEvent.KEYCODE_BUTTON_6,
|
||||
KeyEvent.KEYCODE_BUTTON_7,
|
||||
KeyEvent.KEYCODE_BUTTON_8,
|
||||
KeyEvent.KEYCODE_BUTTON_9,
|
||||
KeyEvent.KEYCODE_BUTTON_10,
|
||||
KeyEvent.KEYCODE_BUTTON_11,
|
||||
KeyEvent.KEYCODE_BUTTON_12,
|
||||
KeyEvent.KEYCODE_BUTTON_13,
|
||||
KeyEvent.KEYCODE_BUTTON_14,
|
||||
KeyEvent.KEYCODE_BUTTON_15,
|
||||
KeyEvent.KEYCODE_BUTTON_16,
|
||||
};
|
||||
int[] masks = new int[] {
|
||||
(1 << 0), // A -> A
|
||||
(1 << 1), // B -> B
|
||||
(1 << 2), // X -> X
|
||||
(1 << 3), // Y -> Y
|
||||
(1 << 4), // BACK -> BACK
|
||||
(1 << 6), // MENU -> START
|
||||
(1 << 5), // MODE -> GUIDE
|
||||
(1 << 6), // START -> START
|
||||
(1 << 7), // THUMBL -> LEFTSTICK
|
||||
(1 << 8), // THUMBR -> RIGHTSTICK
|
||||
(1 << 9), // L1 -> LEFTSHOULDER
|
||||
(1 << 10), // R1 -> RIGHTSHOULDER
|
||||
(1 << 11), // DPAD_UP -> DPAD_UP
|
||||
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
|
||||
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
|
||||
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
|
||||
(1 << 4), // SELECT -> BACK
|
||||
(1 << 0), // DPAD_CENTER -> A
|
||||
(1 << 15), // L2 -> ??
|
||||
(1 << 16), // R2 -> ??
|
||||
(1 << 17), // C -> ??
|
||||
(1 << 18), // Z -> ??
|
||||
(1 << 20), // 1 -> ??
|
||||
(1 << 21), // 2 -> ??
|
||||
(1 << 22), // 3 -> ??
|
||||
(1 << 23), // 4 -> ??
|
||||
(1 << 24), // 5 -> ??
|
||||
(1 << 25), // 6 -> ??
|
||||
(1 << 26), // 7 -> ??
|
||||
(1 << 27), // 8 -> ??
|
||||
(1 << 28), // 9 -> ??
|
||||
(1 << 29), // 10 -> ??
|
||||
(1 << 30), // 11 -> ??
|
||||
(1 << 31), // 12 -> ??
|
||||
// We're out of room...
|
||||
0xFFFFFFFF, // 13 -> ??
|
||||
0xFFFFFFFF, // 14 -> ??
|
||||
0xFFFFFFFF, // 15 -> ??
|
||||
0xFFFFFFFF, // 16 -> ??
|
||||
};
|
||||
boolean[] has_keys = joystickDevice.hasKeys(keys);
|
||||
for (int i = 0; i < keys.length; ++i) {
|
||||
if (has_keys[i]) {
|
||||
button_mask |= masks[i];
|
||||
}
|
||||
}
|
||||
return button_mask;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLHapticHandler_API26 extends SDLHapticHandler {
|
||||
@Override
|
||||
public void run(int device_id, float intensity, int length) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
|
||||
if (intensity == 0.0f) {
|
||||
stop(device_id);
|
||||
return;
|
||||
}
|
||||
|
||||
int vibeValue = Math.round(intensity * 255);
|
||||
|
||||
if (vibeValue > 255) {
|
||||
vibeValue = 255;
|
||||
}
|
||||
if (vibeValue < 1) {
|
||||
stop(device_id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
|
||||
// something went horribly wrong with the Android 8.0 APIs.
|
||||
haptic.vib.vibrate(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLHapticHandler {
|
||||
|
||||
static class SDLHaptic {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public Vibrator vib;
|
||||
}
|
||||
|
||||
private final ArrayList<SDLHaptic> mHaptics;
|
||||
|
||||
public SDLHapticHandler() {
|
||||
mHaptics = new ArrayList<SDLHaptic>();
|
||||
}
|
||||
|
||||
public void run(int device_id, float intensity, int length) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
haptic.vib.vibrate(length);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(int device_id) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
haptic.vib.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void pollHapticDevices() {
|
||||
|
||||
final int deviceId_VIBRATOR_SERVICE = 999999;
|
||||
boolean hasVibratorService = false;
|
||||
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
// It helps processing the device ids in reverse order
|
||||
// For example, in the case of the XBox 360 wireless dongle,
|
||||
// so the first controller seen by SDL matches what the receiver
|
||||
// considers to be the first controller
|
||||
|
||||
for (int i = deviceIds.length - 1; i > -1; i--) {
|
||||
SDLHaptic haptic = getHaptic(deviceIds[i]);
|
||||
if (haptic == null) {
|
||||
InputDevice device = InputDevice.getDevice(deviceIds[i]);
|
||||
Vibrator vib = device.getVibrator();
|
||||
if (vib != null) {
|
||||
if (vib.hasVibrator()) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceIds[i];
|
||||
haptic.name = device.getName();
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check VIBRATOR_SERVICE */
|
||||
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (vib != null) {
|
||||
hasVibratorService = vib.hasVibrator();
|
||||
|
||||
if (hasVibratorService) {
|
||||
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
|
||||
if (haptic == null) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceId_VIBRATOR_SERVICE;
|
||||
haptic.name = "VIBRATOR_SERVICE";
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
int device_id = haptic.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
|
||||
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
} // else: don't remove the vibrator if it is still present
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveHaptic(device_id);
|
||||
for (int i = 0; i < mHaptics.size(); i++) {
|
||||
if (mHaptics.get(i).device_id == device_id) {
|
||||
mHaptics.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLHaptic getHaptic(int device_id) {
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
if (haptic.device_id == device_id) {
|
||||
return haptic;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
float x, y;
|
||||
int action;
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Event was not managed
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsRelativeMouse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean inRelativeMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void reclaimRelativeMouseModeIfNeeded()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public float getEventX(MotionEvent event) {
|
||||
return event.getX(0);
|
||||
}
|
||||
|
||||
public float getEventY(MotionEvent event) {
|
||||
return event.getY(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
|
||||
private boolean mRelativeModeEnabled;
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
|
||||
// Handle relative mouse mode
|
||||
if (mRelativeModeEnabled) {
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Event was not managed, call SDLGenericMotionListener_API12 method
|
||||
return super.onGenericMotion(v, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
mRelativeModeEnabled = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventX(MotionEvent event) {
|
||||
if (mRelativeModeEnabled) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
} else {
|
||||
return event.getX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventY(MotionEvent event) {
|
||||
if (mRelativeModeEnabled) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
} else {
|
||||
return event.getY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
private boolean mRelativeModeEnabled;
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
float x, y;
|
||||
int action;
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
// DeX desktop mouse cursor is a separate non-standard input type.
|
||||
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case InputDevice.SOURCE_MOUSE_RELATIVE:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Event was not managed
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
|
||||
if (enabled) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
} else {
|
||||
SDLActivity.getContentView().releasePointerCapture();
|
||||
}
|
||||
mRelativeModeEnabled = enabled;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reclaimRelativeMouseModeIfNeeded()
|
||||
{
|
||||
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventX(MotionEvent event) {
|
||||
// Relative mouse in capture mode will only have relative for X/Y
|
||||
return event.getX(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventY(MotionEvent event) {
|
||||
// Relative mouse in capture mode will only have relative for X/Y
|
||||
return event.getY(0);
|
||||
}
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
|
||||
/**
|
||||
SDLSurface. This is what we draw on, so we need to know when it's created
|
||||
in order to do anything useful.
|
||||
|
||||
Because of this, that's where we set up the SDL thread
|
||||
*/
|
||||
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
|
||||
|
||||
// Sensors
|
||||
protected SensorManager mSensorManager;
|
||||
protected Display mDisplay;
|
||||
|
||||
// Keep track of the surface size to normalize touch events
|
||||
protected float mWidth, mHeight;
|
||||
|
||||
// Is SurfaceView ready for rendering
|
||||
public boolean mIsSurfaceReady;
|
||||
|
||||
// Startup
|
||||
public SDLSurface(Context context) {
|
||||
super(context);
|
||||
getHolder().addCallback(this);
|
||||
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
|
||||
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||
|
||||
setOnGenericMotionListener(SDLActivity.getMotionListener());
|
||||
|
||||
// Some arbitrary defaults to avoid a potential division by zero
|
||||
mWidth = 1.0f;
|
||||
mHeight = 1.0f;
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
}
|
||||
|
||||
public void handlePause() {
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
|
||||
}
|
||||
|
||||
public void handleResume() {
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
|
||||
}
|
||||
|
||||
public Surface getNativeSurface() {
|
||||
return getHolder().getSurface();
|
||||
}
|
||||
|
||||
// Called when we have a valid drawing surface
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceCreated()");
|
||||
SDLActivity.onNativeSurfaceCreated();
|
||||
}
|
||||
|
||||
// Called when we lose the surface
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceDestroyed()");
|
||||
|
||||
// Transition to pause, if needed
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
||||
SDLActivity.handleNativeState();
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
SDLActivity.onNativeSurfaceDestroyed();
|
||||
}
|
||||
|
||||
// Called when the surface is resized
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder,
|
||||
int format, int width, int height) {
|
||||
Log.v("SDL", "surfaceChanged()");
|
||||
|
||||
if (SDLActivity.mSingleton == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
int nDeviceWidth = width;
|
||||
int nDeviceHeight = height;
|
||||
try
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
|
||||
DisplayMetrics realMetrics = new DisplayMetrics();
|
||||
mDisplay.getRealMetrics( realMetrics );
|
||||
nDeviceWidth = realMetrics.widthPixels;
|
||||
nDeviceHeight = realMetrics.heightPixels;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
synchronized(SDLActivity.getContext()) {
|
||||
// In case we're waiting on a size change after going fullscreen, send a notification.
|
||||
SDLActivity.getContext().notifyAll();
|
||||
}
|
||||
|
||||
Log.v("SDL", "Window size: " + width + "x" + height);
|
||||
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
|
||||
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
|
||||
SDLActivity.onNativeResize();
|
||||
|
||||
// Prevent a screen distortion glitch,
|
||||
// for instance when the device is in Landscape and a Portrait App is resumed.
|
||||
boolean skip = false;
|
||||
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
|
||||
|
||||
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
|
||||
if (mWidth > mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
|
||||
if (mWidth < mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Special Patch for Square Resolution: Black Berry Passport
|
||||
if (skip) {
|
||||
double min = Math.min(mWidth, mHeight);
|
||||
double max = Math.max(mWidth, mHeight);
|
||||
|
||||
if (max / min < 1.20) {
|
||||
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't skip in MultiWindow.
|
||||
if (skip) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
|
||||
Log.v("SDL", "Don't skip in Multi-Window");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
Log.v("SDL", "Skip .. Surface is not ready.");
|
||||
mIsSurfaceReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
|
||||
SDLActivity.onNativeSurfaceChanged();
|
||||
|
||||
/* Surface is ready */
|
||||
mIsSurfaceReady = true;
|
||||
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
||||
SDLActivity.handleNativeState();
|
||||
}
|
||||
|
||||
// Key events
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
|
||||
}
|
||||
|
||||
// Touch events
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
/* Ref: http://developer.android.com/training/gestures/multi.html */
|
||||
int touchDevId = event.getDeviceId();
|
||||
final int pointerCount = event.getPointerCount();
|
||||
int action = event.getActionMasked();
|
||||
int pointerFingerId;
|
||||
int i = -1;
|
||||
float x,y,p;
|
||||
|
||||
/*
|
||||
* Prevent id to be -1, since it's used in SDL internal for synthetic events
|
||||
* Appears when using Android emulator, eg:
|
||||
* adb shell input mouse tap 100 100
|
||||
* adb shell input touchscreen tap 100 100
|
||||
*/
|
||||
if (touchDevId < 0) {
|
||||
touchDevId -= 1;
|
||||
}
|
||||
|
||||
// 12290 = Samsung DeX mode desktop mouse
|
||||
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
|
||||
// 0x2 = SOURCE_CLASS_POINTER
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||
int mouseButton = 1;
|
||||
try {
|
||||
Object object = event.getClass().getMethod("getButtonState").invoke(event);
|
||||
if (object != null) {
|
||||
mouseButton = (Integer) object;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
|
||||
// if we are. We'll leverage our existing mouse motion listener
|
||||
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
|
||||
x = motionListener.getEventX(event);
|
||||
y = motionListener.getEventY(event);
|
||||
|
||||
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
|
||||
} else {
|
||||
switch(action) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Primary pointer up/down, the index is always zero
|
||||
i = 0;
|
||||
/* fallthrough */
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
// Non primary pointer up/down
|
||||
if (i == -1) {
|
||||
i = event.getActionIndex();
|
||||
}
|
||||
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sensor events
|
||||
public void enableSensor(int sensortype, boolean enabled) {
|
||||
// TODO: This uses getDefaultSensor - what if we have >1 accels?
|
||||
if (enabled) {
|
||||
mSensorManager.registerListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype),
|
||||
SensorManager.SENSOR_DELAY_GAME, null);
|
||||
} else {
|
||||
mSensorManager.unregisterListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
||||
|
||||
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
|
||||
// We thus should check here.
|
||||
int newOrientation;
|
||||
|
||||
float x, y;
|
||||
switch (mDisplay.getRotation()) {
|
||||
case Surface.ROTATION_90:
|
||||
x = -event.values[1];
|
||||
y = event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
x = event.values[1];
|
||||
y = -event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
x = -event.values[0];
|
||||
y = -event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_0:
|
||||
default:
|
||||
x = event.values[0];
|
||||
y = event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newOrientation != SDLActivity.mCurrentOrientation) {
|
||||
SDLActivity.mCurrentOrientation = newOrientation;
|
||||
SDLActivity.onNativeOrientationChanged(newOrientation);
|
||||
}
|
||||
|
||||
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
|
||||
y / SensorManager.GRAVITY_EARTH,
|
||||
event.values[2] / SensorManager.GRAVITY_EARTH);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Captured pointer events for API 26.
|
||||
public boolean onCapturedPointerEvent(MotionEvent event)
|
||||
{
|
||||
int action = event.getActionMasked();
|
||||
|
||||
float x, y;
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_BUTTON_PRESS:
|
||||
case MotionEvent.ACTION_BUTTON_RELEASE:
|
||||
|
||||
// Change our action value to what SDL's code expects.
|
||||
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
|
||||
action = MotionEvent.ACTION_DOWN;
|
||||
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
|
||||
action = MotionEvent.ACTION_UP;
|
||||
}
|
||||
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
int button = event.getButtonState();
|
||||
|
||||
SDLActivity.onNativeMouse(button, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -1,7 +1,8 @@
|
||||
#cmakedefine PACKAGE "@PACKAGE@"
|
||||
#define VERSION "@XSYSTEM35_VERSION@"
|
||||
|
||||
#cmakedefine CACHE_TOTALSIZE @CACHE_TOTALSIZE@
|
||||
#ifndef XSYSTEM35_SDL_VERSION
|
||||
#define XSYSTEM35_SDL_VERSION @XSYSTEM35_SDL_VERSION@
|
||||
#endif
|
||||
|
||||
#cmakedefine ENABLE_DEBUGGER @ENABLE_DEBUGGER@
|
||||
#cmakedefine ENABLE_MIDI_SDLMIXER @ENABLE_MIDI_SDLMIXER@
|
||||
|
||||
@@ -12,8 +12,8 @@ or in your `.xsys35rc` file. For example:
|
||||
xsystem35 -censor misc/censor/kichikuou.txt
|
||||
```
|
||||
|
||||
The list files contain image numbers (integers), one per line. Lines starting
|
||||
with `#` are treated as comments.
|
||||
The list files contain image numbers (integers), one per line. A `#` starts a
|
||||
comment that continues to the end of the line.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# Censor list for ランス -光をもとめて- (Rance: The Quest for Hikari)
|
||||
# Images marked with a * might be allowed in an 18+ stream.
|
||||
5
|
||||
6
|
||||
8
|
||||
22
|
||||
26
|
||||
27
|
||||
29
|
||||
32
|
||||
39
|
||||
40
|
||||
41
|
||||
51
|
||||
56
|
||||
57
|
||||
75
|
||||
76
|
||||
77
|
||||
78 # *
|
||||
80
|
||||
82
|
||||
89
|
||||
91
|
||||
96 # *
|
||||
97 # *
|
||||
98
|
||||
99
|
||||
101
|
||||
208
|
||||
@@ -0,0 +1,59 @@
|
||||
# Censor list for ランスII (Rance II)
|
||||
# Images marked with a * might be allowed in an 18+ stream.
|
||||
26
|
||||
39 # *
|
||||
40
|
||||
41
|
||||
77
|
||||
78
|
||||
86
|
||||
98
|
||||
103
|
||||
104
|
||||
121
|
||||
122
|
||||
123
|
||||
124
|
||||
130
|
||||
131
|
||||
132
|
||||
133
|
||||
134
|
||||
140
|
||||
141
|
||||
142
|
||||
148
|
||||
156
|
||||
157
|
||||
158
|
||||
159
|
||||
161
|
||||
166
|
||||
171
|
||||
172
|
||||
185
|
||||
187
|
||||
191
|
||||
195
|
||||
197
|
||||
200
|
||||
201
|
||||
234
|
||||
236
|
||||
245
|
||||
246
|
||||
247
|
||||
272
|
||||
274
|
||||
275
|
||||
276
|
||||
278
|
||||
280
|
||||
281
|
||||
282
|
||||
284
|
||||
289
|
||||
292
|
||||
299
|
||||
300
|
||||
302
|
||||
@@ -0,0 +1,121 @@
|
||||
# Censor list for Rance 3
|
||||
# Images marked with a * might be allowed in an 18+ stream.
|
||||
1 # *
|
||||
2 # *
|
||||
3
|
||||
6
|
||||
8
|
||||
9
|
||||
32 # *
|
||||
53 # *
|
||||
54
|
||||
70
|
||||
71
|
||||
72
|
||||
73
|
||||
75
|
||||
77 # *
|
||||
78
|
||||
79
|
||||
112 # *
|
||||
113
|
||||
114
|
||||
115
|
||||
116
|
||||
117
|
||||
118 # *
|
||||
150
|
||||
151
|
||||
152
|
||||
153
|
||||
154
|
||||
155
|
||||
166
|
||||
167
|
||||
180
|
||||
181
|
||||
183
|
||||
184
|
||||
185
|
||||
186
|
||||
187
|
||||
188
|
||||
189
|
||||
190
|
||||
195
|
||||
196
|
||||
208
|
||||
209
|
||||
210
|
||||
211
|
||||
212
|
||||
213
|
||||
215
|
||||
216
|
||||
217
|
||||
218
|
||||
219
|
||||
231
|
||||
232
|
||||
233
|
||||
234
|
||||
235
|
||||
236
|
||||
238
|
||||
239
|
||||
260
|
||||
261
|
||||
263
|
||||
264
|
||||
265
|
||||
266
|
||||
268
|
||||
269
|
||||
330
|
||||
331
|
||||
332
|
||||
334
|
||||
335
|
||||
336
|
||||
337
|
||||
338
|
||||
339
|
||||
420
|
||||
421
|
||||
422
|
||||
423
|
||||
424
|
||||
425
|
||||
426
|
||||
427
|
||||
428
|
||||
429
|
||||
440
|
||||
441
|
||||
442
|
||||
443
|
||||
444
|
||||
445
|
||||
447
|
||||
473
|
||||
474
|
||||
475
|
||||
476
|
||||
477
|
||||
479
|
||||
519 # *
|
||||
521 # *
|
||||
523
|
||||
524
|
||||
525
|
||||
526
|
||||
530
|
||||
533 # *
|
||||
534
|
||||
535
|
||||
536
|
||||
537
|
||||
570
|
||||
571
|
||||
572
|
||||
581
|
||||
@@ -0,0 +1,168 @@
|
||||
# Censor list for Rance IV
|
||||
# Images marked with a * might be allowed in an 18+ stream.
|
||||
1
|
||||
2
|
||||
10
|
||||
20
|
||||
26
|
||||
28
|
||||
29
|
||||
30 # *
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39 # *
|
||||
40
|
||||
41
|
||||
42
|
||||
43
|
||||
44
|
||||
45
|
||||
46
|
||||
48
|
||||
49
|
||||
75
|
||||
76
|
||||
77
|
||||
78
|
||||
79
|
||||
122
|
||||
126
|
||||
140
|
||||
142
|
||||
148
|
||||
149
|
||||
162 # *
|
||||
163
|
||||
164
|
||||
165 # *
|
||||
166
|
||||
167
|
||||
168
|
||||
169 # *
|
||||
170
|
||||
171
|
||||
172
|
||||
176
|
||||
177
|
||||
178
|
||||
185
|
||||
186
|
||||
188
|
||||
189
|
||||
205
|
||||
207
|
||||
217 # *
|
||||
220
|
||||
221
|
||||
222
|
||||
223
|
||||
228
|
||||
241
|
||||
242
|
||||
251
|
||||
252
|
||||
253
|
||||
254
|
||||
260
|
||||
261
|
||||
262 # *
|
||||
264 # *
|
||||
265
|
||||
266
|
||||
267
|
||||
268
|
||||
269
|
||||
280
|
||||
281
|
||||
283
|
||||
284
|
||||
285
|
||||
286 # *
|
||||
287
|
||||
288
|
||||
292
|
||||
294
|
||||
299
|
||||
370
|
||||
371
|
||||
372
|
||||
373
|
||||
376
|
||||
378
|
||||
382
|
||||
384
|
||||
385 # *
|
||||
386
|
||||
387
|
||||
388
|
||||
389
|
||||
414 # *
|
||||
416
|
||||
428 # *
|
||||
531 # *
|
||||
532
|
||||
533
|
||||
534 # *
|
||||
535
|
||||
550
|
||||
551
|
||||
552
|
||||
553
|
||||
554
|
||||
555
|
||||
562 # *
|
||||
565
|
||||
615
|
||||
625
|
||||
626
|
||||
711
|
||||
723
|
||||
1055
|
||||
1056
|
||||
1057
|
||||
1058
|
||||
1059
|
||||
1060
|
||||
1061
|
||||
1062
|
||||
1545 # *
|
||||
1546 # *
|
||||
1547 # *
|
||||
1548 # *
|
||||
1549 # *
|
||||
1653 # *
|
||||
1654 # *
|
||||
1655 # *
|
||||
1656 # *
|
||||
1657 # *
|
||||
1658 # *
|
||||
1659 # *
|
||||
1660 # *
|
||||
1661 # *
|
||||
1662 # *
|
||||
1663 # *
|
||||
1664 # *
|
||||
1665 # *
|
||||
1728
|
||||
1729
|
||||
2004
|
||||
2005
|
||||
2006
|
||||
2007
|
||||
2008
|
||||
2009
|
||||
2010
|
||||
2011
|
||||
2012
|
||||
2013
|
||||
2014
|
||||
2015
|
||||
2016
|
||||
2017
|
||||
2018
|
||||
@@ -84,7 +84,7 @@ endif()
|
||||
|
||||
target_compile_options(modules PRIVATE -Wno-pointer-sign)
|
||||
|
||||
target_link_libraries(modules PRIVATE sdl2 zlib)
|
||||
if (TARGET sdl2_mixer)
|
||||
target_link_libraries(modules PRIVATE sdl2_mixer)
|
||||
target_link_libraries(modules PRIVATE xsystem35_sdl zlib)
|
||||
if (TARGET xsystem35_sdl_mixer)
|
||||
target_link_libraries(modules PRIVATE xsystem35_sdl_mixer)
|
||||
endif()
|
||||
|
||||
+9
-8
@@ -3,7 +3,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -76,12 +76,13 @@ void gr_copy(surface_t *dst, int dx, int dy, surface_t *src, int sx, int sy, int
|
||||
SDL_Rect r;
|
||||
if (SDL_IntersectRect(&src_rect, &dst_rect, &r)) {
|
||||
SDL_Surface *view = gfx_createSurfaceView(src->sdl_surface, sx, sy, sw, sh);
|
||||
SDL_Surface *tmp = SDL_ConvertSurface(view, dst->sdl_surface->format, 0);
|
||||
SDL_Surface *tmp = sdl_convert_surface(
|
||||
view, sdl_surface_format(dst->sdl_surface));
|
||||
src_rect.x = 0;
|
||||
src_rect.y = 0;
|
||||
SDL_LowerBlit(tmp, &src_rect, dst->sdl_surface, &dst_rect);
|
||||
SDL_FreeSurface(tmp);
|
||||
SDL_FreeSurface(view);
|
||||
sdl_destroy_surface(tmp);
|
||||
sdl_destroy_surface(view);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -99,7 +100,7 @@ void gr_copy_bright(surface_t *dst, int dx, int dy, surface_t *src, int sx, int
|
||||
|
||||
void gr_fill(surface_t *dst, int dx, int dy, int dw, int dh, int r, int g, int b) {
|
||||
SDL_Rect rect = {dx, dy, dw, dh};
|
||||
uint32_t color = SDL_MapRGB(dst->sdl_surface->format, r, g, b);
|
||||
uint32_t color = sdl_map_rgb(dst->sdl_surface, r, g, b);
|
||||
SDL_FillRect(dst->sdl_surface, &rect, color);
|
||||
}
|
||||
|
||||
@@ -227,12 +228,12 @@ void gr_copy_stretch_blend_alpha_map(surface_t *dst, int dx, int dy, int dw, int
|
||||
|
||||
// Fill the rectangle with the specified color (rgb) and blend rate (lv)
|
||||
void gr_fill_alpha_color(surface_t *dst, int dx, int dy, int dw, int dh, int r, int g, int b, int lv) {
|
||||
SDL_Surface *src = SDL_CreateRGBSurfaceWithFormat(0, dw, dh, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_FillRect(src, NULL, SDL_MapRGBA(src->format, r, g, b, lv));
|
||||
SDL_Surface *src = sdl_create_surface(dw, dh, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_FillRect(src, NULL, sdl_map_rgba(src, r, g, b, lv));
|
||||
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_BLEND);
|
||||
SDL_Rect dstrect = {dx, dy, dw, dh};
|
||||
SDL_BlitSurface(src, NULL, dst->sdl_surface, &dstrect);
|
||||
SDL_FreeSurface(src);
|
||||
sdl_destroy_surface(src);
|
||||
}
|
||||
|
||||
#include "graph2.c"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "surface.h"
|
||||
@@ -15,7 +15,8 @@ static surface_t *create(int width, int height, bool has_pixel, bool has_alpha)
|
||||
s->height = height;
|
||||
|
||||
if (has_pixel) {
|
||||
s->sdl_surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_RGB888);
|
||||
s->sdl_surface = sdl_create_surface(
|
||||
width, height, 32, SDL_PIXELFORMAT_RGB888);
|
||||
}
|
||||
|
||||
if (has_alpha) {
|
||||
@@ -47,7 +48,7 @@ surface_t *sf_create_pixel(int width, int height) {
|
||||
*/
|
||||
void sf_free(surface_t *s) {
|
||||
if (s == NULL) return;
|
||||
if (s->sdl_surface) SDL_FreeSurface(s->sdl_surface);
|
||||
if (s->sdl_surface) sdl_destroy_surface(s->sdl_surface);
|
||||
if (s->alpha) free(s->alpha);
|
||||
free(s);
|
||||
}
|
||||
@@ -67,7 +68,8 @@ surface_t *sf_dup(surface_t *in) {
|
||||
memcpy(sf, in, sizeof(surface_t));
|
||||
|
||||
if (in->sdl_surface) {
|
||||
sf->sdl_surface = SDL_ConvertSurface(in->sdl_surface, in->sdl_surface->format, 0);
|
||||
sf->sdl_surface = sdl_convert_surface(
|
||||
in->sdl_surface, sdl_surface_format(in->sdl_surface));
|
||||
}
|
||||
|
||||
if (in->alpha) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <stdio.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -194,5 +194,5 @@ void nt_gr_screencg(int no, int x, int y) {
|
||||
SDL_SetSurfaceBlendMode(sf, SDL_BLENDMODE_ADD);
|
||||
SDL_BlitSurface(sf, NULL, main_surface, &(SDL_Rect){x, y, sf->w, sf->h});
|
||||
ags_updateArea(x, y, sf->w, sf->h);
|
||||
SDL_FreeSurface(sf);
|
||||
sdl_destroy_surface(sf);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -142,7 +142,7 @@ void ntmsg_set_frame(int type) {
|
||||
nt_sp_set_show(night.sp[SPNO_MSGFRAME_BG], true);
|
||||
nt_sp_set_show(night.sp[SPNO_MSGFRAME_FG], true);
|
||||
|
||||
SDL_FillRect(sf, &msgframe_0, SDL_MapRGBA(sf->format, 16, 32, 64, 192));
|
||||
SDL_FillRect(sf, &msgframe_0, sdl_map_rgba(sf, 16, 32, 64, 192));
|
||||
ntmsg_clear(SPNO_MSGFRAME_FG);
|
||||
nt_sp_update_all(true);
|
||||
break;
|
||||
@@ -151,7 +151,7 @@ void ntmsg_set_frame(int type) {
|
||||
nt_sp_set_show(night.sp[SPNO_MSGFRAME_BG], false);
|
||||
nt_sp_set_show(night.sp[SPNO_MSGFRAME_FG], true);
|
||||
|
||||
SDL_FillRect(sf, &msgframe_1, SDL_MapRGBA(sf->format, 32, 32, 32, 128));
|
||||
SDL_FillRect(sf, &msgframe_1, sdl_map_rgba(sf, 32, 32, 32, 128));
|
||||
ntmsg_clear(SPNO_MSGFRAME_FG);
|
||||
nt_sp_update_all(true);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "sprite.h"
|
||||
@@ -62,7 +62,8 @@ sprite_t *nt_sp_msg_new(int no, int x, int y, int width, int height) {
|
||||
sp->height = height;
|
||||
sp->u.msg.dspcur.x = 0;
|
||||
sp->u.msg.dspcur.y = 0;
|
||||
sp->u.msg.canvas = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
sp->u.msg.canvas = sdl_create_surface(
|
||||
width, height, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
sp->update = ntmsg_update;
|
||||
|
||||
return sp;
|
||||
@@ -76,7 +77,7 @@ void nt_sp_free(sprite_t *sp) {
|
||||
if (sp->cg3) scg_deref(sp->cg3);
|
||||
|
||||
if (sp->type == SPRITE_MSG) {
|
||||
SDL_FreeSurface(sp->u.msg.canvas);
|
||||
sdl_destroy_surface(sp->u.msg.canvas);
|
||||
}
|
||||
|
||||
free(sp);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define __SPRITE_H__
|
||||
|
||||
#include "portab.h"
|
||||
#include <SDL_rect.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "sactcg.h"
|
||||
|
||||
#define DEFAULT_UPDATE nt_sp_draw
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -55,7 +55,7 @@ void nt_sp_draw(sprite_t *sp, SDL_Rect *r) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDL_ISPIXELFORMAT_ALPHA(cg->sf->format->format) || sp->blendrate < 255) {
|
||||
if (SDL_ISPIXELFORMAT_ALPHA(sdl_surface_format(cg->sf)) || sp->blendrate < 255) {
|
||||
SDL_SetSurfaceBlendMode(cg->sf, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetSurfaceAlphaMod(cg->sf, sp->blendrate);
|
||||
} else {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -204,5 +204,5 @@ void nt_sp_clear_updatelist(void) {
|
||||
|
||||
// デフォルトの壁紙update
|
||||
void nt_sp_draw_wall(sprite_t *sp, SDL_Rect *area) {
|
||||
SDL_FillRect(main_surface, area, SDL_MapRGB(main_surface->format, 0, 0, 0));
|
||||
SDL_FillRect(main_surface, area, sdl_map_rgb(main_surface, 0, 0, 0));
|
||||
}
|
||||
|
||||
@@ -60,10 +60,11 @@ static void ndd_run(int demonum) {
|
||||
|
||||
cgdata *cg = jpeg_extract(alk->entries[i].data, alk->entries[i].size);
|
||||
if (cg) {
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
|
||||
SDL_Surface *sf = sdl_create_surface_from(cg->pic, cg->width,
|
||||
cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
|
||||
SDL_BlitSurface(sf, NULL, main_surface, NULL);
|
||||
ags_updateFull();
|
||||
SDL_FreeSurface(sf);
|
||||
sdl_destroy_surface(sf);
|
||||
cgdata_free(cg);
|
||||
} else {
|
||||
WARNING("Cannot decode CG %d in %s", i, alk_path);
|
||||
|
||||
+22
-12
@@ -25,7 +25,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
#include "ald_manager.h"
|
||||
#include "audio_meta.h"
|
||||
#include "input.h"
|
||||
#include "msgskip.h"
|
||||
#include "xsystem35.h"
|
||||
@@ -146,7 +147,8 @@ static void Init() {
|
||||
}
|
||||
|
||||
// create depth map
|
||||
sact.dmap = SDL_CreateRGBSurface(0, main_surface->w, main_surface->h, 16, 0, 0, 0, 0);
|
||||
sact.dmap = sdl_create_surface_with_masks(
|
||||
main_surface->w, main_surface->h, 16, 0, 0, 0, 0);
|
||||
|
||||
// その他 System35 のデフォルト動作の変更
|
||||
ags_setAntialiasedStringMode(true);
|
||||
@@ -286,8 +288,11 @@ static void DrawEffect() {
|
||||
if (sact.version >= 110) {
|
||||
wEffectkey = getCaliValue();
|
||||
}
|
||||
|
||||
sp_eupdate(wType, wEffectTime, wEffectkey);
|
||||
|
||||
if (sact.waitskiplv > 1 || msgskip_isSkipping())
|
||||
sp_update_all(true);
|
||||
else
|
||||
sp_eupdate(wType, wEffectTime, wEffectkey);
|
||||
|
||||
TRACE("SACT.DrawEffect %d,%d,%d:", wType, wEffectTime, wEffectkey);
|
||||
}
|
||||
@@ -304,7 +309,10 @@ static void DrawEffectAlphaMap() {
|
||||
int wEffectTime = getCaliValue();
|
||||
int wEffectKey = getCaliValue();
|
||||
|
||||
sp_eupdate_amap(nIndexAlphaMap, wEffectTime, wEffectKey);
|
||||
if (sact.waitskiplv > 1 || msgskip_isSkipping())
|
||||
sp_update_all(true);
|
||||
else
|
||||
sp_eupdate_amap(nIndexAlphaMap, wEffectTime, wEffectKey);
|
||||
|
||||
TRACE("SACT.DrawEffectAlphaMap %d,%d,%d:", nIndexAlphaMap, wEffectTime, wEffectKey);
|
||||
}
|
||||
@@ -622,9 +630,10 @@ static void QuakeSprite() {
|
||||
if (sact.version >= 110) {
|
||||
nfKeyEnable = getCaliValue();
|
||||
}
|
||||
|
||||
sp_quake_sprite(wType, wAmplitudeX, wAmplitudeY, wCount, nfKeyEnable);
|
||||
|
||||
|
||||
if (!msgskip_isSkipping())
|
||||
sp_quake_sprite(wType, wAmplitudeX, wAmplitudeY, wCount, nfKeyEnable);
|
||||
|
||||
TRACE("SACT.QuakeSprite %d,%d,%d,%d:", wType, wAmplitudeX, wAmplitudeY, wCount);
|
||||
}
|
||||
|
||||
@@ -1615,7 +1624,7 @@ static void TimerWait() {
|
||||
|
||||
int msec = (wCount - stimer_get(wTimerID)) * 10;
|
||||
if (msec > 0)
|
||||
sys_keywait(msec, KEYWAIT_CTRL_CANCELABLE);
|
||||
sys_keywait(msec, KEYWAIT_CTRL_CANCELABLE | KEYWAIT_SKIPPABLE);
|
||||
|
||||
TRACE("SACT.TimerWait %d,%d:", wTimerID, wCount);
|
||||
}
|
||||
@@ -1628,7 +1637,7 @@ static void TimerWait() {
|
||||
static void Wait() {
|
||||
int wCount = getCaliValue();
|
||||
|
||||
sys_keywait(wCount * 10, KEYWAIT_CTRL_CANCELABLE);
|
||||
sys_keywait(wCount * 10, KEYWAIT_CTRL_CANCELABLE | KEYWAIT_SKIPPABLE);
|
||||
|
||||
TRACE("SACT.Wait %d:", wCount);
|
||||
}
|
||||
@@ -1845,8 +1854,9 @@ static void MusicPlay() {
|
||||
int wNum = getCaliValue();
|
||||
int wFadeTime = getCaliValue();
|
||||
int wVolume = getCaliValue();
|
||||
bgi_t *bgi = bgi_find(wNum);
|
||||
|
||||
musbgm_play(wNum, wFadeTime, wVolume, 0);
|
||||
musbgm_play(wNum, wFadeTime, wVolume, bgi ? bgi->loopno : 0);
|
||||
|
||||
TRACE("SACT.MusicPlay %d,%d,%d:", wNum, wFadeTime, wVolume);
|
||||
}
|
||||
@@ -2145,7 +2155,7 @@ static void SACT_reset(void) {
|
||||
sp_reset();
|
||||
|
||||
scg_freeall();
|
||||
SDL_FreeSurface(sact.dmap);
|
||||
sdl_destroy_surface(sact.dmap);
|
||||
memset(&sact, 0, sizeof(sact));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
#define __SACT_H__
|
||||
|
||||
#include "config.h"
|
||||
#include <SDL_rect.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "portab.h"
|
||||
#include "list.h"
|
||||
#include "ags.h"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -90,13 +90,14 @@ void sp_eupdate_amap(int index, int time, int cancel) {
|
||||
sp_update_all(true);
|
||||
return;
|
||||
}
|
||||
SDL_Surface *mask_sf = SDL_CreateRGBSurfaceFrom(mask->pic, mask->width, mask->height, 8, mask->width, 0, 0, 0, 0);
|
||||
SDL_Surface *mask_sf = sdl_create_surface_from(mask->pic, mask->width,
|
||||
mask->height, 8, mask->width, SDL_PIXELFORMAT_INDEX8);
|
||||
sp_update_all(false); // old = gfx_texture, new = main_surface
|
||||
struct effect *eff = effect_sactamask_init(mask_sf);
|
||||
ags_runEffect(time * 10, cancel, (ags_EffectStepFunc)effect_step, eff);
|
||||
effect_finish(eff);
|
||||
ags_updateFull();
|
||||
|
||||
SDL_FreeSurface(mask_sf);
|
||||
sdl_destroy_surface(mask_sf);
|
||||
cgdata_free(mask);
|
||||
}
|
||||
|
||||
+11
-6
@@ -26,7 +26,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -73,8 +73,9 @@ static void draw_log() {
|
||||
load_mincho_font();
|
||||
#endif
|
||||
|
||||
SDL_Surface *hline = SDL_CreateRGBSurfaceWithFormat(0, main_surface->w, 3, 32, SDL_PIXELFORMAT_RGB888);
|
||||
SDL_FillRect(hline, NULL, SDL_MapRGB(hline->format, 255, 255, 255));
|
||||
SDL_Surface *hline = sdl_create_surface(
|
||||
main_surface->w, 3, 32, SDL_PIXELFORMAT_RGB888);
|
||||
SDL_FillRect(hline, NULL, sdl_map_rgb(hline, 255, 255, 255));
|
||||
SDL_SetSurfaceBlendMode(hline, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetSurfaceAlphaMod(hline, 128);
|
||||
|
||||
@@ -97,7 +98,11 @@ static void draw_log() {
|
||||
if (0 == strcmp(str, "\n")) {
|
||||
SDL_BlitSurface(hline, NULL, main_surface, &(SDL_Rect){0, y + FONTSIZE/2, main_surface->w, 3});
|
||||
} else {
|
||||
FontSpec font_spec = { .type = cur < 6 ? FONT_MINCHO : FONT_GOTHIC, .size = FONTSIZE };
|
||||
FontSpec font_spec = {
|
||||
.type = cur < 6 ? FONT_MINCHO : FONT_GOTHIC,
|
||||
.weight = cur < 6 ? FONT_WEIGHT_NORMAL : FONT_WEIGHT_BOLD,
|
||||
.size = FONTSIZE
|
||||
};
|
||||
gfx_drawString(0, y, str, 255, font_spec);
|
||||
}
|
||||
y += FONTSIZE;
|
||||
@@ -118,7 +123,7 @@ bool sblog_start(void) {
|
||||
for (int i = 0; i < LOGMSG_LINES; i++)
|
||||
sact.log = list_append(sact.log, logmsg[i]);
|
||||
|
||||
back = SDL_ConvertSurface(main_surface, main_surface->format, 0);
|
||||
back = sdl_convert_surface(main_surface, sdl_surface_format(main_surface));
|
||||
curline = 6;
|
||||
draw_log();
|
||||
return true;
|
||||
@@ -131,7 +136,7 @@ void sblog_end(void) {
|
||||
SDL_BlitSurface(back, NULL, main_surface, NULL);
|
||||
ags_updateFull();
|
||||
|
||||
SDL_FreeSurface(back);
|
||||
sdl_destroy_surface(back);
|
||||
|
||||
// Remove instruction text
|
||||
for (i = 0; i < LOGMSG_LINES; i++) {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "system.h"
|
||||
#include "nact.h"
|
||||
#include "input.h"
|
||||
#include "msgskip.h"
|
||||
#include "sactsound.h"
|
||||
#include "music.h"
|
||||
#include "sact.h"
|
||||
@@ -120,7 +121,7 @@ void ssnd_waitkey(int no, vmvar_t *res) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sact.waitskiplv > 1) {
|
||||
if (sact.waitskiplv > 1 || msgskip_isSkipping()) {
|
||||
*res = SYS35KEY_RET;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "sacttimer.h"
|
||||
#include "system.h"
|
||||
|
||||
#define MAX_TIMER 10
|
||||
#define MAX_TIMER 128
|
||||
#define TICKS_PER_CENTISECOND 10
|
||||
|
||||
uint32_t ticks_base[MAX_TIMER];
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -65,7 +65,8 @@ static int compare_spriteno_smallfirst(const void *a, const void *b) {
|
||||
|
||||
// デフォルトの壁紙update
|
||||
static void sp_draw_wall(sprite_t *sp) {
|
||||
SDL_FillRect(main_surface, &sact.updaterect, SDL_MapRGB(main_surface->format, 0, 0, 0));
|
||||
SDL_FillRect(main_surface, &sact.updaterect,
|
||||
sdl_map_rgb(main_surface, 0, 0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,7 +208,8 @@ void sp_new_msg(int no, int x, int y, int width, int height) {
|
||||
sp->u.msg.buf = NULL;
|
||||
|
||||
// canvas for drawing text
|
||||
sp->u.msg.canvas = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
sp->u.msg.canvas = sdl_create_surface(
|
||||
width, height, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
|
||||
// スプライト再描画用コールバック
|
||||
sp->update = smsg_update;
|
||||
@@ -279,7 +281,7 @@ void sp_free(int no) {
|
||||
|
||||
if (sp->type == SPRITE_MSG) {
|
||||
slist_free(sp->u.msg.buf);
|
||||
SDL_FreeSurface(sp->u.msg.canvas);
|
||||
sdl_destroy_surface(sp->u.msg.canvas);
|
||||
}
|
||||
sact.updatelist = slist_remove(sact.updatelist, sp);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -38,7 +38,7 @@
|
||||
static void fill_dmap_mask(SDL_Surface *src, int sx, int sy, int dx ,int dy, int w, int h, uint16_t val) {
|
||||
uint8_t *sp, *dp;
|
||||
int x, y;
|
||||
assert(src->format->Amask == 0xff000000);
|
||||
assert(sdl_surface_alpha_mask(src) == 0xff000000);
|
||||
dp = PIXEL_AT(sact.dmap, dx, dy);
|
||||
sp = ALPHA_AT(src, sx, sy);
|
||||
|
||||
@@ -75,7 +75,7 @@ void sp_draw(sprite_t *sp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDL_ISPIXELFORMAT_ALPHA(cg->sf->format->format) || sp->blendrate < 255) {
|
||||
if (SDL_ISPIXELFORMAT_ALPHA(sdl_surface_format(cg->sf)) || sp->blendrate < 255) {
|
||||
SDL_SetSurfaceBlendMode(cg->sf, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetSurfaceAlphaMod(cg->sf, sp->blendrate);
|
||||
} else {
|
||||
@@ -116,7 +116,7 @@ void sp_draw_dmap(void* data, void* userdata) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDL_ISPIXELFORMAT_ALPHA(cg->sf->format->format)) {
|
||||
if (SDL_ISPIXELFORMAT_ALPHA(sdl_surface_format(cg->sf))) {
|
||||
fill_dmap_mask(cg->sf, sx, sy, dx, dy, w, h, sp->no);
|
||||
} else {
|
||||
SDL_FillRect(sact.dmap, &(SDL_Rect){dx, dy, w, h}, sp->no);
|
||||
|
||||
@@ -39,11 +39,6 @@
|
||||
@param cancel: キー抜け(0:なし, 1:あり)
|
||||
*/
|
||||
void sp_eupdate(int type, int time, int cancel) {
|
||||
if (sact.waitskiplv > 1) {
|
||||
sp_update_all(true);
|
||||
return;
|
||||
}
|
||||
|
||||
sp_update_all(false);
|
||||
|
||||
enum effect_type effect = from_sact_effect(type);
|
||||
|
||||
@@ -30,9 +30,11 @@
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
#include "ags.h"
|
||||
#include "gfx.h"
|
||||
#include "nact.h"
|
||||
#include "variable.h"
|
||||
#include "input.h"
|
||||
#include "msgskip.h"
|
||||
#include "sact.h"
|
||||
#include "sprite.h"
|
||||
#include "drawtext.h"
|
||||
@@ -240,7 +242,8 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
|
||||
char *msg;
|
||||
sprite_t *sp;
|
||||
int len = 0; // 処理した文字数?
|
||||
bool needupdate = false;
|
||||
bool has_batched_output = false;
|
||||
bool flush_batched_output = wSpeed > 0;
|
||||
SDL_Rect uparea = {0,0,0,0};
|
||||
|
||||
// wRSize == 0 -> ルビ無し(SACT.MessageOutputからの呼出)
|
||||
@@ -250,7 +253,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
|
||||
if (!is_messagesprite(wNum)) return;
|
||||
|
||||
// MessageSkip中は文字送り速度を最大に
|
||||
if (sact.waitskiplv > 1) wSpeed = 0;
|
||||
if (sact.waitskiplv > 1 || msgskip_isSkipping()) wSpeed = 0;
|
||||
|
||||
// shortcut
|
||||
sp = sact.sp[wNum];
|
||||
@@ -302,7 +305,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
|
||||
mbuf,
|
||||
wColorR, wColorG, wColorB);
|
||||
|
||||
needupdate = true;
|
||||
has_batched_output = true;
|
||||
|
||||
append_to_log(mbuf);
|
||||
|
||||
@@ -313,7 +316,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
|
||||
cw,
|
||||
wSize + wRSize + wRLineSpace);
|
||||
sp_update_clipped();
|
||||
needupdate = false;
|
||||
has_batched_output = false;
|
||||
|
||||
// keywait
|
||||
delta = sys_get_ticks() - wcnt;
|
||||
@@ -333,11 +336,17 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
|
||||
// バッファリング中の文字のクリア
|
||||
sact.msgbuf[0] = '\0';
|
||||
|
||||
// Waitなしの出力は最後にupdate
|
||||
if (needupdate) {
|
||||
// Register the update area after rendering without waits.
|
||||
if (has_batched_output) {
|
||||
uparea.w = sp->width;
|
||||
uparea.h = min(sp->height, uparea.y - sp->u.msg.dspcur.y + wLineSpace + wLineSpace + wRSize);
|
||||
uparea.h = min(sp->height - uparea.y,
|
||||
sp->u.msg.dspcur.y - uparea.y + wSize + wRSize + wRLineSpace);
|
||||
sp_updateme_part(sp, uparea.x, uparea.y, uparea.w, uparea.h);
|
||||
// Present now if waiting was skipped.
|
||||
if (flush_batched_output) {
|
||||
sp_update_clipped();
|
||||
gfx_updateScreen();
|
||||
}
|
||||
}
|
||||
|
||||
// ????
|
||||
@@ -408,7 +417,7 @@ int smsg_keywait(int wNum1, int wNum2, int msglen) {
|
||||
struct markinfo minfo[6];
|
||||
int i = 0, j, maxstep;
|
||||
|
||||
if (sact.waitskiplv > 0) {
|
||||
if (sact.waitskiplv > 0 || msgskip_isSkipping()) {
|
||||
sys_getInputInfo();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "nact.h"
|
||||
@@ -140,14 +140,14 @@ static void cb_select_release(agsevent_t *e) {
|
||||
}
|
||||
|
||||
static void draw_box(SDL_Surface *dst, int x, int y, int w, int h) {
|
||||
SDL_FillRect(dst, &(SDL_Rect){x, y, w, h}, SDL_MapRGB(dst->format, 0, 0, 0));
|
||||
SDL_FillRect(dst, &(SDL_Rect){x, y, w, h}, sdl_map_rgb(dst, 0, 0, 0));
|
||||
SDL_Rect border_rects[4] = {
|
||||
{x, y, w, 1},
|
||||
{x, y + h - 1, w, 1},
|
||||
{x, y, 1, h},
|
||||
{x + w - 1, y, 1, h}
|
||||
};
|
||||
SDL_FillRects(dst, border_rects, 4, SDL_MapRGB(dst->format, 255, 255, 255));
|
||||
SDL_FillRects(dst, border_rects, 4, sdl_map_rgb(dst, 255, 255, 255));
|
||||
}
|
||||
|
||||
// 選択ウィンドを更新するときの callback
|
||||
@@ -180,7 +180,8 @@ static void setup_selwindow() {
|
||||
int i;
|
||||
|
||||
// 選択肢文字用 canvas
|
||||
sact.sel.charcanvas = SDL_CreateRGBSurfaceWithFormat(0, sp->cg1->sf->w, sp->cg1->sf->h, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
sact.sel.charcanvas = sdl_create_surface(
|
||||
sp->cg1->sf->w, sp->cg1->sf->h, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_SetSurfaceBlendMode(sact.sel.charcanvas, SDL_BLENDMODE_BLEND);
|
||||
|
||||
dt_setfont(sact.sel.font_type, sact.sel.font_size);
|
||||
@@ -228,7 +229,7 @@ static void remove_selwindow() {
|
||||
|
||||
// 作業用 surface の削除
|
||||
if (sact.sel.charcanvas)
|
||||
SDL_FreeSurface(sact.sel.charcanvas);
|
||||
sdl_destroy_surface(sact.sel.charcanvas);
|
||||
sact.sel.charcanvas = NULL;
|
||||
}
|
||||
|
||||
@@ -266,7 +267,7 @@ void ssel_init() {
|
||||
void ssel_reset(void) {
|
||||
ssel_clear();
|
||||
if (sact.sel.charcanvas) {
|
||||
SDL_FreeSurface(sact.sel.charcanvas);
|
||||
sdl_destroy_surface(sact.sel.charcanvas);
|
||||
sact.sel.charcanvas = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +88,11 @@ static vmvar_t *add_p5[SLOT]; /* どこまでアニメーションのコマが
|
||||
|
||||
static void copy_sprite(int sx, int sy, int width, int height, int dx, int dy, int r, int g, int b) {
|
||||
SDL_Surface *sf = nact->ags.dib->sdl_surface;
|
||||
SDL_SetColorKey(sf, SDL_TRUE, SDL_MapRGB(sf->format, r, g, b));
|
||||
sdl_set_surface_color_key(sf, true, sdl_map_rgb(sf, r, g, b));
|
||||
SDL_Rect src_rect = { sx, sy, width, height };
|
||||
SDL_Rect dst_rect = { dx, dy, width, height };
|
||||
SDL_BlitSurface(sf, &src_rect, sf, &dst_rect);
|
||||
SDL_SetColorKey(sf, SDL_FALSE, 0);
|
||||
sdl_set_surface_color_key(sf, false, 0);
|
||||
}
|
||||
|
||||
static void Init() {
|
||||
|
||||
@@ -25,8 +25,9 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "LittleEndian.h"
|
||||
@@ -44,7 +45,7 @@ static struct {
|
||||
} memwav;
|
||||
|
||||
static void free_memory_wav(void) {
|
||||
SDL_FreeWAV(memwav.buf);
|
||||
sdl_free_wav(memwav.buf);
|
||||
memwav.buf = NULL;
|
||||
}
|
||||
|
||||
@@ -132,11 +133,15 @@ static void wavLoadMemory() {
|
||||
WARNING("cannot open WAVE %d", no - 1);
|
||||
return;
|
||||
}
|
||||
if (!SDL_LoadWAV_RW(SDL_RWFromConstMem(dfile->data, dfile->size), 1, &memwav.spec, &memwav.buf, &memwav.len)) {
|
||||
sdl_iostream_t *stream = sdl_io_from_const_memory(
|
||||
dfile->data, dfile->size);
|
||||
if (!stream || !sdl_load_wav_io(
|
||||
stream, true, &memwav.spec, &memwav.buf, &memwav.len)) {
|
||||
WARNING("cannot load WAVE %d", no - 1);
|
||||
return;
|
||||
}
|
||||
if (memwav.spec.channels != 2 || memwav.spec.format != AUDIO_S16LSB) {
|
||||
if (memwav.spec.channels != 2 ||
|
||||
memwav.spec.format != sdl_audio_s16le_format()) {
|
||||
WARNING("unexpected audio format");
|
||||
free_memory_wav();
|
||||
return;
|
||||
|
||||
+59
-53
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -31,7 +31,7 @@ static void render_quad(SDL_Texture *tex, SDL_Vertex verts[4]) {
|
||||
static void fade_overlay(Uint8 r, Uint8 g, Uint8 b, Uint8 alpha) {
|
||||
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderDrawColor(gfx_renderer, r, g, b, alpha);
|
||||
SDL_RenderFillRect(gfx_renderer, NULL);
|
||||
sdl_render_fill_rect(gfx_renderer, NULL);
|
||||
}
|
||||
|
||||
#define NR_SCENES 13
|
||||
@@ -121,7 +121,7 @@ static void Init() {
|
||||
continue;
|
||||
}
|
||||
dd.images[i] = SDL_CreateTextureFromSurface(gfx_renderer, sf);
|
||||
SDL_FreeSurface(sf);
|
||||
sdl_destroy_surface(sf);
|
||||
}
|
||||
*var = 1;
|
||||
TRACE("dDemo.Init %d,%d,%d,%p:", p1, p2, p3, var);
|
||||
@@ -144,7 +144,7 @@ static void render_affine_bg(SDL_Texture *scroll_bg, int elapsed, int duration,
|
||||
if (!scroll_bg) return;
|
||||
|
||||
int bg_w, bg_h;
|
||||
SDL_QueryTexture(scroll_bg, NULL, NULL, &bg_w, &bg_h);
|
||||
sdl_get_texture_size(scroll_bg, &bg_w, &bg_h);
|
||||
|
||||
int xs[4], ys[4];
|
||||
const float angle = angle_deg * (float)M_PI / 180.0f;
|
||||
@@ -183,7 +183,7 @@ static void render_affine_bg(SDL_Texture *scroll_bg, int elapsed, int duration,
|
||||
for (int i = 0; i < 4; i++) {
|
||||
verts[i].position.x = (i == 1 || i == 2) ? w : 0;
|
||||
verts[i].position.y = (i == 2 || i == 3) ? h : (float)y_toggle;
|
||||
verts[i].color = (SDL_Color){255, 255, 255, 255};
|
||||
verts[i].color = sdl_vertex_color(255, 255, 255, 255);
|
||||
verts[i].tex_coord.x = uv[i][0];
|
||||
verts[i].tex_coord.y = uv[i][1];
|
||||
}
|
||||
@@ -194,7 +194,7 @@ static void render_affine_bg(SDL_Texture *scroll_bg, int elapsed, int duration,
|
||||
y_toggle = (y_toggle + 1) & 1;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
verts[i].position.y = (i == 2 || i == 3) ? h : (float)y_toggle;
|
||||
verts[i].color = (SDL_Color){255, 255, 255, 128};
|
||||
verts[i].color = sdl_vertex_color(255, 255, 255, 128);
|
||||
}
|
||||
// Swap mapping: TL<->BR, TR<->BL for 180° rotation effect
|
||||
verts[0].tex_coord.x = uv[2][0]; verts[0].tex_coord.y = uv[2][1];
|
||||
@@ -212,7 +212,7 @@ static void render_scene0(int elapsed) {
|
||||
if (!sprite) return;
|
||||
|
||||
int spr_w, spr_h;
|
||||
SDL_QueryTexture(sprite, NULL, NULL, &spr_w, &spr_h);
|
||||
sdl_get_texture_size(sprite, &spr_w, &spr_h);
|
||||
const int slide_end = 4700;
|
||||
const int duration = scene_duration[0]; // 5400
|
||||
|
||||
@@ -234,12 +234,12 @@ static void render_scene0(int elapsed) {
|
||||
int dest_w = a0 - ((a0 - spr_w) * elapsed) / slide_end;
|
||||
if (dest_w <= 0) continue;
|
||||
SDL_Rect dst = {view_w - 2 * dest_w, dest_y, dest_w, spr_h};
|
||||
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
|
||||
sdl_render_texture(gfx_renderer, sprite, NULL, &dst);
|
||||
}
|
||||
} else {
|
||||
SDL_SetTextureAlphaMod(sprite, 0xd0);
|
||||
SDL_Rect dst = {view_w - 2 * spr_w, dest_y, spr_w, spr_h};
|
||||
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
|
||||
sdl_render_texture(gfx_renderer, sprite, NULL, &dst);
|
||||
}
|
||||
|
||||
// 3. Horizontal accent line (grows rightward over 4700ms)
|
||||
@@ -247,12 +247,12 @@ static void render_scene0(int elapsed) {
|
||||
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
|
||||
SDL_SetRenderDrawColor(gfx_renderer, 0xc0, 0xc0, 0xc0, 0xff);
|
||||
SDL_Rect hline = {0, (spr_h + view_h) / 2 - 4, line_w, 2};
|
||||
SDL_RenderFillRect(gfx_renderer, &hline);
|
||||
sdl_render_fill_rect(gfx_renderer, &hline);
|
||||
|
||||
// 4. Vertical accent line (right edge of sprite, grows downward)
|
||||
int line_h = elapsed < slide_end ? 479 * elapsed / slide_end + 1 : 480;
|
||||
SDL_Rect vline = {view_w - spr_w, 0, 2, line_h};
|
||||
SDL_RenderFillRect(gfx_renderer, &vline);
|
||||
sdl_render_fill_rect(gfx_renderer, &vline);
|
||||
}
|
||||
|
||||
static void render_scene1(int elapsed) {
|
||||
@@ -261,7 +261,7 @@ static void render_scene1(int elapsed) {
|
||||
if (!sprite) return;
|
||||
|
||||
int spr_w, spr_h;
|
||||
SDL_QueryTexture(sprite, NULL, NULL, &spr_w, &spr_h);
|
||||
sdl_get_texture_size(sprite, &spr_w, &spr_h);
|
||||
const int duration = scene_duration[1]; // 4752
|
||||
|
||||
// --- Background: DrawAffineSurfaceAlpha50 x2 (alpha=50%) ---
|
||||
@@ -275,7 +275,7 @@ static void render_scene1(int elapsed) {
|
||||
SDL_SetTextureBlendMode(sprite, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(sprite, (Uint8)alpha);
|
||||
SDL_Rect dst = {(view_w - spr_w) / 2, (view_h - spr_h) / 2, spr_w, spr_h};
|
||||
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
|
||||
sdl_render_texture(gfx_renderer, sprite, NULL, &dst);
|
||||
}
|
||||
|
||||
// Fade to black: graph_FillAlphaColor(0,0,0), starts at 4000ms.
|
||||
@@ -397,12 +397,14 @@ static void render_scene_2_4_6_3d(int elapsed, int phase_dur, SDL_Texture *tex1,
|
||||
// Object border quad: solid color RGB(244,221,181) = BGR(0xb5,0xdd,0xf4),
|
||||
// drawn first as a backdrop. The bg quad drawn on top leaves only the thin
|
||||
// border ring visible.
|
||||
for (int i = 0; i < 4; i++) obj_verts[i].color = (SDL_Color){0xf4, 0xdd, 0xb5, 0xff};
|
||||
for (int i = 0; i < 4; i++)
|
||||
obj_verts[i].color = sdl_vertex_color(0xf4, 0xdd, 0xb5, 0xff);
|
||||
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
|
||||
render_quad(NULL, obj_verts);
|
||||
|
||||
// Background quad: textured, opaque, drawn on top (covers center of border quad)
|
||||
for (int i = 0; i < 4; i++) bg_verts[i].color = (SDL_Color){255, 255, 255, 255};
|
||||
for (int i = 0; i < 4; i++)
|
||||
bg_verts[i].color = sdl_vertex_color(255, 255, 255, 255);
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
|
||||
render_quad(tex, bg_verts);
|
||||
}
|
||||
@@ -425,7 +427,7 @@ static void render_scene_2_4_6(int elapsed, int scene_idx, int sprite_idx, int t
|
||||
if (bg) {
|
||||
SDL_SetTextureBlendMode(bg, SDL_BLENDMODE_NONE);
|
||||
SDL_SetTextureColorMod(bg, 0xff, 0xff, 0xff);
|
||||
SDL_RenderCopy(gfx_renderer, bg, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, bg, NULL, NULL);
|
||||
}
|
||||
|
||||
// 2. 3D rotating quad animation (8-phase timing)
|
||||
@@ -439,23 +441,23 @@ static void render_scene_2_4_6(int elapsed, int scene_idx, int sprite_idx, int t
|
||||
// 3. Scrolling sprite: x moves from -sprite_w (off-screen left) to view_w (off-screen right)
|
||||
int sprite_w = 0, sprite_h = 0;
|
||||
if (sprite)
|
||||
SDL_QueryTexture(sprite, NULL, NULL, &sprite_w, &sprite_h);
|
||||
sdl_get_texture_size(sprite, &sprite_w, &sprite_h);
|
||||
int x = sprite_w ? ((sprite_w + view_w) * elapsed) / duration - sprite_w : 0;
|
||||
|
||||
// Shadow drawn first (color-modded to black), sprite on top (full color)
|
||||
if (sprite_shadow) {
|
||||
int sw, sh;
|
||||
SDL_QueryTexture(sprite_shadow, NULL, NULL, &sw, &sh);
|
||||
sdl_get_texture_size(sprite_shadow, &sw, &sh);
|
||||
SDL_SetTextureBlendMode(sprite_shadow, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureColorMod(sprite_shadow, 0, 0, 0);
|
||||
SDL_Rect dst = {x, 0, sw, sh};
|
||||
SDL_RenderCopy(gfx_renderer, sprite_shadow, NULL, &dst);
|
||||
sdl_render_texture(gfx_renderer, sprite_shadow, NULL, &dst);
|
||||
}
|
||||
if (sprite) {
|
||||
SDL_SetTextureBlendMode(sprite, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureColorMod(sprite, 0xff, 0xff, 0xff);
|
||||
SDL_Rect dst = {x, 0, sprite_w, sprite_h};
|
||||
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
|
||||
sdl_render_texture(gfx_renderer, sprite, NULL, &dst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +492,7 @@ static void render_scene_3_5_7_9(int elapsed, int image_idx) {
|
||||
if (alpha > 0) {
|
||||
SDL_SetTextureBlendMode(main_img, alpha < 255 ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
|
||||
SDL_SetTextureAlphaMod(main_img, (Uint8)alpha);
|
||||
SDL_RenderCopy(gfx_renderer, main_img, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, main_img, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,10 +502,14 @@ static void render_scene_3_5_7_9(int elapsed, int image_idx) {
|
||||
float t3 = (float)elapsed / (4000.0f * 3.0f); // 0 → 1/3
|
||||
float w = (float)view_w, h = (float)view_h;
|
||||
SDL_Vertex verts[4] = {
|
||||
/* TL */ {{0.f, 0.f}, {255, 255, 255, 128}, {t3, 0.f }},
|
||||
/* TR */ {{w, 0.f}, {255, 255, 255, 128}, {1.0f, t3 }},
|
||||
/* BR */ {{w, h }, {255, 255, 255, 128}, {1.0f - t3, 1.0f }},
|
||||
/* BL */ {{0.f, h }, {255, 255, 255, 128}, {0.f, 1.0f-t3}},
|
||||
{{0.f, 0.f}, sdl_vertex_color(255, 255, 255, 128),
|
||||
{t3, 0.f}},
|
||||
{{w, 0.f}, sdl_vertex_color(255, 255, 255, 128),
|
||||
{1.0f, t3}},
|
||||
{{w, h}, sdl_vertex_color(255, 255, 255, 128),
|
||||
{1.0f - t3, 1.0f}},
|
||||
{{0.f, h}, sdl_vertex_color(255, 255, 255, 128),
|
||||
{0.f, 1.0f - t3}},
|
||||
};
|
||||
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND);
|
||||
render_quad(overlay, verts);
|
||||
@@ -527,7 +533,7 @@ static void render_scene8(int elapsed) {
|
||||
if (imgs[0]) {
|
||||
SDL_SetTextureBlendMode(imgs[0], SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(imgs[0], (Uint8)SDL_min(alpha, 255));
|
||||
SDL_RenderCopy(gfx_renderer, imgs[0], NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, imgs[0], NULL, NULL);
|
||||
}
|
||||
} else if (elapsed < 6 * seg) {
|
||||
// Segments 2–6: crossfade image[idx] → image[idx+1]
|
||||
@@ -536,19 +542,19 @@ static void render_scene8(int elapsed) {
|
||||
if (imgs[idx]) {
|
||||
SDL_SetTextureBlendMode(imgs[idx], SDL_BLENDMODE_NONE);
|
||||
SDL_SetTextureAlphaMod(imgs[idx], 255);
|
||||
SDL_RenderCopy(gfx_renderer, imgs[idx], NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, imgs[idx], NULL, NULL);
|
||||
}
|
||||
if (imgs[idx + 1]) {
|
||||
SDL_SetTextureBlendMode(imgs[idx + 1], SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(imgs[idx + 1], (Uint8)SDL_min(alpha, 255));
|
||||
SDL_RenderCopy(gfx_renderer, imgs[idx + 1], NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, imgs[idx + 1], NULL, NULL);
|
||||
}
|
||||
} else {
|
||||
// Segment 7: hold image[5] at full opacity
|
||||
if (imgs[5]) {
|
||||
SDL_SetTextureBlendMode(imgs[5], SDL_BLENDMODE_NONE);
|
||||
SDL_SetTextureAlphaMod(imgs[5], 255);
|
||||
SDL_RenderCopy(gfx_renderer, imgs[5], NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, imgs[5], NULL, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -588,7 +594,7 @@ static void render_scene10(int elapsed) {
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
|
||||
SDL_SetTextureAlphaMod(tex, 255);
|
||||
SDL_SetTextureColorMod(tex, 255, 255, 255);
|
||||
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, tex, NULL, NULL);
|
||||
}
|
||||
} else if (elapsed < 9610) {
|
||||
// Phase 2: zoom in
|
||||
@@ -607,7 +613,7 @@ static void render_scene10(int elapsed) {
|
||||
SDL_SetTextureBlendMode(surface3, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(surface3, 255);
|
||||
SDL_SetTextureColorMod(surface3, 255, 255, 255);
|
||||
SDL_RenderCopy(gfx_renderer, surface3, &src_rect, NULL);
|
||||
sdl_render_texture(gfx_renderer, surface3, &src_rect, NULL);
|
||||
}
|
||||
} else {
|
||||
// Phase 3: fade out to black
|
||||
@@ -618,7 +624,7 @@ static void render_scene10(int elapsed) {
|
||||
combined < 255 ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
|
||||
SDL_SetTextureAlphaMod(surface3, (Uint8)combined);
|
||||
SDL_SetTextureColorMod(surface3, 255, 255, 255);
|
||||
SDL_RenderCopy(gfx_renderer, surface3, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, surface3, NULL, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -644,12 +650,12 @@ static void render_scene11(int elapsed) {
|
||||
// srcY = (bg_h - view_h) * (duration - elapsed) / duration
|
||||
if (bg) {
|
||||
int bg_w, bg_h;
|
||||
SDL_QueryTexture(bg, NULL, NULL, &bg_w, &bg_h);
|
||||
sdl_get_texture_size(bg, &bg_w, &bg_h);
|
||||
int scroll = (int)((long long)(bg_h - view_h) * elapsed / duration);
|
||||
int src_y = SDL_max(0, (bg_h - view_h) - scroll);
|
||||
SDL_Rect src = {0, src_y, SDL_min(view_w, bg_w), view_h};
|
||||
SDL_SetTextureBlendMode(bg, SDL_BLENDMODE_NONE);
|
||||
SDL_RenderCopy(gfx_renderer, bg, &src, NULL);
|
||||
sdl_render_texture(gfx_renderer, bg, &src, NULL);
|
||||
}
|
||||
|
||||
if (elapsed < unit) {
|
||||
@@ -659,12 +665,12 @@ static void render_scene11(int elapsed) {
|
||||
SDL_Texture *tex = slide[2];
|
||||
if (tex) {
|
||||
int tw, th;
|
||||
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
|
||||
sdl_get_texture_size(tex, &tw, &th);
|
||||
int y = (int)((long long)(th + view_h) * (elapsed - unit) / unit) - th;
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(tex, 0x80);
|
||||
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
|
||||
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
|
||||
sdl_render_texture(gfx_renderer, tex, NULL, &dst);
|
||||
}
|
||||
} else if (elapsed < unit * 3) {
|
||||
// Segment 2: overlay[0] (ALK 69) pingpong fade
|
||||
@@ -675,19 +681,19 @@ static void render_scene11(int elapsed) {
|
||||
int alpha = (t2 * 255) / (unit / 2);
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
|
||||
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, tex, NULL, NULL);
|
||||
}
|
||||
} else if (elapsed < unit * 4) {
|
||||
// Segment 3: slide[1] (ALK 62) passes top-to-bottom at 50% alpha
|
||||
SDL_Texture *tex = slide[1];
|
||||
if (tex) {
|
||||
int tw, th;
|
||||
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
|
||||
sdl_get_texture_size(tex, &tw, &th);
|
||||
int y = (int)((long long)(th + view_h) * (elapsed - unit * 3) / unit) - th;
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(tex, 0x80);
|
||||
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
|
||||
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
|
||||
sdl_render_texture(gfx_renderer, tex, NULL, &dst);
|
||||
}
|
||||
} else if (elapsed < unit * 5) {
|
||||
// Segment 4: overlay[1] (ALK 70) pingpong fade
|
||||
@@ -698,19 +704,19 @@ static void render_scene11(int elapsed) {
|
||||
int alpha = (t2 * 255) / (unit / 2);
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
|
||||
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, tex, NULL, NULL);
|
||||
}
|
||||
} else if (elapsed < unit * 6) {
|
||||
// Segment 5: slide[0] (ALK 61) passes top-to-bottom at 50% alpha
|
||||
SDL_Texture *tex = slide[0];
|
||||
if (tex) {
|
||||
int tw, th;
|
||||
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
|
||||
sdl_get_texture_size(tex, &tw, &th);
|
||||
int y = (int)((long long)(th + view_h) * (elapsed - unit * 5) / unit) - th;
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(tex, 0x80);
|
||||
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
|
||||
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
|
||||
sdl_render_texture(gfx_renderer, tex, NULL, &dst);
|
||||
}
|
||||
} else if (elapsed < unit * 7) {
|
||||
// Segment 6: overlay[2] (ALK 71) pingpong fade
|
||||
@@ -721,7 +727,7 @@ static void render_scene11(int elapsed) {
|
||||
int alpha = (t2 * 255) / (unit / 2);
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
|
||||
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, tex, NULL, NULL);
|
||||
}
|
||||
} else {
|
||||
// Segment 7: white fade out
|
||||
@@ -799,7 +805,7 @@ static void render_scene12_3d(int elapsed) {
|
||||
// NDC → screen
|
||||
verts[i].position.x = (wx * PROJ_SCALE_X / w + 1.0f) * half_w;
|
||||
verts[i].position.y = (wy * PROJ_SCALE_Y / w + 1.0f) * half_h;
|
||||
verts[i].color = (SDL_Color){255, 255, 255, 255};
|
||||
verts[i].color = sdl_vertex_color(255, 255, 255, 255);
|
||||
verts[i].tex_coord.x = muv[i][0];
|
||||
verts[i].tex_coord.y = muv[i][1];
|
||||
}
|
||||
@@ -817,7 +823,7 @@ static void render_scene12(int elapsed) {
|
||||
// 0–2076ms: bg_a with white fade-in overlay
|
||||
if (bg_a) {
|
||||
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
|
||||
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, bg_a, NULL, NULL);
|
||||
}
|
||||
int white_alpha = 0xff - (elapsed * 0xff) / 2076;
|
||||
fade_overlay(0xff, 0xff, 0xff, (Uint8)white_alpha);
|
||||
@@ -825,32 +831,32 @@ static void render_scene12(int elapsed) {
|
||||
// 2076–2226ms: bg_a static
|
||||
if (bg_a) {
|
||||
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
|
||||
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, bg_a, NULL, NULL);
|
||||
}
|
||||
} else if (elapsed < 3826) {
|
||||
// 2226–3826ms: crossfade bg_a → bg_b
|
||||
if (bg_a) {
|
||||
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
|
||||
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, bg_a, NULL, NULL);
|
||||
}
|
||||
if (bg_b) {
|
||||
int alpha = ((elapsed - 2226) * 0xff) / 1600;
|
||||
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(bg_b, (Uint8)SDL_min(alpha, 0xff));
|
||||
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, bg_b, NULL, NULL);
|
||||
}
|
||||
} else if (elapsed < 3976) {
|
||||
// 3826–3976ms: bg_b static
|
||||
if (bg_b) {
|
||||
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_NONE);
|
||||
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, bg_b, NULL, NULL);
|
||||
}
|
||||
} else if (elapsed < 7803) {
|
||||
// 3976–7803ms: TODO wave distortion on bg_b; for now just show bg_b,
|
||||
// then fade to white
|
||||
if (bg_b) {
|
||||
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_BLEND);
|
||||
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, bg_b, NULL, NULL);
|
||||
}
|
||||
int white_alpha = ((elapsed - 3976) * 0xff) / 3827;
|
||||
fade_overlay(0xff, 0xff, 0xff, (Uint8)SDL_min(white_alpha, 0xff));
|
||||
@@ -858,19 +864,19 @@ static void render_scene12(int elapsed) {
|
||||
// 7803–9397ms: white bg + 3D rotating quad + overlay fade-in
|
||||
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
|
||||
SDL_SetRenderDrawColor(gfx_renderer, 0xff, 0xff, 0xff, 0xff);
|
||||
SDL_RenderFillRect(gfx_renderer, NULL);
|
||||
sdl_render_fill_rect(gfx_renderer, NULL);
|
||||
render_scene12_3d(elapsed);
|
||||
if (overlay) {
|
||||
int alpha = ((elapsed - 7803) * 0xff) / 1594;
|
||||
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(overlay, (Uint8)SDL_min(alpha, 0xff));
|
||||
SDL_RenderCopy(gfx_renderer, overlay, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, overlay, NULL, NULL);
|
||||
}
|
||||
} else {
|
||||
// 9397–10338ms: overlay static
|
||||
if (overlay) {
|
||||
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_NONE);
|
||||
SDL_RenderCopy(gfx_renderer, overlay, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, overlay, NULL, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,11 @@
|
||||
#include "ags.h"
|
||||
#include "font.h"
|
||||
|
||||
static FontType ftype;
|
||||
static int fsize; // フォントの大きさ
|
||||
static FontSpec dt_spec = {
|
||||
.type = FONT_GOTHIC,
|
||||
.weight = FONT_WEIGHT_BOLD,
|
||||
.size = 12
|
||||
};
|
||||
|
||||
/**
|
||||
* 次に描く文字のフォントの種類と大きさを設定
|
||||
@@ -48,8 +51,8 @@ void dt_setfont(FontType type, int size) {
|
||||
}
|
||||
#endif
|
||||
|
||||
ftype = type;
|
||||
fsize = size;
|
||||
dt_spec.type = type;
|
||||
dt_spec.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,12 +69,12 @@ void dt_setfont(FontType type, int size) {
|
||||
* @return: 実際に描画した幅
|
||||
*/
|
||||
int dt_drawtext_col(SDL_Surface *sf, int x, int y, char *buf, int r, int g, int b) {
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(buf, r, g, b, (FontSpec){ .type = ftype, .size = fsize });
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(buf, r, g, b, dt_spec);
|
||||
if (glyph == NULL) return 0;
|
||||
|
||||
SDL_Rect rect = {x, y, glyph->w, glyph->h};
|
||||
SDL_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
|
||||
SDL_BlitSurface(glyph, NULL, sf, &rect);
|
||||
SDL_FreeSurface(glyph);
|
||||
sdl_destroy_surface(glyph);
|
||||
return rect.w;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ static void gre_BlendUseAMap(surface_t *dst, int dx, int dy, surface_t *src, int
|
||||
uint8_t *dp = GETOFFSET_PIXEL(dst, dx, dy);
|
||||
uint8_t *ap = GETOFFSET_ALPHA(src, sx, sy);
|
||||
|
||||
switch (dst->sdl_surface->format->BitsPerPixel) {
|
||||
switch (sdl_surface_bits_per_pixel(dst->sdl_surface)) {
|
||||
case 16:
|
||||
for (int y = 0; y < height; y++) {
|
||||
uint16_t *yls = (uint16_t *)(sp + y * src->sdl_surface->pitch);
|
||||
@@ -55,4 +55,3 @@ void gr_blend_alpha_map(surface_t *dst, int dx, int dy, surface_t *src, int sx,
|
||||
|
||||
gre_BlendUseAMap(dst, dx, dy, src, sx, sy, sw, sh);
|
||||
}
|
||||
|
||||
|
||||
+31
-21
@@ -27,7 +27,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "nact.h"
|
||||
@@ -103,7 +103,7 @@ void scg_deref(cginfo_t *cg) {
|
||||
return;
|
||||
|
||||
if (cg->sf)
|
||||
SDL_FreeSurface(cg->sf);
|
||||
sdl_destroy_surface(cg->sf);
|
||||
free(cg);
|
||||
}
|
||||
|
||||
@@ -111,8 +111,9 @@ void scg_deref(cginfo_t *cg) {
|
||||
void scg_create(int wNumCG, int wWidth, int wHeight, int wR, int wG, int wB, int wBlendRate) {
|
||||
SPCG_ASSERT_NO(wNumCG);
|
||||
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, wWidth, wHeight, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_FillRect(sf, NULL, SDL_MapRGBA(sf->format, wR, wG, wB, wBlendRate));
|
||||
SDL_Surface *sf = sdl_create_surface(
|
||||
wWidth, wHeight, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_FillRect(sf, NULL, sdl_map_rgba(sf, wR, wG, wB, wBlendRate));
|
||||
scg_new(CG_SET, wNumCG, sf);
|
||||
}
|
||||
|
||||
@@ -125,7 +126,7 @@ void scg_create_reverse(int wNumCG, int wNumSrcCG, int wReverseX, int wReverseY)
|
||||
if (!src)
|
||||
return;
|
||||
|
||||
SDL_Surface *sf = SDL_ConvertSurface(src->sf, src->sf->format, 0);
|
||||
SDL_Surface *sf = sdl_convert_surface(src->sf, sdl_surface_format(src->sf));
|
||||
if (wReverseX)
|
||||
gfx_FlipSurfaceHorizontal(sf);
|
||||
if (wReverseY)
|
||||
@@ -142,15 +143,17 @@ void scg_create_stretch(int wNumCG, int wWidth, int wHeight, int wNumSrcCG) {
|
||||
if (!src)
|
||||
return;
|
||||
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, wWidth, wHeight, src->sf->format->BitsPerPixel, src->sf->format->format);
|
||||
SDL_Surface *sf = sdl_create_surface(wWidth, wHeight,
|
||||
sdl_surface_bits_per_pixel(src->sf), sdl_surface_format(src->sf));
|
||||
SDL_BlitScaled(src->sf, NULL, sf, NULL);
|
||||
scg_new(CG_STRETCH, wNumCG, sf);
|
||||
}
|
||||
|
||||
static SDL_Surface *blend(SDL_Surface *base, int x, int y, SDL_Surface *blend, int mode) {
|
||||
assert(blend->format->format == SDL_PIXELFORMAT_ARGB8888 || blend->format->format == SDL_PIXELFORMAT_XRGB8888);
|
||||
bool src_has_alpha = SDL_ISPIXELFORMAT_ALPHA(blend->format->format);
|
||||
SDL_Surface *dst = SDL_ConvertSurfaceFormat(base, SDL_PIXELFORMAT_ARGB8888, 0);
|
||||
assert(sdl_surface_format(blend) == SDL_PIXELFORMAT_ARGB8888 ||
|
||||
sdl_surface_format(blend) == SDL_PIXELFORMAT_XRGB8888);
|
||||
bool src_has_alpha = SDL_ISPIXELFORMAT_ALPHA(sdl_surface_format(blend));
|
||||
SDL_Surface *dst = sdl_convert_surface(base, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_Rect rect = {x, y, blend->w, blend->h};
|
||||
SDL_IntersectRect(&(SDL_Rect){0, 0, dst->w, dst->h}, &rect, &rect);
|
||||
|
||||
@@ -220,12 +223,14 @@ void scg_create_text(int wNumCG, int wSize, int wR, int wG, int wB, int wText) {
|
||||
// 勝手に出ていいのかな?
|
||||
if (svar_length(wText) == 0) return;
|
||||
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(svar_get(wText), wR, wG, wB, (FontSpec){ .size = wSize });
|
||||
FontSpec spec = { .type = FONT_GOTHIC, .weight = FONT_WEIGHT_BOLD, .size = wSize };
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(svar_get(wText), wR, wG, wB, spec);
|
||||
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_Surface *sf = sdl_create_surface(
|
||||
glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
|
||||
SDL_BlitSurface(glyph, NULL, sf, NULL);
|
||||
SDL_FreeSurface(glyph);
|
||||
sdl_destroy_surface(glyph);
|
||||
|
||||
scg_new(CG_SET, wNumCG, sf);
|
||||
}
|
||||
@@ -244,12 +249,14 @@ void scg_create_textnum(int wNumCG, int wSize, int wR, int wG, int wB, int wFigs
|
||||
}
|
||||
sprintf(s, ss, wValue);
|
||||
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(s, wR, wG, wB, (FontSpec){ .size = wSize });
|
||||
FontSpec spec = { .type = FONT_GOTHIC, .weight = FONT_WEIGHT_BOLD, .size = wSize };
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(s, wR, wG, wB, spec);
|
||||
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_Surface *sf = sdl_create_surface(
|
||||
glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
|
||||
SDL_BlitSurface(glyph, NULL, sf, NULL);
|
||||
SDL_FreeSurface(glyph);
|
||||
sdl_destroy_surface(glyph);
|
||||
|
||||
scg_new(CG_SET, wNumCG, sf);
|
||||
}
|
||||
@@ -263,7 +270,8 @@ void scg_copy(int wNumDstCG, int wNumSrcCG) {
|
||||
if (!src)
|
||||
return;
|
||||
|
||||
scg_new(CG_SET, wNumDstCG, SDL_ConvertSurface(src->sf, src->sf->format, 0));
|
||||
scg_new(CG_SET, wNumDstCG,
|
||||
sdl_convert_surface(src->sf, sdl_surface_format(src->sf)));
|
||||
}
|
||||
|
||||
// CGの一部を切りぬいたCGを作成
|
||||
@@ -275,7 +283,8 @@ void scg_cut(int wNumDstCG, int wNumSrcCG, int wX, int wY, int wWidth, int wHeig
|
||||
if (!src)
|
||||
return;
|
||||
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, wWidth, wHeight, src->sf->format->BitsPerPixel, src->sf->format->format);
|
||||
SDL_Surface *sf = sdl_create_surface(wWidth, wHeight,
|
||||
sdl_surface_bits_per_pixel(src->sf), sdl_surface_format(src->sf));
|
||||
SDL_BlitSurface(src->sf, &(SDL_Rect){wX, wY, wWidth, wHeight}, sf, NULL);
|
||||
|
||||
scg_new(CG_SET, wNumDstCG, sf);
|
||||
@@ -290,8 +299,9 @@ void scg_partcopy(int wNumDstCG, int wNumSrcCG, int wX, int wY, int wWidth, int
|
||||
if (!src)
|
||||
return;
|
||||
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, src->sf->w, src->sf->h, src->sf->format->BitsPerPixel, src->sf->format->format);
|
||||
SDL_FillRect(sf, NULL, SDL_MapRGBA(sf->format, 0, 0, 0, 255));
|
||||
SDL_Surface *sf = sdl_create_surface(src->sf->w, src->sf->h,
|
||||
sdl_surface_bits_per_pixel(src->sf), sdl_surface_format(src->sf));
|
||||
SDL_FillRect(sf, NULL, sdl_map_rgba(sf, 0, 0, 0, 255));
|
||||
SDL_BlitSurface(src->sf, &(SDL_Rect){wX, wY, wWidth, wHeight}, sf, &(SDL_Rect){wX, wY, wWidth, wHeight});
|
||||
|
||||
scg_new(CG_SET, wNumDstCG, sf);
|
||||
@@ -351,7 +361,7 @@ int scg_querybpp(int wNumCG) {
|
||||
if (wNumCG >= (CGMAX -1)) return 0;
|
||||
if (!cg_store || !cg_store[wNumCG]) return 0;
|
||||
if (cg_store[wNumCG]->sf == NULL) return 0;
|
||||
return cg_store[wNumCG]->sf->format->BitsPerPixel;
|
||||
return sdl_surface_bits_per_pixel(cg_store[wNumCG]->sf);
|
||||
}
|
||||
|
||||
// CGの alphamap が存在するかを取得
|
||||
@@ -359,5 +369,5 @@ bool scg_existalphamap(int wNumCG) {
|
||||
if (wNumCG >= (CGMAX -1)) return false;
|
||||
if (!cg_store || !cg_store[wNumCG]) return false;
|
||||
if (cg_store[wNumCG]->sf == NULL) return false;
|
||||
return SDL_ISPIXELFORMAT_ALPHA(cg_store[wNumCG]->sf->format->format);
|
||||
return SDL_ISPIXELFORMAT_ALPHA(sdl_surface_format(cg_store[wNumCG]->sf));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include "portab.h"
|
||||
|
||||
@@ -84,10 +84,11 @@ static void Run() {
|
||||
|
||||
cgdata *cg = jpeg_extract(alk->entries[i].data, alk->entries[i].size);
|
||||
if (cg) {
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
|
||||
SDL_Surface *sf = sdl_create_surface_from(cg->pic, cg->width,
|
||||
cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
|
||||
SDL_BlitSurface(sf, NULL, main_surface, NULL);
|
||||
ags_updateFull();
|
||||
SDL_FreeSurface(sf);
|
||||
sdl_destroy_surface(sf);
|
||||
cgdata_free(cg);
|
||||
} else {
|
||||
WARNING("Cannot decode CG %d", i);
|
||||
|
||||
+50
-16
@@ -10,6 +10,7 @@ add_library(src_lib STATIC
|
||||
cali.c
|
||||
dri.c
|
||||
filecheck.c
|
||||
font.c
|
||||
gameresource.c
|
||||
hankaku.c
|
||||
mmap.c
|
||||
@@ -72,7 +73,6 @@ target_sources(xsystem35 PRIVATE
|
||||
ecopy.c
|
||||
effect.c
|
||||
event.c
|
||||
font.c
|
||||
hacks.c
|
||||
gfx_draw.c
|
||||
gfx_image.c
|
||||
@@ -105,12 +105,20 @@ target_sources(xsystem35 PRIVATE
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
target_sources(xsystem35 PRIVATE pcm.emscripten.c bgm.emscripten.c)
|
||||
elseif (XSYSTEM35_SDL_VERSION STREQUAL "3")
|
||||
target_sources(xsystem35 PRIVATE
|
||||
pcm.sdl3mixer.c
|
||||
sdl3_mixer_backend.c
|
||||
bgm.sdl3mixer.c)
|
||||
target_compile_definitions(xsystem35 PRIVATE HAVE_SDL3_MIXER_BACKEND)
|
||||
else()
|
||||
target_sources(xsystem35 PRIVATE pcm.sdlmixer.c bgm.sdlmixer.c)
|
||||
endif()
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
target_sources(xsystem35 PRIVATE cdrom.emscripten.c)
|
||||
elseif (XSYSTEM35_SDL_VERSION STREQUAL "3")
|
||||
target_sources(xsystem35 PRIVATE cdrom.sdl3mixer.c)
|
||||
else()
|
||||
target_sources(xsystem35 PRIVATE cdrom.mp3.c)
|
||||
endif()
|
||||
@@ -120,7 +128,11 @@ if (EMSCRIPTEN)
|
||||
elseif (ANDROID)
|
||||
target_sources(xsystem35 PRIVATE midi.android.c)
|
||||
else()
|
||||
target_sources(xsystem35 PRIVATE midi.sdlmixer.c)
|
||||
if (XSYSTEM35_SDL_VERSION STREQUAL "3")
|
||||
target_sources(xsystem35 PRIVATE midi.sdl3mixer.c)
|
||||
else()
|
||||
target_sources(xsystem35 PRIVATE midi.sdlmixer.c)
|
||||
endif()
|
||||
if (ENABLE_MIDI_PORTMIDI)
|
||||
target_sources(xsystem35 PRIVATE midi.portmidi.c midifile.c)
|
||||
endif()
|
||||
@@ -137,18 +149,19 @@ if (ENABLE_DEBUGGER)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_sources(src_lib PRIVATE win/resources.c)
|
||||
target_sources(xsystem35 PRIVATE
|
||||
win/dialog.c win/resources.rc win/resources.c win/menubar.c win/console.c)
|
||||
win/dialog.c win/resources.rc win/menubar.c win/console.c)
|
||||
endif()
|
||||
|
||||
if (NOT HAVE_LIBINTL)
|
||||
target_sources(xsystem35 PRIVATE nls.c)
|
||||
endif()
|
||||
|
||||
target_link_libraries(src_lib PRIVATE sdl2 zlib)
|
||||
target_link_libraries(xsystem35 PRIVATE m zlib sdl2 sdl2_ttf)
|
||||
if (TARGET sdl2_mixer)
|
||||
target_link_libraries(xsystem35 PRIVATE sdl2_mixer)
|
||||
target_link_libraries(src_lib PRIVATE xsystem35_sdl freetype2 zlib)
|
||||
target_link_libraries(xsystem35 PRIVATE m zlib xsystem35_sdl)
|
||||
if (TARGET xsystem35_sdl_mixer)
|
||||
target_link_libraries(xsystem35 PRIVATE xsystem35_sdl_mixer)
|
||||
endif()
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
@@ -157,18 +170,28 @@ if (EMSCRIPTEN)
|
||||
# Without optimizations, Asyncify generates very large code.
|
||||
list(APPEND CMAKE_EXE_LINKER_FLAGS_DEBUG "-O1")
|
||||
|
||||
if (JSPI)
|
||||
target_link_options(xsystem35 PRIVATE
|
||||
-sJSPI
|
||||
-sJSPI_IMPORTS=wait_vsync
|
||||
)
|
||||
else()
|
||||
target_link_options(xsystem35 PRIVATE
|
||||
-sASYNCIFY=1
|
||||
-sASYNCIFY_IGNORE_INDIRECT=1
|
||||
-sASYNCIFY_REMOVE=SDL_Delay
|
||||
-sASYNCIFY_IMPORTS=wait_vsync
|
||||
# Functions that call an asynchronous function via a function pointer must
|
||||
# be added to ASYNCIFY_ADD, even if it had a direct call to another
|
||||
# asynchronous function, so that the indirect call itself is instrumented.
|
||||
-sASYNCIFY_ADD=commands2F60,nact_main,send_agsevent,cb_waitkey_sprite,modal_run
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_options(xsystem35 PRIVATE
|
||||
-sENVIRONMENT=web
|
||||
-sMODULARIZE=1
|
||||
-sEXPORT_ES6=1
|
||||
-sASYNCIFY=1
|
||||
-sASYNCIFY_IGNORE_INDIRECT=1
|
||||
-sASYNCIFY_REMOVE=SDL_Delay
|
||||
-sASYNCIFY_IMPORTS=wait_vsync
|
||||
# Functions that call an asynchronous function via a function pointer must
|
||||
# be added to ASYNCIFY_ADD, even if it had a direct call to another
|
||||
# asynchronous function, so that the indirect call itself is instrumented.
|
||||
-sASYNCIFY_ADD=commands2F60,nact_main,send_agsevent,cb_waitkey_sprite,modal_run
|
||||
-sALLOW_MEMORY_GROWTH=1
|
||||
-sNO_EXIT_RUNTIME=1
|
||||
-sEXPORTED_FUNCTIONS=_main,_malloc
|
||||
@@ -185,19 +208,30 @@ else() # non-emscripten, non-android
|
||||
endif()
|
||||
if (ENABLE_MIDI_PORTMIDI)
|
||||
target_link_libraries(xsystem35 PRIVATE ${PORTMIDI})
|
||||
if (WIN32)
|
||||
# Other static dependencies can place winmm before PortMidi on the link
|
||||
# line. Rescan the pair so PortMidi's WinMM imports are resolved.
|
||||
target_link_libraries(xsystem35 PRIVATE
|
||||
"-Wl,--start-group,-lportmidi,-lwinmm,--end-group")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(TARGETS xsystem35 RUNTIME DESTINATION bin)
|
||||
|
||||
add_executable(src_tests
|
||||
src_tests.c
|
||||
cache_test.c
|
||||
font_test.c
|
||||
gameresource_test.c
|
||||
hankaku_test.c
|
||||
qnt_test.c
|
||||
sdl_compat_test.c
|
||||
variable_test.c
|
||||
)
|
||||
target_compile_options(src_tests PRIVATE -Wno-pointer-sign -Wall)
|
||||
target_link_libraries(src_tests PRIVATE src_lib sdl2)
|
||||
target_compile_definitions(src_tests PRIVATE
|
||||
TEST_FONT_DIR="${PROJECT_SOURCE_DIR}/fonts")
|
||||
target_link_libraries(src_tests PRIVATE src_lib xsystem35_sdl)
|
||||
add_test(NAME src_tests COMMAND src_tests)
|
||||
file(COPY testdata DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
endif()
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
#define __LITTLEENDIAN__
|
||||
|
||||
#include <string.h>
|
||||
#include <SDL_endian.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
static inline int LittleEndian_getDW(const uint8_t *b, int index) {
|
||||
uint32_t t;
|
||||
|
||||
@@ -125,7 +125,7 @@ bool ags_check_param_xy(int *x, int *y) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ags_init(const char *render_driver, bool enable_zb) {
|
||||
void ags_init(const char *render_driver) {
|
||||
nact->ags.mouse_warp_enabled = true;
|
||||
nact->ags.world_width = SYS35_DEFAULT_WIDTH;
|
||||
nact->ags.world_height = SYS35_DEFAULT_HEIGHT;
|
||||
@@ -138,8 +138,7 @@ void ags_init(const char *render_driver, bool enable_zb) {
|
||||
nact->ags.font_type = FONT_GOTHIC;
|
||||
nact->ags.text_decoration_type = 0;
|
||||
nact->ags.text_decoration_color = 0;
|
||||
nact->ags.enable_zb = enable_zb;
|
||||
nact->ags.font_weight = enable_zb ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL;
|
||||
nact->ags.font_weight = FONT_WEIGHT_BOLD;
|
||||
|
||||
gfx_Initialize(render_driver);
|
||||
event_init();
|
||||
@@ -193,7 +192,7 @@ void ags_setViewArea(int x, int y, int width, int height) {
|
||||
nact->ags.view_area.y = y;
|
||||
nact->ags.view_area.w = width;
|
||||
nact->ags.view_area.h = height;
|
||||
gfx_setWindowSize(width, height);
|
||||
gfx_setViewSize(width, height);
|
||||
}
|
||||
|
||||
void ags_setWindowTitle(const char *title_utf8) {
|
||||
@@ -202,20 +201,33 @@ void ags_setWindowTitle(const char *title_utf8) {
|
||||
gfx_setWindowTitle(buf);
|
||||
}
|
||||
|
||||
void ags_getDIBInfo(DispInfo *info) {
|
||||
info->width = nact->ags.world_width;
|
||||
info->height = nact->ags.world_height;
|
||||
info->depth = nact->ags.world_depth;
|
||||
}
|
||||
|
||||
void ags_getViewAreaInfo(DispInfo *info) {
|
||||
gfx_getWindowInfo(NULL, NULL, &info->depth);
|
||||
info->width = nact->ags.view_area.w;
|
||||
info->height = nact->ags.view_area.h;
|
||||
}
|
||||
|
||||
void ags_getWindowInfo(DispInfo *info) {
|
||||
gfx_getWindowInfo(&info->width, &info->height, &info->depth);
|
||||
void ags_getDisplayInfo(enum ags_display display, int *width, int *height, int *depth) {
|
||||
SDL_DisplayMode dm;
|
||||
switch (display) {
|
||||
case AGS_DISPLAY_CURRENT:
|
||||
sdl_get_current_display_mode(&dm);
|
||||
*width = dm.w;
|
||||
*height = dm.h;
|
||||
*depth = SDL_BITSPERPIXEL(dm.format);
|
||||
break;
|
||||
case AGS_DISPLAY_DIB:
|
||||
*width = nact->ags.world_width;
|
||||
*height = nact->ags.world_height;
|
||||
*depth = nact->ags.world_depth;
|
||||
break;
|
||||
case AGS_DISPLAY_VIEW_AREA:
|
||||
sdl_get_current_display_mode(&dm);
|
||||
*width = nact->ags.view_area.w;
|
||||
*height = nact->ags.view_area.h;
|
||||
*depth = SDL_BITSPERPIXEL(dm.format);
|
||||
break;
|
||||
case AGS_DISPLAY_DESKTOP:
|
||||
sdl_get_desktop_display_mode(&dm);
|
||||
*width = dm.w;
|
||||
*height = dm.h;
|
||||
*depth = SDL_BITSPERPIXEL(dm.format);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ags_setExposeSwitch(bool expose) {
|
||||
@@ -321,10 +333,10 @@ void ags_getPixel(int x, int y, PixelColor *cell) {
|
||||
if (!ags_check_param_xy(&x, &y)) return;
|
||||
|
||||
uint32_t pixel = gfx_getPixel(x, y);
|
||||
if (main_surface->format->BitsPerPixel == 8) {
|
||||
if (sdl_surface_bits_per_pixel(main_surface) == 8) {
|
||||
cell->index = pixel;
|
||||
} else {
|
||||
SDL_GetRGB(pixel, main_surface->format, &cell->r, &cell->g, &cell->b);
|
||||
sdl_get_rgb(pixel, main_surface, &cell->r, &cell->g, &cell->b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,7 +478,7 @@ int ags_drawString(int x, int y, const char *src, int col, int size, SDL_Rect *r
|
||||
SDL_Surface *ags_drawStringToSurface(const char *str, int r, int g, int b, FontSpec font) {
|
||||
char *utf8 = toUTF8(str);
|
||||
SDL_Color color = {r, g, b, 255};
|
||||
SDL_Surface *sf = font_render_text(font, utf8, color);
|
||||
SDL_Surface *sf = font_render_text(font, utf8, color, true);
|
||||
free(utf8);
|
||||
return sf;
|
||||
}
|
||||
@@ -716,10 +728,10 @@ void ags_setCursorLocation(int x, int y, bool is_dibgeo, bool for_selection) {
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if (!for_selection) {
|
||||
if (!sl_is_s380 && !for_selection) {
|
||||
// We can't move the actual cursor in the browser, but can change the
|
||||
// internal mouse coordinates. This can help with keyboard/gamepad
|
||||
// navigation.
|
||||
// navigation in Toushin Toshi 2.
|
||||
event_set_mouse_internal_location(x, y);
|
||||
EM_ASM({ xsystem35.shell.showMouseMoveEffect($0, $1); }, x, y);
|
||||
sys_sleep(cursor_move_time);
|
||||
@@ -733,11 +745,11 @@ void ags_setCursorLocation(int x, int y, bool is_dibgeo, bool for_selection) {
|
||||
for (int i = 1; i < 8; i++) {
|
||||
int xi = ((dx*i*i*i) >> 9) - ((3*dx*i*i)>> 6) + ((3*dx*i) >> 3) + p.x;
|
||||
int yi = ((dy*i*i*i) >> 9) - ((3*dy*i*i)>> 6) + ((3*dy*i) >> 3) + p.y;
|
||||
event_set_mouse_location(xi, yi);
|
||||
gfx_warpMouse(xi, yi);
|
||||
sys_sleep(cursor_move_time / 7);
|
||||
}
|
||||
event_set_mouse_location(x, y);
|
||||
} else if (!for_selection) {
|
||||
gfx_warpMouse(x, y);
|
||||
} else if (!sl_is_s380 && !for_selection) {
|
||||
event_set_mouse_internal_location(x, y);
|
||||
sys_sleep(cursor_move_time);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <SDL_surface.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "portab.h"
|
||||
#include "cg.h"
|
||||
#include "font.h"
|
||||
@@ -58,7 +58,7 @@ typedef struct agsurface surface_t;
|
||||
#define GETOFFSET_PIXEL(suf, x, y) PIXEL_AT((suf)->sdl_surface, x, y)
|
||||
#define GETOFFSET_ALPHA(suf, x, y) ((suf)->alpha + (y) * (suf)->width + (x))
|
||||
// for SDL_surface
|
||||
#define PIXEL_AT(suf, x, y) ((suf)->pixels + (y) * (suf)->pitch + (x) * (suf)->format->BytesPerPixel)
|
||||
#define PIXEL_AT(suf, x, y) ((suf)->pixels + (y) * (suf)->pitch + (x) * sdl_surface_bytes_per_pixel(suf))
|
||||
#define ALPHA_AT(suf, x, y) (PIXEL_AT(suf, x, y) + (SDL_BYTEORDER == SDL_LIL_ENDIAN ? 3 : 0))
|
||||
|
||||
typedef struct {
|
||||
@@ -102,11 +102,12 @@ typedef struct {
|
||||
uint32_t index;
|
||||
} PixelColor;
|
||||
|
||||
typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
int depth;
|
||||
} DispInfo;
|
||||
enum ags_display {
|
||||
AGS_DISPLAY_CURRENT,
|
||||
AGS_DISPLAY_DIB,
|
||||
AGS_DISPLAY_VIEW_AREA,
|
||||
AGS_DISPLAY_DESKTOP,
|
||||
};
|
||||
|
||||
struct _ags {
|
||||
SDL_Color pal[256]; /* system palette */
|
||||
@@ -128,7 +129,6 @@ struct _ags {
|
||||
int text_decoration_color;
|
||||
|
||||
bool mouse_warp_enabled;
|
||||
bool enable_zb;
|
||||
bool noantialias; /* antialias を使用しない */
|
||||
bool noimagecursor; /* リソースファイルのカーソルを読みこまない */
|
||||
};
|
||||
@@ -137,7 +137,7 @@ typedef struct _ags ags_t;
|
||||
extern SDL_Surface *main_surface;
|
||||
|
||||
/* 初期化関係 */
|
||||
void ags_init(const char *render_driver, bool enable_zb);
|
||||
void ags_init(const char *render_driver);
|
||||
void ags_remove(void);
|
||||
void ags_reset(void);
|
||||
|
||||
@@ -145,9 +145,7 @@ void ags_reset(void);
|
||||
extern void ags_setWorldSize(int width, int height, int depth);
|
||||
extern void ags_setViewArea(int x, int y, int width, int height);
|
||||
extern void ags_setWindowTitle(const char *title_utf8);
|
||||
extern void ags_getDIBInfo(DispInfo *info);
|
||||
extern void ags_getWindowInfo(DispInfo *info);
|
||||
extern void ags_getViewAreaInfo(DispInfo *info);
|
||||
extern void ags_getDisplayInfo(enum ags_display display, int *width, int *height, int *depth);
|
||||
extern bool ags_check_param(int *x, int *y, int *w, int *h);
|
||||
extern bool ags_check_param_xy(int *x, int *y);
|
||||
extern surface_t *ags_getDIB();
|
||||
|
||||
+37
-7
@@ -33,18 +33,34 @@
|
||||
/* drifiles object */
|
||||
static drifiles *dri[DRIFILETYPEMAX];
|
||||
|
||||
/* cache handler for dri file */
|
||||
static cacher *cacheid;
|
||||
static Cache *dri_cache;
|
||||
|
||||
#ifndef ALD_CACHE_SIZE
|
||||
#define ALD_CACHE_SIZE (10 << 20)
|
||||
#endif
|
||||
|
||||
static uint32_t int_hash(const void *key) {
|
||||
return (uint32_t)*(const int *)key;
|
||||
}
|
||||
|
||||
static bool int_equal(const void *a, const void *b) {
|
||||
return *(const int *)a == *(const int *)b;
|
||||
}
|
||||
|
||||
/*
|
||||
* free dridata
|
||||
* dfile: dridata to be free
|
||||
*/
|
||||
static void ald_free(dridata *dfile) {
|
||||
static void ald_free(void *data) {
|
||||
dridata *dfile = data;
|
||||
free(dfile->data_raw);
|
||||
free(dfile);
|
||||
}
|
||||
|
||||
static bool ald_is_pinned(const void *data) {
|
||||
return ((const dridata *)data)->refcnt != 0;
|
||||
}
|
||||
|
||||
bool ald_is_linked(DRIFILETYPE type, int no) {
|
||||
if (type >= DRIFILETYPEMAX || !dri[type])
|
||||
return false;
|
||||
@@ -80,11 +96,12 @@ dridata *ald_getdata(DRIFILETYPE type, int no) {
|
||||
if (dri[type]->mmapped) return dri_getdata(dri[type], no);
|
||||
|
||||
/* not mmapped */
|
||||
if (NULL == (ddata = (dridata *)cache_foreach(cacheid, (type << 16) + no))) {
|
||||
int key = (type << 16) + no;
|
||||
if (NULL == (ddata = cache_get(dri_cache, &key))) {
|
||||
ddata = dri_getdata(dri[type], no);
|
||||
if (ddata != NULL) {
|
||||
ddata->refcnt = 0;
|
||||
cache_insert(cacheid, (type << 16) + no, (void *)ddata, ddata->size, &(ddata->refcnt));
|
||||
ddata->cached = cache_insert(dri_cache, &key, ddata, ddata->size) == CACHE_INSERT_OK;
|
||||
}
|
||||
}
|
||||
if (ddata != NULL)
|
||||
@@ -105,6 +122,8 @@ void ald_freedata(dridata *data) {
|
||||
free(data);
|
||||
} else {
|
||||
data->refcnt--;
|
||||
if (!data->cached && data->refcnt == 0)
|
||||
ald_free(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,8 +131,15 @@ void ald_init(int type, const char **file, int cnt, bool use_mmap) {
|
||||
if (type >= DRIFILETYPEMAX || cnt <= 0)
|
||||
return;
|
||||
dri[type] = dri_init(file, cnt, use_mmap);
|
||||
if (!dri[type]->mmapped) {
|
||||
cacheid = cache_new(ald_free);
|
||||
if (!dri[type]->mmapped && !dri_cache) {
|
||||
CacheOps ops = {
|
||||
.key_size = sizeof(int),
|
||||
.hash = int_hash,
|
||||
.equal = int_equal,
|
||||
.destroy = ald_free,
|
||||
.is_pinned = ald_is_pinned,
|
||||
};
|
||||
dri_cache = cache_new((size_t)ALD_CACHE_SIZE, &ops);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,3 +148,7 @@ int ald_get_maxno(DRIFILETYPE type) {
|
||||
return 0;
|
||||
return dri[type]->maxno;
|
||||
}
|
||||
|
||||
CacheStats ald_get_cache_stats(void) {
|
||||
return cache_get_stats(dri_cache);
|
||||
}
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@
|
||||
#define __ALD_MANAGER__
|
||||
|
||||
#include "portab.h"
|
||||
#include "cache.h"
|
||||
#include "dri.h"
|
||||
|
||||
#define DRIFILETYPEMAX 7
|
||||
@@ -44,6 +45,6 @@ bool ald_exists(DRIFILETYPE type, int no);
|
||||
dridata *ald_getdata(DRIFILETYPE type, int no);
|
||||
void ald_freedata(dridata *data);
|
||||
int ald_get_maxno(DRIFILETYPE type);
|
||||
CacheStats ald_get_cache_stats(void);
|
||||
|
||||
#endif /* !__ALD_MANAGER__ */
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* bgm.sdl3mixer.c: BGM (*BA.ALD) playback using SDL3_mixer
|
||||
*
|
||||
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
|
||||
* 1998- <masaki-c@is.aist-nara.ac.jp>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ald_manager.h"
|
||||
#include "audio_meta.h"
|
||||
#include "bgm.h"
|
||||
#include "music_private.h"
|
||||
#include "nact.h"
|
||||
#include "sdl3_mixer_backend.h"
|
||||
#include "system.h"
|
||||
|
||||
static DRIFILETYPE dri_type;
|
||||
static int base_no;
|
||||
static int current_no;
|
||||
static int current_vol = 100;
|
||||
static uint32_t start_time;
|
||||
|
||||
static int clamp_volume(int volume)
|
||||
{
|
||||
if (volume < 0)
|
||||
return 0;
|
||||
if (volume > 100)
|
||||
return 100;
|
||||
return volume;
|
||||
}
|
||||
|
||||
static float music_gain(int volume)
|
||||
{
|
||||
float gain = clamp_volume(volume) / 100.0f;
|
||||
return gain * clamp_volume(prv.volval[BGM_VOLVAL_CH]) / 100.0f;
|
||||
}
|
||||
|
||||
static void apply_music_volume(int volume)
|
||||
{
|
||||
current_vol = volume;
|
||||
sdl3_mixer_set_music_gain(SDL3_MIXER_MUSIC_BGM,
|
||||
music_gain(current_vol));
|
||||
}
|
||||
|
||||
void musbgm_reapply_valance(void)
|
||||
{
|
||||
apply_music_volume(current_vol);
|
||||
}
|
||||
|
||||
static void free_music(void)
|
||||
{
|
||||
current_no = 0;
|
||||
sdl3_mixer_stop_music(SDL3_MIXER_MUSIC_BGM, 0);
|
||||
}
|
||||
|
||||
static MIX_Audio *load_music(int no)
|
||||
{
|
||||
free_music();
|
||||
int ald_no = no + base_no - 1;
|
||||
dridata *dfile = ald_getdata(dri_type, ald_no);
|
||||
if (!dfile) {
|
||||
WARNING("Failed to open BGM %d", ald_no);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MIX_Audio *audio = sdl3_mixer_load_music_memory(
|
||||
dfile->data, dfile->size);
|
||||
ald_freedata(dfile);
|
||||
if (!audio)
|
||||
WARNING("Failed to load BGM %d: %s", ald_no, SDL_GetError());
|
||||
return audio;
|
||||
}
|
||||
|
||||
bool musbgm_init(DRIFILETYPE type, int base)
|
||||
{
|
||||
dri_type = type;
|
||||
base_no = base;
|
||||
if (type == DRIFILE_BGM)
|
||||
return bgi_read(nact->files.bgi);
|
||||
return true;
|
||||
}
|
||||
|
||||
void musbgm_exit(void)
|
||||
{
|
||||
free_music();
|
||||
}
|
||||
|
||||
void musbgm_reset(void)
|
||||
{
|
||||
free_music();
|
||||
}
|
||||
|
||||
bool musbgm_play(int no, int time, int volume, int loop_count)
|
||||
{
|
||||
MIX_Audio *audio = load_music(no);
|
||||
if (!audio)
|
||||
return false;
|
||||
|
||||
int loops = loop_count == 0 ? -1 : loop_count - 1;
|
||||
current_vol = volume;
|
||||
if (!sdl3_mixer_play_music(SDL3_MIXER_MUSIC_BGM, audio,
|
||||
loops, time * 10, music_gain(current_vol)))
|
||||
return false;
|
||||
current_no = no;
|
||||
start_time = sys_get_ticks();
|
||||
return true;
|
||||
}
|
||||
|
||||
void musbgm_stop(int no, int time)
|
||||
{
|
||||
if (no == current_no)
|
||||
sdl3_mixer_stop_music(SDL3_MIXER_MUSIC_BGM, time * 10);
|
||||
}
|
||||
|
||||
void musbgm_fade(int no, int time, int volume)
|
||||
{
|
||||
(void)time;
|
||||
if (no == current_no)
|
||||
apply_music_volume(volume);
|
||||
}
|
||||
|
||||
int musbgm_getpos(int no)
|
||||
{
|
||||
if (!musbgm_isplaying(no))
|
||||
return 0;
|
||||
return (sys_get_ticks() - start_time) / 10;
|
||||
}
|
||||
|
||||
int musbgm_getlen(int no)
|
||||
{
|
||||
bgi_t *bgi = bgi_find(no);
|
||||
return bgi ? bgi->len / 441 : 0;
|
||||
}
|
||||
|
||||
bool musbgm_isplaying(int no)
|
||||
{
|
||||
return no == current_no &&
|
||||
sdl3_mixer_music_active(SDL3_MIXER_MUSIC_BGM);
|
||||
}
|
||||
|
||||
void musbgm_stopall(int time)
|
||||
{
|
||||
musbgm_stop(current_no, time);
|
||||
}
|
||||
|
||||
void musbgm_wait(int no, int timeout)
|
||||
{
|
||||
while (timeout-- > 0 && musbgm_isplaying(no))
|
||||
SDL_Delay(10);
|
||||
}
|
||||
+7
-5
@@ -25,8 +25,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL.h>
|
||||
#include <SDL_mixer.h>
|
||||
#include "sdl_mixer_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -111,11 +110,14 @@ bool musbgm_play(int no, int time, int vol, int loop_count) {
|
||||
if (!bgm_load(no))
|
||||
return false;
|
||||
|
||||
// We don't use the loop information in the BGI file, but SDL_mixer
|
||||
// understands loop info in the WAVE's "smpl" chunk.
|
||||
// We don't use the loop positions in the BGI file; SDL_mixer instead
|
||||
// understands loop positions in the WAVE's "smpl" chunk.
|
||||
|
||||
apply_music_volume(vol);
|
||||
if (Mix_FadeInMusic(mix_music, loop_count == 0 ? -1 : loop_count, time * 10) != 0) {
|
||||
// SDL_mixer counts repeats after the first play, while System 3.x counts
|
||||
// the total number of plays. In both APIs, an infinite loop is special.
|
||||
int loops = loop_count == 0 ? -1 : loop_count - 1;
|
||||
if (Mix_FadeInMusic(mix_music, loops, time * 10) != 0) {
|
||||
free_music();
|
||||
return false;
|
||||
}
|
||||
|
||||
+162
-115
@@ -1,8 +1,5 @@
|
||||
/*
|
||||
* cache.c general cache manager
|
||||
*
|
||||
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
|
||||
* 1998- <masaki-c@is.aist-nara.ac.jp>
|
||||
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,130 +16,180 @@
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
/* $Id: cache.c,v 1.5 2003/07/21 23:06:47 chikama Exp $ */
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include "portab.h"
|
||||
#include "cache.h"
|
||||
|
||||
/* maximum cache size (in MB) */
|
||||
#ifndef CACHE_TOTALSIZE
|
||||
#define CACHE_TOTALSIZE 20
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int totalsize; /* total size in cache */
|
||||
static int dummyfalse = 0; /* dummy in_use flag */
|
||||
static int dummytrue = 1; /* dummy in_use flag */
|
||||
#define CACHE_BUCKETS 1024
|
||||
|
||||
/*
|
||||
* static methods
|
||||
*/
|
||||
static void remove_in_cache(cacher *id);
|
||||
typedef struct CacheEntry {
|
||||
struct CacheEntry *hash_next;
|
||||
struct CacheEntry *lru_prev;
|
||||
struct CacheEntry *lru_next;
|
||||
uint32_t hash;
|
||||
size_t cost;
|
||||
void *data;
|
||||
unsigned char key[];
|
||||
} CacheEntry;
|
||||
|
||||
/*
|
||||
* Remove data in cache
|
||||
* id: cache handler
|
||||
*/
|
||||
static void remove_in_cache(cacher *id) {
|
||||
cacheinfo *ip = id->top;
|
||||
cacheinfo *ic = ip->next;
|
||||
if (!ic)
|
||||
return;
|
||||
|
||||
while(ic->next != NULL) {
|
||||
if (!*ic->in_use) {
|
||||
totalsize -= ic->size;
|
||||
ip->next = ic->next;
|
||||
id->free_(ic->data);
|
||||
free(ic);
|
||||
} else {
|
||||
ip = ic;
|
||||
}
|
||||
ic = ip->next;
|
||||
}
|
||||
return;
|
||||
struct Cache {
|
||||
CacheOps ops;
|
||||
CacheEntry *buckets[CACHE_BUCKETS];
|
||||
CacheEntry *lru_head;
|
||||
CacheEntry *lru_tail;
|
||||
size_t capacity;
|
||||
size_t size;
|
||||
size_t count;
|
||||
size_t hits;
|
||||
size_t misses;
|
||||
};
|
||||
|
||||
static bool is_pinned(const Cache *cache, const CacheEntry *entry) {
|
||||
return cache->ops.is_pinned && cache->ops.is_pinned(entry->data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create new cache object
|
||||
* delcallback: callback function for delete cache data object
|
||||
* return: new cache handler
|
||||
*/
|
||||
cacher *cache_new(void *delcallback) {
|
||||
cacher *c = calloc(1, sizeof(cacher));
|
||||
|
||||
c->top = calloc(1, sizeof(cacheinfo));
|
||||
c->top->next = NULL;
|
||||
c->top->in_use = &dummytrue;
|
||||
c->free_ = delcallback;
|
||||
return c;
|
||||
static void lru_remove(Cache *cache, CacheEntry *entry) {
|
||||
if (entry->lru_prev)
|
||||
entry->lru_prev->lru_next = entry->lru_next;
|
||||
else
|
||||
cache->lru_head = entry->lru_next;
|
||||
if (entry->lru_next)
|
||||
entry->lru_next->lru_prev = entry->lru_prev;
|
||||
else
|
||||
cache->lru_tail = entry->lru_prev;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert data to cache
|
||||
* id : cache handler
|
||||
* key : data key
|
||||
* data : data to be cached
|
||||
* size : data size
|
||||
* in_use: in_use mark pointer, if in_use is nonzero, dont remove from cache
|
||||
*/
|
||||
void cache_insert(cacher *id, int key, void *data, int size, int *in_use) {
|
||||
cacheinfo *i = id->top;
|
||||
|
||||
if (CACHE_TOTALSIZE <= (totalsize >> 20)) {
|
||||
remove_in_cache(id);
|
||||
}
|
||||
|
||||
while(i->next != NULL) {
|
||||
i = i->next;
|
||||
}
|
||||
|
||||
i->key = key;
|
||||
i->data = data;
|
||||
i->size = size;
|
||||
i->next = calloc(1, sizeof(cacheinfo));
|
||||
i->next->next = NULL;
|
||||
if (in_use) {
|
||||
i->in_use = in_use;
|
||||
} else {
|
||||
i->in_use = &dummyfalse;
|
||||
}
|
||||
totalsize += size;
|
||||
static void lru_prepend(Cache *cache, CacheEntry *entry) {
|
||||
entry->lru_prev = NULL;
|
||||
entry->lru_next = cache->lru_head;
|
||||
if (cache->lru_head)
|
||||
cache->lru_head->lru_prev = entry;
|
||||
else
|
||||
cache->lru_tail = entry;
|
||||
cache->lru_head = entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search data in cache
|
||||
* id : cache handler
|
||||
* key: data search key
|
||||
* return: pointer to cached data
|
||||
*/
|
||||
void *cache_foreach(cacher *id, int key) {
|
||||
cacheinfo *i = id->top;
|
||||
|
||||
while(i != NULL) {
|
||||
if (i->key == key) {
|
||||
return i->data;
|
||||
}
|
||||
i = i->next;
|
||||
static void remove_entry(Cache *cache, CacheEntry *entry) {
|
||||
CacheEntry **link = &cache->buckets[entry->hash % CACHE_BUCKETS];
|
||||
while (*link != entry)
|
||||
link = &(*link)->hash_next;
|
||||
*link = entry->hash_next;
|
||||
lru_remove(cache, entry);
|
||||
cache->size -= entry->cost;
|
||||
cache->count--;
|
||||
cache->ops.destroy(entry->data);
|
||||
free(entry);
|
||||
}
|
||||
|
||||
static CacheEntry *find_entry(Cache *cache, const void *key, uint32_t hash) {
|
||||
for (CacheEntry *entry = cache->buckets[hash % CACHE_BUCKETS]; entry;
|
||||
entry = entry->hash_next) {
|
||||
if (entry->hash == hash && cache->ops.equal(entry->key, key))
|
||||
return entry;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void cache_clear(cacher *id) {
|
||||
cacheinfo *ic = id->top->next;
|
||||
cacheinfo *next;
|
||||
|
||||
while (ic != NULL) {
|
||||
next = ic->next;
|
||||
if (ic->data) {
|
||||
totalsize -= ic->size;
|
||||
id->free_(ic->data);
|
||||
}
|
||||
free(ic);
|
||||
ic = next;
|
||||
}
|
||||
id->top->next = NULL;
|
||||
Cache *cache_new(size_t capacity, const CacheOps *ops) {
|
||||
if (!ops || !ops->key_size || !ops->hash || !ops->equal || !ops->destroy)
|
||||
return NULL;
|
||||
Cache *cache = calloc(1, sizeof(Cache));
|
||||
if (!cache)
|
||||
return NULL;
|
||||
cache->ops = *ops;
|
||||
cache->capacity = capacity;
|
||||
return cache;
|
||||
}
|
||||
|
||||
void cache_destroy(Cache *cache) {
|
||||
if (!cache)
|
||||
return;
|
||||
while (cache->lru_tail)
|
||||
remove_entry(cache, cache->lru_tail);
|
||||
free(cache);
|
||||
}
|
||||
|
||||
void *cache_get(Cache *cache, const void *key) {
|
||||
if (!cache || !key)
|
||||
return NULL;
|
||||
CacheEntry *entry = find_entry(cache, key, cache->ops.hash(key));
|
||||
if (!entry) {
|
||||
cache->misses++;
|
||||
return NULL;
|
||||
}
|
||||
cache->hits++;
|
||||
lru_remove(cache, entry);
|
||||
lru_prepend(cache, entry);
|
||||
return entry->data;
|
||||
}
|
||||
|
||||
CacheInsertResult cache_insert(Cache *cache, const void *key, void *data, size_t cost) {
|
||||
if (!cache || !key || !data)
|
||||
return CACHE_INSERT_NOMEM;
|
||||
uint32_t hash = cache->ops.hash(key);
|
||||
if (find_entry(cache, key, hash))
|
||||
return CACHE_INSERT_EXISTS;
|
||||
if (cost > cache->capacity)
|
||||
return CACHE_INSERT_FULL;
|
||||
|
||||
CacheEntry *entry = malloc(sizeof(CacheEntry) + cache->ops.key_size);
|
||||
if (!entry)
|
||||
return CACHE_INSERT_NOMEM;
|
||||
|
||||
for (CacheEntry *old = cache->lru_tail, *prev;
|
||||
old && cache->size > cache->capacity - cost; old = prev) {
|
||||
prev = old->lru_prev;
|
||||
if (!is_pinned(cache, old))
|
||||
remove_entry(cache, old);
|
||||
}
|
||||
if (cache->size > cache->capacity - cost) {
|
||||
free(entry);
|
||||
return CACHE_INSERT_FULL;
|
||||
}
|
||||
|
||||
entry->hash = hash;
|
||||
entry->cost = cost;
|
||||
entry->data = data;
|
||||
memcpy(entry->key, key, cache->ops.key_size);
|
||||
unsigned bucket = hash % CACHE_BUCKETS;
|
||||
entry->hash_next = cache->buckets[bucket];
|
||||
cache->buckets[bucket] = entry;
|
||||
lru_prepend(cache, entry);
|
||||
cache->size += cost;
|
||||
cache->count++;
|
||||
return CACHE_INSERT_OK;
|
||||
}
|
||||
|
||||
bool cache_remove(Cache *cache, const void *key) {
|
||||
if (!cache || !key)
|
||||
return false;
|
||||
CacheEntry *entry = find_entry(cache, key, cache->ops.hash(key));
|
||||
if (!entry || is_pinned(cache, entry))
|
||||
return false;
|
||||
remove_entry(cache, entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t cache_clear(Cache *cache) {
|
||||
if (!cache)
|
||||
return 0;
|
||||
for (CacheEntry *entry = cache->lru_tail, *prev; entry; entry = prev) {
|
||||
prev = entry->lru_prev;
|
||||
if (!is_pinned(cache, entry))
|
||||
remove_entry(cache, entry);
|
||||
}
|
||||
return cache->count;
|
||||
}
|
||||
|
||||
CacheStats cache_get_stats(const Cache *cache) {
|
||||
if (!cache)
|
||||
return (CacheStats){0};
|
||||
return (CacheStats){
|
||||
.count = cache->count,
|
||||
.size = cache->size,
|
||||
.capacity = cache->capacity,
|
||||
.hits = cache->hits,
|
||||
.misses = cache->misses,
|
||||
};
|
||||
}
|
||||
|
||||
+46
-28
@@ -1,8 +1,5 @@
|
||||
/*
|
||||
* cache.h general cache manager
|
||||
*
|
||||
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
|
||||
* 1998- <masaki-c@is.aist-nara.ac.jp>
|
||||
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,33 +16,54 @@
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
/* $Id: cache.h,v 1.2 2003/07/21 23:06:47 chikama Exp $ */
|
||||
#ifndef XSYSTEM35_CACHE_H
|
||||
#define XSYSTEM35_CACHE_H
|
||||
|
||||
#ifndef __CASHE__
|
||||
#define __CASHE__
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "portab.h"
|
||||
typedef struct Cache Cache;
|
||||
|
||||
/* cache controlr infomartion */
|
||||
struct _cacheinfo {
|
||||
int key; /* key of data */
|
||||
int size; /* data size */
|
||||
struct _cacheinfo *next; /* next data */
|
||||
int *in_use; /* if *in_use is nonzero, dont remove from cache */
|
||||
void *data; /* real data */
|
||||
};
|
||||
typedef struct _cacheinfo cacheinfo;
|
||||
typedef struct {
|
||||
size_t key_size;
|
||||
uint32_t (*hash)(const void *key);
|
||||
bool (*equal)(const void *a, const void *b);
|
||||
void (*destroy)(void *data);
|
||||
bool (*is_pinned)(const void *data);
|
||||
} CacheOps;
|
||||
|
||||
/* cache handler */
|
||||
struct _cacher {
|
||||
void (*free_)(void *); /* free data callback */
|
||||
struct _cacheinfo *top; /* pointer to data */
|
||||
};
|
||||
typedef struct _cacher cacher;
|
||||
typedef enum {
|
||||
CACHE_INSERT_OK,
|
||||
CACHE_INSERT_EXISTS,
|
||||
CACHE_INSERT_FULL,
|
||||
CACHE_INSERT_NOMEM,
|
||||
} CacheInsertResult;
|
||||
|
||||
extern cacher *cache_new(void *delcallback);
|
||||
extern void cache_insert(cacher *id, int key, void *data, int size, int *in_use);
|
||||
extern void *cache_foreach(cacher *id, int key);
|
||||
extern void cache_clear(cacher *id);
|
||||
typedef struct {
|
||||
size_t count;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
size_t hits;
|
||||
size_t misses;
|
||||
} CacheStats;
|
||||
|
||||
#endif /* !__CASHE__ */
|
||||
Cache *cache_new(size_t capacity, const CacheOps *ops);
|
||||
void cache_destroy(Cache *cache);
|
||||
|
||||
/* The returned pointer is owned by the cache and is invalidated by removal. */
|
||||
void *cache_get(Cache *cache, const void *key);
|
||||
|
||||
/* Ownership of data is transferred only when CACHE_INSERT_OK is returned. */
|
||||
CacheInsertResult cache_insert(Cache *cache, const void *key, void *data, size_t cost);
|
||||
|
||||
/* Pinned entries cannot be removed. */
|
||||
bool cache_remove(Cache *cache, const void *key);
|
||||
|
||||
/* Removes all unpinned entries and returns the number of entries left. */
|
||||
size_t cache_clear(Cache *cache);
|
||||
|
||||
/* Returns zero-filled statistics when cache is NULL. */
|
||||
CacheStats cache_get_stats(const Cache *cache);
|
||||
|
||||
#endif /* XSYSTEM35_CACHE_H */
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
#include "cache.h"
|
||||
#include "unittest.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
int value;
|
||||
bool pinned;
|
||||
} TestValue;
|
||||
|
||||
static int destroyed;
|
||||
|
||||
static uint32_t int_hash(const void *key) {
|
||||
return (uint32_t)*(const int *)key;
|
||||
}
|
||||
|
||||
static bool int_equal(const void *a, const void *b) {
|
||||
return *(const int *)a == *(const int *)b;
|
||||
}
|
||||
|
||||
static void destroy_value(void *data) {
|
||||
destroyed++;
|
||||
free(data);
|
||||
}
|
||||
|
||||
static bool value_is_pinned(const void *data) {
|
||||
return ((const TestValue *)data)->pinned;
|
||||
}
|
||||
|
||||
static TestValue *new_value(int value) {
|
||||
TestValue *data = malloc(sizeof(TestValue));
|
||||
ASSERT_TRUE(data);
|
||||
data->value = value;
|
||||
data->pinned = false;
|
||||
return data;
|
||||
}
|
||||
|
||||
void cache_test(void) {
|
||||
CacheOps ops = {
|
||||
.key_size = sizeof(int),
|
||||
.hash = int_hash,
|
||||
.equal = int_equal,
|
||||
.destroy = destroy_value,
|
||||
.is_pinned = value_is_pinned,
|
||||
};
|
||||
Cache *cache = cache_new(2, &ops);
|
||||
ASSERT_TRUE(cache);
|
||||
destroyed = 0;
|
||||
CacheStats stats = cache_get_stats(cache);
|
||||
ASSERT_EQUAL(stats.count, 0);
|
||||
ASSERT_EQUAL(stats.size, 0);
|
||||
ASSERT_EQUAL(stats.capacity, 2);
|
||||
ASSERT_EQUAL(stats.hits, 0);
|
||||
ASSERT_EQUAL(stats.misses, 0);
|
||||
|
||||
// A lookup makes k1 most-recently used, so inserting k3 evicts k2.
|
||||
int k1 = 1, k2 = 2, k3 = 3, k4 = 4, k5 = 5;
|
||||
ASSERT_EQUAL(cache_insert(cache, &k1, new_value(1), 1), CACHE_INSERT_OK);
|
||||
ASSERT_EQUAL(cache_insert(cache, &k2, new_value(2), 1), CACHE_INSERT_OK);
|
||||
stats = cache_get_stats(cache);
|
||||
ASSERT_EQUAL(stats.count, 2);
|
||||
ASSERT_EQUAL(stats.size, 2);
|
||||
ASSERT_EQUAL(stats.capacity, 2);
|
||||
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k1))->value, 1);
|
||||
ASSERT_EQUAL(cache_insert(cache, &k3, new_value(3), 1), CACHE_INSERT_OK);
|
||||
ASSERT_NULL(cache_get(cache, &k2));
|
||||
stats = cache_get_stats(cache);
|
||||
ASSERT_EQUAL(stats.hits, 1);
|
||||
ASSERT_EQUAL(stats.misses, 1);
|
||||
|
||||
// Pinned entries survive both capacity eviction and cache_clear().
|
||||
((TestValue *)cache_get(cache, &k1))->pinned = true;
|
||||
ASSERT_EQUAL(cache_insert(cache, &k4, new_value(4), 1), CACHE_INSERT_OK);
|
||||
ASSERT_NULL(cache_get(cache, &k3));
|
||||
((TestValue *)cache_get(cache, &k4))->pinned = true;
|
||||
|
||||
// Insertion fails when every remaining entry is pinned.
|
||||
TestValue *rejected = new_value(5);
|
||||
ASSERT_EQUAL(cache_insert(cache, &k5, rejected, 1), CACHE_INSERT_FULL);
|
||||
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k1))->value, 1);
|
||||
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k4))->value, 4);
|
||||
ASSERT_EQUAL(cache_clear(cache), 2);
|
||||
ASSERT_EQUAL(destroyed, 2);
|
||||
free(rejected);
|
||||
|
||||
// Once unpinned, cache_clear() destroys the remaining entries.
|
||||
((TestValue *)cache_get(cache, &k1))->pinned = false;
|
||||
((TestValue *)cache_get(cache, &k4))->pinned = false;
|
||||
ASSERT_EQUAL(cache_clear(cache), 0);
|
||||
ASSERT_EQUAL(destroyed, 4);
|
||||
cache_destroy(cache);
|
||||
|
||||
stats = cache_get_stats(NULL);
|
||||
ASSERT_EQUAL(stats.count, 0);
|
||||
ASSERT_EQUAL(stats.size, 0);
|
||||
ASSERT_EQUAL(stats.capacity, 0);
|
||||
ASSERT_EQUAL(stats.hits, 0);
|
||||
ASSERT_EQUAL(stats.misses, 0);
|
||||
}
|
||||
+1
-2
@@ -26,8 +26,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL_timer.h>
|
||||
#include <SDL_mixer.h>
|
||||
#include "sdl_mixer_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* cdrom.sdl3mixer.c CD audio replacement using SDL3_mixer
|
||||
*
|
||||
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
|
||||
* 1998- <masaki-c@is.aist-nara.ac.jp>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ald_manager.h"
|
||||
#include "cdrom.h"
|
||||
#include "music_private.h"
|
||||
#include "sdl3_mixer_backend.h"
|
||||
#include "system.h"
|
||||
|
||||
#define PLAYLIST_MAX 256
|
||||
|
||||
static bool enabled;
|
||||
static char *playlist[PLAYLIST_MAX];
|
||||
static int track_no;
|
||||
static uint32_t start_time;
|
||||
|
||||
static void cdrom_stop(void);
|
||||
|
||||
static int clamp_volume(int volume)
|
||||
{
|
||||
if (volume < 0)
|
||||
return 0;
|
||||
if (volume > 100)
|
||||
return 100;
|
||||
return volume;
|
||||
}
|
||||
|
||||
static float music_gain(void)
|
||||
{
|
||||
return clamp_volume(prv.volval[BGM_VOLVAL_CH]) / 100.0f;
|
||||
}
|
||||
|
||||
static void apply_volume(void)
|
||||
{
|
||||
sdl3_mixer_set_music_gain(SDL3_MIXER_MUSIC_CD, music_gain());
|
||||
}
|
||||
|
||||
static void free_playlist(void)
|
||||
{
|
||||
for (int track = 0; track < PLAYLIST_MAX; track++) {
|
||||
free(playlist[track]);
|
||||
playlist[track] = NULL;
|
||||
}
|
||||
prv.cd_maxtrk = 0;
|
||||
}
|
||||
|
||||
static bool cdrom_init(char *playlist_path)
|
||||
{
|
||||
if (!playlist_path || !playlist_path[0])
|
||||
return false;
|
||||
|
||||
if (enabled)
|
||||
cdrom_stop();
|
||||
enabled = false;
|
||||
free_playlist();
|
||||
FILE *fp = fopen(playlist_path, "r");
|
||||
char buf[256];
|
||||
if (fp) {
|
||||
/* The first line represents track 1, which is data on mixed CDs. */
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
} else {
|
||||
fp = fopen("_inmm.ini", "r");
|
||||
}
|
||||
if (!fp) {
|
||||
if (ald_get_maxno(DRIFILE_MIDI) == 0)
|
||||
NOTICE("cdrom: Cannot open playlist %s", playlist_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int track = 2; track < PLAYLIST_MAX; track++) {
|
||||
if (!fgets(buf, sizeof(buf), fp))
|
||||
break;
|
||||
for (char *s = buf; *s; s++) {
|
||||
if (*s == '\\')
|
||||
*s = '/';
|
||||
else if (*s == '\r' || *s == '\n') {
|
||||
*s = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*buf) {
|
||||
playlist[track] = strdup(buf);
|
||||
if (playlist[track])
|
||||
prv.cd_maxtrk = track;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
NOTICE("cdrom: Loaded playlist from %s", playlist_path);
|
||||
|
||||
track_no = 0;
|
||||
enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cdrom_exit(void)
|
||||
{
|
||||
if (enabled)
|
||||
cdrom_stop();
|
||||
enabled = false;
|
||||
free_playlist();
|
||||
}
|
||||
|
||||
static void cdrom_reset(void)
|
||||
{
|
||||
if (enabled)
|
||||
cdrom_stop();
|
||||
}
|
||||
|
||||
static bool cdrom_start(int track, int loop)
|
||||
{
|
||||
if (!enabled || track < 0 || track >= PLAYLIST_MAX || !playlist[track])
|
||||
return false;
|
||||
cdrom_stop();
|
||||
|
||||
const char *path = playlist[track];
|
||||
#ifdef __ANDROID__
|
||||
/* SDL's Android file IO requires an absolute path for installed tracks. */
|
||||
char absolute_path[PATH_MAX];
|
||||
if (!realpath(path, absolute_path))
|
||||
return false;
|
||||
path = absolute_path;
|
||||
#endif
|
||||
|
||||
MIX_Audio *audio = sdl3_mixer_load_music_file(path);
|
||||
if (!audio) {
|
||||
WARNING("Cannot load %s: %s", path, SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
/* CD playback passes loop directly; this differs from BGM's loop - 1. */
|
||||
int loops = loop == 0 ? -1 : loop;
|
||||
if (!sdl3_mixer_play_music(SDL3_MIXER_MUSIC_CD, audio,
|
||||
loops, 0, music_gain()))
|
||||
return false;
|
||||
|
||||
track_no = track;
|
||||
start_time = sys_get_ticks();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cdrom_stop(void)
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
sdl3_mixer_stop_music(SDL3_MIXER_MUSIC_CD, 0);
|
||||
track_no = 0;
|
||||
}
|
||||
|
||||
static bool cdrom_get_playing_info(cd_time *info)
|
||||
{
|
||||
if (!enabled || !track_no ||
|
||||
!sdl3_mixer_music_active(SDL3_MIXER_MUSIC_CD))
|
||||
return false;
|
||||
|
||||
int ms = sys_get_ticks() - start_time;
|
||||
info->t = track_no;
|
||||
info->m = ms / (60 * 1000);
|
||||
ms %= 60 * 1000;
|
||||
info->s = ms / 1000;
|
||||
ms %= 1000;
|
||||
info->f = ms * CD_FPS / 1000;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cdrom_is_available(void)
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
static void cdrom_reapply_volume(void)
|
||||
{
|
||||
if (enabled && track_no)
|
||||
apply_volume();
|
||||
}
|
||||
|
||||
cdromdevice_t cdrom_mp3 = {
|
||||
.init = cdrom_init,
|
||||
.exit = cdrom_exit,
|
||||
.reset = cdrom_reset,
|
||||
.start = cdrom_start,
|
||||
.stop = cdrom_stop,
|
||||
.getpos = cdrom_get_playing_info,
|
||||
.is_available = cdrom_is_available,
|
||||
.reapply_volume = cdrom_reapply_volume,
|
||||
};
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
@@ -68,8 +68,11 @@ int cg_brightness;
|
||||
static CG_WHERETODISP loc_policy, loc_policy0;
|
||||
static SDL_Point loc_where, loc_where0;
|
||||
|
||||
/* extracted cg data cache control object */
|
||||
static cacher *cacheid;
|
||||
static Cache *cg_cache;
|
||||
|
||||
#ifndef CG_CACHE_SIZE
|
||||
#define CG_CACHE_SIZE (10 << 20)
|
||||
#endif
|
||||
|
||||
/* static methods */
|
||||
static CG_TYPE check_cgformat(uint8_t *data);
|
||||
@@ -78,6 +81,23 @@ static SDL_Point set_display_loc(cgdata *cg);
|
||||
static void clear_display_loc();
|
||||
static void display_cg(cgdata *cg, int x, int y, int sprite_color, bool alpha_blend);
|
||||
static cgdata *loader(int no);
|
||||
static void release_cg(cgdata *cg);
|
||||
|
||||
static uint32_t int_hash(const void *key) {
|
||||
return (uint32_t)*(const int *)key;
|
||||
}
|
||||
|
||||
static bool int_equal(const void *a, const void *b) {
|
||||
return *(const int *)a == *(const int *)b;
|
||||
}
|
||||
|
||||
static void destroy_cg(void *data) {
|
||||
cgdata_free(data);
|
||||
}
|
||||
|
||||
static bool cg_is_pinned(const void *data) {
|
||||
return ((const cgdata *)data)->refcnt != 0;
|
||||
}
|
||||
|
||||
static int compare_integers(const void *a, const void *b) {
|
||||
int int_a = *(const int *)a;
|
||||
@@ -107,17 +127,19 @@ static void cg_mosaic(cgdata *cg) {
|
||||
default:
|
||||
SYSERROR("Unsupported cg depth %d", cg->depth);
|
||||
}
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormatFrom(
|
||||
cg->pic, cg->width, cg->height, cg->depth, cg->width * (cg->depth / 8), format);
|
||||
SDL_Surface *tmp = SDL_CreateRGBSurfaceWithFormat(
|
||||
0, (cg->width + MOSAIC_SIZE - 1) / MOSAIC_SIZE, (cg->height + MOSAIC_SIZE - 1) / MOSAIC_SIZE, cg->depth, format);
|
||||
if (sf->format->palette)
|
||||
SDL_SetSurfacePalette(tmp, sf->format->palette);
|
||||
SDL_Surface *sf = sdl_create_surface_from(cg->pic, cg->width, cg->height,
|
||||
cg->depth, cg->width * (cg->depth / 8), format);
|
||||
SDL_Surface *tmp = sdl_create_surface(
|
||||
(cg->width + MOSAIC_SIZE - 1) / MOSAIC_SIZE,
|
||||
(cg->height + MOSAIC_SIZE - 1) / MOSAIC_SIZE, cg->depth, format);
|
||||
SDL_Palette *palette = sdl_get_surface_palette(sf);
|
||||
if (palette)
|
||||
sdl_set_surface_palette(tmp, palette);
|
||||
// NOTE: SDL_BlitScaled() does not support 8-bit surfaces.
|
||||
SDL_SoftStretch(sf, NULL, tmp, NULL);
|
||||
SDL_SoftStretch(tmp, NULL, sf, NULL);
|
||||
SDL_FreeSurface(tmp);
|
||||
SDL_FreeSurface(sf);
|
||||
sdl_destroy_surface(tmp);
|
||||
sdl_destroy_surface(sf);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -244,7 +266,10 @@ static cgdata *loader(int no) {
|
||||
cgdata *cg = NULL;
|
||||
|
||||
/* search in cache */
|
||||
if (NULL != (cg = (cgdata *)cache_foreach(cacheid, no))) return cg;
|
||||
if (NULL != (cg = cache_get(cg_cache, &no))) {
|
||||
cg->refcnt++;
|
||||
return cg;
|
||||
}
|
||||
|
||||
/* read from file */
|
||||
if (NULL == (dfile = ald_getdata(DRIFILE_CG, no))) return NULL;
|
||||
@@ -286,8 +311,9 @@ static cgdata *loader(int no) {
|
||||
cg_mosaic(cg);
|
||||
}
|
||||
/* insert to cache */
|
||||
int size = cg->width * cg->height * (cg->depth / 8);
|
||||
cache_insert(cacheid, no, cg, size, NULL);
|
||||
size_t size = (size_t)cg->width * cg->height * (cg->depth / 8);
|
||||
cg->refcnt = 1;
|
||||
cg->cached = cache_insert(cg_cache, &no, cg, size) == CACHE_INSERT_OK;
|
||||
}
|
||||
|
||||
/* ok to free */
|
||||
@@ -296,11 +322,27 @@ static cgdata *loader(int no) {
|
||||
return cg;
|
||||
}
|
||||
|
||||
static void release_cg(cgdata *cg) {
|
||||
if (!cg)
|
||||
return;
|
||||
cg->refcnt--;
|
||||
if (!cg->cached && cg->refcnt == 0)
|
||||
cgdata_free(cg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initilize cache
|
||||
*/
|
||||
void cg_init(void) {
|
||||
cacheid = cache_new(cgdata_free);
|
||||
CacheOps ops = {
|
||||
.key_size = sizeof(int),
|
||||
.hash = int_hash,
|
||||
.equal = int_equal,
|
||||
.destroy = destroy_cg,
|
||||
.is_pinned = cg_is_pinned,
|
||||
};
|
||||
cache_destroy(cg_cache);
|
||||
cg_cache = cache_new((size_t)CG_CACHE_SIZE, &ops);
|
||||
cg_reset();
|
||||
}
|
||||
|
||||
@@ -314,6 +356,10 @@ void cg_reset(void) {
|
||||
memset(&loc_where0, 0, sizeof(loc_where0));
|
||||
}
|
||||
|
||||
CacheStats cg_get_cache_stats(void) {
|
||||
return cache_get_stats(cg_cache);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set cg display location
|
||||
* x : display location x
|
||||
@@ -405,6 +451,7 @@ void cg_load(int no, int flg) {
|
||||
}
|
||||
/* clear display offset */
|
||||
clear_display_loc();
|
||||
release_cg(cg);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -413,7 +460,7 @@ void cg_load(int no, int flg) {
|
||||
* shadowno: file no for alpha ( >= 0 )
|
||||
*/
|
||||
void cg_load_with_alpha(int cgno, int shadowno) {
|
||||
cgdata *cg = NULL, *scg;
|
||||
cgdata *cg = NULL, *scg = NULL;
|
||||
SDL_Point p;
|
||||
|
||||
/* load pixel */
|
||||
@@ -421,15 +468,15 @@ void cg_load_with_alpha(int cgno, int shadowno) {
|
||||
if (NULL == (cg = loader(cgno))) return;
|
||||
if (cg->type != ALCG_PMS16) {
|
||||
WARNING("commandGX cg_no != 16bitPMS");
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* load alpha pixel */
|
||||
if (NULL == (scg = loader(shadowno))) return;
|
||||
if (NULL == (scg = loader(shadowno))) goto cleanup;
|
||||
if (scg->type != ALCG_PMS8) {
|
||||
WARNING("commandGX shadow_no != 8bitPMS");
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* set alpha pixel offset */
|
||||
@@ -445,6 +492,10 @@ void cg_load_with_alpha(int cgno, int shadowno) {
|
||||
|
||||
/* clear display offset */
|
||||
clear_display_loc();
|
||||
|
||||
cleanup:
|
||||
release_cg(cg);
|
||||
release_cg(scg);
|
||||
}
|
||||
|
||||
static uint8_t* load_cg_from_file(char *fname_utf8, int *status, long *filesize) {
|
||||
@@ -555,6 +606,7 @@ void cg_get_info(int no, SDL_Rect *info) {
|
||||
info->y = p.y;
|
||||
info->w = cg->width;
|
||||
info->h = cg->height;
|
||||
release_cg(cg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,34 +646,37 @@ SDL_Surface *cg_load_as_sdlsurface_from_data(uint8_t *data, size_t size, bool mo
|
||||
cg_mosaic(cg);
|
||||
}
|
||||
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, cg->width, cg->height, 32,
|
||||
SDL_Surface *sf = sdl_create_surface(cg->width, cg->height, 32,
|
||||
(cg->alpha || (type == ALCG_PMS8 && as_alpha)) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_XRGB8888);
|
||||
|
||||
if (type == ALCG_PMS8) {
|
||||
if (as_alpha) {
|
||||
// Treat PMS8 as an alpha map. Palette is ignored.
|
||||
SDL_FillRect(sf, NULL, SDL_MapRGBA(sf->format, 255, 255, 255, 0));
|
||||
SDL_FillRect(sf, NULL, sdl_map_rgba(sf, 255, 255, 255, 0));
|
||||
cg->alpha = cg->pic;
|
||||
cg->pic = NULL;
|
||||
} else {
|
||||
// Treat PMS8 as a paletted image.
|
||||
SDL_Surface *pic = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 8, cg->width, SDL_PIXELFORMAT_INDEX8);
|
||||
SDL_Palette *palette = SDL_AllocPalette(256);
|
||||
SDL_Surface *pic = sdl_create_surface_from(cg->pic, cg->width,
|
||||
cg->height, 8, cg->width, SDL_PIXELFORMAT_INDEX8);
|
||||
SDL_Palette *palette = sdl_create_palette(256);
|
||||
SDL_SetPaletteColors(palette, cg->pal, 0, 256);
|
||||
SDL_SetSurfacePalette(pic, palette);
|
||||
SDL_FreePalette(palette);
|
||||
sdl_set_surface_palette(pic, palette);
|
||||
sdl_destroy_palette(palette);
|
||||
SDL_BlitSurface(pic, NULL, sf, NULL);
|
||||
SDL_FreeSurface(pic);
|
||||
sdl_destroy_surface(pic);
|
||||
}
|
||||
} else {
|
||||
SDL_Surface *pic;
|
||||
if (type == ALCG_PMS16 || type == ALCG_BMP24) {
|
||||
pic = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 16, cg->width * 2, SDL_PIXELFORMAT_RGB565);
|
||||
pic = sdl_create_surface_from(cg->pic, cg->width, cg->height,
|
||||
16, cg->width * 2, SDL_PIXELFORMAT_RGB565);
|
||||
} else {
|
||||
pic = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
|
||||
pic = sdl_create_surface_from(cg->pic, cg->width, cg->height,
|
||||
24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
|
||||
}
|
||||
SDL_BlitSurface(pic, NULL, sf, NULL);
|
||||
SDL_FreeSurface(pic);
|
||||
sdl_destroy_surface(pic);
|
||||
}
|
||||
if (cg->alpha) {
|
||||
// Copy alpha values from cg->alpha to sf->pixels.
|
||||
@@ -650,8 +705,8 @@ SDL_Surface *cg_load_as_sdlsurface(int no) {
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void load_censor_list(const char *path) {
|
||||
if (cacheid)
|
||||
cache_clear(cacheid);
|
||||
if (cg_cache)
|
||||
cache_clear(cg_cache);
|
||||
if (censor_list) {
|
||||
free(censor_list);
|
||||
censor_list = NULL;
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
#ifndef __CG__
|
||||
#define __CG__
|
||||
|
||||
#include <SDL_rect.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "cache.h"
|
||||
#include "portab.h"
|
||||
|
||||
struct SDL_Surface;
|
||||
@@ -61,6 +62,8 @@ typedef struct {
|
||||
|
||||
int vsp_bank; // palette bank for vsp
|
||||
int pms_bank; // palette bank for pms
|
||||
int refcnt; // references held by cache users
|
||||
bool cached; // owned by the CG cache
|
||||
} cgdata;
|
||||
|
||||
/*
|
||||
@@ -86,6 +89,7 @@ extern void cgdata_free(cgdata *cg);
|
||||
extern struct SDL_Surface *cg_load_as_sdlsurface(int no);
|
||||
struct SDL_Surface *cg_load_as_sdlsurface_from_data(uint8_t *data, size_t size, bool mosaic, bool as_alpha);
|
||||
extern void load_censor_list(const char *path);
|
||||
extern CacheStats cg_get_cache_stats(void);
|
||||
|
||||
extern int cg_vspPB;
|
||||
extern int cg_fflg;
|
||||
|
||||
+3
-3
@@ -562,7 +562,7 @@ void commands2F52() {
|
||||
void commands2F53() {
|
||||
int eFlag = getCaliValue();
|
||||
|
||||
nact->patch_ec = eFlag;
|
||||
nact->patch_ec = (eFlag == 1);
|
||||
|
||||
TRACE("patchEC %d:", eFlag);
|
||||
}
|
||||
@@ -936,7 +936,7 @@ void commands2F6F() {
|
||||
void commands2F70() {
|
||||
int eFlag = getCaliValue();
|
||||
|
||||
nact->patch_emen = eFlag;
|
||||
nact->patch_emen = (eFlag == 1);
|
||||
|
||||
TRACE("patchEMEN %d:", eFlag);
|
||||
}
|
||||
@@ -1152,7 +1152,7 @@ void commands2F85() {
|
||||
void commands2F86() {
|
||||
int eFlag = getCaliValue();
|
||||
|
||||
nact->patch_g0 = eFlag;
|
||||
nact->patch_g0 = (eFlag == 1);
|
||||
|
||||
TRACE("patchG0 %d:", eFlag);
|
||||
}
|
||||
|
||||
+5
-5
@@ -55,13 +55,13 @@ void commandEC() {
|
||||
int num = getCaliValue();
|
||||
Ecomtbl *e;
|
||||
SDL_Rect *r;
|
||||
int adj = nact->patch_ec ? 0 : -1;
|
||||
|
||||
if (num == 0) {
|
||||
r = &nact->ags.view_area;
|
||||
ags_fillRectangle(r->x, r->y, r->w, r->h, 0);
|
||||
ags_updateArea(r->x, r->y, r->w, r->h);
|
||||
ags_fillRectangle(r->x, r->y, r->w + adj, r->h + adj, 0);
|
||||
ags_updateArea(r->x, r->y, r->w + adj, r->h + adj);
|
||||
} else {
|
||||
int adj = (nact->patch_ec == 0) ? -1 : 0;
|
||||
e = &tbl[num - 1];
|
||||
ags_fillRectangle(e->r.x, e->r.y, e->r.w + adj, e->r.h + adj, e->pal);
|
||||
ags_updateArea(e->r.x, e->r.y, e->r.w + adj, e->r.h + adj);
|
||||
@@ -92,7 +92,7 @@ void commandEM() {
|
||||
|
||||
SDL_Point p = {x, y};
|
||||
SDL_Rect r = tbl[num - 1].r;
|
||||
if (nact->patch_emen == 0) {
|
||||
if (!nact->patch_emen) {
|
||||
r.w -= 1;
|
||||
r.h -= 1;
|
||||
}
|
||||
@@ -112,7 +112,7 @@ void commandEN() {
|
||||
|
||||
for (int i = min; i <= max; i++) {
|
||||
SDL_Rect r = tbl[i - 1].r;
|
||||
if (nact->patch_emen == 0) {
|
||||
if (!nact->patch_emen) {
|
||||
r.w -= 1;
|
||||
r.h -= 1;
|
||||
}
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ void commandTPC() {
|
||||
case 1: sl_pushTextColor(type, nact->sel.MsgFontColor); break;
|
||||
default: WARNING("TPC: unknown type %d", type); break;
|
||||
}
|
||||
TRACE("TPC %d", exp);
|
||||
TRACE("TPC %d", type);
|
||||
}
|
||||
|
||||
void commandTPS() {
|
||||
@@ -67,7 +67,7 @@ void commandTPS() {
|
||||
case 1: sl_pushTextSize(type, nact->sel.MsgFontSize); break;
|
||||
default: WARNING("TPS: unknown type %d", type); break;
|
||||
}
|
||||
TRACE("TPS %d", exp);
|
||||
TRACE("TPS %d", type);
|
||||
}
|
||||
|
||||
void commandTOP() {
|
||||
|
||||
+6
-6
@@ -90,7 +90,7 @@ typedef struct {
|
||||
#define UNITMAP_ATTRIB_WALKRESULT (3)
|
||||
|
||||
/* UnitMAP 全体へのポインタ */
|
||||
static int *UnitMap = NULL;
|
||||
static vmvar_t *UnitMap = NULL;
|
||||
/* VC command */
|
||||
static int nPageNum;
|
||||
static int x0Map;
|
||||
@@ -160,7 +160,7 @@ void commandVC() { /* from Rance4 */
|
||||
return;
|
||||
}
|
||||
|
||||
UnitMap = (int *)calloc(cxMap * cyMap * nPageNum * UNITMAP_ATTRIB_DEPTH, sizeof(int));
|
||||
UnitMap = calloc(cxMap * cyMap * nPageNum * UNITMAP_ATTRIB_DEPTH, sizeof(*UnitMap));
|
||||
srcimg = (UnitMapSrcImg *)calloc(nPageNum, sizeof(UnitMapSrcImg));
|
||||
|
||||
if (NULL == UnitMap || NULL == srcimg) {
|
||||
@@ -458,7 +458,7 @@ void commandVR() { /* from Rance4 */
|
||||
int nPage = getCaliValue();
|
||||
int nType = getCaliValue();
|
||||
vmvar_t *var = getCaliVariable();
|
||||
int *dst;
|
||||
vmvar_t *dst;
|
||||
|
||||
TRACE("VR %d,%d,%p:",nPage, nType, var);
|
||||
|
||||
@@ -481,14 +481,14 @@ void commandVR() { /* from Rance4 */
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(dst, var, sizeof(int) * MAPSIZE_PER_ATTRIB);
|
||||
memcpy(dst, var, MAPSIZE_PER_ATTRIB * sizeof(*dst));
|
||||
}
|
||||
|
||||
void commandVW() { /* from Rance4 */
|
||||
int nPage = getCaliValue();
|
||||
int nType = getCaliValue();
|
||||
vmvar_t *var = getCaliVariable();
|
||||
int *src;
|
||||
vmvar_t *src;
|
||||
|
||||
TRACE("VW %d,%d,%p:",nPage, nType, var);
|
||||
|
||||
@@ -511,7 +511,7 @@ void commandVW() { /* from Rance4 */
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(var, src, sizeof(int) * MAPSIZE_PER_ATTRIB);
|
||||
memcpy(var, src, MAPSIZE_PER_ATTRIB * sizeof(*src));
|
||||
}
|
||||
|
||||
void commandVE() { /* from T2 */
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ void commandY() {
|
||||
}
|
||||
break;
|
||||
case 1900:
|
||||
nact->patch_ec = p2;
|
||||
nact->patch_ec = (p2 == 1);
|
||||
// We're sure this game was built for System3.9 v5.50, which by default
|
||||
// routes CD-DA commands to DRIFILE_WAVE.
|
||||
muscd_init_bgm(DRIFILE_WAVE, 999);
|
||||
|
||||
+16
-48
@@ -22,6 +22,7 @@
|
||||
/* $Id: cmdz.c,v 1.35 2003/01/12 10:48:50 chikama Exp $ */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
@@ -53,6 +54,14 @@ static uint32_t get_counter(int num) {
|
||||
return sys_get_ticks() - counters[num].base;
|
||||
}
|
||||
|
||||
static void write_display_info(vmvar_t *var, enum ags_display display) {
|
||||
int width, height, depth;
|
||||
ags_getDisplayInfo(display, &width, &height, &depth);
|
||||
var[0] = width;
|
||||
var[1] = height;
|
||||
var[2] = depth;
|
||||
}
|
||||
|
||||
void commandZC() {
|
||||
/* システムの使用環境を変更する */
|
||||
int m = getCaliValue();
|
||||
@@ -106,8 +115,7 @@ void commandZB() {
|
||||
/* メッセージ文字を太さを設定 */
|
||||
int weight = getCaliValue();
|
||||
|
||||
if (nact->ags.enable_zb)
|
||||
nact->ags.font_weight = weight > 5 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL;
|
||||
nact->ags.font_weight = weight > 5 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL;
|
||||
|
||||
TRACE("ZB %d:", weight);
|
||||
}
|
||||
@@ -369,24 +377,8 @@ void commandZZ1() {
|
||||
void commandZZ2() {
|
||||
/* 機種文字列を文字列領域 num に返す(MAX12文字)*/
|
||||
int num = getCaliValue();
|
||||
#if defined(linux)
|
||||
static uint8_t str[] = {0x82, 0x6B, 0x82, 0x89, 0x82, 0x8E, 0x82, 0x95, 0x82, 0x98, 0};
|
||||
#elif defined(__FreeBSD__)
|
||||
static uint8_t str[] = {0x82, 0x65, 0x82, 0x92, 0x82, 0x85, 0x82, 0x85, 0x82, 0x61, 0x82, 0x72, 0x82, 0x63, 0};
|
||||
#elif defined(__OpenBSD__)
|
||||
static uint8_t str[] = {0x82, 0x6E, 0x82, 0x90, 0x82, 0x85, 0x82, 0x8E, 0x82, 0x61, 0x82, 0x72, 0x82, 0x63, 0};
|
||||
#elif defined(__NetBSD__)
|
||||
static uint8_t str[] = {0x82, 0x6D, 0x82, 0x85, 0x82, 0x94, 0x82, 0x61, 0x82, 0x72, 0x82, 0x63, 0};
|
||||
#elif defined(sun)
|
||||
static uint8_t str[] = {0x82, 0x72, 0x82, 0x95, 0x82, 0x8E, 0x82, 0x6E, 0x82, 0x72, 0};
|
||||
#elif defined(__osf__)
|
||||
static uint8_t str[] = {0x82, 0x63, 0x82, 0x89, 0x82, 0x87, 0x82, 0x89, 0x82, 0x94, 0x82, 0x81, 0x82, 0x8C, 0x82, 0x74, 0x82, 0x6D, 0x82, 0x68, 0x82, 0x77, 0};
|
||||
#elif defined(sgi)
|
||||
static uint8_t str[] = {0x82, 0x68, 0x82, 0x71, 0x82, 0x68, 0x82, 0x77, 0};
|
||||
#else
|
||||
static uint8_t str[] = {0x82, 0x74, 0x82, 0x8e, 0x82, 0x8b, 0x82, 0x8e, 0x82, 0x8f, 0x82, 0x97, 0x82, 0x8e, 0};
|
||||
#endif
|
||||
svar_set(num, str);
|
||||
|
||||
svar_set(num, SDL_GetPlatform());
|
||||
|
||||
TRACE("ZZ2 %d:",num);
|
||||
}
|
||||
@@ -394,39 +386,21 @@ void commandZZ2() {
|
||||
void commandZZ3() {
|
||||
/* WINDOWSの全画面サイズや表示色数を変数列に返す */
|
||||
vmvar_t *var = getCaliVariable();
|
||||
DispInfo info;
|
||||
|
||||
ags_getWindowInfo(&info);
|
||||
*var = info.width;
|
||||
*(var + 1) = info.height;
|
||||
*(var + 2) = info.depth;
|
||||
|
||||
write_display_info(var, AGS_DISPLAY_CURRENT);
|
||||
TRACE("ZZ3 %p:",var);
|
||||
}
|
||||
|
||||
void commandZZ4() {
|
||||
/* DIB の全画面 サイズや色数を変数列に返す */
|
||||
vmvar_t *var = getCaliVariable();
|
||||
DispInfo info;
|
||||
|
||||
ags_getDIBInfo(&info);
|
||||
*var = info.width;
|
||||
*(var + 1) = info.height;
|
||||
*(var + 2) = info.depth;
|
||||
|
||||
write_display_info(var, AGS_DISPLAY_DIB);
|
||||
TRACE("ZZ4 %p:",var);
|
||||
}
|
||||
|
||||
void commandZZ5() {
|
||||
/* SYSTEM3.5用表示画面 の サイズや色数を変数列に返す */
|
||||
vmvar_t *var = getCaliVariable();
|
||||
DispInfo info;
|
||||
|
||||
ags_getViewAreaInfo(&info);
|
||||
*var = info.width;
|
||||
*(var + 1) = info.height;
|
||||
*(var + 2) = info.depth;
|
||||
|
||||
write_display_info(var, AGS_DISPLAY_VIEW_AREA);
|
||||
TRACE("ZZ5 %p:",var);
|
||||
}
|
||||
|
||||
@@ -449,13 +423,7 @@ void commandZZ8() {
|
||||
void commandZZ9() {
|
||||
/* 起動時のスクリーンサイズを取得する */
|
||||
vmvar_t *var = getCaliVariable();
|
||||
DispInfo info;
|
||||
|
||||
ags_getWindowInfo(&info);
|
||||
*var = info.width;
|
||||
*(var + 1) = info.height;
|
||||
*(var + 2) = info.depth;
|
||||
|
||||
write_display_info(var, AGS_DISPLAY_DESKTOP);
|
||||
TRACE("ZZ9 %p:",var);
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -23,7 +23,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -163,18 +163,18 @@ static SDL_Cursor *init_system_cursor(const char *image[]) {
|
||||
|
||||
void cursor_init(void) {
|
||||
cursor[CURSOR_ARROW] = SDL_GetDefaultCursor();
|
||||
cursor[CURSOR_CROSS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
|
||||
cursor[CURSOR_IBEAM] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
|
||||
cursor[CURSOR_CROSS] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_CROSSHAIR);
|
||||
cursor[CURSOR_IBEAM] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_TEXT);
|
||||
cursor[CURSOR_ICON] = SDL_GetDefaultCursor();
|
||||
cursor[CURSOR_NO] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);
|
||||
cursor[CURSOR_SIZE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
|
||||
cursor[CURSOR_SIZEALL] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
|
||||
cursor[CURSOR_SIZENESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
|
||||
cursor[CURSOR_SIZENS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
|
||||
cursor[CURSOR_SIZENWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
|
||||
cursor[CURSOR_SIZEWE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
|
||||
cursor[CURSOR_NO] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_NOT_ALLOWED);
|
||||
cursor[CURSOR_SIZE] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_MOVE);
|
||||
cursor[CURSOR_SIZEALL] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_MOVE);
|
||||
cursor[CURSOR_SIZENESW] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_NESW_RESIZE);
|
||||
cursor[CURSOR_SIZENS] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_NS_RESIZE);
|
||||
cursor[CURSOR_SIZENWSE] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_NWSE_RESIZE);
|
||||
cursor[CURSOR_SIZEWE] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_EW_RESIZE);
|
||||
cursor[CURSOR_UPARROW] = init_system_cursor(cursor_uparrow);
|
||||
cursor[CURSOR_WAIT] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT);
|
||||
cursor[CURSOR_WAIT] = sdl_create_system_cursor(SDL_COMPAT_SYSTEM_CURSOR_WAIT);
|
||||
}
|
||||
|
||||
static bool cursor_new(uint8_t* data, int no, CursorImage *cursorImage, TCursorDirEntry *cursordirentry) {
|
||||
@@ -215,7 +215,7 @@ static bool cursor_new(uint8_t* data, int no, CursorImage *cursorImage, TCursorD
|
||||
}
|
||||
|
||||
if (cursor[no])
|
||||
SDL_FreeCursor(cursor[no]);
|
||||
sdl_destroy_cursor(cursor[no]);
|
||||
cursor[no] = SDL_CreateCursor(buf3, buf4, 32, 32, cursordirentry->wxHotspot, cursordirentry->wyHotspot);
|
||||
|
||||
free(buf1);
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "debugger.h"
|
||||
|
||||
@@ -18,11 +18,15 @@
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "system.h"
|
||||
#include "debugger.h"
|
||||
#include "debugger_private.h"
|
||||
#include "debug_symbol.h"
|
||||
#include "ald_manager.h"
|
||||
#include "cg.h"
|
||||
#include "font.h"
|
||||
#include "nact.h"
|
||||
#include "variable.h"
|
||||
#ifdef HAVE_SIGACTION
|
||||
@@ -207,6 +211,34 @@ static const char * const help_help = NULL;
|
||||
|
||||
static CommandResult cmd_help(void);
|
||||
|
||||
static const char desc_info[] = "Print information about the program.";
|
||||
static const char help_info[] =
|
||||
"Syntax: info cache\n"
|
||||
"\n"
|
||||
"Displays usage and lookup statistics for each cache.";
|
||||
|
||||
static void print_cache_stats(const char *name, CacheStats stats) {
|
||||
size_t lookups = stats.hits + stats.misses;
|
||||
double hit_rate = lookups ? 100.0 * stats.hits / lookups : 0.0;
|
||||
printf("%-8s %8zu %12zu %12zu %8zu %8zu %8.1f%%\n",
|
||||
name, stats.count, stats.size, stats.capacity,
|
||||
stats.hits, stats.misses, hit_rate);
|
||||
}
|
||||
|
||||
static CommandResult cmd_info(void) {
|
||||
char *arg = strtok(NULL, whitespaces);
|
||||
if (!arg || strcmp(arg, "cache") || strtok(NULL, whitespaces)) {
|
||||
puts(help_info);
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
puts("Cache Entries Size Capacity Hits Misses Hit rate");
|
||||
print_cache_stats("Glyph", font_get_cache_stats());
|
||||
print_cache_stats("CG", cg_get_cache_stats());
|
||||
print_cache_stats("Archive", ald_get_cache_stats());
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
static const char desc_list[] = "List specified function or line.";
|
||||
static const char help_list[] =
|
||||
"Syntax: list\n"
|
||||
@@ -361,6 +393,7 @@ const Command dbg_cui_commands[] = {
|
||||
{"continue", "c", desc_continue, help_continue, cmd_continue},
|
||||
{"delete", "d", desc_delete, help_delete, cmd_delete},
|
||||
{"help", "h", desc_help, help_help, cmd_help},
|
||||
{"info", "i", desc_info, help_info, cmd_info},
|
||||
{"list", "l", desc_list, help_list, cmd_list},
|
||||
{"step", "s", desc_step, help_step, cmd_step},
|
||||
{"finish", NULL, desc_finish, help_finish, cmd_finish},
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL_thread.h>
|
||||
#include "sdl_compat.h"
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
|
||||
@@ -46,6 +46,7 @@ struct _dridata {
|
||||
char *data; /* real data */
|
||||
char *name; /* not used */
|
||||
int refcnt; /* reference count */
|
||||
bool cached; /* owned by the archive cache */
|
||||
drifiles *a; /* archive file obj */
|
||||
};
|
||||
typedef struct _dridata dridata;
|
||||
|
||||
+34
-33
@@ -21,7 +21,8 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <SDL.h>
|
||||
#include <stdlib.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "system.h"
|
||||
#include "gfx.h"
|
||||
@@ -139,7 +140,7 @@ static EffectTexture *create_effect_texture(surface_t *as, int x, int y, int w,
|
||||
} else {
|
||||
SDL_Surface *sf = gfx_createSurfaceView(as->sdl_surface, x, y, w, h);
|
||||
t->tx = SDL_CreateTextureFromSurface(gfx_renderer, sf);
|
||||
SDL_FreeSurface(sf);
|
||||
sdl_destroy_surface(sf);
|
||||
t->rect = (SDL_Rect){ 0, 0, w, h };
|
||||
}
|
||||
return t;
|
||||
@@ -160,7 +161,7 @@ static void destroy_effect_texture(EffectTexture *t) {
|
||||
free(t);
|
||||
}
|
||||
|
||||
static int render_effect_texture(EffectTexture *t, const SDL_Rect* srcrect, const SDL_Rect* dstrect) {
|
||||
static bool render_effect_texture(EffectTexture *t, const SDL_Rect* srcrect, const SDL_Rect* dstrect) {
|
||||
SDL_Rect real_srcrect;
|
||||
if (!srcrect) {
|
||||
real_srcrect = t->rect;
|
||||
@@ -169,7 +170,7 @@ static int render_effect_texture(EffectTexture *t, const SDL_Rect* srcrect, cons
|
||||
real_srcrect.x += t->rect.x;
|
||||
real_srcrect.y += t->rect.y;
|
||||
}
|
||||
return SDL_RenderCopy(gfx_renderer, t->tx, &real_srcrect, dstrect);
|
||||
return sdl_render_texture(gfx_renderer, t->tx, &real_srcrect, dstrect);
|
||||
}
|
||||
|
||||
struct effect {
|
||||
@@ -197,7 +198,7 @@ static void eff_finish(struct effect *eff, bool present) {
|
||||
if (present) {
|
||||
SDL_RenderClear(gfx_renderer);
|
||||
if (!eff->is_fullscreen)
|
||||
SDL_RenderCopy(gfx_renderer, gfx_texture, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, gfx_texture, NULL, NULL);
|
||||
render_effect_texture(eff->tx_new, NULL, &eff->dst_rect);
|
||||
SDL_RenderPresent(gfx_renderer);
|
||||
}
|
||||
@@ -384,7 +385,7 @@ static void mosaic_step(struct effect *eff, float progress);
|
||||
static void mosaic_free(struct effect *eff);
|
||||
|
||||
static struct effect *mosaic_new(SDL_Rect *rect, EffectTexture *old, EffectTexture *new, enum effect_type type) {
|
||||
if (!SDL_RenderTargetSupported(gfx_renderer))
|
||||
if (!sdl_render_target_supported(gfx_renderer))
|
||||
return fallback_effect_new(rect, old, new, type);
|
||||
|
||||
struct mosaic_effect *eff = calloc(1, sizeof(struct mosaic_effect));
|
||||
@@ -409,7 +410,7 @@ static void mosaic(EffectTexture *src, SDL_Texture *tmp, SDL_Texture *dst, int w
|
||||
.h = h / scale,
|
||||
};
|
||||
SDL_SetRenderTarget(gfx_renderer, tmp);
|
||||
SDL_RenderCopyF(gfx_renderer, src->tx, &src->rect, &fr);
|
||||
sdl_render_texture_float(gfx_renderer, src->tx, &src->rect, &fr);
|
||||
|
||||
SDL_Rect dstr = {
|
||||
.x = w * (1 - scale) / 2,
|
||||
@@ -419,8 +420,8 @@ static void mosaic(EffectTexture *src, SDL_Texture *tmp, SDL_Texture *dst, int w
|
||||
};
|
||||
SDL_SetRenderTarget(gfx_renderer, dst);
|
||||
|
||||
SDL_SetTextureScaleMode(tmp, SDL_ScaleModeNearest);
|
||||
SDL_RenderCopy(gfx_renderer, tmp, NULL, &dstr);
|
||||
sdl_set_texture_scale_mode_nearest(tmp);
|
||||
sdl_render_texture(gfx_renderer, tmp, NULL, &dstr);
|
||||
SDL_SetRenderTarget(gfx_renderer, NULL);
|
||||
}
|
||||
|
||||
@@ -431,16 +432,16 @@ static void mosaic_step(struct effect *eff, float progress) {
|
||||
const int max_scale = 80;
|
||||
int scale = (1.f - progress) * (max_scale - 1) + 1;
|
||||
mosaic(eff->tx_new, this->tmp_new, this->tx_mosaic, eff->dst_rect.w, eff->dst_rect.h, scale);
|
||||
SDL_RenderCopy(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
|
||||
sdl_render_texture(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
|
||||
} else if (eff->type == EFFECT_CROSSFADE_MOSAIC) {
|
||||
const int max_scale = 96;
|
||||
float scale = (progress < 0.5f ? progress : 1.f - progress) * 2 * (max_scale - 1) + 1;
|
||||
mosaic(eff->tx_old, this->tmp_old, this->tx_mosaic, eff->dst_rect.w, eff->dst_rect.h, scale);
|
||||
SDL_RenderCopy(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
|
||||
sdl_render_texture(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
|
||||
mosaic(eff->tx_new, this->tmp_new, this->tx_mosaic, eff->dst_rect.w, eff->dst_rect.h, scale);
|
||||
SDL_SetTextureBlendMode(this->tx_mosaic, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(this->tx_mosaic, progress * 255);
|
||||
SDL_RenderCopy(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
|
||||
sdl_render_texture(gfx_renderer, this->tx_mosaic, NULL, &eff->dst_rect);
|
||||
SDL_SetTextureBlendMode(this->tx_mosaic, SDL_BLENDMODE_NONE);
|
||||
} else {
|
||||
assert(!"Cannot happen");
|
||||
@@ -514,7 +515,7 @@ static void brightness_step(struct effect *eff, float progress) {
|
||||
render_effect_texture(texture, NULL, &eff->dst_rect);
|
||||
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderDrawColor(gfx_renderer, color, color, color, alpha);
|
||||
SDL_RenderFillRect(gfx_renderer, &eff->dst_rect);
|
||||
sdl_render_fill_rect(gfx_renderer, &eff->dst_rect);
|
||||
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
|
||||
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
||||
SDL_RenderPresent(gfx_renderer);
|
||||
@@ -531,9 +532,8 @@ static void dithering_fade_step(struct effect *eff, float progress);
|
||||
static void dithering_fade_free(struct effect *eff);
|
||||
|
||||
static EffectTexture *create_dither_pattern_texture(int w, int h, int val) {
|
||||
SDL_PixelFormat *fmt = SDL_AllocFormat(SDL_PIXELFORMAT_ARGB8888);
|
||||
Uint32 col = SDL_MapRGBA(fmt, val, val, val, SDL_ALPHA_OPAQUE);
|
||||
SDL_FreeFormat(fmt);
|
||||
Uint32 col = sdl_map_rgba_format(
|
||||
SDL_PIXELFORMAT_ARGB8888, val, val, val, SDL_ALPHA_OPAQUE);
|
||||
|
||||
Uint32 *pixels = calloc(w * h, sizeof(Uint32));
|
||||
if (!pixels)
|
||||
@@ -610,10 +610,10 @@ static void dithering_fade_step(struct effect *eff, float progress) {
|
||||
static void dithering_fade_free(struct effect *eff) {
|
||||
if (eff->type == EFFECT_DITHERING_FADEOUT) {
|
||||
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, 255);
|
||||
SDL_RenderFillRect(gfx_renderer, &eff->dst_rect);
|
||||
sdl_render_fill_rect(gfx_renderer, &eff->dst_rect);
|
||||
} else if (eff->type == EFFECT_DITHERING_WHITEOUT) {
|
||||
SDL_SetRenderDrawColor(gfx_renderer, 255, 255, 255, 255);
|
||||
SDL_RenderFillRect(gfx_renderer, &eff->dst_rect);
|
||||
sdl_render_fill_rect(gfx_renderer, &eff->dst_rect);
|
||||
}
|
||||
eff_finish(eff, false);
|
||||
free(eff);
|
||||
@@ -1020,7 +1020,7 @@ static void zoom_blend_blur_step(struct effect *eff, float progress);
|
||||
static void zoom_blend_blur_free(struct effect *eff);
|
||||
|
||||
static struct effect *zoom_blend_blur_new(SDL_Rect *rect, EffectTexture *old, EffectTexture *new) {
|
||||
if (!SDL_RenderTargetSupported(gfx_renderer))
|
||||
if (!sdl_render_target_supported(gfx_renderer))
|
||||
return fallback_effect_new(rect, old, new, EFFECT_ZOOM_BLEND_BLUR);
|
||||
|
||||
struct zoom_blend_blur_effect *eff = calloc(1, sizeof(struct zoom_blend_blur_effect));
|
||||
@@ -1054,7 +1054,7 @@ static void zoom_blend_blur_step(struct effect *eff, float progress) {
|
||||
|
||||
this->index = (this->index + 1) % ZOOM_BLEND_BLUR_STEPS;
|
||||
|
||||
SDL_RenderFillRect(gfx_renderer, &eff->dst_rect);
|
||||
sdl_render_fill_rect(gfx_renderer, &eff->dst_rect);
|
||||
EffectTexture texture_rect = { NULL, { 0, 0, eff->dst_rect.w, eff->dst_rect.h } };
|
||||
for (int i = 0; i < ZOOM_BLEND_BLUR_STEPS; i++) {
|
||||
texture_rect.tx = this->tx[i];
|
||||
@@ -1092,7 +1092,7 @@ static void linear_blur_step(struct effect *eff, float progress);
|
||||
static void linear_blur_free(struct effect *eff);
|
||||
|
||||
static struct effect *linear_blur_new(SDL_Rect *rect, EffectTexture *old, EffectTexture *new, enum effect_type type) {
|
||||
if (!SDL_RenderTargetSupported(gfx_renderer))
|
||||
if (!sdl_render_target_supported(gfx_renderer))
|
||||
return fallback_effect_new(rect, old, new, type);
|
||||
|
||||
struct linear_blur_effect *lbe = calloc(1, sizeof(struct linear_blur_effect));
|
||||
@@ -1196,7 +1196,7 @@ static void polygon_mask_step(struct effect *eff, float progress);
|
||||
static void polygon_mask_free(struct effect *eff);
|
||||
|
||||
static struct effect *polygon_mask_new(SDL_Rect *rect, EffectTexture *old, EffectTexture *new, enum effect_type type) {
|
||||
if (SDL_RenderTargetSupported(gfx_renderer)) {
|
||||
if (sdl_render_target_supported(gfx_renderer)) {
|
||||
struct polygon_mask_effect *pmf = calloc(1, sizeof(struct polygon_mask_effect));
|
||||
if (!pmf)
|
||||
NOMEMERR();
|
||||
@@ -1229,7 +1229,7 @@ static void draw_pentagram(int center_x, int center_y, float radius, float rotat
|
||||
for (int i = 0; i < 10; i++) {
|
||||
v[i].position.x = radius * (p[i].x * cos_r - p[i].y * sin_r) + center_x;
|
||||
v[i].position.y = radius * (p[i].x * sin_r + p[i].y * cos_r) + center_y;
|
||||
v[i].color = (SDL_Color){0, 0, 0, 0};
|
||||
v[i].color = sdl_vertex_color(0, 0, 0, 0);
|
||||
}
|
||||
const int indices[15] = {
|
||||
0, 3, 7,
|
||||
@@ -1257,7 +1257,7 @@ static void draw_hexagram(int center_x, int center_y, float radius, float rotate
|
||||
for (int i = 0; i < 6; i++) {
|
||||
v[i].position.x = radius * (p[i].x * cos_r - p[i].y * sin_r) + center_x;
|
||||
v[i].position.y = radius * (p[i].x * sin_r + p[i].y * cos_r) + center_y;
|
||||
v[i].color = (SDL_Color){0, 0, 0, 0};
|
||||
v[i].color = sdl_vertex_color(0, 0, 0, 0);
|
||||
}
|
||||
SDL_RenderGeometry(gfx_renderer, NULL, v, 6, NULL, 0);
|
||||
}
|
||||
@@ -1428,7 +1428,7 @@ static void polygon_mask_step(struct effect *eff, float progress) {
|
||||
SDL_SetRenderTarget(gfx_renderer, NULL);
|
||||
render_effect_texture(eff->tx_new, NULL, &eff->dst_rect);
|
||||
SDL_SetTextureBlendMode(this->tx_tmp, SDL_BLENDMODE_BLEND);
|
||||
SDL_RenderCopy(gfx_renderer, this->tx_tmp, NULL, &eff->dst_rect);
|
||||
sdl_render_texture(gfx_renderer, this->tx_tmp, NULL, &eff->dst_rect);
|
||||
SDL_RenderPresent(gfx_renderer);
|
||||
}
|
||||
|
||||
@@ -1493,7 +1493,8 @@ static void rotate_step(struct effect *eff, float progress) {
|
||||
r.w *= scale;
|
||||
r.h *= scale;
|
||||
render_effect_texture(bg_texture, NULL, &eff->dst_rect);
|
||||
SDL_RenderCopyEx(gfx_renderer, fg_texture->tx, &fg_texture->rect, &r, angle, NULL, SDL_FLIP_NONE);
|
||||
sdl_render_texture_rotated(gfx_renderer, fg_texture->tx,
|
||||
&fg_texture->rect, &r, angle, NULL, SDL_FLIP_NONE);
|
||||
SDL_RenderPresent(gfx_renderer);
|
||||
}
|
||||
|
||||
@@ -1573,7 +1574,7 @@ static void zigzag_crossfade_step(struct effect *eff, float progress);
|
||||
static void zigzag_crossfade_free(struct effect *eff);
|
||||
|
||||
static struct effect *zigzag_crossfade_new(SDL_Rect *rect, EffectTexture *old, EffectTexture *new) {
|
||||
if (!SDL_RenderTargetSupported(gfx_renderer))
|
||||
if (!sdl_render_target_supported(gfx_renderer))
|
||||
return fallback_effect_new(rect, old, new, EFFECT_ZIGZAG_CROSSFADE);
|
||||
|
||||
struct zigzag_crossfade_effect *eff = calloc(1, sizeof(struct zigzag_crossfade_effect));
|
||||
@@ -1608,7 +1609,7 @@ static void wave_warp_v(SDL_Texture *src, SDL_Texture *dst, int w, int h, float
|
||||
for (int x = 0; x < w; x++) {
|
||||
sr.x = dr.x = x;
|
||||
dr.y = sinf(phase + x * (2 * M_PIf / 360 * length)) * amplitude;
|
||||
SDL_RenderCopy(gfx_renderer, src, &sr, &dr);
|
||||
sdl_render_texture(gfx_renderer, src, &sr, &dr);
|
||||
}
|
||||
SDL_SetRenderTarget(gfx_renderer, NULL);
|
||||
}
|
||||
@@ -1622,13 +1623,13 @@ static void zigzag_crossfade_step(struct effect *eff, float progress) {
|
||||
|
||||
wave_warp_h(eff->tx_old, this->tmp1, eff->dst_rect.w, eff->dst_rect.h, amp, len, phase);
|
||||
wave_warp_v(this->tmp1, this->tmp2, eff->dst_rect.w, eff->dst_rect.h, amp, len, phase);
|
||||
SDL_RenderCopy(gfx_renderer, this->tmp2, NULL, &eff->dst_rect);
|
||||
sdl_render_texture(gfx_renderer, this->tmp2, NULL, &eff->dst_rect);
|
||||
|
||||
wave_warp_h(eff->tx_new, this->tmp1, eff->dst_rect.w, eff->dst_rect.h, amp, len, phase);
|
||||
wave_warp_v(this->tmp1, this->tmp2, eff->dst_rect.w, eff->dst_rect.h, amp, len, phase);
|
||||
SDL_SetTextureBlendMode(this->tmp2, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureAlphaMod(this->tmp2, progress * 255);
|
||||
SDL_RenderCopy(gfx_renderer, this->tmp2, NULL, &eff->dst_rect);
|
||||
sdl_render_texture(gfx_renderer, this->tmp2, NULL, &eff->dst_rect);
|
||||
SDL_SetTextureBlendMode(this->tmp2, SDL_BLENDMODE_NONE);
|
||||
|
||||
SDL_RenderPresent(gfx_renderer);
|
||||
@@ -1690,7 +1691,7 @@ static void raster_blend_free(struct effect *eff);
|
||||
static struct effect *raster_blend_new(SDL_Rect *rect, int sx, int sy) {
|
||||
SDL_Surface *sprite = gfx_dib_to_surface_with_alpha(sx, sy, rect->w, rect->h);
|
||||
EffectTexture *texture = create_effect_texture_from_surface(sprite);
|
||||
SDL_FreeSurface(sprite);
|
||||
sdl_destroy_surface(sprite);
|
||||
struct effect *eff = calloc(1, sizeof(struct effect));
|
||||
if (!eff)
|
||||
NOMEMERR();
|
||||
@@ -1876,7 +1877,7 @@ struct effect *sprite_effect_init(SDL_Rect *rect, int dx, int dy, int sx, int sy
|
||||
EffectTexture *tx_old = create_effect_texture(gfx_dibinfo, dx, dy, rect->w, rect->h);
|
||||
SDL_Surface *sprite = gfx_dib_to_surface_colorkey(sx, sy, rect->w, rect->h, col);
|
||||
EffectTexture *tx_new = create_effect_texture_from_surface(sprite);
|
||||
SDL_FreeSurface(sprite);
|
||||
sdl_destroy_surface(sprite);
|
||||
|
||||
switch (type) {
|
||||
case EFFECT_PAN_IN_DOWN:
|
||||
@@ -1906,7 +1907,7 @@ struct effect *effect_sactamask_init(SDL_Surface *mask) {
|
||||
void effect_step(struct effect *eff, float progress) {
|
||||
SDL_RenderClear(gfx_renderer);
|
||||
if (!eff->is_fullscreen)
|
||||
SDL_RenderCopy(gfx_renderer, gfx_texture, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, gfx_texture, NULL, NULL);
|
||||
eff->step(eff, progress);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
#ifndef __EFFECT_H__
|
||||
#define __EFFECT_H__
|
||||
|
||||
#include <SDL_surface.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "ags.h"
|
||||
|
||||
// Effect types of the CE/CD command.
|
||||
|
||||
+146
-109
@@ -24,7 +24,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
@@ -35,7 +35,6 @@
|
||||
#include "debugger.h"
|
||||
#include "nact.h"
|
||||
#include "gfx.h"
|
||||
#include "gfx_private.h"
|
||||
#include "scheduler.h"
|
||||
#include "menu.h"
|
||||
#include "modal.h"
|
||||
@@ -44,6 +43,12 @@
|
||||
#include "hacks.h"
|
||||
#include "volume.h"
|
||||
|
||||
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 2
|
||||
#include <SDL_syswm.h>
|
||||
// Windows defines this name too; the key table below uses input.h's enum.
|
||||
#undef KEY_EXECUTE
|
||||
#endif
|
||||
|
||||
static void get_event(void);
|
||||
static void keyEventProsess(SDL_KeyboardEvent *e, bool pressed);
|
||||
|
||||
@@ -52,17 +57,20 @@ static uint32_t custom_event_type = (uint32_t)-1;
|
||||
enum CustomEventCode {
|
||||
SIMULATE_RIGHT_BUTTON,
|
||||
DEBUGGER_COMMAND,
|
||||
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 3
|
||||
WIN_MENU_COMMAND,
|
||||
#endif
|
||||
};
|
||||
|
||||
/* pointer の状態 */
|
||||
static int mousex, mousey, mouseb;
|
||||
static SDL_Point mouse_pos;
|
||||
static int mouseb;
|
||||
static int mouse_wheel_up, mouse_wheel_down;
|
||||
bool RawKeyInfo[256];
|
||||
|
||||
/* SDL Joystick */
|
||||
static int joyinfo=0;
|
||||
|
||||
static int keytable[SDL_NUM_SCANCODES] = {
|
||||
static int keytable[SDL_COMPAT_SCANCODE_COUNT] = {
|
||||
[SDL_SCANCODE_A] = KEY_A,
|
||||
[SDL_SCANCODE_B] = KEY_B,
|
||||
[SDL_SCANCODE_C] = KEY_C,
|
||||
@@ -187,18 +195,18 @@ static int keytable[SDL_NUM_SCANCODES] = {
|
||||
static int joy_device_index = -1;
|
||||
static SDL_Joystick *js;
|
||||
|
||||
static bool joy_open(int index) {
|
||||
static bool joy_open(sdl_joystick_device_t device) {
|
||||
if (js)
|
||||
return false;
|
||||
|
||||
js = SDL_JoystickOpen(index);
|
||||
js = sdl_open_joystick(device);
|
||||
if (!js)
|
||||
return false;
|
||||
|
||||
const char *name = SDL_JoystickName(js);
|
||||
int axes = SDL_JoystickNumAxes(js);
|
||||
int buttons = SDL_JoystickNumButtons(js);
|
||||
SDL_JoystickEventState(SDL_ENABLE);
|
||||
const char *name = sdl_get_joystick_name(js);
|
||||
int axes = sdl_get_num_joystick_axes(js);
|
||||
int buttons = sdl_get_num_joystick_buttons(js);
|
||||
sdl_set_joystick_events_enabled(true);
|
||||
NOTICE("SDL joystick '%s' %d axes %d buttons", name, axes, buttons);
|
||||
return true;
|
||||
}
|
||||
@@ -206,14 +214,22 @@ static bool joy_open(int index) {
|
||||
static bool joy_init(void) {
|
||||
SDL_Init(SDL_INIT_JOYSTICK);
|
||||
|
||||
if (joy_device_index >= 0)
|
||||
return joy_open(joy_device_index);
|
||||
|
||||
for (int i = 0; i < SDL_NumJoysticks(); i++) {
|
||||
if (joy_open(i))
|
||||
return true;
|
||||
int count = 0;
|
||||
sdl_joystick_device_t *devices = sdl_get_joystick_devices(&count);
|
||||
bool opened = false;
|
||||
if (joy_device_index >= 0) {
|
||||
if (joy_device_index < count)
|
||||
opened = joy_open(devices[joy_device_index]);
|
||||
} else {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (joy_open(devices[i])) {
|
||||
opened = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
SDL_free(devices);
|
||||
return opened;
|
||||
}
|
||||
|
||||
void event_init(void) {
|
||||
@@ -224,7 +240,7 @@ void event_init(void) {
|
||||
|
||||
void event_remove(void) {
|
||||
if (js) {
|
||||
SDL_JoystickClose(js);
|
||||
sdl_close_joystick(js);
|
||||
js = NULL;
|
||||
}
|
||||
}
|
||||
@@ -264,8 +280,8 @@ void send_agsevent(enum agsevent_type type, int code) {
|
||||
agsevent_t agse = {
|
||||
.type = type,
|
||||
.code = code,
|
||||
.mousex = mousex,
|
||||
.mousey = mousey
|
||||
.mousex = mouse_pos.x,
|
||||
.mousey = mouse_pos.y
|
||||
};
|
||||
nact->ags.eventcb(&agse); // Async in emscripten
|
||||
|
||||
@@ -277,37 +293,32 @@ void send_agsevent(enum agsevent_type type, int code) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void event_set_mouse_location(int x, int y) {
|
||||
// scale mouse x and y
|
||||
float scalex, scaley;
|
||||
SDL_RenderGetScale(gfx_renderer, &scalex, &scaley);
|
||||
x *= scalex;
|
||||
y *= scaley;
|
||||
|
||||
// calculate window borders
|
||||
int logw, logh;
|
||||
SDL_RenderGetLogicalSize(gfx_renderer, &logw, &logh);
|
||||
|
||||
float scalew, scaleh;
|
||||
scalew = logw * scalex;
|
||||
scaleh = logh * scaley;
|
||||
|
||||
int winw, winh;
|
||||
SDL_GetWindowSize(gfx_window, &winw, &winh);
|
||||
|
||||
float border_left = (winw - scalew) / 2;
|
||||
float border_top = (winh - scaleh) / 2;
|
||||
|
||||
// offset x and y by window borders
|
||||
x += border_left;
|
||||
y += border_top;
|
||||
SDL_WarpMouseInWindow(gfx_window, x, y);
|
||||
void event_set_mouse_internal_location(int x, int y) {
|
||||
mouse_pos.x = x;
|
||||
mouse_pos.y = y;
|
||||
send_agsevent(AGSEVENT_MOUSE_MOTION, 0);
|
||||
}
|
||||
|
||||
void event_set_mouse_internal_location(int x, int y) {
|
||||
mousex = x;
|
||||
mousey = y;
|
||||
send_agsevent(AGSEVENT_MOUSE_MOTION, 0);
|
||||
SDL_Point event_get_touch_position(const SDL_TouchFingerEvent *e) {
|
||||
#if XSYSTEM35_SDL_VERSION == 2
|
||||
int view_w, view_h;
|
||||
gfx_getViewSize(&view_w, &view_h);
|
||||
return (SDL_Point){e->x * view_w, e->y * view_h};
|
||||
#else
|
||||
return (SDL_Point){(int)e->x, (int)e->y};
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool touch_is_on_view_edge(const SDL_TouchFingerEvent *e) {
|
||||
#if XSYSTEM35_SDL_VERSION == 2
|
||||
return e->x == 0.0f || e->x == 1.0f ||
|
||||
e->y == 0.0f || e->y == 1.0f;
|
||||
#else
|
||||
int view_w, view_h;
|
||||
gfx_getViewSize(&view_w, &view_h);
|
||||
return e->x <= 0.0f || e->x >= view_w ||
|
||||
e->y <= 0.0f || e->y >= view_h;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Stores a deferred touch event (valid if .timestamp != 0) to add a delay
|
||||
@@ -315,28 +326,33 @@ void event_set_mouse_internal_location(int x, int y) {
|
||||
// touch event. This prevents the game from processing a button down event
|
||||
// before reading the pointer position.
|
||||
static SDL_TouchFingerEvent deferred_touch_event;
|
||||
static uint64_t deferred_touch_deadline;
|
||||
#define TOUCH_EVENT_DELAY 20
|
||||
|
||||
static void defer_touch_event(SDL_TouchFingerEvent *e) {
|
||||
switch (deferred_touch_event.type) {
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_COMPAT_EVENT_FINGER_DOWN:
|
||||
mouseb |= 1 << SDL_BUTTON_LEFT;
|
||||
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_LEFT)] = true;
|
||||
break;
|
||||
case SDL_FINGERUP:
|
||||
case SDL_COMPAT_EVENT_FINGER_UP:
|
||||
mouseb &= ~(1 << SDL_BUTTON_LEFT | 1 << SDL_BUTTON_RIGHT);
|
||||
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_LEFT)] = false;
|
||||
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_RIGHT)] = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (e)
|
||||
if (e) {
|
||||
deferred_touch_event = *e;
|
||||
else
|
||||
deferred_touch_event.timestamp = 0;
|
||||
deferred_touch_deadline = SDL_GetTicks() + TOUCH_EVENT_DELAY;
|
||||
} else {
|
||||
deferred_touch_deadline = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void fire_deferred_touch_event(void) {
|
||||
if (deferred_touch_event.timestamp && deferred_touch_event.timestamp + TOUCH_EVENT_DELAY < SDL_GetTicks()) {
|
||||
if (deferred_touch_deadline && deferred_touch_deadline < SDL_GetTicks()) {
|
||||
defer_touch_event(NULL);
|
||||
}
|
||||
}
|
||||
@@ -354,62 +370,66 @@ static void rance4v2_hack(void) {
|
||||
}
|
||||
|
||||
void event_handle_event(SDL_Event *e) {
|
||||
if (!gfx_convertEventCoordinates(e))
|
||||
WARNING("Failed to convert event coordinates: %s", SDL_GetError());
|
||||
|
||||
if (modal_handle_event(e))
|
||||
return;
|
||||
|
||||
switch (sdl_get_window_event(e)) {
|
||||
case SDL_COMPAT_WINDOW_EVENT_EXPOSED:
|
||||
gfx_requestRedraw();
|
||||
return;
|
||||
case SDL_COMPAT_WINDOW_EVENT_FOCUS_LOST:
|
||||
volume_on_window_focus(false);
|
||||
return;
|
||||
case SDL_COMPAT_WINDOW_EVENT_FOCUS_GAINED:
|
||||
volume_on_window_focus(true);
|
||||
return;
|
||||
case SDL_COMPAT_WINDOW_EVENT_NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (e->type) {
|
||||
case SDL_QUIT:
|
||||
case SDL_COMPAT_EVENT_QUIT:
|
||||
menu_quitmenu_open();
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (e->window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
gfx_dirty = true;
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
volume_on_window_focus(false);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
volume_on_window_focus(true);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 2
|
||||
case SDL_SYSWMEVENT:
|
||||
win_menu_onSysWMEvent(e->syswm.msg);
|
||||
if (e->syswm.msg->msg.win.msg == WM_COMMAND)
|
||||
win_menu_onCommand((unsigned int)e->syswm.msg->msg.win.wParam);
|
||||
break;
|
||||
#endif
|
||||
case SDL_APP_DIDENTERFOREGROUND:
|
||||
gfx_dirty = true;
|
||||
case SDL_COMPAT_EVENT_DID_ENTER_FOREGROUND:
|
||||
gfx_requestRedraw();
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_COMPAT_EVENT_KEY_DOWN:
|
||||
keyEventProsess(&e->key, true);
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
case SDL_COMPAT_EVENT_KEY_UP:
|
||||
keyEventProsess(&e->key, false);
|
||||
switch (e->key.keysym.sym) {
|
||||
switch (sdl_keyboard_event_key(&e->key)) {
|
||||
case SDLK_F1:
|
||||
msgskip_activate(!msgskip_isActivated());
|
||||
break;
|
||||
case SDLK_F4:
|
||||
gfx_setFullscreen(!gfx_fullscreen);
|
||||
gfx_setFullscreen(!gfx_isFullscreen());
|
||||
break;
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
if (e->key.keysym.scancode == SDL_SCANCODE_AC_BACK) {
|
||||
if (sdl_keyboard_event_scancode(&e->key) == SDL_SCANCODE_AC_BACK) {
|
||||
menu_quitmenu_open();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_COMPAT_EVENT_MOUSE_MOTION:
|
||||
event_set_mouse_internal_location(e->motion.x, e->motion.y);
|
||||
#ifdef _WIN32
|
||||
win_menu_onMouseMotion(e->motion.x, e->motion.y);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
case SDL_COMPAT_EVENT_MOUSE_WHEEL:
|
||||
{
|
||||
int y = e->wheel.y * (e->wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1);
|
||||
if (y > 0)
|
||||
@@ -420,13 +440,13 @@ void event_handle_event(SDL_Event *e) {
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_COMPAT_EVENT_MOUSE_BUTTON_DOWN:
|
||||
mouseb |= (1 << e->button.button);
|
||||
RawKeyInfo[mouse_to_rawkey(e->button.button)] = true;
|
||||
send_agsevent(AGSEVENT_BUTTON_PRESS, mouse_to_agsevent(e->button.button));
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
case SDL_COMPAT_EVENT_MOUSE_BUTTON_UP:
|
||||
mouseb &= (0xffffffff ^ (1 << e->button.button));
|
||||
RawKeyInfo[mouse_to_rawkey(e->button.button)] = false;
|
||||
send_agsevent(AGSEVENT_BUTTON_RELEASE, mouse_to_agsevent(e->button.button));
|
||||
@@ -435,55 +455,55 @@ void event_handle_event(SDL_Event *e) {
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_FINGERDOWN:
|
||||
if (SDL_GetNumTouchFingers(e->tfinger.touchId) >= 3) {
|
||||
case SDL_COMPAT_EVENT_FINGER_DOWN: {
|
||||
int finger_count =
|
||||
sdl_get_num_touch_fingers(sdl_touch_event_id(&e->tfinger));
|
||||
if (finger_count >= 3) {
|
||||
menu_open();
|
||||
} else if (SDL_GetNumTouchFingers(e->tfinger.touchId) >= 2) {
|
||||
} else if (finger_count >= 2) {
|
||||
mouseb &= ~(1 << SDL_BUTTON_LEFT);
|
||||
mouseb |= 1 << SDL_BUTTON_RIGHT;
|
||||
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_LEFT)] = false;
|
||||
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_RIGHT)] = true;
|
||||
send_agsevent(AGSEVENT_BUTTON_PRESS, AGSEVENT_BUTTON_RIGHT);
|
||||
} else {
|
||||
// SDL_RendererEventWatch clamps touch locations outside of the
|
||||
// viewport to 0.0-1.0. Treat such events as right-clicks.
|
||||
// Treat touches on the edge or outside the viewport as
|
||||
// right-clicks.
|
||||
int button;
|
||||
if (e->tfinger.x == 0.0f || e->tfinger.x == 1.0f || e->tfinger.y == 0.0f || e->tfinger.y == 1.0f) {
|
||||
if (touch_is_on_view_edge(&e->tfinger)) {
|
||||
button = SDL_BUTTON_RIGHT;
|
||||
mouseb |= 1 << SDL_BUTTON_RIGHT;
|
||||
RawKeyInfo[mouse_to_rawkey(SDL_BUTTON_RIGHT)] = true;
|
||||
} else {
|
||||
button = SDL_BUTTON_LEFT;
|
||||
mousex = e->tfinger.x * view_w;
|
||||
mousey = e->tfinger.y * view_h;
|
||||
mouse_pos = event_get_touch_position(&e->tfinger);
|
||||
send_agsevent(AGSEVENT_MOUSE_MOTION, 0);
|
||||
defer_touch_event(&e->tfinger);
|
||||
}
|
||||
send_agsevent(AGSEVENT_BUTTON_PRESS, mouse_to_agsevent(button));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_FINGERUP:
|
||||
if (SDL_GetNumTouchFingers(e->tfinger.touchId) == 0) {
|
||||
case SDL_COMPAT_EVENT_FINGER_UP:
|
||||
if (sdl_get_num_touch_fingers(sdl_touch_event_id(&e->tfinger)) == 0) {
|
||||
int ags_button = (mouseb & 1 << SDL_BUTTON_LEFT) ? AGSEVENT_BUTTON_LEFT : AGSEVENT_BUTTON_RIGHT;
|
||||
mousex = e->tfinger.x * view_w;
|
||||
mousey = e->tfinger.y * view_h;
|
||||
mouse_pos = event_get_touch_position(&e->tfinger);
|
||||
send_agsevent(AGSEVENT_BUTTON_RELEASE, ags_button);
|
||||
defer_touch_event(&e->tfinger);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_FINGERMOTION:
|
||||
mousex = e->tfinger.x * view_w;
|
||||
mousey = e->tfinger.y * view_h;
|
||||
case SDL_COMPAT_EVENT_FINGER_MOTION:
|
||||
mouse_pos = event_get_touch_position(&e->tfinger);
|
||||
send_agsevent(AGSEVENT_MOUSE_MOTION, 0);
|
||||
break;
|
||||
|
||||
case SDL_JOYDEVICEADDED:
|
||||
case SDL_COMPAT_EVENT_JOYSTICK_ADDED:
|
||||
joy_open(e->jdevice.which);
|
||||
break;
|
||||
|
||||
case SDL_JOYAXISMOTION:
|
||||
case SDL_COMPAT_EVENT_JOYSTICK_AXIS_MOTION:
|
||||
if (abs(e->jaxis.value) < 0x4000) {
|
||||
joyinfo &= e->jaxis.axis == 0 ? ~0xc : ~3;
|
||||
} else {
|
||||
@@ -493,10 +513,10 @@ void event_handle_event(SDL_Event *e) {
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_JOYBALLMOTION:
|
||||
case SDL_COMPAT_EVENT_JOYSTICK_BALL_MOTION:
|
||||
break;
|
||||
|
||||
case SDL_JOYHATMOTION:
|
||||
case SDL_COMPAT_EVENT_JOYSTICK_HAT_MOTION:
|
||||
joyinfo &= ~(SYS35KEY_UP | SYS35KEY_DOWN | SYS35KEY_LEFT | SYS35KEY_RIGHT);
|
||||
switch (e->jhat.value) {
|
||||
case SDL_HAT_UP: joyinfo |= SYS35KEY_UP; break;
|
||||
@@ -510,8 +530,8 @@ void event_handle_event(SDL_Event *e) {
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_JOYBUTTONDOWN:
|
||||
case SDL_JOYBUTTONUP:
|
||||
case SDL_COMPAT_EVENT_JOYSTICK_BUTTON_DOWN:
|
||||
case SDL_COMPAT_EVENT_JOYSTICK_BUTTON_UP:
|
||||
{
|
||||
int mask = 0;
|
||||
switch (e->jbutton.button) {
|
||||
@@ -528,7 +548,7 @@ void event_handle_event(SDL_Event *e) {
|
||||
#endif
|
||||
}
|
||||
if (mask) {
|
||||
if (e->jbutton.state == SDL_PRESSED)
|
||||
if (sdl_joystick_button_event_pressed(&e->jbutton))
|
||||
joyinfo |= mask;
|
||||
else
|
||||
joyinfo &= ~mask;
|
||||
@@ -552,6 +572,11 @@ void event_handle_event(SDL_Event *e) {
|
||||
case DEBUGGER_COMMAND:
|
||||
dbg_post_command(e->user.data1);
|
||||
break;
|
||||
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 3
|
||||
case WIN_MENU_COMMAND:
|
||||
win_menu_onCommand((unsigned int)(uintptr_t)e->user.data1);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -576,7 +601,7 @@ static void get_event(void) {
|
||||
|
||||
/* キー情報の取得 */
|
||||
static void keyEventProsess(SDL_KeyboardEvent *e, bool pressed) {
|
||||
int code = keytable[e->keysym.scancode];
|
||||
int code = keytable[sdl_keyboard_event_scancode(e)];
|
||||
RawKeyInfo[code] = pressed;
|
||||
send_agsevent(pressed ? AGSEVENT_KEY_PRESS : AGSEVENT_KEY_RELEASE, code);
|
||||
}
|
||||
@@ -608,8 +633,7 @@ int event_get_mouse(SDL_Point *p) {
|
||||
get_event();
|
||||
|
||||
if (p) {
|
||||
p->x = mousex;
|
||||
p->y = mousey;
|
||||
*p = mouse_pos;
|
||||
}
|
||||
|
||||
int m1 = mouseb & (1 << 1) ? SYS35KEY_RET : 0;
|
||||
@@ -646,6 +670,19 @@ void event_post_debugger_command(void *data) {
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 3
|
||||
void event_post_win_menu_command(unsigned int command) {
|
||||
SDL_Event event = {
|
||||
.user = {
|
||||
.type = custom_event_type,
|
||||
.code = WIN_MENU_COMMAND,
|
||||
.data1 = (void *)(uintptr_t)command
|
||||
}
|
||||
};
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void simulate_right_button(int pressed) {
|
||||
|
||||
+5
-2
@@ -21,14 +21,14 @@
|
||||
#define __EVENT_H__
|
||||
|
||||
#include "config.h"
|
||||
#include <SDL_events.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "portab.h"
|
||||
|
||||
void event_init(void);
|
||||
void event_remove(void);
|
||||
void event_set_joy_device_index(int index);
|
||||
void event_set_mouse_location(int x, int y);
|
||||
void event_set_mouse_internal_location(int x, int y);
|
||||
SDL_Point event_get_touch_position(const SDL_TouchFingerEvent *e);
|
||||
int event_get_key(void);
|
||||
int event_get_mouse(SDL_Point *p);
|
||||
void event_get_wheel(vmvar_t *forward, vmvar_t *back);
|
||||
@@ -36,6 +36,9 @@ void event_clear_wheel(void);
|
||||
int event_get_joy(void);
|
||||
void event_reset_input_state(void);
|
||||
void event_post_debugger_command(void *data);
|
||||
#if defined(_WIN32) && XSYSTEM35_SDL_VERSION == 3
|
||||
void event_post_win_menu_command(unsigned int command);
|
||||
#endif
|
||||
void event_handle_event(SDL_Event *e);
|
||||
|
||||
#endif // __EVENT_H__
|
||||
|
||||
+349
-187
@@ -19,13 +19,18 @@
|
||||
*/
|
||||
#include "config.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <SDL.h>
|
||||
#include <SDL_ttf.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_BITMAP_H
|
||||
#include FT_OUTLINE_H
|
||||
#include FT_TRUETYPE_TABLES_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "win/resources.h"
|
||||
#include "win/resource_io.h"
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
@@ -34,219 +39,370 @@
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
#include "cache.h"
|
||||
#include "font.h"
|
||||
#include "gfx_private.h"
|
||||
#include "hacks.h"
|
||||
#include "utfsjis.h"
|
||||
|
||||
// 26.6 fixed point helpers.
|
||||
#define FT_FLOOR(x) ((x) >> 6)
|
||||
#define FT_CEIL(x) (((x) + 63) >> 6)
|
||||
|
||||
#define GLYPH_CACHE_LIMIT (4 * 1024 * 1024)
|
||||
|
||||
typedef struct {
|
||||
int size;
|
||||
FontType type;
|
||||
TTF_Font *id;
|
||||
} FontTable;
|
||||
FT_Face face;
|
||||
int ascent; // distance from the top of a rendered surface to the baseline
|
||||
int height; // height of a rendered surface
|
||||
} Font;
|
||||
|
||||
#define FONTTABLEMAX 256
|
||||
static FontTable fonttbl[FONTTABLEMAX];
|
||||
static int fontcnt = 0;
|
||||
static FT_Library ft_library;
|
||||
|
||||
typedef struct Glyph {
|
||||
FT_Pos advance;
|
||||
int bitmap_left;
|
||||
int bitmap_top;
|
||||
FT_Bitmap bitmap;
|
||||
} Glyph;
|
||||
|
||||
typedef struct {
|
||||
FontSpec spec;
|
||||
int code;
|
||||
bool antialias;
|
||||
} GlyphKey;
|
||||
|
||||
static Cache *glyph_cache;
|
||||
|
||||
static struct {
|
||||
bool antialiase_on;
|
||||
const char *name[FONTTYPEMAX];
|
||||
int face[FONTTYPEMAX];
|
||||
int index[FONTTYPEMAX];
|
||||
FT_Face face[FONTTYPEMAX]; // opened on first use, shared between all sizes
|
||||
} this;
|
||||
|
||||
static void font_insert(int size, FontType type, TTF_Font *font) {
|
||||
fonttbl[fontcnt].size = size;
|
||||
fonttbl[fontcnt].type = type;
|
||||
fonttbl[fontcnt].id = font;
|
||||
|
||||
if (fontcnt >= (FONTTABLEMAX -1)) {
|
||||
WARNING("Font table is full.");
|
||||
} else {
|
||||
fontcnt++;
|
||||
}
|
||||
}
|
||||
|
||||
static FontTable *font_lookup(int size, FontType type) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < fontcnt; i++) {
|
||||
if (fonttbl[i].size == size && fonttbl[i].type == type) {
|
||||
return &fonttbl[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Resolve a (type, size) pair to a TTF_Font, opening and caching it on first
|
||||
// use, and apply the requested weight. Returns NULL for an invalid type.
|
||||
static FontTable *font_resolve(FontSpec font) {
|
||||
FontType type = font.type;
|
||||
int size = font.size;
|
||||
|
||||
if (type >= FONTTYPEMAX) {
|
||||
WARNING("Invalid font type %d", type);
|
||||
#if defined(_WIN32) || defined(__ANDROID__)
|
||||
// Create a face from the whole content of `stream`. The memory holding the font
|
||||
// file must outlive the face, and faces are never closed, so it is never freed.
|
||||
static FT_Face face_from_io(sdl_iostream_t *stream, int index) {
|
||||
int64_t size = sdl_get_io_size(stream);
|
||||
if (size <= 0) {
|
||||
sdl_close_io(stream);
|
||||
return NULL;
|
||||
}
|
||||
FT_Byte *buf = malloc(size);
|
||||
if (!buf) {
|
||||
sdl_close_io(stream);
|
||||
return NULL;
|
||||
}
|
||||
if (sdl_read_io(stream, buf, size) != (size_t)size) {
|
||||
free(buf);
|
||||
sdl_close_io(stream);
|
||||
return NULL;
|
||||
}
|
||||
sdl_close_io(stream);
|
||||
|
||||
FontTable *tbl;
|
||||
if (NULL == (tbl = font_lookup(size, type))) {
|
||||
TTF_Font *fs = TTF_OpenFontIndex(this.name[type], size, this.face[type]);
|
||||
#ifdef __ANDROID__
|
||||
// If `this.name[type]` is a custom font file specified in .xys35rc,
|
||||
// SDL_RWFromFile used by TTF_OpenFontIndex does not work because it
|
||||
// does not resolve relative path with the current directory. On the
|
||||
// other hand, we can't just use fopen because SDL_RWFromFile can open
|
||||
// apk assets and the default fonts are stored as assets.
|
||||
if (!fs) {
|
||||
FILE *fp = fopen(this.name[type], "r");
|
||||
if (fp)
|
||||
fs = TTF_OpenFontIndexRW(SDL_RWFromFP(fp, true), true, size, this.face[type]);
|
||||
}
|
||||
FT_Face face;
|
||||
if (FT_New_Memory_Face(ft_library, buf, size, index, &face)) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
return face;
|
||||
}
|
||||
#endif
|
||||
|
||||
static FT_Face open_face(const char *name, int index) {
|
||||
FT_Face face = NULL;
|
||||
|
||||
#ifdef _WIN32
|
||||
SDL_RWops *r = open_resource(this.name[type], "fonts");
|
||||
if (r)
|
||||
fs = TTF_OpenFontIndexRW(r, true, size, this.face[type]);
|
||||
#endif
|
||||
if (!fs)
|
||||
SYSERROR("Cannot open font %s", this.name[type]);
|
||||
const char *system_font = NULL;
|
||||
if (!strcmp(name, DEFAULT_GOTHIC_TTF))
|
||||
system_font = "C:/Windows/Fonts/msgothic.ttc";
|
||||
else if (!strcmp(name, DEFAULT_MINCHO_TTF))
|
||||
system_font = "C:/Windows/Fonts/msmincho.ttc";
|
||||
if (system_font && FT_New_Face(ft_library, system_font, index, &face))
|
||||
face = NULL;
|
||||
|
||||
font_insert(size, type, fs);
|
||||
tbl = &fonttbl[fontcnt - 1];
|
||||
if (!face) {
|
||||
sdl_iostream_t *res = open_resource(name, "fonts");
|
||||
if (res)
|
||||
face = face_from_io(res, index);
|
||||
}
|
||||
TTF_SetFontStyle(tbl->id, font.weight == FONT_WEIGHT_BOLD ? TTF_STYLE_BOLD : TTF_STYLE_NORMAL);
|
||||
return tbl;
|
||||
#endif
|
||||
if (!face && FT_New_Face(ft_library, name, index, &face))
|
||||
face = NULL; // FT_New_Face() does not clear `face` on failure.
|
||||
#ifdef __ANDROID__
|
||||
// The default fonts are stored as apk assets, which can only be opened
|
||||
// through SDL's IO API. (It is not used as the first choice because it
|
||||
// does not resolve a relative path against the current directory, which a
|
||||
// custom font specified in .xsys35rc may use.)
|
||||
if (!face) {
|
||||
sdl_iostream_t *stream = sdl_io_from_file(name, "rb");
|
||||
if (stream)
|
||||
face = face_from_io(stream, index);
|
||||
}
|
||||
#endif
|
||||
if (face) {
|
||||
// Prefer a Unicode charmap; if there is none, keep FreeType's choice.
|
||||
FT_Select_Charmap(face, FT_ENCODING_UNICODE);
|
||||
}
|
||||
return face;
|
||||
}
|
||||
|
||||
SDL_Surface *font_render_text(FontSpec font, const char *str_utf8, SDL_Color color) {
|
||||
FontTable *fontset = font_resolve(font);
|
||||
if (!fontset)
|
||||
// Since a face is shared between all sizes of a FontType, the size has to be
|
||||
// applied on every call.
|
||||
static bool font_select(FontSpec spec, Font *font) {
|
||||
if (spec.type >= FONTTYPEMAX) {
|
||||
WARNING("Invalid font type %d", spec.type);
|
||||
return false;
|
||||
}
|
||||
if (!this.face[spec.type]) {
|
||||
this.face[spec.type] = open_face(this.name[spec.type], this.index[spec.type]);
|
||||
if (!this.face[spec.type])
|
||||
SYSERROR("Cannot open font %s", this.name[spec.type]);
|
||||
}
|
||||
FT_Face face = this.face[spec.type];
|
||||
|
||||
// With a resolution of 0 (i.e. the default 72dpi), 1pt equals 1px.
|
||||
if (FT_Set_Char_Size(face, 0, spec.size * 64, 0, 0) &&
|
||||
FT_Set_Pixel_Sizes(face, 0, spec.size)) {
|
||||
WARNING("Cannot set the size of font %s to %d", this.name[spec.type], spec.size);
|
||||
return false;
|
||||
}
|
||||
font->face = face;
|
||||
font->ascent = FT_CEIL(face->size->metrics.ascender);
|
||||
font->height = font->ascent - FT_FLOOR(face->size->metrics.descender);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Distance from the top of the character cell to the baseline, the same value
|
||||
// GDI reports as TEXTMETRIC::tmAscent. The original engine positions text by
|
||||
// the cell top, so this determines where a glyph lands on the screen.
|
||||
static int cell_ascent(const Font *font) {
|
||||
TT_OS2 *os2 = FT_Get_Sfnt_Table(font->face, FT_SFNT_OS2);
|
||||
if (!os2 || os2->version == 0xffff || !os2->usWinAscent)
|
||||
return font->ascent;
|
||||
int upem = font->face->units_per_EM;
|
||||
return (os2->usWinAscent * font->face->size->metrics.y_ppem + upem / 2) / upem;
|
||||
}
|
||||
|
||||
int font_cell_overhang(FontSpec spec) {
|
||||
Font font;
|
||||
if (!font_select(spec, &font))
|
||||
return 0;
|
||||
return font.ascent - cell_ascent(&font);
|
||||
}
|
||||
|
||||
static bool load_glyph(FontSpec spec, FT_Face face, int code) {
|
||||
if (FT_Load_Char(face, code, FT_LOAD_DEFAULT))
|
||||
return false;
|
||||
if (spec.weight == FONT_WEIGHT_BOLD) {
|
||||
// Approximate GDI's synthetic bold without changing the advance.
|
||||
// Expand outlines before rasterization so antialiased edges are
|
||||
// generated only once.
|
||||
FT_GlyphSlot slot = face->glyph;
|
||||
if (slot->format == FT_GLYPH_FORMAT_OUTLINE) {
|
||||
// At small sizes, use half-pixel emboldening to better match
|
||||
// GDI's synthetic bold for the thin bitmap glyphs of MS Gothic
|
||||
// and MS Mincho.
|
||||
int xstrength = spec.size <= 22 ? 32 : 64;
|
||||
if (FT_Outline_EmboldenXY(&slot->outline, xstrength, 0))
|
||||
return false;
|
||||
} else if (slot->format == FT_GLYPH_FORMAT_BITMAP) {
|
||||
if (FT_GlyphSlot_Own_Bitmap(slot) ||
|
||||
FT_Bitmap_Embolden(ft_library, &slot->bitmap, 64, 0))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t glyph_hash(const void *p) {
|
||||
const GlyphKey *key = p;
|
||||
uint32_t hash = (uint32_t)key->code;
|
||||
hash = hash * 31 + key->spec.type;
|
||||
hash = hash * 31 + key->spec.weight;
|
||||
hash = hash * 31 + key->spec.size;
|
||||
return hash * 31 + key->antialias;
|
||||
}
|
||||
|
||||
static bool glyph_key_equal(const void *a, const void *b) {
|
||||
const GlyphKey *ka = a;
|
||||
const GlyphKey *kb = b;
|
||||
return ka->spec.type == kb->spec.type &&
|
||||
ka->spec.weight == kb->spec.weight &&
|
||||
ka->spec.size == kb->spec.size &&
|
||||
ka->code == kb->code && ka->antialias == kb->antialias;
|
||||
}
|
||||
|
||||
static void glyph_destroy(void *data) {
|
||||
Glyph *glyph = data;
|
||||
FT_Bitmap_Done(ft_library, &glyph->bitmap);
|
||||
free(glyph);
|
||||
}
|
||||
|
||||
// Load and render a glyph on a cache miss. If allocation fails, return a
|
||||
// temporary view of the face's glyph slot so text rendering can continue.
|
||||
static const Glyph *get_glyph(FontSpec spec, FT_Face face, int code, bool antialias) {
|
||||
GlyphKey key = {spec, code, antialias};
|
||||
const Glyph *cached = cache_get(glyph_cache, &key);
|
||||
if (cached)
|
||||
return cached;
|
||||
|
||||
if (!load_glyph(spec, face, code))
|
||||
return NULL;
|
||||
FT_GlyphSlot slot = face->glyph;
|
||||
static Glyph temporary;
|
||||
temporary.advance = slot->advance.x;
|
||||
FT_Bitmap_Init(&temporary.bitmap);
|
||||
if (FT_Render_Glyph(slot, antialias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO))
|
||||
return &temporary;
|
||||
temporary.bitmap_left = slot->bitmap_left;
|
||||
temporary.bitmap_top = slot->bitmap_top;
|
||||
temporary.bitmap = slot->bitmap;
|
||||
|
||||
SDL_Surface *fs;
|
||||
fs = TTF_RenderUTF8_Blended(fontset->id, str_utf8, color);
|
||||
if (!fs)
|
||||
WARNING("Text rendering failed: %s", TTF_GetError());
|
||||
size_t bitmap_size = slot->bitmap.rows * (size_t)abs(slot->bitmap.pitch);
|
||||
size_t cache_size = sizeof(Glyph) + bitmap_size;
|
||||
if (cache_size > GLYPH_CACHE_LIMIT)
|
||||
return &temporary;
|
||||
|
||||
return fs;
|
||||
Glyph *glyph = calloc(1, sizeof(Glyph));
|
||||
if (!glyph)
|
||||
return &temporary;
|
||||
FT_Bitmap_Init(&glyph->bitmap);
|
||||
if (FT_Bitmap_Copy(ft_library, &slot->bitmap, &glyph->bitmap)) {
|
||||
FT_Bitmap_Done(ft_library, &glyph->bitmap);
|
||||
free(glyph);
|
||||
return &temporary;
|
||||
}
|
||||
glyph->advance = slot->advance.x;
|
||||
glyph->bitmap_left = slot->bitmap_left;
|
||||
glyph->bitmap_top = slot->bitmap_top;
|
||||
if (cache_insert(glyph_cache, &key, glyph, cache_size) != CACHE_INSERT_OK) {
|
||||
glyph_destroy(glyph);
|
||||
return &temporary;
|
||||
}
|
||||
return glyph;
|
||||
}
|
||||
|
||||
void font_measure_text(FontSpec font, const char *str_utf8, int len, int *w, int *h) {
|
||||
// Total advance of `str_utf8`, in pixels. A negative `len` means the whole
|
||||
// string, otherwise only its first `len` bytes are measured.
|
||||
static int text_width(FontSpec spec, FT_Face face, const char *str_utf8, int len,
|
||||
bool antialias) {
|
||||
const char *end = len < 0 ? NULL : str_utf8 + len;
|
||||
int pen = 0; // 26.6
|
||||
|
||||
while (*str_utf8 && (!end || str_utf8 < end)) {
|
||||
const Glyph *glyph = get_glyph(spec, face,
|
||||
utf8_next_codepoint(&str_utf8), antialias);
|
||||
if (glyph)
|
||||
pen += glyph->advance;
|
||||
}
|
||||
return FT_CEIL(pen);
|
||||
}
|
||||
|
||||
// Composite a rendered glyph into `dst`, which is either an ARGB8888 surface
|
||||
// (the glyph is drawn in `color`, its coverage scaled by `color.a` becoming the
|
||||
// alpha value) or an INDEX8 surface (covered pixels are set to the index 1).
|
||||
static void blit_glyph(const FT_Bitmap *bmp, SDL_Surface *dst, int x, int y, SDL_Color color) {
|
||||
int bpp = sdl_surface_bytes_per_pixel(dst);
|
||||
uint32_t rgb = color.r << 16 | color.g << 8 | color.b;
|
||||
|
||||
for (unsigned int row = 0; row < bmp->rows; row++) {
|
||||
int dy = y + row;
|
||||
if (dy < 0 || dy >= dst->h)
|
||||
continue;
|
||||
const uint8_t *src = bmp->buffer + row * bmp->pitch;
|
||||
uint8_t *dst_row = (uint8_t *)dst->pixels + dy * dst->pitch;
|
||||
for (unsigned int col = 0; col < bmp->width; col++) {
|
||||
int dx = x + col;
|
||||
if (dx < 0 || dx >= dst->w)
|
||||
continue;
|
||||
uint8_t coverage;
|
||||
switch (bmp->pixel_mode) {
|
||||
case FT_PIXEL_MODE_MONO:
|
||||
coverage = src[col >> 3] & (0x80 >> (col & 7)) ? 255 : 0;
|
||||
break;
|
||||
case FT_PIXEL_MODE_GRAY:
|
||||
coverage = src[col];
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (!coverage)
|
||||
continue;
|
||||
uint8_t *dp = dst_row + dx * bpp;
|
||||
if (bpp == 4)
|
||||
*(uint32_t *)dp = (uint32_t)(coverage * color.a / 255) << 24 | rgb;
|
||||
else
|
||||
*dp = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Surface *font_render_text(FontSpec spec, const char *str_utf8, SDL_Color color, bool antialias) {
|
||||
Font font;
|
||||
if (!font_select(spec, &font))
|
||||
return NULL;
|
||||
int width = max(text_width(spec, font.face, str_utf8, -1, antialias), 1);
|
||||
|
||||
SDL_Surface *sf;
|
||||
if (antialias) {
|
||||
sf = sdl_create_surface(width, font.height, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
if (sf)
|
||||
SDL_SetSurfaceBlendMode(sf, SDL_BLENDMODE_BLEND);
|
||||
} else {
|
||||
sf = sdl_create_surface(width, font.height, 8, SDL_PIXELFORMAT_INDEX8);
|
||||
if (sf) {
|
||||
SDL_Color pal[2] = {{0, 0, 0, 0}, {color.r, color.g, color.b, 255}};
|
||||
SDL_SetPaletteColors(sdl_get_surface_palette(sf), pal, 0, 2);
|
||||
sdl_set_surface_color_key(sf, true, 0);
|
||||
}
|
||||
}
|
||||
if (!sf) {
|
||||
WARNING("Text rendering failed: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int pen = 0; // 26.6
|
||||
while (*str_utf8) {
|
||||
const Glyph *glyph = get_glyph(spec, font.face,
|
||||
utf8_next_codepoint(&str_utf8), antialias);
|
||||
if (!glyph)
|
||||
continue;
|
||||
blit_glyph(&glyph->bitmap, sf, FT_FLOOR(pen) + glyph->bitmap_left,
|
||||
font.ascent - glyph->bitmap_top, color);
|
||||
pen += glyph->advance;
|
||||
}
|
||||
return sf;
|
||||
}
|
||||
|
||||
void font_measure_text(FontSpec spec, const char *str_utf8, int len, bool antialias,
|
||||
int *w, int *h) {
|
||||
if (w) *w = 0;
|
||||
if (h) *h = 0;
|
||||
FontTable *fontset = font_resolve(font);
|
||||
if (!fontset)
|
||||
Font font;
|
||||
if (!font_select(spec, &font))
|
||||
return;
|
||||
if (h)
|
||||
*h = TTF_FontHeight(fontset->id);
|
||||
if (!w)
|
||||
return;
|
||||
|
||||
// A negative length means the whole string.
|
||||
if (len < 0) {
|
||||
TTF_SizeUTF8(fontset->id, str_utf8, w, NULL);
|
||||
} else {
|
||||
char buf[256];
|
||||
if (len > (int)sizeof(buf) - 1)
|
||||
len = sizeof(buf) - 1;
|
||||
memcpy(buf, str_utf8, len);
|
||||
buf[len] = '\0';
|
||||
TTF_SizeUTF8(fontset->id, buf, w, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// SDL can't blit ARGB to an indexed bitmap properly, so we do it ourselves.
|
||||
static void gfx_drawAntiAlias_8bpp(int dstx, int dsty, SDL_Surface *src, uint8_t col)
|
||||
{
|
||||
const SDL_Color* palette = gfx_palette->colors;
|
||||
Uint8 cache[256*7];
|
||||
memset(cache, 0, 256);
|
||||
|
||||
for (int y = 0; y < src->h && dsty + y < main_surface->h; y++) {
|
||||
if (dsty + y < 0)
|
||||
continue;
|
||||
uint8_t *sp = (uint8_t*)src->pixels + y * src->pitch;
|
||||
uint8_t *dp = (uint8_t*)main_surface->pixels + (dsty + y) * main_surface->pitch + dstx;
|
||||
for (int x = 0; x < src->w && dstx + x < main_surface->w; x++) {
|
||||
Uint8 r, g, b, alpha;
|
||||
SDL_GetRGBA(*((Uint32*)sp), src->format, &r, &g, &b, &alpha);
|
||||
alpha = alpha >> 5; // reduce bit depth
|
||||
if (!alpha) {
|
||||
// Transparent, do nothing
|
||||
} else if (alpha == 7) {
|
||||
*dp = col; // Fully opaque
|
||||
} else if (cache[*dp] & 1 << alpha) {
|
||||
*dp = cache[alpha << 8 | *dp]; // use cached value
|
||||
} else {
|
||||
// find nearest color in palette
|
||||
cache[*dp] |= 1 << alpha;
|
||||
int c = gfx_nearest_color(
|
||||
(palette[col].r * alpha + palette[*dp].r * (7 - alpha)) / 7,
|
||||
(palette[col].g * alpha + palette[*dp].g * (7 - alpha)) / 7,
|
||||
(palette[col].b * alpha + palette[*dp].b * (7 - alpha)) / 7);
|
||||
cache[alpha << 8 | *dp] = c;
|
||||
*dp = c;
|
||||
}
|
||||
sp += src->format->BytesPerPixel;
|
||||
dp++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Rect font_draw_text(FontSpec font, int x, int y, const char *str_utf8, uint8_t cl) {
|
||||
SDL_Surface *fs;
|
||||
SDL_Rect r_src, r_dst = {};
|
||||
int w, h;
|
||||
|
||||
if (!*str_utf8)
|
||||
return r_dst;
|
||||
FontTable *fontset = font_resolve(font);
|
||||
if (!fontset)
|
||||
return r_dst;
|
||||
|
||||
bool antialias = this.antialiase_on;
|
||||
// The post-effect in Rance 3 opening does not work properly if colors other
|
||||
// than the specified text color (32) are used.
|
||||
// https://github.com/kichikuou/xsystem35-sdl2/issues/54
|
||||
// In Rance 3, text color 32 is only used in the opening.
|
||||
if ((game_id == GAME_RANCE3 || game_id == GAME_RANCE3_ENG) && cl == 32)
|
||||
antialias = false;
|
||||
|
||||
if (antialias) {
|
||||
fs = TTF_RenderUTF8_Blended(fontset->id, str_utf8, gfx_palette->colors[cl]);
|
||||
} else {
|
||||
fs = TTF_RenderUTF8_Solid(fontset->id, str_utf8, gfx_palette->colors[cl]);
|
||||
}
|
||||
if (!fs) {
|
||||
WARNING("Text rendering failed: %s", TTF_GetError());
|
||||
return r_dst;
|
||||
}
|
||||
|
||||
TTF_SizeUTF8(fontset->id, str_utf8, &w, &h);
|
||||
// Center vertically to the box.
|
||||
y -= (TTF_FontHeight(fontset->id) - fontset->size) / 2;
|
||||
r_dst = (SDL_Rect){x, y, w, h};
|
||||
|
||||
if (main_surface->format->BitsPerPixel == 8 && antialias) {
|
||||
gfx_drawAntiAlias_8bpp(x, y, fs, cl);
|
||||
} else {
|
||||
r_src = (SDL_Rect){0, 0, w, h};
|
||||
SDL_BlitSurface(fs, &r_src, main_surface, &r_dst);
|
||||
}
|
||||
|
||||
SDL_FreeSurface(fs);
|
||||
if (r_dst.y < 0) {
|
||||
r_dst.h += r_dst.y;
|
||||
r_dst.y = 0;
|
||||
}
|
||||
return r_dst;
|
||||
*h = font.height;
|
||||
if (w)
|
||||
*w = text_width(spec, font.face, str_utf8, len, antialias);
|
||||
}
|
||||
|
||||
void font_init(void) {
|
||||
cache_clear(glyph_cache);
|
||||
this.antialiase_on = false;
|
||||
|
||||
if (TTF_Init() == -1)
|
||||
SYSERROR("Failed to intialize SDL_ttf: %s", TTF_GetError());
|
||||
|
||||
if (FT_Init_FreeType(&ft_library))
|
||||
SYSERROR("Failed to initialize FreeType");
|
||||
if (!glyph_cache) {
|
||||
CacheOps ops = {
|
||||
.key_size = sizeof(GlyphKey),
|
||||
.hash = glyph_hash,
|
||||
.equal = glyph_key_equal,
|
||||
.destroy = glyph_destroy,
|
||||
};
|
||||
glyph_cache = cache_new(GLYPH_CACHE_LIMIT, &ops);
|
||||
}
|
||||
}
|
||||
|
||||
void font_set_name_and_index(FontType type, const char *name, int index) {
|
||||
@@ -254,8 +410,10 @@ void font_set_name_and_index(FontType type, const char *name, int index) {
|
||||
WARNING("Invalid font type %d", type);
|
||||
return;
|
||||
}
|
||||
// Cached glyphs include the font type but not the font file name.
|
||||
cache_clear(glyph_cache);
|
||||
this.name[type] = name;
|
||||
this.face[type] = index;
|
||||
this.index[type] = index;
|
||||
}
|
||||
|
||||
void font_set_antialias(bool enable) {
|
||||
@@ -266,6 +424,10 @@ bool font_get_antialias(void) {
|
||||
return this.antialiase_on;
|
||||
}
|
||||
|
||||
CacheStats font_get_cache_stats(void) {
|
||||
return cache_get_stats(glyph_cache);
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASYNC_JS(bool, load_mincho_font, (void), {
|
||||
return await xsystem35.load_mincho_font();
|
||||
|
||||
+9
-4
@@ -24,7 +24,8 @@
|
||||
#ifndef __FONT_H__
|
||||
#define __FONT_H__
|
||||
|
||||
#include <SDL_surface.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "cache.h"
|
||||
#include "config.h"
|
||||
#include "portab.h"
|
||||
|
||||
@@ -49,9 +50,13 @@ extern void font_init(void);
|
||||
extern void font_set_name_and_index(FontType type, const char *name, int index);
|
||||
extern void font_set_antialias(bool enable);
|
||||
extern bool font_get_antialias(void);
|
||||
extern struct SDL_Surface *font_render_text(FontSpec font, const char *str_utf8, SDL_Color color);
|
||||
extern SDL_Rect font_draw_text(FontSpec font, int x, int y, const char *str_utf8, uint8_t col);
|
||||
extern void font_measure_text(FontSpec font, const char *str_utf8, int len, int *w, int *h);
|
||||
extern struct SDL_Surface *font_render_text(FontSpec spec, const char *str_utf8, SDL_Color color, bool antialias);
|
||||
extern void font_measure_text(FontSpec spec, const char *str_utf8, int len, bool antialias,
|
||||
int *w, int *h);
|
||||
// Rows by which a rendered surface extends above the character cell. Subtract
|
||||
// it from a Y coordinate that denotes the cell top before blitting.
|
||||
extern int font_cell_overhang(FontSpec spec);
|
||||
extern CacheStats font_get_cache_stats(void);
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
extern bool load_mincho_font(void);
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "font.h"
|
||||
#include "unittest.h"
|
||||
|
||||
#include "sdl_compat.h"
|
||||
|
||||
static const FontType font_types[] = {FONT_GOTHIC, FONT_MINCHO};
|
||||
#define NTYPES ((int)(sizeof(font_types) / sizeof(font_types[0])))
|
||||
|
||||
static const int sizes[] = {8, 16, 24, 32};
|
||||
#define NSIZES ((int)(sizeof(sizes) / sizeof(sizes[0])))
|
||||
|
||||
static const char *texts[] = {
|
||||
"Hello, world!",
|
||||
" ",
|
||||
"iWiW",
|
||||
"\xe9\xad\x94\xe7\x89\xa9\xe3\x81\xae\xe6\xa3\xae", // 魔物の森
|
||||
"\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86" // あいう
|
||||
"\xe3\x82\xa2\xe3\x82\xa4\xe3\x82\xa6", // アイウ
|
||||
};
|
||||
#define NTEXTS ((int)(sizeof(texts) / sizeof(texts[0])))
|
||||
|
||||
static const SDL_Color white = {255, 255, 255, 255};
|
||||
|
||||
static int measure_w(FontSpec f, const char *s, bool antialias) {
|
||||
int w;
|
||||
font_measure_text(f, s, -1, antialias, &w, NULL);
|
||||
return w;
|
||||
}
|
||||
|
||||
static int measure_h(FontSpec f) {
|
||||
int h;
|
||||
font_measure_text(f, "", -1, false, NULL, &h);
|
||||
return h;
|
||||
}
|
||||
|
||||
static void assert_same_pixels(SDL_Surface *a, SDL_Surface *b) {
|
||||
ASSERT_EQUAL(a->w, b->w);
|
||||
ASSERT_EQUAL(a->h, b->h);
|
||||
ASSERT_EQUAL(sdl_surface_format(a), sdl_surface_format(b));
|
||||
int row_bytes = a->w * sdl_surface_bytes_per_pixel(a);
|
||||
for (int y = 0; y < a->h; y++) {
|
||||
const uint8_t *ap = (uint8_t *)a->pixels + y * a->pitch;
|
||||
const uint8_t *bp = (uint8_t *)b->pixels + y * b->pitch;
|
||||
ASSERT_EQUAL(memcmp(ap, bp, row_bytes), 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void glyph_cache_test(void) {
|
||||
for (int ti = 0; ti < NTYPES; ti++) {
|
||||
for (int weight = FONT_WEIGHT_NORMAL; weight <= FONT_WEIGHT_BOLD; weight++) {
|
||||
FontSpec f = {font_types[ti], weight, 24};
|
||||
for (int aa = 0; aa < 2; aa++) {
|
||||
SDL_Surface *first = font_render_text(f, texts[3], white, aa);
|
||||
SDL_Surface *cached = font_render_text(f, texts[3], white, aa);
|
||||
ASSERT_TRUE(first);
|
||||
ASSERT_TRUE(cached);
|
||||
assert_same_pixels(first, cached);
|
||||
sdl_destroy_surface(first);
|
||||
sdl_destroy_surface(cached);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void invariants_test(void) {
|
||||
for (int ti = 0; ti < NTYPES; ti++) {
|
||||
for (int si = 0; si < NSIZES; si++) {
|
||||
FontSpec f = {font_types[ti], FONT_WEIGHT_NORMAL, sizes[si]};
|
||||
for (int i = 0; i < NTEXTS; i++) {
|
||||
const char *text = texts[i];
|
||||
for (int aa = 0; aa < 2; aa++) {
|
||||
SDL_Surface *s = font_render_text(f, text, white, aa);
|
||||
ASSERT_TRUE(s);
|
||||
// gfx_drawString() blits the surface using the measured
|
||||
// width, so the two must agree.
|
||||
ASSERT_EQUAL(s->w, measure_w(f, text, aa));
|
||||
// microui lays out text rows by the measured height,
|
||||
// so the surface must be exactly that tall.
|
||||
ASSERT_EQUAL(s->h, measure_h(f));
|
||||
// The antialiased surface goes to gfx_drawAntiAlias_8bpp(),
|
||||
// which reads a 32-bit alpha channel. The other one is
|
||||
// blitted onto the indexed screen surface as is.
|
||||
ASSERT_EQUAL(sdl_surface_bits_per_pixel(s), aa ? 32 : 8);
|
||||
sdl_destroy_surface(s);
|
||||
}
|
||||
}
|
||||
// gfx_drawString() draws text by the top of the character cell,
|
||||
// shifting the surface up by the overhang. The result must cover
|
||||
// the whole cell.
|
||||
ASSERT_TRUE(font_cell_overhang(f) >= 0);
|
||||
ASSERT_TRUE(measure_h(f) - font_cell_overhang(f) >= f.size);
|
||||
|
||||
// microui positions the text cursor by measuring a prefix of the
|
||||
// string, so that must equal measuring a truncated copy.
|
||||
const char *text = texts[0];
|
||||
for (int len = 0; text[len]; len++) {
|
||||
char buf[64];
|
||||
int w;
|
||||
font_measure_text(f, text, len, false, &w, NULL);
|
||||
memcpy(buf, text, len);
|
||||
buf[len] = '\0';
|
||||
ASSERT_EQUAL(w, measure_w(f, buf, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void font_test(void) {
|
||||
font_init();
|
||||
font_set_name_and_index(FONT_GOTHIC, TEST_FONT_DIR "/MTLc3m.ttf", 0);
|
||||
font_set_name_and_index(FONT_MINCHO, TEST_FONT_DIR "/mincho.ttf", 0);
|
||||
|
||||
invariants_test();
|
||||
glyph_cache_test();
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
#define __GFX_H__
|
||||
|
||||
#include "config.h"
|
||||
#include <SDL_surface.h>
|
||||
#include "sdl_compat.h"
|
||||
#include "portab.h"
|
||||
#include "ags.h"
|
||||
|
||||
@@ -31,9 +31,13 @@ void gfx_Remove(void);
|
||||
|
||||
/* ウィンド関係 */
|
||||
void gfx_setWorldSize(int width, int height, int depth);
|
||||
void gfx_setWindowSize(int w, int h);
|
||||
void gfx_setViewSize(int w, int h);
|
||||
void gfx_getViewSize(int *width, int *height);
|
||||
SDL_Window *gfx_getWindow(void);
|
||||
bool gfx_convertEventCoordinates(SDL_Event *event);
|
||||
void gfx_warpMouse(int x, int y);
|
||||
SDL_Rect gfx_viewToWindowRect(SDL_Rect rect);
|
||||
void gfx_setWindowTitle(char *name);
|
||||
void gfx_getWindowInfo(int *width, int *height, int *depth);
|
||||
void gfx_setFullscreen(bool on);
|
||||
bool gfx_isFullscreen(void);
|
||||
void gfx_raiseWindow(void);
|
||||
@@ -44,10 +48,12 @@ SDL_Surface *gfx_createSurfaceView(SDL_Surface *sf, int x, int y, int w, int h);
|
||||
/* 画面更新 */
|
||||
void gfx_updateArea(SDL_Rect *src, SDL_Point *dst);
|
||||
void gfx_updateAll(SDL_Rect *view_rect);
|
||||
void gfx_requestRedraw(void);
|
||||
void gfx_updateScreen(void);
|
||||
|
||||
/* パレット関係 */
|
||||
void gfx_setPalette(SDL_Color *pal, int first, int count);
|
||||
SDL_Color gfx_getPaletteColor(uint8_t color);
|
||||
|
||||
/* 描画関係 */
|
||||
void gfx_drawRectangle(int x, int y, int w, int h, uint8_t c);
|
||||
@@ -55,6 +61,7 @@ void gfx_fillRectangle(int x, int y, int w, int h, uint8_t c);
|
||||
void gfx_fillRectangleRGB(int x, int y, int w, int h, uint8_t r, uint8_t g, uint8_t b);
|
||||
void gfx_fillCircle(int left, int top, int diameter, uint8_t c);
|
||||
void gfx_drawLine(int x1, int y1, int x2, int y2, uint8_t c);
|
||||
void gfx_drawAntiAlias_8bpp(int x, int y, SDL_Surface *src, uint8_t color);
|
||||
SDL_Rect gfx_floodFill(int x, int y, int col);
|
||||
SDL_Rect gfx_drawString(int x, int y, const char *str_utf8, uint8_t col, FontSpec font);
|
||||
void gfx_copyArea(int sx,int sy, int w, int h, int dx, int dy);
|
||||
|
||||
+104
-22
@@ -27,7 +27,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
@@ -38,11 +38,14 @@
|
||||
#include "gfx_private.h"
|
||||
#include "font.h"
|
||||
#include "ags.h"
|
||||
#include "hacks.h"
|
||||
#include "image.h"
|
||||
#include "nact.h"
|
||||
#include "modal.h"
|
||||
#include "debugger.h"
|
||||
|
||||
static bool gfx_dirty;
|
||||
|
||||
static void gfx_pal_check(void) {
|
||||
if (nact->ags.pal_changed) {
|
||||
nact->ags.pal_changed = false;
|
||||
@@ -51,21 +54,30 @@ static void gfx_pal_check(void) {
|
||||
}
|
||||
|
||||
static Uint32 palette_color(uint8_t c) {
|
||||
if (main_surface->format->BitsPerPixel == 8)
|
||||
if (sdl_surface_bits_per_pixel(main_surface) == 8)
|
||||
return c;
|
||||
return SDL_MapRGB(main_surface->format, gfx_palette->colors[c].r, gfx_palette->colors[c].g, gfx_palette->colors[c].b);
|
||||
return sdl_map_rgb(main_surface, gfx_palette->colors[c].r,
|
||||
gfx_palette->colors[c].g, gfx_palette->colors[c].b);
|
||||
}
|
||||
|
||||
SDL_Color gfx_getPaletteColor(uint8_t color) {
|
||||
return gfx_palette->colors[color];
|
||||
}
|
||||
|
||||
void gfx_updateScreen(void) {
|
||||
if (!gfx_dirty)
|
||||
return;
|
||||
SDL_RenderClear(gfx_renderer);
|
||||
SDL_RenderCopy(gfx_renderer, gfx_texture, NULL, NULL);
|
||||
sdl_render_texture(gfx_renderer, gfx_texture, NULL, NULL);
|
||||
modal_render_overlay();
|
||||
SDL_RenderPresent(gfx_renderer);
|
||||
gfx_dirty = false;
|
||||
}
|
||||
|
||||
void gfx_requestRedraw(void) {
|
||||
gfx_dirty = true;
|
||||
}
|
||||
|
||||
/* off-screen の指定領域を Main Window へ転送 */
|
||||
void gfx_updateArea(SDL_Rect *rect, SDL_Point *dst) {
|
||||
SDL_Rect sw = {0, 0, main_surface->w, main_surface->h};
|
||||
@@ -122,11 +134,11 @@ void gfx_fillRectangle(int x, int y, int w, int h, uint8_t c) {
|
||||
}
|
||||
|
||||
void gfx_fillRectangleRGB(int x, int y, int w, int h, uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (main_surface->format->BitsPerPixel == 8)
|
||||
if (sdl_surface_bits_per_pixel(main_surface) == 8)
|
||||
return;
|
||||
|
||||
SDL_Rect rect = {x, y, w, h};
|
||||
SDL_FillRect(main_surface, &rect, SDL_MapRGB(main_surface->format, r, g, b));
|
||||
SDL_FillRect(main_surface, &rect, sdl_map_rgb(main_surface, r, g, b));
|
||||
}
|
||||
|
||||
void gfx_fillCircle(int left, int top, int diameter, uint8_t c) {
|
||||
@@ -172,13 +184,13 @@ static void gfx_dib_sprite_copy(SDL_Surface *dst, int sx, int sy, int w, int h,
|
||||
gfx_pal_check();
|
||||
|
||||
Uint32 col = palette_color(sp);
|
||||
SDL_SetColorKey(main_surface, SDL_TRUE, col);
|
||||
sdl_set_surface_color_key(main_surface, true, col);
|
||||
|
||||
SDL_Rect r_src = {sx, sy, w, h};
|
||||
SDL_Rect r_dst = {dx, dy, w, h};
|
||||
|
||||
SDL_BlitSurface(main_surface, &r_src, dst, &r_dst);
|
||||
SDL_SetColorKey(main_surface, SDL_FALSE, 0);
|
||||
sdl_set_surface_color_key(main_surface, false, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -189,7 +201,7 @@ void gfx_copyAreaSP(int sx, int sy, int w, int h, int dx, int dy, uint8_t sp) {
|
||||
}
|
||||
|
||||
SDL_Surface *gfx_dib_to_surface_colorkey(int x, int y, int w, int h, int sp) {
|
||||
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_Surface *s = sdl_create_surface(w, h, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_FillRect(s, NULL, 0);
|
||||
gfx_dib_sprite_copy(s, x, y, w, h, 0, 0, sp);
|
||||
return s;
|
||||
@@ -198,24 +210,24 @@ SDL_Surface *gfx_dib_to_surface_colorkey(int x, int y, int w, int h, int sp) {
|
||||
void gfx_drawImage8(cgdata *cg, int dx, int dy, int sprite_color) {
|
||||
int w = cg->width;
|
||||
int h = cg->height;
|
||||
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormatFrom(
|
||||
SDL_Surface *s = sdl_create_surface_from(
|
||||
cg->pic, w, h, 8, w, SDL_PIXELFORMAT_INDEX8);
|
||||
|
||||
gfx_pal_check();
|
||||
|
||||
if (main_surface->format->BitsPerPixel > 8 && cg->pal) {
|
||||
SDL_SetPaletteColors(s->format->palette, cg->pal, 0, 256);
|
||||
if (sdl_surface_bits_per_pixel(main_surface) > 8 && cg->pal) {
|
||||
SDL_SetPaletteColors(sdl_get_surface_palette(s), cg->pal, 0, 256);
|
||||
} else {
|
||||
SDL_SetSurfacePalette(s, gfx_palette);
|
||||
sdl_set_surface_palette(s, gfx_palette);
|
||||
}
|
||||
|
||||
if (sprite_color != -1)
|
||||
SDL_SetColorKey(s, SDL_TRUE, sprite_color);
|
||||
sdl_set_surface_color_key(s, true, sprite_color);
|
||||
|
||||
SDL_Rect r_dst = {dx, dy, w, h};
|
||||
|
||||
SDL_BlitSurface(s, NULL, main_surface, &r_dst);
|
||||
SDL_FreeSurface(s);
|
||||
sdl_destroy_surface(s);
|
||||
}
|
||||
|
||||
/* 直線描画 */
|
||||
@@ -224,7 +236,7 @@ void gfx_drawLine(int x1, int y1, int x2, int y2, uint8_t c) {
|
||||
|
||||
SDL_Renderer *renderer = SDL_CreateSoftwareRenderer(main_surface);
|
||||
SDL_SetRenderDrawColor(renderer, gfx_palette->colors[c].r, gfx_palette->colors[c].g, gfx_palette->colors[c].b, SDL_ALPHA_OPAQUE);
|
||||
SDL_RenderDrawLine(renderer, x1, y1, x2, y2);
|
||||
sdl_render_line_int(renderer, x1, y1, x2, y2);
|
||||
SDL_DestroyRenderer(renderer);
|
||||
}
|
||||
|
||||
@@ -241,7 +253,7 @@ void gfx_drawLine(int x1, int y1, int x2, int y2, uint8_t c) {
|
||||
SDL_Rect gfx_floodFill(int x, int y, int c) {
|
||||
Uint32 col = palette_color(c);
|
||||
|
||||
switch (main_surface->format->BytesPerPixel) {
|
||||
switch (sdl_surface_bytes_per_pixel(main_surface)) {
|
||||
case 1:
|
||||
return gfx_floodFill_uint8_t(x, y, col);
|
||||
case 2:
|
||||
@@ -254,7 +266,7 @@ SDL_Rect gfx_floodFill(int x, int y, int c) {
|
||||
}
|
||||
}
|
||||
|
||||
int gfx_nearest_color(int r, int g, int b) {
|
||||
static int nearest_color(int r, int g, int b) {
|
||||
int i, col, mind = INT_MAX;
|
||||
for (i = 0; i < 256; i++) {
|
||||
int dr = r - gfx_palette->colors[i].r;
|
||||
@@ -269,17 +281,87 @@ int gfx_nearest_color(int r, int g, int b) {
|
||||
return col;
|
||||
}
|
||||
|
||||
// SDL can't blit ARGB to an indexed bitmap properly, so we do it ourselves.
|
||||
void gfx_drawAntiAlias_8bpp(int dstx, int dsty, SDL_Surface *src, uint8_t col) {
|
||||
const SDL_Color *palette = gfx_palette->colors;
|
||||
Uint8 cache[256 * 7];
|
||||
memset(cache, 0, 256);
|
||||
|
||||
for (int y = 0; y < src->h && dsty + y < main_surface->h; y++) {
|
||||
if (dsty + y < 0)
|
||||
continue;
|
||||
uint8_t *sp = (uint8_t *)src->pixels + y * src->pitch;
|
||||
uint8_t *dp = (uint8_t *)main_surface->pixels
|
||||
+ (dsty + y) * main_surface->pitch + dstx;
|
||||
for (int x = 0; x < src->w && dstx + x < main_surface->w; x++) {
|
||||
Uint8 r, g, b, alpha;
|
||||
sdl_get_rgba(*(Uint32 *)sp, src, &r, &g, &b, &alpha);
|
||||
alpha >>= 5; // reduce bit depth
|
||||
if (!alpha) {
|
||||
// Transparent, do nothing.
|
||||
} else if (alpha == 7) {
|
||||
*dp = col; // Fully opaque.
|
||||
} else if (cache[*dp] & 1 << alpha) {
|
||||
*dp = cache[alpha << 8 | *dp];
|
||||
} else {
|
||||
cache[*dp] |= 1 << alpha;
|
||||
int c = nearest_color(
|
||||
(palette[col].r * alpha + palette[*dp].r * (7 - alpha)) / 7,
|
||||
(palette[col].g * alpha + palette[*dp].g * (7 - alpha)) / 7,
|
||||
(palette[col].b * alpha + palette[*dp].b * (7 - alpha)) / 7);
|
||||
cache[alpha << 8 | *dp] = c;
|
||||
*dp = c;
|
||||
}
|
||||
sp += sdl_surface_bytes_per_pixel(src);
|
||||
dp++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Rect gfx_drawString(int x, int y, const char *str_utf8, uint8_t col, FontSpec font) {
|
||||
SDL_Rect r_dst = {};
|
||||
|
||||
gfx_pal_check();
|
||||
return font_draw_text(font, x, y, str_utf8, col);
|
||||
if (!*str_utf8)
|
||||
return r_dst;
|
||||
|
||||
bool antialias = font_get_antialias();
|
||||
// The post-effect in Rance 3 opening does not work properly if colors other
|
||||
// than the specified text color (32) are used.
|
||||
// https://github.com/kichikuou/xsystem35-sdl2/issues/54
|
||||
// In Rance 3, text color 32 is only used in the opening.
|
||||
if ((game_id == GAME_RANCE3 || game_id == GAME_RANCE3_ENG) && col == 32)
|
||||
antialias = false;
|
||||
|
||||
SDL_Surface *fs = font_render_text(font, str_utf8, gfx_getPaletteColor(col), antialias);
|
||||
if (!fs)
|
||||
return r_dst;
|
||||
|
||||
y -= font_cell_overhang(font);
|
||||
r_dst = (SDL_Rect){x, y, fs->w, fs->h};
|
||||
|
||||
if (sdl_surface_bits_per_pixel(main_surface) == 8 && antialias) {
|
||||
gfx_drawAntiAlias_8bpp(x, y, fs, col);
|
||||
} else {
|
||||
SDL_Rect r_src = {0, 0, fs->w, fs->h};
|
||||
SDL_BlitSurface(fs, &r_src, main_surface, &r_dst);
|
||||
}
|
||||
|
||||
sdl_destroy_surface(fs);
|
||||
if (r_dst.y < 0) {
|
||||
r_dst.h += r_dst.y;
|
||||
r_dst.y = 0;
|
||||
}
|
||||
return r_dst;
|
||||
}
|
||||
|
||||
/*
|
||||
* 指定範囲にパレット col を rate の割合で重ねる CK1
|
||||
*/
|
||||
void gfx_wrapColor(int sx, int sy, int w, int h, uint8_t c, int rate) {
|
||||
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormat(0, w, h, main_surface->format->BitsPerPixel, main_surface->format->format);
|
||||
assert(s->format->BitsPerPixel > 8);
|
||||
SDL_Surface *s = sdl_create_surface(w, h,
|
||||
sdl_surface_bits_per_pixel(main_surface), sdl_surface_format(main_surface));
|
||||
assert(sdl_surface_bits_per_pixel(s) > 8);
|
||||
|
||||
SDL_Rect r_src = {0, 0, w, h};
|
||||
SDL_FillRect(s, &r_src, palette_color(c));
|
||||
@@ -288,5 +370,5 @@ void gfx_wrapColor(int sx, int sy, int w, int h, uint8_t c, int rate) {
|
||||
SDL_SetSurfaceAlphaMod(s, rate);
|
||||
SDL_Rect r_dst = {sx, sy, w, h};
|
||||
SDL_BlitSurface(s, &r_src, main_surface, &r_dst);
|
||||
SDL_FreeSurface(s);
|
||||
sdl_destroy_surface(s);
|
||||
}
|
||||
|
||||
+25
-21
@@ -24,7 +24,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -40,7 +40,7 @@ void gfx_FlipSurfaceHorizontal(SDL_Surface *s) {
|
||||
SDL_FlipSurface(s, SDL_FLIP_HORIZONTAL);
|
||||
#else
|
||||
uint8_t *p = s->pixels;
|
||||
int bpp = s->format->BytesPerPixel;
|
||||
int bpp = sdl_surface_bytes_per_pixel(s);
|
||||
uint8_t tmp[4];
|
||||
for (int y = 0; y < s->h; y++) {
|
||||
uint8_t *p1 = p;
|
||||
@@ -88,7 +88,7 @@ void gfx_scaledCopyArea(int sx, int sy, int sw, int sh, int dx, int dy, int dw,
|
||||
gfx_FlipSurfaceVertical(view);
|
||||
if (mirror & 2)
|
||||
gfx_FlipSurfaceHorizontal(view);
|
||||
SDL_FreeSurface(view);
|
||||
sdl_destroy_surface(view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,19 +104,19 @@ void gfx_drawImage16(cgdata *cg, surface_t *sf, int dx, int dy, int brightness,
|
||||
if (cg->alpha && alpha_blend) {
|
||||
uint16_t *p_src = (uint16_t *)cg->pic;
|
||||
uint8_t *a_src = cg->alpha;
|
||||
s = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
s = sdl_create_surface(w, h, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
for (int y = 0; y < h; y++) {
|
||||
uint32_t *dst = (uint32_t *)(((uint8_t *)s->pixels) + y * s->pitch);
|
||||
for (int x = 0; x < w; x++) {
|
||||
*dst++ = rgb565_to_rgb888(*p_src++) | *a_src++ << 24;
|
||||
}
|
||||
}
|
||||
} else if (sf->sdl_surface->format->BitsPerPixel > 16) {
|
||||
} else if (sdl_surface_bits_per_pixel(sf->sdl_surface) > 16) {
|
||||
// Convert to RGB888 by ourselves. SDL blit functions use a slightly
|
||||
// different mapping, so the color expanded by SDL may not match the
|
||||
// color key specified in the CX command.
|
||||
uint16_t *src = (uint16_t *)cg->pic;
|
||||
s = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_RGB888);
|
||||
s = sdl_create_surface(w, h, 32, SDL_PIXELFORMAT_RGB888);
|
||||
for (int y = 0; y < h; y++) {
|
||||
uint32_t *dst = (uint32_t *)(((uint8_t *)s->pixels) + y * s->pitch);
|
||||
for (int x = 0; x < w; x++) {
|
||||
@@ -124,8 +124,8 @@ void gfx_drawImage16(cgdata *cg, surface_t *sf, int dx, int dy, int brightness,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s = SDL_CreateRGBSurfaceWithFormatFrom(
|
||||
cg->pic, cg->width, cg->height, 16, cg->width * 2, SDL_PIXELFORMAT_RGB565);
|
||||
s = sdl_create_surface_from(cg->pic, cg->width, cg->height,
|
||||
16, cg->width * 2, SDL_PIXELFORMAT_RGB565);
|
||||
}
|
||||
|
||||
if (brightness != 255)
|
||||
@@ -133,7 +133,7 @@ void gfx_drawImage16(cgdata *cg, surface_t *sf, int dx, int dy, int brightness,
|
||||
|
||||
SDL_Rect r_dst = {dx, dy, w, h};
|
||||
SDL_BlitSurface(s, NULL, sf->sdl_surface, &r_dst);
|
||||
SDL_FreeSurface(s);
|
||||
sdl_destroy_surface(s);
|
||||
}
|
||||
|
||||
void gfx_drawImage24(cgdata *cg, surface_t *sf, int x, int y, int brightness) {
|
||||
@@ -141,14 +141,14 @@ void gfx_drawImage24(cgdata *cg, surface_t *sf, int x, int y, int brightness) {
|
||||
gfx_drawImageAlphaMap(cg, sf, x, y);
|
||||
}
|
||||
|
||||
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormatFrom(
|
||||
cg->pic, cg->width, cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
|
||||
SDL_Surface *s = sdl_create_surface_from(cg->pic, cg->width, cg->height,
|
||||
24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
|
||||
if (brightness != 255)
|
||||
SDL_SetSurfaceColorMod(s, brightness, brightness, brightness);
|
||||
|
||||
SDL_Rect r_dst = {x, y, cg->width, cg->height};
|
||||
SDL_BlitSurface(s, NULL, sf->sdl_surface, &r_dst);
|
||||
SDL_FreeSurface(s);
|
||||
sdl_destroy_surface(s);
|
||||
}
|
||||
|
||||
void gfx_drawImageAlphaMap(cgdata *cg, surface_t *sf, int x, int y) {
|
||||
@@ -165,7 +165,7 @@ void gfx_drawImageAlphaMap(cgdata *cg, surface_t *sf, int x, int y) {
|
||||
}
|
||||
|
||||
SDL_Surface *gfx_dib_to_surface_with_alpha(int x, int y, int w, int h) {
|
||||
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_Surface *s = sdl_create_surface(w, h, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
|
||||
SDL_Rect r_src = {x, y, w, h};
|
||||
SDL_BlitSurface(main_surface, &r_src, s, NULL);
|
||||
@@ -192,7 +192,7 @@ void gfx_copyAreaSP16_shadow(int sx, int sy, int w, int h, int dx, int dy, int l
|
||||
|
||||
SDL_Rect r_dst = {dx, dy, w, h};
|
||||
SDL_BlitSurface(s, NULL, main_surface, &r_dst);
|
||||
SDL_FreeSurface(s);
|
||||
sdl_destroy_surface(s);
|
||||
}
|
||||
|
||||
void gfx_copyAreaSP16_alphaBlend(int sx, int sy, int w, int h, int dx, int dy, int lv) {
|
||||
@@ -230,7 +230,7 @@ void gfx_copy_to_alpha(int sx, int sy, int w, int h, int dx, int dy, ALPHA_DIB_C
|
||||
uint32_t gfx_getPixel(int x, int y) {
|
||||
uint8_t *p = PIXEL_AT(main_surface, x, y);
|
||||
|
||||
switch (main_surface->format->BytesPerPixel) {
|
||||
switch (sdl_surface_bytes_per_pixel(main_surface)) {
|
||||
case 1:
|
||||
return *p;
|
||||
case 2:
|
||||
@@ -252,10 +252,14 @@ uint32_t gfx_getPixel(int x, int y) {
|
||||
* dib から領域の切り出し
|
||||
*/
|
||||
void* gfx_saveRegion(int x, int y, int w, int h) {
|
||||
SDL_Surface *s = SDL_CreateRGBSurfaceWithFormat(0, w, h, main_surface->format->BitsPerPixel, main_surface->format->format);
|
||||
if (main_surface->format->BitsPerPixel == 8)
|
||||
memcpy(s->format->palette->colors, main_surface->format->palette->colors,
|
||||
sizeof(SDL_Color) * main_surface->format->palette->ncolors);
|
||||
SDL_Surface *s = sdl_create_surface(w, h,
|
||||
sdl_surface_bits_per_pixel(main_surface), sdl_surface_format(main_surface));
|
||||
if (sdl_surface_bits_per_pixel(main_surface) == 8) {
|
||||
SDL_Palette *dst_palette = sdl_get_surface_palette(s);
|
||||
SDL_Palette *src_palette = sdl_get_surface_palette(main_surface);
|
||||
SDL_SetPaletteColors(dst_palette, src_palette->colors, 0,
|
||||
src_palette->ncolors);
|
||||
}
|
||||
SDL_Rect r_src = {x, y, w, h};
|
||||
SDL_Rect r_dst = {0, 0, w, h};
|
||||
SDL_BlitSurface(main_surface, &r_src, s, &r_dst);
|
||||
@@ -266,7 +270,7 @@ void* gfx_saveRegion(int x, int y, int w, int h) {
|
||||
* セーブした領域を破棄
|
||||
*/
|
||||
void gfx_delRegion(void *psrc) {
|
||||
SDL_FreeSurface((SDL_Surface *)psrc);
|
||||
sdl_destroy_surface((SDL_Surface *)psrc);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -287,5 +291,5 @@ void gfx_putRegion(void *psrc, int x, int y) {
|
||||
void gfx_restoreRegion(void *psrc, int x, int y) {
|
||||
SDL_Surface *src = (SDL_Surface *)psrc;
|
||||
gfx_putRegion(src, x ,y);
|
||||
SDL_FreeSurface(src);
|
||||
sdl_destroy_surface(src);
|
||||
}
|
||||
|
||||
+1
-5
@@ -25,22 +25,18 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include "sdl_compat.h"
|
||||
|
||||
#include "portab.h"
|
||||
#include "ags.h"
|
||||
|
||||
extern SDL_Window *gfx_window;
|
||||
extern SDL_Renderer *gfx_renderer;
|
||||
extern SDL_Texture *gfx_texture;
|
||||
extern SDL_Palette *gfx_palette;
|
||||
extern surface_t *gfx_dibinfo;
|
||||
extern int view_w;
|
||||
extern int view_h;
|
||||
extern bool gfx_dirty;
|
||||
extern bool gfx_fullscreen;
|
||||
|
||||
int gfx_nearest_color(int r, int g, int b);
|
||||
SDL_Surface *gfx_dib_to_surface_with_alpha(int x, int y, int w, int h);
|
||||
SDL_Surface *gfx_dib_to_surface_colorkey(int x, int y, int w, int h, int col);
|
||||
|
||||
|
||||
+71
-41
@@ -23,7 +23,8 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <SDL.h>
|
||||
#include <stdlib.h>
|
||||
#include "sdl_compat.h"
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
@@ -36,7 +37,7 @@
|
||||
#include "xsystem35.h"
|
||||
#include "image.h"
|
||||
|
||||
SDL_Window *gfx_window;
|
||||
static SDL_Window *gfx_window;
|
||||
SDL_Renderer *gfx_renderer;
|
||||
SDL_Texture *gfx_texture;
|
||||
SDL_Surface *main_surface; // offscreen surface
|
||||
@@ -44,8 +45,8 @@ SDL_Palette *gfx_palette;
|
||||
surface_t *gfx_dibinfo;
|
||||
int view_w;
|
||||
int view_h;
|
||||
bool gfx_dirty;
|
||||
bool gfx_fullscreen;
|
||||
static bool gfx_fullscreen;
|
||||
static bool gfx_integer_scaling;
|
||||
|
||||
static void window_init(const char *render_driver);
|
||||
static void makeDIB(int width, int height, int depth);
|
||||
@@ -57,7 +58,7 @@ int gfx_Initialize(const char *render_driver) {
|
||||
/* offscreen Pixmap */
|
||||
makeDIB(SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT, SYS35_DEFAULT_DEPTH);
|
||||
|
||||
gfx_setWindowSize(SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT);
|
||||
gfx_setViewSize(SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT);
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Prevent SDL from calling emscripten_exit_fullscreen on visibilitychange
|
||||
@@ -69,9 +70,9 @@ int gfx_Initialize(const char *render_driver) {
|
||||
|
||||
void gfx_Remove(void) {
|
||||
if (gfx_palette)
|
||||
SDL_FreePalette(gfx_palette);
|
||||
sdl_destroy_palette(gfx_palette);
|
||||
if (main_surface)
|
||||
SDL_FreeSurface(main_surface);
|
||||
sdl_destroy_surface(main_surface);
|
||||
if (gfx_renderer)
|
||||
SDL_DestroyRenderer(gfx_renderer);
|
||||
SDL_Quit();
|
||||
@@ -92,7 +93,9 @@ static void window_init(const char *render_driver) {
|
||||
if (render_driver)
|
||||
SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver);
|
||||
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
|
||||
#if XSYSTEM35_SDL_VERSION == 2
|
||||
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
|
||||
#endif
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
|
||||
@@ -109,24 +112,19 @@ static void window_init(const char *render_driver) {
|
||||
|
||||
#ifdef __ANDROID__
|
||||
SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1");
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
|
||||
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
|
||||
Uint32 flags = SDL_WINDOW_FULLSCREEN;
|
||||
#else
|
||||
Uint32 flags = SDL_WINDOW_RESIZABLE;
|
||||
#endif
|
||||
gfx_window = SDL_CreateWindow(
|
||||
title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT, flags);
|
||||
gfx_renderer = SDL_CreateRenderer(gfx_window, -1, 0);
|
||||
|
||||
gfx_window = sdl_create_window(title, SYS35_DEFAULT_WIDTH,
|
||||
SYS35_DEFAULT_HEIGHT, SDL_WINDOW_RESIZABLE);
|
||||
gfx_renderer = sdl_create_renderer(gfx_window);
|
||||
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
||||
gfx_palette = SDL_AllocPalette(256);
|
||||
gfx_palette = sdl_create_palette(256);
|
||||
}
|
||||
|
||||
static void makeDIB(int width, int height, int depth) {
|
||||
|
||||
if (main_surface) {
|
||||
SDL_FreeSurface(main_surface);
|
||||
sdl_destroy_surface(main_surface);
|
||||
}
|
||||
|
||||
uint32_t format = 0;
|
||||
@@ -146,10 +144,10 @@ static void makeDIB(int width, int height, int depth) {
|
||||
SYSERROR("invalid pixel depth %d", depth);
|
||||
}
|
||||
|
||||
main_surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, depth, format);
|
||||
main_surface = sdl_create_surface(width, height, depth, format);
|
||||
|
||||
if (main_surface->format->BitsPerPixel == 8) {
|
||||
SDL_SetSurfacePalette(main_surface, gfx_palette);
|
||||
if (sdl_surface_bits_per_pixel(main_surface) == 8) {
|
||||
sdl_set_surface_palette(main_surface, gfx_palette);
|
||||
}
|
||||
|
||||
if (gfx_dibinfo) {
|
||||
@@ -162,7 +160,7 @@ static void makeDIB(int width, int height, int depth) {
|
||||
gfx_dibinfo->alpha = NULL;
|
||||
gfx_dibinfo->sdl_surface = main_surface;
|
||||
|
||||
image_setdepth(main_surface->format->BitsPerPixel);
|
||||
image_setdepth(sdl_surface_bits_per_pixel(main_surface));
|
||||
}
|
||||
|
||||
/* offscreen の設定 */
|
||||
@@ -171,13 +169,38 @@ void gfx_setWorldSize(int width, int height, int depth) {
|
||||
SDL_FillRect(main_surface, NULL, 0);
|
||||
}
|
||||
|
||||
/* display の size と depth の取得 */
|
||||
void gfx_getWindowInfo(int *width, int *height, int *depth) {
|
||||
SDL_DisplayMode dm;
|
||||
SDL_GetCurrentDisplayMode(0, &dm);
|
||||
if (width) *width = dm.w;
|
||||
if (height) *height = dm.h;
|
||||
if (depth) *depth = SDL_BITSPERPIXEL(dm.format);
|
||||
void gfx_getViewSize(int *width, int *height) {
|
||||
if (width)
|
||||
*width = view_w;
|
||||
if (height)
|
||||
*height = view_h;
|
||||
}
|
||||
|
||||
SDL_Window *gfx_getWindow(void) {
|
||||
return gfx_window;
|
||||
}
|
||||
|
||||
bool gfx_convertEventCoordinates(SDL_Event *event) {
|
||||
#if XSYSTEM35_SDL_VERSION == 2
|
||||
return true;
|
||||
#else
|
||||
return SDL_ConvertEventToRenderCoordinates(gfx_renderer, event);
|
||||
#endif
|
||||
}
|
||||
|
||||
static SDL_Point view_to_window_point(int x, int y) {
|
||||
return sdl_render_coordinates_to_window(gfx_renderer, x, y);
|
||||
}
|
||||
|
||||
void gfx_warpMouse(int x, int y) {
|
||||
SDL_Point point = view_to_window_point(x, y);
|
||||
SDL_WarpMouseInWindow(gfx_window, point.x, point.y);
|
||||
}
|
||||
|
||||
SDL_Rect gfx_viewToWindowRect(SDL_Rect rect) {
|
||||
SDL_Point tl = view_to_window_point(rect.x, rect.y);
|
||||
SDL_Point br = view_to_window_point(rect.x + rect.w, rect.y + rect.h);
|
||||
return (SDL_Rect){tl.x, tl.y, br.x - tl.x, br.y - tl.y};
|
||||
}
|
||||
|
||||
/* DIBの取得 */
|
||||
@@ -187,11 +210,12 @@ surface_t *gfx_getDIB(void) {
|
||||
|
||||
SDL_Surface *gfx_createSurfaceView(SDL_Surface *sf, int x, int y, int w, int h) {
|
||||
uint8_t *pixels = sf->pixels;
|
||||
pixels += y * sf->pitch + x * sf->format->BytesPerPixel;
|
||||
SDL_Surface *view = SDL_CreateRGBSurfaceWithFormatFrom(
|
||||
pixels, w, h, sf->format->BitsPerPixel, sf->pitch, sf->format->format);
|
||||
if (sf->format->palette)
|
||||
SDL_SetSurfacePalette(view, sf->format->palette);
|
||||
pixels += y * sf->pitch + x * sdl_surface_bytes_per_pixel(sf);
|
||||
SDL_Surface *view = sdl_create_surface_from(pixels, w, h,
|
||||
sdl_surface_bits_per_pixel(sf), sf->pitch, sdl_surface_format(sf));
|
||||
SDL_Palette *palette = sdl_get_surface_palette(sf);
|
||||
if (palette)
|
||||
sdl_set_surface_palette(view, palette);
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -199,7 +223,7 @@ void gfx_setFullscreen(bool on) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
if (on == gfx_fullscreen)
|
||||
return;
|
||||
SDL_SetWindowFullscreen(gfx_window, on ? SDL_WINDOW_FULLSCREEN_DESKTOP: 0);
|
||||
sdl_set_window_fullscreen(gfx_window, on);
|
||||
gfx_fullscreen = on;
|
||||
#endif
|
||||
}
|
||||
@@ -212,20 +236,24 @@ void gfx_raiseWindow(void) {
|
||||
SDL_RaiseWindow(gfx_window);
|
||||
}
|
||||
|
||||
void gfx_setWindowSize(int w, int h) {
|
||||
void gfx_setViewSize(int w, int h) {
|
||||
if (w == view_w && h == view_h) return;
|
||||
|
||||
view_w = w;
|
||||
view_h = h;
|
||||
|
||||
#ifndef __ANDROID__
|
||||
SDL_SetWindowSize(gfx_window, w, h);
|
||||
sdl_set_window_content_size(gfx_window, w, h);
|
||||
#endif
|
||||
SDL_RenderSetLogicalSize(gfx_renderer, w, h);
|
||||
sdl_set_render_logical_presentation(
|
||||
gfx_renderer, w, h, gfx_integer_scaling);
|
||||
if (gfx_texture)
|
||||
SDL_DestroyTexture(gfx_texture);
|
||||
gfx_texture = SDL_CreateTexture(
|
||||
gfx_renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, w, h);
|
||||
#ifdef __ANDROID__
|
||||
SDL_SetTextureScaleMode(gfx_texture, SDL_SCALEMODE_LINEAR);
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASM( xsystem35.shell.windowSizeChanged(); );
|
||||
@@ -233,13 +261,15 @@ void gfx_setWindowSize(int w, int h) {
|
||||
}
|
||||
|
||||
void gfx_setIntegerScaling(bool enable) {
|
||||
SDL_RenderSetIntegerScale(gfx_renderer, enable);
|
||||
gfx_integer_scaling = enable;
|
||||
sdl_set_render_logical_presentation(
|
||||
gfx_renderer, view_w, view_h, gfx_integer_scaling);
|
||||
}
|
||||
|
||||
bool EMSCRIPTEN_KEEPALIVE save_screenshot(const char* path) {
|
||||
SDL_Rect *r = &nact->ags.view_area;
|
||||
SDL_Surface *view = gfx_createSurfaceView(main_surface, r->x, r->y, r->w, r->h);
|
||||
bool ok = SDL_SaveBMP(view, path) == 0;
|
||||
SDL_FreeSurface(view);
|
||||
bool ok = sdl_save_bmp(view, path);
|
||||
sdl_destroy_surface(view);
|
||||
return ok;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user