summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/scripts/common/pre-upload.sh2
-rwxr-xr-x.ci/scripts/linux/docker.sh15
-rwxr-xr-x.ci/scripts/linux/upload.sh5
-rw-r--r--CMakeLists.txt45
-rw-r--r--dist/qt_themes/default/style.qss4
-rw-r--r--dist/qt_themes/qdarkstyle/style.qss4
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/style.qss4
-rw-r--r--externals/CMakeLists.txt2
-rw-r--r--externals/nx_tzdb/CMakeLists.txt2
m---------externals/nx_tzdb/tzdb_to_nx0
-rw-r--r--externals/stb/stb_image.cpp7529
-rw-r--r--externals/stb/stb_image.h772
-rw-r--r--externals/stb/stb_image_resize.cpp2282
-rw-r--r--externals/stb/stb_image_resize.h426
-rw-r--r--src/android/app/build.gradle.kts16
-rw-r--r--src/android/app/src/main/AndroidManifest.xml1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt117
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt186
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt75
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt29
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt41
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt158
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt97
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt228
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt112
-rw-r--r--src/android/app/src/main/jni/native.cpp1
-rw-r--r--src/android/app/src/main/res/drawable/ic_build.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_delete.xml9
-rw-r--r--src/android/app/src/main/res/layout/card_driver_option.xml89
-rw-r--r--src/android/app/src/main/res/layout/fragment_driver_manager.xml48
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml7
-rw-r--r--src/android/app/src/main/res/values-de/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml2
-rw-r--r--src/android/app/src/main/res/values/dimens.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml7
-rw-r--r--src/android/build.gradle.kts4
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp9
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.h2
-rw-r--r--src/audio_core/sink/sink_stream.cpp4
-rw-r--r--src/common/CMakeLists.txt8
-rw-r--r--src/common/arm64/native_clock.cpp72
-rw-r--r--src/common/arm64/native_clock.h47
-rw-r--r--src/common/common_funcs.h4
-rw-r--r--src/common/elf.h8
-rw-r--r--src/common/fs/fs_paths.h2
-rw-r--r--src/common/fs/path_util.cpp2
-rw-r--r--src/common/fs/path_util.h2
-rw-r--r--src/common/polyfill_thread.h21
-rw-r--r--src/common/settings.cpp5
-rw-r--r--src/common/settings.h5
-rw-r--r--src/common/settings_enums.h2
-rw-r--r--src/common/string_util.cpp5
-rw-r--r--src/common/string_util.h1
-rw-r--r--src/common/wall_clock.cpp8
-rw-r--r--src/core/CMakeLists.txt8
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/debugger/gdbstub.cpp17
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp84
-rw-r--r--src/core/file_sys/patch_manager.cpp8
-rw-r--r--src/core/file_sys/program_metadata.cpp8
-rw-r--r--src/core/file_sys/program_metadata.h1
-rw-r--r--src/core/file_sys/registered_cache.cpp3
-rw-r--r--src/core/file_sys/romfs.cpp5
-rw-r--r--src/core/file_sys/vfs_cached.cpp6
-rw-r--r--src/core/file_sys/vfs_cached.h2
-rw-r--r--src/core/file_sys/vfs_concat.cpp27
-rw-r--r--src/core/file_sys/vfs_concat.h12
-rw-r--r--src/core/file_sys/vfs_layered.cpp8
-rw-r--r--src/core/hle/kernel/k_page_table.cpp22
-rw-r--r--src/core/hle/kernel/k_page_table.h3
-rw-r--r--src/core/hle/kernel/k_transfer_memory.cpp89
-rw-r--r--src/core/hle/kernel/k_transfer_memory.h14
-rw-r--r--src/core/hle/kernel/kernel.cpp16
-rw-r--r--src/core/hle/kernel/svc/svc_transfer_memory.cpp54
-rw-r--r--src/core/hle/service/acc/acc.cpp55
-rw-r--r--src/core/hle/service/acc/acc.h3
-rw-r--r--src/core/hle/service/acc/acc_su.cpp6
-rw-r--r--src/core/hle/service/acc/profile_manager.h3
-rw-r--r--src/core/hle/service/am/am.cpp147
-rw-r--r--src/core/hle/service/am/am.h23
-rw-r--r--src/core/hle/service/am/applet_ae.cpp20
-rw-r--r--src/core/hle/service/am/applets/applet_error.cpp5
-rw-r--r--src/core/hle/service/caps/caps.cpp21
-rw-r--r--src/core/hle/service/caps/caps.h81
-rw-r--r--src/core/hle/service/caps/caps_a.cpp239
-rw-r--r--src/core/hle/service/caps/caps_a.h21
-rw-r--r--src/core/hle/service/caps/caps_c.cpp50
-rw-r--r--src/core/hle/service/caps/caps_c.h10
-rw-r--r--src/core/hle/service/caps/caps_manager.cpp386
-rw-r--r--src/core/hle/service/caps/caps_manager.h79
-rw-r--r--src/core/hle/service/caps/caps_result.h35
-rw-r--r--src/core/hle/service/caps/caps_sc.cpp5
-rw-r--r--src/core/hle/service/caps/caps_sc.h6
-rw-r--r--src/core/hle/service/caps/caps_ss.cpp5
-rw-r--r--src/core/hle/service/caps/caps_ss.h6
-rw-r--r--src/core/hle/service/caps/caps_su.cpp9
-rw-r--r--src/core/hle/service/caps/caps_su.h6
-rw-r--r--src/core/hle/service/caps/caps_types.h184
-rw-r--r--src/core/hle/service/caps/caps_u.cpp146
-rw-r--r--src/core/hle/service/caps/caps_u.h12
-rw-r--r--src/core/hle/service/hle_ipc.cpp58
-rw-r--r--src/core/hle/service/hle_ipc.h6
-rw-r--r--src/core/hle/service/jit/jit_context.cpp36
-rw-r--r--src/core/hle/service/nifm/nifm.cpp12
-rw-r--r--src/core/hle/service/nifm/nifm.h1
-rw-r--r--src/core/hle/service/ns/ns.cpp27
-rw-r--r--src/core/hle/service/ns/ns.h4
-rw-r--r--src/core/hle/service/pctl/pctl_module.cpp9
-rw-r--r--src/core/hle/service/prepo/prepo.cpp40
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp2
-rw-r--r--src/input_common/drivers/udp_client.cpp1
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h46
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h4
-rw-r--r--src/video_core/engines/draw_manager.cpp24
-rw-r--r--src/video_core/engines/draw_manager.h2
-rw-r--r--src/video_core/host1x/codecs/codec.cpp10
-rw-r--r--src/video_core/host_shaders/convert_d24s8_to_abgr8.frag8
-rw-r--r--src/video_core/host_shaders/convert_s8d24_to_abgr8.frag8
-rw-r--r--src/video_core/renderer_base.h3
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp14
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h2
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h1
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp4
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp5
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp1
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.cpp67
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.h10
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp1
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp16
-rw-r--r--src/video_core/renderer_vulkan/vk_render_pass_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp33
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp45
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h2
-rw-r--r--src/video_core/surface.cpp3
-rw-r--r--src/video_core/surface.h4
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp6
-rw-r--r--src/video_core/texture_cache/formatter.cpp8
-rw-r--r--src/video_core/texture_cache/formatter.h2
-rw-r--r--src/video_core/texture_cache/image_view_base.cpp1
-rw-r--r--src/video_core/texture_cache/samples_helper.h2
-rw-r--r--src/video_core/texture_cache/texture_cache.h1
-rw-r--r--src/video_core/texture_cache/util.cpp36
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp56
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp29
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.h14
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h3
-rw-r--r--src/yuzu/CMakeLists.txt4
-rw-r--r--src/yuzu/applets/qt_controller.cpp39
-rw-r--r--src/yuzu/applets/qt_controller.h6
-rw-r--r--src/yuzu/applets/qt_controller.ui54
-rw-r--r--src/yuzu/configuration/config.cpp4
-rw-r--r--src/yuzu/configuration/configure_audio.cpp3
-rw-r--r--src/yuzu/configuration/configure_input.cpp36
-rw-r--r--src/yuzu/configuration/configure_input.h1
-rw-r--r--src/yuzu/configuration/configure_ui.cpp5
-rw-r--r--src/yuzu/configuration/configure_ui.ui7
-rw-r--r--src/yuzu/configuration/shared_translation.cpp11
-rw-r--r--src/yuzu/game_list.cpp23
-rw-r--r--src/yuzu/game_list.h7
-rw-r--r--src/yuzu/game_list_p.h26
-rw-r--r--src/yuzu/game_list_worker.cpp30
-rw-r--r--src/yuzu/game_list_worker.h11
-rw-r--r--src/yuzu/main.cpp291
-rw-r--r--src/yuzu/main.h26
-rw-r--r--src/yuzu/main.ui6
-rw-r--r--src/yuzu/play_time_manager.cpp179
-rw-r--r--src/yuzu/play_time_manager.h44
-rw-r--r--src/yuzu/uisettings.h16
-rw-r--r--src/yuzu/util/util.cpp102
-rw-r--r--src/yuzu/util/util.h14
190 files changed, 15173 insertions, 1071 deletions
diff --git a/.ci/scripts/common/pre-upload.sh b/.ci/scripts/common/pre-upload.sh
index 705362a3c..3583f9840 100644
--- a/.ci/scripts/common/pre-upload.sh
+++ b/.ci/scripts/common/pre-upload.sh
@@ -5,6 +5,6 @@
5 5
6GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`" 6GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
7GITREV="`git show -s --format='%h'`" 7GITREV="`git show -s --format='%h'`"
8ARTIFACTS_DIR="artifacts" 8ARTIFACTS_DIR="$PWD/artifacts"
9 9
10mkdir -p "${ARTIFACTS_DIR}/" 10mkdir -p "${ARTIFACTS_DIR}/"
diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh
index e5d83d4b9..a16577b27 100755
--- a/.ci/scripts/linux/docker.sh
+++ b/.ci/scripts/linux/docker.sh
@@ -11,7 +11,7 @@ ccache -s
11mkdir build || true && cd build 11mkdir build || true && cd build
12cmake .. \ 12cmake .. \
13 -DBoost_USE_STATIC_LIBS=ON \ 13 -DBoost_USE_STATIC_LIBS=ON \
14 -DCMAKE_BUILD_TYPE=Release \ 14 -DCMAKE_BUILD_TYPE=RelWithDebInfo \
15 -DCMAKE_CXX_FLAGS="-march=x86-64-v2" \ 15 -DCMAKE_CXX_FLAGS="-march=x86-64-v2" \
16 -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ \ 16 -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ \
17 -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc \ 17 -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc \
@@ -31,6 +31,19 @@ ccache -s
31 31
32ctest -VV -C Release 32ctest -VV -C Release
33 33
34# Separate debug symbols from specified executables
35for EXE in yuzu; do
36 EXE_PATH="bin/$EXE"
37 # Copy debug symbols out
38 objcopy --only-keep-debug $EXE_PATH $EXE_PATH.debug
39 # Add debug link and strip debug symbols
40 objcopy -g --add-gnu-debuglink=$EXE_PATH.debug $EXE_PATH $EXE_PATH.out
41 # Overwrite original with stripped copy
42 mv $EXE_PATH.out $EXE_PATH
43done
44# Strip debug symbols from all executables
45find bin/ -type f -not -regex '.*.debug' -exec strip -g {} ';'
46
34DESTDIR="$PWD/AppDir" ninja install 47DESTDIR="$PWD/AppDir" ninja install
35rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester 48rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester
36 49
diff --git a/.ci/scripts/linux/upload.sh b/.ci/scripts/linux/upload.sh
index e0f336427..fbb2d9c1b 100755
--- a/.ci/scripts/linux/upload.sh
+++ b/.ci/scripts/linux/upload.sh
@@ -59,4 +59,9 @@ if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ];
59 cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage" 59 cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage"
60fi 60fi
61 61
62# Copy debug symbols to artifacts
63cd build/bin
64tar $COMPRESSION_FLAGS "${ARTIFACTS_DIR}/${REV_NAME}-debug.tar.xz" *.debug
65cd -
66
62. .ci/scripts/common/post-upload.sh 67. .ci/scripts/common/post-upload.sh
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2bef9d6ed..166024844 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,7 +11,6 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modul
11include(DownloadExternals) 11include(DownloadExternals)
12include(CMakeDependentOption) 12include(CMakeDependentOption)
13include(CTest) 13include(CTest)
14include(FetchContent)
15 14
16# Set bundled sdl2/qt as dependent options. 15# Set bundled sdl2/qt as dependent options.
17# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON 16# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
@@ -99,47 +98,8 @@ if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
99 DESTINATION "${vvl_lib_path}") 98 DESTINATION "${vvl_lib_path}")
100endif() 99endif()
101 100
102# On Android, fetch and compile libcxx before doing anything else
103if (ANDROID) 101if (ANDROID)
104 set(CMAKE_SKIP_INSTALL_RULES ON) 102 set(CMAKE_SKIP_INSTALL_RULES ON)
105 set(LLVM_VERSION "15.0.6")
106
107 # Note: even though libcxx and libcxxabi have separate releases on the project page,
108 # the separated releases cannot be compiled. Only in-tree builds work. Therefore we
109 # must fetch the source release for the entire llvm tree.
110 FetchContent_Declare(llvm
111 URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/llvm-project-${LLVM_VERSION}.src.tar.xz"
112 URL_HASH SHA256=9d53ad04dc60cb7b30e810faf64c5ab8157dadef46c8766f67f286238256ff92
113 TLS_VERIFY TRUE
114 )
115 FetchContent_MakeAvailable(llvm)
116
117 # libcxx has support for most of the range library, but it's gated behind a flag:
118 add_compile_definitions(_LIBCPP_ENABLE_EXPERIMENTAL)
119
120 # Disable standard header inclusion
121 set(ANDROID_STL "none")
122
123 # libcxxabi
124 set(LIBCXXABI_INCLUDE_TESTS OFF)
125 set(LIBCXXABI_ENABLE_SHARED FALSE)
126 set(LIBCXXABI_ENABLE_STATIC TRUE)
127 set(LIBCXXABI_LIBCXX_INCLUDES "${LIBCXX_TARGET_INCLUDE_DIRECTORY}" CACHE STRING "" FORCE)
128 add_subdirectory("${llvm_SOURCE_DIR}/libcxxabi" "${llvm_BINARY_DIR}/libcxxabi")
129 link_libraries(cxxabi_static)
130
131 # libcxx
132 set(LIBCXX_ABI_NAMESPACE "__ndk1" CACHE STRING "" FORCE)
133 set(LIBCXX_CXX_ABI "libcxxabi")
134 set(LIBCXX_INCLUDE_TESTS OFF)
135 set(LIBCXX_INCLUDE_BENCHMARKS OFF)
136 set(LIBCXX_INCLUDE_DOCS OFF)
137 set(LIBCXX_ENABLE_SHARED FALSE)
138 set(LIBCXX_ENABLE_STATIC TRUE)
139 set(LIBCXX_ENABLE_ASSERTIONS FALSE)
140 add_subdirectory("${llvm_SOURCE_DIR}/libcxx" "${llvm_BINARY_DIR}/libcxx")
141 set_target_properties(cxx-headers PROPERTIES INTERFACE_COMPILE_OPTIONS "-isystem${CMAKE_BINARY_DIR}/${LIBCXX_INSTALL_INCLUDE_DIR}")
142 link_libraries(cxx_static cxx-headers)
143endif() 103endif()
144 104
145if (YUZU_USE_BUNDLED_VCPKG) 105if (YUZU_USE_BUNDLED_VCPKG)
@@ -329,7 +289,7 @@ find_package(Boost 1.79.0 REQUIRED context)
329find_package(enet 1.3 MODULE) 289find_package(enet 1.3 MODULE)
330find_package(fmt 9 REQUIRED) 290find_package(fmt 9 REQUIRED)
331find_package(inih 52 MODULE COMPONENTS INIReader) 291find_package(inih 52 MODULE COMPONENTS INIReader)
332find_package(LLVM 17 MODULE COMPONENTS Demangle) 292find_package(LLVM 17.0.2 MODULE COMPONENTS Demangle)
333find_package(lz4 REQUIRED) 293find_package(lz4 REQUIRED)
334find_package(nlohmann_json 3.8 REQUIRED) 294find_package(nlohmann_json 3.8 REQUIRED)
335find_package(Opus 1.3 MODULE) 295find_package(Opus 1.3 MODULE)
@@ -400,6 +360,9 @@ function(set_yuzu_qt_components)
400 if (ENABLE_QT_TRANSLATION) 360 if (ENABLE_QT_TRANSLATION)
401 list(APPEND YUZU_QT_COMPONENTS2 LinguistTools) 361 list(APPEND YUZU_QT_COMPONENTS2 LinguistTools)
402 endif() 362 endif()
363 if (USE_DISCORD_PRESENCE)
364 list(APPEND YUZU_QT_COMPONENTS2 Network)
365 endif()
403 set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE) 366 set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE)
404endfunction(set_yuzu_qt_components) 367endfunction(set_yuzu_qt_components)
405 368
diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss
index 79960ee0c..921950c6c 100644
--- a/dist/qt_themes/default/style.qss
+++ b/dist/qt_themes/default/style.qss
@@ -120,6 +120,10 @@ QWidget#connectedControllers {
120 background: transparent; 120 background: transparent;
121} 121}
122 122
123QWidget#closeButtons {
124 background: transparent;
125}
126
123QWidget#playersSupported, 127QWidget#playersSupported,
124QWidget#controllersSupported, 128QWidget#controllersSupported,
125QWidget#controllerSupported1, 129QWidget#controllerSupported1,
diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss
index 63a636ae6..328ac942f 100644
--- a/dist/qt_themes/qdarkstyle/style.qss
+++ b/dist/qt_themes/qdarkstyle/style.qss
@@ -1380,6 +1380,10 @@ QWidget#connectedControllers {
1380 background: transparent; 1380 background: transparent;
1381} 1381}
1382 1382
1383QWidget#closeButtons {
1384 background: transparent;
1385}
1386
1383QWidget#playersSupported, 1387QWidget#playersSupported,
1384QWidget#controllersSupported, 1388QWidget#controllersSupported,
1385QWidget#controllerSupported1, 1389QWidget#controllerSupported1,
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
index 0c53115f6..eb0889b13 100644
--- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
@@ -2305,6 +2305,10 @@ QWidget#connectedControllers {
2305 background: transparent; 2305 background: transparent;
2306} 2306}
2307 2307
2308QWidget#closeButtons {
2309 background: transparent;
2310}
2311
2308QWidget#playersSupported, 2312QWidget#playersSupported,
2309QWidget#controllersSupported, 2313QWidget#controllersSupported,
2310QWidget#controllerSupported1, 2314QWidget#controllerSupported1,
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 9eebc7d65..b1fd3ac62 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -168,7 +168,7 @@ if (NOT TARGET LLVM::Demangle)
168 add_library(LLVM::Demangle ALIAS demangle) 168 add_library(LLVM::Demangle ALIAS demangle)
169endif() 169endif()
170 170
171add_library(stb stb/stb_dxt.cpp) 171add_library(stb stb/stb_dxt.cpp stb/stb_image.cpp stb/stb_image_resize.cpp)
172target_include_directories(stb PUBLIC ./stb) 172target_include_directories(stb PUBLIC ./stb)
173 173
174add_library(bc_decoder bc_decoder/bc_decoder.cpp) 174add_library(bc_decoder bc_decoder/bc_decoder.cpp)
diff --git a/externals/nx_tzdb/CMakeLists.txt b/externals/nx_tzdb/CMakeLists.txt
index 593786250..0fad24642 100644
--- a/externals/nx_tzdb/CMakeLists.txt
+++ b/externals/nx_tzdb/CMakeLists.txt
@@ -27,7 +27,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)
27 set(CAN_BUILD_NX_TZDB false) 27 set(CAN_BUILD_NX_TZDB false)
28endif() 28endif()
29 29
30set(NX_TZDB_VERSION "220816") 30set(NX_TZDB_VERSION "221202")
31set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip") 31set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
32 32
33set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb") 33set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
diff --git a/externals/nx_tzdb/tzdb_to_nx b/externals/nx_tzdb/tzdb_to_nx
Subproject 212afa2394a74226dcf1b7996a570aae17debb6 Subproject 0d17dd066d91f954a4c89d46dcb067eead6b1e4
diff --git a/externals/stb/stb_image.cpp b/externals/stb/stb_image.cpp
new file mode 100644
index 000000000..dbf26f7c5
--- /dev/null
+++ b/externals/stb/stb_image.cpp
@@ -0,0 +1,7529 @@
1// SPDX-FileCopyrightText: stb http://nothings.org/stb
2// SPDX-License-Identifier: MIT
3
4/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb
5 no warranty implied; use at your own risk
6
7LICENSE
8
9 See end of file for license information.
10
11RECENT REVISION HISTORY:
12
13 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
14 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
15 2.26 (2020-07-13) many minor fixes
16 2.25 (2020-02-02) fix warnings
17 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically
18 2.23 (2019-08-11) fix clang static analysis warning
19 2.22 (2019-03-04) gif fixes, fix warnings
20 2.21 (2019-02-25) fix typo in comment
21 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
22 2.19 (2018-02-11) fix warning
23 2.18 (2018-01-30) fix warnings
24 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings
25 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes
26 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC
27 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
28 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes
29 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
30 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64
31 RGB-format JPEG; remove white matting in PSD;
32 allocate large structures on the stack;
33 correct channel count for PNG & BMP
34 2.10 (2016-01-22) avoid warning introduced in 2.09
35 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED
36
37 See end of file for full revision history.
38
39
40 ============================ Contributors =========================
41
42 Image formats Extensions, features
43 Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info)
44 Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info)
45 Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG)
46 Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks)
47 Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG)
48 Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip)
49 Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD)
50 github:urraka (animated gif) Junggon Kim (PNM comments)
51 Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA)
52 socks-the-fox (16-bit PNG)
53 Jeremy Sawicki (handle all ImageNet JPGs)
54 Optimizations & bugfixes Mikhail Morozov (1-bit BMP)
55 Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query)
56 Arseny Kapoulkine Simon Breuss (16-bit PNM)
57 John-Mark Allen
58 Carmelo J Fdez-Aguera
59
60 Bug & warning fixes
61 Marc LeBlanc David Woo Guillaume George Martins Mozeiko
62 Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski
63 Phil Jordan Dave Moore Roy Eltham
64 Hayaki Saito Nathan Reed Won Chun
65 Luke Graham Johan Duparc Nick Verigakis the Horde3D community
66 Thomas Ruf Ronny Chevalier github:rlyeh
67 Janez Zemva John Bartholomew Michal Cichon github:romigrou
68 Jonathan Blow Ken Hamada Tero Hanninen github:svdijk
69 Eugene Golushkov Laurent Gomila Cort Stratton github:snagar
70 Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex
71 Cass Everitt Ryamond Barbiero github:grim210
72 Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw
73 Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus
74 Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo
75 Julian Raschke Gregory Mullen Christian Floisand github:darealshinji
76 Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007
77 Brad Weinberger Matvey Cherevko github:mosra
78 Luca Sas Alexander Veselov Zack Middleton [reserved]
79 Ryan C. Gordon [reserved] [reserved]
80 DO NOT ADD YOUR NAME HERE
81
82 Jacko Dirks
83
84 To add your name to the credits, pick a random blank space in the middle and fill it.
85 80% of merge conflicts on stb PRs are due to people adding their name at the end
86 of the credits.
87*/
88
89#include <stb_image.h>
90
91#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \
92 || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \
93 || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \
94 || defined(STBI_ONLY_ZLIB)
95 #ifndef STBI_ONLY_JPEG
96 #define STBI_NO_JPEG
97 #endif
98 #ifndef STBI_ONLY_PNG
99 #define STBI_NO_PNG
100 #endif
101 #ifndef STBI_ONLY_BMP
102 #define STBI_NO_BMP
103 #endif
104 #ifndef STBI_ONLY_PSD
105 #define STBI_NO_PSD
106 #endif
107 #ifndef STBI_ONLY_TGA
108 #define STBI_NO_TGA
109 #endif
110 #ifndef STBI_ONLY_GIF
111 #define STBI_NO_GIF
112 #endif
113 #ifndef STBI_ONLY_HDR
114 #define STBI_NO_HDR
115 #endif
116 #ifndef STBI_ONLY_PIC
117 #define STBI_NO_PIC
118 #endif
119 #ifndef STBI_ONLY_PNM
120 #define STBI_NO_PNM
121 #endif
122#endif
123
124#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB)
125#define STBI_NO_ZLIB
126#endif
127
128
129#include <stdarg.h>
130#include <stddef.h> // ptrdiff_t on osx
131#include <stdlib.h>
132#include <string.h>
133#include <limits.h>
134
135#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
136#include <math.h> // ldexp, pow
137#endif
138
139#ifndef STBI_NO_STDIO
140#include <stdio.h>
141#endif
142
143#ifndef STBI_ASSERT
144#include <assert.h>
145#define STBI_ASSERT(x) assert(x)
146#endif
147
148#ifdef __cplusplus
149#define STBI_EXTERN extern "C"
150#else
151#define STBI_EXTERN extern
152#endif
153
154
155#ifndef _MSC_VER
156 #ifdef __cplusplus
157 #define stbi_inline inline
158 #else
159 #define stbi_inline
160 #endif
161#else
162 #define stbi_inline __forceinline
163#endif
164
165#ifndef STBI_NO_THREAD_LOCALS
166 #if defined(__cplusplus) && __cplusplus >= 201103L
167 #define STBI_THREAD_LOCAL thread_local
168 #elif defined(__GNUC__) && __GNUC__ < 5
169 #define STBI_THREAD_LOCAL __thread
170 #elif defined(_MSC_VER)
171 #define STBI_THREAD_LOCAL __declspec(thread)
172 #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
173 #define STBI_THREAD_LOCAL _Thread_local
174 #endif
175
176 #ifndef STBI_THREAD_LOCAL
177 #if defined(__GNUC__)
178 #define STBI_THREAD_LOCAL __thread
179 #endif
180 #endif
181#endif
182
183#if defined(_MSC_VER) || defined(__SYMBIAN32__)
184typedef unsigned short stbi__uint16;
185typedef signed short stbi__int16;
186typedef unsigned int stbi__uint32;
187typedef signed int stbi__int32;
188#else
189#include <stdint.h>
190typedef uint16_t stbi__uint16;
191typedef int16_t stbi__int16;
192typedef uint32_t stbi__uint32;
193typedef int32_t stbi__int32;
194#endif
195
196// should produce compiler error if size is wrong
197typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1];
198
199#ifdef _MSC_VER
200#define STBI_NOTUSED(v) (void)(v)
201#else
202#define STBI_NOTUSED(v) (void)sizeof(v)
203#endif
204
205#ifdef _MSC_VER
206#define STBI_HAS_LROTL
207#endif
208
209#ifdef STBI_HAS_LROTL
210 #define stbi_lrot(x,y) _lrotl(x,y)
211#else
212 #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31)))
213#endif
214
215#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED))
216// ok
217#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED)
218// ok
219#else
220#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)."
221#endif
222
223#ifndef STBI_MALLOC
224#define STBI_MALLOC(sz) malloc(sz)
225#define STBI_REALLOC(p,newsz) realloc(p,newsz)
226#define STBI_FREE(p) free(p)
227#endif
228
229#ifndef STBI_REALLOC_SIZED
230#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz)
231#endif
232
233// x86/x64 detection
234#if defined(__x86_64__) || defined(_M_X64)
235#define STBI__X64_TARGET
236#elif defined(__i386) || defined(_M_IX86)
237#define STBI__X86_TARGET
238#endif
239
240#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD)
241// gcc doesn't support sse2 intrinsics unless you compile with -msse2,
242// which in turn means it gets to use SSE2 everywhere. This is unfortunate,
243// but previous attempts to provide the SSE2 functions with runtime
244// detection caused numerous issues. The way architecture extensions are
245// exposed in GCC/Clang is, sadly, not really suited for one-file libs.
246// New behavior: if compiled with -msse2, we use SSE2 without any
247// detection; if not, we don't use it at all.
248#define STBI_NO_SIMD
249#endif
250
251#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD)
252// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET
253//
254// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the
255// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant.
256// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not
257// simultaneously enabling "-mstackrealign".
258//
259// See https://github.com/nothings/stb/issues/81 for more information.
260//
261// So default to no SSE2 on 32-bit MinGW. If you've read this far and added
262// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2.
263#define STBI_NO_SIMD
264#endif
265
266#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET))
267#define STBI_SSE2
268#include <emmintrin.h>
269
270#ifdef _MSC_VER
271
272#if _MSC_VER >= 1400 // not VC6
273#include <intrin.h> // __cpuid
274static int stbi__cpuid3(void)
275{
276 int info[4];
277 __cpuid(info,1);
278 return info[3];
279}
280#else
281static int stbi__cpuid3(void)
282{
283 int res;
284 __asm {
285 mov eax,1
286 cpuid
287 mov res,edx
288 }
289 return res;
290}
291#endif
292
293#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name
294
295#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)
296static int stbi__sse2_available(void)
297{
298 int info3 = stbi__cpuid3();
299 return ((info3 >> 26) & 1) != 0;
300}
301#endif
302
303#else // assume GCC-style if not VC++
304#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
305
306#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)
307static int stbi__sse2_available(void)
308{
309 // If we're even attempting to compile this on GCC/Clang, that means
310 // -msse2 is on, which means the compiler is allowed to use SSE2
311 // instructions at will, and so are we.
312 return 1;
313}
314#endif
315
316#endif
317#endif
318
319// ARM NEON
320#if defined(STBI_NO_SIMD) && defined(STBI_NEON)
321#undef STBI_NEON
322#endif
323
324#ifdef STBI_NEON
325#include <arm_neon.h>
326#ifdef _MSC_VER
327#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name
328#else
329#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
330#endif
331#endif
332
333#ifndef STBI_SIMD_ALIGN
334#define STBI_SIMD_ALIGN(type, name) type name
335#endif
336
337#ifndef STBI_MAX_DIMENSIONS
338#define STBI_MAX_DIMENSIONS (1 << 24)
339#endif
340
341///////////////////////////////////////////////
342//
343// stbi__context struct and start_xxx functions
344
345// stbi__context structure is our basic context used by all images, so it
346// contains all the IO context, plus some basic image information
347typedef struct
348{
349 stbi__uint32 img_x, img_y;
350 int img_n, img_out_n;
351
352 stbi_io_callbacks io;
353 void *io_user_data;
354
355 int read_from_callbacks;
356 int buflen;
357 stbi_uc buffer_start[128];
358 int callback_already_read;
359
360 stbi_uc *img_buffer, *img_buffer_end;
361 stbi_uc *img_buffer_original, *img_buffer_original_end;
362} stbi__context;
363
364
365static void stbi__refill_buffer(stbi__context *s);
366
367// initialize a memory-decode context
368static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len)
369{
370 s->io.read = NULL;
371 s->read_from_callbacks = 0;
372 s->callback_already_read = 0;
373 s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer;
374 s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len;
375}
376
377// initialize a callback-based context
378static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user)
379{
380 s->io = *c;
381 s->io_user_data = user;
382 s->buflen = sizeof(s->buffer_start);
383 s->read_from_callbacks = 1;
384 s->callback_already_read = 0;
385 s->img_buffer = s->img_buffer_original = s->buffer_start;
386 stbi__refill_buffer(s);
387 s->img_buffer_original_end = s->img_buffer_end;
388}
389
390#ifndef STBI_NO_STDIO
391
392static int stbi__stdio_read(void *user, char *data, int size)
393{
394 return (int) fread(data,1,size,(FILE*) user);
395}
396
397static void stbi__stdio_skip(void *user, int n)
398{
399 int ch;
400 fseek((FILE*) user, n, SEEK_CUR);
401 ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */
402 if (ch != EOF) {
403 ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */
404 }
405}
406
407static int stbi__stdio_eof(void *user)
408{
409 return feof((FILE*) user) || ferror((FILE *) user);
410}
411
412static stbi_io_callbacks stbi__stdio_callbacks =
413{
414 stbi__stdio_read,
415 stbi__stdio_skip,
416 stbi__stdio_eof,
417};
418
419static void stbi__start_file(stbi__context *s, FILE *f)
420{
421 stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f);
422}
423
424//static void stop_file(stbi__context *s) { }
425
426#endif // !STBI_NO_STDIO
427
428static void stbi__rewind(stbi__context *s)
429{
430 // conceptually rewind SHOULD rewind to the beginning of the stream,
431 // but we just rewind to the beginning of the initial buffer, because
432 // we only use it after doing 'test', which only ever looks at at most 92 bytes
433 s->img_buffer = s->img_buffer_original;
434 s->img_buffer_end = s->img_buffer_original_end;
435}
436
437enum
438{
439 STBI_ORDER_RGB,
440 STBI_ORDER_BGR
441};
442
443typedef struct
444{
445 int bits_per_channel;
446 int num_channels;
447 int channel_order;
448} stbi__result_info;
449
450#ifndef STBI_NO_JPEG
451static int stbi__jpeg_test(stbi__context *s);
452static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
453static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp);
454#endif
455
456#ifndef STBI_NO_PNG
457static int stbi__png_test(stbi__context *s);
458static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
459static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp);
460static int stbi__png_is16(stbi__context *s);
461#endif
462
463#ifndef STBI_NO_BMP
464static int stbi__bmp_test(stbi__context *s);
465static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
466static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp);
467#endif
468
469#ifndef STBI_NO_TGA
470static int stbi__tga_test(stbi__context *s);
471static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
472static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp);
473#endif
474
475#ifndef STBI_NO_PSD
476static int stbi__psd_test(stbi__context *s);
477static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc);
478static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp);
479static int stbi__psd_is16(stbi__context *s);
480#endif
481
482#ifndef STBI_NO_HDR
483static int stbi__hdr_test(stbi__context *s);
484static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
485static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp);
486#endif
487
488#ifndef STBI_NO_PIC
489static int stbi__pic_test(stbi__context *s);
490static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
491static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp);
492#endif
493
494#ifndef STBI_NO_GIF
495static int stbi__gif_test(stbi__context *s);
496static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
497static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp);
498static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp);
499#endif
500
501#ifndef STBI_NO_PNM
502static int stbi__pnm_test(stbi__context *s);
503static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
504static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);
505static int stbi__pnm_is16(stbi__context *s);
506#endif
507
508static
509#ifdef STBI_THREAD_LOCAL
510STBI_THREAD_LOCAL
511#endif
512const char *stbi__g_failure_reason;
513
514STBIDEF const char *stbi_failure_reason(void)
515{
516 return stbi__g_failure_reason;
517}
518
519#ifndef STBI_NO_FAILURE_STRINGS
520static int stbi__err(const char *str)
521{
522 stbi__g_failure_reason = str;
523 return 0;
524}
525#endif
526
527static void *stbi__malloc(size_t size)
528{
529 return STBI_MALLOC(size);
530}
531
532// stb_image uses ints pervasively, including for offset calculations.
533// therefore the largest decoded image size we can support with the
534// current code, even on 64-bit targets, is INT_MAX. this is not a
535// significant limitation for the intended use case.
536//
537// we do, however, need to make sure our size calculations don't
538// overflow. hence a few helper functions for size calculations that
539// multiply integers together, making sure that they're non-negative
540// and no overflow occurs.
541
542// return 1 if the sum is valid, 0 on overflow.
543// negative terms are considered invalid.
544static int stbi__addsizes_valid(int a, int b)
545{
546 if (b < 0) return 0;
547 // now 0 <= b <= INT_MAX, hence also
548 // 0 <= INT_MAX - b <= INTMAX.
549 // And "a + b <= INT_MAX" (which might overflow) is the
550 // same as a <= INT_MAX - b (no overflow)
551 return a <= INT_MAX - b;
552}
553
554// returns 1 if the product is valid, 0 on overflow.
555// negative factors are considered invalid.
556static int stbi__mul2sizes_valid(int a, int b)
557{
558 if (a < 0 || b < 0) return 0;
559 if (b == 0) return 1; // mul-by-0 is always safe
560 // portable way to check for no overflows in a*b
561 return a <= INT_MAX/b;
562}
563
564#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)
565// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow
566static int stbi__mad2sizes_valid(int a, int b, int add)
567{
568 return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add);
569}
570#endif
571
572// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow
573static int stbi__mad3sizes_valid(int a, int b, int c, int add)
574{
575 return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&
576 stbi__addsizes_valid(a*b*c, add);
577}
578
579// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow
580#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM)
581static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add)
582{
583 return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&
584 stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add);
585}
586#endif
587
588#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)
589// mallocs with size overflow checking
590static void *stbi__malloc_mad2(int a, int b, int add)
591{
592 if (!stbi__mad2sizes_valid(a, b, add)) return NULL;
593 return stbi__malloc(a*b + add);
594}
595#endif
596
597static void *stbi__malloc_mad3(int a, int b, int c, int add)
598{
599 if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL;
600 return stbi__malloc(a*b*c + add);
601}
602
603#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM)
604static void *stbi__malloc_mad4(int a, int b, int c, int d, int add)
605{
606 if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL;
607 return stbi__malloc(a*b*c*d + add);
608}
609#endif
610
611// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow.
612static int stbi__addints_valid(int a, int b)
613{
614 if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow
615 if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0.
616 return a <= INT_MAX - b;
617}
618
619// returns 1 if the product of two signed shorts is valid, 0 on overflow.
620static int stbi__mul2shorts_valid(short a, short b)
621{
622 if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow
623 if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid
624 if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN
625 return a >= SHRT_MIN / b;
626}
627
628// stbi__err - error
629// stbi__errpf - error returning pointer to float
630// stbi__errpuc - error returning pointer to unsigned char
631
632#ifdef STBI_NO_FAILURE_STRINGS
633 #define stbi__err(x,y) 0
634#elif defined(STBI_FAILURE_USERMSG)
635 #define stbi__err(x,y) stbi__err(y)
636#else
637 #define stbi__err(x,y) stbi__err(x)
638#endif
639
640#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL))
641#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL))
642
643STBIDEF void stbi_image_free(void *retval_from_stbi_load)
644{
645 STBI_FREE(retval_from_stbi_load);
646}
647
648#ifndef STBI_NO_LINEAR
649static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp);
650#endif
651
652#ifndef STBI_NO_HDR
653static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp);
654#endif
655
656static int stbi__vertically_flip_on_load_global = 0;
657
658STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
659{
660 stbi__vertically_flip_on_load_global = flag_true_if_should_flip;
661}
662
663#ifndef STBI_THREAD_LOCAL
664#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global
665#else
666static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set;
667
668STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip)
669{
670 stbi__vertically_flip_on_load_local = flag_true_if_should_flip;
671 stbi__vertically_flip_on_load_set = 1;
672}
673
674#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \
675 ? stbi__vertically_flip_on_load_local \
676 : stbi__vertically_flip_on_load_global)
677#endif // STBI_THREAD_LOCAL
678
679static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
680{
681 memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields
682 ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed
683 ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order
684 ri->num_channels = 0;
685
686 // test the formats with a very explicit header first (at least a FOURCC
687 // or distinctive magic number first)
688 #ifndef STBI_NO_PNG
689 if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri);
690 #endif
691 #ifndef STBI_NO_BMP
692 if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri);
693 #endif
694 #ifndef STBI_NO_GIF
695 if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri);
696 #endif
697 #ifndef STBI_NO_PSD
698 if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc);
699 #else
700 STBI_NOTUSED(bpc);
701 #endif
702 #ifndef STBI_NO_PIC
703 if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri);
704 #endif
705
706 // then the formats that can end up attempting to load with just 1 or 2
707 // bytes matching expectations; these are prone to false positives, so
708 // try them later
709 #ifndef STBI_NO_JPEG
710 if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri);
711 #endif
712 #ifndef STBI_NO_PNM
713 if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri);
714 #endif
715
716 #ifndef STBI_NO_HDR
717 if (stbi__hdr_test(s)) {
718 float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri);
719 return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp);
720 }
721 #endif
722
723 #ifndef STBI_NO_TGA
724 // test tga last because it's a crappy test!
725 if (stbi__tga_test(s))
726 return stbi__tga_load(s,x,y,comp,req_comp, ri);
727 #endif
728
729 return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt");
730}
731
732static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels)
733{
734 int i;
735 int img_len = w * h * channels;
736 stbi_uc *reduced;
737
738 reduced = (stbi_uc *) stbi__malloc(img_len);
739 if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory");
740
741 for (i = 0; i < img_len; ++i)
742 reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling
743
744 STBI_FREE(orig);
745 return reduced;
746}
747
748static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels)
749{
750 int i;
751 int img_len = w * h * channels;
752 stbi__uint16 *enlarged;
753
754 enlarged = (stbi__uint16 *) stbi__malloc(img_len*2);
755 if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
756
757 for (i = 0; i < img_len; ++i)
758 enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff
759
760 STBI_FREE(orig);
761 return enlarged;
762}
763
764static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel)
765{
766 int row;
767 size_t bytes_per_row = (size_t)w * bytes_per_pixel;
768 stbi_uc temp[2048];
769 stbi_uc *bytes = (stbi_uc *)image;
770
771 for (row = 0; row < (h>>1); row++) {
772 stbi_uc *row0 = bytes + row*bytes_per_row;
773 stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row;
774 // swap row0 with row1
775 size_t bytes_left = bytes_per_row;
776 while (bytes_left) {
777 size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp);
778 memcpy(temp, row0, bytes_copy);
779 memcpy(row0, row1, bytes_copy);
780 memcpy(row1, temp, bytes_copy);
781 row0 += bytes_copy;
782 row1 += bytes_copy;
783 bytes_left -= bytes_copy;
784 }
785 }
786}
787
788#ifndef STBI_NO_GIF
789static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel)
790{
791 int slice;
792 int slice_size = w * h * bytes_per_pixel;
793
794 stbi_uc *bytes = (stbi_uc *)image;
795 for (slice = 0; slice < z; ++slice) {
796 stbi__vertical_flip(bytes, w, h, bytes_per_pixel);
797 bytes += slice_size;
798 }
799}
800#endif
801
802static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
803{
804 stbi__result_info ri;
805 void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8);
806
807 if (result == NULL)
808 return NULL;
809
810 // it is the responsibility of the loaders to make sure we get either 8 or 16 bit.
811 STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);
812
813 if (ri.bits_per_channel != 8) {
814 result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
815 ri.bits_per_channel = 8;
816 }
817
818 // @TODO: move stbi__convert_format to here
819
820 if (stbi__vertically_flip_on_load) {
821 int channels = req_comp ? req_comp : *comp;
822 stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc));
823 }
824
825 return (unsigned char *) result;
826}
827
828static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
829{
830 stbi__result_info ri;
831 void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16);
832
833 if (result == NULL)
834 return NULL;
835
836 // it is the responsibility of the loaders to make sure we get either 8 or 16 bit.
837 STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);
838
839 if (ri.bits_per_channel != 16) {
840 result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
841 ri.bits_per_channel = 16;
842 }
843
844 // @TODO: move stbi__convert_format16 to here
845 // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision
846
847 if (stbi__vertically_flip_on_load) {
848 int channels = req_comp ? req_comp : *comp;
849 stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16));
850 }
851
852 return (stbi__uint16 *) result;
853}
854
855#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR)
856static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)
857{
858 if (stbi__vertically_flip_on_load && result != NULL) {
859 int channels = req_comp ? req_comp : *comp;
860 stbi__vertical_flip(result, *x, *y, channels * sizeof(float));
861 }
862}
863#endif
864
865#ifndef STBI_NO_STDIO
866
867#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
868STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);
869STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default);
870#endif
871
872#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
873STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
874{
875 return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
876}
877#endif
878
879static FILE *stbi__fopen(char const *filename, char const *mode)
880{
881 FILE *f;
882#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
883 wchar_t wMode[64];
884 wchar_t wFilename[1024];
885 if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename)))
886 return 0;
887
888 if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode)))
889 return 0;
890
891#if defined(_MSC_VER) && _MSC_VER >= 1400
892 if (0 != _wfopen_s(&f, wFilename, wMode))
893 f = 0;
894#else
895 f = _wfopen(wFilename, wMode);
896#endif
897
898#elif defined(_MSC_VER) && _MSC_VER >= 1400
899 if (0 != fopen_s(&f, filename, mode))
900 f=0;
901#else
902 f = fopen(filename, mode);
903#endif
904 return f;
905}
906
907
908STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)
909{
910 FILE *f = stbi__fopen(filename, "rb");
911 unsigned char *result;
912 if (!f) return stbi__errpuc("can't fopen", "Unable to open file");
913 result = stbi_load_from_file(f,x,y,comp,req_comp);
914 fclose(f);
915 return result;
916}
917
918STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
919{
920 unsigned char *result;
921 stbi__context s;
922 stbi__start_file(&s,f);
923 result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
924 if (result) {
925 // need to 'unget' all the characters in the IO buffer
926 fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
927 }
928 return result;
929}
930
931STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp)
932{
933 stbi__uint16 *result;
934 stbi__context s;
935 stbi__start_file(&s,f);
936 result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp);
937 if (result) {
938 // need to 'unget' all the characters in the IO buffer
939 fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
940 }
941 return result;
942}
943
944STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp)
945{
946 FILE *f = stbi__fopen(filename, "rb");
947 stbi__uint16 *result;
948 if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file");
949 result = stbi_load_from_file_16(f,x,y,comp,req_comp);
950 fclose(f);
951 return result;
952}
953
954
955#endif //!STBI_NO_STDIO
956
957STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels)
958{
959 stbi__context s;
960 stbi__start_mem(&s,buffer,len);
961 return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);
962}
963
964STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels)
965{
966 stbi__context s;
967 stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user);
968 return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);
969}
970
971STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
972{
973 stbi__context s;
974 stbi__start_mem(&s,buffer,len);
975 return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
976}
977
978STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
979{
980 stbi__context s;
981 stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
982 return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
983}
984
985#ifndef STBI_NO_GIF
986STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
987{
988 unsigned char *result;
989 stbi__context s;
990 stbi__start_mem(&s,buffer,len);
991
992 result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp);
993 if (stbi__vertically_flip_on_load) {
994 stbi__vertical_flip_slices( result, *x, *y, *z, *comp );
995 }
996
997 return result;
998}
999#endif
1000
1001#ifndef STBI_NO_LINEAR
1002static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)
1003{
1004 unsigned char *data;
1005 #ifndef STBI_NO_HDR
1006 if (stbi__hdr_test(s)) {
1007 stbi__result_info ri;
1008 float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri);
1009 if (hdr_data)
1010 stbi__float_postprocess(hdr_data,x,y,comp,req_comp);
1011 return hdr_data;
1012 }
1013 #endif
1014 data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp);
1015 if (data)
1016 return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp);
1017 return stbi__errpf("unknown image type", "Image not of any known type, or corrupt");
1018}
1019
1020STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
1021{
1022 stbi__context s;
1023 stbi__start_mem(&s,buffer,len);
1024 return stbi__loadf_main(&s,x,y,comp,req_comp);
1025}
1026
1027STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
1028{
1029 stbi__context s;
1030 stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
1031 return stbi__loadf_main(&s,x,y,comp,req_comp);
1032}
1033
1034#ifndef STBI_NO_STDIO
1035STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp)
1036{
1037 float *result;
1038 FILE *f = stbi__fopen(filename, "rb");
1039 if (!f) return stbi__errpf("can't fopen", "Unable to open file");
1040 result = stbi_loadf_from_file(f,x,y,comp,req_comp);
1041 fclose(f);
1042 return result;
1043}
1044
1045STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
1046{
1047 stbi__context s;
1048 stbi__start_file(&s,f);
1049 return stbi__loadf_main(&s,x,y,comp,req_comp);
1050}
1051#endif // !STBI_NO_STDIO
1052
1053#endif // !STBI_NO_LINEAR
1054
1055// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is
1056// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always
1057// reports false!
1058
1059STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len)
1060{
1061 #ifndef STBI_NO_HDR
1062 stbi__context s;
1063 stbi__start_mem(&s,buffer,len);
1064 return stbi__hdr_test(&s);
1065 #else
1066 STBI_NOTUSED(buffer);
1067 STBI_NOTUSED(len);
1068 return 0;
1069 #endif
1070}
1071
1072#ifndef STBI_NO_STDIO
1073STBIDEF int stbi_is_hdr (char const *filename)
1074{
1075 FILE *f = stbi__fopen(filename, "rb");
1076 int result=0;
1077 if (f) {
1078 result = stbi_is_hdr_from_file(f);
1079 fclose(f);
1080 }
1081 return result;
1082}
1083
1084STBIDEF int stbi_is_hdr_from_file(FILE *f)
1085{
1086 #ifndef STBI_NO_HDR
1087 long pos = ftell(f);
1088 int res;
1089 stbi__context s;
1090 stbi__start_file(&s,f);
1091 res = stbi__hdr_test(&s);
1092 fseek(f, pos, SEEK_SET);
1093 return res;
1094 #else
1095 STBI_NOTUSED(f);
1096 return 0;
1097 #endif
1098}
1099#endif // !STBI_NO_STDIO
1100
1101STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user)
1102{
1103 #ifndef STBI_NO_HDR
1104 stbi__context s;
1105 stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
1106 return stbi__hdr_test(&s);
1107 #else
1108 STBI_NOTUSED(clbk);
1109 STBI_NOTUSED(user);
1110 return 0;
1111 #endif
1112}
1113
1114#ifndef STBI_NO_LINEAR
1115static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f;
1116
1117STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; }
1118STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; }
1119#endif
1120
1121static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f;
1122
1123STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; }
1124STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; }
1125
1126
1127//////////////////////////////////////////////////////////////////////////////
1128//
1129// Common code used by all image loaders
1130//
1131
1132enum
1133{
1134 STBI__SCAN_load=0,
1135 STBI__SCAN_type,
1136 STBI__SCAN_header
1137};
1138
1139static void stbi__refill_buffer(stbi__context *s)
1140{
1141 int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen);
1142 s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original);
1143 if (n == 0) {
1144 // at end of file, treat same as if from memory, but need to handle case
1145 // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file
1146 s->read_from_callbacks = 0;
1147 s->img_buffer = s->buffer_start;
1148 s->img_buffer_end = s->buffer_start+1;
1149 *s->img_buffer = 0;
1150 } else {
1151 s->img_buffer = s->buffer_start;
1152 s->img_buffer_end = s->buffer_start + n;
1153 }
1154}
1155
1156stbi_inline static stbi_uc stbi__get8(stbi__context *s)
1157{
1158 if (s->img_buffer < s->img_buffer_end)
1159 return *s->img_buffer++;
1160 if (s->read_from_callbacks) {
1161 stbi__refill_buffer(s);
1162 return *s->img_buffer++;
1163 }
1164 return 0;
1165}
1166
1167#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
1168// nothing
1169#else
1170stbi_inline static int stbi__at_eof(stbi__context *s)
1171{
1172 if (s->io.read) {
1173 if (!(s->io.eof)(s->io_user_data)) return 0;
1174 // if feof() is true, check if buffer = end
1175 // special case: we've only got the special 0 character at the end
1176 if (s->read_from_callbacks == 0) return 1;
1177 }
1178
1179 return s->img_buffer >= s->img_buffer_end;
1180}
1181#endif
1182
1183#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC)
1184// nothing
1185#else
1186static void stbi__skip(stbi__context *s, int n)
1187{
1188 if (n == 0) return; // already there!
1189 if (n < 0) {
1190 s->img_buffer = s->img_buffer_end;
1191 return;
1192 }
1193 if (s->io.read) {
1194 int blen = (int) (s->img_buffer_end - s->img_buffer);
1195 if (blen < n) {
1196 s->img_buffer = s->img_buffer_end;
1197 (s->io.skip)(s->io_user_data, n - blen);
1198 return;
1199 }
1200 }
1201 s->img_buffer += n;
1202}
1203#endif
1204
1205#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM)
1206// nothing
1207#else
1208static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
1209{
1210 if (s->io.read) {
1211 int blen = (int) (s->img_buffer_end - s->img_buffer);
1212 if (blen < n) {
1213 int res, count;
1214
1215 memcpy(buffer, s->img_buffer, blen);
1216
1217 count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen);
1218 res = (count == (n-blen));
1219 s->img_buffer = s->img_buffer_end;
1220 return res;
1221 }
1222 }
1223
1224 if (s->img_buffer+n <= s->img_buffer_end) {
1225 memcpy(buffer, s->img_buffer, n);
1226 s->img_buffer += n;
1227 return 1;
1228 } else
1229 return 0;
1230}
1231#endif
1232
1233#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)
1234// nothing
1235#else
1236static int stbi__get16be(stbi__context *s)
1237{
1238 int z = stbi__get8(s);
1239 return (z << 8) + stbi__get8(s);
1240}
1241#endif
1242
1243#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)
1244// nothing
1245#else
1246static stbi__uint32 stbi__get32be(stbi__context *s)
1247{
1248 stbi__uint32 z = stbi__get16be(s);
1249 return (z << 16) + stbi__get16be(s);
1250}
1251#endif
1252
1253#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF)
1254// nothing
1255#else
1256static int stbi__get16le(stbi__context *s)
1257{
1258 int z = stbi__get8(s);
1259 return z + (stbi__get8(s) << 8);
1260}
1261#endif
1262
1263#ifndef STBI_NO_BMP
1264static stbi__uint32 stbi__get32le(stbi__context *s)
1265{
1266 stbi__uint32 z = stbi__get16le(s);
1267 z += (stbi__uint32)stbi__get16le(s) << 16;
1268 return z;
1269}
1270#endif
1271
1272#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings
1273
1274#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
1275// nothing
1276#else
1277//////////////////////////////////////////////////////////////////////////////
1278//
1279// generic converter from built-in img_n to req_comp
1280// individual types do this automatically as much as possible (e.g. jpeg
1281// does all cases internally since it needs to colorspace convert anyway,
1282// and it never has alpha, so very few cases ). png can automatically
1283// interleave an alpha=255 channel, but falls back to this for other cases
1284//
1285// assume data buffer is malloced, so malloc a new one and free that one
1286// only failure mode is malloc failing
1287
1288static stbi_uc stbi__compute_y(int r, int g, int b)
1289{
1290 return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8);
1291}
1292#endif
1293
1294#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
1295// nothing
1296#else
1297static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y)
1298{
1299 int i,j;
1300 unsigned char *good;
1301
1302 if (req_comp == img_n) return data;
1303 STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
1304
1305 good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0);
1306 if (good == NULL) {
1307 STBI_FREE(data);
1308 return stbi__errpuc("outofmem", "Out of memory");
1309 }
1310
1311 for (j=0; j < (int) y; ++j) {
1312 unsigned char *src = data + j * x * img_n ;
1313 unsigned char *dest = good + j * x * req_comp;
1314
1315 #define STBI__COMBO(a,b) ((a)*8+(b))
1316 #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
1317 // convert source image with img_n components to one with req_comp components;
1318 // avoid switch per pixel, so use switch per scanline and massive macros
1319 switch (STBI__COMBO(img_n, req_comp)) {
1320 STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break;
1321 STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
1322 STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break;
1323 STBI__CASE(2,1) { dest[0]=src[0]; } break;
1324 STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
1325 STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break;
1326 STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break;
1327 STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break;
1328 STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break;
1329 STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break;
1330 STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break;
1331 STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break;
1332 default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion");
1333 }
1334 #undef STBI__CASE
1335 }
1336
1337 STBI_FREE(data);
1338 return good;
1339}
1340#endif
1341
1342#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
1343// nothing
1344#else
1345static stbi__uint16 stbi__compute_y_16(int r, int g, int b)
1346{
1347 return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8);
1348}
1349#endif
1350
1351#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
1352// nothing
1353#else
1354static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y)
1355{
1356 int i,j;
1357 stbi__uint16 *good;
1358
1359 if (req_comp == img_n) return data;
1360 STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
1361
1362 good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2);
1363 if (good == NULL) {
1364 STBI_FREE(data);
1365 return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
1366 }
1367
1368 for (j=0; j < (int) y; ++j) {
1369 stbi__uint16 *src = data + j * x * img_n ;
1370 stbi__uint16 *dest = good + j * x * req_comp;
1371
1372 #define STBI__COMBO(a,b) ((a)*8+(b))
1373 #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
1374 // convert source image with img_n components to one with req_comp components;
1375 // avoid switch per pixel, so use switch per scanline and massive macros
1376 switch (STBI__COMBO(img_n, req_comp)) {
1377 STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break;
1378 STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
1379 STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break;
1380 STBI__CASE(2,1) { dest[0]=src[0]; } break;
1381 STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
1382 STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break;
1383 STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break;
1384 STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break;
1385 STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break;
1386 STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break;
1387 STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break;
1388 STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break;
1389 default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion");
1390 }
1391 #undef STBI__CASE
1392 }
1393
1394 STBI_FREE(data);
1395 return good;
1396}
1397#endif
1398
1399#ifndef STBI_NO_LINEAR
1400static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp)
1401{
1402 int i,k,n;
1403 float *output;
1404 if (!data) return NULL;
1405 output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0);
1406 if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); }
1407 // compute number of non-alpha components
1408 if (comp & 1) n = comp; else n = comp-1;
1409 for (i=0; i < x*y; ++i) {
1410 for (k=0; k < n; ++k) {
1411 output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale);
1412 }
1413 }
1414 if (n < comp) {
1415 for (i=0; i < x*y; ++i) {
1416 output[i*comp + n] = data[i*comp + n]/255.0f;
1417 }
1418 }
1419 STBI_FREE(data);
1420 return output;
1421}
1422#endif
1423
1424#ifndef STBI_NO_HDR
1425#define stbi__float2int(x) ((int) (x))
1426static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp)
1427{
1428 int i,k,n;
1429 stbi_uc *output;
1430 if (!data) return NULL;
1431 output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0);
1432 if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); }
1433 // compute number of non-alpha components
1434 if (comp & 1) n = comp; else n = comp-1;
1435 for (i=0; i < x*y; ++i) {
1436 for (k=0; k < n; ++k) {
1437 float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f;
1438 if (z < 0) z = 0;
1439 if (z > 255) z = 255;
1440 output[i*comp + k] = (stbi_uc) stbi__float2int(z);
1441 }
1442 if (k < comp) {
1443 float z = data[i*comp+k] * 255 + 0.5f;
1444 if (z < 0) z = 0;
1445 if (z > 255) z = 255;
1446 output[i*comp + k] = (stbi_uc) stbi__float2int(z);
1447 }
1448 }
1449 STBI_FREE(data);
1450 return output;
1451}
1452#endif
1453
1454//////////////////////////////////////////////////////////////////////////////
1455//
1456// "baseline" JPEG/JFIF decoder
1457//
1458// simple implementation
1459// - doesn't support delayed output of y-dimension
1460// - simple interface (only one output format: 8-bit interleaved RGB)
1461// - doesn't try to recover corrupt jpegs
1462// - doesn't allow partial loading, loading multiple at once
1463// - still fast on x86 (copying globals into locals doesn't help x86)
1464// - allocates lots of intermediate memory (full size of all components)
1465// - non-interleaved case requires this anyway
1466// - allows good upsampling (see next)
1467// high-quality
1468// - upsampled channels are bilinearly interpolated, even across blocks
1469// - quality integer IDCT derived from IJG's 'slow'
1470// performance
1471// - fast huffman; reasonable integer IDCT
1472// - some SIMD kernels for common paths on targets with SSE2/NEON
1473// - uses a lot of intermediate memory, could cache poorly
1474
1475#ifndef STBI_NO_JPEG
1476
1477// huffman decoding acceleration
1478#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache
1479
1480typedef struct
1481{
1482 stbi_uc fast[1 << FAST_BITS];
1483 // weirdly, repacking this into AoS is a 10% speed loss, instead of a win
1484 stbi__uint16 code[256];
1485 stbi_uc values[256];
1486 stbi_uc size[257];
1487 unsigned int maxcode[18];
1488 int delta[17]; // old 'firstsymbol' - old 'firstcode'
1489} stbi__huffman;
1490
1491typedef struct
1492{
1493 stbi__context *s;
1494 stbi__huffman huff_dc[4];
1495 stbi__huffman huff_ac[4];
1496 stbi__uint16 dequant[4][64];
1497 stbi__int16 fast_ac[4][1 << FAST_BITS];
1498
1499// sizes for components, interleaved MCUs
1500 int img_h_max, img_v_max;
1501 int img_mcu_x, img_mcu_y;
1502 int img_mcu_w, img_mcu_h;
1503
1504// definition of jpeg image component
1505 struct
1506 {
1507 int id;
1508 int h,v;
1509 int tq;
1510 int hd,ha;
1511 int dc_pred;
1512
1513 int x,y,w2,h2;
1514 stbi_uc *data;
1515 void *raw_data, *raw_coeff;
1516 stbi_uc *linebuf;
1517 short *coeff; // progressive only
1518 int coeff_w, coeff_h; // number of 8x8 coefficient blocks
1519 } img_comp[4];
1520
1521 stbi__uint32 code_buffer; // jpeg entropy-coded buffer
1522 int code_bits; // number of valid bits
1523 unsigned char marker; // marker seen while filling entropy buffer
1524 int nomore; // flag if we saw a marker so must stop
1525
1526 int progressive;
1527 int spec_start;
1528 int spec_end;
1529 int succ_high;
1530 int succ_low;
1531 int eob_run;
1532 int jfif;
1533 int app14_color_transform; // Adobe APP14 tag
1534 int rgb;
1535
1536 int scan_n, order[4];
1537 int restart_interval, todo;
1538
1539// kernels
1540 void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]);
1541 void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step);
1542 stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs);
1543} stbi__jpeg;
1544
1545static int stbi__build_huffman(stbi__huffman *h, int *count)
1546{
1547 int i,j,k=0;
1548 unsigned int code;
1549 // build size list for each symbol (from JPEG spec)
1550 for (i=0; i < 16; ++i) {
1551 for (j=0; j < count[i]; ++j) {
1552 h->size[k++] = (stbi_uc) (i+1);
1553 if(k >= 257) return stbi__err("bad size list","Corrupt JPEG");
1554 }
1555 }
1556 h->size[k] = 0;
1557
1558 // compute actual symbols (from jpeg spec)
1559 code = 0;
1560 k = 0;
1561 for(j=1; j <= 16; ++j) {
1562 // compute delta to add to code to compute symbol id
1563 h->delta[j] = k - code;
1564 if (h->size[k] == j) {
1565 while (h->size[k] == j)
1566 h->code[k++] = (stbi__uint16) (code++);
1567 if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG");
1568 }
1569 // compute largest code + 1 for this size, preshifted as needed later
1570 h->maxcode[j] = code << (16-j);
1571 code <<= 1;
1572 }
1573 h->maxcode[j] = 0xffffffff;
1574
1575 // build non-spec acceleration table; 255 is flag for not-accelerated
1576 memset(h->fast, 255, 1 << FAST_BITS);
1577 for (i=0; i < k; ++i) {
1578 int s = h->size[i];
1579 if (s <= FAST_BITS) {
1580 int c = h->code[i] << (FAST_BITS-s);
1581 int m = 1 << (FAST_BITS-s);
1582 for (j=0; j < m; ++j) {
1583 h->fast[c+j] = (stbi_uc) i;
1584 }
1585 }
1586 }
1587 return 1;
1588}
1589
1590// build a table that decodes both magnitude and value of small ACs in
1591// one go.
1592static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h)
1593{
1594 int i;
1595 for (i=0; i < (1 << FAST_BITS); ++i) {
1596 stbi_uc fast = h->fast[i];
1597 fast_ac[i] = 0;
1598 if (fast < 255) {
1599 int rs = h->values[fast];
1600 int run = (rs >> 4) & 15;
1601 int magbits = rs & 15;
1602 int len = h->size[fast];
1603
1604 if (magbits && len + magbits <= FAST_BITS) {
1605 // magnitude code followed by receive_extend code
1606 int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits);
1607 int m = 1 << (magbits - 1);
1608 if (k < m) k += (~0U << magbits) + 1;
1609 // if the result is small enough, we can fit it in fast_ac table
1610 if (k >= -128 && k <= 127)
1611 fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits));
1612 }
1613 }
1614 }
1615}
1616
1617static void stbi__grow_buffer_unsafe(stbi__jpeg *j)
1618{
1619 do {
1620 unsigned int b = j->nomore ? 0 : stbi__get8(j->s);
1621 if (b == 0xff) {
1622 int c = stbi__get8(j->s);
1623 while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes
1624 if (c != 0) {
1625 j->marker = (unsigned char) c;
1626 j->nomore = 1;
1627 return;
1628 }
1629 }
1630 j->code_buffer |= b << (24 - j->code_bits);
1631 j->code_bits += 8;
1632 } while (j->code_bits <= 24);
1633}
1634
1635// (1 << n) - 1
1636static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535};
1637
1638// decode a jpeg huffman value from the bitstream
1639stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h)
1640{
1641 unsigned int temp;
1642 int c,k;
1643
1644 if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
1645
1646 // look at the top FAST_BITS and determine what symbol ID it is,
1647 // if the code is <= FAST_BITS
1648 c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
1649 k = h->fast[c];
1650 if (k < 255) {
1651 int s = h->size[k];
1652 if (s > j->code_bits)
1653 return -1;
1654 j->code_buffer <<= s;
1655 j->code_bits -= s;
1656 return h->values[k];
1657 }
1658
1659 // naive test is to shift the code_buffer down so k bits are
1660 // valid, then test against maxcode. To speed this up, we've
1661 // preshifted maxcode left so that it has (16-k) 0s at the
1662 // end; in other words, regardless of the number of bits, it
1663 // wants to be compared against something shifted to have 16;
1664 // that way we don't need to shift inside the loop.
1665 temp = j->code_buffer >> 16;
1666 for (k=FAST_BITS+1 ; ; ++k)
1667 if (temp < h->maxcode[k])
1668 break;
1669 if (k == 17) {
1670 // error! code not found
1671 j->code_bits -= 16;
1672 return -1;
1673 }
1674
1675 if (k > j->code_bits)
1676 return -1;
1677
1678 // convert the huffman code to the symbol id
1679 c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];
1680 if(c < 0 || c >= 256) // symbol id out of bounds!
1681 return -1;
1682 STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]);
1683
1684 // convert the id to a symbol
1685 j->code_bits -= k;
1686 j->code_buffer <<= k;
1687 return h->values[c];
1688}
1689
1690// bias[n] = (-1<<n) + 1
1691static const int stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767};
1692
1693// combined JPEG 'receive' and JPEG 'extend', since baseline
1694// always extends everything it receives.
1695stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)
1696{
1697 unsigned int k;
1698 int sgn;
1699 if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
1700 if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing
1701
1702 sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative)
1703 k = stbi_lrot(j->code_buffer, n);
1704 j->code_buffer = k & ~stbi__bmask[n];
1705 k &= stbi__bmask[n];
1706 j->code_bits -= n;
1707 return k + (stbi__jbias[n] & (sgn - 1));
1708}
1709
1710// get some unsigned bits
1711stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n)
1712{
1713 unsigned int k;
1714 if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
1715 if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing
1716 k = stbi_lrot(j->code_buffer, n);
1717 j->code_buffer = k & ~stbi__bmask[n];
1718 k &= stbi__bmask[n];
1719 j->code_bits -= n;
1720 return k;
1721}
1722
1723stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j)
1724{
1725 unsigned int k;
1726 if (j->code_bits < 1) stbi__grow_buffer_unsafe(j);
1727 if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing
1728 k = j->code_buffer;
1729 j->code_buffer <<= 1;
1730 --j->code_bits;
1731 return k & 0x80000000;
1732}
1733
1734// given a value that's at position X in the zigzag stream,
1735// where does it appear in the 8x8 matrix coded as row-major?
1736static const stbi_uc stbi__jpeg_dezigzag[64+15] =
1737{
1738 0, 1, 8, 16, 9, 2, 3, 10,
1739 17, 24, 32, 25, 18, 11, 4, 5,
1740 12, 19, 26, 33, 40, 48, 41, 34,
1741 27, 20, 13, 6, 7, 14, 21, 28,
1742 35, 42, 49, 56, 57, 50, 43, 36,
1743 29, 22, 15, 23, 30, 37, 44, 51,
1744 58, 59, 52, 45, 38, 31, 39, 46,
1745 53, 60, 61, 54, 47, 55, 62, 63,
1746 // let corrupt input sample past end
1747 63, 63, 63, 63, 63, 63, 63, 63,
1748 63, 63, 63, 63, 63, 63, 63
1749};
1750
1751// decode one 64-entry block--
1752static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant)
1753{
1754 int diff,dc,k;
1755 int t;
1756
1757 if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
1758 t = stbi__jpeg_huff_decode(j, hdc);
1759 if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG");
1760
1761 // 0 all the ac values now so we can do it 32-bits at a time
1762 memset(data,0,64*sizeof(data[0]));
1763
1764 diff = t ? stbi__extend_receive(j, t) : 0;
1765 if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG");
1766 dc = j->img_comp[b].dc_pred + diff;
1767 j->img_comp[b].dc_pred = dc;
1768 if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
1769 data[0] = (short) (dc * dequant[0]);
1770
1771 // decode AC components, see JPEG spec
1772 k = 1;
1773 do {
1774 unsigned int zig;
1775 int c,r,s;
1776 if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
1777 c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
1778 r = fac[c];
1779 if (r) { // fast-AC path
1780 k += (r >> 4) & 15; // run
1781 s = r & 15; // combined length
1782 if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available");
1783 j->code_buffer <<= s;
1784 j->code_bits -= s;
1785 // decode into unzigzag'd location
1786 zig = stbi__jpeg_dezigzag[k++];
1787 data[zig] = (short) ((r >> 8) * dequant[zig]);
1788 } else {
1789 int rs = stbi__jpeg_huff_decode(j, hac);
1790 if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
1791 s = rs & 15;
1792 r = rs >> 4;
1793 if (s == 0) {
1794 if (rs != 0xf0) break; // end block
1795 k += 16;
1796 } else {
1797 k += r;
1798 // decode into unzigzag'd location
1799 zig = stbi__jpeg_dezigzag[k++];
1800 data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]);
1801 }
1802 }
1803 } while (k < 64);
1804 return 1;
1805}
1806
1807static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b)
1808{
1809 int diff,dc;
1810 int t;
1811 if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
1812
1813 if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
1814
1815 if (j->succ_high == 0) {
1816 // first scan for DC coefficient, must be first
1817 memset(data,0,64*sizeof(data[0])); // 0 all the ac values now
1818 t = stbi__jpeg_huff_decode(j, hdc);
1819 if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
1820 diff = t ? stbi__extend_receive(j, t) : 0;
1821
1822 if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG");
1823 dc = j->img_comp[b].dc_pred + diff;
1824 j->img_comp[b].dc_pred = dc;
1825 if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
1826 data[0] = (short) (dc * (1 << j->succ_low));
1827 } else {
1828 // refinement scan for DC coefficient
1829 if (stbi__jpeg_get_bit(j))
1830 data[0] += (short) (1 << j->succ_low);
1831 }
1832 return 1;
1833}
1834
1835// @OPTIMIZE: store non-zigzagged during the decode passes,
1836// and only de-zigzag when dequantizing
1837static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac)
1838{
1839 int k;
1840 if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
1841
1842 if (j->succ_high == 0) {
1843 int shift = j->succ_low;
1844
1845 if (j->eob_run) {
1846 --j->eob_run;
1847 return 1;
1848 }
1849
1850 k = j->spec_start;
1851 do {
1852 unsigned int zig;
1853 int c,r,s;
1854 if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
1855 c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
1856 r = fac[c];
1857 if (r) { // fast-AC path
1858 k += (r >> 4) & 15; // run
1859 s = r & 15; // combined length
1860 if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available");
1861 j->code_buffer <<= s;
1862 j->code_bits -= s;
1863 zig = stbi__jpeg_dezigzag[k++];
1864 data[zig] = (short) ((r >> 8) * (1 << shift));
1865 } else {
1866 int rs = stbi__jpeg_huff_decode(j, hac);
1867 if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
1868 s = rs & 15;
1869 r = rs >> 4;
1870 if (s == 0) {
1871 if (r < 15) {
1872 j->eob_run = (1 << r);
1873 if (r)
1874 j->eob_run += stbi__jpeg_get_bits(j, r);
1875 --j->eob_run;
1876 break;
1877 }
1878 k += 16;
1879 } else {
1880 k += r;
1881 zig = stbi__jpeg_dezigzag[k++];
1882 data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift));
1883 }
1884 }
1885 } while (k <= j->spec_end);
1886 } else {
1887 // refinement scan for these AC coefficients
1888
1889 short bit = (short) (1 << j->succ_low);
1890
1891 if (j->eob_run) {
1892 --j->eob_run;
1893 for (k = j->spec_start; k <= j->spec_end; ++k) {
1894 short *p = &data[stbi__jpeg_dezigzag[k]];
1895 if (*p != 0)
1896 if (stbi__jpeg_get_bit(j))
1897 if ((*p & bit)==0) {
1898 if (*p > 0)
1899 *p += bit;
1900 else
1901 *p -= bit;
1902 }
1903 }
1904 } else {
1905 k = j->spec_start;
1906 do {
1907 int r,s;
1908 int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh
1909 if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
1910 s = rs & 15;
1911 r = rs >> 4;
1912 if (s == 0) {
1913 if (r < 15) {
1914 j->eob_run = (1 << r) - 1;
1915 if (r)
1916 j->eob_run += stbi__jpeg_get_bits(j, r);
1917 r = 64; // force end of block
1918 } else {
1919 // r=15 s=0 should write 16 0s, so we just do
1920 // a run of 15 0s and then write s (which is 0),
1921 // so we don't have to do anything special here
1922 }
1923 } else {
1924 if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG");
1925 // sign bit
1926 if (stbi__jpeg_get_bit(j))
1927 s = bit;
1928 else
1929 s = -bit;
1930 }
1931
1932 // advance by r
1933 while (k <= j->spec_end) {
1934 short *p = &data[stbi__jpeg_dezigzag[k++]];
1935 if (*p != 0) {
1936 if (stbi__jpeg_get_bit(j))
1937 if ((*p & bit)==0) {
1938 if (*p > 0)
1939 *p += bit;
1940 else
1941 *p -= bit;
1942 }
1943 } else {
1944 if (r == 0) {
1945 *p = (short) s;
1946 break;
1947 }
1948 --r;
1949 }
1950 }
1951 } while (k <= j->spec_end);
1952 }
1953 }
1954 return 1;
1955}
1956
1957// take a -128..127 value and stbi__clamp it and convert to 0..255
1958stbi_inline static stbi_uc stbi__clamp(int x)
1959{
1960 // trick to use a single test to catch both cases
1961 if ((unsigned int) x > 255) {
1962 if (x < 0) return 0;
1963 if (x > 255) return 255;
1964 }
1965 return (stbi_uc) x;
1966}
1967
1968#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5)))
1969#define stbi__fsh(x) ((x) * 4096)
1970
1971// derived from jidctint -- DCT_ISLOW
1972#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \
1973 int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \
1974 p2 = s2; \
1975 p3 = s6; \
1976 p1 = (p2+p3) * stbi__f2f(0.5411961f); \
1977 t2 = p1 + p3*stbi__f2f(-1.847759065f); \
1978 t3 = p1 + p2*stbi__f2f( 0.765366865f); \
1979 p2 = s0; \
1980 p3 = s4; \
1981 t0 = stbi__fsh(p2+p3); \
1982 t1 = stbi__fsh(p2-p3); \
1983 x0 = t0+t3; \
1984 x3 = t0-t3; \
1985 x1 = t1+t2; \
1986 x2 = t1-t2; \
1987 t0 = s7; \
1988 t1 = s5; \
1989 t2 = s3; \
1990 t3 = s1; \
1991 p3 = t0+t2; \
1992 p4 = t1+t3; \
1993 p1 = t0+t3; \
1994 p2 = t1+t2; \
1995 p5 = (p3+p4)*stbi__f2f( 1.175875602f); \
1996 t0 = t0*stbi__f2f( 0.298631336f); \
1997 t1 = t1*stbi__f2f( 2.053119869f); \
1998 t2 = t2*stbi__f2f( 3.072711026f); \
1999 t3 = t3*stbi__f2f( 1.501321110f); \
2000 p1 = p5 + p1*stbi__f2f(-0.899976223f); \
2001 p2 = p5 + p2*stbi__f2f(-2.562915447f); \
2002 p3 = p3*stbi__f2f(-1.961570560f); \
2003 p4 = p4*stbi__f2f(-0.390180644f); \
2004 t3 += p1+p4; \
2005 t2 += p2+p3; \
2006 t1 += p2+p4; \
2007 t0 += p1+p3;
2008
2009static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64])
2010{
2011 int i,val[64],*v=val;
2012 stbi_uc *o;
2013 short *d = data;
2014
2015 // columns
2016 for (i=0; i < 8; ++i,++d, ++v) {
2017 // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing
2018 if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0
2019 && d[40]==0 && d[48]==0 && d[56]==0) {
2020 // no shortcut 0 seconds
2021 // (1|2|3|4|5|6|7)==0 0 seconds
2022 // all separate -0.047 seconds
2023 // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds
2024 int dcterm = d[0]*4;
2025 v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;
2026 } else {
2027 STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56])
2028 // constants scaled things up by 1<<12; let's bring them back
2029 // down, but keep 2 extra bits of precision
2030 x0 += 512; x1 += 512; x2 += 512; x3 += 512;
2031 v[ 0] = (x0+t3) >> 10;
2032 v[56] = (x0-t3) >> 10;
2033 v[ 8] = (x1+t2) >> 10;
2034 v[48] = (x1-t2) >> 10;
2035 v[16] = (x2+t1) >> 10;
2036 v[40] = (x2-t1) >> 10;
2037 v[24] = (x3+t0) >> 10;
2038 v[32] = (x3-t0) >> 10;
2039 }
2040 }
2041
2042 for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) {
2043 // no fast case since the first 1D IDCT spread components out
2044 STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7])
2045 // constants scaled things up by 1<<12, plus we had 1<<2 from first
2046 // loop, plus horizontal and vertical each scale by sqrt(8) so together
2047 // we've got an extra 1<<3, so 1<<17 total we need to remove.
2048 // so we want to round that, which means adding 0.5 * 1<<17,
2049 // aka 65536. Also, we'll end up with -128 to 127 that we want
2050 // to encode as 0..255 by adding 128, so we'll add that before the shift
2051 x0 += 65536 + (128<<17);
2052 x1 += 65536 + (128<<17);
2053 x2 += 65536 + (128<<17);
2054 x3 += 65536 + (128<<17);
2055 // tried computing the shifts into temps, or'ing the temps to see
2056 // if any were out of range, but that was slower
2057 o[0] = stbi__clamp((x0+t3) >> 17);
2058 o[7] = stbi__clamp((x0-t3) >> 17);
2059 o[1] = stbi__clamp((x1+t2) >> 17);
2060 o[6] = stbi__clamp((x1-t2) >> 17);
2061 o[2] = stbi__clamp((x2+t1) >> 17);
2062 o[5] = stbi__clamp((x2-t1) >> 17);
2063 o[3] = stbi__clamp((x3+t0) >> 17);
2064 o[4] = stbi__clamp((x3-t0) >> 17);
2065 }
2066}
2067
2068#ifdef STBI_SSE2
2069// sse2 integer IDCT. not the fastest possible implementation but it
2070// produces bit-identical results to the generic C version so it's
2071// fully "transparent".
2072static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
2073{
2074 // This is constructed to match our regular (generic) integer IDCT exactly.
2075 __m128i row0, row1, row2, row3, row4, row5, row6, row7;
2076 __m128i tmp;
2077
2078 // dot product constant: even elems=x, odd elems=y
2079 #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y))
2080
2081 // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit)
2082 // out(1) = c1[even]*x + c1[odd]*y
2083 #define dct_rot(out0,out1, x,y,c0,c1) \
2084 __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \
2085 __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \
2086 __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \
2087 __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \
2088 __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \
2089 __m128i out1##_h = _mm_madd_epi16(c0##hi, c1)
2090
2091 // out = in << 12 (in 16-bit, out 32-bit)
2092 #define dct_widen(out, in) \
2093 __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \
2094 __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4)
2095
2096 // wide add
2097 #define dct_wadd(out, a, b) \
2098 __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \
2099 __m128i out##_h = _mm_add_epi32(a##_h, b##_h)
2100
2101 // wide sub
2102 #define dct_wsub(out, a, b) \
2103 __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \
2104 __m128i out##_h = _mm_sub_epi32(a##_h, b##_h)
2105
2106 // butterfly a/b, add bias, then shift by "s" and pack
2107 #define dct_bfly32o(out0, out1, a,b,bias,s) \
2108 { \
2109 __m128i abiased_l = _mm_add_epi32(a##_l, bias); \
2110 __m128i abiased_h = _mm_add_epi32(a##_h, bias); \
2111 dct_wadd(sum, abiased, b); \
2112 dct_wsub(dif, abiased, b); \
2113 out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \
2114 out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \
2115 }
2116
2117 // 8-bit interleave step (for transposes)
2118 #define dct_interleave8(a, b) \
2119 tmp = a; \
2120 a = _mm_unpacklo_epi8(a, b); \
2121 b = _mm_unpackhi_epi8(tmp, b)
2122
2123 // 16-bit interleave step (for transposes)
2124 #define dct_interleave16(a, b) \
2125 tmp = a; \
2126 a = _mm_unpacklo_epi16(a, b); \
2127 b = _mm_unpackhi_epi16(tmp, b)
2128
2129 #define dct_pass(bias,shift) \
2130 { \
2131 /* even part */ \
2132 dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \
2133 __m128i sum04 = _mm_add_epi16(row0, row4); \
2134 __m128i dif04 = _mm_sub_epi16(row0, row4); \
2135 dct_widen(t0e, sum04); \
2136 dct_widen(t1e, dif04); \
2137 dct_wadd(x0, t0e, t3e); \
2138 dct_wsub(x3, t0e, t3e); \
2139 dct_wadd(x1, t1e, t2e); \
2140 dct_wsub(x2, t1e, t2e); \
2141 /* odd part */ \
2142 dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \
2143 dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \
2144 __m128i sum17 = _mm_add_epi16(row1, row7); \
2145 __m128i sum35 = _mm_add_epi16(row3, row5); \
2146 dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \
2147 dct_wadd(x4, y0o, y4o); \
2148 dct_wadd(x5, y1o, y5o); \
2149 dct_wadd(x6, y2o, y5o); \
2150 dct_wadd(x7, y3o, y4o); \
2151 dct_bfly32o(row0,row7, x0,x7,bias,shift); \
2152 dct_bfly32o(row1,row6, x1,x6,bias,shift); \
2153 dct_bfly32o(row2,row5, x2,x5,bias,shift); \
2154 dct_bfly32o(row3,row4, x3,x4,bias,shift); \
2155 }
2156
2157 __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f));
2158 __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f));
2159 __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f));
2160 __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f));
2161 __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f));
2162 __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f));
2163 __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f));
2164 __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f));
2165
2166 // rounding biases in column/row passes, see stbi__idct_block for explanation.
2167 __m128i bias_0 = _mm_set1_epi32(512);
2168 __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17));
2169
2170 // load
2171 row0 = _mm_load_si128((const __m128i *) (data + 0*8));
2172 row1 = _mm_load_si128((const __m128i *) (data + 1*8));
2173 row2 = _mm_load_si128((const __m128i *) (data + 2*8));
2174 row3 = _mm_load_si128((const __m128i *) (data + 3*8));
2175 row4 = _mm_load_si128((const __m128i *) (data + 4*8));
2176 row5 = _mm_load_si128((const __m128i *) (data + 5*8));
2177 row6 = _mm_load_si128((const __m128i *) (data + 6*8));
2178 row7 = _mm_load_si128((const __m128i *) (data + 7*8));
2179
2180 // column pass
2181 dct_pass(bias_0, 10);
2182
2183 {
2184 // 16bit 8x8 transpose pass 1
2185 dct_interleave16(row0, row4);
2186 dct_interleave16(row1, row5);
2187 dct_interleave16(row2, row6);
2188 dct_interleave16(row3, row7);
2189
2190 // transpose pass 2
2191 dct_interleave16(row0, row2);
2192 dct_interleave16(row1, row3);
2193 dct_interleave16(row4, row6);
2194 dct_interleave16(row5, row7);
2195
2196 // transpose pass 3
2197 dct_interleave16(row0, row1);
2198 dct_interleave16(row2, row3);
2199 dct_interleave16(row4, row5);
2200 dct_interleave16(row6, row7);
2201 }
2202
2203 // row pass
2204 dct_pass(bias_1, 17);
2205
2206 {
2207 // pack
2208 __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7
2209 __m128i p1 = _mm_packus_epi16(row2, row3);
2210 __m128i p2 = _mm_packus_epi16(row4, row5);
2211 __m128i p3 = _mm_packus_epi16(row6, row7);
2212
2213 // 8bit 8x8 transpose pass 1
2214 dct_interleave8(p0, p2); // a0e0a1e1...
2215 dct_interleave8(p1, p3); // c0g0c1g1...
2216
2217 // transpose pass 2
2218 dct_interleave8(p0, p1); // a0c0e0g0...
2219 dct_interleave8(p2, p3); // b0d0f0h0...
2220
2221 // transpose pass 3
2222 dct_interleave8(p0, p2); // a0b0c0d0...
2223 dct_interleave8(p1, p3); // a4b4c4d4...
2224
2225 // store
2226 _mm_storel_epi64((__m128i *) out, p0); out += out_stride;
2227 _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride;
2228 _mm_storel_epi64((__m128i *) out, p2); out += out_stride;
2229 _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride;
2230 _mm_storel_epi64((__m128i *) out, p1); out += out_stride;
2231 _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride;
2232 _mm_storel_epi64((__m128i *) out, p3); out += out_stride;
2233 _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e));
2234 }
2235
2236#undef dct_const
2237#undef dct_rot
2238#undef dct_widen
2239#undef dct_wadd
2240#undef dct_wsub
2241#undef dct_bfly32o
2242#undef dct_interleave8
2243#undef dct_interleave16
2244#undef dct_pass
2245}
2246
2247#endif // STBI_SSE2
2248
2249#ifdef STBI_NEON
2250
2251// NEON integer IDCT. should produce bit-identical
2252// results to the generic C version.
2253static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
2254{
2255 int16x8_t row0, row1, row2, row3, row4, row5, row6, row7;
2256
2257 int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f));
2258 int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f));
2259 int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f));
2260 int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f));
2261 int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f));
2262 int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f));
2263 int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f));
2264 int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f));
2265 int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f));
2266 int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f));
2267 int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f));
2268 int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f));
2269
2270#define dct_long_mul(out, inq, coeff) \
2271 int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \
2272 int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff)
2273
2274#define dct_long_mac(out, acc, inq, coeff) \
2275 int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \
2276 int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff)
2277
2278#define dct_widen(out, inq) \
2279 int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \
2280 int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12)
2281
2282// wide add
2283#define dct_wadd(out, a, b) \
2284 int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \
2285 int32x4_t out##_h = vaddq_s32(a##_h, b##_h)
2286
2287// wide sub
2288#define dct_wsub(out, a, b) \
2289 int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \
2290 int32x4_t out##_h = vsubq_s32(a##_h, b##_h)
2291
2292// butterfly a/b, then shift using "shiftop" by "s" and pack
2293#define dct_bfly32o(out0,out1, a,b,shiftop,s) \
2294 { \
2295 dct_wadd(sum, a, b); \
2296 dct_wsub(dif, a, b); \
2297 out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \
2298 out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \
2299 }
2300
2301#define dct_pass(shiftop, shift) \
2302 { \
2303 /* even part */ \
2304 int16x8_t sum26 = vaddq_s16(row2, row6); \
2305 dct_long_mul(p1e, sum26, rot0_0); \
2306 dct_long_mac(t2e, p1e, row6, rot0_1); \
2307 dct_long_mac(t3e, p1e, row2, rot0_2); \
2308 int16x8_t sum04 = vaddq_s16(row0, row4); \
2309 int16x8_t dif04 = vsubq_s16(row0, row4); \
2310 dct_widen(t0e, sum04); \
2311 dct_widen(t1e, dif04); \
2312 dct_wadd(x0, t0e, t3e); \
2313 dct_wsub(x3, t0e, t3e); \
2314 dct_wadd(x1, t1e, t2e); \
2315 dct_wsub(x2, t1e, t2e); \
2316 /* odd part */ \
2317 int16x8_t sum15 = vaddq_s16(row1, row5); \
2318 int16x8_t sum17 = vaddq_s16(row1, row7); \
2319 int16x8_t sum35 = vaddq_s16(row3, row5); \
2320 int16x8_t sum37 = vaddq_s16(row3, row7); \
2321 int16x8_t sumodd = vaddq_s16(sum17, sum35); \
2322 dct_long_mul(p5o, sumodd, rot1_0); \
2323 dct_long_mac(p1o, p5o, sum17, rot1_1); \
2324 dct_long_mac(p2o, p5o, sum35, rot1_2); \
2325 dct_long_mul(p3o, sum37, rot2_0); \
2326 dct_long_mul(p4o, sum15, rot2_1); \
2327 dct_wadd(sump13o, p1o, p3o); \
2328 dct_wadd(sump24o, p2o, p4o); \
2329 dct_wadd(sump23o, p2o, p3o); \
2330 dct_wadd(sump14o, p1o, p4o); \
2331 dct_long_mac(x4, sump13o, row7, rot3_0); \
2332 dct_long_mac(x5, sump24o, row5, rot3_1); \
2333 dct_long_mac(x6, sump23o, row3, rot3_2); \
2334 dct_long_mac(x7, sump14o, row1, rot3_3); \
2335 dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \
2336 dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \
2337 dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \
2338 dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \
2339 }
2340
2341 // load
2342 row0 = vld1q_s16(data + 0*8);
2343 row1 = vld1q_s16(data + 1*8);
2344 row2 = vld1q_s16(data + 2*8);
2345 row3 = vld1q_s16(data + 3*8);
2346 row4 = vld1q_s16(data + 4*8);
2347 row5 = vld1q_s16(data + 5*8);
2348 row6 = vld1q_s16(data + 6*8);
2349 row7 = vld1q_s16(data + 7*8);
2350
2351 // add DC bias
2352 row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0));
2353
2354 // column pass
2355 dct_pass(vrshrn_n_s32, 10);
2356
2357 // 16bit 8x8 transpose
2358 {
2359// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively.
2360// whether compilers actually get this is another story, sadly.
2361#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; }
2362#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); }
2363#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); }
2364
2365 // pass 1
2366 dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6
2367 dct_trn16(row2, row3);
2368 dct_trn16(row4, row5);
2369 dct_trn16(row6, row7);
2370
2371 // pass 2
2372 dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4
2373 dct_trn32(row1, row3);
2374 dct_trn32(row4, row6);
2375 dct_trn32(row5, row7);
2376
2377 // pass 3
2378 dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0
2379 dct_trn64(row1, row5);
2380 dct_trn64(row2, row6);
2381 dct_trn64(row3, row7);
2382
2383#undef dct_trn16
2384#undef dct_trn32
2385#undef dct_trn64
2386 }
2387
2388 // row pass
2389 // vrshrn_n_s32 only supports shifts up to 16, we need
2390 // 17. so do a non-rounding shift of 16 first then follow
2391 // up with a rounding shift by 1.
2392 dct_pass(vshrn_n_s32, 16);
2393
2394 {
2395 // pack and round
2396 uint8x8_t p0 = vqrshrun_n_s16(row0, 1);
2397 uint8x8_t p1 = vqrshrun_n_s16(row1, 1);
2398 uint8x8_t p2 = vqrshrun_n_s16(row2, 1);
2399 uint8x8_t p3 = vqrshrun_n_s16(row3, 1);
2400 uint8x8_t p4 = vqrshrun_n_s16(row4, 1);
2401 uint8x8_t p5 = vqrshrun_n_s16(row5, 1);
2402 uint8x8_t p6 = vqrshrun_n_s16(row6, 1);
2403 uint8x8_t p7 = vqrshrun_n_s16(row7, 1);
2404
2405 // again, these can translate into one instruction, but often don't.
2406#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; }
2407#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); }
2408#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); }
2409
2410 // sadly can't use interleaved stores here since we only write
2411 // 8 bytes to each scan line!
2412
2413 // 8x8 8-bit transpose pass 1
2414 dct_trn8_8(p0, p1);
2415 dct_trn8_8(p2, p3);
2416 dct_trn8_8(p4, p5);
2417 dct_trn8_8(p6, p7);
2418
2419 // pass 2
2420 dct_trn8_16(p0, p2);
2421 dct_trn8_16(p1, p3);
2422 dct_trn8_16(p4, p6);
2423 dct_trn8_16(p5, p7);
2424
2425 // pass 3
2426 dct_trn8_32(p0, p4);
2427 dct_trn8_32(p1, p5);
2428 dct_trn8_32(p2, p6);
2429 dct_trn8_32(p3, p7);
2430
2431 // store
2432 vst1_u8(out, p0); out += out_stride;
2433 vst1_u8(out, p1); out += out_stride;
2434 vst1_u8(out, p2); out += out_stride;
2435 vst1_u8(out, p3); out += out_stride;
2436 vst1_u8(out, p4); out += out_stride;
2437 vst1_u8(out, p5); out += out_stride;
2438 vst1_u8(out, p6); out += out_stride;
2439 vst1_u8(out, p7);
2440
2441#undef dct_trn8_8
2442#undef dct_trn8_16
2443#undef dct_trn8_32
2444 }
2445
2446#undef dct_long_mul
2447#undef dct_long_mac
2448#undef dct_widen
2449#undef dct_wadd
2450#undef dct_wsub
2451#undef dct_bfly32o
2452#undef dct_pass
2453}
2454
2455#endif // STBI_NEON
2456
2457#define STBI__MARKER_none 0xff
2458// if there's a pending marker from the entropy stream, return that
2459// otherwise, fetch from the stream and get a marker. if there's no
2460// marker, return 0xff, which is never a valid marker value
2461static stbi_uc stbi__get_marker(stbi__jpeg *j)
2462{
2463 stbi_uc x;
2464 if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; }
2465 x = stbi__get8(j->s);
2466 if (x != 0xff) return STBI__MARKER_none;
2467 while (x == 0xff)
2468 x = stbi__get8(j->s); // consume repeated 0xff fill bytes
2469 return x;
2470}
2471
2472// in each scan, we'll have scan_n components, and the order
2473// of the components is specified by order[]
2474#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7)
2475
2476// after a restart interval, stbi__jpeg_reset the entropy decoder and
2477// the dc prediction
2478static void stbi__jpeg_reset(stbi__jpeg *j)
2479{
2480 j->code_bits = 0;
2481 j->code_buffer = 0;
2482 j->nomore = 0;
2483 j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0;
2484 j->marker = STBI__MARKER_none;
2485 j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff;
2486 j->eob_run = 0;
2487 // no more than 1<<31 MCUs if no restart_interal? that's plenty safe,
2488 // since we don't even allow 1<<30 pixels
2489}
2490
2491static int stbi__parse_entropy_coded_data(stbi__jpeg *z)
2492{
2493 stbi__jpeg_reset(z);
2494 if (!z->progressive) {
2495 if (z->scan_n == 1) {
2496 int i,j;
2497 STBI_SIMD_ALIGN(short, data[64]);
2498 int n = z->order[0];
2499 // non-interleaved data, we just need to process one block at a time,
2500 // in trivial scanline order
2501 // number of blocks to do just depends on how many actual "pixels" this
2502 // component has, independent of interleaved MCU blocking and such
2503 int w = (z->img_comp[n].x+7) >> 3;
2504 int h = (z->img_comp[n].y+7) >> 3;
2505 for (j=0; j < h; ++j) {
2506 for (i=0; i < w; ++i) {
2507 int ha = z->img_comp[n].ha;
2508 if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;
2509 z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
2510 // every data block is an MCU, so countdown the restart interval
2511 if (--z->todo <= 0) {
2512 if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
2513 // if it's NOT a restart, then just bail, so we get corrupt data
2514 // rather than no data
2515 if (!STBI__RESTART(z->marker)) return 1;
2516 stbi__jpeg_reset(z);
2517 }
2518 }
2519 }
2520 return 1;
2521 } else { // interleaved
2522 int i,j,k,x,y;
2523 STBI_SIMD_ALIGN(short, data[64]);
2524 for (j=0; j < z->img_mcu_y; ++j) {
2525 for (i=0; i < z->img_mcu_x; ++i) {
2526 // scan an interleaved mcu... process scan_n components in order
2527 for (k=0; k < z->scan_n; ++k) {
2528 int n = z->order[k];
2529 // scan out an mcu's worth of this component; that's just determined
2530 // by the basic H and V specified for the component
2531 for (y=0; y < z->img_comp[n].v; ++y) {
2532 for (x=0; x < z->img_comp[n].h; ++x) {
2533 int x2 = (i*z->img_comp[n].h + x)*8;
2534 int y2 = (j*z->img_comp[n].v + y)*8;
2535 int ha = z->img_comp[n].ha;
2536 if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;
2537 z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data);
2538 }
2539 }
2540 }
2541 // after all interleaved components, that's an interleaved MCU,
2542 // so now count down the restart interval
2543 if (--z->todo <= 0) {
2544 if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
2545 if (!STBI__RESTART(z->marker)) return 1;
2546 stbi__jpeg_reset(z);
2547 }
2548 }
2549 }
2550 return 1;
2551 }
2552 } else {
2553 if (z->scan_n == 1) {
2554 int i,j;
2555 int n = z->order[0];
2556 // non-interleaved data, we just need to process one block at a time,
2557 // in trivial scanline order
2558 // number of blocks to do just depends on how many actual "pixels" this
2559 // component has, independent of interleaved MCU blocking and such
2560 int w = (z->img_comp[n].x+7) >> 3;
2561 int h = (z->img_comp[n].y+7) >> 3;
2562 for (j=0; j < h; ++j) {
2563 for (i=0; i < w; ++i) {
2564 short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
2565 if (z->spec_start == 0) {
2566 if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
2567 return 0;
2568 } else {
2569 int ha = z->img_comp[n].ha;
2570 if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha]))
2571 return 0;
2572 }
2573 // every data block is an MCU, so countdown the restart interval
2574 if (--z->todo <= 0) {
2575 if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
2576 if (!STBI__RESTART(z->marker)) return 1;
2577 stbi__jpeg_reset(z);
2578 }
2579 }
2580 }
2581 return 1;
2582 } else { // interleaved
2583 int i,j,k,x,y;
2584 for (j=0; j < z->img_mcu_y; ++j) {
2585 for (i=0; i < z->img_mcu_x; ++i) {
2586 // scan an interleaved mcu... process scan_n components in order
2587 for (k=0; k < z->scan_n; ++k) {
2588 int n = z->order[k];
2589 // scan out an mcu's worth of this component; that's just determined
2590 // by the basic H and V specified for the component
2591 for (y=0; y < z->img_comp[n].v; ++y) {
2592 for (x=0; x < z->img_comp[n].h; ++x) {
2593 int x2 = (i*z->img_comp[n].h + x);
2594 int y2 = (j*z->img_comp[n].v + y);
2595 short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w);
2596 if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
2597 return 0;
2598 }
2599 }
2600 }
2601 // after all interleaved components, that's an interleaved MCU,
2602 // so now count down the restart interval
2603 if (--z->todo <= 0) {
2604 if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
2605 if (!STBI__RESTART(z->marker)) return 1;
2606 stbi__jpeg_reset(z);
2607 }
2608 }
2609 }
2610 return 1;
2611 }
2612 }
2613}
2614
2615static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant)
2616{
2617 int i;
2618 for (i=0; i < 64; ++i)
2619 data[i] *= dequant[i];
2620}
2621
2622static void stbi__jpeg_finish(stbi__jpeg *z)
2623{
2624 if (z->progressive) {
2625 // dequantize and idct the data
2626 int i,j,n;
2627 for (n=0; n < z->s->img_n; ++n) {
2628 int w = (z->img_comp[n].x+7) >> 3;
2629 int h = (z->img_comp[n].y+7) >> 3;
2630 for (j=0; j < h; ++j) {
2631 for (i=0; i < w; ++i) {
2632 short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
2633 stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]);
2634 z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
2635 }
2636 }
2637 }
2638 }
2639}
2640
2641static int stbi__process_marker(stbi__jpeg *z, int m)
2642{
2643 int L;
2644 switch (m) {
2645 case STBI__MARKER_none: // no marker found
2646 return stbi__err("expected marker","Corrupt JPEG");
2647
2648 case 0xDD: // DRI - specify restart interval
2649 if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG");
2650 z->restart_interval = stbi__get16be(z->s);
2651 return 1;
2652
2653 case 0xDB: // DQT - define quantization table
2654 L = stbi__get16be(z->s)-2;
2655 while (L > 0) {
2656 int q = stbi__get8(z->s);
2657 int p = q >> 4, sixteen = (p != 0);
2658 int t = q & 15,i;
2659 if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG");
2660 if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG");
2661
2662 for (i=0; i < 64; ++i)
2663 z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s));
2664 L -= (sixteen ? 129 : 65);
2665 }
2666 return L==0;
2667
2668 case 0xC4: // DHT - define huffman table
2669 L = stbi__get16be(z->s)-2;
2670 while (L > 0) {
2671 stbi_uc *v;
2672 int sizes[16],i,n=0;
2673 int q = stbi__get8(z->s);
2674 int tc = q >> 4;
2675 int th = q & 15;
2676 if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG");
2677 for (i=0; i < 16; ++i) {
2678 sizes[i] = stbi__get8(z->s);
2679 n += sizes[i];
2680 }
2681 if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values!
2682 L -= 17;
2683 if (tc == 0) {
2684 if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0;
2685 v = z->huff_dc[th].values;
2686 } else {
2687 if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0;
2688 v = z->huff_ac[th].values;
2689 }
2690 for (i=0; i < n; ++i)
2691 v[i] = stbi__get8(z->s);
2692 if (tc != 0)
2693 stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th);
2694 L -= n;
2695 }
2696 return L==0;
2697 }
2698
2699 // check for comment block or APP blocks
2700 if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) {
2701 L = stbi__get16be(z->s);
2702 if (L < 2) {
2703 if (m == 0xFE)
2704 return stbi__err("bad COM len","Corrupt JPEG");
2705 else
2706 return stbi__err("bad APP len","Corrupt JPEG");
2707 }
2708 L -= 2;
2709
2710 if (m == 0xE0 && L >= 5) { // JFIF APP0 segment
2711 static const unsigned char tag[5] = {'J','F','I','F','\0'};
2712 int ok = 1;
2713 int i;
2714 for (i=0; i < 5; ++i)
2715 if (stbi__get8(z->s) != tag[i])
2716 ok = 0;
2717 L -= 5;
2718 if (ok)
2719 z->jfif = 1;
2720 } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment
2721 static const unsigned char tag[6] = {'A','d','o','b','e','\0'};
2722 int ok = 1;
2723 int i;
2724 for (i=0; i < 6; ++i)
2725 if (stbi__get8(z->s) != tag[i])
2726 ok = 0;
2727 L -= 6;
2728 if (ok) {
2729 stbi__get8(z->s); // version
2730 stbi__get16be(z->s); // flags0
2731 stbi__get16be(z->s); // flags1
2732 z->app14_color_transform = stbi__get8(z->s); // color transform
2733 L -= 6;
2734 }
2735 }
2736
2737 stbi__skip(z->s, L);
2738 return 1;
2739 }
2740
2741 return stbi__err("unknown marker","Corrupt JPEG");
2742}
2743
2744// after we see SOS
2745static int stbi__process_scan_header(stbi__jpeg *z)
2746{
2747 int i;
2748 int Ls = stbi__get16be(z->s);
2749 z->scan_n = stbi__get8(z->s);
2750 if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG");
2751 if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG");
2752 for (i=0; i < z->scan_n; ++i) {
2753 int id = stbi__get8(z->s), which;
2754 int q = stbi__get8(z->s);
2755 for (which = 0; which < z->s->img_n; ++which)
2756 if (z->img_comp[which].id == id)
2757 break;
2758 if (which == z->s->img_n) return 0; // no match
2759 z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG");
2760 z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG");
2761 z->order[i] = which;
2762 }
2763
2764 {
2765 int aa;
2766 z->spec_start = stbi__get8(z->s);
2767 z->spec_end = stbi__get8(z->s); // should be 63, but might be 0
2768 aa = stbi__get8(z->s);
2769 z->succ_high = (aa >> 4);
2770 z->succ_low = (aa & 15);
2771 if (z->progressive) {
2772 if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13)
2773 return stbi__err("bad SOS", "Corrupt JPEG");
2774 } else {
2775 if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG");
2776 if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG");
2777 z->spec_end = 63;
2778 }
2779 }
2780
2781 return 1;
2782}
2783
2784static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why)
2785{
2786 int i;
2787 for (i=0; i < ncomp; ++i) {
2788 if (z->img_comp[i].raw_data) {
2789 STBI_FREE(z->img_comp[i].raw_data);
2790 z->img_comp[i].raw_data = NULL;
2791 z->img_comp[i].data = NULL;
2792 }
2793 if (z->img_comp[i].raw_coeff) {
2794 STBI_FREE(z->img_comp[i].raw_coeff);
2795 z->img_comp[i].raw_coeff = 0;
2796 z->img_comp[i].coeff = 0;
2797 }
2798 if (z->img_comp[i].linebuf) {
2799 STBI_FREE(z->img_comp[i].linebuf);
2800 z->img_comp[i].linebuf = NULL;
2801 }
2802 }
2803 return why;
2804}
2805
2806static int stbi__process_frame_header(stbi__jpeg *z, int scan)
2807{
2808 stbi__context *s = z->s;
2809 int Lf,p,i,q, h_max=1,v_max=1,c;
2810 Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG
2811 p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline
2812 s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG
2813 s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires
2814 if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
2815 if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
2816 c = stbi__get8(s);
2817 if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG");
2818 s->img_n = c;
2819 for (i=0; i < c; ++i) {
2820 z->img_comp[i].data = NULL;
2821 z->img_comp[i].linebuf = NULL;
2822 }
2823
2824 if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG");
2825
2826 z->rgb = 0;
2827 for (i=0; i < s->img_n; ++i) {
2828 static const unsigned char rgb[3] = { 'R', 'G', 'B' };
2829 z->img_comp[i].id = stbi__get8(s);
2830 if (s->img_n == 3 && z->img_comp[i].id == rgb[i])
2831 ++z->rgb;
2832 q = stbi__get8(s);
2833 z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG");
2834 z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG");
2835 z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG");
2836 }
2837
2838 if (scan != STBI__SCAN_load) return 1;
2839
2840 if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode");
2841
2842 for (i=0; i < s->img_n; ++i) {
2843 if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h;
2844 if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v;
2845 }
2846
2847 // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios
2848 // and I've never seen a non-corrupted JPEG file actually use them
2849 for (i=0; i < s->img_n; ++i) {
2850 if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG");
2851 if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG");
2852 }
2853
2854 // compute interleaved mcu info
2855 z->img_h_max = h_max;
2856 z->img_v_max = v_max;
2857 z->img_mcu_w = h_max * 8;
2858 z->img_mcu_h = v_max * 8;
2859 // these sizes can't be more than 17 bits
2860 z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w;
2861 z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h;
2862
2863 for (i=0; i < s->img_n; ++i) {
2864 // number of effective pixels (e.g. for non-interleaved MCU)
2865 z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max;
2866 z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max;
2867 // to simplify generation, we'll allocate enough memory to decode
2868 // the bogus oversized data from using interleaved MCUs and their
2869 // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't
2870 // discard the extra data until colorspace conversion
2871 //
2872 // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier)
2873 // so these muls can't overflow with 32-bit ints (which we require)
2874 z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8;
2875 z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8;
2876 z->img_comp[i].coeff = 0;
2877 z->img_comp[i].raw_coeff = 0;
2878 z->img_comp[i].linebuf = NULL;
2879 z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15);
2880 if (z->img_comp[i].raw_data == NULL)
2881 return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory"));
2882 // align blocks for idct using mmx/sse
2883 z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15);
2884 if (z->progressive) {
2885 // w2, h2 are multiples of 8 (see above)
2886 z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8;
2887 z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8;
2888 z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15);
2889 if (z->img_comp[i].raw_coeff == NULL)
2890 return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory"));
2891 z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15);
2892 }
2893 }
2894
2895 return 1;
2896}
2897
2898// use comparisons since in some cases we handle more than one case (e.g. SOF)
2899#define stbi__DNL(x) ((x) == 0xdc)
2900#define stbi__SOI(x) ((x) == 0xd8)
2901#define stbi__EOI(x) ((x) == 0xd9)
2902#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2)
2903#define stbi__SOS(x) ((x) == 0xda)
2904
2905#define stbi__SOF_progressive(x) ((x) == 0xc2)
2906
2907static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
2908{
2909 int m;
2910 z->jfif = 0;
2911 z->app14_color_transform = -1; // valid values are 0,1,2
2912 z->marker = STBI__MARKER_none; // initialize cached marker to empty
2913 m = stbi__get_marker(z);
2914 if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG");
2915 if (scan == STBI__SCAN_type) return 1;
2916 m = stbi__get_marker(z);
2917 while (!stbi__SOF(m)) {
2918 if (!stbi__process_marker(z,m)) return 0;
2919 m = stbi__get_marker(z);
2920 while (m == STBI__MARKER_none) {
2921 // some files have extra padding after their blocks, so ok, we'll scan
2922 if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG");
2923 m = stbi__get_marker(z);
2924 }
2925 }
2926 z->progressive = stbi__SOF_progressive(m);
2927 if (!stbi__process_frame_header(z, scan)) return 0;
2928 return 1;
2929}
2930
2931static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j)
2932{
2933 // some JPEGs have junk at end, skip over it but if we find what looks
2934 // like a valid marker, resume there
2935 while (!stbi__at_eof(j->s)) {
2936 int x = stbi__get8(j->s);
2937 while (x == 255) { // might be a marker
2938 if (stbi__at_eof(j->s)) return STBI__MARKER_none;
2939 x = stbi__get8(j->s);
2940 if (x != 0x00 && x != 0xff) {
2941 // not a stuffed zero or lead-in to another marker, looks
2942 // like an actual marker, return it
2943 return x;
2944 }
2945 // stuffed zero has x=0 now which ends the loop, meaning we go
2946 // back to regular scan loop.
2947 // repeated 0xff keeps trying to read the next byte of the marker.
2948 }
2949 }
2950 return STBI__MARKER_none;
2951}
2952
2953// decode image to YCbCr format
2954static int stbi__decode_jpeg_image(stbi__jpeg *j)
2955{
2956 int m;
2957 for (m = 0; m < 4; m++) {
2958 j->img_comp[m].raw_data = NULL;
2959 j->img_comp[m].raw_coeff = NULL;
2960 }
2961 j->restart_interval = 0;
2962 if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0;
2963 m = stbi__get_marker(j);
2964 while (!stbi__EOI(m)) {
2965 if (stbi__SOS(m)) {
2966 if (!stbi__process_scan_header(j)) return 0;
2967 if (!stbi__parse_entropy_coded_data(j)) return 0;
2968 if (j->marker == STBI__MARKER_none ) {
2969 j->marker = stbi__skip_jpeg_junk_at_end(j);
2970 // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0
2971 }
2972 m = stbi__get_marker(j);
2973 if (STBI__RESTART(m))
2974 m = stbi__get_marker(j);
2975 } else if (stbi__DNL(m)) {
2976 int Ld = stbi__get16be(j->s);
2977 stbi__uint32 NL = stbi__get16be(j->s);
2978 if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG");
2979 if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG");
2980 m = stbi__get_marker(j);
2981 } else {
2982 if (!stbi__process_marker(j, m)) return 1;
2983 m = stbi__get_marker(j);
2984 }
2985 }
2986 if (j->progressive)
2987 stbi__jpeg_finish(j);
2988 return 1;
2989}
2990
2991// static jfif-centered resampling (across block boundaries)
2992
2993typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1,
2994 int w, int hs);
2995
2996#define stbi__div4(x) ((stbi_uc) ((x) >> 2))
2997
2998static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
2999{
3000 STBI_NOTUSED(out);
3001 STBI_NOTUSED(in_far);
3002 STBI_NOTUSED(w);
3003 STBI_NOTUSED(hs);
3004 return in_near;
3005}
3006
3007static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3008{
3009 // need to generate two samples vertically for every one in input
3010 int i;
3011 STBI_NOTUSED(hs);
3012 for (i=0; i < w; ++i)
3013 out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2);
3014 return out;
3015}
3016
3017static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3018{
3019 // need to generate two samples horizontally for every one in input
3020 int i;
3021 stbi_uc *input = in_near;
3022
3023 if (w == 1) {
3024 // if only one sample, can't do any interpolation
3025 out[0] = out[1] = input[0];
3026 return out;
3027 }
3028
3029 out[0] = input[0];
3030 out[1] = stbi__div4(input[0]*3 + input[1] + 2);
3031 for (i=1; i < w-1; ++i) {
3032 int n = 3*input[i]+2;
3033 out[i*2+0] = stbi__div4(n+input[i-1]);
3034 out[i*2+1] = stbi__div4(n+input[i+1]);
3035 }
3036 out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2);
3037 out[i*2+1] = input[w-1];
3038
3039 STBI_NOTUSED(in_far);
3040 STBI_NOTUSED(hs);
3041
3042 return out;
3043}
3044
3045#define stbi__div16(x) ((stbi_uc) ((x) >> 4))
3046
3047static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3048{
3049 // need to generate 2x2 samples for every one in input
3050 int i,t0,t1;
3051 if (w == 1) {
3052 out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
3053 return out;
3054 }
3055
3056 t1 = 3*in_near[0] + in_far[0];
3057 out[0] = stbi__div4(t1+2);
3058 for (i=1; i < w; ++i) {
3059 t0 = t1;
3060 t1 = 3*in_near[i]+in_far[i];
3061 out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
3062 out[i*2 ] = stbi__div16(3*t1 + t0 + 8);
3063 }
3064 out[w*2-1] = stbi__div4(t1+2);
3065
3066 STBI_NOTUSED(hs);
3067
3068 return out;
3069}
3070
3071#if defined(STBI_SSE2) || defined(STBI_NEON)
3072static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3073{
3074 // need to generate 2x2 samples for every one in input
3075 int i=0,t0,t1;
3076
3077 if (w == 1) {
3078 out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
3079 return out;
3080 }
3081
3082 t1 = 3*in_near[0] + in_far[0];
3083 // process groups of 8 pixels for as long as we can.
3084 // note we can't handle the last pixel in a row in this loop
3085 // because we need to handle the filter boundary conditions.
3086 for (; i < ((w-1) & ~7); i += 8) {
3087#if defined(STBI_SSE2)
3088 // load and perform the vertical filtering pass
3089 // this uses 3*x + y = 4*x + (y - x)
3090 __m128i zero = _mm_setzero_si128();
3091 __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i));
3092 __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i));
3093 __m128i farw = _mm_unpacklo_epi8(farb, zero);
3094 __m128i nearw = _mm_unpacklo_epi8(nearb, zero);
3095 __m128i diff = _mm_sub_epi16(farw, nearw);
3096 __m128i nears = _mm_slli_epi16(nearw, 2);
3097 __m128i curr = _mm_add_epi16(nears, diff); // current row
3098
3099 // horizontal filter works the same based on shifted vers of current
3100 // row. "prev" is current row shifted right by 1 pixel; we need to
3101 // insert the previous pixel value (from t1).
3102 // "next" is current row shifted left by 1 pixel, with first pixel
3103 // of next block of 8 pixels added in.
3104 __m128i prv0 = _mm_slli_si128(curr, 2);
3105 __m128i nxt0 = _mm_srli_si128(curr, 2);
3106 __m128i prev = _mm_insert_epi16(prv0, t1, 0);
3107 __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7);
3108
3109 // horizontal filter, polyphase implementation since it's convenient:
3110 // even pixels = 3*cur + prev = cur*4 + (prev - cur)
3111 // odd pixels = 3*cur + next = cur*4 + (next - cur)
3112 // note the shared term.
3113 __m128i bias = _mm_set1_epi16(8);
3114 __m128i curs = _mm_slli_epi16(curr, 2);
3115 __m128i prvd = _mm_sub_epi16(prev, curr);
3116 __m128i nxtd = _mm_sub_epi16(next, curr);
3117 __m128i curb = _mm_add_epi16(curs, bias);
3118 __m128i even = _mm_add_epi16(prvd, curb);
3119 __m128i odd = _mm_add_epi16(nxtd, curb);
3120
3121 // interleave even and odd pixels, then undo scaling.
3122 __m128i int0 = _mm_unpacklo_epi16(even, odd);
3123 __m128i int1 = _mm_unpackhi_epi16(even, odd);
3124 __m128i de0 = _mm_srli_epi16(int0, 4);
3125 __m128i de1 = _mm_srli_epi16(int1, 4);
3126
3127 // pack and write output
3128 __m128i outv = _mm_packus_epi16(de0, de1);
3129 _mm_storeu_si128((__m128i *) (out + i*2), outv);
3130#elif defined(STBI_NEON)
3131 // load and perform the vertical filtering pass
3132 // this uses 3*x + y = 4*x + (y - x)
3133 uint8x8_t farb = vld1_u8(in_far + i);
3134 uint8x8_t nearb = vld1_u8(in_near + i);
3135 int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb));
3136 int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2));
3137 int16x8_t curr = vaddq_s16(nears, diff); // current row
3138
3139 // horizontal filter works the same based on shifted vers of current
3140 // row. "prev" is current row shifted right by 1 pixel; we need to
3141 // insert the previous pixel value (from t1).
3142 // "next" is current row shifted left by 1 pixel, with first pixel
3143 // of next block of 8 pixels added in.
3144 int16x8_t prv0 = vextq_s16(curr, curr, 7);
3145 int16x8_t nxt0 = vextq_s16(curr, curr, 1);
3146 int16x8_t prev = vsetq_lane_s16(t1, prv0, 0);
3147 int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7);
3148
3149 // horizontal filter, polyphase implementation since it's convenient:
3150 // even pixels = 3*cur + prev = cur*4 + (prev - cur)
3151 // odd pixels = 3*cur + next = cur*4 + (next - cur)
3152 // note the shared term.
3153 int16x8_t curs = vshlq_n_s16(curr, 2);
3154 int16x8_t prvd = vsubq_s16(prev, curr);
3155 int16x8_t nxtd = vsubq_s16(next, curr);
3156 int16x8_t even = vaddq_s16(curs, prvd);
3157 int16x8_t odd = vaddq_s16(curs, nxtd);
3158
3159 // undo scaling and round, then store with even/odd phases interleaved
3160 uint8x8x2_t o;
3161 o.val[0] = vqrshrun_n_s16(even, 4);
3162 o.val[1] = vqrshrun_n_s16(odd, 4);
3163 vst2_u8(out + i*2, o);
3164#endif
3165
3166 // "previous" value for next iter
3167 t1 = 3*in_near[i+7] + in_far[i+7];
3168 }
3169
3170 t0 = t1;
3171 t1 = 3*in_near[i] + in_far[i];
3172 out[i*2] = stbi__div16(3*t1 + t0 + 8);
3173
3174 for (++i; i < w; ++i) {
3175 t0 = t1;
3176 t1 = 3*in_near[i]+in_far[i];
3177 out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
3178 out[i*2 ] = stbi__div16(3*t1 + t0 + 8);
3179 }
3180 out[w*2-1] = stbi__div4(t1+2);
3181
3182 STBI_NOTUSED(hs);
3183
3184 return out;
3185}
3186#endif
3187
3188static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3189{
3190 // resample with nearest-neighbor
3191 int i,j;
3192 STBI_NOTUSED(in_far);
3193 for (i=0; i < w; ++i)
3194 for (j=0; j < hs; ++j)
3195 out[i*hs+j] = in_near[i];
3196 return out;
3197}
3198
3199// this is a reduced-precision calculation of YCbCr-to-RGB introduced
3200// to make sure the code produces the same results in both SIMD and scalar
3201#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8)
3202static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step)
3203{
3204 int i;
3205 for (i=0; i < count; ++i) {
3206 int y_fixed = (y[i] << 20) + (1<<19); // rounding
3207 int r,g,b;
3208 int cr = pcr[i] - 128;
3209 int cb = pcb[i] - 128;
3210 r = y_fixed + cr* stbi__float2fixed(1.40200f);
3211 g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);
3212 b = y_fixed + cb* stbi__float2fixed(1.77200f);
3213 r >>= 20;
3214 g >>= 20;
3215 b >>= 20;
3216 if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
3217 if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
3218 if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
3219 out[0] = (stbi_uc)r;
3220 out[1] = (stbi_uc)g;
3221 out[2] = (stbi_uc)b;
3222 out[3] = 255;
3223 out += step;
3224 }
3225}
3226
3227#if defined(STBI_SSE2) || defined(STBI_NEON)
3228static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step)
3229{
3230 int i = 0;
3231
3232#ifdef STBI_SSE2
3233 // step == 3 is pretty ugly on the final interleave, and i'm not convinced
3234 // it's useful in practice (you wouldn't use it for textures, for example).
3235 // so just accelerate step == 4 case.
3236 if (step == 4) {
3237 // this is a fairly straightforward implementation and not super-optimized.
3238 __m128i signflip = _mm_set1_epi8(-0x80);
3239 __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f));
3240 __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f));
3241 __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f));
3242 __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f));
3243 __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128);
3244 __m128i xw = _mm_set1_epi16(255); // alpha channel
3245
3246 for (; i+7 < count; i += 8) {
3247 // load
3248 __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i));
3249 __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i));
3250 __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i));
3251 __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128
3252 __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128
3253
3254 // unpack to short (and left-shift cr, cb by 8)
3255 __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes);
3256 __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased);
3257 __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased);
3258
3259 // color transform
3260 __m128i yws = _mm_srli_epi16(yw, 4);
3261 __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw);
3262 __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw);
3263 __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1);
3264 __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1);
3265 __m128i rws = _mm_add_epi16(cr0, yws);
3266 __m128i gwt = _mm_add_epi16(cb0, yws);
3267 __m128i bws = _mm_add_epi16(yws, cb1);
3268 __m128i gws = _mm_add_epi16(gwt, cr1);
3269
3270 // descale
3271 __m128i rw = _mm_srai_epi16(rws, 4);
3272 __m128i bw = _mm_srai_epi16(bws, 4);
3273 __m128i gw = _mm_srai_epi16(gws, 4);
3274
3275 // back to byte, set up for transpose
3276 __m128i brb = _mm_packus_epi16(rw, bw);
3277 __m128i gxb = _mm_packus_epi16(gw, xw);
3278
3279 // transpose to interleave channels
3280 __m128i t0 = _mm_unpacklo_epi8(brb, gxb);
3281 __m128i t1 = _mm_unpackhi_epi8(brb, gxb);
3282 __m128i o0 = _mm_unpacklo_epi16(t0, t1);
3283 __m128i o1 = _mm_unpackhi_epi16(t0, t1);
3284
3285 // store
3286 _mm_storeu_si128((__m128i *) (out + 0), o0);
3287 _mm_storeu_si128((__m128i *) (out + 16), o1);
3288 out += 32;
3289 }
3290 }
3291#endif
3292
3293#ifdef STBI_NEON
3294 // in this version, step=3 support would be easy to add. but is there demand?
3295 if (step == 4) {
3296 // this is a fairly straightforward implementation and not super-optimized.
3297 uint8x8_t signflip = vdup_n_u8(0x80);
3298 int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f));
3299 int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f));
3300 int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f));
3301 int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f));
3302
3303 for (; i+7 < count; i += 8) {
3304 // load
3305 uint8x8_t y_bytes = vld1_u8(y + i);
3306 uint8x8_t cr_bytes = vld1_u8(pcr + i);
3307 uint8x8_t cb_bytes = vld1_u8(pcb + i);
3308 int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip));
3309 int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip));
3310
3311 // expand to s16
3312 int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4));
3313 int16x8_t crw = vshll_n_s8(cr_biased, 7);
3314 int16x8_t cbw = vshll_n_s8(cb_biased, 7);
3315
3316 // color transform
3317 int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0);
3318 int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0);
3319 int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1);
3320 int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1);
3321 int16x8_t rws = vaddq_s16(yws, cr0);
3322 int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1);
3323 int16x8_t bws = vaddq_s16(yws, cb1);
3324
3325 // undo scaling, round, convert to byte
3326 uint8x8x4_t o;
3327 o.val[0] = vqrshrun_n_s16(rws, 4);
3328 o.val[1] = vqrshrun_n_s16(gws, 4);
3329 o.val[2] = vqrshrun_n_s16(bws, 4);
3330 o.val[3] = vdup_n_u8(255);
3331
3332 // store, interleaving r/g/b/a
3333 vst4_u8(out, o);
3334 out += 8*4;
3335 }
3336 }
3337#endif
3338
3339 for (; i < count; ++i) {
3340 int y_fixed = (y[i] << 20) + (1<<19); // rounding
3341 int r,g,b;
3342 int cr = pcr[i] - 128;
3343 int cb = pcb[i] - 128;
3344 r = y_fixed + cr* stbi__float2fixed(1.40200f);
3345 g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);
3346 b = y_fixed + cb* stbi__float2fixed(1.77200f);
3347 r >>= 20;
3348 g >>= 20;
3349 b >>= 20;
3350 if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
3351 if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
3352 if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
3353 out[0] = (stbi_uc)r;
3354 out[1] = (stbi_uc)g;
3355 out[2] = (stbi_uc)b;
3356 out[3] = 255;
3357 out += step;
3358 }
3359}
3360#endif
3361
3362// set up the kernels
3363static void stbi__setup_jpeg(stbi__jpeg *j)
3364{
3365 j->idct_block_kernel = stbi__idct_block;
3366 j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row;
3367 j->resample_row_hv_2_kernel = stbi__resample_row_hv_2;
3368
3369#ifdef STBI_SSE2
3370 if (stbi__sse2_available()) {
3371 j->idct_block_kernel = stbi__idct_simd;
3372 j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
3373 j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
3374 }
3375#endif
3376
3377#ifdef STBI_NEON
3378 j->idct_block_kernel = stbi__idct_simd;
3379 j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
3380 j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
3381#endif
3382}
3383
3384// clean up the temporary component buffers
3385static void stbi__cleanup_jpeg(stbi__jpeg *j)
3386{
3387 stbi__free_jpeg_components(j, j->s->img_n, 0);
3388}
3389
3390typedef struct
3391{
3392 resample_row_func resample;
3393 stbi_uc *line0,*line1;
3394 int hs,vs; // expansion factor in each axis
3395 int w_lores; // horizontal pixels pre-expansion
3396 int ystep; // how far through vertical expansion we are
3397 int ypos; // which pre-expansion row we're on
3398} stbi__resample;
3399
3400// fast 0..255 * 0..255 => 0..255 rounded multiplication
3401static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y)
3402{
3403 unsigned int t = x*y + 128;
3404 return (stbi_uc) ((t + (t >>8)) >> 8);
3405}
3406
3407static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)
3408{
3409 int n, decode_n, is_rgb;
3410 z->s->img_n = 0; // make stbi__cleanup_jpeg safe
3411
3412 // validate req_comp
3413 if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
3414
3415 // load a jpeg image from whichever source, but leave in YCbCr format
3416 if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; }
3417
3418 // determine actual number of components to generate
3419 n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1;
3420
3421 is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif));
3422
3423 if (z->s->img_n == 3 && n < 3 && !is_rgb)
3424 decode_n = 1;
3425 else
3426 decode_n = z->s->img_n;
3427
3428 // nothing to do if no components requested; check this now to avoid
3429 // accessing uninitialized coutput[0] later
3430 if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; }
3431
3432 // resample and color-convert
3433 {
3434 int k;
3435 unsigned int i,j;
3436 stbi_uc *output;
3437 stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL };
3438
3439 stbi__resample res_comp[4];
3440
3441 for (k=0; k < decode_n; ++k) {
3442 stbi__resample *r = &res_comp[k];
3443
3444 // allocate line buffer big enough for upsampling off the edges
3445 // with upsample factor of 4
3446 z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3);
3447 if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
3448
3449 r->hs = z->img_h_max / z->img_comp[k].h;
3450 r->vs = z->img_v_max / z->img_comp[k].v;
3451 r->ystep = r->vs >> 1;
3452 r->w_lores = (z->s->img_x + r->hs-1) / r->hs;
3453 r->ypos = 0;
3454 r->line0 = r->line1 = z->img_comp[k].data;
3455
3456 if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1;
3457 else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2;
3458 else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2;
3459 else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel;
3460 else r->resample = stbi__resample_row_generic;
3461 }
3462
3463 // can't error after this so, this is safe
3464 output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1);
3465 if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
3466
3467 // now go ahead and resample
3468 for (j=0; j < z->s->img_y; ++j) {
3469 stbi_uc *out = output + n * z->s->img_x * j;
3470 for (k=0; k < decode_n; ++k) {
3471 stbi__resample *r = &res_comp[k];
3472 int y_bot = r->ystep >= (r->vs >> 1);
3473 coutput[k] = r->resample(z->img_comp[k].linebuf,
3474 y_bot ? r->line1 : r->line0,
3475 y_bot ? r->line0 : r->line1,
3476 r->w_lores, r->hs);
3477 if (++r->ystep >= r->vs) {
3478 r->ystep = 0;
3479 r->line0 = r->line1;
3480 if (++r->ypos < z->img_comp[k].y)
3481 r->line1 += z->img_comp[k].w2;
3482 }
3483 }
3484 if (n >= 3) {
3485 stbi_uc *y = coutput[0];
3486 if (z->s->img_n == 3) {
3487 if (is_rgb) {
3488 for (i=0; i < z->s->img_x; ++i) {
3489 out[0] = y[i];
3490 out[1] = coutput[1][i];
3491 out[2] = coutput[2][i];
3492 out[3] = 255;
3493 out += n;
3494 }
3495 } else {
3496 z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
3497 }
3498 } else if (z->s->img_n == 4) {
3499 if (z->app14_color_transform == 0) { // CMYK
3500 for (i=0; i < z->s->img_x; ++i) {
3501 stbi_uc m = coutput[3][i];
3502 out[0] = stbi__blinn_8x8(coutput[0][i], m);
3503 out[1] = stbi__blinn_8x8(coutput[1][i], m);
3504 out[2] = stbi__blinn_8x8(coutput[2][i], m);
3505 out[3] = 255;
3506 out += n;
3507 }
3508 } else if (z->app14_color_transform == 2) { // YCCK
3509 z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
3510 for (i=0; i < z->s->img_x; ++i) {
3511 stbi_uc m = coutput[3][i];
3512 out[0] = stbi__blinn_8x8(255 - out[0], m);
3513 out[1] = stbi__blinn_8x8(255 - out[1], m);
3514 out[2] = stbi__blinn_8x8(255 - out[2], m);
3515 out += n;
3516 }
3517 } else { // YCbCr + alpha? Ignore the fourth channel for now
3518 z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
3519 }
3520 } else
3521 for (i=0; i < z->s->img_x; ++i) {
3522 out[0] = out[1] = out[2] = y[i];
3523 out[3] = 255; // not used if n==3
3524 out += n;
3525 }
3526 } else {
3527 if (is_rgb) {
3528 if (n == 1)
3529 for (i=0; i < z->s->img_x; ++i)
3530 *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);
3531 else {
3532 for (i=0; i < z->s->img_x; ++i, out += 2) {
3533 out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);
3534 out[1] = 255;
3535 }
3536 }
3537 } else if (z->s->img_n == 4 && z->app14_color_transform == 0) {
3538 for (i=0; i < z->s->img_x; ++i) {
3539 stbi_uc m = coutput[3][i];
3540 stbi_uc r = stbi__blinn_8x8(coutput[0][i], m);
3541 stbi_uc g = stbi__blinn_8x8(coutput[1][i], m);
3542 stbi_uc b = stbi__blinn_8x8(coutput[2][i], m);
3543 out[0] = stbi__compute_y(r, g, b);
3544 out[1] = 255;
3545 out += n;
3546 }
3547 } else if (z->s->img_n == 4 && z->app14_color_transform == 2) {
3548 for (i=0; i < z->s->img_x; ++i) {
3549 out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]);
3550 out[1] = 255;
3551 out += n;
3552 }
3553 } else {
3554 stbi_uc *y = coutput[0];
3555 if (n == 1)
3556 for (i=0; i < z->s->img_x; ++i) out[i] = y[i];
3557 else
3558 for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; }
3559 }
3560 }
3561 }
3562 stbi__cleanup_jpeg(z);
3563 *out_x = z->s->img_x;
3564 *out_y = z->s->img_y;
3565 if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output
3566 return output;
3567 }
3568}
3569
3570static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
3571{
3572 unsigned char* result;
3573 stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));
3574 if (!j) return stbi__errpuc("outofmem", "Out of memory");
3575 memset(j, 0, sizeof(stbi__jpeg));
3576 STBI_NOTUSED(ri);
3577 j->s = s;
3578 stbi__setup_jpeg(j);
3579 result = load_jpeg_image(j, x,y,comp,req_comp);
3580 STBI_FREE(j);
3581 return result;
3582}
3583
3584static int stbi__jpeg_test(stbi__context *s)
3585{
3586 int r;
3587 stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg));
3588 if (!j) return stbi__err("outofmem", "Out of memory");
3589 memset(j, 0, sizeof(stbi__jpeg));
3590 j->s = s;
3591 stbi__setup_jpeg(j);
3592 r = stbi__decode_jpeg_header(j, STBI__SCAN_type);
3593 stbi__rewind(s);
3594 STBI_FREE(j);
3595 return r;
3596}
3597
3598static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp)
3599{
3600 if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) {
3601 stbi__rewind( j->s );
3602 return 0;
3603 }
3604 if (x) *x = j->s->img_x;
3605 if (y) *y = j->s->img_y;
3606 if (comp) *comp = j->s->img_n >= 3 ? 3 : 1;
3607 return 1;
3608}
3609
3610static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
3611{
3612 int result;
3613 stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));
3614 if (!j) return stbi__err("outofmem", "Out of memory");
3615 memset(j, 0, sizeof(stbi__jpeg));
3616 j->s = s;
3617 result = stbi__jpeg_info_raw(j, x, y, comp);
3618 STBI_FREE(j);
3619 return result;
3620}
3621#endif
3622
3623// public domain zlib decode v0.2 Sean Barrett 2006-11-18
3624// simple implementation
3625// - all input must be provided in an upfront buffer
3626// - all output is written to a single output buffer (can malloc/realloc)
3627// performance
3628// - fast huffman
3629
3630#ifndef STBI_NO_ZLIB
3631
3632// fast-way is faster to check than jpeg huffman, but slow way is slower
3633#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables
3634#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1)
3635#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet
3636
3637// zlib-style huffman encoding
3638// (jpegs packs from left, zlib from right, so can't share code)
3639typedef struct
3640{
3641 stbi__uint16 fast[1 << STBI__ZFAST_BITS];
3642 stbi__uint16 firstcode[16];
3643 int maxcode[17];
3644 stbi__uint16 firstsymbol[16];
3645 stbi_uc size[STBI__ZNSYMS];
3646 stbi__uint16 value[STBI__ZNSYMS];
3647} stbi__zhuffman;
3648
3649stbi_inline static int stbi__bitreverse16(int n)
3650{
3651 n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1);
3652 n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2);
3653 n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4);
3654 n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8);
3655 return n;
3656}
3657
3658stbi_inline static int stbi__bit_reverse(int v, int bits)
3659{
3660 STBI_ASSERT(bits <= 16);
3661 // to bit reverse n bits, reverse 16 and shift
3662 // e.g. 11 bits, bit reverse and shift away 5
3663 return stbi__bitreverse16(v) >> (16-bits);
3664}
3665
3666static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num)
3667{
3668 int i,k=0;
3669 int code, next_code[16], sizes[17];
3670
3671 // DEFLATE spec for generating codes
3672 memset(sizes, 0, sizeof(sizes));
3673 memset(z->fast, 0, sizeof(z->fast));
3674 for (i=0; i < num; ++i)
3675 ++sizes[sizelist[i]];
3676 sizes[0] = 0;
3677 for (i=1; i < 16; ++i)
3678 if (sizes[i] > (1 << i))
3679 return stbi__err("bad sizes", "Corrupt PNG");
3680 code = 0;
3681 for (i=1; i < 16; ++i) {
3682 next_code[i] = code;
3683 z->firstcode[i] = (stbi__uint16) code;
3684 z->firstsymbol[i] = (stbi__uint16) k;
3685 code = (code + sizes[i]);
3686 if (sizes[i])
3687 if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG");
3688 z->maxcode[i] = code << (16-i); // preshift for inner loop
3689 code <<= 1;
3690 k += sizes[i];
3691 }
3692 z->maxcode[16] = 0x10000; // sentinel
3693 for (i=0; i < num; ++i) {
3694 int s = sizelist[i];
3695 if (s) {
3696 int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s];
3697 stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i);
3698 z->size [c] = (stbi_uc ) s;
3699 z->value[c] = (stbi__uint16) i;
3700 if (s <= STBI__ZFAST_BITS) {
3701 int j = stbi__bit_reverse(next_code[s],s);
3702 while (j < (1 << STBI__ZFAST_BITS)) {
3703 z->fast[j] = fastv;
3704 j += (1 << s);
3705 }
3706 }
3707 ++next_code[s];
3708 }
3709 }
3710 return 1;
3711}
3712
3713// zlib-from-memory implementation for PNG reading
3714// because PNG allows splitting the zlib stream arbitrarily,
3715// and it's annoying structurally to have PNG call ZLIB call PNG,
3716// we require PNG read all the IDATs and combine them into a single
3717// memory buffer
3718
3719typedef struct
3720{
3721 stbi_uc *zbuffer, *zbuffer_end;
3722 int num_bits;
3723 stbi__uint32 code_buffer;
3724
3725 char *zout;
3726 char *zout_start;
3727 char *zout_end;
3728 int z_expandable;
3729
3730 stbi__zhuffman z_length, z_distance;
3731} stbi__zbuf;
3732
3733stbi_inline static int stbi__zeof(stbi__zbuf *z)
3734{
3735 return (z->zbuffer >= z->zbuffer_end);
3736}
3737
3738stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z)
3739{
3740 return stbi__zeof(z) ? 0 : *z->zbuffer++;
3741}
3742
3743static void stbi__fill_bits(stbi__zbuf *z)
3744{
3745 do {
3746 if (z->code_buffer >= (1U << z->num_bits)) {
3747 z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */
3748 return;
3749 }
3750 z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits;
3751 z->num_bits += 8;
3752 } while (z->num_bits <= 24);
3753}
3754
3755stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n)
3756{
3757 unsigned int k;
3758 if (z->num_bits < n) stbi__fill_bits(z);
3759 k = z->code_buffer & ((1 << n) - 1);
3760 z->code_buffer >>= n;
3761 z->num_bits -= n;
3762 return k;
3763}
3764
3765static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z)
3766{
3767 int b,s,k;
3768 // not resolved by fast table, so compute it the slow way
3769 // use jpeg approach, which requires MSbits at top
3770 k = stbi__bit_reverse(a->code_buffer, 16);
3771 for (s=STBI__ZFAST_BITS+1; ; ++s)
3772 if (k < z->maxcode[s])
3773 break;
3774 if (s >= 16) return -1; // invalid code!
3775 // code size is s, so:
3776 b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s];
3777 if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere!
3778 if (z->size[b] != s) return -1; // was originally an assert, but report failure instead.
3779 a->code_buffer >>= s;
3780 a->num_bits -= s;
3781 return z->value[b];
3782}
3783
3784stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
3785{
3786 int b,s;
3787 if (a->num_bits < 16) {
3788 if (stbi__zeof(a)) {
3789 return -1; /* report error for unexpected end of data. */
3790 }
3791 stbi__fill_bits(a);
3792 }
3793 b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
3794 if (b) {
3795 s = b >> 9;
3796 a->code_buffer >>= s;
3797 a->num_bits -= s;
3798 return b & 511;
3799 }
3800 return stbi__zhuffman_decode_slowpath(a, z);
3801}
3802
3803static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes
3804{
3805 char *q;
3806 unsigned int cur, limit, old_limit;
3807 z->zout = zout;
3808 if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG");
3809 cur = (unsigned int) (z->zout - z->zout_start);
3810 limit = old_limit = (unsigned) (z->zout_end - z->zout_start);
3811 if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory");
3812 while (cur + n > limit) {
3813 if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory");
3814 limit *= 2;
3815 }
3816 q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit);
3817 STBI_NOTUSED(old_limit);
3818 if (q == NULL) return stbi__err("outofmem", "Out of memory");
3819 z->zout_start = q;
3820 z->zout = q + cur;
3821 z->zout_end = q + limit;
3822 return 1;
3823}
3824
3825static const int stbi__zlength_base[31] = {
3826 3,4,5,6,7,8,9,10,11,13,
3827 15,17,19,23,27,31,35,43,51,59,
3828 67,83,99,115,131,163,195,227,258,0,0 };
3829
3830static const int stbi__zlength_extra[31]=
3831{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 };
3832
3833static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,
3834257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0};
3835
3836static const int stbi__zdist_extra[32] =
3837{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
3838
3839static int stbi__parse_huffman_block(stbi__zbuf *a)
3840{
3841 char *zout = a->zout;
3842 for(;;) {
3843 int z = stbi__zhuffman_decode(a, &a->z_length);
3844 if (z < 256) {
3845 if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes
3846 if (zout >= a->zout_end) {
3847 if (!stbi__zexpand(a, zout, 1)) return 0;
3848 zout = a->zout;
3849 }
3850 *zout++ = (char) z;
3851 } else {
3852 stbi_uc *p;
3853 int len,dist;
3854 if (z == 256) {
3855 a->zout = zout;
3856 return 1;
3857 }
3858 if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data
3859 z -= 257;
3860 len = stbi__zlength_base[z];
3861 if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]);
3862 z = stbi__zhuffman_decode(a, &a->z_distance);
3863 if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data
3864 dist = stbi__zdist_base[z];
3865 if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
3866 if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG");
3867 if (zout + len > a->zout_end) {
3868 if (!stbi__zexpand(a, zout, len)) return 0;
3869 zout = a->zout;
3870 }
3871 p = (stbi_uc *) (zout - dist);
3872 if (dist == 1) { // run of one byte; common in images.
3873 stbi_uc v = *p;
3874 if (len) { do *zout++ = v; while (--len); }
3875 } else {
3876 if (len) { do *zout++ = *p++; while (--len); }
3877 }
3878 }
3879 }
3880}
3881
3882static int stbi__compute_huffman_codes(stbi__zbuf *a)
3883{
3884 static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };
3885 stbi__zhuffman z_codelength;
3886 stbi_uc lencodes[286+32+137];//padding for maximum single op
3887 stbi_uc codelength_sizes[19];
3888 int i,n;
3889
3890 int hlit = stbi__zreceive(a,5) + 257;
3891 int hdist = stbi__zreceive(a,5) + 1;
3892 int hclen = stbi__zreceive(a,4) + 4;
3893 int ntot = hlit + hdist;
3894
3895 memset(codelength_sizes, 0, sizeof(codelength_sizes));
3896 for (i=0; i < hclen; ++i) {
3897 int s = stbi__zreceive(a,3);
3898 codelength_sizes[length_dezigzag[i]] = (stbi_uc) s;
3899 }
3900 if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0;
3901
3902 n = 0;
3903 while (n < ntot) {
3904 int c = stbi__zhuffman_decode(a, &z_codelength);
3905 if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG");
3906 if (c < 16)
3907 lencodes[n++] = (stbi_uc) c;
3908 else {
3909 stbi_uc fill = 0;
3910 if (c == 16) {
3911 c = stbi__zreceive(a,2)+3;
3912 if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG");
3913 fill = lencodes[n-1];
3914 } else if (c == 17) {
3915 c = stbi__zreceive(a,3)+3;
3916 } else if (c == 18) {
3917 c = stbi__zreceive(a,7)+11;
3918 } else {
3919 return stbi__err("bad codelengths", "Corrupt PNG");
3920 }
3921 if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG");
3922 memset(lencodes+n, fill, c);
3923 n += c;
3924 }
3925 }
3926 if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG");
3927 if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0;
3928 if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0;
3929 return 1;
3930}
3931
3932static int stbi__parse_uncompressed_block(stbi__zbuf *a)
3933{
3934 stbi_uc header[4];
3935 int len,nlen,k;
3936 if (a->num_bits & 7)
3937 stbi__zreceive(a, a->num_bits & 7); // discard
3938 // drain the bit-packed data into header
3939 k = 0;
3940 while (a->num_bits > 0) {
3941 header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check
3942 a->code_buffer >>= 8;
3943 a->num_bits -= 8;
3944 }
3945 if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG");
3946 // now fill header the normal way
3947 while (k < 4)
3948 header[k++] = stbi__zget8(a);
3949 len = header[1] * 256 + header[0];
3950 nlen = header[3] * 256 + header[2];
3951 if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG");
3952 if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG");
3953 if (a->zout + len > a->zout_end)
3954 if (!stbi__zexpand(a, a->zout, len)) return 0;
3955 memcpy(a->zout, a->zbuffer, len);
3956 a->zbuffer += len;
3957 a->zout += len;
3958 return 1;
3959}
3960
3961static int stbi__parse_zlib_header(stbi__zbuf *a)
3962{
3963 int cmf = stbi__zget8(a);
3964 int cm = cmf & 15;
3965 /* int cinfo = cmf >> 4; */
3966 int flg = stbi__zget8(a);
3967 if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec
3968 if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec
3969 if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png
3970 if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png
3971 // window = 1 << (8 + cinfo)... but who cares, we fully buffer output
3972 return 1;
3973}
3974
3975static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] =
3976{
3977 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
3978 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
3979 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
3980 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
3981 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
3982 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
3983 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
3984 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
3985 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8
3986};
3987static const stbi_uc stbi__zdefault_distance[32] =
3988{
3989 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5
3990};
3991/*
3992Init algorithm:
3993{
3994 int i; // use <= to match clearly with spec
3995 for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8;
3996 for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9;
3997 for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7;
3998 for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8;
3999
4000 for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5;
4001}
4002*/
4003
4004static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
4005{
4006 int final, type;
4007 if (parse_header)
4008 if (!stbi__parse_zlib_header(a)) return 0;
4009 a->num_bits = 0;
4010 a->code_buffer = 0;
4011 do {
4012 final = stbi__zreceive(a,1);
4013 type = stbi__zreceive(a,2);
4014 if (type == 0) {
4015 if (!stbi__parse_uncompressed_block(a)) return 0;
4016 } else if (type == 3) {
4017 return 0;
4018 } else {
4019 if (type == 1) {
4020 // use fixed code lengths
4021 if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0;
4022 if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0;
4023 } else {
4024 if (!stbi__compute_huffman_codes(a)) return 0;
4025 }
4026 if (!stbi__parse_huffman_block(a)) return 0;
4027 }
4028 } while (!final);
4029 return 1;
4030}
4031
4032static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header)
4033{
4034 a->zout_start = obuf;
4035 a->zout = obuf;
4036 a->zout_end = obuf + olen;
4037 a->z_expandable = exp;
4038
4039 return stbi__parse_zlib(a, parse_header);
4040}
4041
4042STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen)
4043{
4044 stbi__zbuf a;
4045 char *p = (char *) stbi__malloc(initial_size);
4046 if (p == NULL) return NULL;
4047 a.zbuffer = (stbi_uc *) buffer;
4048 a.zbuffer_end = (stbi_uc *) buffer + len;
4049 if (stbi__do_zlib(&a, p, initial_size, 1, 1)) {
4050 if (outlen) *outlen = (int) (a.zout - a.zout_start);
4051 return a.zout_start;
4052 } else {
4053 STBI_FREE(a.zout_start);
4054 return NULL;
4055 }
4056}
4057
4058STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen)
4059{
4060 return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
4061}
4062
4063STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header)
4064{
4065 stbi__zbuf a;
4066 char *p = (char *) stbi__malloc(initial_size);
4067 if (p == NULL) return NULL;
4068 a.zbuffer = (stbi_uc *) buffer;
4069 a.zbuffer_end = (stbi_uc *) buffer + len;
4070 if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) {
4071 if (outlen) *outlen = (int) (a.zout - a.zout_start);
4072 return a.zout_start;
4073 } else {
4074 STBI_FREE(a.zout_start);
4075 return NULL;
4076 }
4077}
4078
4079STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen)
4080{
4081 stbi__zbuf a;
4082 a.zbuffer = (stbi_uc *) ibuffer;
4083 a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
4084 if (stbi__do_zlib(&a, obuffer, olen, 0, 1))
4085 return (int) (a.zout - a.zout_start);
4086 else
4087 return -1;
4088}
4089
4090STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen)
4091{
4092 stbi__zbuf a;
4093 char *p = (char *) stbi__malloc(16384);
4094 if (p == NULL) return NULL;
4095 a.zbuffer = (stbi_uc *) buffer;
4096 a.zbuffer_end = (stbi_uc *) buffer+len;
4097 if (stbi__do_zlib(&a, p, 16384, 1, 0)) {
4098 if (outlen) *outlen = (int) (a.zout - a.zout_start);
4099 return a.zout_start;
4100 } else {
4101 STBI_FREE(a.zout_start);
4102 return NULL;
4103 }
4104}
4105
4106STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen)
4107{
4108 stbi__zbuf a;
4109 a.zbuffer = (stbi_uc *) ibuffer;
4110 a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
4111 if (stbi__do_zlib(&a, obuffer, olen, 0, 0))
4112 return (int) (a.zout - a.zout_start);
4113 else
4114 return -1;
4115}
4116#endif
4117
4118// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18
4119// simple implementation
4120// - only 8-bit samples
4121// - no CRC checking
4122// - allocates lots of intermediate memory
4123// - avoids problem of streaming data between subsystems
4124// - avoids explicit window management
4125// performance
4126// - uses stb_zlib, a PD zlib implementation with fast huffman decoding
4127
4128#ifndef STBI_NO_PNG
4129typedef struct
4130{
4131 stbi__uint32 length;
4132 stbi__uint32 type;
4133} stbi__pngchunk;
4134
4135static stbi__pngchunk stbi__get_chunk_header(stbi__context *s)
4136{
4137 stbi__pngchunk c;
4138 c.length = stbi__get32be(s);
4139 c.type = stbi__get32be(s);
4140 return c;
4141}
4142
4143static int stbi__check_png_header(stbi__context *s)
4144{
4145 static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 };
4146 int i;
4147 for (i=0; i < 8; ++i)
4148 if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG");
4149 return 1;
4150}
4151
4152typedef struct
4153{
4154 stbi__context *s;
4155 stbi_uc *idata, *expanded, *out;
4156 int depth;
4157} stbi__png;
4158
4159
4160enum {
4161 STBI__F_none=0,
4162 STBI__F_sub=1,
4163 STBI__F_up=2,
4164 STBI__F_avg=3,
4165 STBI__F_paeth=4,
4166 // synthetic filters used for first scanline to avoid needing a dummy row of 0s
4167 STBI__F_avg_first,
4168 STBI__F_paeth_first
4169};
4170
4171static stbi_uc first_row_filter[5] =
4172{
4173 STBI__F_none,
4174 STBI__F_sub,
4175 STBI__F_none,
4176 STBI__F_avg_first,
4177 STBI__F_paeth_first
4178};
4179
4180static int stbi__paeth(int a, int b, int c)
4181{
4182 int p = a + b - c;
4183 int pa = abs(p-a);
4184 int pb = abs(p-b);
4185 int pc = abs(p-c);
4186 if (pa <= pb && pa <= pc) return a;
4187 if (pb <= pc) return b;
4188 return c;
4189}
4190
4191static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
4192
4193// create the png data from post-deflated data
4194static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
4195{
4196 int bytes = (depth == 16? 2 : 1);
4197 stbi__context *s = a->s;
4198 stbi__uint32 i,j,stride = x*out_n*bytes;
4199 stbi__uint32 img_len, img_width_bytes;
4200 int k;
4201 int img_n = s->img_n; // copy it into a local for later
4202
4203 int output_bytes = out_n*bytes;
4204 int filter_bytes = img_n*bytes;
4205 int width = x;
4206
4207 STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);
4208 a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into
4209 if (!a->out) return stbi__err("outofmem", "Out of memory");
4210
4211 if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG");
4212 img_width_bytes = (((img_n * x * depth) + 7) >> 3);
4213 img_len = (img_width_bytes + 1) * y;
4214
4215 // we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
4216 // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros),
4217 // so just check for raw_len < img_len always.
4218 if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
4219
4220 for (j=0; j < y; ++j) {
4221 stbi_uc *cur = a->out + stride*j;
4222 stbi_uc *prior;
4223 int filter = *raw++;
4224
4225 if (filter > 4)
4226 return stbi__err("invalid filter","Corrupt PNG");
4227
4228 if (depth < 8) {
4229 if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG");
4230 cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
4231 filter_bytes = 1;
4232 width = img_width_bytes;
4233 }
4234 prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above
4235
4236 // if first row, use special filter that doesn't sample previous row
4237 if (j == 0) filter = first_row_filter[filter];
4238
4239 // handle first byte explicitly
4240 for (k=0; k < filter_bytes; ++k) {
4241 switch (filter) {
4242 case STBI__F_none : cur[k] = raw[k]; break;
4243 case STBI__F_sub : cur[k] = raw[k]; break;
4244 case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
4245 case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
4246 case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;
4247 case STBI__F_avg_first : cur[k] = raw[k]; break;
4248 case STBI__F_paeth_first: cur[k] = raw[k]; break;
4249 }
4250 }
4251
4252 if (depth == 8) {
4253 if (img_n != out_n)
4254 cur[img_n] = 255; // first pixel
4255 raw += img_n;
4256 cur += out_n;
4257 prior += out_n;
4258 } else if (depth == 16) {
4259 if (img_n != out_n) {
4260 cur[filter_bytes] = 255; // first pixel top byte
4261 cur[filter_bytes+1] = 255; // first pixel bottom byte
4262 }
4263 raw += filter_bytes;
4264 cur += output_bytes;
4265 prior += output_bytes;
4266 } else {
4267 raw += 1;
4268 cur += 1;
4269 prior += 1;
4270 }
4271
4272 // this is a little gross, so that we don't switch per-pixel or per-component
4273 if (depth < 8 || img_n == out_n) {
4274 int nk = (width - 1)*filter_bytes;
4275 #define STBI__CASE(f) \
4276 case f: \
4277 for (k=0; k < nk; ++k)
4278 switch (filter) {
4279 // "none" filter turns into a memcpy here; make that explicit.
4280 case STBI__F_none: memcpy(cur, raw, nk); break;
4281 STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;
4282 STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
4283 STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;
4284 STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;
4285 STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;
4286 STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break;
4287 }
4288 #undef STBI__CASE
4289 raw += nk;
4290 } else {
4291 STBI_ASSERT(img_n+1 == out_n);
4292 #define STBI__CASE(f) \
4293 case f: \
4294 for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
4295 for (k=0; k < filter_bytes; ++k)
4296 switch (filter) {
4297 STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break;
4298 STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;
4299 STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
4300 STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;
4301 STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;
4302 STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;
4303 STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break;
4304 }
4305 #undef STBI__CASE
4306
4307 // the loop above sets the high byte of the pixels' alpha, but for
4308 // 16 bit png files we also need the low byte set. we'll do that here.
4309 if (depth == 16) {
4310 cur = a->out + stride*j; // start at the beginning of the row again
4311 for (i=0; i < x; ++i,cur+=output_bytes) {
4312 cur[filter_bytes+1] = 255;
4313 }
4314 }
4315 }
4316 }
4317
4318 // we make a separate pass to expand bits to pixels; for performance,
4319 // this could run two scanlines behind the above code, so it won't
4320 // intefere with filtering but will still be in the cache.
4321 if (depth < 8) {
4322 for (j=0; j < y; ++j) {
4323 stbi_uc *cur = a->out + stride*j;
4324 stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes;
4325 // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit
4326 // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop
4327 stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
4328
4329 // note that the final byte might overshoot and write more data than desired.
4330 // we can allocate enough data that this never writes out of memory, but it
4331 // could also overwrite the next scanline. can it overwrite non-empty data
4332 // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.
4333 // so we need to explicitly clamp the final ones
4334
4335 if (depth == 4) {
4336 for (k=x*img_n; k >= 2; k-=2, ++in) {
4337 *cur++ = scale * ((*in >> 4) );
4338 *cur++ = scale * ((*in ) & 0x0f);
4339 }
4340 if (k > 0) *cur++ = scale * ((*in >> 4) );
4341 } else if (depth == 2) {
4342 for (k=x*img_n; k >= 4; k-=4, ++in) {
4343 *cur++ = scale * ((*in >> 6) );
4344 *cur++ = scale * ((*in >> 4) & 0x03);
4345 *cur++ = scale * ((*in >> 2) & 0x03);
4346 *cur++ = scale * ((*in ) & 0x03);
4347 }
4348 if (k > 0) *cur++ = scale * ((*in >> 6) );
4349 if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
4350 if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
4351 } else if (depth == 1) {
4352 for (k=x*img_n; k >= 8; k-=8, ++in) {
4353 *cur++ = scale * ((*in >> 7) );
4354 *cur++ = scale * ((*in >> 6) & 0x01);
4355 *cur++ = scale * ((*in >> 5) & 0x01);
4356 *cur++ = scale * ((*in >> 4) & 0x01);
4357 *cur++ = scale * ((*in >> 3) & 0x01);
4358 *cur++ = scale * ((*in >> 2) & 0x01);
4359 *cur++ = scale * ((*in >> 1) & 0x01);
4360 *cur++ = scale * ((*in ) & 0x01);
4361 }
4362 if (k > 0) *cur++ = scale * ((*in >> 7) );
4363 if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
4364 if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
4365 if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
4366 if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
4367 if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
4368 if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
4369 }
4370 if (img_n != out_n) {
4371 int q;
4372 // insert alpha = 255
4373 cur = a->out + stride*j;
4374 if (img_n == 1) {
4375 for (q=x-1; q >= 0; --q) {
4376 cur[q*2+1] = 255;
4377 cur[q*2+0] = cur[q];
4378 }
4379 } else {
4380 STBI_ASSERT(img_n == 3);
4381 for (q=x-1; q >= 0; --q) {
4382 cur[q*4+3] = 255;
4383 cur[q*4+2] = cur[q*3+2];
4384 cur[q*4+1] = cur[q*3+1];
4385 cur[q*4+0] = cur[q*3+0];
4386 }
4387 }
4388 }
4389 }
4390 } else if (depth == 16) {
4391 // force the image data from big-endian to platform-native.
4392 // this is done in a separate pass due to the decoding relying
4393 // on the data being untouched, but could probably be done
4394 // per-line during decode if care is taken.
4395 stbi_uc *cur = a->out;
4396 stbi__uint16 *cur16 = (stbi__uint16*)cur;
4397
4398 for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
4399 *cur16 = (cur[0] << 8) | cur[1];
4400 }
4401 }
4402
4403 return 1;
4404}
4405
4406static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced)
4407{
4408 int bytes = (depth == 16 ? 2 : 1);
4409 int out_bytes = out_n * bytes;
4410 stbi_uc *final;
4411 int p;
4412 if (!interlaced)
4413 return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color);
4414
4415 // de-interlacing
4416 final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0);
4417 if (!final) return stbi__err("outofmem", "Out of memory");
4418 for (p=0; p < 7; ++p) {
4419 int xorig[] = { 0,4,0,2,0,1,0 };
4420 int yorig[] = { 0,0,4,0,2,0,1 };
4421 int xspc[] = { 8,8,4,4,2,2,1 };
4422 int yspc[] = { 8,8,8,4,4,2,2 };
4423 int i,j,x,y;
4424 // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1
4425 x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p];
4426 y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p];
4427 if (x && y) {
4428 stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y;
4429 if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) {
4430 STBI_FREE(final);
4431 return 0;
4432 }
4433 for (j=0; j < y; ++j) {
4434 for (i=0; i < x; ++i) {
4435 int out_y = j*yspc[p]+yorig[p];
4436 int out_x = i*xspc[p]+xorig[p];
4437 memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes,
4438 a->out + (j*x+i)*out_bytes, out_bytes);
4439 }
4440 }
4441 STBI_FREE(a->out);
4442 image_data += img_len;
4443 image_data_len -= img_len;
4444 }
4445 }
4446 a->out = final;
4447
4448 return 1;
4449}
4450
4451static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n)
4452{
4453 stbi__context *s = z->s;
4454 stbi__uint32 i, pixel_count = s->img_x * s->img_y;
4455 stbi_uc *p = z->out;
4456
4457 // compute color-based transparency, assuming we've
4458 // already got 255 as the alpha value in the output
4459 STBI_ASSERT(out_n == 2 || out_n == 4);
4460
4461 if (out_n == 2) {
4462 for (i=0; i < pixel_count; ++i) {
4463 p[1] = (p[0] == tc[0] ? 0 : 255);
4464 p += 2;
4465 }
4466 } else {
4467 for (i=0; i < pixel_count; ++i) {
4468 if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
4469 p[3] = 0;
4470 p += 4;
4471 }
4472 }
4473 return 1;
4474}
4475
4476static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n)
4477{
4478 stbi__context *s = z->s;
4479 stbi__uint32 i, pixel_count = s->img_x * s->img_y;
4480 stbi__uint16 *p = (stbi__uint16*) z->out;
4481
4482 // compute color-based transparency, assuming we've
4483 // already got 65535 as the alpha value in the output
4484 STBI_ASSERT(out_n == 2 || out_n == 4);
4485
4486 if (out_n == 2) {
4487 for (i = 0; i < pixel_count; ++i) {
4488 p[1] = (p[0] == tc[0] ? 0 : 65535);
4489 p += 2;
4490 }
4491 } else {
4492 for (i = 0; i < pixel_count; ++i) {
4493 if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
4494 p[3] = 0;
4495 p += 4;
4496 }
4497 }
4498 return 1;
4499}
4500
4501static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n)
4502{
4503 stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;
4504 stbi_uc *p, *temp_out, *orig = a->out;
4505
4506 p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0);
4507 if (p == NULL) return stbi__err("outofmem", "Out of memory");
4508
4509 // between here and free(out) below, exitting would leak
4510 temp_out = p;
4511
4512 if (pal_img_n == 3) {
4513 for (i=0; i < pixel_count; ++i) {
4514 int n = orig[i]*4;
4515 p[0] = palette[n ];
4516 p[1] = palette[n+1];
4517 p[2] = palette[n+2];
4518 p += 3;
4519 }
4520 } else {
4521 for (i=0; i < pixel_count; ++i) {
4522 int n = orig[i]*4;
4523 p[0] = palette[n ];
4524 p[1] = palette[n+1];
4525 p[2] = palette[n+2];
4526 p[3] = palette[n+3];
4527 p += 4;
4528 }
4529 }
4530 STBI_FREE(a->out);
4531 a->out = temp_out;
4532
4533 STBI_NOTUSED(len);
4534
4535 return 1;
4536}
4537
4538static int stbi__unpremultiply_on_load_global = 0;
4539static int stbi__de_iphone_flag_global = 0;
4540
4541STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)
4542{
4543 stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply;
4544}
4545
4546STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
4547{
4548 stbi__de_iphone_flag_global = flag_true_if_should_convert;
4549}
4550
4551#ifndef STBI_THREAD_LOCAL
4552#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global
4553#define stbi__de_iphone_flag stbi__de_iphone_flag_global
4554#else
4555static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set;
4556static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set;
4557
4558STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply)
4559{
4560 stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply;
4561 stbi__unpremultiply_on_load_set = 1;
4562}
4563
4564STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert)
4565{
4566 stbi__de_iphone_flag_local = flag_true_if_should_convert;
4567 stbi__de_iphone_flag_set = 1;
4568}
4569
4570#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \
4571 ? stbi__unpremultiply_on_load_local \
4572 : stbi__unpremultiply_on_load_global)
4573#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \
4574 ? stbi__de_iphone_flag_local \
4575 : stbi__de_iphone_flag_global)
4576#endif // STBI_THREAD_LOCAL
4577
4578static void stbi__de_iphone(stbi__png *z)
4579{
4580 stbi__context *s = z->s;
4581 stbi__uint32 i, pixel_count = s->img_x * s->img_y;
4582 stbi_uc *p = z->out;
4583
4584 if (s->img_out_n == 3) { // convert bgr to rgb
4585 for (i=0; i < pixel_count; ++i) {
4586 stbi_uc t = p[0];
4587 p[0] = p[2];
4588 p[2] = t;
4589 p += 3;
4590 }
4591 } else {
4592 STBI_ASSERT(s->img_out_n == 4);
4593 if (stbi__unpremultiply_on_load) {
4594 // convert bgr to rgb and unpremultiply
4595 for (i=0; i < pixel_count; ++i) {
4596 stbi_uc a = p[3];
4597 stbi_uc t = p[0];
4598 if (a) {
4599 stbi_uc half = a / 2;
4600 p[0] = (p[2] * 255 + half) / a;
4601 p[1] = (p[1] * 255 + half) / a;
4602 p[2] = ( t * 255 + half) / a;
4603 } else {
4604 p[0] = p[2];
4605 p[2] = t;
4606 }
4607 p += 4;
4608 }
4609 } else {
4610 // convert bgr to rgb
4611 for (i=0; i < pixel_count; ++i) {
4612 stbi_uc t = p[0];
4613 p[0] = p[2];
4614 p[2] = t;
4615 p += 4;
4616 }
4617 }
4618 }
4619}
4620
4621#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d))
4622
4623static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
4624{
4625 stbi_uc palette[1024], pal_img_n=0;
4626 stbi_uc has_trans=0, tc[3]={0};
4627 stbi__uint16 tc16[3];
4628 stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;
4629 int first=1,k,interlace=0, color=0, is_iphone=0;
4630 stbi__context *s = z->s;
4631
4632 z->expanded = NULL;
4633 z->idata = NULL;
4634 z->out = NULL;
4635
4636 if (!stbi__check_png_header(s)) return 0;
4637
4638 if (scan == STBI__SCAN_type) return 1;
4639
4640 for (;;) {
4641 stbi__pngchunk c = stbi__get_chunk_header(s);
4642 switch (c.type) {
4643 case STBI__PNG_TYPE('C','g','B','I'):
4644 is_iphone = 1;
4645 stbi__skip(s, c.length);
4646 break;
4647 case STBI__PNG_TYPE('I','H','D','R'): {
4648 int comp,filter;
4649 if (!first) return stbi__err("multiple IHDR","Corrupt PNG");
4650 first = 0;
4651 if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG");
4652 s->img_x = stbi__get32be(s);
4653 s->img_y = stbi__get32be(s);
4654 if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
4655 if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
4656 z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only");
4657 color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG");
4658 if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG");
4659 if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG");
4660 comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG");
4661 filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG");
4662 interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG");
4663 if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG");
4664 if (!pal_img_n) {
4665 s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
4666 if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode");
4667 } else {
4668 // if paletted, then pal_n is our final components, and
4669 // img_n is # components to decompress/filter.
4670 s->img_n = 1;
4671 if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG");
4672 }
4673 // even with SCAN_header, have to scan to see if we have a tRNS
4674 break;
4675 }
4676
4677 case STBI__PNG_TYPE('P','L','T','E'): {
4678 if (first) return stbi__err("first not IHDR", "Corrupt PNG");
4679 if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG");
4680 pal_len = c.length / 3;
4681 if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG");
4682 for (i=0; i < pal_len; ++i) {
4683 palette[i*4+0] = stbi__get8(s);
4684 palette[i*4+1] = stbi__get8(s);
4685 palette[i*4+2] = stbi__get8(s);
4686 palette[i*4+3] = 255;
4687 }
4688 break;
4689 }
4690
4691 case STBI__PNG_TYPE('t','R','N','S'): {
4692 if (first) return stbi__err("first not IHDR", "Corrupt PNG");
4693 if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG");
4694 if (pal_img_n) {
4695 if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; }
4696 if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG");
4697 if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG");
4698 pal_img_n = 4;
4699 for (i=0; i < c.length; ++i)
4700 palette[i*4+3] = stbi__get8(s);
4701 } else {
4702 if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG");
4703 if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG");
4704 has_trans = 1;
4705 // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now.
4706 if (scan == STBI__SCAN_header) { ++s->img_n; return 1; }
4707 if (z->depth == 16) {
4708 for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
4709 } else {
4710 for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
4711 }
4712 }
4713 break;
4714 }
4715
4716 case STBI__PNG_TYPE('I','D','A','T'): {
4717 if (first) return stbi__err("first not IHDR", "Corrupt PNG");
4718 if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG");
4719 if (scan == STBI__SCAN_header) {
4720 // header scan definitely stops at first IDAT
4721 if (pal_img_n)
4722 s->img_n = pal_img_n;
4723 return 1;
4724 }
4725 if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes");
4726 if ((int)(ioff + c.length) < (int)ioff) return 0;
4727 if (ioff + c.length > idata_limit) {
4728 stbi__uint32 idata_limit_old = idata_limit;
4729 stbi_uc *p;
4730 if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;
4731 while (ioff + c.length > idata_limit)
4732 idata_limit *= 2;
4733 STBI_NOTUSED(idata_limit_old);
4734 p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory");
4735 z->idata = p;
4736 }
4737 if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG");
4738 ioff += c.length;
4739 break;
4740 }
4741
4742 case STBI__PNG_TYPE('I','E','N','D'): {
4743 stbi__uint32 raw_len, bpl;
4744 if (first) return stbi__err("first not IHDR", "Corrupt PNG");
4745 if (scan != STBI__SCAN_load) return 1;
4746 if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG");
4747 // initial guess for decoded data size to avoid unnecessary reallocs
4748 bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component
4749 raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */;
4750 z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone);
4751 if (z->expanded == NULL) return 0; // zlib should set error
4752 STBI_FREE(z->idata); z->idata = NULL;
4753 if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)
4754 s->img_out_n = s->img_n+1;
4755 else
4756 s->img_out_n = s->img_n;
4757 if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0;
4758 if (has_trans) {
4759 if (z->depth == 16) {
4760 if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0;
4761 } else {
4762 if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0;
4763 }
4764 }
4765 if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2)
4766 stbi__de_iphone(z);
4767 if (pal_img_n) {
4768 // pal_img_n == 3 or 4
4769 s->img_n = pal_img_n; // record the actual colors we had
4770 s->img_out_n = pal_img_n;
4771 if (req_comp >= 3) s->img_out_n = req_comp;
4772 if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n))
4773 return 0;
4774 } else if (has_trans) {
4775 // non-paletted image with tRNS -> source image has (constant) alpha
4776 ++s->img_n;
4777 }
4778 STBI_FREE(z->expanded); z->expanded = NULL;
4779 // end of PNG chunk, read and skip CRC
4780 stbi__get32be(s);
4781 return 1;
4782 }
4783
4784 default:
4785 // if critical, fail
4786 if (first) return stbi__err("first not IHDR", "Corrupt PNG");
4787 if ((c.type & (1 << 29)) == 0) {
4788 #ifndef STBI_NO_FAILURE_STRINGS
4789 // not threadsafe
4790 static char invalid_chunk[] = "XXXX PNG chunk not known";
4791 invalid_chunk[0] = STBI__BYTECAST(c.type >> 24);
4792 invalid_chunk[1] = STBI__BYTECAST(c.type >> 16);
4793 invalid_chunk[2] = STBI__BYTECAST(c.type >> 8);
4794 invalid_chunk[3] = STBI__BYTECAST(c.type >> 0);
4795 #endif
4796 return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type");
4797 }
4798 stbi__skip(s, c.length);
4799 break;
4800 }
4801 // end of PNG chunk, read and skip CRC
4802 stbi__get32be(s);
4803 }
4804}
4805
4806static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri)
4807{
4808 void *result=NULL;
4809 if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
4810 if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {
4811 if (p->depth <= 8)
4812 ri->bits_per_channel = 8;
4813 else if (p->depth == 16)
4814 ri->bits_per_channel = 16;
4815 else
4816 return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth");
4817 result = p->out;
4818 p->out = NULL;
4819 if (req_comp && req_comp != p->s->img_out_n) {
4820 if (ri->bits_per_channel == 8)
4821 result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
4822 else
4823 result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
4824 p->s->img_out_n = req_comp;
4825 if (result == NULL) return result;
4826 }
4827 *x = p->s->img_x;
4828 *y = p->s->img_y;
4829 if (n) *n = p->s->img_n;
4830 }
4831 STBI_FREE(p->out); p->out = NULL;
4832 STBI_FREE(p->expanded); p->expanded = NULL;
4833 STBI_FREE(p->idata); p->idata = NULL;
4834
4835 return result;
4836}
4837
4838static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
4839{
4840 stbi__png p;
4841 p.s = s;
4842 return stbi__do_png(&p, x,y,comp,req_comp, ri);
4843}
4844
4845static int stbi__png_test(stbi__context *s)
4846{
4847 int r;
4848 r = stbi__check_png_header(s);
4849 stbi__rewind(s);
4850 return r;
4851}
4852
4853static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp)
4854{
4855 if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) {
4856 stbi__rewind( p->s );
4857 return 0;
4858 }
4859 if (x) *x = p->s->img_x;
4860 if (y) *y = p->s->img_y;
4861 if (comp) *comp = p->s->img_n;
4862 return 1;
4863}
4864
4865static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp)
4866{
4867 stbi__png p;
4868 p.s = s;
4869 return stbi__png_info_raw(&p, x, y, comp);
4870}
4871
4872static int stbi__png_is16(stbi__context *s)
4873{
4874 stbi__png p;
4875 p.s = s;
4876 if (!stbi__png_info_raw(&p, NULL, NULL, NULL))
4877 return 0;
4878 if (p.depth != 16) {
4879 stbi__rewind(p.s);
4880 return 0;
4881 }
4882 return 1;
4883}
4884#endif
4885
4886// Microsoft/Windows BMP image
4887
4888#ifndef STBI_NO_BMP
4889static int stbi__bmp_test_raw(stbi__context *s)
4890{
4891 int r;
4892 int sz;
4893 if (stbi__get8(s) != 'B') return 0;
4894 if (stbi__get8(s) != 'M') return 0;
4895 stbi__get32le(s); // discard filesize
4896 stbi__get16le(s); // discard reserved
4897 stbi__get16le(s); // discard reserved
4898 stbi__get32le(s); // discard data offset
4899 sz = stbi__get32le(s);
4900 r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124);
4901 return r;
4902}
4903
4904static int stbi__bmp_test(stbi__context *s)
4905{
4906 int r = stbi__bmp_test_raw(s);
4907 stbi__rewind(s);
4908 return r;
4909}
4910
4911
4912// returns 0..31 for the highest set bit
4913static int stbi__high_bit(unsigned int z)
4914{
4915 int n=0;
4916 if (z == 0) return -1;
4917 if (z >= 0x10000) { n += 16; z >>= 16; }
4918 if (z >= 0x00100) { n += 8; z >>= 8; }
4919 if (z >= 0x00010) { n += 4; z >>= 4; }
4920 if (z >= 0x00004) { n += 2; z >>= 2; }
4921 if (z >= 0x00002) { n += 1;/* >>= 1;*/ }
4922 return n;
4923}
4924
4925static int stbi__bitcount(unsigned int a)
4926{
4927 a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2
4928 a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4
4929 a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits
4930 a = (a + (a >> 8)); // max 16 per 8 bits
4931 a = (a + (a >> 16)); // max 32 per 8 bits
4932 return a & 0xff;
4933}
4934
4935// extract an arbitrarily-aligned N-bit value (N=bits)
4936// from v, and then make it 8-bits long and fractionally
4937// extend it to full full range.
4938static int stbi__shiftsigned(unsigned int v, int shift, int bits)
4939{
4940 static unsigned int mul_table[9] = {
4941 0,
4942 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/,
4943 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/,
4944 };
4945 static unsigned int shift_table[9] = {
4946 0, 0,0,1,0,2,4,6,0,
4947 };
4948 if (shift < 0)
4949 v <<= -shift;
4950 else
4951 v >>= shift;
4952 STBI_ASSERT(v < 256);
4953 v >>= (8-bits);
4954 STBI_ASSERT(bits >= 0 && bits <= 8);
4955 return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits];
4956}
4957
4958typedef struct
4959{
4960 int bpp, offset, hsz;
4961 unsigned int mr,mg,mb,ma, all_a;
4962 int extra_read;
4963} stbi__bmp_data;
4964
4965static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress)
4966{
4967 // BI_BITFIELDS specifies masks explicitly, don't override
4968 if (compress == 3)
4969 return 1;
4970
4971 if (compress == 0) {
4972 if (info->bpp == 16) {
4973 info->mr = 31u << 10;
4974 info->mg = 31u << 5;
4975 info->mb = 31u << 0;
4976 } else if (info->bpp == 32) {
4977 info->mr = 0xffu << 16;
4978 info->mg = 0xffu << 8;
4979 info->mb = 0xffu << 0;
4980 info->ma = 0xffu << 24;
4981 info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0
4982 } else {
4983 // otherwise, use defaults, which is all-0
4984 info->mr = info->mg = info->mb = info->ma = 0;
4985 }
4986 return 1;
4987 }
4988 return 0; // error
4989}
4990
4991static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
4992{
4993 int hsz;
4994 if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP");
4995 stbi__get32le(s); // discard filesize
4996 stbi__get16le(s); // discard reserved
4997 stbi__get16le(s); // discard reserved
4998 info->offset = stbi__get32le(s);
4999 info->hsz = hsz = stbi__get32le(s);
5000 info->mr = info->mg = info->mb = info->ma = 0;
5001 info->extra_read = 14;
5002
5003 if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP");
5004
5005 if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown");
5006 if (hsz == 12) {
5007 s->img_x = stbi__get16le(s);
5008 s->img_y = stbi__get16le(s);
5009 } else {
5010 s->img_x = stbi__get32le(s);
5011 s->img_y = stbi__get32le(s);
5012 }
5013 if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP");
5014 info->bpp = stbi__get16le(s);
5015 if (hsz != 12) {
5016 int compress = stbi__get32le(s);
5017 if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE");
5018 if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes
5019 if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel
5020 stbi__get32le(s); // discard sizeof
5021 stbi__get32le(s); // discard hres
5022 stbi__get32le(s); // discard vres
5023 stbi__get32le(s); // discard colorsused
5024 stbi__get32le(s); // discard max important
5025 if (hsz == 40 || hsz == 56) {
5026 if (hsz == 56) {
5027 stbi__get32le(s);
5028 stbi__get32le(s);
5029 stbi__get32le(s);
5030 stbi__get32le(s);
5031 }
5032 if (info->bpp == 16 || info->bpp == 32) {
5033 if (compress == 0) {
5034 stbi__bmp_set_mask_defaults(info, compress);
5035 } else if (compress == 3) {
5036 info->mr = stbi__get32le(s);
5037 info->mg = stbi__get32le(s);
5038 info->mb = stbi__get32le(s);
5039 info->extra_read += 12;
5040 // not documented, but generated by photoshop and handled by mspaint
5041 if (info->mr == info->mg && info->mg == info->mb) {
5042 // ?!?!?
5043 return stbi__errpuc("bad BMP", "bad BMP");
5044 }
5045 } else
5046 return stbi__errpuc("bad BMP", "bad BMP");
5047 }
5048 } else {
5049 // V4/V5 header
5050 int i;
5051 if (hsz != 108 && hsz != 124)
5052 return stbi__errpuc("bad BMP", "bad BMP");
5053 info->mr = stbi__get32le(s);
5054 info->mg = stbi__get32le(s);
5055 info->mb = stbi__get32le(s);
5056 info->ma = stbi__get32le(s);
5057 if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs
5058 stbi__bmp_set_mask_defaults(info, compress);
5059 stbi__get32le(s); // discard color space
5060 for (i=0; i < 12; ++i)
5061 stbi__get32le(s); // discard color space parameters
5062 if (hsz == 124) {
5063 stbi__get32le(s); // discard rendering intent
5064 stbi__get32le(s); // discard offset of profile data
5065 stbi__get32le(s); // discard size of profile data
5066 stbi__get32le(s); // discard reserved
5067 }
5068 }
5069 }
5070 return (void *) 1;
5071}
5072
5073
5074static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
5075{
5076 stbi_uc *out;
5077 unsigned int mr=0,mg=0,mb=0,ma=0, all_a;
5078 stbi_uc pal[256][4];
5079 int psize=0,i,j,width;
5080 int flip_vertically, pad, target;
5081 stbi__bmp_data info;
5082 STBI_NOTUSED(ri);
5083
5084 info.all_a = 255;
5085 if (stbi__bmp_parse_header(s, &info) == NULL)
5086 return NULL; // error code already set
5087
5088 flip_vertically = ((int) s->img_y) > 0;
5089 s->img_y = abs((int) s->img_y);
5090
5091 if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
5092 if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
5093
5094 mr = info.mr;
5095 mg = info.mg;
5096 mb = info.mb;
5097 ma = info.ma;
5098 all_a = info.all_a;
5099
5100 if (info.hsz == 12) {
5101 if (info.bpp < 24)
5102 psize = (info.offset - info.extra_read - 24) / 3;
5103 } else {
5104 if (info.bpp < 16)
5105 psize = (info.offset - info.extra_read - info.hsz) >> 2;
5106 }
5107 if (psize == 0) {
5108 // accept some number of extra bytes after the header, but if the offset points either to before
5109 // the header ends or implies a large amount of extra data, reject the file as malformed
5110 int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original);
5111 int header_limit = 1024; // max we actually read is below 256 bytes currently.
5112 int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size.
5113 if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) {
5114 return stbi__errpuc("bad header", "Corrupt BMP");
5115 }
5116 // we established that bytes_read_so_far is positive and sensible.
5117 // the first half of this test rejects offsets that are either too small positives, or
5118 // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn
5119 // ensures the number computed in the second half of the test can't overflow.
5120 if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) {
5121 return stbi__errpuc("bad offset", "Corrupt BMP");
5122 } else {
5123 stbi__skip(s, info.offset - bytes_read_so_far);
5124 }
5125 }
5126
5127 if (info.bpp == 24 && ma == 0xff000000)
5128 s->img_n = 3;
5129 else
5130 s->img_n = ma ? 4 : 3;
5131 if (req_comp && req_comp >= 3) // we can directly decode 3 or 4
5132 target = req_comp;
5133 else
5134 target = s->img_n; // if they want monochrome, we'll post-convert
5135
5136 // sanity-check size
5137 if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0))
5138 return stbi__errpuc("too large", "Corrupt BMP");
5139
5140 out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0);
5141 if (!out) return stbi__errpuc("outofmem", "Out of memory");
5142 if (info.bpp < 16) {
5143 int z=0;
5144 if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); }
5145 for (i=0; i < psize; ++i) {
5146 pal[i][2] = stbi__get8(s);
5147 pal[i][1] = stbi__get8(s);
5148 pal[i][0] = stbi__get8(s);
5149 if (info.hsz != 12) stbi__get8(s);
5150 pal[i][3] = 255;
5151 }
5152 stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4));
5153 if (info.bpp == 1) width = (s->img_x + 7) >> 3;
5154 else if (info.bpp == 4) width = (s->img_x + 1) >> 1;
5155 else if (info.bpp == 8) width = s->img_x;
5156 else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); }
5157 pad = (-width)&3;
5158 if (info.bpp == 1) {
5159 for (j=0; j < (int) s->img_y; ++j) {
5160 int bit_offset = 7, v = stbi__get8(s);
5161 for (i=0; i < (int) s->img_x; ++i) {
5162 int color = (v>>bit_offset)&0x1;
5163 out[z++] = pal[color][0];
5164 out[z++] = pal[color][1];
5165 out[z++] = pal[color][2];
5166 if (target == 4) out[z++] = 255;
5167 if (i+1 == (int) s->img_x) break;
5168 if((--bit_offset) < 0) {
5169 bit_offset = 7;
5170 v = stbi__get8(s);
5171 }
5172 }
5173 stbi__skip(s, pad);
5174 }
5175 } else {
5176 for (j=0; j < (int) s->img_y; ++j) {
5177 for (i=0; i < (int) s->img_x; i += 2) {
5178 int v=stbi__get8(s),v2=0;
5179 if (info.bpp == 4) {
5180 v2 = v & 15;
5181 v >>= 4;
5182 }
5183 out[z++] = pal[v][0];
5184 out[z++] = pal[v][1];
5185 out[z++] = pal[v][2];
5186 if (target == 4) out[z++] = 255;
5187 if (i+1 == (int) s->img_x) break;
5188 v = (info.bpp == 8) ? stbi__get8(s) : v2;
5189 out[z++] = pal[v][0];
5190 out[z++] = pal[v][1];
5191 out[z++] = pal[v][2];
5192 if (target == 4) out[z++] = 255;
5193 }
5194 stbi__skip(s, pad);
5195 }
5196 }
5197 } else {
5198 int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0;
5199 int z = 0;
5200 int easy=0;
5201 stbi__skip(s, info.offset - info.extra_read - info.hsz);
5202 if (info.bpp == 24) width = 3 * s->img_x;
5203 else if (info.bpp == 16) width = 2*s->img_x;
5204 else /* bpp = 32 and pad = 0 */ width=0;
5205 pad = (-width) & 3;
5206 if (info.bpp == 24) {
5207 easy = 1;
5208 } else if (info.bpp == 32) {
5209 if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)
5210 easy = 2;
5211 }
5212 if (!easy) {
5213 if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); }
5214 // right shift amt to put high bit in position #7
5215 rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr);
5216 gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg);
5217 bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb);
5218 ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma);
5219 if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); }
5220 }
5221 for (j=0; j < (int) s->img_y; ++j) {
5222 if (easy) {
5223 for (i=0; i < (int) s->img_x; ++i) {
5224 unsigned char a;
5225 out[z+2] = stbi__get8(s);
5226 out[z+1] = stbi__get8(s);
5227 out[z+0] = stbi__get8(s);
5228 z += 3;
5229 a = (easy == 2 ? stbi__get8(s) : 255);
5230 all_a |= a;
5231 if (target == 4) out[z++] = a;
5232 }
5233 } else {
5234 int bpp = info.bpp;
5235 for (i=0; i < (int) s->img_x; ++i) {
5236 stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s));
5237 unsigned int a;
5238 out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount));
5239 out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount));
5240 out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount));
5241 a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);
5242 all_a |= a;
5243 if (target == 4) out[z++] = STBI__BYTECAST(a);
5244 }
5245 }
5246 stbi__skip(s, pad);
5247 }
5248 }
5249
5250 // if alpha channel is all 0s, replace with all 255s
5251 if (target == 4 && all_a == 0)
5252 for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4)
5253 out[i] = 255;
5254
5255 if (flip_vertically) {
5256 stbi_uc t;
5257 for (j=0; j < (int) s->img_y>>1; ++j) {
5258 stbi_uc *p1 = out + j *s->img_x*target;
5259 stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target;
5260 for (i=0; i < (int) s->img_x*target; ++i) {
5261 t = p1[i]; p1[i] = p2[i]; p2[i] = t;
5262 }
5263 }
5264 }
5265
5266 if (req_comp && req_comp != target) {
5267 out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y);
5268 if (out == NULL) return out; // stbi__convert_format frees input on failure
5269 }
5270
5271 *x = s->img_x;
5272 *y = s->img_y;
5273 if (comp) *comp = s->img_n;
5274 return out;
5275}
5276#endif
5277
5278// Targa Truevision - TGA
5279// by Jonathan Dummer
5280#ifndef STBI_NO_TGA
5281// returns STBI_rgb or whatever, 0 on error
5282static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16)
5283{
5284 // only RGB or RGBA (incl. 16bit) or grey allowed
5285 if (is_rgb16) *is_rgb16 = 0;
5286 switch(bits_per_pixel) {
5287 case 8: return STBI_grey;
5288 case 16: if(is_grey) return STBI_grey_alpha;
5289 // fallthrough
5290 case 15: if(is_rgb16) *is_rgb16 = 1;
5291 return STBI_rgb;
5292 case 24: // fallthrough
5293 case 32: return bits_per_pixel/8;
5294 default: return 0;
5295 }
5296}
5297
5298static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp)
5299{
5300 int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp;
5301 int sz, tga_colormap_type;
5302 stbi__get8(s); // discard Offset
5303 tga_colormap_type = stbi__get8(s); // colormap type
5304 if( tga_colormap_type > 1 ) {
5305 stbi__rewind(s);
5306 return 0; // only RGB or indexed allowed
5307 }
5308 tga_image_type = stbi__get8(s); // image type
5309 if ( tga_colormap_type == 1 ) { // colormapped (paletted) image
5310 if (tga_image_type != 1 && tga_image_type != 9) {
5311 stbi__rewind(s);
5312 return 0;
5313 }
5314 stbi__skip(s,4); // skip index of first colormap entry and number of entries
5315 sz = stbi__get8(s); // check bits per palette color entry
5316 if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) {
5317 stbi__rewind(s);
5318 return 0;
5319 }
5320 stbi__skip(s,4); // skip image x and y origin
5321 tga_colormap_bpp = sz;
5322 } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE
5323 if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) {
5324 stbi__rewind(s);
5325 return 0; // only RGB or grey allowed, +/- RLE
5326 }
5327 stbi__skip(s,9); // skip colormap specification and image x/y origin
5328 tga_colormap_bpp = 0;
5329 }
5330 tga_w = stbi__get16le(s);
5331 if( tga_w < 1 ) {
5332 stbi__rewind(s);
5333 return 0; // test width
5334 }
5335 tga_h = stbi__get16le(s);
5336 if( tga_h < 1 ) {
5337 stbi__rewind(s);
5338 return 0; // test height
5339 }
5340 tga_bits_per_pixel = stbi__get8(s); // bits per pixel
5341 stbi__get8(s); // ignore alpha bits
5342 if (tga_colormap_bpp != 0) {
5343 if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) {
5344 // when using a colormap, tga_bits_per_pixel is the size of the indexes
5345 // I don't think anything but 8 or 16bit indexes makes sense
5346 stbi__rewind(s);
5347 return 0;
5348 }
5349 tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL);
5350 } else {
5351 tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL);
5352 }
5353 if(!tga_comp) {
5354 stbi__rewind(s);
5355 return 0;
5356 }
5357 if (x) *x = tga_w;
5358 if (y) *y = tga_h;
5359 if (comp) *comp = tga_comp;
5360 return 1; // seems to have passed everything
5361}
5362
5363static int stbi__tga_test(stbi__context *s)
5364{
5365 int res = 0;
5366 int sz, tga_color_type;
5367 stbi__get8(s); // discard Offset
5368 tga_color_type = stbi__get8(s); // color type
5369 if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed
5370 sz = stbi__get8(s); // image type
5371 if ( tga_color_type == 1 ) { // colormapped (paletted) image
5372 if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9
5373 stbi__skip(s,4); // skip index of first colormap entry and number of entries
5374 sz = stbi__get8(s); // check bits per palette color entry
5375 if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
5376 stbi__skip(s,4); // skip image x and y origin
5377 } else { // "normal" image w/o colormap
5378 if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE
5379 stbi__skip(s,9); // skip colormap specification and image x/y origin
5380 }
5381 if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width
5382 if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height
5383 sz = stbi__get8(s); // bits per pixel
5384 if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index
5385 if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
5386
5387 res = 1; // if we got this far, everything's good and we can return 1 instead of 0
5388
5389errorEnd:
5390 stbi__rewind(s);
5391 return res;
5392}
5393
5394// read 16bit value and convert to 24bit RGB
5395static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out)
5396{
5397 stbi__uint16 px = (stbi__uint16)stbi__get16le(s);
5398 stbi__uint16 fiveBitMask = 31;
5399 // we have 3 channels with 5bits each
5400 int r = (px >> 10) & fiveBitMask;
5401 int g = (px >> 5) & fiveBitMask;
5402 int b = px & fiveBitMask;
5403 // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later
5404 out[0] = (stbi_uc)((r * 255)/31);
5405 out[1] = (stbi_uc)((g * 255)/31);
5406 out[2] = (stbi_uc)((b * 255)/31);
5407
5408 // some people claim that the most significant bit might be used for alpha
5409 // (possibly if an alpha-bit is set in the "image descriptor byte")
5410 // but that only made 16bit test images completely translucent..
5411 // so let's treat all 15 and 16bit TGAs as RGB with no alpha.
5412}
5413
5414static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
5415{
5416 // read in the TGA header stuff
5417 int tga_offset = stbi__get8(s);
5418 int tga_indexed = stbi__get8(s);
5419 int tga_image_type = stbi__get8(s);
5420 int tga_is_RLE = 0;
5421 int tga_palette_start = stbi__get16le(s);
5422 int tga_palette_len = stbi__get16le(s);
5423 int tga_palette_bits = stbi__get8(s);
5424 int tga_x_origin = stbi__get16le(s);
5425 int tga_y_origin = stbi__get16le(s);
5426 int tga_width = stbi__get16le(s);
5427 int tga_height = stbi__get16le(s);
5428 int tga_bits_per_pixel = stbi__get8(s);
5429 int tga_comp, tga_rgb16=0;
5430 int tga_inverted = stbi__get8(s);
5431 // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?)
5432 // image data
5433 unsigned char *tga_data;
5434 unsigned char *tga_palette = NULL;
5435 int i, j;
5436 unsigned char raw_data[4] = {0};
5437 int RLE_count = 0;
5438 int RLE_repeating = 0;
5439 int read_next_pixel = 1;
5440 STBI_NOTUSED(ri);
5441 STBI_NOTUSED(tga_x_origin); // @TODO
5442 STBI_NOTUSED(tga_y_origin); // @TODO
5443
5444 if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
5445 if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
5446
5447 // do a tiny bit of precessing
5448 if ( tga_image_type >= 8 )
5449 {
5450 tga_image_type -= 8;
5451 tga_is_RLE = 1;
5452 }
5453 tga_inverted = 1 - ((tga_inverted >> 5) & 1);
5454
5455 // If I'm paletted, then I'll use the number of bits from the palette
5456 if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16);
5457 else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16);
5458
5459 if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency
5460 return stbi__errpuc("bad format", "Can't find out TGA pixelformat");
5461
5462 // tga info
5463 *x = tga_width;
5464 *y = tga_height;
5465 if (comp) *comp = tga_comp;
5466
5467 if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0))
5468 return stbi__errpuc("too large", "Corrupt TGA");
5469
5470 tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0);
5471 if (!tga_data) return stbi__errpuc("outofmem", "Out of memory");
5472
5473 // skip to the data's starting position (offset usually = 0)
5474 stbi__skip(s, tga_offset );
5475
5476 if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) {
5477 for (i=0; i < tga_height; ++i) {
5478 int row = tga_inverted ? tga_height -i - 1 : i;
5479 stbi_uc *tga_row = tga_data + row*tga_width*tga_comp;
5480 stbi__getn(s, tga_row, tga_width * tga_comp);
5481 }
5482 } else {
5483 // do I need to load a palette?
5484 if ( tga_indexed)
5485 {
5486 if (tga_palette_len == 0) { /* you have to have at least one entry! */
5487 STBI_FREE(tga_data);
5488 return stbi__errpuc("bad palette", "Corrupt TGA");
5489 }
5490
5491 // any data to skip? (offset usually = 0)
5492 stbi__skip(s, tga_palette_start );
5493 // load the palette
5494 tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0);
5495 if (!tga_palette) {
5496 STBI_FREE(tga_data);
5497 return stbi__errpuc("outofmem", "Out of memory");
5498 }
5499 if (tga_rgb16) {
5500 stbi_uc *pal_entry = tga_palette;
5501 STBI_ASSERT(tga_comp == STBI_rgb);
5502 for (i=0; i < tga_palette_len; ++i) {
5503 stbi__tga_read_rgb16(s, pal_entry);
5504 pal_entry += tga_comp;
5505 }
5506 } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) {
5507 STBI_FREE(tga_data);
5508 STBI_FREE(tga_palette);
5509 return stbi__errpuc("bad palette", "Corrupt TGA");
5510 }
5511 }
5512 // load the data
5513 for (i=0; i < tga_width * tga_height; ++i)
5514 {
5515 // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk?
5516 if ( tga_is_RLE )
5517 {
5518 if ( RLE_count == 0 )
5519 {
5520 // yep, get the next byte as a RLE command
5521 int RLE_cmd = stbi__get8(s);
5522 RLE_count = 1 + (RLE_cmd & 127);
5523 RLE_repeating = RLE_cmd >> 7;
5524 read_next_pixel = 1;
5525 } else if ( !RLE_repeating )
5526 {
5527 read_next_pixel = 1;
5528 }
5529 } else
5530 {
5531 read_next_pixel = 1;
5532 }
5533 // OK, if I need to read a pixel, do it now
5534 if ( read_next_pixel )
5535 {
5536 // load however much data we did have
5537 if ( tga_indexed )
5538 {
5539 // read in index, then perform the lookup
5540 int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s);
5541 if ( pal_idx >= tga_palette_len ) {
5542 // invalid index
5543 pal_idx = 0;
5544 }
5545 pal_idx *= tga_comp;
5546 for (j = 0; j < tga_comp; ++j) {
5547 raw_data[j] = tga_palette[pal_idx+j];
5548 }
5549 } else if(tga_rgb16) {
5550 STBI_ASSERT(tga_comp == STBI_rgb);
5551 stbi__tga_read_rgb16(s, raw_data);
5552 } else {
5553 // read in the data raw
5554 for (j = 0; j < tga_comp; ++j) {
5555 raw_data[j] = stbi__get8(s);
5556 }
5557 }
5558 // clear the reading flag for the next pixel
5559 read_next_pixel = 0;
5560 } // end of reading a pixel
5561
5562 // copy data
5563 for (j = 0; j < tga_comp; ++j)
5564 tga_data[i*tga_comp+j] = raw_data[j];
5565
5566 // in case we're in RLE mode, keep counting down
5567 --RLE_count;
5568 }
5569 // do I need to invert the image?
5570 if ( tga_inverted )
5571 {
5572 for (j = 0; j*2 < tga_height; ++j)
5573 {
5574 int index1 = j * tga_width * tga_comp;
5575 int index2 = (tga_height - 1 - j) * tga_width * tga_comp;
5576 for (i = tga_width * tga_comp; i > 0; --i)
5577 {
5578 unsigned char temp = tga_data[index1];
5579 tga_data[index1] = tga_data[index2];
5580 tga_data[index2] = temp;
5581 ++index1;
5582 ++index2;
5583 }
5584 }
5585 }
5586 // clear my palette, if I had one
5587 if ( tga_palette != NULL )
5588 {
5589 STBI_FREE( tga_palette );
5590 }
5591 }
5592
5593 // swap RGB - if the source data was RGB16, it already is in the right order
5594 if (tga_comp >= 3 && !tga_rgb16)
5595 {
5596 unsigned char* tga_pixel = tga_data;
5597 for (i=0; i < tga_width * tga_height; ++i)
5598 {
5599 unsigned char temp = tga_pixel[0];
5600 tga_pixel[0] = tga_pixel[2];
5601 tga_pixel[2] = temp;
5602 tga_pixel += tga_comp;
5603 }
5604 }
5605
5606 // convert to target component count
5607 if (req_comp && req_comp != tga_comp)
5608 tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height);
5609
5610 // the things I do to get rid of an error message, and yet keep
5611 // Microsoft's C compilers happy... [8^(
5612 tga_palette_start = tga_palette_len = tga_palette_bits =
5613 tga_x_origin = tga_y_origin = 0;
5614 STBI_NOTUSED(tga_palette_start);
5615 // OK, done
5616 return tga_data;
5617}
5618#endif
5619
5620// *************************************************************************************************
5621// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB
5622
5623#ifndef STBI_NO_PSD
5624static int stbi__psd_test(stbi__context *s)
5625{
5626 int r = (stbi__get32be(s) == 0x38425053);
5627 stbi__rewind(s);
5628 return r;
5629}
5630
5631static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount)
5632{
5633 int count, nleft, len;
5634
5635 count = 0;
5636 while ((nleft = pixelCount - count) > 0) {
5637 len = stbi__get8(s);
5638 if (len == 128) {
5639 // No-op.
5640 } else if (len < 128) {
5641 // Copy next len+1 bytes literally.
5642 len++;
5643 if (len > nleft) return 0; // corrupt data
5644 count += len;
5645 while (len) {
5646 *p = stbi__get8(s);
5647 p += 4;
5648 len--;
5649 }
5650 } else if (len > 128) {
5651 stbi_uc val;
5652 // Next -len+1 bytes in the dest are replicated from next source byte.
5653 // (Interpret len as a negative 8-bit int.)
5654 len = 257 - len;
5655 if (len > nleft) return 0; // corrupt data
5656 val = stbi__get8(s);
5657 count += len;
5658 while (len) {
5659 *p = val;
5660 p += 4;
5661 len--;
5662 }
5663 }
5664 }
5665
5666 return 1;
5667}
5668
5669static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
5670{
5671 int pixelCount;
5672 int channelCount, compression;
5673 int channel, i;
5674 int bitdepth;
5675 int w,h;
5676 stbi_uc *out;
5677 STBI_NOTUSED(ri);
5678
5679 // Check identifier
5680 if (stbi__get32be(s) != 0x38425053) // "8BPS"
5681 return stbi__errpuc("not PSD", "Corrupt PSD image");
5682
5683 // Check file type version.
5684 if (stbi__get16be(s) != 1)
5685 return stbi__errpuc("wrong version", "Unsupported version of PSD image");
5686
5687 // Skip 6 reserved bytes.
5688 stbi__skip(s, 6 );
5689
5690 // Read the number of channels (R, G, B, A, etc).
5691 channelCount = stbi__get16be(s);
5692 if (channelCount < 0 || channelCount > 16)
5693 return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image");
5694
5695 // Read the rows and columns of the image.
5696 h = stbi__get32be(s);
5697 w = stbi__get32be(s);
5698
5699 if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
5700 if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
5701
5702 // Make sure the depth is 8 bits.
5703 bitdepth = stbi__get16be(s);
5704 if (bitdepth != 8 && bitdepth != 16)
5705 return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit");
5706
5707 // Make sure the color mode is RGB.
5708 // Valid options are:
5709 // 0: Bitmap
5710 // 1: Grayscale
5711 // 2: Indexed color
5712 // 3: RGB color
5713 // 4: CMYK color
5714 // 7: Multichannel
5715 // 8: Duotone
5716 // 9: Lab color
5717 if (stbi__get16be(s) != 3)
5718 return stbi__errpuc("wrong color format", "PSD is not in RGB color format");
5719
5720 // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.)
5721 stbi__skip(s,stbi__get32be(s) );
5722
5723 // Skip the image resources. (resolution, pen tool paths, etc)
5724 stbi__skip(s, stbi__get32be(s) );
5725
5726 // Skip the reserved data.
5727 stbi__skip(s, stbi__get32be(s) );
5728
5729 // Find out if the data is compressed.
5730 // Known values:
5731 // 0: no compression
5732 // 1: RLE compressed
5733 compression = stbi__get16be(s);
5734 if (compression > 1)
5735 return stbi__errpuc("bad compression", "PSD has an unknown compression format");
5736
5737 // Check size
5738 if (!stbi__mad3sizes_valid(4, w, h, 0))
5739 return stbi__errpuc("too large", "Corrupt PSD");
5740
5741 // Create the destination image.
5742
5743 if (!compression && bitdepth == 16 && bpc == 16) {
5744 out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0);
5745 ri->bits_per_channel = 16;
5746 } else
5747 out = (stbi_uc *) stbi__malloc(4 * w*h);
5748
5749 if (!out) return stbi__errpuc("outofmem", "Out of memory");
5750 pixelCount = w*h;
5751
5752 // Initialize the data to zero.
5753 //memset( out, 0, pixelCount * 4 );
5754
5755 // Finally, the image data.
5756 if (compression) {
5757 // RLE as used by .PSD and .TIFF
5758 // Loop until you get the number of unpacked bytes you are expecting:
5759 // Read the next source byte into n.
5760 // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.
5761 // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.
5762 // Else if n is 128, noop.
5763 // Endloop
5764
5765 // The RLE-compressed data is preceded by a 2-byte data count for each row in the data,
5766 // which we're going to just skip.
5767 stbi__skip(s, h * channelCount * 2 );
5768
5769 // Read the RLE data by channel.
5770 for (channel = 0; channel < 4; channel++) {
5771 stbi_uc *p;
5772
5773 p = out+channel;
5774 if (channel >= channelCount) {
5775 // Fill this channel with default data.
5776 for (i = 0; i < pixelCount; i++, p += 4)
5777 *p = (channel == 3 ? 255 : 0);
5778 } else {
5779 // Read the RLE data.
5780 if (!stbi__psd_decode_rle(s, p, pixelCount)) {
5781 STBI_FREE(out);
5782 return stbi__errpuc("corrupt", "bad RLE data");
5783 }
5784 }
5785 }
5786
5787 } else {
5788 // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...)
5789 // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image.
5790
5791 // Read the data by channel.
5792 for (channel = 0; channel < 4; channel++) {
5793 if (channel >= channelCount) {
5794 // Fill this channel with default data.
5795 if (bitdepth == 16 && bpc == 16) {
5796 stbi__uint16 *q = ((stbi__uint16 *) out) + channel;
5797 stbi__uint16 val = channel == 3 ? 65535 : 0;
5798 for (i = 0; i < pixelCount; i++, q += 4)
5799 *q = val;
5800 } else {
5801 stbi_uc *p = out+channel;
5802 stbi_uc val = channel == 3 ? 255 : 0;
5803 for (i = 0; i < pixelCount; i++, p += 4)
5804 *p = val;
5805 }
5806 } else {
5807 if (ri->bits_per_channel == 16) { // output bpc
5808 stbi__uint16 *q = ((stbi__uint16 *) out) + channel;
5809 for (i = 0; i < pixelCount; i++, q += 4)
5810 *q = (stbi__uint16) stbi__get16be(s);
5811 } else {
5812 stbi_uc *p = out+channel;
5813 if (bitdepth == 16) { // input bpc
5814 for (i = 0; i < pixelCount; i++, p += 4)
5815 *p = (stbi_uc) (stbi__get16be(s) >> 8);
5816 } else {
5817 for (i = 0; i < pixelCount; i++, p += 4)
5818 *p = stbi__get8(s);
5819 }
5820 }
5821 }
5822 }
5823 }
5824
5825 // remove weird white matte from PSD
5826 if (channelCount >= 4) {
5827 if (ri->bits_per_channel == 16) {
5828 for (i=0; i < w*h; ++i) {
5829 stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i;
5830 if (pixel[3] != 0 && pixel[3] != 65535) {
5831 float a = pixel[3] / 65535.0f;
5832 float ra = 1.0f / a;
5833 float inv_a = 65535.0f * (1 - ra);
5834 pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a);
5835 pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a);
5836 pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a);
5837 }
5838 }
5839 } else {
5840 for (i=0; i < w*h; ++i) {
5841 unsigned char *pixel = out + 4*i;
5842 if (pixel[3] != 0 && pixel[3] != 255) {
5843 float a = pixel[3] / 255.0f;
5844 float ra = 1.0f / a;
5845 float inv_a = 255.0f * (1 - ra);
5846 pixel[0] = (unsigned char) (pixel[0]*ra + inv_a);
5847 pixel[1] = (unsigned char) (pixel[1]*ra + inv_a);
5848 pixel[2] = (unsigned char) (pixel[2]*ra + inv_a);
5849 }
5850 }
5851 }
5852 }
5853
5854 // convert to desired output format
5855 if (req_comp && req_comp != 4) {
5856 if (ri->bits_per_channel == 16)
5857 out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h);
5858 else
5859 out = stbi__convert_format(out, 4, req_comp, w, h);
5860 if (out == NULL) return out; // stbi__convert_format frees input on failure
5861 }
5862
5863 if (comp) *comp = 4;
5864 *y = h;
5865 *x = w;
5866
5867 return out;
5868}
5869#endif
5870
5871// *************************************************************************************************
5872// Softimage PIC loader
5873// by Tom Seddon
5874//
5875// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format
5876// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/
5877
5878#ifndef STBI_NO_PIC
5879static int stbi__pic_is4(stbi__context *s,const char *str)
5880{
5881 int i;
5882 for (i=0; i<4; ++i)
5883 if (stbi__get8(s) != (stbi_uc)str[i])
5884 return 0;
5885
5886 return 1;
5887}
5888
5889static int stbi__pic_test_core(stbi__context *s)
5890{
5891 int i;
5892
5893 if (!stbi__pic_is4(s,"\x53\x80\xF6\x34"))
5894 return 0;
5895
5896 for(i=0;i<84;++i)
5897 stbi__get8(s);
5898
5899 if (!stbi__pic_is4(s,"PICT"))
5900 return 0;
5901
5902 return 1;
5903}
5904
5905typedef struct
5906{
5907 stbi_uc size,type,channel;
5908} stbi__pic_packet;
5909
5910static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest)
5911{
5912 int mask=0x80, i;
5913
5914 for (i=0; i<4; ++i, mask>>=1) {
5915 if (channel & mask) {
5916 if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short");
5917 dest[i]=stbi__get8(s);
5918 }
5919 }
5920
5921 return dest;
5922}
5923
5924static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src)
5925{
5926 int mask=0x80,i;
5927
5928 for (i=0;i<4; ++i, mask>>=1)
5929 if (channel&mask)
5930 dest[i]=src[i];
5931}
5932
5933static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result)
5934{
5935 int act_comp=0,num_packets=0,y,chained;
5936 stbi__pic_packet packets[10];
5937
5938 // this will (should...) cater for even some bizarre stuff like having data
5939 // for the same channel in multiple packets.
5940 do {
5941 stbi__pic_packet *packet;
5942
5943 if (num_packets==sizeof(packets)/sizeof(packets[0]))
5944 return stbi__errpuc("bad format","too many packets");
5945
5946 packet = &packets[num_packets++];
5947
5948 chained = stbi__get8(s);
5949 packet->size = stbi__get8(s);
5950 packet->type = stbi__get8(s);
5951 packet->channel = stbi__get8(s);
5952
5953 act_comp |= packet->channel;
5954
5955 if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)");
5956 if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp");
5957 } while (chained);
5958
5959 *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel?
5960
5961 for(y=0; y<height; ++y) {
5962 int packet_idx;
5963
5964 for(packet_idx=0; packet_idx < num_packets; ++packet_idx) {
5965 stbi__pic_packet *packet = &packets[packet_idx];
5966 stbi_uc *dest = result+y*width*4;
5967
5968 switch (packet->type) {
5969 default:
5970 return stbi__errpuc("bad format","packet has bad compression type");
5971
5972 case 0: {//uncompressed
5973 int x;
5974
5975 for(x=0;x<width;++x, dest+=4)
5976 if (!stbi__readval(s,packet->channel,dest))
5977 return 0;
5978 break;
5979 }
5980
5981 case 1://Pure RLE
5982 {
5983 int left=width, i;
5984
5985 while (left>0) {
5986 stbi_uc count,value[4];
5987
5988 count=stbi__get8(s);
5989 if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)");
5990
5991 if (count > left)
5992 count = (stbi_uc) left;
5993
5994 if (!stbi__readval(s,packet->channel,value)) return 0;
5995
5996 for(i=0; i<count; ++i,dest+=4)
5997 stbi__copyval(packet->channel,dest,value);
5998 left -= count;
5999 }
6000 }
6001 break;
6002
6003 case 2: {//Mixed RLE
6004 int left=width;
6005 while (left>0) {
6006 int count = stbi__get8(s), i;
6007 if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)");
6008
6009 if (count >= 128) { // Repeated
6010 stbi_uc value[4];
6011
6012 if (count==128)
6013 count = stbi__get16be(s);
6014 else
6015 count -= 127;
6016 if (count > left)
6017 return stbi__errpuc("bad file","scanline overrun");
6018
6019 if (!stbi__readval(s,packet->channel,value))
6020 return 0;
6021
6022 for(i=0;i<count;++i, dest += 4)
6023 stbi__copyval(packet->channel,dest,value);
6024 } else { // Raw
6025 ++count;
6026 if (count>left) return stbi__errpuc("bad file","scanline overrun");
6027
6028 for(i=0;i<count;++i, dest+=4)
6029 if (!stbi__readval(s,packet->channel,dest))
6030 return 0;
6031 }
6032 left-=count;
6033 }
6034 break;
6035 }
6036 }
6037 }
6038 }
6039
6040 return result;
6041}
6042
6043static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri)
6044{
6045 stbi_uc *result;
6046 int i, x,y, internal_comp;
6047 STBI_NOTUSED(ri);
6048
6049 if (!comp) comp = &internal_comp;
6050
6051 for (i=0; i<92; ++i)
6052 stbi__get8(s);
6053
6054 x = stbi__get16be(s);
6055 y = stbi__get16be(s);
6056
6057 if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
6058 if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
6059
6060 if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)");
6061 if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode");
6062
6063 stbi__get32be(s); //skip `ratio'
6064 stbi__get16be(s); //skip `fields'
6065 stbi__get16be(s); //skip `pad'
6066
6067 // intermediate buffer is RGBA
6068 result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0);
6069 if (!result) return stbi__errpuc("outofmem", "Out of memory");
6070 memset(result, 0xff, x*y*4);
6071
6072 if (!stbi__pic_load_core(s,x,y,comp, result)) {
6073 STBI_FREE(result);
6074 result=0;
6075 }
6076 *px = x;
6077 *py = y;
6078 if (req_comp == 0) req_comp = *comp;
6079 result=stbi__convert_format(result,4,req_comp,x,y);
6080
6081 return result;
6082}
6083
6084static int stbi__pic_test(stbi__context *s)
6085{
6086 int r = stbi__pic_test_core(s);
6087 stbi__rewind(s);
6088 return r;
6089}
6090#endif
6091
6092// *************************************************************************************************
6093// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb
6094
6095#ifndef STBI_NO_GIF
6096typedef struct
6097{
6098 stbi__int16 prefix;
6099 stbi_uc first;
6100 stbi_uc suffix;
6101} stbi__gif_lzw;
6102
6103typedef struct
6104{
6105 int w,h;
6106 stbi_uc *out; // output buffer (always 4 components)
6107 stbi_uc *background; // The current "background" as far as a gif is concerned
6108 stbi_uc *history;
6109 int flags, bgindex, ratio, transparent, eflags;
6110 stbi_uc pal[256][4];
6111 stbi_uc lpal[256][4];
6112 stbi__gif_lzw codes[8192];
6113 stbi_uc *color_table;
6114 int parse, step;
6115 int lflags;
6116 int start_x, start_y;
6117 int max_x, max_y;
6118 int cur_x, cur_y;
6119 int line_size;
6120 int delay;
6121} stbi__gif;
6122
6123static int stbi__gif_test_raw(stbi__context *s)
6124{
6125 int sz;
6126 if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0;
6127 sz = stbi__get8(s);
6128 if (sz != '9' && sz != '7') return 0;
6129 if (stbi__get8(s) != 'a') return 0;
6130 return 1;
6131}
6132
6133static int stbi__gif_test(stbi__context *s)
6134{
6135 int r = stbi__gif_test_raw(s);
6136 stbi__rewind(s);
6137 return r;
6138}
6139
6140static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp)
6141{
6142 int i;
6143 for (i=0; i < num_entries; ++i) {
6144 pal[i][2] = stbi__get8(s);
6145 pal[i][1] = stbi__get8(s);
6146 pal[i][0] = stbi__get8(s);
6147 pal[i][3] = transp == i ? 0 : 255;
6148 }
6149}
6150
6151static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info)
6152{
6153 stbi_uc version;
6154 if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8')
6155 return stbi__err("not GIF", "Corrupt GIF");
6156
6157 version = stbi__get8(s);
6158 if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF");
6159 if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF");
6160
6161 stbi__g_failure_reason = "";
6162 g->w = stbi__get16le(s);
6163 g->h = stbi__get16le(s);
6164 g->flags = stbi__get8(s);
6165 g->bgindex = stbi__get8(s);
6166 g->ratio = stbi__get8(s);
6167 g->transparent = -1;
6168
6169 if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
6170 if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
6171
6172 if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments
6173
6174 if (is_info) return 1;
6175
6176 if (g->flags & 0x80)
6177 stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1);
6178
6179 return 1;
6180}
6181
6182static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)
6183{
6184 stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));
6185 if (!g) return stbi__err("outofmem", "Out of memory");
6186 if (!stbi__gif_header(s, g, comp, 1)) {
6187 STBI_FREE(g);
6188 stbi__rewind( s );
6189 return 0;
6190 }
6191 if (x) *x = g->w;
6192 if (y) *y = g->h;
6193 STBI_FREE(g);
6194 return 1;
6195}
6196
6197static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
6198{
6199 stbi_uc *p, *c;
6200 int idx;
6201
6202 // recurse to decode the prefixes, since the linked-list is backwards,
6203 // and working backwards through an interleaved image would be nasty
6204 if (g->codes[code].prefix >= 0)
6205 stbi__out_gif_code(g, g->codes[code].prefix);
6206
6207 if (g->cur_y >= g->max_y) return;
6208
6209 idx = g->cur_x + g->cur_y;
6210 p = &g->out[idx];
6211 g->history[idx / 4] = 1;
6212
6213 c = &g->color_table[g->codes[code].suffix * 4];
6214 if (c[3] > 128) { // don't render transparent pixels;
6215 p[0] = c[2];
6216 p[1] = c[1];
6217 p[2] = c[0];
6218 p[3] = c[3];
6219 }
6220 g->cur_x += 4;
6221
6222 if (g->cur_x >= g->max_x) {
6223 g->cur_x = g->start_x;
6224 g->cur_y += g->step;
6225
6226 while (g->cur_y >= g->max_y && g->parse > 0) {
6227 g->step = (1 << g->parse) * g->line_size;
6228 g->cur_y = g->start_y + (g->step >> 1);
6229 --g->parse;
6230 }
6231 }
6232}
6233
6234static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
6235{
6236 stbi_uc lzw_cs;
6237 stbi__int32 len, init_code;
6238 stbi__uint32 first;
6239 stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;
6240 stbi__gif_lzw *p;
6241
6242 lzw_cs = stbi__get8(s);
6243 if (lzw_cs > 12) return NULL;
6244 clear = 1 << lzw_cs;
6245 first = 1;
6246 codesize = lzw_cs + 1;
6247 codemask = (1 << codesize) - 1;
6248 bits = 0;
6249 valid_bits = 0;
6250 for (init_code = 0; init_code < clear; init_code++) {
6251 g->codes[init_code].prefix = -1;
6252 g->codes[init_code].first = (stbi_uc) init_code;
6253 g->codes[init_code].suffix = (stbi_uc) init_code;
6254 }
6255
6256 // support no starting clear code
6257 avail = clear+2;
6258 oldcode = -1;
6259
6260 len = 0;
6261 for(;;) {
6262 if (valid_bits < codesize) {
6263 if (len == 0) {
6264 len = stbi__get8(s); // start new block
6265 if (len == 0)
6266 return g->out;
6267 }
6268 --len;
6269 bits |= (stbi__int32) stbi__get8(s) << valid_bits;
6270 valid_bits += 8;
6271 } else {
6272 stbi__int32 code = bits & codemask;
6273 bits >>= codesize;
6274 valid_bits -= codesize;
6275 // @OPTIMIZE: is there some way we can accelerate the non-clear path?
6276 if (code == clear) { // clear code
6277 codesize = lzw_cs + 1;
6278 codemask = (1 << codesize) - 1;
6279 avail = clear + 2;
6280 oldcode = -1;
6281 first = 0;
6282 } else if (code == clear + 1) { // end of stream code
6283 stbi__skip(s, len);
6284 while ((len = stbi__get8(s)) > 0)
6285 stbi__skip(s,len);
6286 return g->out;
6287 } else if (code <= avail) {
6288 if (first) {
6289 return stbi__errpuc("no clear code", "Corrupt GIF");
6290 }
6291
6292 if (oldcode >= 0) {
6293 p = &g->codes[avail++];
6294 if (avail > 8192) {
6295 return stbi__errpuc("too many codes", "Corrupt GIF");
6296 }
6297
6298 p->prefix = (stbi__int16) oldcode;
6299 p->first = g->codes[oldcode].first;
6300 p->suffix = (code == avail) ? p->first : g->codes[code].first;
6301 } else if (code == avail)
6302 return stbi__errpuc("illegal code in raster", "Corrupt GIF");
6303
6304 stbi__out_gif_code(g, (stbi__uint16) code);
6305
6306 if ((avail & codemask) == 0 && avail <= 0x0FFF) {
6307 codesize++;
6308 codemask = (1 << codesize) - 1;
6309 }
6310
6311 oldcode = code;
6312 } else {
6313 return stbi__errpuc("illegal code in raster", "Corrupt GIF");
6314 }
6315 }
6316 }
6317}
6318
6319// this function is designed to support animated gifs, although stb_image doesn't support it
6320// two back is the image from two frames ago, used for a very specific disposal format
6321static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back)
6322{
6323 int dispose;
6324 int first_frame;
6325 int pi;
6326 int pcount;
6327 STBI_NOTUSED(req_comp);
6328
6329 // on first frame, any non-written pixels get the background colour (non-transparent)
6330 first_frame = 0;
6331 if (g->out == 0) {
6332 if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header
6333 if (!stbi__mad3sizes_valid(4, g->w, g->h, 0))
6334 return stbi__errpuc("too large", "GIF image is too large");
6335 pcount = g->w * g->h;
6336 g->out = (stbi_uc *) stbi__malloc(4 * pcount);
6337 g->background = (stbi_uc *) stbi__malloc(4 * pcount);
6338 g->history = (stbi_uc *) stbi__malloc(pcount);
6339 if (!g->out || !g->background || !g->history)
6340 return stbi__errpuc("outofmem", "Out of memory");
6341
6342 // image is treated as "transparent" at the start - ie, nothing overwrites the current background;
6343 // background colour is only used for pixels that are not rendered first frame, after that "background"
6344 // color refers to the color that was there the previous frame.
6345 memset(g->out, 0x00, 4 * pcount);
6346 memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent)
6347 memset(g->history, 0x00, pcount); // pixels that were affected previous frame
6348 first_frame = 1;
6349 } else {
6350 // second frame - how do we dispose of the previous one?
6351 dispose = (g->eflags & 0x1C) >> 2;
6352 pcount = g->w * g->h;
6353
6354 if ((dispose == 3) && (two_back == 0)) {
6355 dispose = 2; // if I don't have an image to revert back to, default to the old background
6356 }
6357
6358 if (dispose == 3) { // use previous graphic
6359 for (pi = 0; pi < pcount; ++pi) {
6360 if (g->history[pi]) {
6361 memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 );
6362 }
6363 }
6364 } else if (dispose == 2) {
6365 // restore what was changed last frame to background before that frame;
6366 for (pi = 0; pi < pcount; ++pi) {
6367 if (g->history[pi]) {
6368 memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 );
6369 }
6370 }
6371 } else {
6372 // This is a non-disposal case eithe way, so just
6373 // leave the pixels as is, and they will become the new background
6374 // 1: do not dispose
6375 // 0: not specified.
6376 }
6377
6378 // background is what out is after the undoing of the previou frame;
6379 memcpy( g->background, g->out, 4 * g->w * g->h );
6380 }
6381
6382 // clear my history;
6383 memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame
6384
6385 for (;;) {
6386 int tag = stbi__get8(s);
6387 switch (tag) {
6388 case 0x2C: /* Image Descriptor */
6389 {
6390 stbi__int32 x, y, w, h;
6391 stbi_uc *o;
6392
6393 x = stbi__get16le(s);
6394 y = stbi__get16le(s);
6395 w = stbi__get16le(s);
6396 h = stbi__get16le(s);
6397 if (((x + w) > (g->w)) || ((y + h) > (g->h)))
6398 return stbi__errpuc("bad Image Descriptor", "Corrupt GIF");
6399
6400 g->line_size = g->w * 4;
6401 g->start_x = x * 4;
6402 g->start_y = y * g->line_size;
6403 g->max_x = g->start_x + w * 4;
6404 g->max_y = g->start_y + h * g->line_size;
6405 g->cur_x = g->start_x;
6406 g->cur_y = g->start_y;
6407
6408 // if the width of the specified rectangle is 0, that means
6409 // we may not see *any* pixels or the image is malformed;
6410 // to make sure this is caught, move the current y down to
6411 // max_y (which is what out_gif_code checks).
6412 if (w == 0)
6413 g->cur_y = g->max_y;
6414
6415 g->lflags = stbi__get8(s);
6416
6417 if (g->lflags & 0x40) {
6418 g->step = 8 * g->line_size; // first interlaced spacing
6419 g->parse = 3;
6420 } else {
6421 g->step = g->line_size;
6422 g->parse = 0;
6423 }
6424
6425 if (g->lflags & 0x80) {
6426 stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);
6427 g->color_table = (stbi_uc *) g->lpal;
6428 } else if (g->flags & 0x80) {
6429 g->color_table = (stbi_uc *) g->pal;
6430 } else
6431 return stbi__errpuc("missing color table", "Corrupt GIF");
6432
6433 o = stbi__process_gif_raster(s, g);
6434 if (!o) return NULL;
6435
6436 // if this was the first frame,
6437 pcount = g->w * g->h;
6438 if (first_frame && (g->bgindex > 0)) {
6439 // if first frame, any pixel not drawn to gets the background color
6440 for (pi = 0; pi < pcount; ++pi) {
6441 if (g->history[pi] == 0) {
6442 g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be;
6443 memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 );
6444 }
6445 }
6446 }
6447
6448 return o;
6449 }
6450
6451 case 0x21: // Comment Extension.
6452 {
6453 int len;
6454 int ext = stbi__get8(s);
6455 if (ext == 0xF9) { // Graphic Control Extension.
6456 len = stbi__get8(s);
6457 if (len == 4) {
6458 g->eflags = stbi__get8(s);
6459 g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths.
6460
6461 // unset old transparent
6462 if (g->transparent >= 0) {
6463 g->pal[g->transparent][3] = 255;
6464 }
6465 if (g->eflags & 0x01) {
6466 g->transparent = stbi__get8(s);
6467 if (g->transparent >= 0) {
6468 g->pal[g->transparent][3] = 0;
6469 }
6470 } else {
6471 // don't need transparent
6472 stbi__skip(s, 1);
6473 g->transparent = -1;
6474 }
6475 } else {
6476 stbi__skip(s, len);
6477 break;
6478 }
6479 }
6480 while ((len = stbi__get8(s)) != 0) {
6481 stbi__skip(s, len);
6482 }
6483 break;
6484 }
6485
6486 case 0x3B: // gif stream termination code
6487 return (stbi_uc *) s; // using '1' causes warning on some compilers
6488
6489 default:
6490 return stbi__errpuc("unknown code", "Corrupt GIF");
6491 }
6492 }
6493}
6494
6495static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays)
6496{
6497 STBI_FREE(g->out);
6498 STBI_FREE(g->history);
6499 STBI_FREE(g->background);
6500
6501 if (out) STBI_FREE(out);
6502 if (delays && *delays) STBI_FREE(*delays);
6503 return stbi__errpuc("outofmem", "Out of memory");
6504}
6505
6506static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
6507{
6508 if (stbi__gif_test(s)) {
6509 int layers = 0;
6510 stbi_uc *u = 0;
6511 stbi_uc *out = 0;
6512 stbi_uc *two_back = 0;
6513 stbi__gif g;
6514 int stride;
6515 int out_size = 0;
6516 int delays_size = 0;
6517
6518 STBI_NOTUSED(out_size);
6519 STBI_NOTUSED(delays_size);
6520
6521 memset(&g, 0, sizeof(g));
6522 if (delays) {
6523 *delays = 0;
6524 }
6525
6526 do {
6527 u = stbi__gif_load_next(s, &g, comp, req_comp, two_back);
6528 if (u == (stbi_uc *) s) u = 0; // end of animated gif marker
6529
6530 if (u) {
6531 *x = g.w;
6532 *y = g.h;
6533 ++layers;
6534 stride = g.w * g.h * 4;
6535
6536 if (out) {
6537 void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride );
6538 if (!tmp)
6539 return stbi__load_gif_main_outofmem(&g, out, delays);
6540 else {
6541 out = (stbi_uc*) tmp;
6542 out_size = layers * stride;
6543 }
6544
6545 if (delays) {
6546 int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers );
6547 if (!new_delays)
6548 return stbi__load_gif_main_outofmem(&g, out, delays);
6549 *delays = new_delays;
6550 delays_size = layers * sizeof(int);
6551 }
6552 } else {
6553 out = (stbi_uc*)stbi__malloc( layers * stride );
6554 if (!out)
6555 return stbi__load_gif_main_outofmem(&g, out, delays);
6556 out_size = layers * stride;
6557 if (delays) {
6558 *delays = (int*) stbi__malloc( layers * sizeof(int) );
6559 if (!*delays)
6560 return stbi__load_gif_main_outofmem(&g, out, delays);
6561 delays_size = layers * sizeof(int);
6562 }
6563 }
6564 memcpy( out + ((layers - 1) * stride), u, stride );
6565 if (layers >= 2) {
6566 two_back = out - 2 * stride;
6567 }
6568
6569 if (delays) {
6570 (*delays)[layers - 1U] = g.delay;
6571 }
6572 }
6573 } while (u != 0);
6574
6575 // free temp buffer;
6576 STBI_FREE(g.out);
6577 STBI_FREE(g.history);
6578 STBI_FREE(g.background);
6579
6580 // do the final conversion after loading everything;
6581 if (req_comp && req_comp != 4)
6582 out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h);
6583
6584 *z = layers;
6585 return out;
6586 } else {
6587 return stbi__errpuc("not GIF", "Image was not as a gif type.");
6588 }
6589}
6590
6591static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
6592{
6593 stbi_uc *u = 0;
6594 stbi__gif g;
6595 memset(&g, 0, sizeof(g));
6596 STBI_NOTUSED(ri);
6597
6598 u = stbi__gif_load_next(s, &g, comp, req_comp, 0);
6599 if (u == (stbi_uc *) s) u = 0; // end of animated gif marker
6600 if (u) {
6601 *x = g.w;
6602 *y = g.h;
6603
6604 // moved conversion to after successful load so that the same
6605 // can be done for multiple frames.
6606 if (req_comp && req_comp != 4)
6607 u = stbi__convert_format(u, 4, req_comp, g.w, g.h);
6608 } else if (g.out) {
6609 // if there was an error and we allocated an image buffer, free it!
6610 STBI_FREE(g.out);
6611 }
6612
6613 // free buffers needed for multiple frame loading;
6614 STBI_FREE(g.history);
6615 STBI_FREE(g.background);
6616
6617 return u;
6618}
6619
6620static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp)
6621{
6622 return stbi__gif_info_raw(s,x,y,comp);
6623}
6624#endif
6625
6626// *************************************************************************************************
6627// Radiance RGBE HDR loader
6628// originally by Nicolas Schulz
6629#ifndef STBI_NO_HDR
6630static int stbi__hdr_test_core(stbi__context *s, const char *signature)
6631{
6632 int i;
6633 for (i=0; signature[i]; ++i)
6634 if (stbi__get8(s) != signature[i])
6635 return 0;
6636 stbi__rewind(s);
6637 return 1;
6638}
6639
6640static int stbi__hdr_test(stbi__context* s)
6641{
6642 int r = stbi__hdr_test_core(s, "#?RADIANCE\n");
6643 stbi__rewind(s);
6644 if(!r) {
6645 r = stbi__hdr_test_core(s, "#?RGBE\n");
6646 stbi__rewind(s);
6647 }
6648 return r;
6649}
6650
6651#define STBI__HDR_BUFLEN 1024
6652static char *stbi__hdr_gettoken(stbi__context *z, char *buffer)
6653{
6654 int len=0;
6655 char c = '\0';
6656
6657 c = (char) stbi__get8(z);
6658
6659 while (!stbi__at_eof(z) && c != '\n') {
6660 buffer[len++] = c;
6661 if (len == STBI__HDR_BUFLEN-1) {
6662 // flush to end of line
6663 while (!stbi__at_eof(z) && stbi__get8(z) != '\n')
6664 ;
6665 break;
6666 }
6667 c = (char) stbi__get8(z);
6668 }
6669
6670 buffer[len] = 0;
6671 return buffer;
6672}
6673
6674static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp)
6675{
6676 if ( input[3] != 0 ) {
6677 float f1;
6678 // Exponent
6679 f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8));
6680 if (req_comp <= 2)
6681 output[0] = (input[0] + input[1] + input[2]) * f1 / 3;
6682 else {
6683 output[0] = input[0] * f1;
6684 output[1] = input[1] * f1;
6685 output[2] = input[2] * f1;
6686 }
6687 if (req_comp == 2) output[1] = 1;
6688 if (req_comp == 4) output[3] = 1;
6689 } else {
6690 switch (req_comp) {
6691 case 4: output[3] = 1; /* fallthrough */
6692 case 3: output[0] = output[1] = output[2] = 0;
6693 break;
6694 case 2: output[1] = 1; /* fallthrough */
6695 case 1: output[0] = 0;
6696 break;
6697 }
6698 }
6699}
6700
6701static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
6702{
6703 char buffer[STBI__HDR_BUFLEN];
6704 char *token;
6705 int valid = 0;
6706 int width, height;
6707 stbi_uc *scanline;
6708 float *hdr_data;
6709 int len;
6710 unsigned char count, value;
6711 int i, j, k, c1,c2, z;
6712 const char *headerToken;
6713 STBI_NOTUSED(ri);
6714
6715 // Check identifier
6716 headerToken = stbi__hdr_gettoken(s,buffer);
6717 if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0)
6718 return stbi__errpf("not HDR", "Corrupt HDR image");
6719
6720 // Parse header
6721 for(;;) {
6722 token = stbi__hdr_gettoken(s,buffer);
6723 if (token[0] == 0) break;
6724 if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
6725 }
6726
6727 if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format");
6728
6729 // Parse width and height
6730 // can't use sscanf() if we're not using stdio!
6731 token = stbi__hdr_gettoken(s,buffer);
6732 if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format");
6733 token += 3;
6734 height = (int) strtol(token, &token, 10);
6735 while (*token == ' ') ++token;
6736 if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format");
6737 token += 3;
6738 width = (int) strtol(token, NULL, 10);
6739
6740 if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)");
6741 if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)");
6742
6743 *x = width;
6744 *y = height;
6745
6746 if (comp) *comp = 3;
6747 if (req_comp == 0) req_comp = 3;
6748
6749 if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0))
6750 return stbi__errpf("too large", "HDR image is too large");
6751
6752 // Read data
6753 hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0);
6754 if (!hdr_data)
6755 return stbi__errpf("outofmem", "Out of memory");
6756
6757 // Load image data
6758 // image data is stored as some number of sca
6759 if ( width < 8 || width >= 32768) {
6760 // Read flat data
6761 for (j=0; j < height; ++j) {
6762 for (i=0; i < width; ++i) {
6763 stbi_uc rgbe[4];
6764 main_decode_loop:
6765 stbi__getn(s, rgbe, 4);
6766 stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);
6767 }
6768 }
6769 } else {
6770 // Read RLE-encoded data
6771 scanline = NULL;
6772
6773 for (j = 0; j < height; ++j) {
6774 c1 = stbi__get8(s);
6775 c2 = stbi__get8(s);
6776 len = stbi__get8(s);
6777 if (c1 != 2 || c2 != 2 || (len & 0x80)) {
6778 // not run-length encoded, so we have to actually use THIS data as a decoded
6779 // pixel (note this can't be a valid pixel--one of RGB must be >= 128)
6780 stbi_uc rgbe[4];
6781 rgbe[0] = (stbi_uc) c1;
6782 rgbe[1] = (stbi_uc) c2;
6783 rgbe[2] = (stbi_uc) len;
6784 rgbe[3] = (stbi_uc) stbi__get8(s);
6785 stbi__hdr_convert(hdr_data, rgbe, req_comp);
6786 i = 1;
6787 j = 0;
6788 STBI_FREE(scanline);
6789 goto main_decode_loop; // yes, this makes no sense
6790 }
6791 len <<= 8;
6792 len |= stbi__get8(s);
6793 if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); }
6794 if (scanline == NULL) {
6795 scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0);
6796 if (!scanline) {
6797 STBI_FREE(hdr_data);
6798 return stbi__errpf("outofmem", "Out of memory");
6799 }
6800 }
6801
6802 for (k = 0; k < 4; ++k) {
6803 int nleft;
6804 i = 0;
6805 while ((nleft = width - i) > 0) {
6806 count = stbi__get8(s);
6807 if (count > 128) {
6808 // Run
6809 value = stbi__get8(s);
6810 count -= 128;
6811 if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
6812 for (z = 0; z < count; ++z)
6813 scanline[i++ * 4 + k] = value;
6814 } else {
6815 // Dump
6816 if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
6817 for (z = 0; z < count; ++z)
6818 scanline[i++ * 4 + k] = stbi__get8(s);
6819 }
6820 }
6821 }
6822 for (i=0; i < width; ++i)
6823 stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp);
6824 }
6825 if (scanline)
6826 STBI_FREE(scanline);
6827 }
6828
6829 return hdr_data;
6830}
6831
6832static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp)
6833{
6834 char buffer[STBI__HDR_BUFLEN];
6835 char *token;
6836 int valid = 0;
6837 int dummy;
6838
6839 if (!x) x = &dummy;
6840 if (!y) y = &dummy;
6841 if (!comp) comp = &dummy;
6842
6843 if (stbi__hdr_test(s) == 0) {
6844 stbi__rewind( s );
6845 return 0;
6846 }
6847
6848 for(;;) {
6849 token = stbi__hdr_gettoken(s,buffer);
6850 if (token[0] == 0) break;
6851 if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
6852 }
6853
6854 if (!valid) {
6855 stbi__rewind( s );
6856 return 0;
6857 }
6858 token = stbi__hdr_gettoken(s,buffer);
6859 if (strncmp(token, "-Y ", 3)) {
6860 stbi__rewind( s );
6861 return 0;
6862 }
6863 token += 3;
6864 *y = (int) strtol(token, &token, 10);
6865 while (*token == ' ') ++token;
6866 if (strncmp(token, "+X ", 3)) {
6867 stbi__rewind( s );
6868 return 0;
6869 }
6870 token += 3;
6871 *x = (int) strtol(token, NULL, 10);
6872 *comp = 3;
6873 return 1;
6874}
6875#endif // STBI_NO_HDR
6876
6877#ifndef STBI_NO_BMP
6878static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)
6879{
6880 void *p;
6881 stbi__bmp_data info;
6882
6883 info.all_a = 255;
6884 p = stbi__bmp_parse_header(s, &info);
6885 if (p == NULL) {
6886 stbi__rewind( s );
6887 return 0;
6888 }
6889 if (x) *x = s->img_x;
6890 if (y) *y = s->img_y;
6891 if (comp) {
6892 if (info.bpp == 24 && info.ma == 0xff000000)
6893 *comp = 3;
6894 else
6895 *comp = info.ma ? 4 : 3;
6896 }
6897 return 1;
6898}
6899#endif
6900
6901#ifndef STBI_NO_PSD
6902static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp)
6903{
6904 int channelCount, dummy, depth;
6905 if (!x) x = &dummy;
6906 if (!y) y = &dummy;
6907 if (!comp) comp = &dummy;
6908 if (stbi__get32be(s) != 0x38425053) {
6909 stbi__rewind( s );
6910 return 0;
6911 }
6912 if (stbi__get16be(s) != 1) {
6913 stbi__rewind( s );
6914 return 0;
6915 }
6916 stbi__skip(s, 6);
6917 channelCount = stbi__get16be(s);
6918 if (channelCount < 0 || channelCount > 16) {
6919 stbi__rewind( s );
6920 return 0;
6921 }
6922 *y = stbi__get32be(s);
6923 *x = stbi__get32be(s);
6924 depth = stbi__get16be(s);
6925 if (depth != 8 && depth != 16) {
6926 stbi__rewind( s );
6927 return 0;
6928 }
6929 if (stbi__get16be(s) != 3) {
6930 stbi__rewind( s );
6931 return 0;
6932 }
6933 *comp = 4;
6934 return 1;
6935}
6936
6937static int stbi__psd_is16(stbi__context *s)
6938{
6939 int channelCount, depth;
6940 if (stbi__get32be(s) != 0x38425053) {
6941 stbi__rewind( s );
6942 return 0;
6943 }
6944 if (stbi__get16be(s) != 1) {
6945 stbi__rewind( s );
6946 return 0;
6947 }
6948 stbi__skip(s, 6);
6949 channelCount = stbi__get16be(s);
6950 if (channelCount < 0 || channelCount > 16) {
6951 stbi__rewind( s );
6952 return 0;
6953 }
6954 STBI_NOTUSED(stbi__get32be(s));
6955 STBI_NOTUSED(stbi__get32be(s));
6956 depth = stbi__get16be(s);
6957 if (depth != 16) {
6958 stbi__rewind( s );
6959 return 0;
6960 }
6961 return 1;
6962}
6963#endif
6964
6965#ifndef STBI_NO_PIC
6966static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)
6967{
6968 int act_comp=0,num_packets=0,chained,dummy;
6969 stbi__pic_packet packets[10];
6970
6971 if (!x) x = &dummy;
6972 if (!y) y = &dummy;
6973 if (!comp) comp = &dummy;
6974
6975 if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) {
6976 stbi__rewind(s);
6977 return 0;
6978 }
6979
6980 stbi__skip(s, 88);
6981
6982 *x = stbi__get16be(s);
6983 *y = stbi__get16be(s);
6984 if (stbi__at_eof(s)) {
6985 stbi__rewind( s);
6986 return 0;
6987 }
6988 if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {
6989 stbi__rewind( s );
6990 return 0;
6991 }
6992
6993 stbi__skip(s, 8);
6994
6995 do {
6996 stbi__pic_packet *packet;
6997
6998 if (num_packets==sizeof(packets)/sizeof(packets[0]))
6999 return 0;
7000
7001 packet = &packets[num_packets++];
7002 chained = stbi__get8(s);
7003 packet->size = stbi__get8(s);
7004 packet->type = stbi__get8(s);
7005 packet->channel = stbi__get8(s);
7006 act_comp |= packet->channel;
7007
7008 if (stbi__at_eof(s)) {
7009 stbi__rewind( s );
7010 return 0;
7011 }
7012 if (packet->size != 8) {
7013 stbi__rewind( s );
7014 return 0;
7015 }
7016 } while (chained);
7017
7018 *comp = (act_comp & 0x10 ? 4 : 3);
7019
7020 return 1;
7021}
7022#endif
7023
7024// *************************************************************************************************
7025// Portable Gray Map and Portable Pixel Map loader
7026// by Ken Miller
7027//
7028// PGM: http://netpbm.sourceforge.net/doc/pgm.html
7029// PPM: http://netpbm.sourceforge.net/doc/ppm.html
7030//
7031// Known limitations:
7032// Does not support comments in the header section
7033// Does not support ASCII image data (formats P2 and P3)
7034
7035#ifndef STBI_NO_PNM
7036
7037static int stbi__pnm_test(stbi__context *s)
7038{
7039 char p, t;
7040 p = (char) stbi__get8(s);
7041 t = (char) stbi__get8(s);
7042 if (p != 'P' || (t != '5' && t != '6')) {
7043 stbi__rewind( s );
7044 return 0;
7045 }
7046 return 1;
7047}
7048
7049static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
7050{
7051 stbi_uc *out;
7052 STBI_NOTUSED(ri);
7053
7054 ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n);
7055 if (ri->bits_per_channel == 0)
7056 return 0;
7057
7058 if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
7059 if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
7060
7061 *x = s->img_x;
7062 *y = s->img_y;
7063 if (comp) *comp = s->img_n;
7064
7065 if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0))
7066 return stbi__errpuc("too large", "PNM too large");
7067
7068 out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0);
7069 if (!out) return stbi__errpuc("outofmem", "Out of memory");
7070 if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) {
7071 STBI_FREE(out);
7072 return stbi__errpuc("bad PNM", "PNM file truncated");
7073 }
7074
7075 if (req_comp && req_comp != s->img_n) {
7076 if (ri->bits_per_channel == 16) {
7077 out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y);
7078 } else {
7079 out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
7080 }
7081 if (out == NULL) return out; // stbi__convert_format frees input on failure
7082 }
7083 return out;
7084}
7085
7086static int stbi__pnm_isspace(char c)
7087{
7088 return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
7089}
7090
7091static void stbi__pnm_skip_whitespace(stbi__context *s, char *c)
7092{
7093 for (;;) {
7094 while (!stbi__at_eof(s) && stbi__pnm_isspace(*c))
7095 *c = (char) stbi__get8(s);
7096
7097 if (stbi__at_eof(s) || *c != '#')
7098 break;
7099
7100 while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' )
7101 *c = (char) stbi__get8(s);
7102 }
7103}
7104
7105static int stbi__pnm_isdigit(char c)
7106{
7107 return c >= '0' && c <= '9';
7108}
7109
7110static int stbi__pnm_getinteger(stbi__context *s, char *c)
7111{
7112 int value = 0;
7113
7114 while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) {
7115 value = value*10 + (*c - '0');
7116 *c = (char) stbi__get8(s);
7117 if((value > 214748364) || (value == 214748364 && *c > '7'))
7118 return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int");
7119 }
7120
7121 return value;
7122}
7123
7124static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)
7125{
7126 int maxv, dummy;
7127 char c, p, t;
7128
7129 if (!x) x = &dummy;
7130 if (!y) y = &dummy;
7131 if (!comp) comp = &dummy;
7132
7133 stbi__rewind(s);
7134
7135 // Get identifier
7136 p = (char) stbi__get8(s);
7137 t = (char) stbi__get8(s);
7138 if (p != 'P' || (t != '5' && t != '6')) {
7139 stbi__rewind(s);
7140 return 0;
7141 }
7142
7143 *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm
7144
7145 c = (char) stbi__get8(s);
7146 stbi__pnm_skip_whitespace(s, &c);
7147
7148 *x = stbi__pnm_getinteger(s, &c); // read width
7149 if(*x == 0)
7150 return stbi__err("invalid width", "PPM image header had zero or overflowing width");
7151 stbi__pnm_skip_whitespace(s, &c);
7152
7153 *y = stbi__pnm_getinteger(s, &c); // read height
7154 if (*y == 0)
7155 return stbi__err("invalid width", "PPM image header had zero or overflowing width");
7156 stbi__pnm_skip_whitespace(s, &c);
7157
7158 maxv = stbi__pnm_getinteger(s, &c); // read max value
7159 if (maxv > 65535)
7160 return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images");
7161 else if (maxv > 255)
7162 return 16;
7163 else
7164 return 8;
7165}
7166
7167static int stbi__pnm_is16(stbi__context *s)
7168{
7169 if (stbi__pnm_info(s, NULL, NULL, NULL) == 16)
7170 return 1;
7171 return 0;
7172}
7173#endif
7174
7175static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp)
7176{
7177 #ifndef STBI_NO_JPEG
7178 if (stbi__jpeg_info(s, x, y, comp)) return 1;
7179 #endif
7180
7181 #ifndef STBI_NO_PNG
7182 if (stbi__png_info(s, x, y, comp)) return 1;
7183 #endif
7184
7185 #ifndef STBI_NO_GIF
7186 if (stbi__gif_info(s, x, y, comp)) return 1;
7187 #endif
7188
7189 #ifndef STBI_NO_BMP
7190 if (stbi__bmp_info(s, x, y, comp)) return 1;
7191 #endif
7192
7193 #ifndef STBI_NO_PSD
7194 if (stbi__psd_info(s, x, y, comp)) return 1;
7195 #endif
7196
7197 #ifndef STBI_NO_PIC
7198 if (stbi__pic_info(s, x, y, comp)) return 1;
7199 #endif
7200
7201 #ifndef STBI_NO_PNM
7202 if (stbi__pnm_info(s, x, y, comp)) return 1;
7203 #endif
7204
7205 #ifndef STBI_NO_HDR
7206 if (stbi__hdr_info(s, x, y, comp)) return 1;
7207 #endif
7208
7209 // test tga last because it's a crappy test!
7210 #ifndef STBI_NO_TGA
7211 if (stbi__tga_info(s, x, y, comp))
7212 return 1;
7213 #endif
7214 return stbi__err("unknown image type", "Image not of any known type, or corrupt");
7215}
7216
7217static int stbi__is_16_main(stbi__context *s)
7218{
7219 #ifndef STBI_NO_PNG
7220 if (stbi__png_is16(s)) return 1;
7221 #endif
7222
7223 #ifndef STBI_NO_PSD
7224 if (stbi__psd_is16(s)) return 1;
7225 #endif
7226
7227 #ifndef STBI_NO_PNM
7228 if (stbi__pnm_is16(s)) return 1;
7229 #endif
7230 return 0;
7231}
7232
7233#ifndef STBI_NO_STDIO
7234STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp)
7235{
7236 FILE *f = stbi__fopen(filename, "rb");
7237 int result;
7238 if (!f) return stbi__err("can't fopen", "Unable to open file");
7239 result = stbi_info_from_file(f, x, y, comp);
7240 fclose(f);
7241 return result;
7242}
7243
7244STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp)
7245{
7246 int r;
7247 stbi__context s;
7248 long pos = ftell(f);
7249 stbi__start_file(&s, f);
7250 r = stbi__info_main(&s,x,y,comp);
7251 fseek(f,pos,SEEK_SET);
7252 return r;
7253}
7254
7255STBIDEF int stbi_is_16_bit(char const *filename)
7256{
7257 FILE *f = stbi__fopen(filename, "rb");
7258 int result;
7259 if (!f) return stbi__err("can't fopen", "Unable to open file");
7260 result = stbi_is_16_bit_from_file(f);
7261 fclose(f);
7262 return result;
7263}
7264
7265STBIDEF int stbi_is_16_bit_from_file(FILE *f)
7266{
7267 int r;
7268 stbi__context s;
7269 long pos = ftell(f);
7270 stbi__start_file(&s, f);
7271 r = stbi__is_16_main(&s);
7272 fseek(f,pos,SEEK_SET);
7273 return r;
7274}
7275#endif // !STBI_NO_STDIO
7276
7277STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp)
7278{
7279 stbi__context s;
7280 stbi__start_mem(&s,buffer,len);
7281 return stbi__info_main(&s,x,y,comp);
7282}
7283
7284STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp)
7285{
7286 stbi__context s;
7287 stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
7288 return stbi__info_main(&s,x,y,comp);
7289}
7290
7291STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len)
7292{
7293 stbi__context s;
7294 stbi__start_mem(&s,buffer,len);
7295 return stbi__is_16_main(&s);
7296}
7297
7298STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user)
7299{
7300 stbi__context s;
7301 stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
7302 return stbi__is_16_main(&s);
7303}
7304
7305/*
7306 revision history:
7307 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
7308 2.19 (2018-02-11) fix warning
7309 2.18 (2018-01-30) fix warnings
7310 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug
7311 1-bit BMP
7312 *_is_16_bit api
7313 avoid warnings
7314 2.16 (2017-07-23) all functions have 16-bit variants;
7315 STBI_NO_STDIO works again;
7316 compilation fixes;
7317 fix rounding in unpremultiply;
7318 optimize vertical flip;
7319 disable raw_len validation;
7320 documentation fixes
7321 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode;
7322 warning fixes; disable run-time SSE detection on gcc;
7323 uniform handling of optional "return" values;
7324 thread-safe initialization of zlib tables
7325 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
7326 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now
7327 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
7328 2.11 (2016-04-02) allocate large structures on the stack
7329 remove white matting for transparent PSD
7330 fix reported channel count for PNG & BMP
7331 re-enable SSE2 in non-gcc 64-bit
7332 support RGB-formatted JPEG
7333 read 16-bit PNGs (only as 8-bit)
7334 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED
7335 2.09 (2016-01-16) allow comments in PNM files
7336 16-bit-per-pixel TGA (not bit-per-component)
7337 info() for TGA could break due to .hdr handling
7338 info() for BMP to shares code instead of sloppy parse
7339 can use STBI_REALLOC_SIZED if allocator doesn't support realloc
7340 code cleanup
7341 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA
7342 2.07 (2015-09-13) fix compiler warnings
7343 partial animated GIF support
7344 limited 16-bpc PSD support
7345 #ifdef unused functions
7346 bug with < 92 byte PIC,PNM,HDR,TGA
7347 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value
7348 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning
7349 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit
7350 2.03 (2015-04-12) extra corruption checking (mmozeiko)
7351 stbi_set_flip_vertically_on_load (nguillemot)
7352 fix NEON support; fix mingw support
7353 2.02 (2015-01-19) fix incorrect assert, fix warning
7354 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2
7355 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG
7356 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg)
7357 progressive JPEG (stb)
7358 PGM/PPM support (Ken Miller)
7359 STBI_MALLOC,STBI_REALLOC,STBI_FREE
7360 GIF bugfix -- seemingly never worked
7361 STBI_NO_*, STBI_ONLY_*
7362 1.48 (2014-12-14) fix incorrectly-named assert()
7363 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb)
7364 optimize PNG (ryg)
7365 fix bug in interlaced PNG with user-specified channel count (stb)
7366 1.46 (2014-08-26)
7367 fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG
7368 1.45 (2014-08-16)
7369 fix MSVC-ARM internal compiler error by wrapping malloc
7370 1.44 (2014-08-07)
7371 various warning fixes from Ronny Chevalier
7372 1.43 (2014-07-15)
7373 fix MSVC-only compiler problem in code changed in 1.42
7374 1.42 (2014-07-09)
7375 don't define _CRT_SECURE_NO_WARNINGS (affects user code)
7376 fixes to stbi__cleanup_jpeg path
7377 added STBI_ASSERT to avoid requiring assert.h
7378 1.41 (2014-06-25)
7379 fix search&replace from 1.36 that messed up comments/error messages
7380 1.40 (2014-06-22)
7381 fix gcc struct-initialization warning
7382 1.39 (2014-06-15)
7383 fix to TGA optimization when req_comp != number of components in TGA;
7384 fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite)
7385 add support for BMP version 5 (more ignored fields)
7386 1.38 (2014-06-06)
7387 suppress MSVC warnings on integer casts truncating values
7388 fix accidental rename of 'skip' field of I/O
7389 1.37 (2014-06-04)
7390 remove duplicate typedef
7391 1.36 (2014-06-03)
7392 convert to header file single-file library
7393 if de-iphone isn't set, load iphone images color-swapped instead of returning NULL
7394 1.35 (2014-05-27)
7395 various warnings
7396 fix broken STBI_SIMD path
7397 fix bug where stbi_load_from_file no longer left file pointer in correct place
7398 fix broken non-easy path for 32-bit BMP (possibly never used)
7399 TGA optimization by Arseny Kapoulkine
7400 1.34 (unknown)
7401 use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case
7402 1.33 (2011-07-14)
7403 make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements
7404 1.32 (2011-07-13)
7405 support for "info" function for all supported filetypes (SpartanJ)
7406 1.31 (2011-06-20)
7407 a few more leak fixes, bug in PNG handling (SpartanJ)
7408 1.30 (2011-06-11)
7409 added ability to load files via callbacks to accomidate custom input streams (Ben Wenger)
7410 removed deprecated format-specific test/load functions
7411 removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway
7412 error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha)
7413 fix inefficiency in decoding 32-bit BMP (David Woo)
7414 1.29 (2010-08-16)
7415 various warning fixes from Aurelien Pocheville
7416 1.28 (2010-08-01)
7417 fix bug in GIF palette transparency (SpartanJ)
7418 1.27 (2010-08-01)
7419 cast-to-stbi_uc to fix warnings
7420 1.26 (2010-07-24)
7421 fix bug in file buffering for PNG reported by SpartanJ
7422 1.25 (2010-07-17)
7423 refix trans_data warning (Won Chun)
7424 1.24 (2010-07-12)
7425 perf improvements reading from files on platforms with lock-heavy fgetc()
7426 minor perf improvements for jpeg
7427 deprecated type-specific functions so we'll get feedback if they're needed
7428 attempt to fix trans_data warning (Won Chun)
7429 1.23 fixed bug in iPhone support
7430 1.22 (2010-07-10)
7431 removed image *writing* support
7432 stbi_info support from Jetro Lauha
7433 GIF support from Jean-Marc Lienher
7434 iPhone PNG-extensions from James Brown
7435 warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva)
7436 1.21 fix use of 'stbi_uc' in header (reported by jon blow)
7437 1.20 added support for Softimage PIC, by Tom Seddon
7438 1.19 bug in interlaced PNG corruption check (found by ryg)
7439 1.18 (2008-08-02)
7440 fix a threading bug (local mutable static)
7441 1.17 support interlaced PNG
7442 1.16 major bugfix - stbi__convert_format converted one too many pixels
7443 1.15 initialize some fields for thread safety
7444 1.14 fix threadsafe conversion bug
7445 header-file-only version (#define STBI_HEADER_FILE_ONLY before including)
7446 1.13 threadsafe
7447 1.12 const qualifiers in the API
7448 1.11 Support installable IDCT, colorspace conversion routines
7449 1.10 Fixes for 64-bit (don't use "unsigned long")
7450 optimized upsampling by Fabian "ryg" Giesen
7451 1.09 Fix format-conversion for PSD code (bad global variables!)
7452 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz
7453 1.07 attempt to fix C++ warning/errors again
7454 1.06 attempt to fix C++ warning/errors again
7455 1.05 fix TGA loading to return correct *comp and use good luminance calc
7456 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free
7457 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR
7458 1.02 support for (subset of) HDR files, float interface for preferred access to them
7459 1.01 fix bug: possible bug in handling right-side up bmps... not sure
7460 fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all
7461 1.00 interface to zlib that skips zlib header
7462 0.99 correct handling of alpha in palette
7463 0.98 TGA loader by lonesock; dynamically add loaders (untested)
7464 0.97 jpeg errors on too large a file; also catch another malloc failure
7465 0.96 fix detection of invalid v value - particleman@mollyrocket forum
7466 0.95 during header scan, seek to markers in case of padding
7467 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same
7468 0.93 handle jpegtran output; verbose errors
7469 0.92 read 4,8,16,24,32-bit BMP files of several formats
7470 0.91 output 24-bit Windows 3.0 BMP files
7471 0.90 fix a few more warnings; bump version number to approach 1.0
7472 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd
7473 0.60 fix compiling as c++
7474 0.59 fix warnings: merge Dave Moore's -Wall fixes
7475 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian
7476 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available
7477 0.56 fix bug: zlib uncompressed mode len vs. nlen
7478 0.55 fix bug: restart_interval not initialized to 0
7479 0.54 allow NULL for 'int *comp'
7480 0.53 fix bug in png 3->4; speedup png decoding
7481 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments
7482 0.51 obey req_comp requests, 1-component jpegs return as 1-component,
7483 on 'test' only check type, not whether we support this variant
7484 0.50 (2006-11-19)
7485 first released version
7486*/
7487
7488
7489/*
7490------------------------------------------------------------------------------
7491This software is available under 2 licenses -- choose whichever you prefer.
7492------------------------------------------------------------------------------
7493ALTERNATIVE A - MIT License
7494Copyright (c) 2017 Sean Barrett
7495Permission is hereby granted, free of charge, to any person obtaining a copy of
7496this software and associated documentation files (the "Software"), to deal in
7497the Software without restriction, including without limitation the rights to
7498use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7499of the Software, and to permit persons to whom the Software is furnished to do
7500so, subject to the following conditions:
7501The above copyright notice and this permission notice shall be included in all
7502copies or substantial portions of the Software.
7503THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
7504IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7505FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7506AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
7507LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
7508OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
7509SOFTWARE.
7510------------------------------------------------------------------------------
7511ALTERNATIVE B - Public Domain (www.unlicense.org)
7512This is free and unencumbered software released into the public domain.
7513Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
7514software, either in source code form or as a compiled binary, for any purpose,
7515commercial or non-commercial, and by any means.
7516In jurisdictions that recognize copyright laws, the author or authors of this
7517software dedicate any and all copyright interest in the software to the public
7518domain. We make this dedication for the benefit of the public at large and to
7519the detriment of our heirs and successors. We intend this dedication to be an
7520overt act of relinquishment in perpetuity of all present and future rights to
7521this software under copyright law.
7522THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
7523IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7524FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7525AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
7526ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
7527WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7528------------------------------------------------------------------------------
7529*/
diff --git a/externals/stb/stb_image.h b/externals/stb/stb_image.h
new file mode 100644
index 000000000..f0dfad1c6
--- /dev/null
+++ b/externals/stb/stb_image.h
@@ -0,0 +1,772 @@
1// SPDX-FileCopyrightText: stb http://nothings.org/stb
2// SPDX-License-Identifier: MIT
3
4/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb
5 no warranty implied; use at your own risk
6
7 Do this:
8 #define STB_IMAGE_IMPLEMENTATION
9 before you include this file in *one* C or C++ file to create the implementation.
10
11 // i.e. it should look like this:
12 #include ...
13 #include ...
14 #include ...
15 #define STB_IMAGE_IMPLEMENTATION
16 #include "stb_image.h"
17
18 You can #define STBI_ASSERT(x) before the #include to avoid using assert.h.
19 And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free
20
21
22 QUICK NOTES:
23 Primarily of interest to game developers and other people who can
24 avoid problematic images and only need the trivial interface
25
26 JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)
27 PNG 1/2/4/8/16-bit-per-channel
28
29 TGA (not sure what subset, if a subset)
30 BMP non-1bpp, non-RLE
31 PSD (composited view only, no extra channels, 8/16 bit-per-channel)
32
33 GIF (*comp always reports as 4-channel)
34 HDR (radiance rgbE format)
35 PIC (Softimage PIC)
36 PNM (PPM and PGM binary only)
37
38 Animated GIF still needs a proper API, but here's one way to do it:
39 http://gist.github.com/urraka/685d9a6340b26b830d49
40
41 - decode from memory or through FILE (define STBI_NO_STDIO to remove code)
42 - decode from arbitrary I/O callbacks
43 - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)
44
45 Full documentation under "DOCUMENTATION" below.
46
47
48LICENSE
49
50 See end of file for license information.
51
52RECENT REVISION HISTORY:
53
54 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
55 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
56 2.26 (2020-07-13) many minor fixes
57 2.25 (2020-02-02) fix warnings
58 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically
59 2.23 (2019-08-11) fix clang static analysis warning
60 2.22 (2019-03-04) gif fixes, fix warnings
61 2.21 (2019-02-25) fix typo in comment
62 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
63 2.19 (2018-02-11) fix warning
64 2.18 (2018-01-30) fix warnings
65 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings
66 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes
67 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC
68 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
69 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes
70 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
71 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64
72 RGB-format JPEG; remove white matting in PSD;
73 allocate large structures on the stack;
74 correct channel count for PNG & BMP
75 2.10 (2016-01-22) avoid warning introduced in 2.09
76 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED
77
78 See end of file for full revision history.
79
80
81 ============================ Contributors =========================
82
83 Image formats Extensions, features
84 Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info)
85 Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info)
86 Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG)
87 Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks)
88 Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG)
89 Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip)
90 Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD)
91 github:urraka (animated gif) Junggon Kim (PNM comments)
92 Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA)
93 socks-the-fox (16-bit PNG)
94 Jeremy Sawicki (handle all ImageNet JPGs)
95 Optimizations & bugfixes Mikhail Morozov (1-bit BMP)
96 Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query)
97 Arseny Kapoulkine Simon Breuss (16-bit PNM)
98 John-Mark Allen
99 Carmelo J Fdez-Aguera
100
101 Bug & warning fixes
102 Marc LeBlanc David Woo Guillaume George Martins Mozeiko
103 Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski
104 Phil Jordan Dave Moore Roy Eltham
105 Hayaki Saito Nathan Reed Won Chun
106 Luke Graham Johan Duparc Nick Verigakis the Horde3D community
107 Thomas Ruf Ronny Chevalier github:rlyeh
108 Janez Zemva John Bartholomew Michal Cichon github:romigrou
109 Jonathan Blow Ken Hamada Tero Hanninen github:svdijk
110 Eugene Golushkov Laurent Gomila Cort Stratton github:snagar
111 Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex
112 Cass Everitt Ryamond Barbiero github:grim210
113 Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw
114 Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus
115 Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo
116 Julian Raschke Gregory Mullen Christian Floisand github:darealshinji
117 Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007
118 Brad Weinberger Matvey Cherevko github:mosra
119 Luca Sas Alexander Veselov Zack Middleton [reserved]
120 Ryan C. Gordon [reserved] [reserved]
121 DO NOT ADD YOUR NAME HERE
122
123 Jacko Dirks
124
125 To add your name to the credits, pick a random blank space in the middle and fill it.
126 80% of merge conflicts on stb PRs are due to people adding their name at the end
127 of the credits.
128*/
129
130#ifndef STBI_INCLUDE_STB_IMAGE_H
131#define STBI_INCLUDE_STB_IMAGE_H
132
133// DOCUMENTATION
134//
135// Limitations:
136// - no 12-bit-per-channel JPEG
137// - no JPEGs with arithmetic coding
138// - GIF always returns *comp=4
139//
140// Basic usage (see HDR discussion below for HDR usage):
141// int x,y,n;
142// unsigned char *data = stbi_load(filename, &x, &y, &n, 0);
143// // ... process data if not NULL ...
144// // ... x = width, y = height, n = # 8-bit components per pixel ...
145// // ... replace '0' with '1'..'4' to force that many components per pixel
146// // ... but 'n' will always be the number that it would have been if you said 0
147// stbi_image_free(data);
148//
149// Standard parameters:
150// int *x -- outputs image width in pixels
151// int *y -- outputs image height in pixels
152// int *channels_in_file -- outputs # of image components in image file
153// int desired_channels -- if non-zero, # of image components requested in result
154//
155// The return value from an image loader is an 'unsigned char *' which points
156// to the pixel data, or NULL on an allocation failure or if the image is
157// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels,
158// with each pixel consisting of N interleaved 8-bit components; the first
159// pixel pointed to is top-left-most in the image. There is no padding between
160// image scanlines or between pixels, regardless of format. The number of
161// components N is 'desired_channels' if desired_channels is non-zero, or
162// *channels_in_file otherwise. If desired_channels is non-zero,
163// *channels_in_file has the number of components that _would_ have been
164// output otherwise. E.g. if you set desired_channels to 4, you will always
165// get RGBA output, but you can check *channels_in_file to see if it's trivially
166// opaque because e.g. there were only 3 channels in the source image.
167//
168// An output image with N components has the following components interleaved
169// in this order in each pixel:
170//
171// N=#comp components
172// 1 grey
173// 2 grey, alpha
174// 3 red, green, blue
175// 4 red, green, blue, alpha
176//
177// If image loading fails for any reason, the return value will be NULL,
178// and *x, *y, *channels_in_file will be unchanged. The function
179// stbi_failure_reason() can be queried for an extremely brief, end-user
180// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS
181// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly
182// more user-friendly ones.
183//
184// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.
185//
186// To query the width, height and component count of an image without having to
187// decode the full file, you can use the stbi_info family of functions:
188//
189// int x,y,n,ok;
190// ok = stbi_info(filename, &x, &y, &n);
191// // returns ok=1 and sets x, y, n if image is a supported format,
192// // 0 otherwise.
193//
194// Note that stb_image pervasively uses ints in its public API for sizes,
195// including sizes of memory buffers. This is now part of the API and thus
196// hard to change without causing breakage. As a result, the various image
197// loaders all have certain limits on image size; these differ somewhat
198// by format but generally boil down to either just under 2GB or just under
199// 1GB. When the decoded image would be larger than this, stb_image decoding
200// will fail.
201//
202// Additionally, stb_image will reject image files that have any of their
203// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS,
204// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit,
205// the only way to have an image with such dimensions load correctly
206// is for it to have a rather extreme aspect ratio. Either way, the
207// assumption here is that such larger images are likely to be malformed
208// or malicious. If you do need to load an image with individual dimensions
209// larger than that, and it still fits in the overall size limit, you can
210// #define STBI_MAX_DIMENSIONS on your own to be something larger.
211//
212// ===========================================================================
213//
214// UNICODE:
215//
216// If compiling for Windows and you wish to use Unicode filenames, compile
217// with
218// #define STBI_WINDOWS_UTF8
219// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert
220// Windows wchar_t filenames to utf8.
221//
222// ===========================================================================
223//
224// Philosophy
225//
226// stb libraries are designed with the following priorities:
227//
228// 1. easy to use
229// 2. easy to maintain
230// 3. good performance
231//
232// Sometimes I let "good performance" creep up in priority over "easy to maintain",
233// and for best performance I may provide less-easy-to-use APIs that give higher
234// performance, in addition to the easy-to-use ones. Nevertheless, it's important
235// to keep in mind that from the standpoint of you, a client of this library,
236// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all.
237//
238// Some secondary priorities arise directly from the first two, some of which
239// provide more explicit reasons why performance can't be emphasized.
240//
241// - Portable ("ease of use")
242// - Small source code footprint ("easy to maintain")
243// - No dependencies ("ease of use")
244//
245// ===========================================================================
246//
247// I/O callbacks
248//
249// I/O callbacks allow you to read from arbitrary sources, like packaged
250// files or some other source. Data read from callbacks are processed
251// through a small internal buffer (currently 128 bytes) to try to reduce
252// overhead.
253//
254// The three functions you must define are "read" (reads some bytes of data),
255// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end).
256//
257// ===========================================================================
258//
259// SIMD support
260//
261// The JPEG decoder will try to automatically use SIMD kernels on x86 when
262// supported by the compiler. For ARM Neon support, you must explicitly
263// request it.
264//
265// (The old do-it-yourself SIMD API is no longer supported in the current
266// code.)
267//
268// On x86, SSE2 will automatically be used when available based on a run-time
269// test; if not, the generic C versions are used as a fall-back. On ARM targets,
270// the typical path is to have separate builds for NEON and non-NEON devices
271// (at least this is true for iOS and Android). Therefore, the NEON support is
272// toggled by a build flag: define STBI_NEON to get NEON loops.
273//
274// If for some reason you do not want to use any of SIMD code, or if
275// you have issues compiling it, you can disable it entirely by
276// defining STBI_NO_SIMD.
277//
278// ===========================================================================
279//
280// HDR image support (disable by defining STBI_NO_HDR)
281//
282// stb_image supports loading HDR images in general, and currently the Radiance
283// .HDR file format specifically. You can still load any file through the existing
284// interface; if you attempt to load an HDR file, it will be automatically remapped
285// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1;
286// both of these constants can be reconfigured through this interface:
287//
288// stbi_hdr_to_ldr_gamma(2.2f);
289// stbi_hdr_to_ldr_scale(1.0f);
290//
291// (note, do not use _inverse_ constants; stbi_image will invert them
292// appropriately).
293//
294// Additionally, there is a new, parallel interface for loading files as
295// (linear) floats to preserve the full dynamic range:
296//
297// float *data = stbi_loadf(filename, &x, &y, &n, 0);
298//
299// If you load LDR images through this interface, those images will
300// be promoted to floating point values, run through the inverse of
301// constants corresponding to the above:
302//
303// stbi_ldr_to_hdr_scale(1.0f);
304// stbi_ldr_to_hdr_gamma(2.2f);
305//
306// Finally, given a filename (or an open file or memory block--see header
307// file for details) containing image data, you can query for the "most
308// appropriate" interface to use (that is, whether the image is HDR or
309// not), using:
310//
311// stbi_is_hdr(char *filename);
312//
313// ===========================================================================
314//
315// iPhone PNG support:
316//
317// We optionally support converting iPhone-formatted PNGs (which store
318// premultiplied BGRA) back to RGB, even though they're internally encoded
319// differently. To enable this conversion, call
320// stbi_convert_iphone_png_to_rgb(1).
321//
322// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per
323// pixel to remove any premultiplied alpha *only* if the image file explicitly
324// says there's premultiplied data (currently only happens in iPhone images,
325// and only if iPhone convert-to-rgb processing is on).
326//
327// ===========================================================================
328//
329// ADDITIONAL CONFIGURATION
330//
331// - You can suppress implementation of any of the decoders to reduce
332// your code footprint by #defining one or more of the following
333// symbols before creating the implementation.
334//
335// STBI_NO_JPEG
336// STBI_NO_PNG
337// STBI_NO_BMP
338// STBI_NO_PSD
339// STBI_NO_TGA
340// STBI_NO_GIF
341// STBI_NO_HDR
342// STBI_NO_PIC
343// STBI_NO_PNM (.ppm and .pgm)
344//
345// - You can request *only* certain decoders and suppress all other ones
346// (this will be more forward-compatible, as addition of new decoders
347// doesn't require you to disable them explicitly):
348//
349// STBI_ONLY_JPEG
350// STBI_ONLY_PNG
351// STBI_ONLY_BMP
352// STBI_ONLY_PSD
353// STBI_ONLY_TGA
354// STBI_ONLY_GIF
355// STBI_ONLY_HDR
356// STBI_ONLY_PIC
357// STBI_ONLY_PNM (.ppm and .pgm)
358//
359// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still
360// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB
361//
362// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater
363// than that size (in either width or height) without further processing.
364// This is to let programs in the wild set an upper bound to prevent
365// denial-of-service attacks on untrusted data, as one could generate a
366// valid image of gigantic dimensions and force stb_image to allocate a
367// huge block of memory and spend disproportionate time decoding it. By
368// default this is set to (1 << 24), which is 16777216, but that's still
369// very big.
370
371#ifndef STBI_NO_STDIO
372#include <stdio.h>
373#endif // STBI_NO_STDIO
374
375#define STBI_VERSION 1
376
377enum
378{
379 STBI_default = 0, // only used for desired_channels
380
381 STBI_grey = 1,
382 STBI_grey_alpha = 2,
383 STBI_rgb = 3,
384 STBI_rgb_alpha = 4
385};
386
387#include <stdlib.h>
388typedef unsigned char stbi_uc;
389typedef unsigned short stbi_us;
390
391#ifdef __cplusplus
392extern "C" {
393#endif
394
395#ifndef STBIDEF
396#ifdef STB_IMAGE_STATIC
397#define STBIDEF static
398#else
399#define STBIDEF extern
400#endif
401#endif
402
403//////////////////////////////////////////////////////////////////////////////
404//
405// PRIMARY API - works on images of any type
406//
407
408//
409// load image by filename, open file, or memory buffer
410//
411
412typedef struct
413{
414 int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read
415 void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative
416 int (*eof) (void *user); // returns nonzero if we are at end of file/data
417} stbi_io_callbacks;
418
419////////////////////////////////////
420//
421// 8-bits-per-channel interface
422//
423
424STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels);
425STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels);
426
427#ifndef STBI_NO_STDIO
428STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
429STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
430// for stbi_load_from_file, file pointer is left pointing immediately after image
431#endif
432
433#ifndef STBI_NO_GIF
434STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp);
435#endif
436
437#ifdef STBI_WINDOWS_UTF8
438STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);
439#endif
440
441////////////////////////////////////
442//
443// 16-bits-per-channel interface
444//
445
446STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);
447STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels);
448
449#ifndef STBI_NO_STDIO
450STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
451STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
452#endif
453
454////////////////////////////////////
455//
456// float-per-channel interface
457//
458#ifndef STBI_NO_LINEAR
459 STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);
460 STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels);
461
462 #ifndef STBI_NO_STDIO
463 STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
464 STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
465 #endif
466#endif
467
468#ifndef STBI_NO_HDR
469 STBIDEF void stbi_hdr_to_ldr_gamma(float gamma);
470 STBIDEF void stbi_hdr_to_ldr_scale(float scale);
471#endif // STBI_NO_HDR
472
473#ifndef STBI_NO_LINEAR
474 STBIDEF void stbi_ldr_to_hdr_gamma(float gamma);
475 STBIDEF void stbi_ldr_to_hdr_scale(float scale);
476#endif // STBI_NO_LINEAR
477
478// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR
479STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user);
480STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);
481#ifndef STBI_NO_STDIO
482STBIDEF int stbi_is_hdr (char const *filename);
483STBIDEF int stbi_is_hdr_from_file(FILE *f);
484#endif // STBI_NO_STDIO
485
486
487// get a VERY brief reason for failure
488// on most compilers (and ALL modern mainstream compilers) this is threadsafe
489STBIDEF const char *stbi_failure_reason (void);
490
491// free the loaded image -- this is just free()
492STBIDEF void stbi_image_free (void *retval_from_stbi_load);
493
494// get image dimensions & components without fully decoding
495STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);
496STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp);
497STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len);
498STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user);
499
500#ifndef STBI_NO_STDIO
501STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp);
502STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp);
503STBIDEF int stbi_is_16_bit (char const *filename);
504STBIDEF int stbi_is_16_bit_from_file(FILE *f);
505#endif
506
507
508
509// for image formats that explicitly notate that they have premultiplied alpha,
510// we just return the colors as stored in the file. set this flag to force
511// unpremultiplication. results are undefined if the unpremultiply overflow.
512STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);
513
514// indicate whether we should process iphone images back to canonical format,
515// or just pass them through "as-is"
516STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);
517
518// flip the image vertically, so the first pixel in the output array is the bottom left
519STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);
520
521// as above, but only applies to images loaded on the thread that calls the function
522// this function is only available if your compiler supports thread-local variables;
523// calling it will fail to link if your compiler doesn't
524STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply);
525STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert);
526STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip);
527
528// ZLIB client - used by PNG, available for other purposes
529
530STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);
531STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header);
532STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);
533STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
534
535STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);
536STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
537
538
539#ifdef __cplusplus
540}
541#endif
542
543//
544//
545//// end header file /////////////////////////////////////////////////////
546#endif // STBI_INCLUDE_STB_IMAGE_H
547
548/*
549 revision history:
550 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
551 2.19 (2018-02-11) fix warning
552 2.18 (2018-01-30) fix warnings
553 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug
554 1-bit BMP
555 *_is_16_bit api
556 avoid warnings
557 2.16 (2017-07-23) all functions have 16-bit variants;
558 STBI_NO_STDIO works again;
559 compilation fixes;
560 fix rounding in unpremultiply;
561 optimize vertical flip;
562 disable raw_len validation;
563 documentation fixes
564 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode;
565 warning fixes; disable run-time SSE detection on gcc;
566 uniform handling of optional "return" values;
567 thread-safe initialization of zlib tables
568 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
569 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now
570 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
571 2.11 (2016-04-02) allocate large structures on the stack
572 remove white matting for transparent PSD
573 fix reported channel count for PNG & BMP
574 re-enable SSE2 in non-gcc 64-bit
575 support RGB-formatted JPEG
576 read 16-bit PNGs (only as 8-bit)
577 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED
578 2.09 (2016-01-16) allow comments in PNM files
579 16-bit-per-pixel TGA (not bit-per-component)
580 info() for TGA could break due to .hdr handling
581 info() for BMP to shares code instead of sloppy parse
582 can use STBI_REALLOC_SIZED if allocator doesn't support realloc
583 code cleanup
584 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA
585 2.07 (2015-09-13) fix compiler warnings
586 partial animated GIF support
587 limited 16-bpc PSD support
588 #ifdef unused functions
589 bug with < 92 byte PIC,PNM,HDR,TGA
590 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value
591 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning
592 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit
593 2.03 (2015-04-12) extra corruption checking (mmozeiko)
594 stbi_set_flip_vertically_on_load (nguillemot)
595 fix NEON support; fix mingw support
596 2.02 (2015-01-19) fix incorrect assert, fix warning
597 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2
598 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG
599 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg)
600 progressive JPEG (stb)
601 PGM/PPM support (Ken Miller)
602 STBI_MALLOC,STBI_REALLOC,STBI_FREE
603 GIF bugfix -- seemingly never worked
604 STBI_NO_*, STBI_ONLY_*
605 1.48 (2014-12-14) fix incorrectly-named assert()
606 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb)
607 optimize PNG (ryg)
608 fix bug in interlaced PNG with user-specified channel count (stb)
609 1.46 (2014-08-26)
610 fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG
611 1.45 (2014-08-16)
612 fix MSVC-ARM internal compiler error by wrapping malloc
613 1.44 (2014-08-07)
614 various warning fixes from Ronny Chevalier
615 1.43 (2014-07-15)
616 fix MSVC-only compiler problem in code changed in 1.42
617 1.42 (2014-07-09)
618 don't define _CRT_SECURE_NO_WARNINGS (affects user code)
619 fixes to stbi__cleanup_jpeg path
620 added STBI_ASSERT to avoid requiring assert.h
621 1.41 (2014-06-25)
622 fix search&replace from 1.36 that messed up comments/error messages
623 1.40 (2014-06-22)
624 fix gcc struct-initialization warning
625 1.39 (2014-06-15)
626 fix to TGA optimization when req_comp != number of components in TGA;
627 fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite)
628 add support for BMP version 5 (more ignored fields)
629 1.38 (2014-06-06)
630 suppress MSVC warnings on integer casts truncating values
631 fix accidental rename of 'skip' field of I/O
632 1.37 (2014-06-04)
633 remove duplicate typedef
634 1.36 (2014-06-03)
635 convert to header file single-file library
636 if de-iphone isn't set, load iphone images color-swapped instead of returning NULL
637 1.35 (2014-05-27)
638 various warnings
639 fix broken STBI_SIMD path
640 fix bug where stbi_load_from_file no longer left file pointer in correct place
641 fix broken non-easy path for 32-bit BMP (possibly never used)
642 TGA optimization by Arseny Kapoulkine
643 1.34 (unknown)
644 use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case
645 1.33 (2011-07-14)
646 make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements
647 1.32 (2011-07-13)
648 support for "info" function for all supported filetypes (SpartanJ)
649 1.31 (2011-06-20)
650 a few more leak fixes, bug in PNG handling (SpartanJ)
651 1.30 (2011-06-11)
652 added ability to load files via callbacks to accomidate custom input streams (Ben Wenger)
653 removed deprecated format-specific test/load functions
654 removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway
655 error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha)
656 fix inefficiency in decoding 32-bit BMP (David Woo)
657 1.29 (2010-08-16)
658 various warning fixes from Aurelien Pocheville
659 1.28 (2010-08-01)
660 fix bug in GIF palette transparency (SpartanJ)
661 1.27 (2010-08-01)
662 cast-to-stbi_uc to fix warnings
663 1.26 (2010-07-24)
664 fix bug in file buffering for PNG reported by SpartanJ
665 1.25 (2010-07-17)
666 refix trans_data warning (Won Chun)
667 1.24 (2010-07-12)
668 perf improvements reading from files on platforms with lock-heavy fgetc()
669 minor perf improvements for jpeg
670 deprecated type-specific functions so we'll get feedback if they're needed
671 attempt to fix trans_data warning (Won Chun)
672 1.23 fixed bug in iPhone support
673 1.22 (2010-07-10)
674 removed image *writing* support
675 stbi_info support from Jetro Lauha
676 GIF support from Jean-Marc Lienher
677 iPhone PNG-extensions from James Brown
678 warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva)
679 1.21 fix use of 'stbi_uc' in header (reported by jon blow)
680 1.20 added support for Softimage PIC, by Tom Seddon
681 1.19 bug in interlaced PNG corruption check (found by ryg)
682 1.18 (2008-08-02)
683 fix a threading bug (local mutable static)
684 1.17 support interlaced PNG
685 1.16 major bugfix - stbi__convert_format converted one too many pixels
686 1.15 initialize some fields for thread safety
687 1.14 fix threadsafe conversion bug
688 header-file-only version (#define STBI_HEADER_FILE_ONLY before including)
689 1.13 threadsafe
690 1.12 const qualifiers in the API
691 1.11 Support installable IDCT, colorspace conversion routines
692 1.10 Fixes for 64-bit (don't use "unsigned long")
693 optimized upsampling by Fabian "ryg" Giesen
694 1.09 Fix format-conversion for PSD code (bad global variables!)
695 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz
696 1.07 attempt to fix C++ warning/errors again
697 1.06 attempt to fix C++ warning/errors again
698 1.05 fix TGA loading to return correct *comp and use good luminance calc
699 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free
700 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR
701 1.02 support for (subset of) HDR files, float interface for preferred access to them
702 1.01 fix bug: possible bug in handling right-side up bmps... not sure
703 fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all
704 1.00 interface to zlib that skips zlib header
705 0.99 correct handling of alpha in palette
706 0.98 TGA loader by lonesock; dynamically add loaders (untested)
707 0.97 jpeg errors on too large a file; also catch another malloc failure
708 0.96 fix detection of invalid v value - particleman@mollyrocket forum
709 0.95 during header scan, seek to markers in case of padding
710 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same
711 0.93 handle jpegtran output; verbose errors
712 0.92 read 4,8,16,24,32-bit BMP files of several formats
713 0.91 output 24-bit Windows 3.0 BMP files
714 0.90 fix a few more warnings; bump version number to approach 1.0
715 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd
716 0.60 fix compiling as c++
717 0.59 fix warnings: merge Dave Moore's -Wall fixes
718 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian
719 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available
720 0.56 fix bug: zlib uncompressed mode len vs. nlen
721 0.55 fix bug: restart_interval not initialized to 0
722 0.54 allow NULL for 'int *comp'
723 0.53 fix bug in png 3->4; speedup png decoding
724 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments
725 0.51 obey req_comp requests, 1-component jpegs return as 1-component,
726 on 'test' only check type, not whether we support this variant
727 0.50 (2006-11-19)
728 first released version
729*/
730
731
732/*
733------------------------------------------------------------------------------
734This software is available under 2 licenses -- choose whichever you prefer.
735------------------------------------------------------------------------------
736ALTERNATIVE A - MIT License
737Copyright (c) 2017 Sean Barrett
738Permission is hereby granted, free of charge, to any person obtaining a copy of
739this software and associated documentation files (the "Software"), to deal in
740the Software without restriction, including without limitation the rights to
741use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
742of the Software, and to permit persons to whom the Software is furnished to do
743so, subject to the following conditions:
744The above copyright notice and this permission notice shall be included in all
745copies or substantial portions of the Software.
746THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
747IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
748FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
749AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
750LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
751OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
752SOFTWARE.
753------------------------------------------------------------------------------
754ALTERNATIVE B - Public Domain (www.unlicense.org)
755This is free and unencumbered software released into the public domain.
756Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
757software, either in source code form or as a compiled binary, for any purpose,
758commercial or non-commercial, and by any means.
759In jurisdictions that recognize copyright laws, the author or authors of this
760software dedicate any and all copyright interest in the software to the public
761domain. We make this dedication for the benefit of the public at large and to
762the detriment of our heirs and successors. We intend this dedication to be an
763overt act of relinquishment in perpetuity of all present and future rights to
764this software under copyright law.
765THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
766IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
767FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
768AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
769ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
770WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
771------------------------------------------------------------------------------
772*/
diff --git a/externals/stb/stb_image_resize.cpp b/externals/stb/stb_image_resize.cpp
new file mode 100644
index 000000000..6f023629e
--- /dev/null
+++ b/externals/stb/stb_image_resize.cpp
@@ -0,0 +1,2282 @@
1// SPDX-FileCopyrightText: Jorge L Rodriguez
2// SPDX-License-Identifier: MIT
3
4/* stb_image_resize - v0.97 - public domain image resizing
5 by Jorge L Rodriguez (@VinoBS) - 2014
6 http://github.com/nothings/stb
7
8 CONTRIBUTORS
9 Jorge L Rodriguez: Implementation
10 Sean Barrett: API design, optimizations
11 Aras Pranckevicius: bugfix
12 Nathan Reed: warning fixes
13
14 REVISIONS
15 0.97 (2020-02-02) fixed warning
16 0.96 (2019-03-04) fixed warnings
17 0.95 (2017-07-23) fixed warnings
18 0.94 (2017-03-18) fixed warnings
19 0.93 (2017-03-03) fixed bug with certain combinations of heights
20 0.92 (2017-01-02) fix integer overflow on large (>2GB) images
21 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions
22 0.90 (2014-09-17) first released version
23
24 LICENSE
25 See end of file for license information.
26
27 TODO
28 Don't decode all of the image data when only processing a partial tile
29 Don't use full-width decode buffers when only processing a partial tile
30 When processing wide images, break processing into tiles so data fits in L1 cache
31 Installable filters?
32 Resize that respects alpha test coverage
33 (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage:
34 https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp )
35*/
36
37#include <stb_image_resize.h>
38
39#ifndef STBIR_ASSERT
40#include <assert.h>
41#define STBIR_ASSERT(x) assert(x)
42#endif
43
44// For memset
45#include <string.h>
46
47#include <math.h>
48
49#ifndef STBIR_MALLOC
50#include <stdlib.h>
51// use comma operator to evaluate c, to avoid "unused parameter" warnings
52#define STBIR_MALLOC(size,c) ((void)(c), malloc(size))
53#define STBIR_FREE(ptr,c) ((void)(c), free(ptr))
54#endif
55
56#ifndef _MSC_VER
57#ifdef __cplusplus
58#define stbir__inline inline
59#else
60#define stbir__inline
61#endif
62#else
63#define stbir__inline __forceinline
64#endif
65
66
67// should produce compiler error if size is wrong
68typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1];
69
70#ifdef _MSC_VER
71#define STBIR__NOTUSED(v) (void)(v)
72#else
73#define STBIR__NOTUSED(v) (void)sizeof(v)
74#endif
75
76#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0]))
77
78#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE
79#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM
80#endif
81
82#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE
83#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL
84#endif
85
86#ifndef STBIR_PROGRESS_REPORT
87#define STBIR_PROGRESS_REPORT(float_0_to_1)
88#endif
89
90#ifndef STBIR_MAX_CHANNELS
91#define STBIR_MAX_CHANNELS 64
92#endif
93
94#if STBIR_MAX_CHANNELS > 65536
95#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536."
96// because we store the indices in 16-bit variables
97#endif
98
99// This value is added to alpha just before premultiplication to avoid
100// zeroing out color values. It is equivalent to 2^-80. If you don't want
101// that behavior (it may interfere if you have floating point images with
102// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to
103// disable it.
104#ifndef STBIR_ALPHA_EPSILON
105#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20))
106#endif
107
108
109
110#ifdef _MSC_VER
111#define STBIR__UNUSED_PARAM(v) (void)(v)
112#else
113#define STBIR__UNUSED_PARAM(v) (void)sizeof(v)
114#endif
115
116// must match stbir_datatype
117static unsigned char stbir__type_size[] = {
118 1, // STBIR_TYPE_UINT8
119 2, // STBIR_TYPE_UINT16
120 4, // STBIR_TYPE_UINT32
121 4, // STBIR_TYPE_FLOAT
122};
123
124// Kernel function centered at 0
125typedef float (stbir__kernel_fn)(float x, float scale);
126typedef float (stbir__support_fn)(float scale);
127
128typedef struct
129{
130 stbir__kernel_fn* kernel;
131 stbir__support_fn* support;
132} stbir__filter_info;
133
134// When upsampling, the contributors are which source pixels contribute.
135// When downsampling, the contributors are which destination pixels are contributed to.
136typedef struct
137{
138 int n0; // First contributing pixel
139 int n1; // Last contributing pixel
140} stbir__contributors;
141
142typedef struct
143{
144 const void* input_data;
145 int input_w;
146 int input_h;
147 int input_stride_bytes;
148
149 void* output_data;
150 int output_w;
151 int output_h;
152 int output_stride_bytes;
153
154 float s0, t0, s1, t1;
155
156 float horizontal_shift; // Units: output pixels
157 float vertical_shift; // Units: output pixels
158 float horizontal_scale;
159 float vertical_scale;
160
161 int channels;
162 int alpha_channel;
163 stbir_uint32 flags;
164 stbir_datatype type;
165 stbir_filter horizontal_filter;
166 stbir_filter vertical_filter;
167 stbir_edge edge_horizontal;
168 stbir_edge edge_vertical;
169 stbir_colorspace colorspace;
170
171 stbir__contributors* horizontal_contributors;
172 float* horizontal_coefficients;
173
174 stbir__contributors* vertical_contributors;
175 float* vertical_coefficients;
176
177 int decode_buffer_pixels;
178 float* decode_buffer;
179
180 float* horizontal_buffer;
181
182 // cache these because ceil/floor are inexplicably showing up in profile
183 int horizontal_coefficient_width;
184 int vertical_coefficient_width;
185 int horizontal_filter_pixel_width;
186 int vertical_filter_pixel_width;
187 int horizontal_filter_pixel_margin;
188 int vertical_filter_pixel_margin;
189 int horizontal_num_contributors;
190 int vertical_num_contributors;
191
192 int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter)
193 int ring_buffer_num_entries; // Total number of entries in the ring buffer.
194 int ring_buffer_first_scanline;
195 int ring_buffer_last_scanline;
196 int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer
197 float* ring_buffer;
198
199 float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds.
200
201 int horizontal_contributors_size;
202 int horizontal_coefficients_size;
203 int vertical_contributors_size;
204 int vertical_coefficients_size;
205 int decode_buffer_size;
206 int horizontal_buffer_size;
207 int ring_buffer_size;
208 int encode_buffer_size;
209} stbir__info;
210
211
212static const float stbir__max_uint8_as_float = 255.0f;
213static const float stbir__max_uint16_as_float = 65535.0f;
214static const double stbir__max_uint32_as_float = 4294967295.0;
215
216
217static stbir__inline int stbir__min(int a, int b)
218{
219 return a < b ? a : b;
220}
221
222static stbir__inline float stbir__saturate(float x)
223{
224 if (x < 0)
225 return 0;
226
227 if (x > 1)
228 return 1;
229
230 return x;
231}
232
233#ifdef STBIR_SATURATE_INT
234static stbir__inline stbir_uint8 stbir__saturate8(int x)
235{
236 if ((unsigned int) x <= 255)
237 return x;
238
239 if (x < 0)
240 return 0;
241
242 return 255;
243}
244
245static stbir__inline stbir_uint16 stbir__saturate16(int x)
246{
247 if ((unsigned int) x <= 65535)
248 return x;
249
250 if (x < 0)
251 return 0;
252
253 return 65535;
254}
255#endif
256
257static float stbir__srgb_uchar_to_linear_float[256] = {
258 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f,
259 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f,
260 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f,
261 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f,
262 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f,
263 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f,
264 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f,
265 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f,
266 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f,
267 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f,
268 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f,
269 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f,
270 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f,
271 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f,
272 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f,
273 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f,
274 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f,
275 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f,
276 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f,
277 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f,
278 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f,
279 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f,
280 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f,
281 0.982251f, 0.991102f, 1.0f
282};
283
284static float stbir__srgb_to_linear(float f)
285{
286 if (f <= 0.04045f)
287 return f / 12.92f;
288 else
289 return (float)pow((f + 0.055f) / 1.055f, 2.4f);
290}
291
292static float stbir__linear_to_srgb(float f)
293{
294 if (f <= 0.0031308f)
295 return f * 12.92f;
296 else
297 return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f;
298}
299
300#ifndef STBIR_NON_IEEE_FLOAT
301// From https://gist.github.com/rygorous/2203834
302
303typedef union
304{
305 stbir_uint32 u;
306 float f;
307} stbir__FP32;
308
309static const stbir_uint32 fp32_to_srgb8_tab4[104] = {
310 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d,
311 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a,
312 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033,
313 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067,
314 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5,
315 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2,
316 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143,
317 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af,
318 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240,
319 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300,
320 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401,
321 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559,
322 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723,
323};
324
325static stbir_uint8 stbir__linear_to_srgb_uchar(float in)
326{
327 static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps
328 static const stbir__FP32 minval = { (127-13) << 23 };
329 stbir_uint32 tab,bias,scale,t;
330 stbir__FP32 f;
331
332 // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively.
333 // The tests are carefully written so that NaNs map to 0, same as in the reference
334 // implementation.
335 if (!(in > minval.f)) // written this way to catch NaNs
336 in = minval.f;
337 if (in > almostone.f)
338 in = almostone.f;
339
340 // Do the table lookup and unpack bias, scale
341 f.f = in;
342 tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20];
343 bias = (tab >> 16) << 9;
344 scale = tab & 0xffff;
345
346 // Grab next-highest mantissa bits and perform linear interpolation
347 t = (f.u >> 12) & 0xff;
348 return (unsigned char) ((bias + scale*t) >> 16);
349}
350
351#else
352// sRGB transition values, scaled by 1<<28
353static int stbir__srgb_offset_to_linear_scaled[256] =
354{
355 0, 40738, 122216, 203693, 285170, 366648, 448125, 529603,
356 611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926,
357 1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148,
358 2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856,
359 3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731,
360 5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369,
361 7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021,
362 10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073,
363 13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389,
364 17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552,
365 21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066,
366 25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490,
367 31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568,
368 36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316,
369 43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096,
370 49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700,
371 57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376,
372 65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912,
373 74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648,
374 83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512,
375 93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072,
376 104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544,
377 115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832,
378 127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528,
379 140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968,
380 154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184,
381 168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992,
382 183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968,
383 199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480,
384 215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656,
385 232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464,
386 250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664,
387};
388
389static stbir_uint8 stbir__linear_to_srgb_uchar(float f)
390{
391 int x = (int) (f * (1 << 28)); // has headroom so you don't need to clamp
392 int v = 0;
393 int i;
394
395 // Refine the guess with a short binary search.
396 i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
397 i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
398 i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
399 i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
400 i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
401 i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
402 i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
403 i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
404
405 return (stbir_uint8) v;
406}
407#endif
408
409static float stbir__filter_trapezoid(float x, float scale)
410{
411 float halfscale = scale / 2;
412 float t = 0.5f + halfscale;
413 STBIR_ASSERT(scale <= 1);
414
415 x = (float)fabs(x);
416
417 if (x >= t)
418 return 0;
419 else
420 {
421 float r = 0.5f - halfscale;
422 if (x <= r)
423 return 1;
424 else
425 return (t - x) / scale;
426 }
427}
428
429static float stbir__support_trapezoid(float scale)
430{
431 STBIR_ASSERT(scale <= 1);
432 return 0.5f + scale / 2;
433}
434
435static float stbir__filter_triangle(float x, float s)
436{
437 STBIR__UNUSED_PARAM(s);
438
439 x = (float)fabs(x);
440
441 if (x <= 1.0f)
442 return 1 - x;
443 else
444 return 0;
445}
446
447static float stbir__filter_cubic(float x, float s)
448{
449 STBIR__UNUSED_PARAM(s);
450
451 x = (float)fabs(x);
452
453 if (x < 1.0f)
454 return (4 + x*x*(3*x - 6))/6;
455 else if (x < 2.0f)
456 return (8 + x*(-12 + x*(6 - x)))/6;
457
458 return (0.0f);
459}
460
461static float stbir__filter_catmullrom(float x, float s)
462{
463 STBIR__UNUSED_PARAM(s);
464
465 x = (float)fabs(x);
466
467 if (x < 1.0f)
468 return 1 - x*x*(2.5f - 1.5f*x);
469 else if (x < 2.0f)
470 return 2 - x*(4 + x*(0.5f*x - 2.5f));
471
472 return (0.0f);
473}
474
475static float stbir__filter_mitchell(float x, float s)
476{
477 STBIR__UNUSED_PARAM(s);
478
479 x = (float)fabs(x);
480
481 if (x < 1.0f)
482 return (16 + x*x*(21 * x - 36))/18;
483 else if (x < 2.0f)
484 return (32 + x*(-60 + x*(36 - 7*x)))/18;
485
486 return (0.0f);
487}
488
489static float stbir__support_zero(float s)
490{
491 STBIR__UNUSED_PARAM(s);
492 return 0;
493}
494
495static float stbir__support_one(float s)
496{
497 STBIR__UNUSED_PARAM(s);
498 return 1;
499}
500
501static float stbir__support_two(float s)
502{
503 STBIR__UNUSED_PARAM(s);
504 return 2;
505}
506
507static stbir__filter_info stbir__filter_info_table[] = {
508 { NULL, stbir__support_zero },
509 { stbir__filter_trapezoid, stbir__support_trapezoid },
510 { stbir__filter_triangle, stbir__support_one },
511 { stbir__filter_cubic, stbir__support_two },
512 { stbir__filter_catmullrom, stbir__support_two },
513 { stbir__filter_mitchell, stbir__support_two },
514};
515
516stbir__inline static int stbir__use_upsampling(float ratio)
517{
518 return ratio > 1;
519}
520
521stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info)
522{
523 return stbir__use_upsampling(stbir_info->horizontal_scale);
524}
525
526stbir__inline static int stbir__use_height_upsampling(stbir__info* stbir_info)
527{
528 return stbir__use_upsampling(stbir_info->vertical_scale);
529}
530
531// This is the maximum number of input samples that can affect an output sample
532// with the given filter
533static int stbir__get_filter_pixel_width(stbir_filter filter, float scale)
534{
535 STBIR_ASSERT(filter != 0);
536 STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));
537
538 if (stbir__use_upsampling(scale))
539 return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2);
540 else
541 return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale);
542}
543
544// This is how much to expand buffers to account for filters seeking outside
545// the image boundaries.
546static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale)
547{
548 return stbir__get_filter_pixel_width(filter, scale) / 2;
549}
550
551static int stbir__get_coefficient_width(stbir_filter filter, float scale)
552{
553 if (stbir__use_upsampling(scale))
554 return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2);
555 else
556 return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2);
557}
558
559static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size)
560{
561 if (stbir__use_upsampling(scale))
562 return output_size;
563 else
564 return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2);
565}
566
567static int stbir__get_total_horizontal_coefficients(stbir__info* info)
568{
569 return info->horizontal_num_contributors
570 * stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale);
571}
572
573static int stbir__get_total_vertical_coefficients(stbir__info* info)
574{
575 return info->vertical_num_contributors
576 * stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale);
577}
578
579static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n)
580{
581 return &contributors[n];
582}
583
584// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample,
585// if you change it here change it there too.
586static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c)
587{
588 int width = stbir__get_coefficient_width(filter, scale);
589 return &coefficients[width*n + c];
590}
591
592static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max)
593{
594 switch (edge)
595 {
596 case STBIR_EDGE_ZERO:
597 return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later
598
599 case STBIR_EDGE_CLAMP:
600 if (n < 0)
601 return 0;
602
603 if (n >= max)
604 return max - 1;
605
606 return n; // NOTREACHED
607
608 case STBIR_EDGE_REFLECT:
609 {
610 if (n < 0)
611 {
612 if (n < max)
613 return -n;
614 else
615 return max - 1;
616 }
617
618 if (n >= max)
619 {
620 int max2 = max * 2;
621 if (n >= max2)
622 return 0;
623 else
624 return max2 - n - 1;
625 }
626
627 return n; // NOTREACHED
628 }
629
630 case STBIR_EDGE_WRAP:
631 if (n >= 0)
632 return (n % max);
633 else
634 {
635 int m = (-n) % max;
636
637 if (m != 0)
638 m = max - m;
639
640 return (m);
641 }
642 // NOTREACHED
643
644 default:
645 STBIR_ASSERT(!"Unimplemented edge type");
646 return 0;
647 }
648}
649
650stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max)
651{
652 // avoid per-pixel switch
653 if (n >= 0 && n < max)
654 return n;
655 return stbir__edge_wrap_slow(edge, n, max);
656}
657
658// What input pixels contribute to this output pixel?
659static void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out)
660{
661 float out_pixel_center = (float)n + 0.5f;
662 float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius;
663 float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius;
664
665 float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio;
666 float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio;
667
668 *in_center_of_out = (out_pixel_center + out_shift) / scale_ratio;
669 *in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5));
670 *in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5));
671}
672
673// What output pixels does this input pixel contribute to?
674static void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in)
675{
676 float in_pixel_center = (float)n + 0.5f;
677 float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius;
678 float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius;
679
680 float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift;
681 float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift;
682
683 *out_center_of_in = in_pixel_center * scale_ratio - out_shift;
684 *out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5));
685 *out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5));
686}
687
688static void stbir__calculate_coefficients_upsample(stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group)
689{
690 int i;
691 float total_filter = 0;
692 float filter_scale;
693
694 STBIR_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.
695
696 contributor->n0 = in_first_pixel;
697 contributor->n1 = in_last_pixel;
698
699 STBIR_ASSERT(contributor->n1 >= contributor->n0);
700
701 for (i = 0; i <= in_last_pixel - in_first_pixel; i++)
702 {
703 float in_pixel_center = (float)(i + in_first_pixel) + 0.5f;
704 coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale);
705
706 // If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.)
707 if (i == 0 && !coefficient_group[i])
708 {
709 contributor->n0 = ++in_first_pixel;
710 i--;
711 continue;
712 }
713
714 total_filter += coefficient_group[i];
715 }
716
717 // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be.
718 // It would be true in exact math but is at best approximately true in floating-point math,
719 // and it would not make sense to try and put actual bounds on this here because it depends
720 // on the image aspect ratio which can get pretty extreme.
721 //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0);
722
723 STBIR_ASSERT(total_filter > 0.9);
724 STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off.
725
726 // Make sure the sum of all coefficients is 1.
727 filter_scale = 1 / total_filter;
728
729 for (i = 0; i <= in_last_pixel - in_first_pixel; i++)
730 coefficient_group[i] *= filter_scale;
731
732 for (i = in_last_pixel - in_first_pixel; i >= 0; i--)
733 {
734 if (coefficient_group[i])
735 break;
736
737 // This line has no weight. We can skip it.
738 contributor->n1 = contributor->n0 + i - 1;
739 }
740}
741
742static void stbir__calculate_coefficients_downsample(stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group)
743{
744 int i;
745
746 STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.
747
748 contributor->n0 = out_first_pixel;
749 contributor->n1 = out_last_pixel;
750
751 STBIR_ASSERT(contributor->n1 >= contributor->n0);
752
753 for (i = 0; i <= out_last_pixel - out_first_pixel; i++)
754 {
755 float out_pixel_center = (float)(i + out_first_pixel) + 0.5f;
756 float x = out_pixel_center - out_center_of_in;
757 coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio;
758 }
759
760 // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be.
761 // It would be true in exact math but is at best approximately true in floating-point math,
762 // and it would not make sense to try and put actual bounds on this here because it depends
763 // on the image aspect ratio which can get pretty extreme.
764 //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0);
765
766 for (i = out_last_pixel - out_first_pixel; i >= 0; i--)
767 {
768 if (coefficient_group[i])
769 break;
770
771 // This line has no weight. We can skip it.
772 contributor->n1 = contributor->n0 + i - 1;
773 }
774}
775
776static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size)
777{
778 int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size);
779 int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio);
780 int i, j;
781 int skip;
782
783 for (i = 0; i < output_size; i++)
784 {
785 float scale;
786 float total = 0;
787
788 for (j = 0; j < num_contributors; j++)
789 {
790 if (i >= contributors[j].n0 && i <= contributors[j].n1)
791 {
792 float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0);
793 total += coefficient;
794 }
795 else if (i < contributors[j].n0)
796 break;
797 }
798
799 STBIR_ASSERT(total > 0.9f);
800 STBIR_ASSERT(total < 1.1f);
801
802 scale = 1 / total;
803
804 for (j = 0; j < num_contributors; j++)
805 {
806 if (i >= contributors[j].n0 && i <= contributors[j].n1)
807 *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale;
808 else if (i < contributors[j].n0)
809 break;
810 }
811 }
812
813 // Optimize: Skip zero coefficients and contributions outside of image bounds.
814 // Do this after normalizing because normalization depends on the n0/n1 values.
815 for (j = 0; j < num_contributors; j++)
816 {
817 int range, max, width;
818
819 skip = 0;
820 while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0)
821 skip++;
822
823 contributors[j].n0 += skip;
824
825 while (contributors[j].n0 < 0)
826 {
827 contributors[j].n0++;
828 skip++;
829 }
830
831 range = contributors[j].n1 - contributors[j].n0 + 1;
832 max = stbir__min(num_coefficients, range);
833
834 width = stbir__get_coefficient_width(filter, scale_ratio);
835 for (i = 0; i < max; i++)
836 {
837 if (i + skip >= width)
838 break;
839
840 *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip);
841 }
842
843 continue;
844 }
845
846 // Using min to avoid writing into invalid pixels.
847 for (i = 0; i < num_contributors; i++)
848 contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1);
849}
850
851// Each scan line uses the same kernel values so we should calculate the kernel
852// values once and then we can use them for every scan line.
853static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size)
854{
855 int n;
856 int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size);
857
858 if (stbir__use_upsampling(scale_ratio))
859 {
860 float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio;
861
862 // Looping through out pixels
863 for (n = 0; n < total_contributors; n++)
864 {
865 float in_center_of_out; // Center of the current out pixel in the in pixel space
866 int in_first_pixel, in_last_pixel;
867
868 stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out);
869
870 stbir__calculate_coefficients_upsample(filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0));
871 }
872 }
873 else
874 {
875 float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio;
876
877 // Looping through in pixels
878 for (n = 0; n < total_contributors; n++)
879 {
880 float out_center_of_in; // Center of the current out pixel in the in pixel space
881 int out_first_pixel, out_last_pixel;
882 int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio);
883
884 stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in);
885
886 stbir__calculate_coefficients_downsample(filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0));
887 }
888
889 stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size);
890 }
891}
892
893static float* stbir__get_decode_buffer(stbir__info* stbir_info)
894{
895 // The 0 index of the decode buffer starts after the margin. This makes
896 // it okay to use negative indexes on the decode buffer.
897 return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels];
898}
899
900#define STBIR__DECODE(type, colorspace) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace))
901
902static void stbir__decode_scanline(stbir__info* stbir_info, int n)
903{
904 int c;
905 int channels = stbir_info->channels;
906 int alpha_channel = stbir_info->alpha_channel;
907 int type = stbir_info->type;
908 int colorspace = stbir_info->colorspace;
909 int input_w = stbir_info->input_w;
910 size_t input_stride_bytes = stbir_info->input_stride_bytes;
911 float* decode_buffer = stbir__get_decode_buffer(stbir_info);
912 stbir_edge edge_horizontal = stbir_info->edge_horizontal;
913 stbir_edge edge_vertical = stbir_info->edge_vertical;
914 size_t in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes;
915 const void* input_data = (char *) stbir_info->input_data + in_buffer_row_offset;
916 int max_x = input_w + stbir_info->horizontal_filter_pixel_margin;
917 int decode = STBIR__DECODE(type, colorspace);
918
919 int x = -stbir_info->horizontal_filter_pixel_margin;
920
921 // special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input,
922 // and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO
923 if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h))
924 {
925 for (; x < max_x; x++)
926 for (c = 0; c < channels; c++)
927 decode_buffer[x*channels + c] = 0;
928 return;
929 }
930
931 switch (decode)
932 {
933 case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR):
934 for (; x < max_x; x++)
935 {
936 int decode_pixel_index = x * channels;
937 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
938 for (c = 0; c < channels; c++)
939 decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float;
940 }
941 break;
942
943 case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB):
944 for (; x < max_x; x++)
945 {
946 int decode_pixel_index = x * channels;
947 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
948 for (c = 0; c < channels; c++)
949 decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]];
950
951 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
952 decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float;
953 }
954 break;
955
956 case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR):
957 for (; x < max_x; x++)
958 {
959 int decode_pixel_index = x * channels;
960 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
961 for (c = 0; c < channels; c++)
962 decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float;
963 }
964 break;
965
966 case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB):
967 for (; x < max_x; x++)
968 {
969 int decode_pixel_index = x * channels;
970 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
971 for (c = 0; c < channels; c++)
972 decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float);
973
974 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
975 decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float;
976 }
977 break;
978
979 case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR):
980 for (; x < max_x; x++)
981 {
982 int decode_pixel_index = x * channels;
983 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
984 for (c = 0; c < channels; c++)
985 decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float);
986 }
987 break;
988
989 case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB):
990 for (; x < max_x; x++)
991 {
992 int decode_pixel_index = x * channels;
993 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
994 for (c = 0; c < channels; c++)
995 decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float));
996
997 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
998 decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float);
999 }
1000 break;
1001
1002 case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR):
1003 for (; x < max_x; x++)
1004 {
1005 int decode_pixel_index = x * channels;
1006 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
1007 for (c = 0; c < channels; c++)
1008 decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c];
1009 }
1010 break;
1011
1012 case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB):
1013 for (; x < max_x; x++)
1014 {
1015 int decode_pixel_index = x * channels;
1016 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
1017 for (c = 0; c < channels; c++)
1018 decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]);
1019
1020 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1021 decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel];
1022 }
1023
1024 break;
1025
1026 default:
1027 STBIR_ASSERT(!"Unknown type/colorspace/channels combination.");
1028 break;
1029 }
1030
1031 if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED))
1032 {
1033 for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++)
1034 {
1035 int decode_pixel_index = x * channels;
1036
1037 // If the alpha value is 0 it will clobber the color values. Make sure it's not.
1038 float alpha = decode_buffer[decode_pixel_index + alpha_channel];
1039#ifndef STBIR_NO_ALPHA_EPSILON
1040 if (stbir_info->type != STBIR_TYPE_FLOAT) {
1041 alpha += STBIR_ALPHA_EPSILON;
1042 decode_buffer[decode_pixel_index + alpha_channel] = alpha;
1043 }
1044#endif
1045 for (c = 0; c < channels; c++)
1046 {
1047 if (c == alpha_channel)
1048 continue;
1049
1050 decode_buffer[decode_pixel_index + c] *= alpha;
1051 }
1052 }
1053 }
1054
1055 if (edge_horizontal == STBIR_EDGE_ZERO)
1056 {
1057 for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++)
1058 {
1059 for (c = 0; c < channels; c++)
1060 decode_buffer[x*channels + c] = 0;
1061 }
1062 for (x = input_w; x < max_x; x++)
1063 {
1064 for (c = 0; c < channels; c++)
1065 decode_buffer[x*channels + c] = 0;
1066 }
1067 }
1068}
1069
1070static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length)
1071{
1072 return &ring_buffer[index * ring_buffer_length];
1073}
1074
1075static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n)
1076{
1077 int ring_buffer_index;
1078 float* ring_buffer;
1079
1080 stbir_info->ring_buffer_last_scanline = n;
1081
1082 if (stbir_info->ring_buffer_begin_index < 0)
1083 {
1084 ring_buffer_index = stbir_info->ring_buffer_begin_index = 0;
1085 stbir_info->ring_buffer_first_scanline = n;
1086 }
1087 else
1088 {
1089 ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries;
1090 STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index);
1091 }
1092
1093 ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float));
1094 memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes);
1095
1096 return ring_buffer;
1097}
1098
1099
1100static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer)
1101{
1102 int x, k;
1103 int output_w = stbir_info->output_w;
1104 int channels = stbir_info->channels;
1105 float* decode_buffer = stbir__get_decode_buffer(stbir_info);
1106 stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors;
1107 float* horizontal_coefficients = stbir_info->horizontal_coefficients;
1108 int coefficient_width = stbir_info->horizontal_coefficient_width;
1109
1110 for (x = 0; x < output_w; x++)
1111 {
1112 int n0 = horizontal_contributors[x].n0;
1113 int n1 = horizontal_contributors[x].n1;
1114
1115 int out_pixel_index = x * channels;
1116 int coefficient_group = coefficient_width * x;
1117 int coefficient_counter = 0;
1118
1119 STBIR_ASSERT(n1 >= n0);
1120 STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin);
1121 STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin);
1122 STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
1123 STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
1124
1125 switch (channels) {
1126 case 1:
1127 for (k = n0; k <= n1; k++)
1128 {
1129 int in_pixel_index = k * 1;
1130 float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
1131 STBIR_ASSERT(coefficient != 0);
1132 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1133 }
1134 break;
1135 case 2:
1136 for (k = n0; k <= n1; k++)
1137 {
1138 int in_pixel_index = k * 2;
1139 float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
1140 STBIR_ASSERT(coefficient != 0);
1141 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1142 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1143 }
1144 break;
1145 case 3:
1146 for (k = n0; k <= n1; k++)
1147 {
1148 int in_pixel_index = k * 3;
1149 float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
1150 STBIR_ASSERT(coefficient != 0);
1151 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1152 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1153 output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
1154 }
1155 break;
1156 case 4:
1157 for (k = n0; k <= n1; k++)
1158 {
1159 int in_pixel_index = k * 4;
1160 float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
1161 STBIR_ASSERT(coefficient != 0);
1162 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1163 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1164 output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
1165 output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient;
1166 }
1167 break;
1168 default:
1169 for (k = n0; k <= n1; k++)
1170 {
1171 int in_pixel_index = k * channels;
1172 float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
1173 int c;
1174 STBIR_ASSERT(coefficient != 0);
1175 for (c = 0; c < channels; c++)
1176 output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
1177 }
1178 break;
1179 }
1180 }
1181}
1182
1183static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer)
1184{
1185 int x, k;
1186 int input_w = stbir_info->input_w;
1187 int channels = stbir_info->channels;
1188 float* decode_buffer = stbir__get_decode_buffer(stbir_info);
1189 stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors;
1190 float* horizontal_coefficients = stbir_info->horizontal_coefficients;
1191 int coefficient_width = stbir_info->horizontal_coefficient_width;
1192 int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin;
1193 int max_x = input_w + filter_pixel_margin * 2;
1194
1195 STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info));
1196
1197 switch (channels) {
1198 case 1:
1199 for (x = 0; x < max_x; x++)
1200 {
1201 int n0 = horizontal_contributors[x].n0;
1202 int n1 = horizontal_contributors[x].n1;
1203
1204 int in_x = x - filter_pixel_margin;
1205 int in_pixel_index = in_x * 1;
1206 int max_n = n1;
1207 int coefficient_group = coefficient_width * x;
1208
1209 for (k = n0; k <= max_n; k++)
1210 {
1211 int out_pixel_index = k * 1;
1212 float coefficient = horizontal_coefficients[coefficient_group + k - n0];
1213 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1214 }
1215 }
1216 break;
1217
1218 case 2:
1219 for (x = 0; x < max_x; x++)
1220 {
1221 int n0 = horizontal_contributors[x].n0;
1222 int n1 = horizontal_contributors[x].n1;
1223
1224 int in_x = x - filter_pixel_margin;
1225 int in_pixel_index = in_x * 2;
1226 int max_n = n1;
1227 int coefficient_group = coefficient_width * x;
1228
1229 for (k = n0; k <= max_n; k++)
1230 {
1231 int out_pixel_index = k * 2;
1232 float coefficient = horizontal_coefficients[coefficient_group + k - n0];
1233 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1234 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1235 }
1236 }
1237 break;
1238
1239 case 3:
1240 for (x = 0; x < max_x; x++)
1241 {
1242 int n0 = horizontal_contributors[x].n0;
1243 int n1 = horizontal_contributors[x].n1;
1244
1245 int in_x = x - filter_pixel_margin;
1246 int in_pixel_index = in_x * 3;
1247 int max_n = n1;
1248 int coefficient_group = coefficient_width * x;
1249
1250 for (k = n0; k <= max_n; k++)
1251 {
1252 int out_pixel_index = k * 3;
1253 float coefficient = horizontal_coefficients[coefficient_group + k - n0];
1254 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1255 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1256 output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
1257 }
1258 }
1259 break;
1260
1261 case 4:
1262 for (x = 0; x < max_x; x++)
1263 {
1264 int n0 = horizontal_contributors[x].n0;
1265 int n1 = horizontal_contributors[x].n1;
1266
1267 int in_x = x - filter_pixel_margin;
1268 int in_pixel_index = in_x * 4;
1269 int max_n = n1;
1270 int coefficient_group = coefficient_width * x;
1271
1272 for (k = n0; k <= max_n; k++)
1273 {
1274 int out_pixel_index = k * 4;
1275 float coefficient = horizontal_coefficients[coefficient_group + k - n0];
1276 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1277 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1278 output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
1279 output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient;
1280 }
1281 }
1282 break;
1283
1284 default:
1285 for (x = 0; x < max_x; x++)
1286 {
1287 int n0 = horizontal_contributors[x].n0;
1288 int n1 = horizontal_contributors[x].n1;
1289
1290 int in_x = x - filter_pixel_margin;
1291 int in_pixel_index = in_x * channels;
1292 int max_n = n1;
1293 int coefficient_group = coefficient_width * x;
1294
1295 for (k = n0; k <= max_n; k++)
1296 {
1297 int c;
1298 int out_pixel_index = k * channels;
1299 float coefficient = horizontal_coefficients[coefficient_group + k - n0];
1300 for (c = 0; c < channels; c++)
1301 output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
1302 }
1303 }
1304 break;
1305 }
1306}
1307
1308static void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n)
1309{
1310 // Decode the nth scanline from the source image into the decode buffer.
1311 stbir__decode_scanline(stbir_info, n);
1312
1313 // Now resample it into the ring buffer.
1314 if (stbir__use_width_upsampling(stbir_info))
1315 stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n));
1316 else
1317 stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n));
1318
1319 // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling.
1320}
1321
1322static void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n)
1323{
1324 // Decode the nth scanline from the source image into the decode buffer.
1325 stbir__decode_scanline(stbir_info, n);
1326
1327 memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float));
1328
1329 // Now resample it into the horizontal buffer.
1330 if (stbir__use_width_upsampling(stbir_info))
1331 stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer);
1332 else
1333 stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer);
1334
1335 // Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers.
1336}
1337
1338// Get the specified scan line from the ring buffer.
1339static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length)
1340{
1341 int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries;
1342 return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length);
1343}
1344
1345
1346static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode)
1347{
1348 int x;
1349 int n;
1350 int num_nonalpha;
1351 stbir_uint16 nonalpha[STBIR_MAX_CHANNELS];
1352
1353 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_PREMULTIPLIED))
1354 {
1355 for (x=0; x < num_pixels; ++x)
1356 {
1357 int pixel_index = x*channels;
1358
1359 float alpha = encode_buffer[pixel_index + alpha_channel];
1360 float reciprocal_alpha = alpha ? 1.0f / alpha : 0;
1361
1362 // unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb
1363 for (n = 0; n < channels; n++)
1364 if (n != alpha_channel)
1365 encode_buffer[pixel_index + n] *= reciprocal_alpha;
1366
1367 // We added in a small epsilon to prevent the color channel from being deleted with zero alpha.
1368 // Because we only add it for integer types, it will automatically be discarded on integer
1369 // conversion, so we don't need to subtract it back out (which would be problematic for
1370 // numeric precision reasons).
1371 }
1372 }
1373
1374 // build a table of all channels that need colorspace correction, so
1375 // we don't perform colorspace correction on channels that don't need it.
1376 for (x = 0, num_nonalpha = 0; x < channels; ++x)
1377 {
1378 if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE))
1379 {
1380 nonalpha[num_nonalpha++] = (stbir_uint16)x;
1381 }
1382 }
1383
1384 #define STBIR__ROUND_INT(f) ((int) ((f)+0.5))
1385 #define STBIR__ROUND_UINT(f) ((stbir_uint32) ((f)+0.5))
1386
1387 #ifdef STBIR__SATURATE_INT
1388 #define STBIR__ENCODE_LINEAR8(f) stbir__saturate8 (STBIR__ROUND_INT((f) * stbir__max_uint8_as_float ))
1389 #define STBIR__ENCODE_LINEAR16(f) stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float))
1390 #else
1391 #define STBIR__ENCODE_LINEAR8(f) (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float )
1392 #define STBIR__ENCODE_LINEAR16(f) (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float)
1393 #endif
1394
1395 switch (decode)
1396 {
1397 case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR):
1398 for (x=0; x < num_pixels; ++x)
1399 {
1400 int pixel_index = x*channels;
1401
1402 for (n = 0; n < channels; n++)
1403 {
1404 int index = pixel_index + n;
1405 ((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]);
1406 }
1407 }
1408 break;
1409
1410 case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB):
1411 for (x=0; x < num_pixels; ++x)
1412 {
1413 int pixel_index = x*channels;
1414
1415 for (n = 0; n < num_nonalpha; n++)
1416 {
1417 int index = pixel_index + nonalpha[n];
1418 ((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]);
1419 }
1420
1421 if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE))
1422 ((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]);
1423 }
1424 break;
1425
1426 case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR):
1427 for (x=0; x < num_pixels; ++x)
1428 {
1429 int pixel_index = x*channels;
1430
1431 for (n = 0; n < channels; n++)
1432 {
1433 int index = pixel_index + n;
1434 ((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]);
1435 }
1436 }
1437 break;
1438
1439 case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB):
1440 for (x=0; x < num_pixels; ++x)
1441 {
1442 int pixel_index = x*channels;
1443
1444 for (n = 0; n < num_nonalpha; n++)
1445 {
1446 int index = pixel_index + nonalpha[n];
1447 ((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float);
1448 }
1449
1450 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1451 ((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]);
1452 }
1453
1454 break;
1455
1456 case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR):
1457 for (x=0; x < num_pixels; ++x)
1458 {
1459 int pixel_index = x*channels;
1460
1461 for (n = 0; n < channels; n++)
1462 {
1463 int index = pixel_index + n;
1464 ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float);
1465 }
1466 }
1467 break;
1468
1469 case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB):
1470 for (x=0; x < num_pixels; ++x)
1471 {
1472 int pixel_index = x*channels;
1473
1474 for (n = 0; n < num_nonalpha; n++)
1475 {
1476 int index = pixel_index + nonalpha[n];
1477 ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * stbir__max_uint32_as_float);
1478 }
1479
1480 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1481 ((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * stbir__max_uint32_as_float);
1482 }
1483 break;
1484
1485 case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR):
1486 for (x=0; x < num_pixels; ++x)
1487 {
1488 int pixel_index = x*channels;
1489
1490 for (n = 0; n < channels; n++)
1491 {
1492 int index = pixel_index + n;
1493 ((float*)output_buffer)[index] = encode_buffer[index];
1494 }
1495 }
1496 break;
1497
1498 case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB):
1499 for (x=0; x < num_pixels; ++x)
1500 {
1501 int pixel_index = x*channels;
1502
1503 for (n = 0; n < num_nonalpha; n++)
1504 {
1505 int index = pixel_index + nonalpha[n];
1506 ((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]);
1507 }
1508
1509 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1510 ((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel];
1511 }
1512 break;
1513
1514 default:
1515 STBIR_ASSERT(!"Unknown type/colorspace/channels combination.");
1516 break;
1517 }
1518}
1519
1520static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n)
1521{
1522 int x, k;
1523 int output_w = stbir_info->output_w;
1524 stbir__contributors* vertical_contributors = stbir_info->vertical_contributors;
1525 float* vertical_coefficients = stbir_info->vertical_coefficients;
1526 int channels = stbir_info->channels;
1527 int alpha_channel = stbir_info->alpha_channel;
1528 int type = stbir_info->type;
1529 int colorspace = stbir_info->colorspace;
1530 int ring_buffer_entries = stbir_info->ring_buffer_num_entries;
1531 void* output_data = stbir_info->output_data;
1532 float* encode_buffer = stbir_info->encode_buffer;
1533 int decode = STBIR__DECODE(type, colorspace);
1534 int coefficient_width = stbir_info->vertical_coefficient_width;
1535 int coefficient_counter;
1536 int contributor = n;
1537
1538 float* ring_buffer = stbir_info->ring_buffer;
1539 int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index;
1540 int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline;
1541 int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float);
1542
1543 int n0,n1, output_row_start;
1544 int coefficient_group = coefficient_width * contributor;
1545
1546 n0 = vertical_contributors[contributor].n0;
1547 n1 = vertical_contributors[contributor].n1;
1548
1549 output_row_start = n * stbir_info->output_stride_bytes;
1550
1551 STBIR_ASSERT(stbir__use_height_upsampling(stbir_info));
1552
1553 memset(encode_buffer, 0, output_w * sizeof(float) * channels);
1554
1555 // I tried reblocking this for better cache usage of encode_buffer
1556 // (using x_outer, k, x_inner), but it lost speed. -- stb
1557
1558 coefficient_counter = 0;
1559 switch (channels) {
1560 case 1:
1561 for (k = n0; k <= n1; k++)
1562 {
1563 int coefficient_index = coefficient_counter++;
1564 float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
1565 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1566 for (x = 0; x < output_w; ++x)
1567 {
1568 int in_pixel_index = x * 1;
1569 encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
1570 }
1571 }
1572 break;
1573 case 2:
1574 for (k = n0; k <= n1; k++)
1575 {
1576 int coefficient_index = coefficient_counter++;
1577 float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
1578 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1579 for (x = 0; x < output_w; ++x)
1580 {
1581 int in_pixel_index = x * 2;
1582 encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
1583 encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
1584 }
1585 }
1586 break;
1587 case 3:
1588 for (k = n0; k <= n1; k++)
1589 {
1590 int coefficient_index = coefficient_counter++;
1591 float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
1592 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1593 for (x = 0; x < output_w; ++x)
1594 {
1595 int in_pixel_index = x * 3;
1596 encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
1597 encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
1598 encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient;
1599 }
1600 }
1601 break;
1602 case 4:
1603 for (k = n0; k <= n1; k++)
1604 {
1605 int coefficient_index = coefficient_counter++;
1606 float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
1607 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1608 for (x = 0; x < output_w; ++x)
1609 {
1610 int in_pixel_index = x * 4;
1611 encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
1612 encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
1613 encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient;
1614 encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient;
1615 }
1616 }
1617 break;
1618 default:
1619 for (k = n0; k <= n1; k++)
1620 {
1621 int coefficient_index = coefficient_counter++;
1622 float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
1623 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1624 for (x = 0; x < output_w; ++x)
1625 {
1626 int in_pixel_index = x * channels;
1627 int c;
1628 for (c = 0; c < channels; c++)
1629 encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient;
1630 }
1631 }
1632 break;
1633 }
1634 stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, encode_buffer, channels, alpha_channel, decode);
1635}
1636
1637static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n)
1638{
1639 int x, k;
1640 int output_w = stbir_info->output_w;
1641 stbir__contributors* vertical_contributors = stbir_info->vertical_contributors;
1642 float* vertical_coefficients = stbir_info->vertical_coefficients;
1643 int channels = stbir_info->channels;
1644 int ring_buffer_entries = stbir_info->ring_buffer_num_entries;
1645 float* horizontal_buffer = stbir_info->horizontal_buffer;
1646 int coefficient_width = stbir_info->vertical_coefficient_width;
1647 int contributor = n + stbir_info->vertical_filter_pixel_margin;
1648
1649 float* ring_buffer = stbir_info->ring_buffer;
1650 int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index;
1651 int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline;
1652 int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float);
1653 int n0,n1;
1654
1655 n0 = vertical_contributors[contributor].n0;
1656 n1 = vertical_contributors[contributor].n1;
1657
1658 STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info));
1659
1660 for (k = n0; k <= n1; k++)
1661 {
1662 int coefficient_index = k - n0;
1663 int coefficient_group = coefficient_width * contributor;
1664 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1665
1666 float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
1667
1668 switch (channels) {
1669 case 1:
1670 for (x = 0; x < output_w; x++)
1671 {
1672 int in_pixel_index = x * 1;
1673 ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
1674 }
1675 break;
1676 case 2:
1677 for (x = 0; x < output_w; x++)
1678 {
1679 int in_pixel_index = x * 2;
1680 ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
1681 ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
1682 }
1683 break;
1684 case 3:
1685 for (x = 0; x < output_w; x++)
1686 {
1687 int in_pixel_index = x * 3;
1688 ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
1689 ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
1690 ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient;
1691 }
1692 break;
1693 case 4:
1694 for (x = 0; x < output_w; x++)
1695 {
1696 int in_pixel_index = x * 4;
1697 ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
1698 ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
1699 ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient;
1700 ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient;
1701 }
1702 break;
1703 default:
1704 for (x = 0; x < output_w; x++)
1705 {
1706 int in_pixel_index = x * channels;
1707
1708 int c;
1709 for (c = 0; c < channels; c++)
1710 ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient;
1711 }
1712 break;
1713 }
1714 }
1715}
1716
1717static void stbir__buffer_loop_upsample(stbir__info* stbir_info)
1718{
1719 int y;
1720 float scale_ratio = stbir_info->vertical_scale;
1721 float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio;
1722
1723 STBIR_ASSERT(stbir__use_height_upsampling(stbir_info));
1724
1725 for (y = 0; y < stbir_info->output_h; y++)
1726 {
1727 float in_center_of_out = 0; // Center of the current out scanline in the in scanline space
1728 int in_first_scanline = 0, in_last_scanline = 0;
1729
1730 stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out);
1731
1732 STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries);
1733
1734 if (stbir_info->ring_buffer_begin_index >= 0)
1735 {
1736 // Get rid of whatever we don't need anymore.
1737 while (in_first_scanline > stbir_info->ring_buffer_first_scanline)
1738 {
1739 if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline)
1740 {
1741 // We just popped the last scanline off the ring buffer.
1742 // Reset it to the empty state.
1743 stbir_info->ring_buffer_begin_index = -1;
1744 stbir_info->ring_buffer_first_scanline = 0;
1745 stbir_info->ring_buffer_last_scanline = 0;
1746 break;
1747 }
1748 else
1749 {
1750 stbir_info->ring_buffer_first_scanline++;
1751 stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries;
1752 }
1753 }
1754 }
1755
1756 // Load in new ones.
1757 if (stbir_info->ring_buffer_begin_index < 0)
1758 stbir__decode_and_resample_upsample(stbir_info, in_first_scanline);
1759
1760 while (in_last_scanline > stbir_info->ring_buffer_last_scanline)
1761 stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1);
1762
1763 // Now all buffers should be ready to write a row of vertical sampling.
1764 stbir__resample_vertical_upsample(stbir_info, y);
1765
1766 STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h);
1767 }
1768}
1769
1770static void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline)
1771{
1772 int output_stride_bytes = stbir_info->output_stride_bytes;
1773 int channels = stbir_info->channels;
1774 int alpha_channel = stbir_info->alpha_channel;
1775 int type = stbir_info->type;
1776 int colorspace = stbir_info->colorspace;
1777 int output_w = stbir_info->output_w;
1778 void* output_data = stbir_info->output_data;
1779 int decode = STBIR__DECODE(type, colorspace);
1780
1781 float* ring_buffer = stbir_info->ring_buffer;
1782 int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float);
1783
1784 if (stbir_info->ring_buffer_begin_index >= 0)
1785 {
1786 // Get rid of whatever we don't need anymore.
1787 while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline)
1788 {
1789 if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h)
1790 {
1791 int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes;
1792 float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length);
1793 stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode);
1794 STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h);
1795 }
1796
1797 if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline)
1798 {
1799 // We just popped the last scanline off the ring buffer.
1800 // Reset it to the empty state.
1801 stbir_info->ring_buffer_begin_index = -1;
1802 stbir_info->ring_buffer_first_scanline = 0;
1803 stbir_info->ring_buffer_last_scanline = 0;
1804 break;
1805 }
1806 else
1807 {
1808 stbir_info->ring_buffer_first_scanline++;
1809 stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries;
1810 }
1811 }
1812 }
1813}
1814
1815static void stbir__buffer_loop_downsample(stbir__info* stbir_info)
1816{
1817 int y;
1818 float scale_ratio = stbir_info->vertical_scale;
1819 int output_h = stbir_info->output_h;
1820 float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio;
1821 int pixel_margin = stbir_info->vertical_filter_pixel_margin;
1822 int max_y = stbir_info->input_h + pixel_margin;
1823
1824 STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info));
1825
1826 for (y = -pixel_margin; y < max_y; y++)
1827 {
1828 float out_center_of_in; // Center of the current out scanline in the in scanline space
1829 int out_first_scanline, out_last_scanline;
1830
1831 stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in);
1832
1833 STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries);
1834
1835 if (out_last_scanline < 0 || out_first_scanline >= output_h)
1836 continue;
1837
1838 stbir__empty_ring_buffer(stbir_info, out_first_scanline);
1839
1840 stbir__decode_and_resample_downsample(stbir_info, y);
1841
1842 // Load in new ones.
1843 if (stbir_info->ring_buffer_begin_index < 0)
1844 stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline);
1845
1846 while (out_last_scanline > stbir_info->ring_buffer_last_scanline)
1847 stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1);
1848
1849 // Now the horizontal buffer is ready to write to all ring buffer rows.
1850 stbir__resample_vertical_downsample(stbir_info, y);
1851 }
1852
1853 stbir__empty_ring_buffer(stbir_info, stbir_info->output_h);
1854}
1855
1856static void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels)
1857{
1858 info->input_w = input_w;
1859 info->input_h = input_h;
1860 info->output_w = output_w;
1861 info->output_h = output_h;
1862 info->channels = channels;
1863}
1864
1865static void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform)
1866{
1867 info->s0 = s0;
1868 info->t0 = t0;
1869 info->s1 = s1;
1870 info->t1 = t1;
1871
1872 if (transform)
1873 {
1874 info->horizontal_scale = transform[0];
1875 info->vertical_scale = transform[1];
1876 info->horizontal_shift = transform[2];
1877 info->vertical_shift = transform[3];
1878 }
1879 else
1880 {
1881 info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0);
1882 info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0);
1883
1884 info->horizontal_shift = s0 * info->output_w / (s1 - s0);
1885 info->vertical_shift = t0 * info->output_h / (t1 - t0);
1886 }
1887}
1888
1889static void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter)
1890{
1891 if (h_filter == 0)
1892 h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE;
1893 if (v_filter == 0)
1894 v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE;
1895 info->horizontal_filter = h_filter;
1896 info->vertical_filter = v_filter;
1897}
1898
1899static stbir_uint32 stbir__calculate_memory(stbir__info *info)
1900{
1901 int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale);
1902 int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale);
1903
1904 info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w);
1905 info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale , info->vertical_filter , info->input_h, info->output_h);
1906
1907 // One extra entry because floating point precision problems sometimes cause an extra to be necessary.
1908 info->ring_buffer_num_entries = filter_height + 1;
1909
1910 info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors);
1911 info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float);
1912 info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors);
1913 info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float);
1914 info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float);
1915 info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float);
1916 info->ring_buffer_size = info->output_w * info->channels * info->ring_buffer_num_entries * sizeof(float);
1917 info->encode_buffer_size = info->output_w * info->channels * sizeof(float);
1918
1919 STBIR_ASSERT(info->horizontal_filter != 0);
1920 STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late
1921 STBIR_ASSERT(info->vertical_filter != 0);
1922 STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late
1923
1924 if (stbir__use_height_upsampling(info))
1925 // The horizontal buffer is for when we're downsampling the height and we
1926 // can't output the result of sampling the decode buffer directly into the
1927 // ring buffers.
1928 info->horizontal_buffer_size = 0;
1929 else
1930 // The encode buffer is to retain precision in the height upsampling method
1931 // and isn't used when height downsampling.
1932 info->encode_buffer_size = 0;
1933
1934 return info->horizontal_contributors_size + info->horizontal_coefficients_size
1935 + info->vertical_contributors_size + info->vertical_coefficients_size
1936 + info->decode_buffer_size + info->horizontal_buffer_size
1937 + info->ring_buffer_size + info->encode_buffer_size;
1938}
1939
1940static int stbir__resize_allocated(stbir__info *info,
1941 const void* input_data, int input_stride_in_bytes,
1942 void* output_data, int output_stride_in_bytes,
1943 int alpha_channel, stbir_uint32 flags, stbir_datatype type,
1944 stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace,
1945 void* tempmem, size_t tempmem_size_in_bytes)
1946{
1947 size_t memory_required = stbir__calculate_memory(info);
1948
1949 int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type];
1950 int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type];
1951
1952#ifdef STBIR_DEBUG_OVERWRITE_TEST
1953#define OVERWRITE_ARRAY_SIZE 8
1954 unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE];
1955 unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE];
1956 unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE];
1957 unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE];
1958
1959 size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type];
1960 memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE);
1961 memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE);
1962 memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE);
1963 memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE);
1964#endif
1965
1966 STBIR_ASSERT(info->channels >= 0);
1967 STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS);
1968
1969 if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS)
1970 return 0;
1971
1972 STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));
1973 STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));
1974
1975 if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table))
1976 return 0;
1977 if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table))
1978 return 0;
1979
1980 if (alpha_channel < 0)
1981 flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED;
1982
1983 if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) {
1984 STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels);
1985 }
1986
1987 if (alpha_channel >= info->channels)
1988 return 0;
1989
1990 STBIR_ASSERT(tempmem);
1991
1992 if (!tempmem)
1993 return 0;
1994
1995 STBIR_ASSERT(tempmem_size_in_bytes >= memory_required);
1996
1997 if (tempmem_size_in_bytes < memory_required)
1998 return 0;
1999
2000 memset(tempmem, 0, tempmem_size_in_bytes);
2001
2002 info->input_data = input_data;
2003 info->input_stride_bytes = width_stride_input;
2004
2005 info->output_data = output_data;
2006 info->output_stride_bytes = width_stride_output;
2007
2008 info->alpha_channel = alpha_channel;
2009 info->flags = flags;
2010 info->type = type;
2011 info->edge_horizontal = edge_horizontal;
2012 info->edge_vertical = edge_vertical;
2013 info->colorspace = colorspace;
2014
2015 info->horizontal_coefficient_width = stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale);
2016 info->vertical_coefficient_width = stbir__get_coefficient_width (info->vertical_filter , info->vertical_scale );
2017 info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width (info->horizontal_filter, info->horizontal_scale);
2018 info->vertical_filter_pixel_width = stbir__get_filter_pixel_width (info->vertical_filter , info->vertical_scale );
2019 info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale);
2020 info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter , info->vertical_scale );
2021
2022 info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float);
2023 info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2;
2024
2025#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size)
2026
2027 info->horizontal_contributors = (stbir__contributors *) tempmem;
2028 info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float);
2029 info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors);
2030 info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float);
2031 info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float);
2032
2033 if (stbir__use_height_upsampling(info))
2034 {
2035 info->horizontal_buffer = NULL;
2036 info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
2037 info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float);
2038
2039 STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
2040 }
2041 else
2042 {
2043 info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
2044 info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float);
2045 info->encode_buffer = NULL;
2046
2047 STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
2048 }
2049
2050#undef STBIR__NEXT_MEMPTR
2051
2052 // This signals that the ring buffer is empty
2053 info->ring_buffer_begin_index = -1;
2054
2055 stbir__calculate_filters(info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w);
2056 stbir__calculate_filters(info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h);
2057
2058 STBIR_PROGRESS_REPORT(0);
2059
2060 if (stbir__use_height_upsampling(info))
2061 stbir__buffer_loop_upsample(info);
2062 else
2063 stbir__buffer_loop_downsample(info);
2064
2065 STBIR_PROGRESS_REPORT(1);
2066
2067#ifdef STBIR_DEBUG_OVERWRITE_TEST
2068 STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
2069 STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0);
2070 STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
2071 STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0);
2072#endif
2073
2074 return 1;
2075}
2076
2077
2078static int stbir__resize_arbitrary(
2079 void *alloc_context,
2080 const void* input_data, int input_w, int input_h, int input_stride_in_bytes,
2081 void* output_data, int output_w, int output_h, int output_stride_in_bytes,
2082 float s0, float t0, float s1, float t1, float *transform,
2083 int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type,
2084 stbir_filter h_filter, stbir_filter v_filter,
2085 stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace)
2086{
2087 stbir__info info;
2088 int result;
2089 size_t memory_required;
2090 void* extra_memory;
2091
2092 stbir__setup(&info, input_w, input_h, output_w, output_h, channels);
2093 stbir__calculate_transform(&info, s0,t0,s1,t1,transform);
2094 stbir__choose_filter(&info, h_filter, v_filter);
2095 memory_required = stbir__calculate_memory(&info);
2096 extra_memory = STBIR_MALLOC(memory_required, alloc_context);
2097
2098 if (!extra_memory)
2099 return 0;
2100
2101 result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes,
2102 output_data, output_stride_in_bytes,
2103 alpha_channel, flags, type,
2104 edge_horizontal, edge_vertical,
2105 colorspace, extra_memory, memory_required);
2106
2107 STBIR_FREE(extra_memory, alloc_context);
2108
2109 return result;
2110}
2111
2112STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2113 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2114 int num_channels)
2115{
2116 return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
2117 output_pixels, output_w, output_h, output_stride_in_bytes,
2118 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
2119 STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR);
2120}
2121
2122STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2123 float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2124 int num_channels)
2125{
2126 return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
2127 output_pixels, output_w, output_h, output_stride_in_bytes,
2128 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
2129 STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR);
2130}
2131
2132STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2133 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2134 int num_channels, int alpha_channel, int flags)
2135{
2136 return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
2137 output_pixels, output_w, output_h, output_stride_in_bytes,
2138 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
2139 STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB);
2140}
2141
2142STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2143 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2144 int num_channels, int alpha_channel, int flags,
2145 stbir_edge edge_wrap_mode)
2146{
2147 return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
2148 output_pixels, output_w, output_h, output_stride_in_bytes,
2149 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
2150 edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB);
2151}
2152
2153STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2154 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2155 int num_channels, int alpha_channel, int flags,
2156 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
2157 void *alloc_context)
2158{
2159 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2160 output_pixels, output_w, output_h, output_stride_in_bytes,
2161 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter,
2162 edge_wrap_mode, edge_wrap_mode, space);
2163}
2164
2165STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2166 stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
2167 int num_channels, int alpha_channel, int flags,
2168 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
2169 void *alloc_context)
2170{
2171 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2172 output_pixels, output_w, output_h, output_stride_in_bytes,
2173 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter,
2174 edge_wrap_mode, edge_wrap_mode, space);
2175}
2176
2177
2178STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2179 float *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
2180 int num_channels, int alpha_channel, int flags,
2181 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
2182 void *alloc_context)
2183{
2184 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2185 output_pixels, output_w, output_h, output_stride_in_bytes,
2186 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter,
2187 edge_wrap_mode, edge_wrap_mode, space);
2188}
2189
2190
2191STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2192 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2193 stbir_datatype datatype,
2194 int num_channels, int alpha_channel, int flags,
2195 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
2196 stbir_filter filter_horizontal, stbir_filter filter_vertical,
2197 stbir_colorspace space, void *alloc_context)
2198{
2199 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2200 output_pixels, output_w, output_h, output_stride_in_bytes,
2201 0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
2202 edge_mode_horizontal, edge_mode_vertical, space);
2203}
2204
2205
2206STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2207 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2208 stbir_datatype datatype,
2209 int num_channels, int alpha_channel, int flags,
2210 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
2211 stbir_filter filter_horizontal, stbir_filter filter_vertical,
2212 stbir_colorspace space, void *alloc_context,
2213 float x_scale, float y_scale,
2214 float x_offset, float y_offset)
2215{
2216 float transform[4];
2217 transform[0] = x_scale;
2218 transform[1] = y_scale;
2219 transform[2] = x_offset;
2220 transform[3] = y_offset;
2221 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2222 output_pixels, output_w, output_h, output_stride_in_bytes,
2223 0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
2224 edge_mode_horizontal, edge_mode_vertical, space);
2225}
2226
2227STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2228 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2229 stbir_datatype datatype,
2230 int num_channels, int alpha_channel, int flags,
2231 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
2232 stbir_filter filter_horizontal, stbir_filter filter_vertical,
2233 stbir_colorspace space, void *alloc_context,
2234 float s0, float t0, float s1, float t1)
2235{
2236 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2237 output_pixels, output_w, output_h, output_stride_in_bytes,
2238 s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
2239 edge_mode_horizontal, edge_mode_vertical, space);
2240}
2241
2242/*
2243------------------------------------------------------------------------------
2244This software is available under 2 licenses -- choose whichever you prefer.
2245------------------------------------------------------------------------------
2246ALTERNATIVE A - MIT License
2247Copyright (c) 2017 Sean Barrett
2248Permission is hereby granted, free of charge, to any person obtaining a copy of
2249this software and associated documentation files (the "Software"), to deal in
2250the Software without restriction, including without limitation the rights to
2251use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
2252of the Software, and to permit persons to whom the Software is furnished to do
2253so, subject to the following conditions:
2254The above copyright notice and this permission notice shall be included in all
2255copies or substantial portions of the Software.
2256THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2257IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2258FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2259AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2260LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2261OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2262SOFTWARE.
2263------------------------------------------------------------------------------
2264ALTERNATIVE B - Public Domain (www.unlicense.org)
2265This is free and unencumbered software released into the public domain.
2266Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
2267software, either in source code form or as a compiled binary, for any purpose,
2268commercial or non-commercial, and by any means.
2269In jurisdictions that recognize copyright laws, the author or authors of this
2270software dedicate any and all copyright interest in the software to the public
2271domain. We make this dedication for the benefit of the public at large and to
2272the detriment of our heirs and successors. We intend this dedication to be an
2273overt act of relinquishment in perpetuity of all present and future rights to
2274this software under copyright law.
2275THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2276IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2277FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2278AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
2279ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2280WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2281------------------------------------------------------------------------------
2282*/
diff --git a/externals/stb/stb_image_resize.h b/externals/stb/stb_image_resize.h
new file mode 100644
index 000000000..3107e0670
--- /dev/null
+++ b/externals/stb/stb_image_resize.h
@@ -0,0 +1,426 @@
1// SPDX-FileCopyrightText: Jorge L Rodriguez
2// SPDX-License-Identifier: MIT
3
4/* stb_image_resize - v0.97 - public domain image resizing
5 by Jorge L Rodriguez (@VinoBS) - 2014
6 http://github.com/nothings/stb
7
8 Written with emphasis on usability, portability, and efficiency. (No
9 SIMD or threads, so it be easily outperformed by libs that use those.)
10 Only scaling and translation is supported, no rotations or shears.
11 Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation.
12
13 COMPILING & LINKING
14 In one C/C++ file that #includes this file, do this:
15 #define STB_IMAGE_RESIZE_IMPLEMENTATION
16 before the #include. That will create the implementation in that file.
17
18 QUICKSTART
19 stbir_resize_uint8( input_pixels , in_w , in_h , 0,
20 output_pixels, out_w, out_h, 0, num_channels)
21 stbir_resize_float(...)
22 stbir_resize_uint8_srgb( input_pixels , in_w , in_h , 0,
23 output_pixels, out_w, out_h, 0,
24 num_channels , alpha_chan , 0)
25 stbir_resize_uint8_srgb_edgemode(
26 input_pixels , in_w , in_h , 0,
27 output_pixels, out_w, out_h, 0,
28 num_channels , alpha_chan , 0, STBIR_EDGE_CLAMP)
29 // WRAP/REFLECT/ZERO
30
31 FULL API
32 See the "header file" section of the source for API documentation.
33
34 ADDITIONAL DOCUMENTATION
35
36 SRGB & FLOATING POINT REPRESENTATION
37 The sRGB functions presume IEEE floating point. If you do not have
38 IEEE floating point, define STBIR_NON_IEEE_FLOAT. This will use
39 a slower implementation.
40
41 MEMORY ALLOCATION
42 The resize functions here perform a single memory allocation using
43 malloc. To control the memory allocation, before the #include that
44 triggers the implementation, do:
45
46 #define STBIR_MALLOC(size,context) ...
47 #define STBIR_FREE(ptr,context) ...
48
49 Each resize function makes exactly one call to malloc/free, so to use
50 temp memory, store the temp memory in the context and return that.
51
52 ASSERT
53 Define STBIR_ASSERT(boolval) to override assert() and not use assert.h
54
55 OPTIMIZATION
56 Define STBIR_SATURATE_INT to compute clamp values in-range using
57 integer operations instead of float operations. This may be faster
58 on some platforms.
59
60 DEFAULT FILTERS
61 For functions which don't provide explicit control over what filters
62 to use, you can change the compile-time defaults with
63
64 #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something
65 #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something
66
67 See stbir_filter in the header-file section for the list of filters.
68
69 NEW FILTERS
70 A number of 1D filter kernels are used. For a list of
71 supported filters see the stbir_filter enum. To add a new filter,
72 write a filter function and add it to stbir__filter_info_table.
73
74 PROGRESS
75 For interactive use with slow resize operations, you can install
76 a progress-report callback:
77
78 #define STBIR_PROGRESS_REPORT(val) some_func(val)
79
80 The parameter val is a float which goes from 0 to 1 as progress is made.
81
82 For example:
83
84 static void my_progress_report(float progress);
85 #define STBIR_PROGRESS_REPORT(val) my_progress_report(val)
86
87 #define STB_IMAGE_RESIZE_IMPLEMENTATION
88 #include "stb_image_resize.h"
89
90 static void my_progress_report(float progress)
91 {
92 printf("Progress: %f%%\n", progress*100);
93 }
94
95 MAX CHANNELS
96 If your image has more than 64 channels, define STBIR_MAX_CHANNELS
97 to the max you'll have.
98
99 ALPHA CHANNEL
100 Most of the resizing functions provide the ability to control how
101 the alpha channel of an image is processed. The important things
102 to know about this:
103
104 1. The best mathematically-behaved version of alpha to use is
105 called "premultiplied alpha", in which the other color channels
106 have had the alpha value multiplied in. If you use premultiplied
107 alpha, linear filtering (such as image resampling done by this
108 library, or performed in texture units on GPUs) does the "right
109 thing". While premultiplied alpha is standard in the movie CGI
110 industry, it is still uncommon in the videogame/real-time world.
111
112 If you linearly filter non-premultiplied alpha, strange effects
113 occur. (For example, the 50/50 average of 99% transparent bright green
114 and 1% transparent black produces 50% transparent dark green when
115 non-premultiplied, whereas premultiplied it produces 50%
116 transparent near-black. The former introduces green energy
117 that doesn't exist in the source image.)
118
119 2. Artists should not edit premultiplied-alpha images; artists
120 want non-premultiplied alpha images. Thus, art tools generally output
121 non-premultiplied alpha images.
122
123 3. You will get best results in most cases by converting images
124 to premultiplied alpha before processing them mathematically.
125
126 4. If you pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, the
127 resizer does not do anything special for the alpha channel;
128 it is resampled identically to other channels. This produces
129 the correct results for premultiplied-alpha images, but produces
130 less-than-ideal results for non-premultiplied-alpha images.
131
132 5. If you do not pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED,
133 then the resizer weights the contribution of input pixels
134 based on their alpha values, or, equivalently, it multiplies
135 the alpha value into the color channels, resamples, then divides
136 by the resultant alpha value. Input pixels which have alpha=0 do
137 not contribute at all to output pixels unless _all_ of the input
138 pixels affecting that output pixel have alpha=0, in which case
139 the result for that pixel is the same as it would be without
140 STBIR_FLAG_ALPHA_PREMULTIPLIED. However, this is only true for
141 input images in integer formats. For input images in float format,
142 input pixels with alpha=0 have no effect, and output pixels
143 which have alpha=0 will be 0 in all channels. (For float images,
144 you can manually achieve the same result by adding a tiny epsilon
145 value to the alpha channel of every image, and then subtracting
146 or clamping it at the end.)
147
148 6. You can suppress the behavior described in #5 and make
149 all-0-alpha pixels have 0 in all channels by #defining
150 STBIR_NO_ALPHA_EPSILON.
151
152 7. You can separately control whether the alpha channel is
153 interpreted as linear or affected by the colorspace. By default
154 it is linear; you almost never want to apply the colorspace.
155 (For example, graphics hardware does not apply sRGB conversion
156 to the alpha channel.)
157
158 CONTRIBUTORS
159 Jorge L Rodriguez: Implementation
160 Sean Barrett: API design, optimizations
161 Aras Pranckevicius: bugfix
162 Nathan Reed: warning fixes
163
164 REVISIONS
165 0.97 (2020-02-02) fixed warning
166 0.96 (2019-03-04) fixed warnings
167 0.95 (2017-07-23) fixed warnings
168 0.94 (2017-03-18) fixed warnings
169 0.93 (2017-03-03) fixed bug with certain combinations of heights
170 0.92 (2017-01-02) fix integer overflow on large (>2GB) images
171 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions
172 0.90 (2014-09-17) first released version
173
174 LICENSE
175 See end of file for license information.
176
177 TODO
178 Don't decode all of the image data when only processing a partial tile
179 Don't use full-width decode buffers when only processing a partial tile
180 When processing wide images, break processing into tiles so data fits in L1 cache
181 Installable filters?
182 Resize that respects alpha test coverage
183 (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage:
184 https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp )
185*/
186
187#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H
188#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H
189
190#ifdef _MSC_VER
191typedef unsigned char stbir_uint8;
192typedef unsigned short stbir_uint16;
193typedef unsigned int stbir_uint32;
194#else
195#include <stdint.h>
196typedef uint8_t stbir_uint8;
197typedef uint16_t stbir_uint16;
198typedef uint32_t stbir_uint32;
199#endif
200
201#ifndef STBIRDEF
202#ifdef STB_IMAGE_RESIZE_STATIC
203#define STBIRDEF static
204#else
205#ifdef __cplusplus
206#define STBIRDEF extern "C"
207#else
208#define STBIRDEF extern
209#endif
210#endif
211#endif
212
213//////////////////////////////////////////////////////////////////////////////
214//
215// Easy-to-use API:
216//
217// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4)
218// * input_w is input image width (x-axis), input_h is input image height (y-axis)
219// * stride is the offset between successive rows of image data in memory, in bytes. you can
220// specify 0 to mean packed continuously in memory
221// * alpha channel is treated identically to other channels.
222// * colorspace is linear or sRGB as specified by function name
223// * returned result is 1 for success or 0 in case of an error.
224// #define STBIR_ASSERT() to trigger an assert on parameter validation errors.
225// * Memory required grows approximately linearly with input and output size, but with
226// discontinuities at input_w == output_w and input_h == output_h.
227// * These functions use a "default" resampling filter defined at compile time. To change the filter,
228// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE
229// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API.
230
231STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
232 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
233 int num_channels);
234
235STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
236 float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
237 int num_channels);
238
239
240// The following functions interpret image data as gamma-corrected sRGB.
241// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel,
242// or otherwise provide the index of the alpha channel. Flags value
243// of 0 will probably do the right thing if you're not sure what
244// the flags mean.
245
246#define STBIR_ALPHA_CHANNEL_NONE -1
247
248// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will
249// use alpha-weighted resampling (effectively premultiplying, resampling,
250// then unpremultiplying).
251#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0)
252// The specified alpha channel should be handled as gamma-corrected value even
253// when doing sRGB operations.
254#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1)
255
256STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
257 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
258 int num_channels, int alpha_channel, int flags);
259
260
261typedef enum
262{
263 STBIR_EDGE_CLAMP = 1,
264 STBIR_EDGE_REFLECT = 2,
265 STBIR_EDGE_WRAP = 3,
266 STBIR_EDGE_ZERO = 4,
267} stbir_edge;
268
269// This function adds the ability to specify how requests to sample off the edge of the image are handled.
270STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
271 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
272 int num_channels, int alpha_channel, int flags,
273 stbir_edge edge_wrap_mode);
274
275//////////////////////////////////////////////////////////////////////////////
276//
277// Medium-complexity API
278//
279// This extends the easy-to-use API as follows:
280//
281// * Alpha-channel can be processed separately
282// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE
283// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT)
284// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)
285// * Filter can be selected explicitly
286// * uint16 image type
287// * sRGB colorspace available for all types
288// * context parameter for passing to STBIR_MALLOC
289
290typedef enum
291{
292 STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses
293 STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios
294 STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering
295 STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque
296 STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline
297 STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3
298} stbir_filter;
299
300typedef enum
301{
302 STBIR_COLORSPACE_LINEAR,
303 STBIR_COLORSPACE_SRGB,
304
305 STBIR_MAX_COLORSPACES,
306} stbir_colorspace;
307
308// The following functions are all identical except for the type of the image data
309
310STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
311 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
312 int num_channels, int alpha_channel, int flags,
313 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
314 void *alloc_context);
315
316STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
317 stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
318 int num_channels, int alpha_channel, int flags,
319 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
320 void *alloc_context);
321
322STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
323 float *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
324 int num_channels, int alpha_channel, int flags,
325 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
326 void *alloc_context);
327
328
329
330//////////////////////////////////////////////////////////////////////////////
331//
332// Full-complexity API
333//
334// This extends the medium API as follows:
335//
336// * uint32 image type
337// * not typesafe
338// * separate filter types for each axis
339// * separate edge modes for each axis
340// * can specify scale explicitly for subpixel correctness
341// * can specify image source tile using texture coordinates
342
343typedef enum
344{
345 STBIR_TYPE_UINT8 ,
346 STBIR_TYPE_UINT16,
347 STBIR_TYPE_UINT32,
348 STBIR_TYPE_FLOAT ,
349
350 STBIR_MAX_TYPES
351} stbir_datatype;
352
353STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
354 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
355 stbir_datatype datatype,
356 int num_channels, int alpha_channel, int flags,
357 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
358 stbir_filter filter_horizontal, stbir_filter filter_vertical,
359 stbir_colorspace space, void *alloc_context);
360
361STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
362 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
363 stbir_datatype datatype,
364 int num_channels, int alpha_channel, int flags,
365 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
366 stbir_filter filter_horizontal, stbir_filter filter_vertical,
367 stbir_colorspace space, void *alloc_context,
368 float x_scale, float y_scale,
369 float x_offset, float y_offset);
370
371STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
372 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
373 stbir_datatype datatype,
374 int num_channels, int alpha_channel, int flags,
375 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
376 stbir_filter filter_horizontal, stbir_filter filter_vertical,
377 stbir_colorspace space, void *alloc_context,
378 float s0, float t0, float s1, float t1);
379// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use.
380
381//
382//
383//// end header file /////////////////////////////////////////////////////
384#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H
385
386/*
387------------------------------------------------------------------------------
388This software is available under 2 licenses -- choose whichever you prefer.
389------------------------------------------------------------------------------
390ALTERNATIVE A - MIT License
391Copyright (c) 2017 Sean Barrett
392Permission is hereby granted, free of charge, to any person obtaining a copy of
393this software and associated documentation files (the "Software"), to deal in
394the Software without restriction, including without limitation the rights to
395use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
396of the Software, and to permit persons to whom the Software is furnished to do
397so, subject to the following conditions:
398The above copyright notice and this permission notice shall be included in all
399copies or substantial portions of the Software.
400THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
401IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
402FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
403AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
404LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
405OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
406SOFTWARE.
407------------------------------------------------------------------------------
408ALTERNATIVE B - Public Domain (www.unlicense.org)
409This is free and unencumbered software released into the public domain.
410Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
411software, either in source code form or as a compiled binary, for any purpose,
412commercial or non-commercial, and by any means.
413In jurisdictions that recognize copyright laws, the author or authors of this
414software dedicate any and all copyright interest in the software to the public
415domain. We make this dedication for the benefit of the public at large and to
416the detriment of our heirs and successors. We intend this dedication to be an
417overt act of relinquishment in perpetuity of all present and future rights to
418this software under copyright law.
419THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
420IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
421FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
422AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
423ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
424WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
425------------------------------------------------------------------------------
426*/
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 84a3308b7..ac43d84b7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -27,7 +27,7 @@ android {
27 namespace = "org.yuzu.yuzu_emu" 27 namespace = "org.yuzu.yuzu_emu"
28 28
29 compileSdkVersion = "android-34" 29 compileSdkVersion = "android-34"
30 ndkVersion = "25.2.9519653" 30 ndkVersion = "26.1.10909125"
31 31
32 buildFeatures { 32 buildFeatures {
33 viewBinding = true 33 viewBinding = true
@@ -203,23 +203,23 @@ ktlint {
203} 203}
204 204
205dependencies { 205dependencies {
206 implementation("androidx.core:core-ktx:1.10.1") 206 implementation("androidx.core:core-ktx:1.12.0")
207 implementation("androidx.appcompat:appcompat:1.6.1") 207 implementation("androidx.appcompat:appcompat:1.6.1")
208 implementation("androidx.recyclerview:recyclerview:1.3.0") 208 implementation("androidx.recyclerview:recyclerview:1.3.1")
209 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 209 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
210 implementation("androidx.fragment:fragment-ktx:1.6.0") 210 implementation("androidx.fragment:fragment-ktx:1.6.1")
211 implementation("androidx.documentfile:documentfile:1.0.1") 211 implementation("androidx.documentfile:documentfile:1.0.1")
212 implementation("com.google.android.material:material:1.9.0") 212 implementation("com.google.android.material:material:1.9.0")
213 implementation("androidx.preference:preference:1.2.0") 213 implementation("androidx.preference:preference-ktx:1.2.1")
214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") 214 implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
215 implementation("io.coil-kt:coil:2.2.2") 215 implementation("io.coil-kt:coil:2.2.2")
216 implementation("androidx.core:core-splashscreen:1.0.1") 216 implementation("androidx.core:core-splashscreen:1.0.1")
217 implementation("androidx.window:window:1.2.0-beta03") 217 implementation("androidx.window:window:1.2.0-beta03")
218 implementation("org.ini4j:ini4j:0.5.4") 218 implementation("org.ini4j:ini4j:0.5.4")
219 implementation("androidx.constraintlayout:constraintlayout:2.1.4") 219 implementation("androidx.constraintlayout:constraintlayout:2.1.4")
220 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") 220 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
221 implementation("androidx.navigation:navigation-fragment-ktx:2.6.0") 221 implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
222 implementation("androidx.navigation:navigation-ui-ktx:2.6.0") 222 implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
223 implementation("info.debatty:java-string-similarity:2.0.0") 223 implementation("info.debatty:java-string-similarity:2.0.0")
224 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") 224 implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
225} 225}
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 832c08e15..a67351727 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -28,7 +28,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
28 android:appCategory="game" 28 android:appCategory="game"
29 android:localeConfig="@xml/locales_config" 29 android:localeConfig="@xml/locales_config"
30 android:banner="@drawable/tv_banner" 30 android:banner="@drawable/tv_banner"
31 android:extractNativeLibs="true"
32 android:fullBackupContent="@xml/data_extraction_rules" 31 android:fullBackupContent="@xml/data_extraction_rules"
33 android:dataExtractionRules="@xml/data_extraction_rules_api_31" 32 android:dataExtractionRules="@xml/data_extraction_rules_api_31"
34 android:enableOnBackInvokedCallback="true"> 33 android:enableOnBackInvokedCallback="true">
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 6e39e542b..115f72710 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -15,13 +15,9 @@ import androidx.annotation.Keep
15import androidx.fragment.app.DialogFragment 15import androidx.fragment.app.DialogFragment
16import com.google.android.material.dialog.MaterialAlertDialogBuilder 16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import java.lang.ref.WeakReference 17import java.lang.ref.WeakReference
18import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
19import org.yuzu.yuzu_emu.activities.EmulationActivity 18import org.yuzu.yuzu_emu.activities.EmulationActivity
20import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath 19import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
21import org.yuzu.yuzu_emu.utils.FileUtil.exists 20import org.yuzu.yuzu_emu.utils.FileUtil
22import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
23import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
24import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
25import org.yuzu.yuzu_emu.utils.Log 21import org.yuzu.yuzu_emu.utils.Log
26import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable 22import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
27 23
@@ -75,7 +71,7 @@ object NativeLibrary {
75 return if (isNativePath(path!!)) { 71 return if (isNativePath(path!!)) {
76 YuzuApplication.documentsTree!!.openContentUri(path, openmode) 72 YuzuApplication.documentsTree!!.openContentUri(path, openmode)
77 } else { 73 } else {
78 openContentUri(appContext, path, openmode) 74 FileUtil.openContentUri(path, openmode)
79 } 75 }
80 } 76 }
81 77
@@ -85,7 +81,7 @@ object NativeLibrary {
85 return if (isNativePath(path!!)) { 81 return if (isNativePath(path!!)) {
86 YuzuApplication.documentsTree!!.getFileSize(path) 82 YuzuApplication.documentsTree!!.getFileSize(path)
87 } else { 83 } else {
88 getFileSize(appContext, path) 84 FileUtil.getFileSize(path)
89 } 85 }
90 } 86 }
91 87
@@ -95,7 +91,7 @@ object NativeLibrary {
95 return if (isNativePath(path!!)) { 91 return if (isNativePath(path!!)) {
96 YuzuApplication.documentsTree!!.exists(path) 92 YuzuApplication.documentsTree!!.exists(path)
97 } else { 93 } else {
98 exists(appContext, path) 94 FileUtil.exists(path)
99 } 95 }
100 } 96 }
101 97
@@ -105,7 +101,7 @@ object NativeLibrary {
105 return if (isNativePath(path!!)) { 101 return if (isNativePath(path!!)) {
106 YuzuApplication.documentsTree!!.isDirectory(path) 102 YuzuApplication.documentsTree!!.isDirectory(path)
107 } else { 103 } else {
108 isDirectory(appContext, path) 104 FileUtil.isDirectory(path)
109 } 105 }
110 } 106 }
111 107
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index 9561748cb..8c053670c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -47,7 +47,7 @@ class YuzuApplication : Application() {
47 application = this 47 application = this
48 documentsTree = DocumentsTree() 48 documentsTree = DocumentsTree()
49 DirectoryInitialization.start() 49 DirectoryInitialization.start()
50 GpuDriverHelper.initializeDriverParameters(applicationContext) 50 GpuDriverHelper.initializeDriverParameters()
51 NativeLibrary.logDeviceInfo() 51 NativeLibrary.logDeviceInfo()
52 52
53 createNotificationChannels() 53 createNotificationChannels()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
new file mode 100644
index 000000000..0e818cab9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
@@ -0,0 +1,117 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.adapters
5
6import android.text.TextUtils
7import android.view.LayoutInflater
8import android.view.View
9import android.view.ViewGroup
10import androidx.recyclerview.widget.AsyncDifferConfig
11import androidx.recyclerview.widget.DiffUtil
12import androidx.recyclerview.widget.ListAdapter
13import androidx.recyclerview.widget.RecyclerView
14import org.yuzu.yuzu_emu.R
15import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
16import org.yuzu.yuzu_emu.model.DriverViewModel
17import org.yuzu.yuzu_emu.utils.GpuDriverHelper
18import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
19
20class DriverAdapter(private val driverViewModel: DriverViewModel) :
21 ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
22 AsyncDifferConfig.Builder(DiffCallback()).build()
23 ) {
24 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
25 val binding =
26 CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
27 return DriverViewHolder(binding)
28 }
29
30 override fun getItemCount(): Int = currentList.size
31
32 override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
33 holder.bind(currentList[position])
34
35 private fun onSelectDriver(position: Int) {
36 driverViewModel.setSelectedDriverIndex(position)
37 notifyItemChanged(driverViewModel.previouslySelectedDriver)
38 notifyItemChanged(driverViewModel.selectedDriver)
39 }
40
41 private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) {
42 if (driverViewModel.selectedDriver > position) {
43 driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
44 }
45 if (GpuDriverHelper.customDriverData == driverData.second) {
46 driverViewModel.setSelectedDriverIndex(0)
47 }
48 driverViewModel.driversToDelete.add(driverData.first)
49 driverViewModel.removeDriver(driverData)
50 notifyItemRemoved(position)
51 notifyItemChanged(driverViewModel.selectedDriver)
52 }
53
54 inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
55 RecyclerView.ViewHolder(binding.root) {
56 private lateinit var driverData: Pair<String, GpuDriverMetadata>
57
58 fun bind(driverData: Pair<String, GpuDriverMetadata>) {
59 this.driverData = driverData
60 val driver = driverData.second
61
62 binding.apply {
63 radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
64 root.setOnClickListener {
65 onSelectDriver(bindingAdapterPosition)
66 }
67 buttonDelete.setOnClickListener {
68 onDeleteDriver(driverData, bindingAdapterPosition)
69 }
70
71 // Delay marquee by 3s
72 title.postDelayed(
73 {
74 title.isSelected = true
75 title.ellipsize = TextUtils.TruncateAt.MARQUEE
76 version.isSelected = true
77 version.ellipsize = TextUtils.TruncateAt.MARQUEE
78 description.isSelected = true
79 description.ellipsize = TextUtils.TruncateAt.MARQUEE
80 },
81 3000
82 )
83 if (driver.name == null) {
84 title.setText(R.string.system_gpu_driver)
85 description.text = ""
86 version.text = ""
87 version.visibility = View.GONE
88 description.visibility = View.GONE
89 buttonDelete.visibility = View.GONE
90 } else {
91 title.text = driver.name
92 version.text = driver.version
93 description.text = driver.description
94 version.visibility = View.VISIBLE
95 description.visibility = View.VISIBLE
96 buttonDelete.visibility = View.VISIBLE
97 }
98 }
99 }
100 }
101
102 private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() {
103 override fun areItemsTheSame(
104 oldItem: Pair<String, GpuDriverMetadata>,
105 newItem: Pair<String, GpuDriverMetadata>
106 ): Boolean {
107 return oldItem.first == newItem.first
108 }
109
110 override fun areContentsTheSame(
111 oldItem: Pair<String, GpuDriverMetadata>,
112 newItem: Pair<String, GpuDriverMetadata>
113 ): Boolean {
114 return oldItem.second == newItem.second
115 }
116 }
117}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
new file mode 100644
index 000000000..df21d74b2
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -0,0 +1,186 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.os.Bundle
7import android.view.LayoutInflater
8import android.view.View
9import android.view.ViewGroup
10import androidx.activity.result.contract.ActivityResultContracts
11import androidx.core.view.ViewCompat
12import androidx.core.view.WindowInsetsCompat
13import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment
15import androidx.fragment.app.activityViewModels
16import androidx.lifecycle.lifecycleScope
17import androidx.navigation.findNavController
18import androidx.recyclerview.widget.GridLayoutManager
19import com.google.android.material.transition.MaterialSharedAxis
20import kotlinx.coroutines.flow.collectLatest
21import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.adapters.DriverAdapter
24import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
25import org.yuzu.yuzu_emu.model.DriverViewModel
26import org.yuzu.yuzu_emu.model.HomeViewModel
27import org.yuzu.yuzu_emu.utils.FileUtil
28import org.yuzu.yuzu_emu.utils.GpuDriverHelper
29import java.io.File
30import java.io.IOException
31
32class DriverManagerFragment : Fragment() {
33 private var _binding: FragmentDriverManagerBinding? = null
34 private val binding get() = _binding!!
35
36 private val homeViewModel: HomeViewModel by activityViewModels()
37 private val driverViewModel: DriverViewModel by activityViewModels()
38
39 override fun onCreate(savedInstanceState: Bundle?) {
40 super.onCreate(savedInstanceState)
41 enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
42 returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
43 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
44 }
45
46 override fun onCreateView(
47 inflater: LayoutInflater,
48 container: ViewGroup?,
49 savedInstanceState: Bundle?
50 ): View {
51 _binding = FragmentDriverManagerBinding.inflate(inflater)
52 return binding.root
53 }
54
55 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
56 super.onViewCreated(view, savedInstanceState)
57 homeViewModel.setNavigationVisibility(visible = false, animated = true)
58 homeViewModel.setStatusBarShadeVisibility(visible = false)
59
60 if (!driverViewModel.isInteractionAllowed) {
61 DriversLoadingDialogFragment().show(
62 childFragmentManager,
63 DriversLoadingDialogFragment.TAG
64 )
65 }
66
67 binding.toolbarDrivers.setNavigationOnClickListener {
68 binding.root.findNavController().popBackStack()
69 }
70
71 binding.buttonInstall.setOnClickListener {
72 getDriver.launch(arrayOf("application/zip"))
73 }
74
75 binding.listDrivers.apply {
76 layoutManager = GridLayoutManager(
77 requireContext(),
78 resources.getInteger(R.integer.grid_columns)
79 )
80 adapter = DriverAdapter(driverViewModel)
81 }
82
83 viewLifecycleOwner.lifecycleScope.apply {
84 launch {
85 driverViewModel.driverList.collectLatest {
86 (binding.listDrivers.adapter as DriverAdapter).submitList(it)
87 }
88 }
89 launch {
90 driverViewModel.newDriverInstalled.collect {
91 if (_binding != null && it) {
92 (binding.listDrivers.adapter as DriverAdapter).apply {
93 notifyItemChanged(driverViewModel.previouslySelectedDriver)
94 notifyItemChanged(driverViewModel.selectedDriver)
95 driverViewModel.setNewDriverInstalled(false)
96 }
97 }
98 }
99 }
100 }
101
102 setInsets()
103 }
104
105 // Start installing requested driver
106 override fun onStop() {
107 super.onStop()
108 driverViewModel.onCloseDriverManager()
109 }
110
111 private fun setInsets() =
112 ViewCompat.setOnApplyWindowInsetsListener(
113 binding.root
114 ) { _: View, windowInsets: WindowInsetsCompat ->
115 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
116 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
117
118 val leftInsets = barInsets.left + cutoutInsets.left
119 val rightInsets = barInsets.right + cutoutInsets.right
120
121 val mlpAppBar = binding.toolbarDrivers.layoutParams as ViewGroup.MarginLayoutParams
122 mlpAppBar.leftMargin = leftInsets
123 mlpAppBar.rightMargin = rightInsets
124 binding.toolbarDrivers.layoutParams = mlpAppBar
125
126 val mlplistDrivers = binding.listDrivers.layoutParams as ViewGroup.MarginLayoutParams
127 mlplistDrivers.leftMargin = leftInsets
128 mlplistDrivers.rightMargin = rightInsets
129 binding.listDrivers.layoutParams = mlplistDrivers
130
131 val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
132 val mlpFab =
133 binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams
134 mlpFab.leftMargin = leftInsets + fabSpacing
135 mlpFab.rightMargin = rightInsets + fabSpacing
136 mlpFab.bottomMargin = barInsets.bottom + fabSpacing
137 binding.buttonInstall.layoutParams = mlpFab
138
139 binding.listDrivers.updatePadding(
140 bottom = barInsets.bottom +
141 resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
142 )
143
144 windowInsets
145 }
146
147 private val getDriver =
148 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
149 if (result == null) {
150 return@registerForActivityResult
151 }
152
153 IndeterminateProgressDialogFragment.newInstance(
154 requireActivity(),
155 R.string.installing_driver,
156 false
157 ) {
158 val driverPath =
159 "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}"
160 val driverFile = File(driverPath)
161
162 // Ignore file exceptions when a user selects an invalid zip
163 try {
164 if (!GpuDriverHelper.copyDriverToInternalStorage(result)) {
165 throw IOException("Driver failed validation!")
166 }
167 } catch (_: IOException) {
168 if (driverFile.exists()) {
169 driverFile.delete()
170 }
171 return@newInstance getString(R.string.select_gpu_driver_error)
172 }
173
174 val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
175 val driverInList =
176 driverViewModel.driverList.value.firstOrNull { it.second == driverData }
177 if (driverInList != null) {
178 return@newInstance getString(R.string.driver_already_installed)
179 } else {
180 driverViewModel.addDriver(Pair(driverPath, driverData))
181 driverViewModel.setNewDriverInstalled(true)
182 }
183 return@newInstance Any()
184 }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
185 }
186}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
new file mode 100644
index 000000000..f8c34346a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
@@ -0,0 +1,75 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.os.Bundle
8import android.view.LayoutInflater
9import android.view.View
10import android.view.ViewGroup
11import androidx.fragment.app.DialogFragment
12import androidx.fragment.app.activityViewModels
13import androidx.lifecycle.Lifecycle
14import androidx.lifecycle.lifecycleScope
15import androidx.lifecycle.repeatOnLifecycle
16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import kotlinx.coroutines.launch
18import org.yuzu.yuzu_emu.R
19import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
20import org.yuzu.yuzu_emu.model.DriverViewModel
21
22class DriversLoadingDialogFragment : DialogFragment() {
23 private val driverViewModel: DriverViewModel by activityViewModels()
24
25 private lateinit var binding: DialogProgressBarBinding
26
27 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
28 binding = DialogProgressBarBinding.inflate(layoutInflater)
29 binding.progressBar.isIndeterminate = true
30
31 isCancelable = false
32
33 return MaterialAlertDialogBuilder(requireContext())
34 .setTitle(R.string.loading)
35 .setView(binding.root)
36 .create()
37 }
38
39 override fun onCreateView(
40 inflater: LayoutInflater,
41 container: ViewGroup?,
42 savedInstanceState: Bundle?
43 ): View = binding.root
44
45 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
46 super.onViewCreated(view, savedInstanceState)
47 viewLifecycleOwner.lifecycleScope.apply {
48 launch {
49 repeatOnLifecycle(Lifecycle.State.RESUMED) {
50 driverViewModel.areDriversLoading.collect { checkForDismiss() }
51 }
52 }
53 launch {
54 repeatOnLifecycle(Lifecycle.State.RESUMED) {
55 driverViewModel.isDriverReady.collect { checkForDismiss() }
56 }
57 }
58 launch {
59 repeatOnLifecycle(Lifecycle.State.RESUMED) {
60 driverViewModel.isDeletingDrivers.collect { checkForDismiss() }
61 }
62 }
63 }
64 }
65
66 private fun checkForDismiss() {
67 if (driverViewModel.isInteractionAllowed) {
68 dismiss()
69 }
70 }
71
72 companion object {
73 const val TAG = "DriversLoadingDialogFragment"
74 }
75}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index e6ad2aa77..598a9d42b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
39import com.google.android.material.dialog.MaterialAlertDialogBuilder 39import com.google.android.material.dialog.MaterialAlertDialogBuilder
40import com.google.android.material.slider.Slider 40import com.google.android.material.slider.Slider
41import kotlinx.coroutines.Dispatchers 41import kotlinx.coroutines.Dispatchers
42import kotlinx.coroutines.flow.collect
42import kotlinx.coroutines.flow.collectLatest 43import kotlinx.coroutines.flow.collectLatest
43import kotlinx.coroutines.launch 44import kotlinx.coroutines.launch
44import org.yuzu.yuzu_emu.HomeNavigationDirections 45import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -50,6 +51,7 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
50import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding 51import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
51import org.yuzu.yuzu_emu.features.settings.model.IntSetting 52import org.yuzu.yuzu_emu.features.settings.model.IntSetting
52import org.yuzu.yuzu_emu.features.settings.model.Settings 53import org.yuzu.yuzu_emu.features.settings.model.Settings
54import org.yuzu.yuzu_emu.model.DriverViewModel
53import org.yuzu.yuzu_emu.model.Game 55import org.yuzu.yuzu_emu.model.Game
54import org.yuzu.yuzu_emu.model.EmulationViewModel 56import org.yuzu.yuzu_emu.model.EmulationViewModel
55import org.yuzu.yuzu_emu.overlay.InputOverlay 57import org.yuzu.yuzu_emu.overlay.InputOverlay
@@ -70,6 +72,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
70 private lateinit var game: Game 72 private lateinit var game: Game
71 73
72 private val emulationViewModel: EmulationViewModel by activityViewModels() 74 private val emulationViewModel: EmulationViewModel by activityViewModels()
75 private val driverViewModel: DriverViewModel by activityViewModels()
73 76
74 private var isInFoldableLayout = false 77 private var isInFoldableLayout = false
75 78
@@ -299,6 +302,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
299 } 302 }
300 } 303 }
301 } 304 }
305 launch {
306 repeatOnLifecycle(Lifecycle.State.RESUMED) {
307 driverViewModel.isDriverReady.collect {
308 if (it && !emulationState.isRunning) {
309 if (!DirectoryInitialization.areDirectoriesReady) {
310 DirectoryInitialization.start()
311 }
312
313 updateScreenLayout()
314
315 emulationState.run(emulationActivity!!.isActivityRecreated)
316 }
317 }
318 }
319 }
302 } 320 }
303 } 321 }
304 322
@@ -332,17 +350,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
332 } 350 }
333 } 351 }
334 352
335 override fun onResume() {
336 super.onResume()
337 if (!DirectoryInitialization.areDirectoriesReady) {
338 DirectoryInitialization.start()
339 }
340
341 updateScreenLayout()
342
343 emulationState.run(emulationActivity!!.isActivityRecreated)
344 }
345
346 override fun onPause() { 353 override fun onPause() {
347 if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) { 354 if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
348 emulationState.pause() 355 emulationState.pause()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index 8923c0ea2..fd9785075 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.fragments
5 5
6import android.Manifest 6import android.Manifest
7import android.content.ActivityNotFoundException 7import android.content.ActivityNotFoundException
8import android.content.DialogInterface
9import android.content.Intent 8import android.content.Intent
10import android.content.pm.PackageManager 9import android.content.pm.PackageManager
11import android.os.Bundle 10import android.os.Bundle
@@ -28,7 +27,6 @@ import androidx.fragment.app.activityViewModels
28import androidx.navigation.findNavController 27import androidx.navigation.findNavController
29import androidx.navigation.fragment.findNavController 28import androidx.navigation.fragment.findNavController
30import androidx.recyclerview.widget.LinearLayoutManager 29import androidx.recyclerview.widget.LinearLayoutManager
31import com.google.android.material.dialog.MaterialAlertDialogBuilder
32import com.google.android.material.transition.MaterialSharedAxis 30import com.google.android.material.transition.MaterialSharedAxis
33import org.yuzu.yuzu_emu.BuildConfig 31import org.yuzu.yuzu_emu.BuildConfig
34import org.yuzu.yuzu_emu.HomeNavigationDirections 32import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -37,6 +35,7 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding 35import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
38import org.yuzu.yuzu_emu.features.DocumentProvider 36import org.yuzu.yuzu_emu.features.DocumentProvider
39import org.yuzu.yuzu_emu.features.settings.model.Settings 37import org.yuzu.yuzu_emu.features.settings.model.Settings
38import org.yuzu.yuzu_emu.model.DriverViewModel
40import org.yuzu.yuzu_emu.model.HomeSetting 39import org.yuzu.yuzu_emu.model.HomeSetting
41import org.yuzu.yuzu_emu.model.HomeViewModel 40import org.yuzu.yuzu_emu.model.HomeViewModel
42import org.yuzu.yuzu_emu.ui.main.MainActivity 41import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -50,6 +49,7 @@ class HomeSettingsFragment : Fragment() {
50 private lateinit var mainActivity: MainActivity 49 private lateinit var mainActivity: MainActivity
51 50
52 private val homeViewModel: HomeViewModel by activityViewModels() 51 private val homeViewModel: HomeViewModel by activityViewModels()
52 private val driverViewModel: DriverViewModel by activityViewModels()
53 53
54 override fun onCreate(savedInstanceState: Bundle?) { 54 override fun onCreate(savedInstanceState: Bundle?) {
55 super.onCreate(savedInstanceState) 55 super.onCreate(savedInstanceState)
@@ -107,13 +107,17 @@ class HomeSettingsFragment : Fragment() {
107 ) 107 )
108 add( 108 add(
109 HomeSetting( 109 HomeSetting(
110 R.string.install_gpu_driver, 110 R.string.gpu_driver_manager,
111 R.string.install_gpu_driver_description, 111 R.string.install_gpu_driver_description,
112 R.drawable.ic_exit, 112 R.drawable.ic_build,
113 { driverInstaller() }, 113 {
114 binding.root.findNavController()
115 .navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment)
116 },
114 { GpuDriverHelper.supportsCustomDriverLoading() }, 117 { GpuDriverHelper.supportsCustomDriverLoading() },
115 R.string.custom_driver_not_supported, 118 R.string.custom_driver_not_supported,
116 R.string.custom_driver_not_supported_description 119 R.string.custom_driver_not_supported_description,
120 driverViewModel.selectedDriverMetadata
117 ) 121 )
118 ) 122 )
119 add( 123 add(
@@ -292,31 +296,6 @@ class HomeSettingsFragment : Fragment() {
292 } 296 }
293 } 297 }
294 298
295 private fun driverInstaller() {
296 // Get the driver name for the dialog message.
297 var driverName = GpuDriverHelper.customDriverName
298 if (driverName == null) {
299 driverName = getString(R.string.system_gpu_driver)
300 }
301
302 MaterialAlertDialogBuilder(requireContext())
303 .setTitle(getString(R.string.select_gpu_driver_title))
304 .setMessage(driverName)
305 .setNegativeButton(android.R.string.cancel, null)
306 .setNeutralButton(R.string.select_gpu_driver_default) { _: DialogInterface?, _: Int ->
307 GpuDriverHelper.installDefaultDriver(requireContext())
308 Toast.makeText(
309 requireContext(),
310 R.string.select_gpu_driver_use_default,
311 Toast.LENGTH_SHORT
312 ).show()
313 }
314 .setPositiveButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
315 mainActivity.getDriver.launch(arrayOf("application/zip"))
316 }
317 .show()
318 }
319
320 private fun shareLog() { 299 private fun shareLog() {
321 val file = DocumentFile.fromSingleUri( 300 val file = DocumentFile.fromSingleUri(
322 mainActivity, 301 mainActivity,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
index f128deda8..7e467814d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -10,8 +10,8 @@ import android.view.View
10import android.view.ViewGroup 10import android.view.ViewGroup
11import android.widget.Toast 11import android.widget.Toast
12import androidx.appcompat.app.AlertDialog 12import androidx.appcompat.app.AlertDialog
13import androidx.appcompat.app.AppCompatActivity
14import androidx.fragment.app.DialogFragment 13import androidx.fragment.app.DialogFragment
14import androidx.fragment.app.FragmentActivity
15import androidx.fragment.app.activityViewModels 15import androidx.fragment.app.activityViewModels
16import androidx.lifecycle.Lifecycle 16import androidx.lifecycle.Lifecycle
17import androidx.lifecycle.ViewModelProvider 17import androidx.lifecycle.ViewModelProvider
@@ -78,6 +78,10 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
78 requireActivity().supportFragmentManager, 78 requireActivity().supportFragmentManager,
79 MessageDialogFragment.TAG 79 MessageDialogFragment.TAG
80 ) 80 )
81
82 else -> {
83 // Do nothing
84 }
81 } 85 }
82 taskViewModel.clear() 86 taskViewModel.clear()
83 } 87 }
@@ -115,7 +119,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
115 private const val CANCELLABLE = "Cancellable" 119 private const val CANCELLABLE = "Cancellable"
116 120
117 fun newInstance( 121 fun newInstance(
118 activity: AppCompatActivity, 122 activity: FragmentActivity,
119 titleId: Int, 123 titleId: Int,
120 cancellable: Boolean = false, 124 cancellable: Boolean = false,
121 task: () -> Any 125 task: () -> Any
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
new file mode 100644
index 000000000..62945ad65
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
@@ -0,0 +1,158 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6import androidx.lifecycle.ViewModel
7import androidx.lifecycle.viewModelScope
8import kotlinx.coroutines.Dispatchers
9import kotlinx.coroutines.flow.MutableStateFlow
10import kotlinx.coroutines.flow.StateFlow
11import kotlinx.coroutines.launch
12import kotlinx.coroutines.withContext
13import org.yuzu.yuzu_emu.R
14import org.yuzu.yuzu_emu.YuzuApplication
15import org.yuzu.yuzu_emu.utils.FileUtil
16import org.yuzu.yuzu_emu.utils.GpuDriverHelper
17import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
18import java.io.BufferedOutputStream
19import java.io.File
20
21class DriverViewModel : ViewModel() {
22 private val _areDriversLoading = MutableStateFlow(false)
23 val areDriversLoading: StateFlow<Boolean> get() = _areDriversLoading
24
25 private val _isDriverReady = MutableStateFlow(true)
26 val isDriverReady: StateFlow<Boolean> get() = _isDriverReady
27
28 private val _isDeletingDrivers = MutableStateFlow(false)
29 val isDeletingDrivers: StateFlow<Boolean> get() = _isDeletingDrivers
30
31 private val _driverList = MutableStateFlow(mutableListOf<Pair<String, GpuDriverMetadata>>())
32 val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
33
34 var previouslySelectedDriver = 0
35 var selectedDriver = -1
36
37 private val _selectedDriverMetadata =
38 MutableStateFlow(
39 GpuDriverHelper.customDriverData.name
40 ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
41 )
42 val selectedDriverMetadata: StateFlow<String> get() = _selectedDriverMetadata
43
44 private val _newDriverInstalled = MutableStateFlow(false)
45 val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
46
47 val driversToDelete = mutableListOf<String>()
48
49 val isInteractionAllowed
50 get() = !areDriversLoading.value && isDriverReady.value && !isDeletingDrivers.value
51
52 init {
53 _areDriversLoading.value = true
54 viewModelScope.launch {
55 withContext(Dispatchers.IO) {
56 val drivers = GpuDriverHelper.getDrivers()
57 val currentDriverMetadata = GpuDriverHelper.customDriverData
58 for (i in drivers.indices) {
59 if (drivers[i].second == currentDriverMetadata) {
60 setSelectedDriverIndex(i)
61 break
62 }
63 }
64
65 // If a user had installed a driver before the manager was implemented, this zips
66 // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
67 // be indexed and exported as expected.
68 if (selectedDriver == -1) {
69 val driverToSave =
70 File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip")
71 driverToSave.createNewFile()
72 FileUtil.zipFromInternalStorage(
73 File(GpuDriverHelper.driverInstallationPath!!),
74 GpuDriverHelper.driverInstallationPath!!,
75 BufferedOutputStream(driverToSave.outputStream())
76 )
77 drivers.add(Pair(driverToSave.path, currentDriverMetadata))
78 setSelectedDriverIndex(drivers.size - 1)
79 }
80
81 _driverList.value = drivers
82 _areDriversLoading.value = false
83 }
84 }
85 }
86
87 fun setSelectedDriverIndex(value: Int) {
88 if (selectedDriver != -1) {
89 previouslySelectedDriver = selectedDriver
90 }
91 selectedDriver = value
92 }
93
94 fun setNewDriverInstalled(value: Boolean) {
95 _newDriverInstalled.value = value
96 }
97
98 fun addDriver(driverData: Pair<String, GpuDriverMetadata>) {
99 val driverIndex = _driverList.value.indexOfFirst { it == driverData }
100 if (driverIndex == -1) {
101 setSelectedDriverIndex(_driverList.value.size)
102 _driverList.value.add(driverData)
103 _selectedDriverMetadata.value = driverData.second.name
104 ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
105 } else {
106 setSelectedDriverIndex(driverIndex)
107 }
108 }
109
110 fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) {
111 _driverList.value.remove(driverData)
112 }
113
114 fun onCloseDriverManager() {
115 _isDeletingDrivers.value = true
116 viewModelScope.launch {
117 withContext(Dispatchers.IO) {
118 driversToDelete.forEach {
119 val driver = File(it)
120 if (driver.exists()) {
121 driver.delete()
122 }
123 }
124 driversToDelete.clear()
125 _isDeletingDrivers.value = false
126 }
127 }
128
129 if (GpuDriverHelper.customDriverData == driverList.value[selectedDriver].second) {
130 return
131 }
132
133 _isDriverReady.value = false
134 viewModelScope.launch {
135 withContext(Dispatchers.IO) {
136 if (selectedDriver == 0) {
137 GpuDriverHelper.installDefaultDriver()
138 setDriverReady()
139 return@withContext
140 }
141
142 val driverToInstall = File(driverList.value[selectedDriver].first)
143 if (driverToInstall.exists()) {
144 GpuDriverHelper.installCustomDriver(driverToInstall)
145 } else {
146 GpuDriverHelper.installDefaultDriver()
147 }
148 setDriverReady()
149 }
150 }
151 }
152
153 private fun setDriverReady() {
154 _isDriverReady.value = true
155 _selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name
156 ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
157 }
158}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 0fa5df5e5..233aa4101 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -29,12 +29,10 @@ import androidx.navigation.fragment.NavHostFragment
29import androidx.navigation.ui.setupWithNavController 29import androidx.navigation.ui.setupWithNavController
30import androidx.preference.PreferenceManager 30import androidx.preference.PreferenceManager
31import com.google.android.material.color.MaterialColors 31import com.google.android.material.color.MaterialColors
32import com.google.android.material.dialog.MaterialAlertDialogBuilder
33import com.google.android.material.navigation.NavigationBarView 32import com.google.android.material.navigation.NavigationBarView
34import kotlinx.coroutines.CoroutineScope 33import kotlinx.coroutines.CoroutineScope
35import java.io.File 34import java.io.File
36import java.io.FilenameFilter 35import java.io.FilenameFilter
37import java.io.IOException
38import kotlinx.coroutines.Dispatchers 36import kotlinx.coroutines.Dispatchers
39import kotlinx.coroutines.launch 37import kotlinx.coroutines.launch
40import kotlinx.coroutines.withContext 38import kotlinx.coroutines.withContext
@@ -43,7 +41,6 @@ import org.yuzu.yuzu_emu.NativeLibrary
43import org.yuzu.yuzu_emu.R 41import org.yuzu.yuzu_emu.R
44import org.yuzu.yuzu_emu.activities.EmulationActivity 42import org.yuzu.yuzu_emu.activities.EmulationActivity
45import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 43import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
46import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
47import org.yuzu.yuzu_emu.features.DocumentProvider 44import org.yuzu.yuzu_emu.features.DocumentProvider
48import org.yuzu.yuzu_emu.features.settings.model.Settings 45import org.yuzu.yuzu_emu.features.settings.model.Settings
49import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 46import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
@@ -343,11 +340,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
343 340
344 val dstPath = DirectoryInitialization.userDirectory + "/keys/" 341 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
345 if (FileUtil.copyUriToInternalStorage( 342 if (FileUtil.copyUriToInternalStorage(
346 applicationContext,
347 result, 343 result,
348 dstPath, 344 dstPath,
349 "prod.keys" 345 "prod.keys"
350 ) 346 ) != null
351 ) { 347 ) {
352 if (NativeLibrary.reloadKeys()) { 348 if (NativeLibrary.reloadKeys()) {
353 Toast.makeText( 349 Toast.makeText(
@@ -446,11 +442,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
446 442
447 val dstPath = DirectoryInitialization.userDirectory + "/keys/" 443 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
448 if (FileUtil.copyUriToInternalStorage( 444 if (FileUtil.copyUriToInternalStorage(
449 applicationContext,
450 result, 445 result,
451 dstPath, 446 dstPath,
452 "key_retail.bin" 447 "key_retail.bin"
453 ) 448 ) != null
454 ) { 449 ) {
455 if (NativeLibrary.reloadKeys()) { 450 if (NativeLibrary.reloadKeys()) {
456 Toast.makeText( 451 Toast.makeText(
@@ -469,59 +464,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
469 } 464 }
470 } 465 }
471 466
472 val getDriver =
473 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
474 if (result == null) {
475 return@registerForActivityResult
476 }
477
478 val takeFlags =
479 Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
480 contentResolver.takePersistableUriPermission(
481 result,
482 takeFlags
483 )
484
485 val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
486 progressBinding.progressBar.isIndeterminate = true
487 val installationDialog = MaterialAlertDialogBuilder(this)
488 .setTitle(R.string.installing_driver)
489 .setView(progressBinding.root)
490 .show()
491
492 lifecycleScope.launch {
493 withContext(Dispatchers.IO) {
494 // Ignore file exceptions when a user selects an invalid zip
495 try {
496 GpuDriverHelper.installCustomDriver(applicationContext, result)
497 } catch (_: IOException) {
498 }
499
500 withContext(Dispatchers.Main) {
501 installationDialog.dismiss()
502
503 val driverName = GpuDriverHelper.customDriverName
504 if (driverName != null) {
505 Toast.makeText(
506 applicationContext,
507 getString(
508 R.string.select_gpu_driver_install_success,
509 driverName
510 ),
511 Toast.LENGTH_SHORT
512 ).show()
513 } else {
514 Toast.makeText(
515 applicationContext,
516 R.string.select_gpu_driver_error,
517 Toast.LENGTH_LONG
518 ).show()
519 }
520 }
521 }
522 }
523 }
524
525 val installGameUpdate = registerForActivityResult( 467 val installGameUpdate = registerForActivityResult(
526 ActivityResultContracts.OpenMultipleDocuments() 468 ActivityResultContracts.OpenMultipleDocuments()
527 ) { documents: List<Uri> -> 469 ) { documents: List<Uri> ->
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
index cf226ad94..eafcf9e42 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
@@ -7,7 +7,6 @@ import android.net.Uri
7import androidx.documentfile.provider.DocumentFile 7import androidx.documentfile.provider.DocumentFile
8import java.io.File 8import java.io.File
9import java.util.* 9import java.util.*
10import org.yuzu.yuzu_emu.YuzuApplication
11import org.yuzu.yuzu_emu.model.MinimalDocumentFile 10import org.yuzu.yuzu_emu.model.MinimalDocumentFile
12 11
13class DocumentsTree { 12class DocumentsTree {
@@ -22,7 +21,7 @@ class DocumentsTree {
22 21
23 fun openContentUri(filepath: String, openMode: String?): Int { 22 fun openContentUri(filepath: String, openMode: String?): Int {
24 val node = resolvePath(filepath) ?: return -1 23 val node = resolvePath(filepath) ?: return -1
25 return FileUtil.openContentUri(YuzuApplication.appContext, node.uri.toString(), openMode) 24 return FileUtil.openContentUri(node.uri.toString(), openMode)
26 } 25 }
27 26
28 fun getFileSize(filepath: String): Long { 27 fun getFileSize(filepath: String): Long {
@@ -30,7 +29,7 @@ class DocumentsTree {
30 return if (node == null || node.isDirectory) { 29 return if (node == null || node.isDirectory) {
31 0 30 0
32 } else { 31 } else {
33 FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) 32 FileUtil.getFileSize(node.uri.toString())
34 } 33 }
35 } 34 }
36 35
@@ -67,7 +66,7 @@ class DocumentsTree {
67 * @param parent parent node of this level 66 * @param parent parent node of this level
68 */ 67 */
69 private fun structTree(parent: DocumentsNode) { 68 private fun structTree(parent: DocumentsNode) {
70 val documents = FileUtil.listFiles(YuzuApplication.appContext, parent.uri!!) 69 val documents = FileUtil.listFiles(parent.uri!!)
71 for (document in documents) { 70 for (document in documents) {
72 val node = DocumentsNode(document) 71 val node = DocumentsNode(document)
73 node.parent = parent 72 node.parent = parent
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index c3f53f1c5..5ee74a52c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -3,7 +3,6 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.content.Context
7import android.database.Cursor 6import android.database.Cursor
8import android.net.Uri 7import android.net.Uri
9import android.provider.DocumentsContract 8import android.provider.DocumentsContract
@@ -11,7 +10,6 @@ import androidx.documentfile.provider.DocumentFile
11import kotlinx.coroutines.flow.StateFlow 10import kotlinx.coroutines.flow.StateFlow
12import java.io.BufferedInputStream 11import java.io.BufferedInputStream
13import java.io.File 12import java.io.File
14import java.io.FileOutputStream
15import java.io.IOException 13import java.io.IOException
16import java.io.InputStream 14import java.io.InputStream
17import java.net.URLDecoder 15import java.net.URLDecoder
@@ -21,6 +19,8 @@ import org.yuzu.yuzu_emu.YuzuApplication
21import org.yuzu.yuzu_emu.model.MinimalDocumentFile 19import org.yuzu.yuzu_emu.model.MinimalDocumentFile
22import org.yuzu.yuzu_emu.model.TaskState 20import org.yuzu.yuzu_emu.model.TaskState
23import java.io.BufferedOutputStream 21import java.io.BufferedOutputStream
22import java.lang.NullPointerException
23import java.nio.charset.StandardCharsets
24import java.util.zip.ZipOutputStream 24import java.util.zip.ZipOutputStream
25 25
26object FileUtil { 26object FileUtil {
@@ -29,6 +29,8 @@ object FileUtil {
29 const val APPLICATION_OCTET_STREAM = "application/octet-stream" 29 const val APPLICATION_OCTET_STREAM = "application/octet-stream"
30 const val TEXT_PLAIN = "text/plain" 30 const val TEXT_PLAIN = "text/plain"
31 31
32 private val context get() = YuzuApplication.appContext
33
32 /** 34 /**
33 * Create a file from directory with filename. 35 * Create a file from directory with filename.
34 * @param context Application context 36 * @param context Application context
@@ -36,11 +38,11 @@ object FileUtil {
36 * @param filename file display name. 38 * @param filename file display name.
37 * @return boolean 39 * @return boolean
38 */ 40 */
39 fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? { 41 fun createFile(directory: String?, filename: String): DocumentFile? {
40 var decodedFilename = filename 42 var decodedFilename = filename
41 try { 43 try {
42 val directoryUri = Uri.parse(directory) 44 val directoryUri = Uri.parse(directory)
43 val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null 45 val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
44 decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD) 46 decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
45 var mimeType = APPLICATION_OCTET_STREAM 47 var mimeType = APPLICATION_OCTET_STREAM
46 if (decodedFilename.endsWith(".txt")) { 48 if (decodedFilename.endsWith(".txt")) {
@@ -56,16 +58,15 @@ object FileUtil {
56 58
57 /** 59 /**
58 * Create a directory from directory with filename. 60 * Create a directory from directory with filename.
59 * @param context Application context
60 * @param directory parent path for directory. 61 * @param directory parent path for directory.
61 * @param directoryName directory display name. 62 * @param directoryName directory display name.
62 * @return boolean 63 * @return boolean
63 */ 64 */
64 fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? { 65 fun createDir(directory: String?, directoryName: String?): DocumentFile? {
65 var decodedDirectoryName = directoryName 66 var decodedDirectoryName = directoryName
66 try { 67 try {
67 val directoryUri = Uri.parse(directory) 68 val directoryUri = Uri.parse(directory)
68 val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null 69 val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
69 decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD) 70 decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
70 val isExist = parent.findFile(decodedDirectoryName) 71 val isExist = parent.findFile(decodedDirectoryName)
71 return isExist ?: parent.createDirectory(decodedDirectoryName) 72 return isExist ?: parent.createDirectory(decodedDirectoryName)
@@ -77,13 +78,12 @@ object FileUtil {
77 78
78 /** 79 /**
79 * Open content uri and return file descriptor to JNI. 80 * Open content uri and return file descriptor to JNI.
80 * @param context Application context
81 * @param path Native content uri path 81 * @param path Native content uri path
82 * @param openMode will be one of "r", "r", "rw", "wa", "rwa" 82 * @param openMode will be one of "r", "r", "rw", "wa", "rwa"
83 * @return file descriptor 83 * @return file descriptor
84 */ 84 */
85 @JvmStatic 85 @JvmStatic
86 fun openContentUri(context: Context, path: String, openMode: String?): Int { 86 fun openContentUri(path: String, openMode: String?): Int {
87 try { 87 try {
88 val uri = Uri.parse(path) 88 val uri = Uri.parse(path)
89 val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!) 89 val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
@@ -103,11 +103,10 @@ object FileUtil {
103 /** 103 /**
104 * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow 104 * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
105 * This function will be faster than DoucmentFile.listFiles 105 * This function will be faster than DoucmentFile.listFiles
106 * @param context Application context
107 * @param uri Directory uri. 106 * @param uri Directory uri.
108 * @return CheapDocument lists. 107 * @return CheapDocument lists.
109 */ 108 */
110 fun listFiles(context: Context, uri: Uri): Array<MinimalDocumentFile> { 109 fun listFiles(uri: Uri): Array<MinimalDocumentFile> {
111 val resolver = context.contentResolver 110 val resolver = context.contentResolver
112 val columns = arrayOf( 111 val columns = arrayOf(
113 DocumentsContract.Document.COLUMN_DOCUMENT_ID, 112 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -145,7 +144,7 @@ object FileUtil {
145 * @param path Native content uri path 144 * @param path Native content uri path
146 * @return bool 145 * @return bool
147 */ 146 */
148 fun exists(context: Context, path: String?): Boolean { 147 fun exists(path: String?): Boolean {
149 var c: Cursor? = null 148 var c: Cursor? = null
150 try { 149 try {
151 val mUri = Uri.parse(path) 150 val mUri = Uri.parse(path)
@@ -165,7 +164,7 @@ object FileUtil {
165 * @param path content uri path 164 * @param path content uri path
166 * @return bool 165 * @return bool
167 */ 166 */
168 fun isDirectory(context: Context, path: String): Boolean { 167 fun isDirectory(path: String): Boolean {
169 val resolver = context.contentResolver 168 val resolver = context.contentResolver
170 val columns = arrayOf( 169 val columns = arrayOf(
171 DocumentsContract.Document.COLUMN_MIME_TYPE 170 DocumentsContract.Document.COLUMN_MIME_TYPE
@@ -210,10 +209,10 @@ object FileUtil {
210 return filename 209 return filename
211 } 210 }
212 211
213 fun getFilesName(context: Context, path: String): Array<String> { 212 fun getFilesName(path: String): Array<String> {
214 val uri = Uri.parse(path) 213 val uri = Uri.parse(path)
215 val files: MutableList<String> = ArrayList() 214 val files: MutableList<String> = ArrayList()
216 for (file in listFiles(context, uri)) { 215 for (file in listFiles(uri)) {
217 files.add(file.filename) 216 files.add(file.filename)
218 } 217 }
219 return files.toTypedArray() 218 return files.toTypedArray()
@@ -225,7 +224,7 @@ object FileUtil {
225 * @return long file size 224 * @return long file size
226 */ 225 */
227 @JvmStatic 226 @JvmStatic
228 fun getFileSize(context: Context, path: String): Long { 227 fun getFileSize(path: String): Long {
229 val resolver = context.contentResolver 228 val resolver = context.contentResolver
230 val columns = arrayOf( 229 val columns = arrayOf(
231 DocumentsContract.Document.COLUMN_SIZE 230 DocumentsContract.Document.COLUMN_SIZE
@@ -245,44 +244,38 @@ object FileUtil {
245 return size 244 return size
246 } 245 }
247 246
247 /**
248 * Creates an input stream with a given [Uri] and copies its data to the given path. This will
249 * overwrite any pre-existing files.
250 *
251 * @param sourceUri The [Uri] to copy data from
252 * @param destinationParentPath Destination directory
253 * @param destinationFilename Optionally renames the file once copied
254 */
248 fun copyUriToInternalStorage( 255 fun copyUriToInternalStorage(
249 context: Context, 256 sourceUri: Uri,
250 sourceUri: Uri?,
251 destinationParentPath: String, 257 destinationParentPath: String,
252 destinationFilename: String 258 destinationFilename: String = ""
253 ): Boolean { 259 ): File? =
254 var input: InputStream? = null
255 var output: FileOutputStream? = null
256 try { 260 try {
257 input = context.contentResolver.openInputStream(sourceUri!!) 261 val fileName =
258 output = FileOutputStream("$destinationParentPath/$destinationFilename") 262 if (destinationFilename == "") getFilename(sourceUri) else "/$destinationFilename"
259 val buffer = ByteArray(1024) 263 val inputStream = context.contentResolver.openInputStream(sourceUri)!!
260 var len: Int 264
261 while (input!!.read(buffer).also { len = it } != -1) { 265 val destinationFile = File("$destinationParentPath$fileName")
262 output.write(buffer, 0, len) 266 if (destinationFile.exists()) {
263 } 267 destinationFile.delete()
264 output.flush()
265 return true
266 } catch (e: Exception) {
267 Log.error("[FileUtil]: Cannot copy file, error: " + e.message)
268 } finally {
269 if (input != null) {
270 try {
271 input.close()
272 } catch (e: IOException) {
273 Log.error("[FileUtil]: Cannot close input file, error: " + e.message)
274 }
275 } 268 }
276 if (output != null) { 269
277 try { 270 destinationFile.outputStream().use { fos ->
278 output.close() 271 inputStream.use { it.copyTo(fos) }
279 } catch (e: IOException) {
280 Log.error("[FileUtil]: Cannot close output file, error: " + e.message)
281 }
282 } 272 }
273 destinationFile
274 } catch (e: IOException) {
275 null
276 } catch (e: NullPointerException) {
277 null
283 } 278 }
284 return false
285 }
286 279
287 /** 280 /**
288 * Extracts the given zip file into the given directory. 281 * Extracts the given zip file into the given directory.
@@ -368,4 +361,12 @@ object FileUtil {
368 return fileName.substring(fileName.lastIndexOf(".") + 1) 361 return fileName.substring(fileName.lastIndexOf(".") + 1)
369 .lowercase() 362 .lowercase()
370 } 363 }
364
365 @Throws(IOException::class)
366 fun getStringFromFile(file: File): String =
367 String(file.readBytes(), StandardCharsets.UTF_8)
368
369 @Throws(IOException::class)
370 fun getStringFromInputStream(stream: InputStream): String =
371 String(stream.readBytes(), StandardCharsets.UTF_8)
371} 372}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index e0ee29c9b..9001ca9ab 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -30,7 +30,7 @@ object GameHelper {
30 // Ensure keys are loaded so that ROM metadata can be decrypted. 30 // Ensure keys are loaded so that ROM metadata can be decrypted.
31 NativeLibrary.reloadKeys() 31 NativeLibrary.reloadKeys()
32 32
33 addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3) 33 addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
34 34
35 // Cache list of games found on disk 35 // Cache list of games found on disk
36 val serializedGames = mutableSetOf<String>() 36 val serializedGames = mutableSetOf<String>()
@@ -58,7 +58,7 @@ object GameHelper {
58 if (it.isDirectory) { 58 if (it.isDirectory) {
59 addGamesRecursive( 59 addGamesRecursive(
60 games, 60 games,
61 FileUtil.listFiles(YuzuApplication.appContext, it.uri), 61 FileUtil.listFiles(it.uri),
62 depth - 1 62 depth - 1
63 ) 63 )
64 } else { 64 } else {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
index 1d4695a2a..f6882ce6c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
@@ -3,64 +3,33 @@
3 3
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import android.content.Context
7import android.net.Uri 6import android.net.Uri
7import android.os.Build
8import java.io.BufferedInputStream 8import java.io.BufferedInputStream
9import java.io.File 9import java.io.File
10import java.io.FileInputStream
11import java.io.FileOutputStream
12import java.io.IOException 10import java.io.IOException
13import java.util.zip.ZipInputStream
14import org.yuzu.yuzu_emu.NativeLibrary 11import org.yuzu.yuzu_emu.NativeLibrary
15import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage 12import org.yuzu.yuzu_emu.YuzuApplication
13import java.util.zip.ZipException
14import java.util.zip.ZipFile
16 15
17object GpuDriverHelper { 16object GpuDriverHelper {
18 private const val META_JSON_FILENAME = "meta.json" 17 private const val META_JSON_FILENAME = "meta.json"
19 private const val DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"
20 private var fileRedirectionPath: String? = null 18 private var fileRedirectionPath: String? = null
21 private var driverInstallationPath: String? = null 19 var driverInstallationPath: String? = null
22 private var hookLibPath: String? = null 20 private var hookLibPath: String? = null
23 21
24 @Throws(IOException::class) 22 val driverStoragePath get() = DirectoryInitialization.userDirectory!! + "/gpu_drivers/"
25 private fun unzip(zipFilePath: String, destDir: String) {
26 val dir = File(destDir)
27
28 // Create output directory if it doesn't exist
29 if (!dir.exists()) dir.mkdirs()
30
31 // Unpack the files.
32 val inputStream = FileInputStream(zipFilePath)
33 val zis = ZipInputStream(BufferedInputStream(inputStream))
34 val buffer = ByteArray(1024)
35 var ze = zis.nextEntry
36 while (ze != null) {
37 val newFile = File(destDir, ze.name)
38 val canonicalPath = newFile.canonicalPath
39 if (!canonicalPath.startsWith(destDir + ze.name)) {
40 throw SecurityException("Zip file attempted path traversal! " + ze.name)
41 }
42
43 newFile.parentFile!!.mkdirs()
44 val fos = FileOutputStream(newFile)
45 var len: Int
46 while (zis.read(buffer).also { len = it } > 0) {
47 fos.write(buffer, 0, len)
48 }
49 fos.close()
50 zis.closeEntry()
51 ze = zis.nextEntry
52 }
53 zis.closeEntry()
54 }
55 23
56 fun initializeDriverParameters(context: Context) { 24 fun initializeDriverParameters() {
57 try { 25 try {
58 // Initialize the file redirection directory. 26 // Initialize the file redirection directory.
59 fileRedirectionPath = 27 fileRedirectionPath = YuzuApplication.appContext
60 context.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/" 28 .getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
61 29
62 // Initialize the driver installation directory. 30 // Initialize the driver installation directory.
63 driverInstallationPath = context.filesDir.canonicalPath + "/gpu_driver/" 31 driverInstallationPath = YuzuApplication.appContext
32 .filesDir.canonicalPath + "/gpu_driver/"
64 } catch (e: IOException) { 33 } catch (e: IOException) {
65 throw RuntimeException(e) 34 throw RuntimeException(e)
66 } 35 }
@@ -69,68 +38,169 @@ object GpuDriverHelper {
69 initializeDirectories() 38 initializeDirectories()
70 39
71 // Initialize hook libraries directory. 40 // Initialize hook libraries directory.
72 hookLibPath = context.applicationInfo.nativeLibraryDir + "/" 41 hookLibPath = YuzuApplication.appContext.applicationInfo.nativeLibraryDir + "/"
73 42
74 // Initialize GPU driver. 43 // Initialize GPU driver.
75 NativeLibrary.initializeGpuDriver( 44 NativeLibrary.initializeGpuDriver(
76 hookLibPath, 45 hookLibPath,
77 driverInstallationPath, 46 driverInstallationPath,
78 customDriverLibraryName, 47 customDriverData.libraryName,
79 fileRedirectionPath 48 fileRedirectionPath
80 ) 49 )
81 } 50 }
82 51
83 fun installDefaultDriver(context: Context) { 52 fun getDrivers(): MutableList<Pair<String, GpuDriverMetadata>> {
53 val driverZips = File(driverStoragePath).listFiles()
54 val drivers: MutableList<Pair<String, GpuDriverMetadata>> =
55 driverZips
56 ?.mapNotNull {
57 val metadata = getMetadataFromZip(it)
58 metadata.name?.let { _ -> Pair(it.path, metadata) }
59 }
60 ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
61 ?.distinct()
62 ?.toMutableList() ?: mutableListOf()
63
64 // TODO: Get system driver information
65 drivers.add(0, Pair("", GpuDriverMetadata()))
66 return drivers
67 }
68
69 fun installDefaultDriver() {
84 // Removing the installed driver will result in the backend using the default system driver. 70 // Removing the installed driver will result in the backend using the default system driver.
85 val driverInstallationDir = File(driverInstallationPath!!) 71 File(driverInstallationPath!!).deleteRecursively()
86 deleteRecursive(driverInstallationDir) 72 initializeDriverParameters()
87 initializeDriverParameters(context) 73 }
74
75 fun copyDriverToInternalStorage(driverUri: Uri): Boolean {
76 // Ensure we have directories.
77 initializeDirectories()
78
79 // Copy the zip file URI to user data
80 val copiedFile =
81 FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
82
83 // Validate driver
84 val metadata = getMetadataFromZip(copiedFile)
85 if (metadata.name == null) {
86 copiedFile.delete()
87 return false
88 }
89
90 if (metadata.minApi > Build.VERSION.SDK_INT) {
91 copiedFile.delete()
92 return false
93 }
94 return true
88 } 95 }
89 96
90 fun installCustomDriver(context: Context, driverPathUri: Uri?) { 97 /**
98 * Copies driver zip into user data directory so that it can be exported along with
99 * other user data and also unzipped into the installation directory
100 */
101 fun installCustomDriver(driverUri: Uri): Boolean {
91 // Revert to system default in the event the specified driver is bad. 102 // Revert to system default in the event the specified driver is bad.
92 installDefaultDriver(context) 103 installDefaultDriver()
93 104
94 // Ensure we have directories. 105 // Ensure we have directories.
95 initializeDirectories() 106 initializeDirectories()
96 107
97 // Copy the zip file URI into our private storage. 108 // Copy the zip file URI to user data
98 copyUriToInternalStorage( 109 val copiedFile =
99 context, 110 FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
100 driverPathUri, 111
101 driverInstallationPath!!, 112 // Validate driver
102 DRIVER_INTERNAL_FILENAME 113 val metadata = getMetadataFromZip(copiedFile)
103 ) 114 if (metadata.name == null) {
115 copiedFile.delete()
116 return false
117 }
118
119 if (metadata.minApi > Build.VERSION.SDK_INT) {
120 copiedFile.delete()
121 return false
122 }
104 123
105 // Unzip the driver. 124 // Unzip the driver.
106 try { 125 try {
107 unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!) 126 FileUtil.unzipToInternalStorage(
127 BufferedInputStream(copiedFile.inputStream()),
128 File(driverInstallationPath!!)
129 )
108 } catch (e: SecurityException) { 130 } catch (e: SecurityException) {
109 return 131 return false
110 } 132 }
111 133
112 // Initialize the driver parameters. 134 // Initialize the driver parameters.
113 initializeDriverParameters(context) 135 initializeDriverParameters()
136
137 return true
114 } 138 }
115 139
116 external fun supportsCustomDriverLoading(): Boolean 140 /**
141 * Unzips driver into installation directory
142 */
143 fun installCustomDriver(driver: File): Boolean {
144 // Revert to system default in the event the specified driver is bad.
145 installDefaultDriver()
117 146
118 // Parse the custom driver metadata to retrieve the name. 147 // Ensure we have directories.
119 val customDriverName: String? 148 initializeDirectories()
120 get() { 149
121 val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME) 150 // Validate driver
122 return metadata.name 151 val metadata = getMetadataFromZip(driver)
152 if (metadata.name == null) {
153 driver.delete()
154 return false
123 } 155 }
124 156
125 // Parse the custom driver metadata to retrieve the library name. 157 // Unzip the driver to the private installation directory
126 private val customDriverLibraryName: String? 158 try {
127 get() { 159 FileUtil.unzipToInternalStorage(
128 // Parse the custom driver metadata to retrieve the library name. 160 BufferedInputStream(driver.inputStream()),
129 val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME) 161 File(driverInstallationPath!!)
130 return metadata.libraryName 162 )
163 } catch (e: SecurityException) {
164 return false
131 } 165 }
132 166
133 private fun initializeDirectories() { 167 // Initialize the driver parameters.
168 initializeDriverParameters()
169
170 return true
171 }
172
173 /**
174 * Takes in a zip file and reads the meta.json file for presentation to the UI
175 *
176 * @param driver Zip containing driver and meta.json file
177 * @return A non-null [GpuDriverMetadata] instance that may have null members
178 */
179 fun getMetadataFromZip(driver: File): GpuDriverMetadata {
180 try {
181 ZipFile(driver).use { zf ->
182 val entries = zf.entries()
183 while (entries.hasMoreElements()) {
184 val entry = entries.nextElement()
185 if (!entry.isDirectory && entry.name.lowercase().contains(".json")) {
186 zf.getInputStream(entry).use {
187 return GpuDriverMetadata(it, entry.size)
188 }
189 }
190 }
191 }
192 } catch (_: ZipException) {
193 }
194 return GpuDriverMetadata()
195 }
196
197 external fun supportsCustomDriverLoading(): Boolean
198
199 // Parse the custom driver metadata to retrieve the name.
200 val customDriverData: GpuDriverMetadata
201 get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME))
202
203 fun initializeDirectories() {
134 // Ensure the file redirection directory exists. 204 // Ensure the file redirection directory exists.
135 val fileRedirectionDir = File(fileRedirectionPath!!) 205 val fileRedirectionDir = File(fileRedirectionPath!!)
136 if (!fileRedirectionDir.exists()) { 206 if (!fileRedirectionDir.exists()) {
@@ -141,14 +211,10 @@ object GpuDriverHelper {
141 if (!driverInstallationDir.exists()) { 211 if (!driverInstallationDir.exists()) {
142 driverInstallationDir.mkdirs() 212 driverInstallationDir.mkdirs()
143 } 213 }
144 } 214 // Ensure the driver storage directory exists
145 215 val driverStorageDirectory = File(driverStoragePath)
146 private fun deleteRecursive(fileOrDirectory: File) { 216 if (!driverStorageDirectory.exists()) {
147 if (fileOrDirectory.isDirectory) { 217 driverStorageDirectory.mkdirs()
148 for (child in fileOrDirectory.listFiles()!!) {
149 deleteRecursive(child)
150 }
151 } 218 }
152 fileOrDirectory.delete()
153 } 219 }
154} 220}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
index a4e64070a..511a4171a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
@@ -4,29 +4,29 @@
4package org.yuzu.yuzu_emu.utils 4package org.yuzu.yuzu_emu.utils
5 5
6import java.io.IOException 6import java.io.IOException
7import java.nio.charset.StandardCharsets
8import java.nio.file.Files
9import java.nio.file.Paths
10import org.json.JSONException 7import org.json.JSONException
11import org.json.JSONObject 8import org.json.JSONObject
9import java.io.File
10import java.io.InputStream
12 11
13class GpuDriverMetadata(metadataFilePath: String) { 12class GpuDriverMetadata {
14 var name: String? = null 13 /**
15 var description: String? = null 14 * Tries to get driver metadata information from a meta.json [File]
16 var author: String? = null 15 *
17 var vendor: String? = null 16 * @param metadataFile meta.json file provided with a GPU driver
18 var driverVersion: String? = null 17 */
19 var minApi = 0 18 constructor(metadataFile: File) {
20 var libraryName: String? = null 19 if (metadataFile.length() > MAX_META_SIZE_BYTES) {
20 return
21 }
21 22
22 init {
23 try { 23 try {
24 val json = JSONObject(getStringFromFile(metadataFilePath)) 24 val json = JSONObject(FileUtil.getStringFromFile(metadataFile))
25 name = json.getString("name") 25 name = json.getString("name")
26 description = json.getString("description") 26 description = json.getString("description")
27 author = json.getString("author") 27 author = json.getString("author")
28 vendor = json.getString("vendor") 28 vendor = json.getString("vendor")
29 driverVersion = json.getString("driverVersion") 29 version = json.getString("driverVersion")
30 minApi = json.getInt("minApi") 30 minApi = json.getInt("minApi")
31 libraryName = json.getString("libraryName") 31 libraryName = json.getString("libraryName")
32 } catch (e: JSONException) { 32 } catch (e: JSONException) {
@@ -36,12 +36,84 @@ class GpuDriverMetadata(metadataFilePath: String) {
36 } 36 }
37 } 37 }
38 38
39 companion object { 39 /**
40 @Throws(IOException::class) 40 * Tries to get driver metadata information from an input stream that's intended to be
41 private fun getStringFromFile(filePath: String): String { 41 * from a zip file
42 val path = Paths.get(filePath) 42 *
43 val bytes = Files.readAllBytes(path) 43 * @param metadataStream ZipEntry input stream
44 return String(bytes, StandardCharsets.UTF_8) 44 * @param size Size of the file in bytes
45 */
46 constructor(metadataStream: InputStream, size: Long) {
47 if (size > MAX_META_SIZE_BYTES) {
48 return
45 } 49 }
50
51 try {
52 val json = JSONObject(FileUtil.getStringFromInputStream(metadataStream))
53 name = json.getString("name")
54 description = json.getString("description")
55 author = json.getString("author")
56 vendor = json.getString("vendor")
57 version = json.getString("driverVersion")
58 minApi = json.getInt("minApi")
59 libraryName = json.getString("libraryName")
60 } catch (e: JSONException) {
61 // JSON is malformed, ignore and treat as unsupported metadata.
62 } catch (e: IOException) {
63 // File is inaccessible, ignore and treat as unsupported metadata.
64 }
65 }
66
67 /**
68 * Creates an empty metadata instance
69 */
70 constructor()
71
72 override fun equals(other: Any?): Boolean {
73 if (other !is GpuDriverMetadata) {
74 return false
75 }
76
77 return other.name == name &&
78 other.description == description &&
79 other.author == author &&
80 other.vendor == vendor &&
81 other.version == version &&
82 other.minApi == minApi &&
83 other.libraryName == libraryName
84 }
85
86 override fun hashCode(): Int {
87 var result = name?.hashCode() ?: 0
88 result = 31 * result + (description?.hashCode() ?: 0)
89 result = 31 * result + (author?.hashCode() ?: 0)
90 result = 31 * result + (vendor?.hashCode() ?: 0)
91 result = 31 * result + (version?.hashCode() ?: 0)
92 result = 31 * result + minApi
93 result = 31 * result + (libraryName?.hashCode() ?: 0)
94 return result
95 }
96
97 override fun toString(): String =
98 """
99 Name - $name
100 Description - $description
101 Author - $author
102 Vendor - $vendor
103 Version - $version
104 Min API - $minApi
105 Library Name - $libraryName
106 """.trimMargin().trimIndent()
107
108 var name: String? = null
109 var description: String? = null
110 var author: String? = null
111 var vendor: String? = null
112 var version: String? = null
113 var minApi = 0
114 var libraryName: String? = null
115
116 companion object {
117 private const val MAX_META_SIZE_BYTES = 500000
46 } 118 }
47} 119}
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 9cf71680c..598f4e8bf 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -218,7 +218,6 @@ public:
218 return; 218 return;
219 } 219 }
220 m_window->OnSurfaceChanged(m_native_window); 220 m_window->OnSurfaceChanged(m_native_window);
221 m_system.Renderer().NotifySurfaceChanged();
222 } 221 }
223 222
224 void ConfigureFilesystemProvider(const std::string& filepath) { 223 void ConfigureFilesystemProvider(const std::string& filepath) {
diff --git a/src/android/app/src/main/res/drawable/ic_build.xml b/src/android/app/src/main/res/drawable/ic_build.xml
new file mode 100644
index 000000000..91d52f1b8
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_build.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
9</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_delete.xml b/src/android/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 000000000..d26a79711
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,9 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 android:width="24dp"
3 android:height="24dp"
4 android:viewportWidth="24"
5 android:viewportHeight="24">
6 <path
7 android:fillColor="?attr/colorControlNormal"
8 android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
9</vector>
diff --git a/src/android/app/src/main/res/layout/card_driver_option.xml b/src/android/app/src/main/res/layout/card_driver_option.xml
new file mode 100644
index 000000000..1dd9a6d7d
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_driver_option.xml
@@ -0,0 +1,89 @@
1<?xml version="1.0" encoding="utf-8"?>
2<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 style="?attr/materialCardViewOutlinedStyle"
6 android:layout_width="match_parent"
7 android:layout_height="wrap_content"
8 android:layout_marginHorizontal="16dp"
9 android:layout_marginVertical="12dp"
10 android:background="?attr/selectableItemBackground"
11 android:clickable="true"
12 android:focusable="true">
13
14 <LinearLayout
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:orientation="horizontal"
18 android:layout_gravity="center"
19 android:padding="16dp">
20
21 <RadioButton
22 android:id="@+id/radio_button"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content"
25 android:layout_gravity="center_vertical"
26 android:clickable="false"
27 android:checked="false" />
28
29 <LinearLayout
30 android:layout_width="0dp"
31 android:layout_height="wrap_content"
32 android:layout_weight="1"
33 android:orientation="vertical"
34 android:layout_gravity="center_vertical">
35
36 <com.google.android.material.textview.MaterialTextView
37 android:id="@+id/title"
38 style="@style/TextAppearance.Material3.TitleMedium"
39 android:layout_width="match_parent"
40 android:layout_height="wrap_content"
41 android:ellipsize="none"
42 android:marqueeRepeatLimit="marquee_forever"
43 android:requiresFadingEdge="horizontal"
44 android:singleLine="true"
45 android:textAlignment="viewStart"
46 tools:text="@string/select_gpu_driver_default" />
47
48 <com.google.android.material.textview.MaterialTextView
49 android:id="@+id/version"
50 style="@style/TextAppearance.Material3.BodyMedium"
51 android:layout_width="match_parent"
52 android:layout_height="wrap_content"
53 android:layout_marginTop="6dp"
54 android:ellipsize="none"
55 android:marqueeRepeatLimit="marquee_forever"
56 android:requiresFadingEdge="horizontal"
57 android:singleLine="true"
58 android:textAlignment="viewStart"
59 tools:text="@string/install_gpu_driver_description" />
60
61 <com.google.android.material.textview.MaterialTextView
62 android:id="@+id/description"
63 style="@style/TextAppearance.Material3.BodyMedium"
64 android:layout_width="match_parent"
65 android:layout_height="wrap_content"
66 android:layout_marginTop="6dp"
67 android:ellipsize="none"
68 android:marqueeRepeatLimit="marquee_forever"
69 android:requiresFadingEdge="horizontal"
70 android:singleLine="true"
71 android:textAlignment="viewStart"
72 tools:text="@string/install_gpu_driver_description" />
73
74 </LinearLayout>
75
76 <Button
77 android:id="@+id/button_delete"
78 style="@style/Widget.Material3.Button.IconButton"
79 android:layout_width="wrap_content"
80 android:layout_height="wrap_content"
81 android:layout_gravity="center_vertical"
82 android:contentDescription="@string/delete"
83 android:tooltipText="@string/delete"
84 app:icon="@drawable/ic_delete"
85 app:iconTint="?attr/colorControlNormal" />
86
87 </LinearLayout>
88
89</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/fragment_driver_manager.xml b/src/android/app/src/main/res/layout/fragment_driver_manager.xml
new file mode 100644
index 000000000..6cea2d164
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_driver_manager.xml
@@ -0,0 +1,48 @@
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 android:id="@+id/coordinator_licenses"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="?attr/colorSurface">
8
9 <androidx.coordinatorlayout.widget.CoordinatorLayout
10 android:layout_width="match_parent"
11 android:layout_height="match_parent">
12
13 <com.google.android.material.appbar.AppBarLayout
14 android:id="@+id/appbar_drivers"
15 android:layout_width="match_parent"
16 android:layout_height="wrap_content"
17 android:fitsSystemWindows="true"
18 app:liftOnScrollTargetViewId="@id/list_drivers">
19
20 <com.google.android.material.appbar.MaterialToolbar
21 android:id="@+id/toolbar_drivers"
22 android:layout_width="match_parent"
23 android:layout_height="?attr/actionBarSize"
24 app:navigationIcon="@drawable/ic_back"
25 app:title="@string/gpu_driver_manager" />
26
27 </com.google.android.material.appbar.AppBarLayout>
28
29 <androidx.recyclerview.widget.RecyclerView
30 android:id="@+id/list_drivers"
31 android:layout_width="match_parent"
32 android:layout_height="match_parent"
33 android:clipToPadding="false"
34 app:layout_behavior="@string/appbar_scrolling_view_behavior" />
35
36 </androidx.coordinatorlayout.widget.CoordinatorLayout>
37
38 <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
39 android:id="@+id/button_install"
40 android:layout_width="wrap_content"
41 android:layout_height="wrap_content"
42 android:layout_gravity="bottom|end"
43 android:text="@string/install"
44 app:icon="@drawable/ic_add"
45 app:layout_constraintBottom_toBottomOf="parent"
46 app:layout_constraintEnd_toEndOf="parent" />
47
48</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 2356b802b..82749359d 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -22,6 +22,9 @@
22 <action 22 <action
23 android:id="@+id/action_homeSettingsFragment_to_installableFragment" 23 android:id="@+id/action_homeSettingsFragment_to_installableFragment"
24 app:destination="@id/installableFragment" /> 24 app:destination="@id/installableFragment" />
25 <action
26 android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
27 app:destination="@id/driverManagerFragment" />
25 </fragment> 28 </fragment>
26 29
27 <fragment 30 <fragment
@@ -95,5 +98,9 @@
95 android:id="@+id/installableFragment" 98 android:id="@+id/installableFragment"
96 android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment" 99 android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
97 android:label="InstallableFragment" /> 100 android:label="InstallableFragment" />
101 <fragment
102 android:id="@+id/driverManagerFragment"
103 android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
104 android:label="DriverManagerFragment" />
98 105
99</navigation> 106</navigation>
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml
index dd0f36392..72a47fbdb 100644
--- a/src/android/app/src/main/res/values-de/strings.xml
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -168,9 +168,7 @@
168 <string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string> 168 <string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string>
169 <string name="select_gpu_driver_install">Installieren</string> 169 <string name="select_gpu_driver_install">Installieren</string>
170 <string name="select_gpu_driver_default">Standard</string> 170 <string name="select_gpu_driver_default">Standard</string>
171 <string name="select_gpu_driver_install_success">%s wurde installiert</string>
172 <string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string> 171 <string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string>
173 <string name="select_gpu_driver_error">Ungültiger Treiber ausgewählt, Standard-Treiber wird verwendet!</string>
174 <string name="system_gpu_driver">System GPU-Treiber</string> 172 <string name="system_gpu_driver">System GPU-Treiber</string>
175 <string name="installing_driver">Treiber wird installiert...</string> 173 <string name="installing_driver">Treiber wird installiert...</string>
176 174
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml
index d398f862f..e5bdd5889 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string> 171 <string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string>
172 <string name="select_gpu_driver_install">Instalar</string> 172 <string name="select_gpu_driver_install">Instalar</string>
173 <string name="select_gpu_driver_default">Predeterminado</string> 173 <string name="select_gpu_driver_default">Predeterminado</string>
174 <string name="select_gpu_driver_install_success">Instalado %s</string>
175 <string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string> 174 <string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string>
176 <string name="select_gpu_driver_error">¡Driver no válido, utilizando el predeterminado del sistema!</string>
177 <string name="system_gpu_driver">Driver GPU del sistema</string> 175 <string name="system_gpu_driver">Driver GPU del sistema</string>
178 <string name="installing_driver">Instalando driver...</string> 176 <string name="installing_driver">Instalando driver...</string>
179 177
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml
index a7abd9077..1e02828aa 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string> 171 <string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string>
172 <string name="select_gpu_driver_install">Installer</string> 172 <string name="select_gpu_driver_install">Installer</string>
173 <string name="select_gpu_driver_default">Défaut</string> 173 <string name="select_gpu_driver_default">Défaut</string>
174 <string name="select_gpu_driver_install_success">%s Installé</string>
175 <string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string> 174 <string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string>
176 <string name="select_gpu_driver_error">Pilote non valide sélectionné, utilisation du paramètre par défaut du système !</string>
177 <string name="system_gpu_driver">Pilote du GPU du système</string> 175 <string name="system_gpu_driver">Pilote du GPU du système</string>
178 <string name="installing_driver">Installation du pilote...</string> 176 <string name="installing_driver">Installation du pilote...</string>
179 177
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml
index b18161801..09c9345b0 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string> 171 <string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string>
172 <string name="select_gpu_driver_install">Installa</string> 172 <string name="select_gpu_driver_install">Installa</string>
173 <string name="select_gpu_driver_default">Predefinito</string> 173 <string name="select_gpu_driver_default">Predefinito</string>
174 <string name="select_gpu_driver_install_success">Installato%s</string>
175 <string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string> 174 <string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string>
176 <string name="select_gpu_driver_error">Il driver selezionato è invalido, è in utilizzo quello predefinito di sistema!</string>
177 <string name="system_gpu_driver">Driver GPU del sistema</string> 175 <string name="system_gpu_driver">Driver GPU del sistema</string>
178 <string name="installing_driver">Installando i driver...</string> 176 <string name="installing_driver">Installando i driver...</string>
179 177
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml
index 88fa5a0bb..a0ea78bef 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -170,9 +170,7 @@
170 <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string> 170 <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string>
171 <string name="select_gpu_driver_install">インストール</string> 171 <string name="select_gpu_driver_install">インストール</string>
172 <string name="select_gpu_driver_default">デフォルト</string> 172 <string name="select_gpu_driver_default">デフォルト</string>
173 <string name="select_gpu_driver_install_success">%s をインストールしました</string>
174 <string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string> 173 <string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string>
175 <string name="select_gpu_driver_error">選択されたドライバが無効なため、システムのデフォルトを使用します!</string>
176 <string name="system_gpu_driver">システムのGPUドライバ</string> 174 <string name="system_gpu_driver">システムのGPUドライバ</string>
177 <string name="installing_driver">インストール中…</string> 175 <string name="installing_driver">インストール中…</string>
178 176
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml
index 4b658255c..214f95706 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string> 171 <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
172 <string name="select_gpu_driver_install">설치</string> 172 <string name="select_gpu_driver_install">설치</string>
173 <string name="select_gpu_driver_default">기본값</string> 173 <string name="select_gpu_driver_default">기본값</string>
174 <string name="select_gpu_driver_install_success">설치된 %s</string>
175 <string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string> 174 <string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string>
176 <string name="select_gpu_driver_error">시스템 기본값을 사용하여 잘못된 드라이버를 선택했습니다!</string>
177 <string name="system_gpu_driver">시스템 GPU 드라이버</string> 175 <string name="system_gpu_driver">시스템 GPU 드라이버</string>
178 <string name="installing_driver">드라이버 설치 중...</string> 176 <string name="installing_driver">드라이버 설치 중...</string>
179 177
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml
index dd602a389..5443cef42 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string> 171 <string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string>
172 <string name="select_gpu_driver_install">Installer</string> 172 <string name="select_gpu_driver_install">Installer</string>
173 <string name="select_gpu_driver_default">Standard</string> 173 <string name="select_gpu_driver_default">Standard</string>
174 <string name="select_gpu_driver_install_success">Installert %s</string>
175 <string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string> 174 <string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string>
176 <string name="select_gpu_driver_error">Ugyldig driver valgt, bruker systemstandard!</string>
177 <string name="system_gpu_driver">Systemets GPU-driver</string> 175 <string name="system_gpu_driver">Systemets GPU-driver</string>
178 <string name="installing_driver">Installerer driver...</string> 176 <string name="installing_driver">Installerer driver...</string>
179 177
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml
index 2fdd1f952..899e233d0 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string> 171 <string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string>
172 <string name="select_gpu_driver_install">Zainstaluj</string> 172 <string name="select_gpu_driver_install">Zainstaluj</string>
173 <string name="select_gpu_driver_default">Domyślne</string> 173 <string name="select_gpu_driver_default">Domyślne</string>
174 <string name="select_gpu_driver_install_success">Zainstalowano %s</string>
175 <string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string> 174 <string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string>
176 <string name="select_gpu_driver_error">Wybrano błędny sterownik, powrót do domyślnego. </string>
177 <string name="system_gpu_driver">Systemowy sterownik GPU</string> 175 <string name="system_gpu_driver">Systemowy sterownik GPU</string>
178 <string name="installing_driver">Instalowanie sterownika...</string> 176 <string name="installing_driver">Instalowanie sterownika...</string>
179 177
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml
index 2f26367fe..caa095364 100644
--- a/src/android/app/src/main/res/values-pt-rBR/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> 171 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
172 <string name="select_gpu_driver_install">Instalar</string> 172 <string name="select_gpu_driver_install">Instalar</string>
173 <string name="select_gpu_driver_default">Padrão</string> 173 <string name="select_gpu_driver_default">Padrão</string>
174 <string name="select_gpu_driver_install_success">Instalado%s</string>
175 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string> 174 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
176 <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
177 <string name="system_gpu_driver">Driver do GPU padrão</string> 175 <string name="system_gpu_driver">Driver do GPU padrão</string>
178 <string name="installing_driver">A instalar o Driver...</string> 176 <string name="installing_driver">A instalar o Driver...</string>
179 177
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml
index 4e1eb4cd7..0a1a47fbb 100644
--- a/src/android/app/src/main/res/values-pt-rPT/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> 171 <string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
172 <string name="select_gpu_driver_install">Instalar</string> 172 <string name="select_gpu_driver_install">Instalar</string>
173 <string name="select_gpu_driver_default">Padrão</string> 173 <string name="select_gpu_driver_default">Padrão</string>
174 <string name="select_gpu_driver_install_success">Instalado%s</string>
175 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string> 174 <string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
176 <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
177 <string name="system_gpu_driver">Driver do GPU padrão</string> 175 <string name="system_gpu_driver">Driver do GPU padrão</string>
178 <string name="installing_driver">A instalar o Driver...</string> 176 <string name="installing_driver">A instalar o Driver...</string>
179 177
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml
index f5695dc93..0bef035d6 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string> 171 <string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string>
172 <string name="select_gpu_driver_install">Установить</string> 172 <string name="select_gpu_driver_install">Установить</string>
173 <string name="select_gpu_driver_default">По умолчанию</string> 173 <string name="select_gpu_driver_default">По умолчанию</string>
174 <string name="select_gpu_driver_install_success">Установлено %s</string>
175 <string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string> 174 <string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string>
176 <string name="select_gpu_driver_error">Выбран неверный драйвер, используется стандартный системный!</string>
177 <string name="system_gpu_driver">Системный драйвер ГП</string> 175 <string name="system_gpu_driver">Системный драйвер ГП</string>
178 <string name="installing_driver">Установка драйвера...</string> 176 <string name="installing_driver">Установка драйвера...</string>
179 177
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml
index 061bc6f04..5b789ee98 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string> 171 <string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string>
172 <string name="select_gpu_driver_install">Встановити</string> 172 <string name="select_gpu_driver_install">Встановити</string>
173 <string name="select_gpu_driver_default">За замовчуванням</string> 173 <string name="select_gpu_driver_default">За замовчуванням</string>
174 <string name="select_gpu_driver_install_success">Встановлено %s</string>
175 <string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string> 174 <string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string>
176 <string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string>
177 <string name="system_gpu_driver">Системний драйвер ГП</string> 175 <string name="system_gpu_driver">Системний драйвер ГП</string>
178 <string name="installing_driver">Встановлення драйвера...</string> 176 <string name="installing_driver">Встановлення драйвера...</string>
179 177
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml
index fe6dd5eaa..c0e885751 100644
--- a/src/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string> 171 <string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string>
172 <string name="select_gpu_driver_install">安装</string> 172 <string name="select_gpu_driver_install">安装</string>
173 <string name="select_gpu_driver_default">系统默认</string> 173 <string name="select_gpu_driver_default">系统默认</string>
174 <string name="select_gpu_driver_install_success">已安装 %s</string>
175 <string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string> 174 <string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string>
176 <string name="select_gpu_driver_error">选择的驱动程序无效,将使用系统默认的驱动程序!</string>
177 <string name="system_gpu_driver">系统 GPU 驱动程序</string> 175 <string name="system_gpu_driver">系统 GPU 驱动程序</string>
178 <string name="installing_driver">正在安装驱动程序…</string> 176 <string name="installing_driver">正在安装驱动程序…</string>
179 177
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml
index 9b3e54224..4a21bf893 100644
--- a/src/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -171,9 +171,7 @@
171 <string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string> 171 <string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string>
172 <string name="select_gpu_driver_install">安裝</string> 172 <string name="select_gpu_driver_install">安裝</string>
173 <string name="select_gpu_driver_default">預設</string> 173 <string name="select_gpu_driver_default">預設</string>
174 <string name="select_gpu_driver_install_success">已安裝 %s</string>
175 <string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string> 174 <string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string>
176 <string name="select_gpu_driver_error">選取的驅動程式無效,將使用系統預設驅動程式!</string>
177 <string name="system_gpu_driver">系統 GPU 驅動程式</string> 175 <string name="system_gpu_driver">系統 GPU 驅動程式</string>
178 <string name="installing_driver">正在安裝驅動程式…</string> 176 <string name="installing_driver">正在安裝驅動程式…</string>
179 177
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index 7b2296d95..ef855ea6f 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -13,6 +13,8 @@
13 <dimen name="menu_width">256dp</dimen> 13 <dimen name="menu_width">256dp</dimen>
14 <dimen name="card_width">165dp</dimen> 14 <dimen name="card_width">165dp</dimen>
15 <dimen name="icon_inset">24dp</dimen> 15 <dimen name="icon_inset">24dp</dimen>
16 <dimen name="spacing_bottom_list_fab">72dp</dimen>
17 <dimen name="spacing_fab">24dp</dimen>
16 18
17 <dimen name="dialog_margin">20dp</dimen> 19 <dimen name="dialog_margin">20dp</dimen>
18 <dimen name="elevated_app_bar">3dp</dimen> 20 <dimen name="elevated_app_bar">3dp</dimen>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index e51edf872..9e4854221 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -72,6 +72,7 @@
72 <string name="invalid_keys_error">Invalid encryption keys</string> 72 <string name="invalid_keys_error">Invalid encryption keys</string>
73 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string> 73 <string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
74 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string> 74 <string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
75 <string name="gpu_driver_manager">GPU Driver Manager</string>
75 <string name="install_gpu_driver">Install GPU driver</string> 76 <string name="install_gpu_driver">Install GPU driver</string>
76 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> 77 <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
77 <string name="advanced_settings">Advanced settings</string> 78 <string name="advanced_settings">Advanced settings</string>
@@ -234,15 +235,17 @@
234 <string name="export_failed">Export failed</string> 235 <string name="export_failed">Export failed</string>
235 <string name="import_failed">Import failed</string> 236 <string name="import_failed">Import failed</string>
236 <string name="cancelling">Cancelling</string> 237 <string name="cancelling">Cancelling</string>
238 <string name="install">Install</string>
239 <string name="delete">Delete</string>
237 240
238 <!-- GPU driver installation --> 241 <!-- GPU driver installation -->
239 <string name="select_gpu_driver">Select GPU driver</string> 242 <string name="select_gpu_driver">Select GPU driver</string>
240 <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string> 243 <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string>
241 <string name="select_gpu_driver_install">Install</string> 244 <string name="select_gpu_driver_install">Install</string>
242 <string name="select_gpu_driver_default">Default</string> 245 <string name="select_gpu_driver_default">Default</string>
243 <string name="select_gpu_driver_install_success">Installed %s</string>
244 <string name="select_gpu_driver_use_default">Using default GPU driver</string> 246 <string name="select_gpu_driver_use_default">Using default GPU driver</string>
245 <string name="select_gpu_driver_error">Invalid driver selected, using system default!</string> 247 <string name="select_gpu_driver_error">Invalid driver selected</string>
248 <string name="driver_already_installed">Driver already installed</string>
246 <string name="system_gpu_driver">System GPU driver</string> 249 <string name="system_gpu_driver">System GPU driver</string>
247 <string name="installing_driver">Installing driver…</string> 250 <string name="installing_driver">Installing driver…</string>
248 251
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index 80f370c16..51e559321 100644
--- a/src/android/build.gradle.kts
+++ b/src/android/build.gradle.kts
@@ -3,8 +3,8 @@
3 3
4// Top-level build file where you can add configuration options common to all sub-projects/modules. 4// Top-level build file where you can add configuration options common to all sub-projects/modules.
5plugins { 5plugins {
6 id("com.android.application") version "8.0.2" apply false 6 id("com.android.application") version "8.1.2" apply false
7 id("com.android.library") version "8.0.2" apply false 7 id("com.android.library") version "8.1.2" apply false
8 id("org.jetbrains.kotlin.android") version "1.8.21" apply false 8 id("org.jetbrains.kotlin.android") version "1.8.21" apply false
9} 9}
10 10
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
index 972d5e45b..ef301d8b4 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
@@ -77,6 +77,7 @@ void AudioRenderer::Wait() {
77 "{}, got {}", 77 "{}, got {}",
78 Message::RenderResponse, msg); 78 Message::RenderResponse, msg);
79 } 79 }
80 PostDSPClearCommandBuffer();
80} 81}
81 82
82void AudioRenderer::Send(Direction dir, u32 message) { 83void AudioRenderer::Send(Direction dir, u32 message) {
@@ -96,6 +97,14 @@ void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u
96 command_buffers[session_id].reset_buffer = reset; 97 command_buffers[session_id].reset_buffer = reset;
97} 98}
98 99
100void AudioRenderer::PostDSPClearCommandBuffer() noexcept {
101 for (auto& buffer : command_buffers) {
102 buffer.buffer = 0;
103 buffer.size = 0;
104 buffer.reset_buffer = false;
105 }
106}
107
99u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { 108u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
100 return command_buffers[session_id].remaining_command_count; 109 return command_buffers[session_id].remaining_command_count;
101} 110}
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
index 85874d88a..57b89d9fe 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
@@ -85,6 +85,8 @@ private:
85 */ 85 */
86 void CreateSinkStreams(); 86 void CreateSinkStreams();
87 87
88 void PostDSPClearCommandBuffer() noexcept;
89
88 /// Core system 90 /// Core system
89 Core::System& system; 91 Core::System& system;
90 /// The output sink the AudioRenderer will send samples to 92 /// The output sink the AudioRenderer will send samples to
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index 6081352a2..d66d04fae 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -204,6 +204,10 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
204 // paused and we'll desync, so just play silence. 204 // paused and we'll desync, so just play silence.
205 if (system.IsPaused() || system.IsShuttingDown()) { 205 if (system.IsPaused() || system.IsShuttingDown()) {
206 if (system.IsShuttingDown()) { 206 if (system.IsShuttingDown()) {
207 {
208 std::scoped_lock lk{release_mutex};
209 queued_buffers.store(0);
210 }
207 release_cv.notify_one(); 211 release_cv.notify_one();
208 } 212 }
209 213
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 416203c59..8a1861051 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -189,6 +189,14 @@ if(ARCHITECTURE_x86_64)
189 target_link_libraries(common PRIVATE xbyak::xbyak) 189 target_link_libraries(common PRIVATE xbyak::xbyak)
190endif() 190endif()
191 191
192if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
193 target_sources(common
194 PRIVATE
195 arm64/native_clock.cpp
196 arm64/native_clock.h
197 )
198endif()
199
192if (MSVC) 200if (MSVC)
193 target_compile_definitions(common PRIVATE 201 target_compile_definitions(common PRIVATE
194 # The standard library doesn't provide any replacement for codecvt yet 202 # The standard library doesn't provide any replacement for codecvt yet
diff --git a/src/common/arm64/native_clock.cpp b/src/common/arm64/native_clock.cpp
new file mode 100644
index 000000000..88fdba527
--- /dev/null
+++ b/src/common/arm64/native_clock.cpp
@@ -0,0 +1,72 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/arm64/native_clock.h"
5
6namespace Common::Arm64 {
7
8namespace {
9
10NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) {
11 return (static_cast<NativeClock::FactorType>(num) << 64) / den;
12}
13
14u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) {
15 return static_cast<u64>((m * factor) >> 64);
16}
17
18} // namespace
19
20NativeClock::NativeClock() {
21 const u64 host_cntfrq = GetHostCNTFRQ();
22 ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
23 us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
24 ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
25 guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
26 gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
27}
28
29std::chrono::nanoseconds NativeClock::GetTimeNS() const {
30 return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_cntfrq_factor)};
31}
32
33std::chrono::microseconds NativeClock::GetTimeUS() const {
34 return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_cntfrq_factor)};
35}
36
37std::chrono::milliseconds NativeClock::GetTimeMS() const {
38 return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_cntfrq_factor)};
39}
40
41u64 NativeClock::GetCNTPCT() const {
42 return MultiplyHigh(GetHostTicksElapsed(), guest_cntfrq_factor);
43}
44
45u64 NativeClock::GetGPUTick() const {
46 return MultiplyHigh(GetHostTicksElapsed(), gputick_cntfrq_factor);
47}
48
49u64 NativeClock::GetHostTicksNow() const {
50 u64 cntvct_el0 = 0;
51 asm volatile("dsb ish\n\t"
52 "mrs %[cntvct_el0], cntvct_el0\n\t"
53 "dsb ish\n\t"
54 : [cntvct_el0] "=r"(cntvct_el0));
55 return cntvct_el0;
56}
57
58u64 NativeClock::GetHostTicksElapsed() const {
59 return GetHostTicksNow();
60}
61
62bool NativeClock::IsNative() const {
63 return true;
64}
65
66u64 NativeClock::GetHostCNTFRQ() {
67 u64 cntfrq_el0 = 0;
68 asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
69 return cntfrq_el0;
70}
71
72} // namespace Common::Arm64
diff --git a/src/common/arm64/native_clock.h b/src/common/arm64/native_clock.h
new file mode 100644
index 000000000..a28b419f2
--- /dev/null
+++ b/src/common/arm64/native_clock.h
@@ -0,0 +1,47 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/wall_clock.h"
7
8namespace Common::Arm64 {
9
10class NativeClock final : public WallClock {
11public:
12 explicit NativeClock();
13
14 std::chrono::nanoseconds GetTimeNS() const override;
15
16 std::chrono::microseconds GetTimeUS() const override;
17
18 std::chrono::milliseconds GetTimeMS() const override;
19
20 u64 GetCNTPCT() const override;
21
22 u64 GetGPUTick() const override;
23
24 u64 GetHostTicksNow() const override;
25
26 u64 GetHostTicksElapsed() const override;
27
28 bool IsNative() const override;
29
30 static u64 GetHostCNTFRQ();
31
32public:
33 using FactorType = unsigned __int128;
34
35 FactorType GetGuestCNTFRQFactor() const {
36 return guest_cntfrq_factor;
37 }
38
39private:
40 FactorType ns_cntfrq_factor;
41 FactorType us_cntfrq_factor;
42 FactorType ms_cntfrq_factor;
43 FactorType guest_cntfrq_factor;
44 FactorType gputick_cntfrq_factor;
45};
46
47} // namespace Common::Arm64
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 0dad9338a..47d028d48 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -39,8 +39,12 @@
39#define Crash() exit(1) 39#define Crash() exit(1)
40#endif 40#endif
41 41
42#define LTO_NOINLINE __attribute__((noinline))
43
42#else // _MSC_VER 44#else // _MSC_VER
43 45
46#define LTO_NOINLINE
47
44// Locale Cross-Compatibility 48// Locale Cross-Compatibility
45#define locale_t _locale_t 49#define locale_t _locale_t
46 50
diff --git a/src/common/elf.h b/src/common/elf.h
index 14a5e9597..0b728dc54 100644
--- a/src/common/elf.h
+++ b/src/common/elf.h
@@ -211,6 +211,11 @@ struct Elf64_Rela {
211 Elf64_Sxword r_addend; /* Addend */ 211 Elf64_Sxword r_addend; /* Addend */
212}; 212};
213 213
214/* RELR relocation table entry */
215
216using Elf32_Relr = Elf32_Word;
217using Elf64_Relr = Elf64_Xword;
218
214/* How to extract and insert information held in the r_info field. */ 219/* How to extract and insert information held in the r_info field. */
215 220
216static inline u32 Elf32RelSymIndex(Elf32_Word r_info) { 221static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
@@ -328,6 +333,9 @@ constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */
328constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */ 333constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */
329constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */ 334constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
330constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */ 335constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */
336constexpr u32 ElfDtRelrsz = 35; /* Size of RELR relative relocations */
337constexpr u32 ElfDtRelr = 36; /* Address of RELR relative relocations */
338constexpr u32 ElfDtRelrent = 37; /* Size of one RELR relative relocation */
331 339
332} // namespace ELF 340} // namespace ELF
333} // namespace Common 341} // namespace Common
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index 61bac9eba..441c8af97 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -18,10 +18,12 @@
18#define LOAD_DIR "load" 18#define LOAD_DIR "load"
19#define LOG_DIR "log" 19#define LOG_DIR "log"
20#define NAND_DIR "nand" 20#define NAND_DIR "nand"
21#define PLAY_TIME_DIR "play_time"
21#define SCREENSHOTS_DIR "screenshots" 22#define SCREENSHOTS_DIR "screenshots"
22#define SDMC_DIR "sdmc" 23#define SDMC_DIR "sdmc"
23#define SHADER_DIR "shader" 24#define SHADER_DIR "shader"
24#define TAS_DIR "tas" 25#define TAS_DIR "tas"
26#define ICONS_DIR "icons"
25 27
26// yuzu-specific files 28// yuzu-specific files
27 29
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index dce219fcf..0abd81a45 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -124,10 +124,12 @@ public:
124 GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); 124 GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
125 GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); 125 GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
126 GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); 126 GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
127 GenerateYuzuPath(YuzuPath::PlayTimeDir, yuzu_path / PLAY_TIME_DIR);
127 GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); 128 GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
128 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); 129 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
129 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); 130 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
130 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); 131 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
132 GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
131 } 133 }
132 134
133private: 135private:
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index ba28964d0..63801c924 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -20,10 +20,12 @@ enum class YuzuPath {
20 LoadDir, // Where cheat/mod files are stored. 20 LoadDir, // Where cheat/mod files are stored.
21 LogDir, // Where log files are stored. 21 LogDir, // Where log files are stored.
22 NANDDir, // Where the emulated NAND is stored. 22 NANDDir, // Where the emulated NAND is stored.
23 PlayTimeDir, // Where play time data is stored.
23 ScreenshotsDir, // Where yuzu screenshots are stored. 24 ScreenshotsDir, // Where yuzu screenshots are stored.
24 SDMCDir, // Where the emulated SDMC is stored. 25 SDMCDir, // Where the emulated SDMC is stored.
25 ShaderDir, // Where shaders are stored. 26 ShaderDir, // Where shaders are stored.
26 TASDir, // Where TAS scripts are stored. 27 TASDir, // Where TAS scripts are stored.
28 IconsDir, // Where Icons for Windows shortcuts are stored.
27}; 29};
28 30
29/** 31/**
diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h
index 41cbb9ed5..12e59a893 100644
--- a/src/common/polyfill_thread.h
+++ b/src/common/polyfill_thread.h
@@ -15,12 +15,13 @@
15#include <condition_variable> 15#include <condition_variable>
16#include <stop_token> 16#include <stop_token>
17#include <thread> 17#include <thread>
18#include <utility>
18 19
19namespace Common { 20namespace Common {
20 21
21template <typename Condvar, typename Lock, typename Pred> 22template <typename Condvar, typename Lock, typename Pred>
22void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) { 23void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
23 cv.wait(lk, token, std::move(pred)); 24 cv.wait(lk, token, std::forward<Pred>(pred));
24} 25}
25 26
26template <typename Rep, typename Period> 27template <typename Rep, typename Period>
@@ -109,7 +110,7 @@ public:
109 110
110 // Insert the callback. 111 // Insert the callback.
111 stop_state_callback ret = ++m_next_callback; 112 stop_state_callback ret = ++m_next_callback;
112 m_callbacks.emplace(ret, move(f)); 113 m_callbacks.emplace(ret, std::move(f));
113 return ret; 114 return ret;
114 } 115 }
115 116
@@ -162,7 +163,7 @@ private:
162 friend class stop_source; 163 friend class stop_source;
163 template <typename Callback> 164 template <typename Callback>
164 friend class stop_callback; 165 friend class stop_callback;
165 stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {} 166 stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
166 167
167private: 168private:
168 shared_ptr<polyfill::stop_state> m_stop_state; 169 shared_ptr<polyfill::stop_state> m_stop_state;
@@ -198,7 +199,7 @@ public:
198private: 199private:
199 friend class jthread; 200 friend class jthread;
200 explicit stop_source(shared_ptr<polyfill::stop_state> stop_state) 201 explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
201 : m_stop_state(move(stop_state)) {} 202 : m_stop_state(std::move(stop_state)) {}
202 203
203private: 204private:
204 shared_ptr<polyfill::stop_state> m_stop_state; 205 shared_ptr<polyfill::stop_state> m_stop_state;
@@ -218,16 +219,16 @@ public:
218 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) 219 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
219 : m_stop_state(st.m_stop_state) { 220 : m_stop_state(st.m_stop_state) {
220 if (m_stop_state) { 221 if (m_stop_state) {
221 m_callback = m_stop_state->insert_callback(move(cb)); 222 m_callback = m_stop_state->insert_callback(std::move(cb));
222 } 223 }
223 } 224 }
224 template <typename C> 225 template <typename C>
225 requires constructible_from<Callback, C> 226 requires constructible_from<Callback, C>
226 explicit stop_callback(stop_token&& st, 227 explicit stop_callback(stop_token&& st,
227 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) 228 C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
228 : m_stop_state(move(st.m_stop_state)) { 229 : m_stop_state(std::move(st.m_stop_state)) {
229 if (m_stop_state) { 230 if (m_stop_state) {
230 m_callback = m_stop_state->insert_callback(move(cb)); 231 m_callback = m_stop_state->insert_callback(std::move(cb));
231 } 232 }
232 } 233 }
233 ~stop_callback() { 234 ~stop_callback() {
@@ -260,7 +261,7 @@ public:
260 typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>> 261 typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
261 explicit jthread(F&& f, Args&&... args) 262 explicit jthread(F&& f, Args&&... args)
262 : m_stop_state(make_shared<polyfill::stop_state>()), 263 : m_stop_state(make_shared<polyfill::stop_state>()),
263 m_thread(make_thread(move(f), move(args)...)) {} 264 m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {}
264 265
265 ~jthread() { 266 ~jthread() {
266 if (joinable()) { 267 if (joinable()) {
@@ -317,9 +318,9 @@ private:
317 template <typename F, typename... Args> 318 template <typename F, typename... Args>
318 thread make_thread(F&& f, Args&&... args) { 319 thread make_thread(F&& f, Args&&... args) {
319 if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) { 320 if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
320 return thread(move(f), get_stop_token(), move(args)...); 321 return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
321 } else { 322 } else {
322 return thread(move(f), move(args)...); 323 return thread(std::forward<F>(f), std::forward<Args>(args)...);
323 } 324 }
324 } 325 }
325 326
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 3fde3cae6..98b43e49c 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -45,6 +45,7 @@ SWITCHABLE(CpuAccuracy, true);
45SWITCHABLE(FullscreenMode, true); 45SWITCHABLE(FullscreenMode, true);
46SWITCHABLE(GpuAccuracy, true); 46SWITCHABLE(GpuAccuracy, true);
47SWITCHABLE(Language, true); 47SWITCHABLE(Language, true);
48SWITCHABLE(MemoryLayout, true);
48SWITCHABLE(NvdecEmulation, false); 49SWITCHABLE(NvdecEmulation, false);
49SWITCHABLE(Region, true); 50SWITCHABLE(Region, true);
50SWITCHABLE(RendererBackend, true); 51SWITCHABLE(RendererBackend, true);
@@ -61,6 +62,10 @@ SWITCHABLE(u32, false);
61SWITCHABLE(u8, false); 62SWITCHABLE(u8, false);
62SWITCHABLE(u8, true); 63SWITCHABLE(u8, true);
63 64
65// Used in UISettings
66// TODO see if we can move this to uisettings.cpp
67SWITCHABLE(ConfirmStop, true);
68
64#undef SETTING 69#undef SETTING
65#undef SWITCHABLE 70#undef SWITCHABLE
66#endif 71#endif
diff --git a/src/common/settings.h b/src/common/settings.h
index 98ab0ec2e..236e33bee 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -67,6 +67,7 @@ SWITCHABLE(CpuAccuracy, true);
67SWITCHABLE(FullscreenMode, true); 67SWITCHABLE(FullscreenMode, true);
68SWITCHABLE(GpuAccuracy, true); 68SWITCHABLE(GpuAccuracy, true);
69SWITCHABLE(Language, true); 69SWITCHABLE(Language, true);
70SWITCHABLE(MemoryLayout, true);
70SWITCHABLE(NvdecEmulation, false); 71SWITCHABLE(NvdecEmulation, false);
71SWITCHABLE(Region, true); 72SWITCHABLE(Region, true);
72SWITCHABLE(RendererBackend, true); 73SWITCHABLE(RendererBackend, true);
@@ -83,6 +84,10 @@ SWITCHABLE(u32, false);
83SWITCHABLE(u8, false); 84SWITCHABLE(u8, false);
84SWITCHABLE(u8, true); 85SWITCHABLE(u8, true);
85 86
87// Used in UISettings
88// TODO see if we can move this to uisettings.h
89SWITCHABLE(ConfirmStop, true);
90
86#undef SETTING 91#undef SETTING
87#undef SWITCHABLE 92#undef SWITCHABLE
88#endif 93#endif
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index 815cafe15..11429d7a8 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -133,6 +133,8 @@ ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
133 133
134ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb); 134ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
135 135
136ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
137
136ENUM(FullscreenMode, Borderless, Exclusive); 138ENUM(FullscreenMode, Borderless, Exclusive);
137 139
138ENUM(NvdecEmulation, Off, Cpu, Gpu); 140ENUM(NvdecEmulation, Off, Cpu, Gpu);
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index feab1653d..4c7aba3f5 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -135,6 +135,11 @@ std::u16string UTF8ToUTF16(std::string_view input) {
135 return convert.from_bytes(input.data(), input.data() + input.size()); 135 return convert.from_bytes(input.data(), input.data() + input.size());
136} 136}
137 137
138std::u32string UTF8ToUTF32(std::string_view input) {
139 std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
140 return convert.from_bytes(input.data(), input.data() + input.size());
141}
142
138#ifdef _WIN32 143#ifdef _WIN32
139static std::wstring CPToUTF16(u32 code_page, std::string_view input) { 144static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
140 const auto size = 145 const auto size =
diff --git a/src/common/string_util.h b/src/common/string_util.h
index c351f1a0c..9da1ca4e9 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -38,6 +38,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
38 38
39[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input); 39[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
40[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input); 40[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
41[[nodiscard]] std::u32string UTF8ToUTF32(std::string_view input);
41 42
42#ifdef _WIN32 43#ifdef _WIN32
43[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input); 44[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
index 71e15ab4c..caca9a123 100644
--- a/src/common/wall_clock.cpp
+++ b/src/common/wall_clock.cpp
@@ -10,6 +10,10 @@
10#include "common/x64/rdtsc.h" 10#include "common/x64/rdtsc.h"
11#endif 11#endif
12 12
13#if defined(ARCHITECTURE_arm64) && defined(__linux__)
14#include "common/arm64/native_clock.h"
15#endif
16
13namespace Common { 17namespace Common {
14 18
15class StandardWallClock final : public WallClock { 19class StandardWallClock final : public WallClock {
@@ -53,7 +57,7 @@ private:
53}; 57};
54 58
55std::unique_ptr<WallClock> CreateOptimalClock() { 59std::unique_ptr<WallClock> CreateOptimalClock() {
56#ifdef ARCHITECTURE_x86_64 60#if defined(ARCHITECTURE_x86_64)
57 const auto& caps = GetCPUCaps(); 61 const auto& caps = GetCPUCaps();
58 62
59 if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) { 63 if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
@@ -64,6 +68,8 @@ std::unique_ptr<WallClock> CreateOptimalClock() {
64 // - Is not more precise than 1 GHz (1ns resolution) 68 // - Is not more precise than 1 GHz (1ns resolution)
65 return std::make_unique<StandardWallClock>(); 69 return std::make_unique<StandardWallClock>();
66 } 70 }
71#elif defined(ARCHITECTURE_arm64) && defined(__linux__)
72 return std::make_unique<Arm64::NativeClock>();
67#else 73#else
68 return std::make_unique<StandardWallClock>(); 74 return std::make_unique<StandardWallClock>();
69#endif 75#endif
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index e02ededfc..e4f499135 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -466,14 +466,18 @@ add_library(core STATIC
466 hle/service/caps/caps_a.h 466 hle/service/caps/caps_a.h
467 hle/service/caps/caps_c.cpp 467 hle/service/caps/caps_c.cpp
468 hle/service/caps/caps_c.h 468 hle/service/caps/caps_c.h
469 hle/service/caps/caps_u.cpp 469 hle/service/caps/caps_manager.cpp
470 hle/service/caps/caps_u.h 470 hle/service/caps/caps_manager.h
471 hle/service/caps/caps_result.h
471 hle/service/caps/caps_sc.cpp 472 hle/service/caps/caps_sc.cpp
472 hle/service/caps/caps_sc.h 473 hle/service/caps/caps_sc.h
473 hle/service/caps/caps_ss.cpp 474 hle/service/caps/caps_ss.cpp
474 hle/service/caps/caps_ss.h 475 hle/service/caps/caps_ss.h
475 hle/service/caps/caps_su.cpp 476 hle/service/caps/caps_su.cpp
476 hle/service/caps/caps_su.h 477 hle/service/caps/caps_su.h
478 hle/service/caps/caps_types.h
479 hle/service/caps/caps_u.cpp
480 hle/service/caps/caps_u.h
477 hle/service/erpt/erpt.cpp 481 hle/service/erpt/erpt.cpp
478 hle/service/erpt/erpt.h 482 hle/service/erpt/erpt.h
479 hle/service/es/es.cpp 483 hle/service/es/es.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 08cbb8978..d7e2efbd7 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -116,11 +116,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
116 } 116 }
117 } 117 }
118 118
119 if (concat.empty()) { 119 return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(dir->GetName(),
120 return nullptr; 120 std::move(concat));
121 }
122
123 return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
124 } 121 }
125 122
126 if (Common::FS::IsDir(path)) { 123 if (Common::FS::IsDir(path)) {
@@ -1078,6 +1075,10 @@ void System::ApplySettings() {
1078 impl->RefreshTime(); 1075 impl->RefreshTime();
1079 1076
1080 if (IsPoweredOn()) { 1077 if (IsPoweredOn()) {
1078 if (Settings::values.custom_rtc_enabled) {
1079 const s64 posix_time{Settings::values.custom_rtc.GetValue()};
1080 GetTimeManager().UpdateLocalSystemClockTime(posix_time);
1081 }
1081 Renderer().RefreshBaseSettings(); 1082 Renderer().RefreshBaseSettings();
1082 } 1083 }
1083} 1084}
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index e55831f27..82964f0a1 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -2,6 +2,8 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <atomic> 4#include <atomic>
5#include <codecvt>
6#include <locale>
5#include <numeric> 7#include <numeric>
6#include <optional> 8#include <optional>
7#include <thread> 9#include <thread>
@@ -12,6 +14,7 @@
12#include "common/logging/log.h" 14#include "common/logging/log.h"
13#include "common/scope_exit.h" 15#include "common/scope_exit.h"
14#include "common/settings.h" 16#include "common/settings.h"
17#include "common/string_util.h"
15#include "core/arm/arm_interface.h" 18#include "core/arm/arm_interface.h"
16#include "core/core.h" 19#include "core/core.h"
17#include "core/debugger/gdbstub.h" 20#include "core/debugger/gdbstub.h"
@@ -68,10 +71,16 @@ static std::string EscapeGDB(std::string_view data) {
68} 71}
69 72
70static std::string EscapeXML(std::string_view data) { 73static std::string EscapeXML(std::string_view data) {
74 std::u32string converted = U"[Encoding error]";
75 try {
76 converted = Common::UTF8ToUTF32(data);
77 } catch (std::range_error&) {
78 }
79
71 std::string escaped; 80 std::string escaped;
72 escaped.reserve(data.size()); 81 escaped.reserve(data.size());
73 82
74 for (char c : data) { 83 for (char32_t c : converted) {
75 switch (c) { 84 switch (c) {
76 case '&': 85 case '&':
77 escaped += "&amp;"; 86 escaped += "&amp;";
@@ -86,7 +95,11 @@ static std::string EscapeXML(std::string_view data) {
86 escaped += "&gt;"; 95 escaped += "&gt;";
87 break; 96 break;
88 default: 97 default:
89 escaped += c; 98 if (c > 0x7f) {
99 escaped += fmt::format("&#{};", static_cast<u32>(c));
100 } else {
101 escaped += static_cast<char>(c);
102 }
90 break; 103 break;
91 } 104 }
92 } 105 }
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
index e39c7b62b..f1d3e4129 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.cpp
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -107,62 +107,56 @@ static u64 romfs_get_hash_table_count(u64 num_entries) {
107 107
108void RomFSBuildContext::VisitDirectory(VirtualDir romfs_dir, VirtualDir ext_dir, 108void RomFSBuildContext::VisitDirectory(VirtualDir romfs_dir, VirtualDir ext_dir,
109 std::shared_ptr<RomFSBuildDirectoryContext> parent) { 109 std::shared_ptr<RomFSBuildDirectoryContext> parent) {
110 std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs; 110 for (auto& child_romfs_file : romfs_dir->GetFiles()) {
111 const auto name = child_romfs_file->GetName();
112 const auto child = std::make_shared<RomFSBuildFileContext>();
113 // Set child's path.
114 child->cur_path_ofs = parent->path_len + 1;
115 child->path_len = child->cur_path_ofs + static_cast<u32>(name.size());
116 child->path = parent->path + "/" + name;
117
118 if (ext_dir != nullptr && ext_dir->GetFile(name + ".stub") != nullptr) {
119 continue;
120 }
111 121
112 const auto entries = romfs_dir->GetEntries(); 122 // Sanity check on path_len
123 ASSERT(child->path_len < FS_MAX_PATH);
113 124
114 for (const auto& kv : entries) { 125 child->source = std::move(child_romfs_file);
115 if (kv.second == VfsEntryType::Directory) {
116 const auto child = std::make_shared<RomFSBuildDirectoryContext>();
117 // Set child's path.
118 child->cur_path_ofs = parent->path_len + 1;
119 child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
120 child->path = parent->path + "/" + kv.first;
121 126
122 if (ext_dir != nullptr && ext_dir->GetFile(kv.first + ".stub") != nullptr) { 127 if (ext_dir != nullptr) {
123 continue; 128 if (const auto ips = ext_dir->GetFile(name + ".ips")) {
129 if (auto patched = PatchIPS(child->source, ips)) {
130 child->source = std::move(patched);
131 }
124 } 132 }
133 }
125 134
126 // Sanity check on path_len 135 child->size = child->source->GetSize();
127 ASSERT(child->path_len < FS_MAX_PATH);
128
129 if (AddDirectory(parent, child)) {
130 child_dirs.push_back(child);
131 }
132 } else {
133 const auto child = std::make_shared<RomFSBuildFileContext>();
134 // Set child's path.
135 child->cur_path_ofs = parent->path_len + 1;
136 child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
137 child->path = parent->path + "/" + kv.first;
138
139 if (ext_dir != nullptr && ext_dir->GetFile(kv.first + ".stub") != nullptr) {
140 continue;
141 }
142 136
143 // Sanity check on path_len 137 AddFile(parent, child);
144 ASSERT(child->path_len < FS_MAX_PATH); 138 }
145 139
146 child->source = romfs_dir->GetFile(kv.first); 140 for (auto& child_romfs_dir : romfs_dir->GetSubdirectories()) {
141 const auto name = child_romfs_dir->GetName();
142 const auto child = std::make_shared<RomFSBuildDirectoryContext>();
143 // Set child's path.
144 child->cur_path_ofs = parent->path_len + 1;
145 child->path_len = child->cur_path_ofs + static_cast<u32>(name.size());
146 child->path = parent->path + "/" + name;
147 147
148 if (ext_dir != nullptr) { 148 if (ext_dir != nullptr && ext_dir->GetFile(name + ".stub") != nullptr) {
149 if (const auto ips = ext_dir->GetFile(kv.first + ".ips")) { 149 continue;
150 if (auto patched = PatchIPS(child->source, ips)) { 150 }
151 child->source = std::move(patched);
152 }
153 }
154 }
155 151
156 child->size = child->source->GetSize(); 152 // Sanity check on path_len
153 ASSERT(child->path_len < FS_MAX_PATH);
157 154
158 AddFile(parent, child); 155 if (!AddDirectory(parent, child)) {
156 continue;
159 } 157 }
160 }
161 158
162 for (auto& child : child_dirs) { 159 auto child_ext_dir = ext_dir != nullptr ? ext_dir->GetSubdirectory(name) : nullptr;
163 auto subdir_name = std::string_view(child->path).substr(child->cur_path_ofs);
164 auto child_romfs_dir = romfs_dir->GetSubdirectory(subdir_name);
165 auto child_ext_dir = ext_dir != nullptr ? ext_dir->GetSubdirectory(subdir_name) : nullptr;
166 this->VisitDirectory(child_romfs_dir, child_ext_dir, child); 160 this->VisitDirectory(child_romfs_dir, child_ext_dir, child);
167 } 161 }
168} 162}
@@ -293,7 +287,7 @@ std::multimap<u64, VirtualFile> RomFSBuildContext::Build() {
293 287
294 cur_entry.name_size = name_size; 288 cur_entry.name_size = name_size;
295 289
296 out.emplace(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->source); 290 out.emplace(cur_file->offset + ROMFS_FILEPARTITION_OFS, std::move(cur_file->source));
297 std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry)); 291 std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry));
298 std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0, 292 std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0,
299 Common::AlignUp(cur_entry.name_size, 4)); 293 Common::AlignUp(cur_entry.name_size, 4));
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 8e475f25a..0bca05587 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -377,16 +377,16 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
377 377
378 auto romfs_dir = FindSubdirectoryCaseless(subdir, "romfs"); 378 auto romfs_dir = FindSubdirectoryCaseless(subdir, "romfs");
379 if (romfs_dir != nullptr) 379 if (romfs_dir != nullptr)
380 layers.push_back(std::make_shared<CachedVfsDirectory>(romfs_dir)); 380 layers.emplace_back(std::make_shared<CachedVfsDirectory>(std::move(romfs_dir)));
381 381
382 auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext"); 382 auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext");
383 if (ext_dir != nullptr) 383 if (ext_dir != nullptr)
384 layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir)); 384 layers_ext.emplace_back(std::make_shared<CachedVfsDirectory>(std::move(ext_dir)));
385 385
386 if (type == ContentRecordType::HtmlDocument) { 386 if (type == ContentRecordType::HtmlDocument) {
387 auto manual_dir = FindSubdirectoryCaseless(subdir, "manual_html"); 387 auto manual_dir = FindSubdirectoryCaseless(subdir, "manual_html");
388 if (manual_dir != nullptr) 388 if (manual_dir != nullptr)
389 layers.push_back(std::make_shared<CachedVfsDirectory>(manual_dir)); 389 layers.emplace_back(std::make_shared<CachedVfsDirectory>(std::move(manual_dir)));
390 } 390 }
391 } 391 }
392 392
@@ -400,7 +400,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
400 return; 400 return;
401 } 401 }
402 402
403 layers.push_back(std::move(extracted)); 403 layers.emplace_back(std::move(extracted));
404 404
405 auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); 405 auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
406 if (layered == nullptr) { 406 if (layered == nullptr) {
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index f00479bd3..8e291ff67 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -5,6 +5,7 @@
5#include <vector> 5#include <vector>
6 6
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "common/scope_exit.h"
8#include "core/file_sys/program_metadata.h" 9#include "core/file_sys/program_metadata.h"
9#include "core/file_sys/vfs.h" 10#include "core/file_sys/vfs.h"
10#include "core/loader/loader.h" 11#include "core/loader/loader.h"
@@ -95,6 +96,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
95 return Loader::ResultStatus::Success; 96 return Loader::ResultStatus::Success;
96} 97}
97 98
99Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
100 const u64 original_program_id = aci_header.title_id;
101 SCOPE_EXIT({ aci_header.title_id = original_program_id; });
102
103 return this->Load(file);
104}
105
98/*static*/ ProgramMetadata ProgramMetadata::GetDefault() { 106/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
99 // Allow use of cores 0~3 and thread priorities 1~63. 107 // Allow use of cores 0~3 and thread priorities 1~63.
100 constexpr u32 default_thread_info_capability = 0x30007F7; 108 constexpr u32 default_thread_info_capability = 0x30007F7;
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 2e8960b07..9f8e74b13 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -56,6 +56,7 @@ public:
56 static ProgramMetadata GetDefault(); 56 static ProgramMetadata GetDefault();
57 57
58 Loader::ResultStatus Load(VirtualFile file); 58 Loader::ResultStatus Load(VirtualFile file);
59 Loader::ResultStatus Reload(VirtualFile file);
59 60
60 /// Load from parameters instead of NPDM file, used for KIP 61 /// Load from parameters instead of NPDM file, used for KIP
61 void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio, 62 void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio,
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 04da93d5c..1cc77ad14 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -322,7 +322,8 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& open_di
322 return nullptr; 322 return nullptr;
323 } 323 }
324 324
325 return ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName()); 325 auto name = concat.front()->GetName();
326 return ConcatenatedVfsFile::MakeConcatenatedFile(std::move(name), std::move(concat));
326} 327}
327 328
328VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { 329VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index 614da2130..1c580de57 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -133,7 +133,7 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
133 out = out->GetSubdirectories().front(); 133 out = out->GetSubdirectories().front();
134 } 134 }
135 135
136 return std::make_shared<CachedVfsDirectory>(out); 136 return std::make_shared<CachedVfsDirectory>(std::move(out));
137} 137}
138 138
139VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) { 139VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
@@ -141,8 +141,7 @@ VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
141 return nullptr; 141 return nullptr;
142 142
143 RomFSBuildContext ctx{dir, ext}; 143 RomFSBuildContext ctx{dir, ext};
144 auto file_map = ctx.Build(); 144 return ConcatenatedVfsFile::MakeConcatenatedFile(0, dir->GetName(), ctx.Build());
145 return ConcatenatedVfsFile::MakeConcatenatedFile(0, file_map, dir->GetName());
146} 145}
147 146
148} // namespace FileSys 147} // namespace FileSys
diff --git a/src/core/file_sys/vfs_cached.cpp b/src/core/file_sys/vfs_cached.cpp
index c3154ee81..7ee5300e5 100644
--- a/src/core/file_sys/vfs_cached.cpp
+++ b/src/core/file_sys/vfs_cached.cpp
@@ -6,13 +6,13 @@
6 6
7namespace FileSys { 7namespace FileSys {
8 8
9CachedVfsDirectory::CachedVfsDirectory(VirtualDir& source_dir) 9CachedVfsDirectory::CachedVfsDirectory(VirtualDir&& source_dir)
10 : name(source_dir->GetName()), parent(source_dir->GetParentDirectory()) { 10 : name(source_dir->GetName()), parent(source_dir->GetParentDirectory()) {
11 for (auto& dir : source_dir->GetSubdirectories()) { 11 for (auto& dir : source_dir->GetSubdirectories()) {
12 dirs.emplace(dir->GetName(), std::make_shared<CachedVfsDirectory>(dir)); 12 dirs.emplace(dir->GetName(), std::make_shared<CachedVfsDirectory>(std::move(dir)));
13 } 13 }
14 for (auto& file : source_dir->GetFiles()) { 14 for (auto& file : source_dir->GetFiles()) {
15 files.emplace(file->GetName(), file); 15 files.emplace(file->GetName(), std::move(file));
16 } 16 }
17} 17}
18 18
diff --git a/src/core/file_sys/vfs_cached.h b/src/core/file_sys/vfs_cached.h
index 113acac12..1e5300784 100644
--- a/src/core/file_sys/vfs_cached.h
+++ b/src/core/file_sys/vfs_cached.h
@@ -11,7 +11,7 @@ namespace FileSys {
11 11
12class CachedVfsDirectory : public ReadOnlyVfsDirectory { 12class CachedVfsDirectory : public ReadOnlyVfsDirectory {
13public: 13public:
14 CachedVfsDirectory(VirtualDir& source_directory); 14 CachedVfsDirectory(VirtualDir&& source_directory);
15 15
16 ~CachedVfsDirectory() override; 16 ~CachedVfsDirectory() override;
17 VirtualFile GetFile(std::string_view file_name) const override; 17 VirtualFile GetFile(std::string_view file_name) const override;
diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp
index 311a59e5f..168b9cbec 100644
--- a/src/core/file_sys/vfs_concat.cpp
+++ b/src/core/file_sys/vfs_concat.cpp
@@ -10,7 +10,7 @@
10 10
11namespace FileSys { 11namespace FileSys {
12 12
13ConcatenatedVfsFile::ConcatenatedVfsFile(ConcatenationMap&& concatenation_map_, std::string&& name_) 13ConcatenatedVfsFile::ConcatenatedVfsFile(std::string&& name_, ConcatenationMap&& concatenation_map_)
14 : concatenation_map(std::move(concatenation_map_)), name(std::move(name_)) { 14 : concatenation_map(std::move(concatenation_map_)), name(std::move(name_)) {
15 DEBUG_ASSERT(this->VerifyContinuity()); 15 DEBUG_ASSERT(this->VerifyContinuity());
16} 16}
@@ -30,8 +30,8 @@ bool ConcatenatedVfsFile::VerifyContinuity() const {
30 30
31ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; 31ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
32 32
33VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(const std::vector<VirtualFile>& files, 33VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::string&& name,
34 std::string&& name) { 34 std::vector<VirtualFile>&& files) {
35 // Fold trivial cases. 35 // Fold trivial cases.
36 if (files.empty()) { 36 if (files.empty()) {
37 return nullptr; 37 return nullptr;
@@ -46,20 +46,21 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(const std::vector<VirtualF
46 u64 last_offset = 0; 46 u64 last_offset = 0;
47 47
48 for (auto& file : files) { 48 for (auto& file : files) {
49 const auto size = file->GetSize();
50
49 concatenation_map.emplace_back(ConcatenationEntry{ 51 concatenation_map.emplace_back(ConcatenationEntry{
50 .offset = last_offset, 52 .offset = last_offset,
51 .file = file, 53 .file = std::move(file),
52 }); 54 });
53 55
54 last_offset += file->GetSize(); 56 last_offset += size;
55 } 57 }
56 58
57 return VirtualFile(new ConcatenatedVfsFile(std::move(concatenation_map), std::move(name))); 59 return VirtualFile(new ConcatenatedVfsFile(std::move(name), std::move(concatenation_map)));
58} 60}
59 61
60VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte, 62VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte, std::string&& name,
61 const std::multimap<u64, VirtualFile>& files, 63 std::multimap<u64, VirtualFile>&& files) {
62 std::string&& name) {
63 // Fold trivial cases. 64 // Fold trivial cases.
64 if (files.empty()) { 65 if (files.empty()) {
65 return nullptr; 66 return nullptr;
@@ -76,6 +77,8 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
76 77
77 // Iteration of a multimap is ordered, so offset will be strictly non-decreasing. 78 // Iteration of a multimap is ordered, so offset will be strictly non-decreasing.
78 for (auto& [offset, file] : files) { 79 for (auto& [offset, file] : files) {
80 const auto size = file->GetSize();
81
79 if (offset > last_offset) { 82 if (offset > last_offset) {
80 concatenation_map.emplace_back(ConcatenationEntry{ 83 concatenation_map.emplace_back(ConcatenationEntry{
81 .offset = last_offset, 84 .offset = last_offset,
@@ -85,13 +88,13 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
85 88
86 concatenation_map.emplace_back(ConcatenationEntry{ 89 concatenation_map.emplace_back(ConcatenationEntry{
87 .offset = offset, 90 .offset = offset,
88 .file = file, 91 .file = std::move(file),
89 }); 92 });
90 93
91 last_offset = offset + file->GetSize(); 94 last_offset = offset + size;
92 } 95 }
93 96
94 return VirtualFile(new ConcatenatedVfsFile(std::move(concatenation_map), std::move(name))); 97 return VirtualFile(new ConcatenatedVfsFile(std::move(name), std::move(concatenation_map)));
95} 98}
96 99
97std::string ConcatenatedVfsFile::GetName() const { 100std::string ConcatenatedVfsFile::GetName() const {
diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h
index 6b329d545..cbddd12bd 100644
--- a/src/core/file_sys/vfs_concat.h
+++ b/src/core/file_sys/vfs_concat.h
@@ -24,22 +24,20 @@ private:
24 }; 24 };
25 using ConcatenationMap = std::vector<ConcatenationEntry>; 25 using ConcatenationMap = std::vector<ConcatenationEntry>;
26 26
27 explicit ConcatenatedVfsFile(std::vector<ConcatenationEntry>&& concatenation_map, 27 explicit ConcatenatedVfsFile(std::string&& name,
28 std::string&& name); 28 std::vector<ConcatenationEntry>&& concatenation_map);
29 bool VerifyContinuity() const; 29 bool VerifyContinuity() const;
30 30
31public: 31public:
32 ~ConcatenatedVfsFile() override; 32 ~ConcatenatedVfsFile() override;
33 33
34 /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases. 34 /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
35 static VirtualFile MakeConcatenatedFile(const std::vector<VirtualFile>& files, 35 static VirtualFile MakeConcatenatedFile(std::string&& name, std::vector<VirtualFile>&& files);
36 std::string&& name);
37 36
38 /// Convenience function that turns a map of offsets to files into a concatenated file, filling 37 /// Convenience function that turns a map of offsets to files into a concatenated file, filling
39 /// gaps with a given filler byte. 38 /// gaps with a given filler byte.
40 static VirtualFile MakeConcatenatedFile(u8 filler_byte, 39 static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::string&& name,
41 const std::multimap<u64, VirtualFile>& files, 40 std::multimap<u64, VirtualFile>&& files);
42 std::string&& name);
43 41
44 std::string GetName() const override; 42 std::string GetName() const override;
45 std::size_t GetSize() const override; 43 std::size_t GetSize() const override;
diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp
index 3e6426afc..08daca397 100644
--- a/src/core/file_sys/vfs_layered.cpp
+++ b/src/core/file_sys/vfs_layered.cpp
@@ -38,7 +38,7 @@ VirtualDir LayeredVfsDirectory::GetDirectoryRelative(std::string_view path) cons
38 for (const auto& layer : dirs) { 38 for (const auto& layer : dirs) {
39 auto dir = layer->GetDirectoryRelative(path); 39 auto dir = layer->GetDirectoryRelative(path);
40 if (dir != nullptr) { 40 if (dir != nullptr) {
41 out.push_back(std::move(dir)); 41 out.emplace_back(std::move(dir));
42 } 42 }
43 } 43 }
44 44
@@ -62,11 +62,11 @@ std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
62 std::set<std::string, std::less<>> out_names; 62 std::set<std::string, std::less<>> out_names;
63 63
64 for (const auto& layer : dirs) { 64 for (const auto& layer : dirs) {
65 for (const auto& file : layer->GetFiles()) { 65 for (auto& file : layer->GetFiles()) {
66 auto file_name = file->GetName(); 66 auto file_name = file->GetName();
67 if (!out_names.contains(file_name)) { 67 if (!out_names.contains(file_name)) {
68 out_names.emplace(std::move(file_name)); 68 out_names.emplace(std::move(file_name));
69 out.push_back(file); 69 out.emplace_back(std::move(file));
70 } 70 }
71 } 71 }
72 } 72 }
@@ -86,7 +86,7 @@ std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const {
86 std::vector<VirtualDir> out; 86 std::vector<VirtualDir> out;
87 out.reserve(names.size()); 87 out.reserve(names.size());
88 for (const auto& subdir : names) 88 for (const auto& subdir : names)
89 out.push_back(GetSubdirectory(subdir)); 89 out.emplace_back(GetSubdirectory(subdir));
90 90
91 return out; 91 return out;
92} 92}
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 5b51edf30..0b0cef984 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -2949,6 +2949,23 @@ Result KPageTable::UnlockForIpcUserBuffer(KProcessAddress address, size_t size)
2949 KMemoryAttribute::Locked, nullptr)); 2949 KMemoryAttribute::Locked, nullptr));
2950} 2950}
2951 2951
2952Result KPageTable::LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
2953 KMemoryPermission perm) {
2954 R_RETURN(this->LockMemoryAndOpen(out, nullptr, address, size, KMemoryState::FlagCanTransfer,
2955 KMemoryState::FlagCanTransfer, KMemoryPermission::All,
2956 KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
2957 KMemoryAttribute::None, perm, KMemoryAttribute::Locked));
2958}
2959
2960Result KPageTable::UnlockForTransferMemory(KProcessAddress address, size_t size,
2961 const KPageGroup& pg) {
2962 R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanTransfer,
2963 KMemoryState::FlagCanTransfer, KMemoryPermission::None,
2964 KMemoryPermission::None, KMemoryAttribute::All,
2965 KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
2966 KMemoryAttribute::Locked, std::addressof(pg)));
2967}
2968
2952Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) { 2969Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) {
2953 R_RETURN(this->LockMemoryAndOpen( 2970 R_RETURN(this->LockMemoryAndOpen(
2954 out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, 2971 out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
@@ -3388,6 +3405,11 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_K
3388 new_attr, KMemoryBlockDisableMergeAttribute::Locked, 3405 new_attr, KMemoryBlockDisableMergeAttribute::Locked,
3389 KMemoryBlockDisableMergeAttribute::None); 3406 KMemoryBlockDisableMergeAttribute::None);
3390 3407
3408 // If we have an output page group, open.
3409 if (out_pg) {
3410 out_pg->Open();
3411 }
3412
3391 R_SUCCEED(); 3413 R_SUCCEED();
3392} 3414}
3393 3415
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index b9e8c6042..7da675f27 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -104,6 +104,9 @@ public:
104 Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state); 104 Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state);
105 Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state); 105 Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state);
106 106
107 Result LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
108 KMemoryPermission perm);
109 Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup& pg);
107 Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size); 110 Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size);
108 Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg); 111 Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg);
109 Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages, 112 Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages,
diff --git a/src/core/hle/kernel/k_transfer_memory.cpp b/src/core/hle/kernel/k_transfer_memory.cpp
index 13d34125c..0e2e11743 100644
--- a/src/core/hle/kernel/k_transfer_memory.cpp
+++ b/src/core/hle/kernel/k_transfer_memory.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/scope_exit.h"
4#include "core/hle/kernel/k_process.h" 5#include "core/hle/kernel/k_process.h"
5#include "core/hle/kernel/k_resource_limit.h" 6#include "core/hle/kernel/k_resource_limit.h"
6#include "core/hle/kernel/k_transfer_memory.h" 7#include "core/hle/kernel/k_transfer_memory.h"
@@ -9,28 +10,50 @@
9namespace Kernel { 10namespace Kernel {
10 11
11KTransferMemory::KTransferMemory(KernelCore& kernel) 12KTransferMemory::KTransferMemory(KernelCore& kernel)
12 : KAutoObjectWithSlabHeapAndContainer{kernel} {} 13 : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{kernel} {}
13 14
14KTransferMemory::~KTransferMemory() = default; 15KTransferMemory::~KTransferMemory() = default;
15 16
16Result KTransferMemory::Initialize(KProcessAddress address, std::size_t size, 17Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size,
17 Svc::MemoryPermission owner_perm) { 18 Svc::MemoryPermission own_perm) {
18 // Set members. 19 // Set members.
19 m_owner = GetCurrentProcessPointer(m_kernel); 20 m_owner = GetCurrentProcessPointer(m_kernel);
20 21
21 // TODO(bunnei): Lock for transfer memory 22 // Get the owner page table.
23 auto& page_table = m_owner->GetPageTable();
24
25 // Construct the page group, guarding to make sure our state is valid on exit.
26 m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager());
27 auto pg_guard = SCOPE_GUARD({ m_page_group.reset(); });
28
29 // Lock the memory.
30 R_TRY(page_table.LockForTransferMemory(std::addressof(*m_page_group), addr, size,
31 ConvertToKMemoryPermission(own_perm)));
22 32
23 // Set remaining tracking members. 33 // Set remaining tracking members.
24 m_owner->Open(); 34 m_owner->Open();
25 m_owner_perm = owner_perm; 35 m_owner_perm = own_perm;
26 m_address = address; 36 m_address = addr;
27 m_size = size;
28 m_is_initialized = true; 37 m_is_initialized = true;
38 m_is_mapped = false;
29 39
40 // We succeeded.
41 pg_guard.Cancel();
30 R_SUCCEED(); 42 R_SUCCEED();
31} 43}
32 44
33void KTransferMemory::Finalize() {} 45void KTransferMemory::Finalize() {
46 // Unlock.
47 if (!m_is_mapped) {
48 const size_t size = m_page_group->GetNumPages() * PageSize;
49 ASSERT(R_SUCCEEDED(
50 m_owner->GetPageTable().UnlockForTransferMemory(m_address, size, *m_page_group)));
51 }
52
53 // Close the page group.
54 m_page_group->Close();
55 m_page_group->Finalize();
56}
34 57
35void KTransferMemory::PostDestroy(uintptr_t arg) { 58void KTransferMemory::PostDestroy(uintptr_t arg) {
36 KProcess* owner = reinterpret_cast<KProcess*>(arg); 59 KProcess* owner = reinterpret_cast<KProcess*>(arg);
@@ -38,4 +61,54 @@ void KTransferMemory::PostDestroy(uintptr_t arg) {
38 owner->Close(); 61 owner->Close();
39} 62}
40 63
64Result KTransferMemory::Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm) {
65 // Validate the size.
66 R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
67
68 // Validate the permission.
69 R_UNLESS(m_owner_perm == map_perm, ResultInvalidState);
70
71 // Lock ourselves.
72 KScopedLightLock lk(m_lock);
73
74 // Ensure we're not already mapped.
75 R_UNLESS(!m_is_mapped, ResultInvalidState);
76
77 // Map the memory.
78 const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
79 ? KMemoryState::Transfered
80 : KMemoryState::SharedTransfered;
81 R_TRY(GetCurrentProcess(m_kernel).GetPageTable().MapPageGroup(
82 address, *m_page_group, state, KMemoryPermission::UserReadWrite));
83
84 // Mark ourselves as mapped.
85 m_is_mapped = true;
86
87 R_SUCCEED();
88}
89
90Result KTransferMemory::Unmap(KProcessAddress address, size_t size) {
91 // Validate the size.
92 R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
93
94 // Lock ourselves.
95 KScopedLightLock lk(m_lock);
96
97 // Unmap the memory.
98 const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
99 ? KMemoryState::Transfered
100 : KMemoryState::SharedTransfered;
101 R_TRY(GetCurrentProcess(m_kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, state));
102
103 // Mark ourselves as unmapped.
104 ASSERT(m_is_mapped);
105 m_is_mapped = false;
106
107 R_SUCCEED();
108}
109
110size_t KTransferMemory::GetSize() const {
111 return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0;
112}
113
41} // namespace Kernel 114} // namespace Kernel
diff --git a/src/core/hle/kernel/k_transfer_memory.h b/src/core/hle/kernel/k_transfer_memory.h
index 54f97ccb4..8a0b08761 100644
--- a/src/core/hle/kernel/k_transfer_memory.h
+++ b/src/core/hle/kernel/k_transfer_memory.h
@@ -3,6 +3,9 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <optional>
7
8#include "core/hle/kernel/k_page_group.h"
6#include "core/hle/kernel/slab_helpers.h" 9#include "core/hle/kernel/slab_helpers.h"
7#include "core/hle/kernel/svc_types.h" 10#include "core/hle/kernel/svc_types.h"
8#include "core/hle/result.h" 11#include "core/hle/result.h"
@@ -48,16 +51,19 @@ public:
48 return m_address; 51 return m_address;
49 } 52 }
50 53
51 size_t GetSize() const { 54 size_t GetSize() const;
52 return m_is_initialized ? m_size : 0; 55
53 } 56 Result Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm);
57 Result Unmap(KProcessAddress address, size_t size);
54 58
55private: 59private:
60 std::optional<KPageGroup> m_page_group{};
56 KProcess* m_owner{}; 61 KProcess* m_owner{};
57 KProcessAddress m_address{}; 62 KProcessAddress m_address{};
63 KLightLock m_lock;
58 Svc::MemoryPermission m_owner_perm{}; 64 Svc::MemoryPermission m_owner_perm{};
59 size_t m_size{};
60 bool m_is_initialized{}; 65 bool m_is_initialized{};
66 bool m_is_mapped{};
61}; 67};
62 68
63} // namespace Kernel 69} // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index a1134b7e2..cb025c3d6 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -373,7 +373,7 @@ struct KernelCore::Impl {
373 static inline thread_local u8 host_thread_id = UINT8_MAX; 373 static inline thread_local u8 host_thread_id = UINT8_MAX;
374 374
375 /// Sets the host thread ID for the caller. 375 /// Sets the host thread ID for the caller.
376 u32 SetHostThreadId(std::size_t core_id) { 376 LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) {
377 // This should only be called during core init. 377 // This should only be called during core init.
378 ASSERT(host_thread_id == UINT8_MAX); 378 ASSERT(host_thread_id == UINT8_MAX);
379 379
@@ -384,13 +384,13 @@ struct KernelCore::Impl {
384 } 384 }
385 385
386 /// Gets the host thread ID for the caller 386 /// Gets the host thread ID for the caller
387 u32 GetHostThreadId() const { 387 LTO_NOINLINE u32 GetHostThreadId() const {
388 return host_thread_id; 388 return host_thread_id;
389 } 389 }
390 390
391 // Gets the dummy KThread for the caller, allocating a new one if this is the first time 391 // Gets the dummy KThread for the caller, allocating a new one if this is the first time
392 KThread* GetHostDummyThread(KThread* existing_thread) { 392 LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) {
393 const auto initialize{[](KThread* thread) { 393 const auto initialize{[](KThread* thread) LTO_NOINLINE {
394 ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); 394 ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
395 return thread; 395 return thread;
396 }}; 396 }};
@@ -424,11 +424,11 @@ struct KernelCore::Impl {
424 424
425 static inline thread_local bool is_phantom_mode_for_singlecore{false}; 425 static inline thread_local bool is_phantom_mode_for_singlecore{false};
426 426
427 bool IsPhantomModeForSingleCore() const { 427 LTO_NOINLINE bool IsPhantomModeForSingleCore() const {
428 return is_phantom_mode_for_singlecore; 428 return is_phantom_mode_for_singlecore;
429 } 429 }
430 430
431 void SetIsPhantomModeForSingleCore(bool value) { 431 LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) {
432 ASSERT(!is_multicore); 432 ASSERT(!is_multicore);
433 is_phantom_mode_for_singlecore = value; 433 is_phantom_mode_for_singlecore = value;
434 } 434 }
@@ -439,14 +439,14 @@ struct KernelCore::Impl {
439 439
440 static inline thread_local KThread* current_thread{nullptr}; 440 static inline thread_local KThread* current_thread{nullptr};
441 441
442 KThread* GetCurrentEmuThread() { 442 LTO_NOINLINE KThread* GetCurrentEmuThread() {
443 if (!current_thread) { 443 if (!current_thread) {
444 current_thread = GetHostDummyThread(nullptr); 444 current_thread = GetHostDummyThread(nullptr);
445 } 445 }
446 return current_thread; 446 return current_thread;
447 } 447 }
448 448
449 void SetCurrentEmuThread(KThread* thread) { 449 LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) {
450 current_thread = thread; 450 current_thread = thread;
451 } 451 }
452 452
diff --git a/src/core/hle/kernel/svc/svc_transfer_memory.cpp b/src/core/hle/kernel/svc/svc_transfer_memory.cpp
index 7d94e7f09..1f97121b3 100644
--- a/src/core/hle/kernel/svc/svc_transfer_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_transfer_memory.cpp
@@ -71,15 +71,59 @@ Result CreateTransferMemory(Core::System& system, Handle* out, u64 address, u64
71} 71}
72 72
73Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size, 73Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size,
74 MemoryPermission owner_perm) { 74 MemoryPermission map_perm) {
75 UNIMPLEMENTED(); 75 // Validate the address/size.
76 R_THROW(ResultNotImplemented); 76 R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
77 R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
78 R_UNLESS(size > 0, ResultInvalidSize);
79 R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
80
81 // Validate the permission.
82 R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidState);
83
84 // Get the transfer memory.
85 KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
86 .GetHandleTable()
87 .GetObject<KTransferMemory>(trmem_handle);
88 R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
89
90 // Verify that the mapping is in range.
91 R_UNLESS(GetCurrentProcess(system.Kernel())
92 .GetPageTable()
93 .CanContain(address, size, KMemoryState::Transfered),
94 ResultInvalidMemoryRegion);
95
96 // Map the transfer memory.
97 R_TRY(trmem->Map(address, size, map_perm));
98
99 // We succeeded.
100 R_SUCCEED();
77} 101}
78 102
79Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, 103Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address,
80 uint64_t size) { 104 uint64_t size) {
81 UNIMPLEMENTED(); 105 // Validate the address/size.
82 R_THROW(ResultNotImplemented); 106 R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
107 R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
108 R_UNLESS(size > 0, ResultInvalidSize);
109 R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
110
111 // Get the transfer memory.
112 KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
113 .GetHandleTable()
114 .GetObject<KTransferMemory>(trmem_handle);
115 R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
116
117 // Verify that the mapping is in range.
118 R_UNLESS(GetCurrentProcess(system.Kernel())
119 .GetPageTable()
120 .CanContain(address, size, KMemoryState::Transfered),
121 ResultInvalidMemoryRegion);
122
123 // Unmap the transfer memory.
124 R_TRY(trmem->Unmap(address, size));
125
126 R_SUCCEED();
83} 127}
84 128
85Result MapTransferMemory64(Core::System& system, Handle trmem_handle, uint64_t address, 129Result MapTransferMemory64(Core::System& system, Handle trmem_handle, uint64_t address,
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index b971401e6..1b1c8190e 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -49,7 +49,7 @@ public:
49 : ServiceFramework{system_, "IManagerForSystemService"} { 49 : ServiceFramework{system_, "IManagerForSystemService"} {
50 // clang-format off 50 // clang-format off
51 static const FunctionInfo functions[] = { 51 static const FunctionInfo functions[] = {
52 {0, nullptr, "CheckAvailability"}, 52 {0, &IManagerForSystemService::CheckAvailability, "CheckAvailability"},
53 {1, nullptr, "GetAccountId"}, 53 {1, nullptr, "GetAccountId"},
54 {2, nullptr, "EnsureIdTokenCacheAsync"}, 54 {2, nullptr, "EnsureIdTokenCacheAsync"},
55 {3, nullptr, "LoadIdTokenCache"}, 55 {3, nullptr, "LoadIdTokenCache"},
@@ -78,6 +78,13 @@ public:
78 78
79 RegisterHandlers(functions); 79 RegisterHandlers(functions);
80 } 80 }
81
82private:
83 void CheckAvailability(HLERequestContext& ctx) {
84 LOG_WARNING(Service_ACC, "(STUBBED) called");
85 IPC::ResponseBuilder rb{ctx, 2};
86 rb.Push(ResultSuccess);
87 }
81}; 88};
82 89
83// 3.0.0+ 90// 3.0.0+
@@ -400,13 +407,13 @@ protected:
400 IPC::RequestParser rp{ctx}; 407 IPC::RequestParser rp{ctx};
401 const auto base = rp.PopRaw<ProfileBase>(); 408 const auto base = rp.PopRaw<ProfileBase>();
402 409
403 const auto user_data = ctx.ReadBuffer(); 410 const auto image_data = ctx.ReadBufferA(0);
404 const auto image_data = ctx.ReadBuffer(1); 411 const auto user_data = ctx.ReadBufferX(0);
405 412
406 LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid=0x{}", 413 LOG_INFO(Service_ACC, "called, username='{}', timestamp={:016X}, uuid=0x{}",
407 Common::StringFromFixedZeroTerminatedBuffer( 414 Common::StringFromFixedZeroTerminatedBuffer(
408 reinterpret_cast<const char*>(base.username.data()), base.username.size()), 415 reinterpret_cast<const char*>(base.username.data()), base.username.size()),
409 base.timestamp, base.user_uuid.RawString()); 416 base.timestamp, base.user_uuid.RawString());
410 417
411 if (user_data.size() < sizeof(UserData)) { 418 if (user_data.size() < sizeof(UserData)) {
412 LOG_ERROR(Service_ACC, "UserData buffer too small!"); 419 LOG_ERROR(Service_ACC, "UserData buffer too small!");
@@ -837,6 +844,29 @@ void Module::Interface::InitializeApplicationInfoV2(HLERequestContext& ctx) {
837 rb.Push(ResultSuccess); 844 rb.Push(ResultSuccess);
838} 845}
839 846
847void Module::Interface::BeginUserRegistration(HLERequestContext& ctx) {
848 const auto user_id = Common::UUID::MakeRandom();
849 profile_manager->CreateNewUser(user_id, "yuzu");
850
851 LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString());
852
853 IPC::ResponseBuilder rb{ctx, 6};
854 rb.Push(ResultSuccess);
855 rb.PushRaw(user_id);
856}
857
858void Module::Interface::CompleteUserRegistration(HLERequestContext& ctx) {
859 IPC::RequestParser rp{ctx};
860 Common::UUID user_id = rp.PopRaw<Common::UUID>();
861
862 LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString());
863
864 profile_manager->WriteUserSaveFile();
865
866 IPC::ResponseBuilder rb{ctx, 2};
867 rb.Push(ResultSuccess);
868}
869
840void Module::Interface::GetProfileEditor(HLERequestContext& ctx) { 870void Module::Interface::GetProfileEditor(HLERequestContext& ctx) {
841 IPC::RequestParser rp{ctx}; 871 IPC::RequestParser rp{ctx};
842 Common::UUID user_id = rp.PopRaw<Common::UUID>(); 872 Common::UUID user_id = rp.PopRaw<Common::UUID>();
@@ -880,6 +910,17 @@ void Module::Interface::StoreSaveDataThumbnailApplication(HLERequestContext& ctx
880 StoreSaveDataThumbnail(ctx, uuid, tid); 910 StoreSaveDataThumbnail(ctx, uuid, tid);
881} 911}
882 912
913void Module::Interface::GetBaasAccountManagerForSystemService(HLERequestContext& ctx) {
914 IPC::RequestParser rp{ctx};
915 const auto uuid = rp.PopRaw<Common::UUID>();
916
917 LOG_INFO(Service_ACC, "called, uuid=0x{}", uuid.RawString());
918
919 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
920 rb.Push(ResultSuccess);
921 rb.PushIpcInterface<IManagerForSystemService>(system, uuid);
922}
923
883void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) { 924void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) {
884 IPC::RequestParser rp{ctx}; 925 IPC::RequestParser rp{ctx};
885 const auto uuid = rp.PopRaw<Common::UUID>(); 926 const auto uuid = rp.PopRaw<Common::UUID>();
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 6b4735c2f..0395229b4 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -33,10 +33,13 @@ public:
33 void TrySelectUserWithoutInteraction(HLERequestContext& ctx); 33 void TrySelectUserWithoutInteraction(HLERequestContext& ctx);
34 void IsUserAccountSwitchLocked(HLERequestContext& ctx); 34 void IsUserAccountSwitchLocked(HLERequestContext& ctx);
35 void InitializeApplicationInfoV2(HLERequestContext& ctx); 35 void InitializeApplicationInfoV2(HLERequestContext& ctx);
36 void BeginUserRegistration(HLERequestContext& ctx);
37 void CompleteUserRegistration(HLERequestContext& ctx);
36 void GetProfileEditor(HLERequestContext& ctx); 38 void GetProfileEditor(HLERequestContext& ctx);
37 void ListQualifiedUsers(HLERequestContext& ctx); 39 void ListQualifiedUsers(HLERequestContext& ctx);
38 void ListOpenContextStoredUsers(HLERequestContext& ctx); 40 void ListOpenContextStoredUsers(HLERequestContext& ctx);
39 void StoreSaveDataThumbnailApplication(HLERequestContext& ctx); 41 void StoreSaveDataThumbnailApplication(HLERequestContext& ctx);
42 void GetBaasAccountManagerForSystemService(HLERequestContext& ctx);
40 void StoreSaveDataThumbnailSystem(HLERequestContext& ctx); 43 void StoreSaveDataThumbnailSystem(HLERequestContext& ctx);
41 44
42 private: 45 private:
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index d9882ecd3..770d13ec5 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -23,7 +23,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
23 {99, nullptr, "DebugActivateOpenContextRetention"}, 23 {99, nullptr, "DebugActivateOpenContextRetention"},
24 {100, nullptr, "GetUserRegistrationNotifier"}, 24 {100, nullptr, "GetUserRegistrationNotifier"},
25 {101, nullptr, "GetUserStateChangeNotifier"}, 25 {101, nullptr, "GetUserStateChangeNotifier"},
26 {102, nullptr, "GetBaasAccountManagerForSystemService"}, 26 {102, &ACC_SU::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"},
27 {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, 27 {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
28 {104, nullptr, "GetProfileUpdateNotifier"}, 28 {104, nullptr, "GetProfileUpdateNotifier"},
29 {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, 29 {105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
@@ -40,8 +40,8 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
40 {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, 40 {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
41 {190, nullptr, "GetUserLastOpenedApplication"}, 41 {190, nullptr, "GetUserLastOpenedApplication"},
42 {191, nullptr, "ActivateOpenContextHolder"}, 42 {191, nullptr, "ActivateOpenContextHolder"},
43 {200, nullptr, "BeginUserRegistration"}, 43 {200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"},
44 {201, nullptr, "CompleteUserRegistration"}, 44 {201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"},
45 {202, nullptr, "CancelUserRegistration"}, 45 {202, nullptr, "CancelUserRegistration"},
46 {203, nullptr, "DeleteUser"}, 46 {203, nullptr, "DeleteUser"},
47 {204, nullptr, "SetUserPosition"}, 47 {204, nullptr, "SetUserPosition"},
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 993a5a57a..900e32200 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -96,9 +96,10 @@ public:
96 bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, 96 bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
97 const UserData& data_new); 97 const UserData& data_new);
98 98
99 void WriteUserSaveFile();
100
99private: 101private:
100 void ParseUserSaveFile(); 102 void ParseUserSaveFile();
101 void WriteUserSaveFile();
102 std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile); 103 std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
103 bool RemoveProfileAtIndex(std::size_t index); 104 bool RemoveProfileAtIndex(std::size_t index);
104 105
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 819dea6a7..98765b81a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -31,7 +31,7 @@
31#include "core/hle/service/apm/apm_controller.h" 31#include "core/hle/service/apm/apm_controller.h"
32#include "core/hle/service/apm/apm_interface.h" 32#include "core/hle/service/apm/apm_interface.h"
33#include "core/hle/service/bcat/backend/backend.h" 33#include "core/hle/service/bcat/backend/backend.h"
34#include "core/hle/service/caps/caps.h" 34#include "core/hle/service/caps/caps_types.h"
35#include "core/hle/service/filesystem/filesystem.h" 35#include "core/hle/service/filesystem/filesystem.h"
36#include "core/hle/service/ipc_helpers.h" 36#include "core/hle/service/ipc_helpers.h"
37#include "core/hle/service/ns/ns.h" 37#include "core/hle/service/ns/ns.h"
@@ -210,8 +210,8 @@ IDisplayController::IDisplayController(Core::System& system_)
210 {21, nullptr, "ClearAppletTransitionBuffer"}, 210 {21, nullptr, "ClearAppletTransitionBuffer"},
211 {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"}, 211 {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"},
212 {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"}, 212 {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
213 {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"}, 213 {24, &IDisplayController::AcquireLastForegroundCaptureSharedBuffer, "AcquireLastForegroundCaptureSharedBuffer"},
214 {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"}, 214 {25, &IDisplayController::ReleaseLastForegroundCaptureSharedBuffer, "ReleaseLastForegroundCaptureSharedBuffer"},
215 {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"}, 215 {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"},
216 {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"}, 216 {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"},
217 {28, nullptr, "TakeScreenShotOfOwnLayerEx"}, 217 {28, nullptr, "TakeScreenShotOfOwnLayerEx"},
@@ -239,6 +239,22 @@ void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
239 rb.Push(ResultSuccess); 239 rb.Push(ResultSuccess);
240} 240}
241 241
242void IDisplayController::AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) {
243 LOG_WARNING(Service_AM, "(STUBBED) called");
244
245 IPC::ResponseBuilder rb{ctx, 4};
246 rb.Push(ResultSuccess);
247 rb.Push(1U);
248 rb.Push(0);
249}
250
251void IDisplayController::ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) {
252 LOG_WARNING(Service_AM, "(STUBBED) called");
253
254 IPC::ResponseBuilder rb{ctx, 2};
255 rb.Push(ResultSuccess);
256}
257
242void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) { 258void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
243 LOG_WARNING(Service_AM, "(STUBBED) called"); 259 LOG_WARNING(Service_AM, "(STUBBED) called");
244 260
@@ -764,6 +780,66 @@ void AppletMessageQueue::OperationModeChanged() {
764 on_operation_mode_changed->Signal(); 780 on_operation_mode_changed->Signal();
765} 781}
766 782
783ILockAccessor::ILockAccessor(Core::System& system_)
784 : ServiceFramework{system_, "ILockAccessor"}, service_context{system_, "ILockAccessor"} {
785 // clang-format off
786 static const FunctionInfo functions[] = {
787 {1, &ILockAccessor::TryLock, "TryLock"},
788 {2, &ILockAccessor::Unlock, "Unlock"},
789 {3, &ILockAccessor::GetEvent, "GetEvent"},
790 {4,&ILockAccessor::IsLocked, "IsLocked"},
791 };
792 // clang-format on
793
794 RegisterHandlers(functions);
795
796 lock_event = service_context.CreateEvent("ILockAccessor::LockEvent");
797}
798
799ILockAccessor::~ILockAccessor() = default;
800
801void ILockAccessor::TryLock(HLERequestContext& ctx) {
802 IPC::RequestParser rp{ctx};
803 const auto return_handle = rp.Pop<bool>();
804
805 LOG_WARNING(Service_AM, "(STUBBED) called, return_handle={}", return_handle);
806
807 // TODO: When return_handle is true this function should return the lock handle
808
809 is_locked = true;
810
811 IPC::ResponseBuilder rb{ctx, 3};
812 rb.Push(ResultSuccess);
813 rb.Push<u8>(is_locked);
814}
815
816void ILockAccessor::Unlock(HLERequestContext& ctx) {
817 LOG_INFO(Service_AM, "called");
818
819 is_locked = false;
820
821 IPC::ResponseBuilder rb{ctx, 2};
822 rb.Push(ResultSuccess);
823}
824
825void ILockAccessor::GetEvent(HLERequestContext& ctx) {
826 LOG_INFO(Service_AM, "called");
827
828 lock_event->Signal();
829
830 IPC::ResponseBuilder rb{ctx, 2, 1};
831 rb.Push(ResultSuccess);
832 rb.PushCopyObjects(lock_event->GetReadableEvent());
833}
834
835void ILockAccessor::IsLocked(HLERequestContext& ctx) {
836 LOG_INFO(Service_AM, "called");
837
838 IPC::ResponseBuilder rb{ctx, 2};
839 rb.Push(ResultSuccess);
840 rb.Push<u8>(is_locked);
841}
842
767ICommonStateGetter::ICommonStateGetter(Core::System& system_, 843ICommonStateGetter::ICommonStateGetter(Core::System& system_,
768 std::shared_ptr<AppletMessageQueue> msg_queue_) 844 std::shared_ptr<AppletMessageQueue> msg_queue_)
769 : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)}, 845 : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)},
@@ -787,7 +863,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
787 {14, nullptr, "GetWakeupCount"}, 863 {14, nullptr, "GetWakeupCount"},
788 {20, nullptr, "PushToGeneralChannel"}, 864 {20, nullptr, "PushToGeneralChannel"},
789 {30, nullptr, "GetHomeButtonReaderLockAccessor"}, 865 {30, nullptr, "GetHomeButtonReaderLockAccessor"},
790 {31, nullptr, "GetReaderLockAccessorEx"}, 866 {31, &ICommonStateGetter::GetReaderLockAccessorEx, "GetReaderLockAccessorEx"},
791 {32, nullptr, "GetWriterLockAccessorEx"}, 867 {32, nullptr, "GetWriterLockAccessorEx"},
792 {40, nullptr, "GetCradleFwVersion"}, 868 {40, nullptr, "GetCradleFwVersion"},
793 {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"}, 869 {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"},
@@ -805,7 +881,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
805 {65, nullptr, "GetApplicationIdByContentActionName"}, 881 {65, nullptr, "GetApplicationIdByContentActionName"},
806 {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"}, 882 {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
807 {67, nullptr, "CancelCpuBoostMode"}, 883 {67, nullptr, "CancelCpuBoostMode"},
808 {68, nullptr, "GetBuiltInDisplayType"}, 884 {68, &ICommonStateGetter::GetBuiltInDisplayType, "GetBuiltInDisplayType"},
809 {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"}, 885 {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"},
810 {90, nullptr, "SetPerformanceConfigurationChangedNotification"}, 886 {90, nullptr, "SetPerformanceConfigurationChangedNotification"},
811 {91, nullptr, "GetCurrentPerformanceConfiguration"}, 887 {91, nullptr, "GetCurrentPerformanceConfiguration"},
@@ -886,6 +962,18 @@ void ICommonStateGetter::RequestToAcquireSleepLock(HLERequestContext& ctx) {
886 rb.Push(ResultSuccess); 962 rb.Push(ResultSuccess);
887} 963}
888 964
965void ICommonStateGetter::GetReaderLockAccessorEx(HLERequestContext& ctx) {
966 IPC::RequestParser rp{ctx};
967 const auto unknown = rp.Pop<u32>();
968
969 LOG_INFO(Service_AM, "called, unknown={}", unknown);
970
971 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
972
973 rb.Push(ResultSuccess);
974 rb.PushIpcInterface<ILockAccessor>(system);
975}
976
889void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) { 977void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) {
890 LOG_WARNING(Service_AM, "called"); 978 LOG_WARNING(Service_AM, "called");
891 979
@@ -970,6 +1058,14 @@ void ICommonStateGetter::SetCpuBoostMode(HLERequestContext& ctx) {
970 apm_sys->SetCpuBoostMode(ctx); 1058 apm_sys->SetCpuBoostMode(ctx);
971} 1059}
972 1060
1061void ICommonStateGetter::GetBuiltInDisplayType(HLERequestContext& ctx) {
1062 LOG_WARNING(Service_AM, "(STUBBED) called");
1063
1064 IPC::ResponseBuilder rb{ctx, 3};
1065 rb.Push(ResultSuccess);
1066 rb.Push(0);
1067}
1068
973void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) { 1069void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) {
974 IPC::RequestParser rp{ctx}; 1070 IPC::RequestParser rp{ctx};
975 const auto system_button{rp.PopEnum<SystemButtonType>()}; 1071 const auto system_button{rp.PopEnum<SystemButtonType>()};
@@ -1477,7 +1573,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1477 {100, nullptr, "CreateGameMovieTrimmer"}, 1573 {100, nullptr, "CreateGameMovieTrimmer"},
1478 {101, nullptr, "ReserveResourceForMovieOperation"}, 1574 {101, nullptr, "ReserveResourceForMovieOperation"},
1479 {102, nullptr, "UnreserveResourceForMovieOperation"}, 1575 {102, nullptr, "UnreserveResourceForMovieOperation"},
1480 {110, nullptr, "GetMainAppletAvailableUsers"}, 1576 {110, &ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers, "GetMainAppletAvailableUsers"},
1481 {120, nullptr, "GetLaunchStorageInfoForDebug"}, 1577 {120, nullptr, "GetLaunchStorageInfoForDebug"},
1482 {130, nullptr, "GetGpuErrorDetectedSystemEvent"}, 1578 {130, nullptr, "GetGpuErrorDetectedSystemEvent"},
1483 {140, nullptr, "SetApplicationMemoryReservation"}, 1579 {140, nullptr, "SetApplicationMemoryReservation"},
@@ -1493,6 +1589,9 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1493 case Applets::AppletId::MiiEdit: 1589 case Applets::AppletId::MiiEdit:
1494 PushInShowMiiEditData(); 1590 PushInShowMiiEditData();
1495 break; 1591 break;
1592 case Applets::AppletId::PhotoViewer:
1593 PushInShowAlbum();
1594 break;
1496 default: 1595 default:
1497 break; 1596 break;
1498 } 1597 }
@@ -1569,6 +1668,42 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext&
1569 rb.PushRaw(applet_info); 1668 rb.PushRaw(applet_info);
1570} 1669}
1571 1670
1671void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) {
1672 const Service::Account::ProfileManager manager{};
1673 bool is_empty{true};
1674 s32 user_count{-1};
1675
1676 LOG_INFO(Service_AM, "called");
1677
1678 if (manager.GetUserCount() > 0) {
1679 is_empty = false;
1680 user_count = static_cast<s32>(manager.GetUserCount());
1681 ctx.WriteBuffer(manager.GetAllUsers());
1682 }
1683
1684 IPC::ResponseBuilder rb{ctx, 4};
1685 rb.Push(ResultSuccess);
1686 rb.Push<u8>(is_empty);
1687 rb.Push(user_count);
1688}
1689
1690void ILibraryAppletSelfAccessor::PushInShowAlbum() {
1691 const Applets::CommonArguments arguments{
1692 .arguments_version = Applets::CommonArgumentVersion::Version3,
1693 .size = Applets::CommonArgumentSize::Version3,
1694 .library_version = 1,
1695 .theme_color = Applets::ThemeColor::BasicBlack,
1696 .play_startup_sound = true,
1697 .system_tick = system.CoreTiming().GetClockTicks(),
1698 };
1699
1700 std::vector<u8> argument_data(sizeof(arguments));
1701 std::vector<u8> settings_data{2};
1702 std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
1703 queue_data.emplace_back(std::move(argument_data));
1704 queue_data.emplace_back(std::move(settings_data));
1705}
1706
1572void ILibraryAppletSelfAccessor::PushInShowCabinetData() { 1707void ILibraryAppletSelfAccessor::PushInShowCabinetData() {
1573 const Applets::CommonArguments arguments{ 1708 const Applets::CommonArguments arguments{
1574 .arguments_version = Applets::CommonArgumentVersion::Version3, 1709 .arguments_version = Applets::CommonArgumentVersion::Version3,
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 349482dcc..64b3f3fe2 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -124,6 +124,8 @@ public:
124private: 124private:
125 void GetCallerAppletCaptureImageEx(HLERequestContext& ctx); 125 void GetCallerAppletCaptureImageEx(HLERequestContext& ctx);
126 void TakeScreenShotOfOwnLayer(HLERequestContext& ctx); 126 void TakeScreenShotOfOwnLayer(HLERequestContext& ctx);
127 void AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx);
128 void ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx);
127 void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); 129 void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
128 void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); 130 void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
129}; 131};
@@ -195,6 +197,23 @@ private:
195 ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit; 197 ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit;
196}; 198};
197 199
200class ILockAccessor final : public ServiceFramework<ILockAccessor> {
201public:
202 explicit ILockAccessor(Core::System& system_);
203 ~ILockAccessor() override;
204
205private:
206 void TryLock(HLERequestContext& ctx);
207 void Unlock(HLERequestContext& ctx);
208 void GetEvent(HLERequestContext& ctx);
209 void IsLocked(HLERequestContext& ctx);
210
211 bool is_locked{};
212
213 Kernel::KEvent* lock_event;
214 KernelHelpers::ServiceContext service_context;
215};
216
198class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { 217class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
199public: 218public:
200 explicit ICommonStateGetter(Core::System& system_, 219 explicit ICommonStateGetter(Core::System& system_,
@@ -237,6 +256,7 @@ private:
237 void GetCurrentFocusState(HLERequestContext& ctx); 256 void GetCurrentFocusState(HLERequestContext& ctx);
238 void RequestToAcquireSleepLock(HLERequestContext& ctx); 257 void RequestToAcquireSleepLock(HLERequestContext& ctx);
239 void GetAcquiredSleepLockEvent(HLERequestContext& ctx); 258 void GetAcquiredSleepLockEvent(HLERequestContext& ctx);
259 void GetReaderLockAccessorEx(HLERequestContext& ctx);
240 void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx); 260 void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx);
241 void GetOperationMode(HLERequestContext& ctx); 261 void GetOperationMode(HLERequestContext& ctx);
242 void GetPerformanceMode(HLERequestContext& ctx); 262 void GetPerformanceMode(HLERequestContext& ctx);
@@ -248,6 +268,7 @@ private:
248 void EndVrModeEx(HLERequestContext& ctx); 268 void EndVrModeEx(HLERequestContext& ctx);
249 void GetDefaultDisplayResolution(HLERequestContext& ctx); 269 void GetDefaultDisplayResolution(HLERequestContext& ctx);
250 void SetCpuBoostMode(HLERequestContext& ctx); 270 void SetCpuBoostMode(HLERequestContext& ctx);
271 void GetBuiltInDisplayType(HLERequestContext& ctx);
251 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx); 272 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx);
252 void GetSettingsPlatformRegion(HLERequestContext& ctx); 273 void GetSettingsPlatformRegion(HLERequestContext& ctx);
253 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx); 274 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx);
@@ -326,7 +347,9 @@ private:
326 void GetLibraryAppletInfo(HLERequestContext& ctx); 347 void GetLibraryAppletInfo(HLERequestContext& ctx);
327 void ExitProcessAndReturn(HLERequestContext& ctx); 348 void ExitProcessAndReturn(HLERequestContext& ctx);
328 void GetCallerAppletIdentityInfo(HLERequestContext& ctx); 349 void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
350 void GetMainAppletAvailableUsers(HLERequestContext& ctx);
329 351
352 void PushInShowAlbum();
330 void PushInShowCabinetData(); 353 void PushInShowCabinetData();
331 void PushInShowMiiEditData(); 354 void PushInShowMiiEditData();
332 355
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index eb12312cc..e30e6478a 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -28,8 +28,8 @@ public:
28 {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, 28 {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
29 {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"}, 29 {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"},
30 {21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"}, 30 {21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
31 {22, nullptr, "GetHomeMenuFunctions"}, 31 {22, &ILibraryAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
32 {23, nullptr, "GetGlobalStateController"}, 32 {23, &ILibraryAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
33 {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, 33 {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
34 }; 34 };
35 // clang-format on 35 // clang-format on
@@ -110,6 +110,22 @@ private:
110 rb.PushIpcInterface<IAppletCommonFunctions>(system); 110 rb.PushIpcInterface<IAppletCommonFunctions>(system);
111 } 111 }
112 112
113 void GetHomeMenuFunctions(HLERequestContext& ctx) {
114 LOG_DEBUG(Service_AM, "called");
115
116 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
117 rb.Push(ResultSuccess);
118 rb.PushIpcInterface<IHomeMenuFunctions>(system);
119 }
120
121 void GetGlobalStateController(HLERequestContext& ctx) {
122 LOG_DEBUG(Service_AM, "called");
123
124 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
125 rb.Push(ResultSuccess);
126 rb.PushIpcInterface<IGlobalStateController>(system);
127 }
128
113 void GetDebugFunctions(HLERequestContext& ctx) { 129 void GetDebugFunctions(HLERequestContext& ctx) {
114 LOG_DEBUG(Service_AM, "called"); 130 LOG_DEBUG(Service_AM, "called");
115 131
diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp
index b46ea840c..5d17c353f 100644
--- a/src/core/hle/service/am/applets/applet_error.cpp
+++ b/src/core/hle/service/am/applets/applet_error.cpp
@@ -138,6 +138,10 @@ void Error::Initialize() {
138 CopyArgumentData(data, args->application_error); 138 CopyArgumentData(data, args->application_error);
139 error_code = Result(args->application_error.error_code); 139 error_code = Result(args->application_error.error_code);
140 break; 140 break;
141 case ErrorAppletMode::ShowErrorPctl:
142 CopyArgumentData(data, args->error_record);
143 error_code = Decode64BitError(args->error_record.error_code_64);
144 break;
141 case ErrorAppletMode::ShowErrorRecord: 145 case ErrorAppletMode::ShowErrorRecord:
142 CopyArgumentData(data, args->error_record); 146 CopyArgumentData(data, args->error_record);
143 error_code = Decode64BitError(args->error_record.error_code_64); 147 error_code = Decode64BitError(args->error_record.error_code_64);
@@ -191,6 +195,7 @@ void Error::Execute() {
191 frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback); 195 frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback);
192 break; 196 break;
193 } 197 }
198 case ErrorAppletMode::ShowErrorPctl:
194 case ErrorAppletMode::ShowErrorRecord: 199 case ErrorAppletMode::ShowErrorRecord:
195 reporter.SaveErrorReport(title_id, error_code, 200 reporter.SaveErrorReport(title_id, error_code,
196 fmt::format("{:016X}", args->error_record.posix_time)); 201 fmt::format("{:016X}", args->error_record.posix_time));
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 610fe9940..31dd98140 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -4,6 +4,7 @@
4#include "core/hle/service/caps/caps.h" 4#include "core/hle/service/caps/caps.h"
5#include "core/hle/service/caps/caps_a.h" 5#include "core/hle/service/caps/caps_a.h"
6#include "core/hle/service/caps/caps_c.h" 6#include "core/hle/service/caps/caps_c.h"
7#include "core/hle/service/caps/caps_manager.h"
7#include "core/hle/service/caps/caps_sc.h" 8#include "core/hle/service/caps/caps_sc.h"
8#include "core/hle/service/caps/caps_ss.h" 9#include "core/hle/service/caps/caps_ss.h"
9#include "core/hle/service/caps/caps_su.h" 10#include "core/hle/service/caps/caps_su.h"
@@ -15,13 +16,21 @@ namespace Service::Capture {
15 16
16void LoopProcess(Core::System& system) { 17void LoopProcess(Core::System& system) {
17 auto server_manager = std::make_unique<ServerManager>(system); 18 auto server_manager = std::make_unique<ServerManager>(system);
19 auto album_manager = std::make_shared<AlbumManager>(system);
20
21 server_manager->RegisterNamedService(
22 "caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
23 server_manager->RegisterNamedService(
24 "caps:c", std::make_shared<IAlbumControlService>(system, album_manager));
25 server_manager->RegisterNamedService(
26 "caps:u", std::make_shared<IAlbumApplicationService>(system, album_manager));
27
28 server_manager->RegisterNamedService("caps:ss", std::make_shared<IScreenShotService>(system));
29 server_manager->RegisterNamedService("caps:sc",
30 std::make_shared<IScreenShotControlService>(system));
31 server_manager->RegisterNamedService("caps:su",
32 std::make_shared<IScreenShotApplicationService>(system));
18 33
19 server_manager->RegisterNamedService("caps:a", std::make_shared<CAPS_A>(system));
20 server_manager->RegisterNamedService("caps:c", std::make_shared<CAPS_C>(system));
21 server_manager->RegisterNamedService("caps:u", std::make_shared<CAPS_U>(system));
22 server_manager->RegisterNamedService("caps:sc", std::make_shared<CAPS_SC>(system));
23 server_manager->RegisterNamedService("caps:ss", std::make_shared<CAPS_SS>(system));
24 server_manager->RegisterNamedService("caps:su", std::make_shared<CAPS_SU>(system));
25 ServerManager::RunServer(std::move(server_manager)); 34 ServerManager::RunServer(std::move(server_manager));
26} 35}
27 36
diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h
index 15f0ecfaa..58e9725b8 100644
--- a/src/core/hle/service/caps/caps.h
+++ b/src/core/hle/service/caps/caps.h
@@ -3,93 +3,12 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace Core { 6namespace Core {
10class System; 7class System;
11} 8}
12 9
13namespace Service::SM {
14class ServiceManager;
15}
16
17namespace Service::Capture { 10namespace Service::Capture {
18 11
19enum class AlbumImageOrientation {
20 Orientation0 = 0,
21 Orientation1 = 1,
22 Orientation2 = 2,
23 Orientation3 = 3,
24};
25
26enum class AlbumReportOption : s32 {
27 Disable = 0,
28 Enable = 1,
29};
30
31enum class ContentType : u8 {
32 Screenshot = 0,
33 Movie = 1,
34 ExtraMovie = 3,
35};
36
37enum class AlbumStorage : u8 {
38 NAND = 0,
39 SD = 1,
40};
41
42struct AlbumFileDateTime {
43 s16 year{};
44 s8 month{};
45 s8 day{};
46 s8 hour{};
47 s8 minute{};
48 s8 second{};
49 s8 uid{};
50};
51static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
52
53struct AlbumEntry {
54 u64 size{};
55 u64 application_id{};
56 AlbumFileDateTime datetime{};
57 AlbumStorage storage{};
58 ContentType content{};
59 INSERT_PADDING_BYTES(6);
60};
61static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
62
63struct AlbumFileEntry {
64 u64 size{}; // Size of the entry
65 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
66 AlbumFileDateTime datetime{};
67 AlbumStorage storage{};
68 ContentType content{};
69 INSERT_PADDING_BYTES(5);
70 u8 unknown{1}; // Set to 1 on official SW
71};
72static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
73
74struct ApplicationAlbumEntry {
75 u64 size{}; // Size of the entry
76 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
77 AlbumFileDateTime datetime{};
78 AlbumStorage storage{};
79 ContentType content{};
80 INSERT_PADDING_BYTES(5);
81 u8 unknown{1}; // Set to 1 on official SW
82};
83static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
84
85struct ApplicationAlbumFileEntry {
86 ApplicationAlbumEntry entry{};
87 AlbumFileDateTime datetime{};
88 u64 unknown{};
89};
90static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
91 "ApplicationAlbumFileEntry has incorrect size.");
92
93void LoopProcess(Core::System& system); 12void LoopProcess(Core::System& system);
94 13
95} // namespace Service::Capture 14} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index 44267b284..9925720a3 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -1,40 +1,26 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/logging/log.h"
4#include "core/hle/service/caps/caps_a.h" 5#include "core/hle/service/caps/caps_a.h"
6#include "core/hle/service/caps/caps_manager.h"
7#include "core/hle/service/caps/caps_result.h"
8#include "core/hle/service/caps/caps_types.h"
9#include "core/hle/service/ipc_helpers.h"
5 10
6namespace Service::Capture { 11namespace Service::Capture {
7 12
8class IAlbumAccessorSession final : public ServiceFramework<IAlbumAccessorSession> { 13IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
9public: 14 std::shared_ptr<AlbumManager> album_manager)
10 explicit IAlbumAccessorSession(Core::System& system_) 15 : ServiceFramework{system_, "caps:a"}, manager{album_manager} {
11 : ServiceFramework{system_, "IAlbumAccessorSession"} {
12 // clang-format off
13 static const FunctionInfo functions[] = {
14 {2001, nullptr, "OpenAlbumMovieReadStream"},
15 {2002, nullptr, "CloseAlbumMovieReadStream"},
16 {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
17 {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
18 {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
19 {2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
20 {2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
21 {2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
22 };
23 // clang-format on
24
25 RegisterHandlers(functions);
26 }
27};
28
29CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
30 // clang-format off 16 // clang-format off
31 static const FunctionInfo functions[] = { 17 static const FunctionInfo functions[] = {
32 {0, nullptr, "GetAlbumFileCount"}, 18 {0, nullptr, "GetAlbumFileCount"},
33 {1, nullptr, "GetAlbumFileList"}, 19 {1, nullptr, "GetAlbumFileList"},
34 {2, nullptr, "LoadAlbumFile"}, 20 {2, nullptr, "LoadAlbumFile"},
35 {3, nullptr, "DeleteAlbumFile"}, 21 {3, &IAlbumAccessorService::DeleteAlbumFile, "DeleteAlbumFile"},
36 {4, nullptr, "StorageCopyAlbumFile"}, 22 {4, nullptr, "StorageCopyAlbumFile"},
37 {5, nullptr, "IsAlbumMounted"}, 23 {5, &IAlbumAccessorService::IsAlbumMounted, "IsAlbumMounted"},
38 {6, nullptr, "GetAlbumUsage"}, 24 {6, nullptr, "GetAlbumUsage"},
39 {7, nullptr, "GetAlbumFileSize"}, 25 {7, nullptr, "GetAlbumFileSize"},
40 {8, nullptr, "LoadAlbumFileThumbnail"}, 26 {8, nullptr, "LoadAlbumFileThumbnail"},
@@ -47,18 +33,18 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
47 {15, nullptr, "GetAlbumUsage3"}, 33 {15, nullptr, "GetAlbumUsage3"},
48 {16, nullptr, "GetAlbumMountResult"}, 34 {16, nullptr, "GetAlbumMountResult"},
49 {17, nullptr, "GetAlbumUsage16"}, 35 {17, nullptr, "GetAlbumUsage16"},
50 {18, nullptr, "Unknown18"}, 36 {18, &IAlbumAccessorService::Unknown18, "Unknown18"},
51 {19, nullptr, "Unknown19"}, 37 {19, nullptr, "Unknown19"},
52 {100, nullptr, "GetAlbumFileCountEx0"}, 38 {100, nullptr, "GetAlbumFileCountEx0"},
53 {101, nullptr, "GetAlbumFileListEx0"}, 39 {101, &IAlbumAccessorService::GetAlbumFileListEx0, "GetAlbumFileListEx0"},
54 {202, nullptr, "SaveEditedScreenShot"}, 40 {202, nullptr, "SaveEditedScreenShot"},
55 {301, nullptr, "GetLastThumbnail"}, 41 {301, nullptr, "GetLastThumbnail"},
56 {302, nullptr, "GetLastOverlayMovieThumbnail"}, 42 {302, nullptr, "GetLastOverlayMovieThumbnail"},
57 {401, nullptr, "GetAutoSavingStorage"}, 43 {401, &IAlbumAccessorService::GetAutoSavingStorage, "GetAutoSavingStorage"},
58 {501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"}, 44 {501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"},
59 {1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"}, 45 {1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"},
60 {1002, nullptr, "LoadAlbumScreenShotImageEx1"}, 46 {1002, &IAlbumAccessorService::LoadAlbumScreenShotImageEx1, "LoadAlbumScreenShotImageEx1"},
61 {1003, nullptr, "LoadAlbumScreenShotThumbnailImageEx1"}, 47 {1003, &IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1, "LoadAlbumScreenShotThumbnailImageEx1"},
62 {8001, nullptr, "ForceAlbumUnmounted"}, 48 {8001, nullptr, "ForceAlbumUnmounted"},
63 {8002, nullptr, "ResetAlbumMountStatus"}, 49 {8002, nullptr, "ResetAlbumMountStatus"},
64 {8011, nullptr, "RefreshAlbumCache"}, 50 {8011, nullptr, "RefreshAlbumCache"},
@@ -74,6 +60,199 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
74 RegisterHandlers(functions); 60 RegisterHandlers(functions);
75} 61}
76 62
77CAPS_A::~CAPS_A() = default; 63IAlbumAccessorService::~IAlbumAccessorService() = default;
64
65void IAlbumAccessorService::DeleteAlbumFile(HLERequestContext& ctx) {
66 IPC::RequestParser rp{ctx};
67 const auto file_id{rp.PopRaw<AlbumFileId>()};
68
69 LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}",
70 file_id.application_id, file_id.storage, file_id.type);
71
72 Result result = manager->DeleteAlbumFile(file_id);
73 result = TranslateResult(result);
74
75 IPC::ResponseBuilder rb{ctx, 2};
76 rb.Push(result);
77}
78
79void IAlbumAccessorService::IsAlbumMounted(HLERequestContext& ctx) {
80 IPC::RequestParser rp{ctx};
81 const auto storage{rp.PopEnum<AlbumStorage>()};
82
83 LOG_INFO(Service_Capture, "called, storage={}", storage);
84
85 Result result = manager->IsAlbumMounted(storage);
86 const bool is_mounted = result.IsSuccess();
87 result = TranslateResult(result);
88
89 IPC::ResponseBuilder rb{ctx, 3};
90 rb.Push(result);
91 rb.Push<u8>(is_mounted);
92}
93
94void IAlbumAccessorService::Unknown18(HLERequestContext& ctx) {
95 struct UnknownBuffer {
96 INSERT_PADDING_BYTES(0x10);
97 };
98 static_assert(sizeof(UnknownBuffer) == 0x10, "UnknownBuffer is an invalid size");
99
100 LOG_WARNING(Service_Capture, "(STUBBED) called");
101
102 std::vector<UnknownBuffer> buffer{};
103
104 if (!buffer.empty()) {
105 ctx.WriteBuffer(buffer);
106 }
107
108 IPC::ResponseBuilder rb{ctx, 3};
109 rb.Push(ResultSuccess);
110 rb.Push(static_cast<u32>(buffer.size()));
111}
112
113void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
114 IPC::RequestParser rp{ctx};
115 const auto storage{rp.PopEnum<AlbumStorage>()};
116 const auto flags{rp.Pop<u8>()};
117 const auto album_entry_size{ctx.GetWriteBufferNumElements<AlbumEntry>()};
118
119 LOG_INFO(Service_Capture, "called, storage={}, flags={}", storage, flags);
120
121 std::vector<AlbumEntry> entries;
122 Result result = manager->GetAlbumFileList(entries, storage, flags);
123 result = TranslateResult(result);
124
125 entries.resize(std::min(album_entry_size, entries.size()));
126
127 if (!entries.empty()) {
128 ctx.WriteBuffer(entries);
129 }
130
131 IPC::ResponseBuilder rb{ctx, 4};
132 rb.Push(result);
133 rb.Push<u64>(entries.size());
134}
135
136void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
137 LOG_WARNING(Service_Capture, "(STUBBED) called");
138
139 bool is_autosaving{};
140 Result result = manager->GetAutoSavingStorage(is_autosaving);
141 result = TranslateResult(result);
142
143 IPC::ResponseBuilder rb{ctx, 3};
144 rb.Push(result);
145 rb.Push<u8>(is_autosaving);
146}
147
148void IAlbumAccessorService::LoadAlbumScreenShotImageEx1(HLERequestContext& ctx) {
149 IPC::RequestParser rp{ctx};
150 const auto file_id{rp.PopRaw<AlbumFileId>()};
151 const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
152 const auto image_buffer_size{ctx.GetWriteBufferSize(1)};
153
154 LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
155 file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
156
157 std::vector<u8> image;
158 LoadAlbumScreenShotImageOutput image_output;
159 Result result =
160 manager->LoadAlbumScreenShotImage(image_output, image, file_id, decoder_options);
161 result = TranslateResult(result);
162
163 if (image.size() > image_buffer_size) {
164 result = ResultWorkMemoryError;
165 }
166
167 if (result.IsSuccess()) {
168 ctx.WriteBuffer(image_output, 0);
169 ctx.WriteBuffer(image, 1);
170 }
171
172 IPC::ResponseBuilder rb{ctx, 2};
173 rb.Push(result);
174}
175
176void IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx) {
177 IPC::RequestParser rp{ctx};
178 const auto file_id{rp.PopRaw<AlbumFileId>()};
179 const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
180
181 LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
182 file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
183
184 std::vector<u8> image(ctx.GetWriteBufferSize(1));
185 LoadAlbumScreenShotImageOutput image_output;
186 Result result =
187 manager->LoadAlbumScreenShotThumbnail(image_output, image, file_id, decoder_options);
188 result = TranslateResult(result);
189
190 if (result.IsSuccess()) {
191 ctx.WriteBuffer(image_output, 0);
192 ctx.WriteBuffer(image, 1);
193 }
194
195 IPC::ResponseBuilder rb{ctx, 2};
196 rb.Push(result);
197}
198
199Result IAlbumAccessorService::TranslateResult(Result in_result) {
200 if (in_result.IsSuccess()) {
201 return in_result;
202 }
203
204 if ((in_result.raw & 0x3801ff) == ResultUnknown1024.raw) {
205 if (in_result.description - 0x514 < 100) {
206 return ResultInvalidFileData;
207 }
208 if (in_result.description - 0x5dc < 100) {
209 return ResultInvalidFileData;
210 }
211
212 if (in_result.description - 0x578 < 100) {
213 if (in_result == ResultFileCountLimit) {
214 return ResultUnknown22;
215 }
216 return ResultUnknown25;
217 }
218
219 if (in_result.raw < ResultUnknown1801.raw) {
220 if (in_result == ResultUnknown1202) {
221 return ResultUnknown810;
222 }
223 if (in_result == ResultUnknown1203) {
224 return ResultUnknown810;
225 }
226 if (in_result == ResultUnknown1701) {
227 return ResultUnknown5;
228 }
229 } else if (in_result.raw < ResultUnknown1803.raw) {
230 if (in_result == ResultUnknown1801) {
231 return ResultUnknown5;
232 }
233 if (in_result == ResultUnknown1802) {
234 return ResultUnknown6;
235 }
236 } else {
237 if (in_result == ResultUnknown1803) {
238 return ResultUnknown7;
239 }
240 if (in_result == ResultUnknown1804) {
241 return ResultOutOfRange;
242 }
243 }
244 return ResultUnknown1024;
245 }
246
247 if (in_result.module == ErrorModule::FS) {
248 if ((in_result.description >> 0xc < 0x7d) || (in_result.description - 1000 < 2000) ||
249 (((in_result.description - 3000) >> 3) < 0x271)) {
250 // TODO: Translate FS error
251 return in_result;
252 }
253 }
254
255 return in_result;
256}
78 257
79} // namespace Service::Capture 258} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h
index 98a21a5ad..c90cff71e 100644
--- a/src/core/hle/service/caps/caps_a.h
+++ b/src/core/hle/service/caps/caps_a.h
@@ -10,11 +10,26 @@ class System;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13class AlbumManager;
13 14
14class CAPS_A final : public ServiceFramework<CAPS_A> { 15class IAlbumAccessorService final : public ServiceFramework<IAlbumAccessorService> {
15public: 16public:
16 explicit CAPS_A(Core::System& system_); 17 explicit IAlbumAccessorService(Core::System& system_,
17 ~CAPS_A() override; 18 std::shared_ptr<AlbumManager> album_manager);
19 ~IAlbumAccessorService() override;
20
21private:
22 void DeleteAlbumFile(HLERequestContext& ctx);
23 void IsAlbumMounted(HLERequestContext& ctx);
24 void Unknown18(HLERequestContext& ctx);
25 void GetAlbumFileListEx0(HLERequestContext& ctx);
26 void GetAutoSavingStorage(HLERequestContext& ctx);
27 void LoadAlbumScreenShotImageEx1(HLERequestContext& ctx);
28 void LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx);
29
30 Result TranslateResult(Result in_result);
31
32 std::shared_ptr<AlbumManager> manager = nullptr;
18}; 33};
19 34
20} // namespace Service::Capture 35} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp
index fc77e35cd..1e7fe6474 100644
--- a/src/core/hle/service/caps/caps_c.cpp
+++ b/src/core/hle/service/caps/caps_c.cpp
@@ -3,53 +3,21 @@
3 3
4#include "common/logging/log.h" 4#include "common/logging/log.h"
5#include "core/hle/service/caps/caps_c.h" 5#include "core/hle/service/caps/caps_c.h"
6#include "core/hle/service/caps/caps_manager.h"
7#include "core/hle/service/caps/caps_result.h"
8#include "core/hle/service/caps/caps_types.h"
6#include "core/hle/service/ipc_helpers.h" 9#include "core/hle/service/ipc_helpers.h"
7 10
8namespace Service::Capture { 11namespace Service::Capture {
9 12
10class IAlbumControlSession final : public ServiceFramework<IAlbumControlSession> { 13IAlbumControlService::IAlbumControlService(Core::System& system_,
11public: 14 std::shared_ptr<AlbumManager> album_manager)
12 explicit IAlbumControlSession(Core::System& system_) 15 : ServiceFramework{system_, "caps:c"}, manager{album_manager} {
13 : ServiceFramework{system_, "IAlbumControlSession"} {
14 // clang-format off
15 static const FunctionInfo functions[] = {
16 {2001, nullptr, "OpenAlbumMovieReadStream"},
17 {2002, nullptr, "CloseAlbumMovieReadStream"},
18 {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
19 {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
20 {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
21 {2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
22 {2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
23 {2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
24 {2401, nullptr, "OpenAlbumMovieWriteStream"},
25 {2402, nullptr, "FinishAlbumMovieWriteStream"},
26 {2403, nullptr, "CommitAlbumMovieWriteStream"},
27 {2404, nullptr, "DiscardAlbumMovieWriteStream"},
28 {2405, nullptr, "DiscardAlbumMovieWriteStreamNoDelete"},
29 {2406, nullptr, "CommitAlbumMovieWriteStreamEx"},
30 {2411, nullptr, "StartAlbumMovieWriteStreamDataSection"},
31 {2412, nullptr, "EndAlbumMovieWriteStreamDataSection"},
32 {2413, nullptr, "StartAlbumMovieWriteStreamMetaSection"},
33 {2414, nullptr, "EndAlbumMovieWriteStreamMetaSection"},
34 {2421, nullptr, "ReadDataFromAlbumMovieWriteStream"},
35 {2422, nullptr, "WriteDataToAlbumMovieWriteStream"},
36 {2424, nullptr, "WriteMetaToAlbumMovieWriteStream"},
37 {2431, nullptr, "GetAlbumMovieWriteStreamBrokenReason"},
38 {2433, nullptr, "GetAlbumMovieWriteStreamDataSize"},
39 {2434, nullptr, "SetAlbumMovieWriteStreamDataSize"},
40 };
41 // clang-format on
42
43 RegisterHandlers(functions);
44 }
45};
46
47CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
48 // clang-format off 16 // clang-format off
49 static const FunctionInfo functions[] = { 17 static const FunctionInfo functions[] = {
50 {1, nullptr, "CaptureRawImage"}, 18 {1, nullptr, "CaptureRawImage"},
51 {2, nullptr, "CaptureRawImageWithTimeout"}, 19 {2, nullptr, "CaptureRawImageWithTimeout"},
52 {33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"}, 20 {33, &IAlbumControlService::SetShimLibraryVersion, "SetShimLibraryVersion"},
53 {1001, nullptr, "RequestTakingScreenShot"}, 21 {1001, nullptr, "RequestTakingScreenShot"},
54 {1002, nullptr, "RequestTakingScreenShotWithTimeout"}, 22 {1002, nullptr, "RequestTakingScreenShotWithTimeout"},
55 {1011, nullptr, "NotifyTakingScreenShotRefused"}, 23 {1011, nullptr, "NotifyTakingScreenShotRefused"},
@@ -72,9 +40,9 @@ CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
72 RegisterHandlers(functions); 40 RegisterHandlers(functions);
73} 41}
74 42
75CAPS_C::~CAPS_C() = default; 43IAlbumControlService::~IAlbumControlService() = default;
76 44
77void CAPS_C::SetShimLibraryVersion(HLERequestContext& ctx) { 45void IAlbumControlService::SetShimLibraryVersion(HLERequestContext& ctx) {
78 IPC::RequestParser rp{ctx}; 46 IPC::RequestParser rp{ctx};
79 const auto library_version{rp.Pop<u64>()}; 47 const auto library_version{rp.Pop<u64>()};
80 const auto applet_resource_user_id{rp.Pop<u64>()}; 48 const auto applet_resource_user_id{rp.Pop<u64>()};
diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h
index 537b3a2e3..92ba242db 100644
--- a/src/core/hle/service/caps/caps_c.h
+++ b/src/core/hle/service/caps/caps_c.h
@@ -10,14 +10,18 @@ class System;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13class AlbumManager;
13 14
14class CAPS_C final : public ServiceFramework<CAPS_C> { 15class IAlbumControlService final : public ServiceFramework<IAlbumControlService> {
15public: 16public:
16 explicit CAPS_C(Core::System& system_); 17 explicit IAlbumControlService(Core::System& system_,
17 ~CAPS_C() override; 18 std::shared_ptr<AlbumManager> album_manager);
19 ~IAlbumControlService() override;
18 20
19private: 21private:
20 void SetShimLibraryVersion(HLERequestContext& ctx); 22 void SetShimLibraryVersion(HLERequestContext& ctx);
23
24 std::shared_ptr<AlbumManager> manager = nullptr;
21}; 25};
22 26
23} // namespace Service::Capture 27} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp
new file mode 100644
index 000000000..2b4e3f076
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -0,0 +1,386 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <sstream>
5#include <stb_image.h>
6#include <stb_image_resize.h>
7
8#include "common/fs/file.h"
9#include "common/fs/path_util.h"
10#include "common/logging/log.h"
11#include "core/core.h"
12#include "core/hle/service/caps/caps_manager.h"
13#include "core/hle/service/caps/caps_result.h"
14#include "core/hle/service/time/time_manager.h"
15#include "core/hle/service/time/time_zone_content_manager.h"
16
17namespace Service::Capture {
18
19AlbumManager::AlbumManager(Core::System& system_) : system{system_} {}
20
21AlbumManager::~AlbumManager() = default;
22
23Result AlbumManager::DeleteAlbumFile(const AlbumFileId& file_id) {
24 if (file_id.storage > AlbumStorage::Sd) {
25 return ResultInvalidStorage;
26 }
27
28 if (!is_mounted) {
29 return ResultIsNotMounted;
30 }
31
32 std::filesystem::path path;
33 const auto result = GetFile(path, file_id);
34
35 if (result.IsError()) {
36 return result;
37 }
38
39 if (!Common::FS::RemoveFile(path)) {
40 return ResultFileNotFound;
41 }
42
43 return ResultSuccess;
44}
45
46Result AlbumManager::IsAlbumMounted(AlbumStorage storage) {
47 if (storage > AlbumStorage::Sd) {
48 return ResultInvalidStorage;
49 }
50
51 is_mounted = true;
52
53 if (storage == AlbumStorage::Sd) {
54 FindScreenshots();
55 }
56
57 return is_mounted ? ResultSuccess : ResultIsNotMounted;
58}
59
60Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
61 u8 flags) const {
62 if (storage > AlbumStorage::Sd) {
63 return ResultInvalidStorage;
64 }
65
66 if (!is_mounted) {
67 return ResultIsNotMounted;
68 }
69
70 for (auto& [file_id, path] : album_files) {
71 if (file_id.storage != storage) {
72 continue;
73 }
74 if (out_entries.size() >= SdAlbumFileLimit) {
75 break;
76 }
77
78 const auto entry_size = Common::FS::GetSize(path);
79 out_entries.push_back({
80 .entry_size = entry_size,
81 .file_id = file_id,
82 });
83 }
84
85 return ResultSuccess;
86}
87
88Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
89 ContentType contex_type, s64 start_posix_time,
90 s64 end_posix_time, u64 aruid) const {
91 if (!is_mounted) {
92 return ResultIsNotMounted;
93 }
94
95 std::vector<ApplicationAlbumEntry> album_entries;
96 const auto start_date = ConvertToAlbumDateTime(start_posix_time);
97 const auto end_date = ConvertToAlbumDateTime(end_posix_time);
98 const auto result = GetAlbumFileList(album_entries, contex_type, start_date, end_date, aruid);
99
100 if (result.IsError()) {
101 return result;
102 }
103
104 for (const auto& album_entry : album_entries) {
105 ApplicationAlbumFileEntry entry{
106 .entry = album_entry,
107 .datetime = album_entry.datetime,
108 .unknown = {},
109 };
110 out_entries.push_back(entry);
111 }
112
113 return ResultSuccess;
114}
115
116Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
117 ContentType contex_type, AlbumFileDateTime start_date,
118 AlbumFileDateTime end_date, u64 aruid) const {
119 if (!is_mounted) {
120 return ResultIsNotMounted;
121 }
122
123 for (auto& [file_id, path] : album_files) {
124 if (file_id.type != contex_type) {
125 continue;
126 }
127 if (file_id.date > start_date) {
128 continue;
129 }
130 if (file_id.date < end_date) {
131 continue;
132 }
133 if (out_entries.size() >= SdAlbumFileLimit) {
134 break;
135 }
136
137 const auto entry_size = Common::FS::GetSize(path);
138 ApplicationAlbumEntry entry{
139 .size = entry_size,
140 .hash{},
141 .datetime = file_id.date,
142 .storage = file_id.storage,
143 .content = contex_type,
144 .unknown = 1,
145 };
146 out_entries.push_back(entry);
147 }
148
149 return ResultSuccess;
150}
151
152Result AlbumManager::GetAutoSavingStorage(bool& out_is_autosaving) const {
153 out_is_autosaving = false;
154 return ResultSuccess;
155}
156
157Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
158 std::vector<u8>& out_image,
159 const AlbumFileId& file_id,
160 const ScreenShotDecodeOption& decoder_options) const {
161 if (file_id.storage > AlbumStorage::Sd) {
162 return ResultInvalidStorage;
163 }
164
165 if (!is_mounted) {
166 return ResultIsNotMounted;
167 }
168
169 out_image_output = {
170 .width = 1280,
171 .height = 720,
172 .attribute =
173 {
174 .unknown_0{},
175 .orientation = AlbumImageOrientation::None,
176 .unknown_1{},
177 .unknown_2{},
178 },
179 };
180
181 std::filesystem::path path;
182 const auto result = GetFile(path, file_id);
183
184 if (result.IsError()) {
185 return result;
186 }
187
188 out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
189
190 return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
191 +static_cast<int>(out_image_output.height), decoder_options.flags);
192}
193
194Result AlbumManager::LoadAlbumScreenShotThumbnail(
195 LoadAlbumScreenShotImageOutput& out_image_output, std::vector<u8>& out_image,
196 const AlbumFileId& file_id, const ScreenShotDecodeOption& decoder_options) const {
197 if (file_id.storage > AlbumStorage::Sd) {
198 return ResultInvalidStorage;
199 }
200
201 if (!is_mounted) {
202 return ResultIsNotMounted;
203 }
204
205 out_image_output = {
206 .width = 320,
207 .height = 180,
208 .attribute =
209 {
210 .unknown_0{},
211 .orientation = AlbumImageOrientation::None,
212 .unknown_1{},
213 .unknown_2{},
214 },
215 };
216
217 std::filesystem::path path;
218 const auto result = GetFile(path, file_id);
219
220 if (result.IsError()) {
221 return result;
222 }
223
224 out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
225
226 return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
227 +static_cast<int>(out_image_output.height), decoder_options.flags);
228}
229
230Result AlbumManager::GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const {
231 const auto file = album_files.find(file_id);
232
233 if (file == album_files.end()) {
234 return ResultFileNotFound;
235 }
236
237 out_path = file->second;
238 return ResultSuccess;
239}
240
241void AlbumManager::FindScreenshots() {
242 is_mounted = false;
243 album_files.clear();
244
245 // TODO: Swap this with a blocking operation.
246 const auto screenshots_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir);
247 Common::FS::IterateDirEntries(
248 screenshots_dir,
249 [this](const std::filesystem::path& full_path) {
250 AlbumEntry entry;
251 if (GetAlbumEntry(entry, full_path).IsError()) {
252 return true;
253 }
254 while (album_files.contains(entry.file_id)) {
255 if (++entry.file_id.date.unique_id == 0) {
256 break;
257 }
258 }
259 album_files[entry.file_id] = full_path;
260 return true;
261 },
262 Common::FS::DirEntryFilter::File);
263
264 is_mounted = true;
265}
266
267Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const {
268 std::istringstream line_stream(path.filename().string());
269 std::string date;
270 std::string application;
271 std::string time;
272
273 // Parse filename to obtain entry properties
274 std::getline(line_stream, application, '_');
275 std::getline(line_stream, date, '_');
276 std::getline(line_stream, time, '_');
277
278 std::istringstream date_stream(date);
279 std::istringstream time_stream(time);
280 std::string year;
281 std::string month;
282 std::string day;
283 std::string hour;
284 std::string minute;
285 std::string second;
286
287 std::getline(date_stream, year, '-');
288 std::getline(date_stream, month, '-');
289 std::getline(date_stream, day, '-');
290
291 std::getline(time_stream, hour, '-');
292 std::getline(time_stream, minute, '-');
293 std::getline(time_stream, second, '-');
294
295 try {
296 out_entry = {
297 .entry_size = 1,
298 .file_id{
299 .application_id = static_cast<u64>(std::stoll(application, 0, 16)),
300 .date =
301 {
302 .year = static_cast<s16>(std::stoi(year)),
303 .month = static_cast<s8>(std::stoi(month)),
304 .day = static_cast<s8>(std::stoi(day)),
305 .hour = static_cast<s8>(std::stoi(hour)),
306 .minute = static_cast<s8>(std::stoi(minute)),
307 .second = static_cast<s8>(std::stoi(second)),
308 .unique_id = 0,
309 },
310 .storage = AlbumStorage::Sd,
311 .type = ContentType::Screenshot,
312 .unknown = 1,
313 },
314 };
315 } catch (const std::invalid_argument&) {
316 return ResultUnknown;
317 } catch (const std::out_of_range&) {
318 return ResultUnknown;
319 } catch (const std::exception&) {
320 return ResultUnknown;
321 }
322
323 return ResultSuccess;
324}
325
326Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::path& path,
327 int width, int height, ScreenShotDecoderFlag flag) const {
328 if (out_image.size() != static_cast<std::size_t>(width * height * STBI_rgb_alpha)) {
329 return ResultUnknown;
330 }
331
332 const Common::FS::IOFile db_file{path, Common::FS::FileAccessMode::Read,
333 Common::FS::FileType::BinaryFile};
334
335 std::vector<u8> raw_file(db_file.GetSize());
336 if (db_file.Read(raw_file) != raw_file.size()) {
337 return ResultUnknown;
338 }
339
340 int filter_flag = STBIR_FILTER_DEFAULT;
341 int original_width, original_height, color_channels;
342 const auto dbi_image =
343 stbi_load_from_memory(raw_file.data(), static_cast<int>(raw_file.size()), &original_width,
344 &original_height, &color_channels, STBI_rgb_alpha);
345
346 if (dbi_image == nullptr) {
347 return ResultUnknown;
348 }
349
350 switch (flag) {
351 case ScreenShotDecoderFlag::EnableFancyUpsampling:
352 filter_flag = STBIR_FILTER_TRIANGLE;
353 break;
354 case ScreenShotDecoderFlag::EnableBlockSmoothing:
355 filter_flag = STBIR_FILTER_BOX;
356 break;
357 default:
358 filter_flag = STBIR_FILTER_DEFAULT;
359 break;
360 }
361
362 stbir_resize_uint8_srgb(dbi_image, original_width, original_height, 0, out_image.data(), width,
363 height, 0, STBI_rgb_alpha, 3, filter_flag);
364
365 return ResultSuccess;
366}
367
368AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
369 Time::TimeZone::CalendarInfo calendar_date{};
370 const auto& time_zone_manager =
371 system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
372
373 time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date);
374
375 return {
376 .year = calendar_date.time.year,
377 .month = calendar_date.time.month,
378 .day = calendar_date.time.day,
379 .hour = calendar_date.time.hour,
380 .minute = calendar_date.time.minute,
381 .second = calendar_date.time.second,
382 .unique_id = 0,
383 };
384}
385
386} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h
new file mode 100644
index 000000000..f65eb12c1
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -0,0 +1,79 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <unordered_map>
7
8#include "common/fs/fs.h"
9#include "core/hle/result.h"
10#include "core/hle/service/caps/caps_types.h"
11
12namespace Core {
13class System;
14}
15
16namespace std {
17// Hash used to create lists from AlbumFileId data
18template <>
19struct hash<Service::Capture::AlbumFileId> {
20 size_t operator()(const Service::Capture::AlbumFileId& pad_id) const noexcept {
21 u64 hash_value = (static_cast<u64>(pad_id.date.year) << 8);
22 hash_value ^= (static_cast<u64>(pad_id.date.month) << 7);
23 hash_value ^= (static_cast<u64>(pad_id.date.day) << 6);
24 hash_value ^= (static_cast<u64>(pad_id.date.hour) << 5);
25 hash_value ^= (static_cast<u64>(pad_id.date.minute) << 4);
26 hash_value ^= (static_cast<u64>(pad_id.date.second) << 3);
27 hash_value ^= (static_cast<u64>(pad_id.date.unique_id) << 2);
28 hash_value ^= (static_cast<u64>(pad_id.storage) << 1);
29 hash_value ^= static_cast<u64>(pad_id.type);
30 return static_cast<size_t>(hash_value);
31 }
32};
33
34} // namespace std
35
36namespace Service::Capture {
37
38class AlbumManager {
39public:
40 explicit AlbumManager(Core::System& system_);
41 ~AlbumManager();
42
43 Result DeleteAlbumFile(const AlbumFileId& file_id);
44 Result IsAlbumMounted(AlbumStorage storage);
45 Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
46 u8 flags) const;
47 Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
48 ContentType contex_type, s64 start_posix_time, s64 end_posix_time,
49 u64 aruid) const;
50 Result GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
51 ContentType contex_type, AlbumFileDateTime start_date,
52 AlbumFileDateTime end_date, u64 aruid) const;
53 Result GetAutoSavingStorage(bool& out_is_autosaving) const;
54 Result LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
55 std::vector<u8>& out_image, const AlbumFileId& file_id,
56 const ScreenShotDecodeOption& decoder_options) const;
57 Result LoadAlbumScreenShotThumbnail(LoadAlbumScreenShotImageOutput& out_image_output,
58 std::vector<u8>& out_image, const AlbumFileId& file_id,
59 const ScreenShotDecodeOption& decoder_options) const;
60
61private:
62 static constexpr std::size_t NandAlbumFileLimit = 1000;
63 static constexpr std::size_t SdAlbumFileLimit = 10000;
64
65 void FindScreenshots();
66 Result GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const;
67 Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const;
68 Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
69 int height, ScreenShotDecoderFlag flag) const;
70
71 AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
72
73 bool is_mounted{};
74 std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
75
76 Core::System& system;
77};
78
79} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_result.h b/src/core/hle/service/caps/caps_result.h
new file mode 100644
index 000000000..c65e5fb9a
--- /dev/null
+++ b/src/core/hle/service/caps/caps_result.h
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/result.h"
7
8namespace Service::Capture {
9
10constexpr Result ResultWorkMemoryError(ErrorModule::Capture, 3);
11constexpr Result ResultUnknown5(ErrorModule::Capture, 5);
12constexpr Result ResultUnknown6(ErrorModule::Capture, 6);
13constexpr Result ResultUnknown7(ErrorModule::Capture, 7);
14constexpr Result ResultOutOfRange(ErrorModule::Capture, 8);
15constexpr Result ResulInvalidTimestamp(ErrorModule::Capture, 12);
16constexpr Result ResultInvalidStorage(ErrorModule::Capture, 13);
17constexpr Result ResultInvalidFileContents(ErrorModule::Capture, 14);
18constexpr Result ResultIsNotMounted(ErrorModule::Capture, 21);
19constexpr Result ResultUnknown22(ErrorModule::Capture, 22);
20constexpr Result ResultFileNotFound(ErrorModule::Capture, 23);
21constexpr Result ResultInvalidFileData(ErrorModule::Capture, 24);
22constexpr Result ResultUnknown25(ErrorModule::Capture, 25);
23constexpr Result ResultReadBufferShortage(ErrorModule::Capture, 30);
24constexpr Result ResultUnknown810(ErrorModule::Capture, 810);
25constexpr Result ResultUnknown1024(ErrorModule::Capture, 1024);
26constexpr Result ResultUnknown1202(ErrorModule::Capture, 1202);
27constexpr Result ResultUnknown1203(ErrorModule::Capture, 1203);
28constexpr Result ResultFileCountLimit(ErrorModule::Capture, 1401);
29constexpr Result ResultUnknown1701(ErrorModule::Capture, 1701);
30constexpr Result ResultUnknown1801(ErrorModule::Capture, 1801);
31constexpr Result ResultUnknown1802(ErrorModule::Capture, 1802);
32constexpr Result ResultUnknown1803(ErrorModule::Capture, 1803);
33constexpr Result ResultUnknown1804(ErrorModule::Capture, 1804);
34
35} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp
index 395b13da7..6117cb7c6 100644
--- a/src/core/hle/service/caps/caps_sc.cpp
+++ b/src/core/hle/service/caps/caps_sc.cpp
@@ -5,7 +5,8 @@
5 5
6namespace Service::Capture { 6namespace Service::Capture {
7 7
8CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} { 8IScreenShotControlService::IScreenShotControlService(Core::System& system_)
9 : ServiceFramework{system_, "caps:sc"} {
9 // clang-format off 10 // clang-format off
10 static const FunctionInfo functions[] = { 11 static const FunctionInfo functions[] = {
11 {1, nullptr, "CaptureRawImage"}, 12 {1, nullptr, "CaptureRawImage"},
@@ -34,6 +35,6 @@ CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
34 RegisterHandlers(functions); 35 RegisterHandlers(functions);
35} 36}
36 37
37CAPS_SC::~CAPS_SC() = default; 38IScreenShotControlService::~IScreenShotControlService() = default;
38 39
39} // namespace Service::Capture 40} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h
index e5600f6d7..d555f4979 100644
--- a/src/core/hle/service/caps/caps_sc.h
+++ b/src/core/hle/service/caps/caps_sc.h
@@ -11,10 +11,10 @@ class System;
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13 13
14class CAPS_SC final : public ServiceFramework<CAPS_SC> { 14class IScreenShotControlService final : public ServiceFramework<IScreenShotControlService> {
15public: 15public:
16 explicit CAPS_SC(Core::System& system_); 16 explicit IScreenShotControlService(Core::System& system_);
17 ~CAPS_SC() override; 17 ~IScreenShotControlService() override;
18}; 18};
19 19
20} // namespace Service::Capture 20} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp
index 62b9edd41..d0d1b5425 100644
--- a/src/core/hle/service/caps/caps_ss.cpp
+++ b/src/core/hle/service/caps/caps_ss.cpp
@@ -5,7 +5,8 @@
5 5
6namespace Service::Capture { 6namespace Service::Capture {
7 7
8CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} { 8IScreenShotService::IScreenShotService(Core::System& system_)
9 : ServiceFramework{system_, "caps:ss"} {
9 // clang-format off 10 // clang-format off
10 static const FunctionInfo functions[] = { 11 static const FunctionInfo functions[] = {
11 {201, nullptr, "SaveScreenShot"}, 12 {201, nullptr, "SaveScreenShot"},
@@ -21,6 +22,6 @@ CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
21 RegisterHandlers(functions); 22 RegisterHandlers(functions);
22} 23}
23 24
24CAPS_SS::~CAPS_SS() = default; 25IScreenShotService::~IScreenShotService() = default;
25 26
26} // namespace Service::Capture 27} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h
index 718ade485..381e44fd4 100644
--- a/src/core/hle/service/caps/caps_ss.h
+++ b/src/core/hle/service/caps/caps_ss.h
@@ -11,10 +11,10 @@ class System;
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13 13
14class CAPS_SS final : public ServiceFramework<CAPS_SS> { 14class IScreenShotService final : public ServiceFramework<IScreenShotService> {
15public: 15public:
16 explicit CAPS_SS(Core::System& system_); 16 explicit IScreenShotService(Core::System& system_);
17 ~CAPS_SS() override; 17 ~IScreenShotService() override;
18}; 18};
19 19
20} // namespace Service::Capture 20} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp
index 3b11cc95c..cad173dc7 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -7,10 +7,11 @@
7 7
8namespace Service::Capture { 8namespace Service::Capture {
9 9
10CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} { 10IScreenShotApplicationService::IScreenShotApplicationService(Core::System& system_)
11 : ServiceFramework{system_, "caps:su"} {
11 // clang-format off 12 // clang-format off
12 static const FunctionInfo functions[] = { 13 static const FunctionInfo functions[] = {
13 {32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"}, 14 {32, &IScreenShotApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
14 {201, nullptr, "SaveScreenShot"}, 15 {201, nullptr, "SaveScreenShot"},
15 {203, nullptr, "SaveScreenShotEx0"}, 16 {203, nullptr, "SaveScreenShotEx0"},
16 {205, nullptr, "SaveScreenShotEx1"}, 17 {205, nullptr, "SaveScreenShotEx1"},
@@ -21,9 +22,9 @@ CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
21 RegisterHandlers(functions); 22 RegisterHandlers(functions);
22} 23}
23 24
24CAPS_SU::~CAPS_SU() = default; 25IScreenShotApplicationService::~IScreenShotApplicationService() = default;
25 26
26void CAPS_SU::SetShimLibraryVersion(HLERequestContext& ctx) { 27void IScreenShotApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
27 IPC::RequestParser rp{ctx}; 28 IPC::RequestParser rp{ctx};
28 const auto library_version{rp.Pop<u64>()}; 29 const auto library_version{rp.Pop<u64>()};
29 const auto applet_resource_user_id{rp.Pop<u64>()}; 30 const auto applet_resource_user_id{rp.Pop<u64>()};
diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h
index c6398858d..647e3059d 100644
--- a/src/core/hle/service/caps/caps_su.h
+++ b/src/core/hle/service/caps/caps_su.h
@@ -11,10 +11,10 @@ class System;
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13 13
14class CAPS_SU final : public ServiceFramework<CAPS_SU> { 14class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
15public: 15public:
16 explicit CAPS_SU(Core::System& system_); 16 explicit IScreenShotApplicationService(Core::System& system_);
17 ~CAPS_SU() override; 17 ~IScreenShotApplicationService() override;
18 18
19private: 19private:
20 void SetShimLibraryVersion(HLERequestContext& ctx); 20 void SetShimLibraryVersion(HLERequestContext& ctx);
diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h
new file mode 100644
index 000000000..7fd357954
--- /dev/null
+++ b/src/core/hle/service/caps/caps_types.h
@@ -0,0 +1,184 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace Service::Capture {
10
11// This is nn::album::ImageOrientation
12enum class AlbumImageOrientation {
13 None,
14 Rotate90,
15 Rotate180,
16 Rotate270,
17};
18
19// This is nn::album::AlbumReportOption
20enum class AlbumReportOption : s32 {
21 Disable,
22 Enable,
23};
24
25enum class ContentType : u8 {
26 Screenshot = 0,
27 Movie = 1,
28 ExtraMovie = 3,
29};
30
31enum class AlbumStorage : u8 {
32 Nand,
33 Sd,
34};
35
36enum class ScreenShotDecoderFlag : u64 {
37 None = 0,
38 EnableFancyUpsampling = 1 << 0,
39 EnableBlockSmoothing = 1 << 1,
40};
41
42// This is nn::capsrv::AlbumFileDateTime
43struct AlbumFileDateTime {
44 s16 year{};
45 s8 month{};
46 s8 day{};
47 s8 hour{};
48 s8 minute{};
49 s8 second{};
50 s8 unique_id{};
51
52 friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
53 friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
54 if (a.year > b.year) {
55 return true;
56 }
57 if (a.month > b.month) {
58 return true;
59 }
60 if (a.day > b.day) {
61 return true;
62 }
63 if (a.hour > b.hour) {
64 return true;
65 }
66 if (a.minute > b.minute) {
67 return true;
68 }
69 return a.second > b.second;
70 };
71 friend constexpr bool operator<(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
72 if (a.year < b.year) {
73 return true;
74 }
75 if (a.month < b.month) {
76 return true;
77 }
78 if (a.day < b.day) {
79 return true;
80 }
81 if (a.hour < b.hour) {
82 return true;
83 }
84 if (a.minute < b.minute) {
85 return true;
86 }
87 return a.second < b.second;
88 };
89};
90static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
91
92// This is nn::album::AlbumEntry
93struct AlbumFileEntry {
94 u64 size{}; // Size of the entry
95 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
96 AlbumFileDateTime datetime{};
97 AlbumStorage storage{};
98 ContentType content{};
99 INSERT_PADDING_BYTES(5);
100 u8 unknown{}; // Set to 1 on official SW
101};
102static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
103
104struct AlbumFileId {
105 u64 application_id{};
106 AlbumFileDateTime date{};
107 AlbumStorage storage{};
108 ContentType type{};
109 INSERT_PADDING_BYTES(0x5);
110 u8 unknown{};
111
112 friend constexpr bool operator==(const AlbumFileId&, const AlbumFileId&) = default;
113};
114static_assert(sizeof(AlbumFileId) == 0x18, "AlbumFileId is an invalid size");
115
116// This is nn::capsrv::AlbumEntry
117struct AlbumEntry {
118 u64 entry_size{};
119 AlbumFileId file_id{};
120};
121static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
122
123// This is nn::capsrv::ApplicationAlbumEntry
124struct ApplicationAlbumEntry {
125 u64 size{}; // Size of the entry
126 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
127 AlbumFileDateTime datetime{};
128 AlbumStorage storage{};
129 ContentType content{};
130 INSERT_PADDING_BYTES(5);
131 u8 unknown{1}; // Set to 1 on official SW
132};
133static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
134
135// This is nn::capsrv::ApplicationAlbumFileEntry
136struct ApplicationAlbumFileEntry {
137 ApplicationAlbumEntry entry{};
138 AlbumFileDateTime datetime{};
139 u64 unknown{};
140};
141static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
142 "ApplicationAlbumFileEntry has incorrect size.");
143
144struct ApplicationData {
145 std::array<u8, 0x400> data{};
146 u32 data_size{};
147};
148static_assert(sizeof(ApplicationData) == 0x404, "ApplicationData is an invalid size");
149
150struct ScreenShotAttribute {
151 u32 unknown_0{};
152 AlbumImageOrientation orientation{};
153 u32 unknown_1{};
154 u32 unknown_2{};
155 INSERT_PADDING_BYTES(0x30);
156};
157static_assert(sizeof(ScreenShotAttribute) == 0x40, "ScreenShotAttribute is an invalid size");
158
159struct ScreenShotDecodeOption {
160 ScreenShotDecoderFlag flags{};
161 INSERT_PADDING_BYTES(0x18);
162};
163static_assert(sizeof(ScreenShotDecodeOption) == 0x20, "ScreenShotDecodeOption is an invalid size");
164
165struct LoadAlbumScreenShotImageOutput {
166 s64 width{};
167 s64 height{};
168 ScreenShotAttribute attribute{};
169 INSERT_PADDING_BYTES(0x400);
170};
171static_assert(sizeof(LoadAlbumScreenShotImageOutput) == 0x450,
172 "LoadAlbumScreenShotImageOutput is an invalid size");
173
174struct LoadAlbumScreenShotImageOutputForApplication {
175 s64 width{};
176 s64 height{};
177 ScreenShotAttribute attribute{};
178 ApplicationData data{};
179 INSERT_PADDING_BYTES(0xAC);
180};
181static_assert(sizeof(LoadAlbumScreenShotImageOutputForApplication) == 0x500,
182 "LoadAlbumScreenShotImageOutput is an invalid size");
183
184} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp
index bffe0f8d0..b6b33fb2f 100644
--- a/src/core/hle/service/caps/caps_u.cpp
+++ b/src/core/hle/service/caps/caps_u.cpp
@@ -2,45 +2,29 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/logging/log.h" 4#include "common/logging/log.h"
5#include "core/hle/service/caps/caps.h" 5#include "core/hle/service/caps/caps_manager.h"
6#include "core/hle/service/caps/caps_types.h"
6#include "core/hle/service/caps/caps_u.h" 7#include "core/hle/service/caps/caps_u.h"
7#include "core/hle/service/ipc_helpers.h" 8#include "core/hle/service/ipc_helpers.h"
8 9
9namespace Service::Capture { 10namespace Service::Capture {
10 11
11class IAlbumAccessorApplicationSession final 12IAlbumApplicationService::IAlbumApplicationService(Core::System& system_,
12 : public ServiceFramework<IAlbumAccessorApplicationSession> { 13 std::shared_ptr<AlbumManager> album_manager)
13public: 14 : ServiceFramework{system_, "caps:u"}, manager{album_manager} {
14 explicit IAlbumAccessorApplicationSession(Core::System& system_)
15 : ServiceFramework{system_, "IAlbumAccessorApplicationSession"} {
16 // clang-format off
17 static const FunctionInfo functions[] = {
18 {2001, nullptr, "OpenAlbumMovieReadStream"},
19 {2002, nullptr, "CloseAlbumMovieReadStream"},
20 {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
21 {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
22 {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
23 };
24 // clang-format on
25
26 RegisterHandlers(functions);
27 }
28};
29
30CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
31 // clang-format off 15 // clang-format off
32 static const FunctionInfo functions[] = { 16 static const FunctionInfo functions[] = {
33 {32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"}, 17 {32, &IAlbumApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
34 {102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"}, 18 {102, &IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated, "GetAlbumFileList0AafeAruidDeprecated"},
35 {103, nullptr, "DeleteAlbumContentsFileForApplication"}, 19 {103, nullptr, "DeleteAlbumFileByAruid"},
36 {104, nullptr, "GetAlbumContentsFileSizeForApplication"}, 20 {104, nullptr, "GetAlbumFileSizeByAruid"},
37 {105, nullptr, "DeleteAlbumFileByAruidForDebug"}, 21 {105, nullptr, "DeleteAlbumFileByAruidForDebug"},
38 {110, nullptr, "LoadAlbumContentsFileScreenShotImageForApplication"}, 22 {110, nullptr, "LoadAlbumScreenShotImageByAruid"},
39 {120, nullptr, "LoadAlbumContentsFileThumbnailImageForApplication"}, 23 {120, nullptr, "LoadAlbumScreenShotThumbnailImageByAruid"},
40 {130, nullptr, "PrecheckToCreateContentsForApplication"}, 24 {130, nullptr, "PrecheckToCreateContentsByAruid"},
41 {140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"}, 25 {140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
42 {141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"}, 26 {141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
43 {142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"}, 27 {142, &IAlbumApplicationService::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
44 {143, nullptr, "GetAlbumFileList4AaeUidAruid"}, 28 {143, nullptr, "GetAlbumFileList4AaeUidAruid"},
45 {144, nullptr, "GetAllAlbumFileList3AaeAruid"}, 29 {144, nullptr, "GetAllAlbumFileList3AaeAruid"},
46 {60002, nullptr, "OpenAccessorSessionForApplication"}, 30 {60002, nullptr, "OpenAccessorSessionForApplication"},
@@ -50,9 +34,9 @@ CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
50 RegisterHandlers(functions); 34 RegisterHandlers(functions);
51} 35}
52 36
53CAPS_U::~CAPS_U() = default; 37IAlbumApplicationService::~IAlbumApplicationService() = default;
54 38
55void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) { 39void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
56 IPC::RequestParser rp{ctx}; 40 IPC::RequestParser rp{ctx};
57 const auto library_version{rp.Pop<u64>()}; 41 const auto library_version{rp.Pop<u64>()};
58 const auto applet_resource_user_id{rp.Pop<u64>()}; 42 const auto applet_resource_user_id{rp.Pop<u64>()};
@@ -64,37 +48,89 @@ void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) {
64 rb.Push(ResultSuccess); 48 rb.Push(ResultSuccess);
65} 49}
66 50
67void CAPS_U::GetAlbumContentsFileListForApplication(HLERequestContext& ctx) { 51void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
68 // Takes a type-0x6 output buffer containing an array of ApplicationAlbumFileEntry, a PID, an
69 // u8 ContentType, two s64s, and an u64 AppletResourceUserId. Returns an output u64 for total
70 // output entries (which is copied to a s32 by official SW).
71 IPC::RequestParser rp{ctx}; 52 IPC::RequestParser rp{ctx};
72 const auto pid{rp.Pop<s32>()}; 53 struct Parameters {
73 const auto content_type{rp.PopEnum<ContentType>()}; 54 ContentType content_type;
74 const auto start_posix_time{rp.Pop<s64>()}; 55 INSERT_PADDING_BYTES(7);
75 const auto end_posix_time{rp.Pop<s64>()}; 56 s64 start_posix_time;
76 const auto applet_resource_user_id{rp.Pop<u64>()}; 57 s64 end_posix_time;
58 u64 applet_resource_user_id;
59 };
60 static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
77 61
78 // TODO: Update this when we implement the album. 62 const auto parameters{rp.PopRaw<Parameters>()};
79 // Currently we do not have a method of accessing album entries, set this to 0 for now.
80 constexpr u32 total_entries_1{};
81 constexpr u32 total_entries_2{};
82 63
83 LOG_WARNING( 64 LOG_WARNING(Service_Capture,
84 Service_Capture, 65 "(STUBBED) called. content_type={}, start_posix_time={}, end_posix_time={}, "
85 "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, " 66 "applet_resource_user_id={}",
86 "end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}", 67 parameters.content_type, parameters.start_posix_time, parameters.end_posix_time,
87 pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id, 68 parameters.applet_resource_user_id);
88 total_entries_1, total_entries_2); 69
70 Result result = ResultSuccess;
71
72 if (result.IsSuccess()) {
73 result = manager->IsAlbumMounted(AlbumStorage::Sd);
74 }
75
76 std::vector<ApplicationAlbumFileEntry> entries;
77 if (result.IsSuccess()) {
78 result = manager->GetAlbumFileList(entries, parameters.content_type,
79 parameters.start_posix_time, parameters.end_posix_time,
80 parameters.applet_resource_user_id);
81 }
82
83 if (!entries.empty()) {
84 ctx.WriteBuffer(entries);
85 }
89 86
90 IPC::ResponseBuilder rb{ctx, 4}; 87 IPC::ResponseBuilder rb{ctx, 4};
91 rb.Push(ResultSuccess); 88 rb.Push(result);
92 rb.Push(total_entries_1); 89 rb.Push<u64>(entries.size());
93 rb.Push(total_entries_2);
94} 90}
95 91
96void CAPS_U::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) { 92void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
97 GetAlbumContentsFileListForApplication(ctx); 93 IPC::RequestParser rp{ctx};
94 struct Parameters {
95 ContentType content_type;
96 INSERT_PADDING_BYTES(1);
97 AlbumFileDateTime start_date_time;
98 AlbumFileDateTime end_date_time;
99 INSERT_PADDING_BYTES(6);
100 u64 applet_resource_user_id;
101 };
102 static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
103
104 const auto parameters{rp.PopRaw<Parameters>()};
105
106 LOG_WARNING(Service_Capture,
107 "(STUBBED) called. content_type={}, start_date={}/{}/{}, "
108 "end_date={}/{}/{}, applet_resource_user_id={}",
109 parameters.content_type, parameters.start_date_time.year,
110 parameters.start_date_time.month, parameters.start_date_time.day,
111 parameters.end_date_time.year, parameters.end_date_time.month,
112 parameters.end_date_time.day, parameters.applet_resource_user_id);
113
114 Result result = ResultSuccess;
115
116 if (result.IsSuccess()) {
117 result = manager->IsAlbumMounted(AlbumStorage::Sd);
118 }
119
120 std::vector<ApplicationAlbumEntry> entries;
121 if (result.IsSuccess()) {
122 result =
123 manager->GetAlbumFileList(entries, parameters.content_type, parameters.start_date_time,
124 parameters.end_date_time, parameters.applet_resource_user_id);
125 }
126
127 if (!entries.empty()) {
128 ctx.WriteBuffer(entries);
129 }
130
131 IPC::ResponseBuilder rb{ctx, 4};
132 rb.Push(result);
133 rb.Push<u64>(entries.size());
98} 134}
99 135
100} // namespace Service::Capture 136} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h
index e8dd037d7..9458c128e 100644
--- a/src/core/hle/service/caps/caps_u.h
+++ b/src/core/hle/service/caps/caps_u.h
@@ -10,16 +10,20 @@ class System;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13class AlbumManager;
13 14
14class CAPS_U final : public ServiceFramework<CAPS_U> { 15class IAlbumApplicationService final : public ServiceFramework<IAlbumApplicationService> {
15public: 16public:
16 explicit CAPS_U(Core::System& system_); 17 explicit IAlbumApplicationService(Core::System& system_,
17 ~CAPS_U() override; 18 std::shared_ptr<AlbumManager> album_manager);
19 ~IAlbumApplicationService() override;
18 20
19private: 21private:
20 void SetShimLibraryVersion(HLERequestContext& ctx); 22 void SetShimLibraryVersion(HLERequestContext& ctx);
21 void GetAlbumContentsFileListForApplication(HLERequestContext& ctx); 23 void GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx);
22 void GetAlbumFileList3AaeAruid(HLERequestContext& ctx); 24 void GetAlbumFileList3AaeAruid(HLERequestContext& ctx);
25
26 std::shared_ptr<AlbumManager> manager = nullptr;
23}; 27};
24 28
25} // namespace Service::Capture 29} // namespace Service::Capture
diff --git a/src/core/hle/service/hle_ipc.cpp b/src/core/hle/service/hle_ipc.cpp
index f6a1e54f2..6f3ae3cc4 100644
--- a/src/core/hle/service/hle_ipc.cpp
+++ b/src/core/hle/service/hle_ipc.cpp
@@ -23,6 +23,17 @@
23#include "core/hle/service/ipc_helpers.h" 23#include "core/hle/service/ipc_helpers.h"
24#include "core/memory.h" 24#include "core/memory.h"
25 25
26namespace {
27static thread_local std::array read_buffer_data_a{
28 Common::ScratchBuffer<u8>(),
29 Common::ScratchBuffer<u8>(),
30};
31static thread_local std::array read_buffer_data_x{
32 Common::ScratchBuffer<u8>(),
33 Common::ScratchBuffer<u8>(),
34};
35} // Anonymous namespace
36
26namespace Service { 37namespace Service {
27 38
28SessionRequestHandler::SessionRequestHandler(Kernel::KernelCore& kernel_, const char* service_name_) 39SessionRequestHandler::SessionRequestHandler(Kernel::KernelCore& kernel_, const char* service_name_)
@@ -328,26 +339,57 @@ std::vector<u8> HLERequestContext::ReadBufferCopy(std::size_t buffer_index) cons
328 } 339 }
329} 340}
330 341
331std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const { 342std::span<const u8> HLERequestContext::ReadBufferA(std::size_t buffer_index) const {
332 static thread_local std::array read_buffer_a{ 343 static thread_local std::array read_buffer_a{
333 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), 344 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
334 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), 345 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
335 }; 346 };
336 static thread_local std::array read_buffer_data_a{ 347
337 Common::ScratchBuffer<u8>(), 348 ASSERT_OR_EXECUTE_MSG(
338 Common::ScratchBuffer<u8>(), 349 BufferDescriptorA().size() > buffer_index, { return {}; },
339 }; 350 "BufferDescriptorA invalid buffer_index {}", buffer_index);
351 auto& read_buffer = read_buffer_a[buffer_index];
352 return read_buffer.Read(BufferDescriptorA()[buffer_index].Address(),
353 BufferDescriptorA()[buffer_index].Size(),
354 &read_buffer_data_a[buffer_index]);
355}
356
357std::span<const u8> HLERequestContext::ReadBufferX(std::size_t buffer_index) const {
340 static thread_local std::array read_buffer_x{ 358 static thread_local std::array read_buffer_x{
341 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), 359 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
342 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), 360 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
343 }; 361 };
344 static thread_local std::array read_buffer_data_x{ 362
345 Common::ScratchBuffer<u8>(), 363 ASSERT_OR_EXECUTE_MSG(
346 Common::ScratchBuffer<u8>(), 364 BufferDescriptorX().size() > buffer_index, { return {}; },
365 "BufferDescriptorX invalid buffer_index {}", buffer_index);
366 auto& read_buffer = read_buffer_x[buffer_index];
367 return read_buffer.Read(BufferDescriptorX()[buffer_index].Address(),
368 BufferDescriptorX()[buffer_index].Size(),
369 &read_buffer_data_x[buffer_index]);
370}
371
372std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const {
373 static thread_local std::array read_buffer_a{
374 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
375 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
376 };
377 static thread_local std::array read_buffer_x{
378 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
379 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
347 }; 380 };
348 381
349 const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && 382 const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
350 BufferDescriptorA()[buffer_index].Size()}; 383 BufferDescriptorA()[buffer_index].Size()};
384 const bool is_buffer_x{BufferDescriptorX().size() > buffer_index &&
385 BufferDescriptorX()[buffer_index].Size()};
386
387 if (is_buffer_a && is_buffer_x) {
388 LOG_WARNING(Input, "Both buffer descriptors are available a.size={}, x.size={}",
389 BufferDescriptorA()[buffer_index].Size(),
390 BufferDescriptorX()[buffer_index].Size());
391 }
392
351 if (is_buffer_a) { 393 if (is_buffer_a) {
352 ASSERT_OR_EXECUTE_MSG( 394 ASSERT_OR_EXECUTE_MSG(
353 BufferDescriptorA().size() > buffer_index, { return {}; }, 395 BufferDescriptorA().size() > buffer_index, { return {}; },
diff --git a/src/core/hle/service/hle_ipc.h b/src/core/hle/service/hle_ipc.h
index 4bd24c899..ad5259a5c 100644
--- a/src/core/hle/service/hle_ipc.h
+++ b/src/core/hle/service/hle_ipc.h
@@ -253,6 +253,12 @@ public:
253 return domain_message_header.has_value(); 253 return domain_message_header.has_value();
254 } 254 }
255 255
256 /// Helper function to get a span of a buffer using the buffer descriptor A
257 [[nodiscard]] std::span<const u8> ReadBufferA(std::size_t buffer_index = 0) const;
258
259 /// Helper function to get a span of a buffer using the buffer descriptor X
260 [[nodiscard]] std::span<const u8> ReadBufferX(std::size_t buffer_index = 0) const;
261
256 /// Helper function to get a span of a buffer using the appropriate buffer descriptor 262 /// Helper function to get a span of a buffer using the appropriate buffer descriptor
257 [[nodiscard]] std::span<const u8> ReadBuffer(std::size_t buffer_index = 0) const; 263 [[nodiscard]] std::span<const u8> ReadBuffer(std::size_t buffer_index = 0) const;
258 264
diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp
index 4ed3f02e2..0090e8568 100644
--- a/src/core/hle/service/jit/jit_context.cpp
+++ b/src/core/hle/service/jit/jit_context.cpp
@@ -156,6 +156,8 @@ public:
156 156
157 bool LoadNRO(std::span<const u8> data) { 157 bool LoadNRO(std::span<const u8> data) {
158 local_memory.clear(); 158 local_memory.clear();
159
160 relocbase = local_memory.size();
159 local_memory.insert(local_memory.end(), data.begin(), data.end()); 161 local_memory.insert(local_memory.end(), data.begin(), data.end());
160 162
161 if (FixupRelocations()) { 163 if (FixupRelocations()) {
@@ -181,8 +183,8 @@ public:
181 // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html 183 // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
182 // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html 184 // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
183 VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; 185 VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
184 VAddr rela_dyn = 0; 186 VAddr rela_dyn = 0, relr_dyn = 0;
185 size_t num_rela = 0; 187 size_t num_rela = 0, num_relr = 0;
186 while (true) { 188 while (true) {
187 const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)}; 189 const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
188 dynamic_offset += sizeof(Elf64_Dyn); 190 dynamic_offset += sizeof(Elf64_Dyn);
@@ -196,6 +198,12 @@ public:
196 if (dyn.d_tag == ElfDtRelasz) { 198 if (dyn.d_tag == ElfDtRelasz) {
197 num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela); 199 num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela);
198 } 200 }
201 if (dyn.d_tag == ElfDtRelr) {
202 relr_dyn = dyn.d_un.d_ptr;
203 }
204 if (dyn.d_tag == ElfDtRelrsz) {
205 num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr);
206 }
199 } 207 }
200 208
201 for (size_t i = 0; i < num_rela; i++) { 209 for (size_t i = 0; i < num_rela; i++) {
@@ -207,6 +215,29 @@ public:
207 callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); 215 callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
208 } 216 }
209 217
218 VAddr relr_where = 0;
219 for (size_t i = 0; i < num_relr; i++) {
220 const auto relr{callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr))};
221 const auto incr{[&](VAddr where) {
222 callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase);
223 }};
224
225 if ((relr & 1) == 0) {
226 // where pointer
227 relr_where = relocbase + relr;
228 incr(relr_where);
229 relr_where += sizeof(Elf64_Addr);
230 } else {
231 // bitmap
232 for (int bit = 1; bit < 64; bit++) {
233 if ((relr & (1ULL << bit)) != 0) {
234 incr(relr_where + i * sizeof(Elf64_Addr));
235 }
236 }
237 relr_where += 63 * sizeof(Elf64_Addr);
238 }
239 }
240
210 return true; 241 return true;
211 } 242 }
212 243
@@ -313,6 +344,7 @@ public:
313 Core::Memory::Memory& memory; 344 Core::Memory::Memory& memory;
314 VAddr top_of_stack; 345 VAddr top_of_stack;
315 VAddr heap_pointer; 346 VAddr heap_pointer;
347 VAddr relocbase;
316}; 348};
317 349
318void DynarmicCallbacks64::CallSVC(u32 swi) { 350void DynarmicCallbacks64::CallSVC(u32 swi) {
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 21b06d10b..22dc55a6d 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -545,6 +545,16 @@ void IGeneralService::IsAnyInternetRequestAccepted(HLERequestContext& ctx) {
545 } 545 }
546} 546}
547 547
548void IGeneralService::IsAnyForegroundRequestAccepted(HLERequestContext& ctx) {
549 const bool is_accepted{};
550
551 LOG_WARNING(Service_NIFM, "(STUBBED) called, is_accepted={}", is_accepted);
552
553 IPC::ResponseBuilder rb{ctx, 3};
554 rb.Push(ResultSuccess);
555 rb.Push<u8>(is_accepted);
556}
557
548IGeneralService::IGeneralService(Core::System& system_) 558IGeneralService::IGeneralService(Core::System& system_)
549 : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} { 559 : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} {
550 // clang-format off 560 // clang-format off
@@ -569,7 +579,7 @@ IGeneralService::IGeneralService(Core::System& system_)
569 {19, nullptr, "SetEthernetCommunicationEnabled"}, 579 {19, nullptr, "SetEthernetCommunicationEnabled"},
570 {20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"}, 580 {20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"},
571 {21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"}, 581 {21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"},
572 {22, nullptr, "IsAnyForegroundRequestAccepted"}, 582 {22, &IGeneralService::IsAnyForegroundRequestAccepted, "IsAnyForegroundRequestAccepted"},
573 {23, nullptr, "PutToSleep"}, 583 {23, nullptr, "PutToSleep"},
574 {24, nullptr, "WakeUp"}, 584 {24, nullptr, "WakeUp"},
575 {25, nullptr, "GetSsidListVersion"}, 585 {25, nullptr, "GetSsidListVersion"},
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
index ae99c4695..b74b66438 100644
--- a/src/core/hle/service/nifm/nifm.h
+++ b/src/core/hle/service/nifm/nifm.h
@@ -35,6 +35,7 @@ private:
35 void GetInternetConnectionStatus(HLERequestContext& ctx); 35 void GetInternetConnectionStatus(HLERequestContext& ctx);
36 void IsEthernetCommunicationEnabled(HLERequestContext& ctx); 36 void IsEthernetCommunicationEnabled(HLERequestContext& ctx);
37 void IsAnyInternetRequestAccepted(HLERequestContext& ctx); 37 void IsAnyInternetRequestAccepted(HLERequestContext& ctx);
38 void IsAnyForegroundRequestAccepted(HLERequestContext& ctx);
38 39
39 Network::RoomNetwork& network; 40 Network::RoomNetwork& network;
40}; 41};
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 6e0baf0be..f9e0e272d 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -7,6 +7,7 @@
7#include "core/file_sys/control_metadata.h" 7#include "core/file_sys/control_metadata.h"
8#include "core/file_sys/patch_manager.h" 8#include "core/file_sys/patch_manager.h"
9#include "core/file_sys/vfs.h" 9#include "core/file_sys/vfs.h"
10#include "core/hle/service/filesystem/filesystem.h"
10#include "core/hle/service/glue/glue_manager.h" 11#include "core/hle/service/glue/glue_manager.h"
11#include "core/hle/service/ipc_helpers.h" 12#include "core/hle/service/ipc_helpers.h"
12#include "core/hle/service/ns/errors.h" 13#include "core/hle/service/ns/errors.h"
@@ -502,8 +503,8 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
502 static const FunctionInfo functions[] = { 503 static const FunctionInfo functions[] = {
503 {11, nullptr, "CalculateApplicationOccupiedSize"}, 504 {11, nullptr, "CalculateApplicationOccupiedSize"},
504 {43, nullptr, "CheckSdCardMountStatus"}, 505 {43, nullptr, "CheckSdCardMountStatus"},
505 {47, nullptr, "GetTotalSpaceSize"}, 506 {47, &IContentManagementInterface::GetTotalSpaceSize, "GetTotalSpaceSize"},
506 {48, nullptr, "GetFreeSpaceSize"}, 507 {48, &IContentManagementInterface::GetFreeSpaceSize, "GetFreeSpaceSize"},
507 {600, nullptr, "CountApplicationContentMeta"}, 508 {600, nullptr, "CountApplicationContentMeta"},
508 {601, nullptr, "ListApplicationContentMetaStatus"}, 509 {601, nullptr, "ListApplicationContentMetaStatus"},
509 {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"}, 510 {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
@@ -516,6 +517,28 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
516 517
517IContentManagementInterface::~IContentManagementInterface() = default; 518IContentManagementInterface::~IContentManagementInterface() = default;
518 519
520void IContentManagementInterface::GetTotalSpaceSize(HLERequestContext& ctx) {
521 IPC::RequestParser rp{ctx};
522 const auto storage{rp.PopEnum<FileSys::StorageId>()};
523
524 LOG_INFO(Service_Capture, "called, storage={}", storage);
525
526 IPC::ResponseBuilder rb{ctx, 4};
527 rb.Push(ResultSuccess);
528 rb.Push<u64>(system.GetFileSystemController().GetTotalSpaceSize(storage));
529}
530
531void IContentManagementInterface::GetFreeSpaceSize(HLERequestContext& ctx) {
532 IPC::RequestParser rp{ctx};
533 const auto storage{rp.PopEnum<FileSys::StorageId>()};
534
535 LOG_INFO(Service_Capture, "called, storage={}", storage);
536
537 IPC::ResponseBuilder rb{ctx, 4};
538 rb.Push(ResultSuccess);
539 rb.Push<u64>(system.GetFileSystemController().GetFreeSpaceSize(storage));
540}
541
519IDocumentInterface::IDocumentInterface(Core::System& system_) 542IDocumentInterface::IDocumentInterface(Core::System& system_)
520 : ServiceFramework{system_, "IDocumentInterface"} { 543 : ServiceFramework{system_, "IDocumentInterface"} {
521 // clang-format off 544 // clang-format off
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 175dad780..34d2a45dc 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -48,6 +48,10 @@ class IContentManagementInterface final : public ServiceFramework<IContentManage
48public: 48public:
49 explicit IContentManagementInterface(Core::System& system_); 49 explicit IContentManagementInterface(Core::System& system_);
50 ~IContentManagementInterface() override; 50 ~IContentManagementInterface() override;
51
52private:
53 void GetTotalSpaceSize(HLERequestContext& ctx);
54 void GetFreeSpaceSize(HLERequestContext& ctx);
51}; 55};
52 56
53class IDocumentInterface final : public ServiceFramework<IDocumentInterface> { 57class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp
index 5db1703d1..938330dd0 100644
--- a/src/core/hle/service/pctl/pctl_module.cpp
+++ b/src/core/hle/service/pctl/pctl_module.cpp
@@ -33,7 +33,7 @@ public:
33 {1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"}, 33 {1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"},
34 {1002, nullptr, "ConfirmLaunchApplicationPermission"}, 34 {1002, nullptr, "ConfirmLaunchApplicationPermission"},
35 {1003, nullptr, "ConfirmResumeApplicationPermission"}, 35 {1003, nullptr, "ConfirmResumeApplicationPermission"},
36 {1004, nullptr, "ConfirmSnsPostPermission"}, 36 {1004, &IParentalControlService::ConfirmSnsPostPermission, "ConfirmSnsPostPermission"},
37 {1005, nullptr, "ConfirmSystemSettingsPermission"}, 37 {1005, nullptr, "ConfirmSystemSettingsPermission"},
38 {1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"}, 38 {1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"},
39 {1007, nullptr, "RevertRestrictionTemporaryUnlocked"}, 39 {1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
@@ -236,6 +236,13 @@ private:
236 states.free_communication = true; 236 states.free_communication = true;
237 } 237 }
238 238
239 void ConfirmSnsPostPermission(HLERequestContext& ctx) {
240 LOG_WARNING(Service_PCTL, "(STUBBED) called");
241
242 IPC::ResponseBuilder rb{ctx, 2};
243 rb.Push(Error::ResultNoFreeCommunication);
244 }
245
239 void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) { 246 void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) {
240 const bool is_temporary_unlocked = false; 247 const bool is_temporary_unlocked = false;
241 248
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index ec4a84989..14e8df63a 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -58,14 +58,8 @@ private:
58 IPC::RequestParser rp{ctx}; 58 IPC::RequestParser rp{ctx};
59 const auto process_id = rp.PopRaw<u64>(); 59 const auto process_id = rp.PopRaw<u64>();
60 60
61 const auto data1 = ctx.ReadBuffer(0); 61 const auto data1 = ctx.ReadBufferA(0);
62 const auto data2 = [&ctx] { 62 const auto data2 = ctx.ReadBufferX(0);
63 if (ctx.CanReadBuffer(1)) {
64 return ctx.ReadBuffer(1);
65 }
66
67 return std::span<const u8>{};
68 }();
69 63
70 LOG_DEBUG(Service_PREPO, 64 LOG_DEBUG(Service_PREPO,
71 "called, type={:02X}, process_id={:016X}, data1_size={:016X}, data2_size={:016X}", 65 "called, type={:02X}, process_id={:016X}, data1_size={:016X}, data2_size={:016X}",
@@ -85,14 +79,8 @@ private:
85 const auto user_id = rp.PopRaw<u128>(); 79 const auto user_id = rp.PopRaw<u128>();
86 const auto process_id = rp.PopRaw<u64>(); 80 const auto process_id = rp.PopRaw<u64>();
87 81
88 const auto data1 = ctx.ReadBuffer(0); 82 const auto data1 = ctx.ReadBufferA(0);
89 const auto data2 = [&ctx] { 83 const auto data2 = ctx.ReadBufferX(0);
90 if (ctx.CanReadBuffer(1)) {
91 return ctx.ReadBuffer(1);
92 }
93
94 return std::span<const u8>{};
95 }();
96 84
97 LOG_DEBUG(Service_PREPO, 85 LOG_DEBUG(Service_PREPO,
98 "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, " 86 "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, "
@@ -137,14 +125,8 @@ private:
137 IPC::RequestParser rp{ctx}; 125 IPC::RequestParser rp{ctx};
138 const auto title_id = rp.PopRaw<u64>(); 126 const auto title_id = rp.PopRaw<u64>();
139 127
140 const auto data1 = ctx.ReadBuffer(0); 128 const auto data1 = ctx.ReadBufferA(0);
141 const auto data2 = [&ctx] { 129 const auto data2 = ctx.ReadBufferX(0);
142 if (ctx.CanReadBuffer(1)) {
143 return ctx.ReadBuffer(1);
144 }
145
146 return std::span<const u8>{};
147 }();
148 130
149 LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}", 131 LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}",
150 title_id, data1.size(), data2.size()); 132 title_id, data1.size(), data2.size());
@@ -161,14 +143,8 @@ private:
161 const auto user_id = rp.PopRaw<u128>(); 143 const auto user_id = rp.PopRaw<u128>();
162 const auto title_id = rp.PopRaw<u64>(); 144 const auto title_id = rp.PopRaw<u64>();
163 145
164 const auto data1 = ctx.ReadBuffer(0); 146 const auto data1 = ctx.ReadBufferA(0);
165 const auto data2 = [&ctx] { 147 const auto data2 = ctx.ReadBufferX(0);
166 if (ctx.CanReadBuffer(1)) {
167 return ctx.ReadBuffer(1);
168 }
169
170 return std::span<const u8>{};
171 }();
172 148
173 LOG_DEBUG(Service_PREPO, 149 LOG_DEBUG(Service_PREPO,
174 "called, user_id={:016X}{:016X}, title_id={:016X}, data1_size={:016X}, " 150 "called, user_id={:016X}{:016X}, title_id={:016X}, data1_size={:016X}, "
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 5a42dea48..5c36b71e5 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -118,7 +118,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
118 return {ResultStatus::ErrorMissingNPDM, {}}; 118 return {ResultStatus::ErrorMissingNPDM, {}};
119 } 119 }
120 120
121 const ResultStatus result2 = metadata.Load(npdm); 121 const ResultStatus result2 = metadata.Reload(npdm);
122 if (result2 != ResultStatus::Success) { 122 if (result2 != ResultStatus::Success) {
123 return {result2, {}}; 123 return {result2, {}};
124 } 124 }
diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
index 808b21069..77db60e92 100644
--- a/src/input_common/drivers/udp_client.cpp
+++ b/src/input_common/drivers/udp_client.cpp
@@ -338,6 +338,7 @@ void UDPClient::StartCommunication(std::size_t client, const std::string& host,
338 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { 338 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
339 const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index); 339 const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
340 PreSetController(identifier); 340 PreSetController(identifier);
341 PreSetMotion(identifier, 0);
341 } 342 }
342} 343}
343 344
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 9e90c587c..9b2698fad 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -544,7 +544,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
544 it++; 544 it++;
545 } 545 }
546 546
547 boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads; 547 boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads;
548 u64 total_size_bytes = 0; 548 u64 total_size_bytes = 0;
549 u64 largest_copy = 0; 549 u64 largest_copy = 0;
550 for (const IntervalSet& intervals : committed_ranges) { 550 for (const IntervalSet& intervals : committed_ranges) {
@@ -914,6 +914,11 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
914 914
915 const u32 offset = buffer.Offset(binding.cpu_addr); 915 const u32 offset = buffer.Offset(binding.cpu_addr);
916 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; 916 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
917
918 if (is_written) {
919 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
920 }
921
917 if constexpr (NEEDS_BIND_STORAGE_INDEX) { 922 if constexpr (NEEDS_BIND_STORAGE_INDEX) {
918 runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written); 923 runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
919 ++binding_index; 924 ++binding_index;
@@ -931,6 +936,11 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
931 const u32 size = binding.size; 936 const u32 size = binding.size;
932 SynchronizeBuffer(buffer, binding.cpu_addr, size); 937 SynchronizeBuffer(buffer, binding.cpu_addr, size);
933 938
939 const bool is_written = ((channel_state->written_texture_buffers[stage] >> index) & 1) != 0;
940 if (is_written) {
941 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
942 }
943
934 const u32 offset = buffer.Offset(binding.cpu_addr); 944 const u32 offset = buffer.Offset(binding.cpu_addr);
935 const PixelFormat format = binding.format; 945 const PixelFormat format = binding.format;
936 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 946 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@@ -962,6 +972,8 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
962 const u32 size = binding.size; 972 const u32 size = binding.size;
963 SynchronizeBuffer(buffer, binding.cpu_addr, size); 973 SynchronizeBuffer(buffer, binding.cpu_addr, size);
964 974
975 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
976
965 const u32 offset = buffer.Offset(binding.cpu_addr); 977 const u32 offset = buffer.Offset(binding.cpu_addr);
966 host_bindings.buffers.push_back(&buffer); 978 host_bindings.buffers.push_back(&buffer);
967 host_bindings.offsets.push_back(offset); 979 host_bindings.offsets.push_back(offset);
@@ -1011,6 +1023,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
1011 const u32 offset = buffer.Offset(binding.cpu_addr); 1023 const u32 offset = buffer.Offset(binding.cpu_addr);
1012 const bool is_written = 1024 const bool is_written =
1013 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; 1025 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
1026
1027 if (is_written) {
1028 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
1029 }
1030
1014 if constexpr (NEEDS_BIND_STORAGE_INDEX) { 1031 if constexpr (NEEDS_BIND_STORAGE_INDEX) {
1015 runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written); 1032 runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
1016 ++binding_index; 1033 ++binding_index;
@@ -1028,6 +1045,12 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
1028 const u32 size = binding.size; 1045 const u32 size = binding.size;
1029 SynchronizeBuffer(buffer, binding.cpu_addr, size); 1046 SynchronizeBuffer(buffer, binding.cpu_addr, size);
1030 1047
1048 const bool is_written =
1049 ((channel_state->written_compute_texture_buffers >> index) & 1) != 0;
1050 if (is_written) {
1051 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
1052 }
1053
1031 const u32 offset = buffer.Offset(binding.cpu_addr); 1054 const u32 offset = buffer.Offset(binding.cpu_addr);
1032 const PixelFormat format = binding.format; 1055 const PixelFormat format = binding.format;
1033 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 1056 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@@ -1201,16 +1224,11 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
1201 1224
1202template <class P> 1225template <class P>
1203void BufferCache<P>::UpdateStorageBuffers(size_t stage) { 1226void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
1204 const u32 written_mask = channel_state->written_storage_buffers[stage];
1205 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { 1227 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
1206 // Resolve buffer 1228 // Resolve buffer
1207 Binding& binding = channel_state->storage_buffers[stage][index]; 1229 Binding& binding = channel_state->storage_buffers[stage][index];
1208 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1230 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1209 binding.buffer_id = buffer_id; 1231 binding.buffer_id = buffer_id;
1210 // Mark buffer as written if needed
1211 if (((written_mask >> index) & 1) != 0) {
1212 MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size);
1213 }
1214 }); 1232 });
1215} 1233}
1216 1234
@@ -1219,10 +1237,6 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
1219 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { 1237 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
1220 Binding& binding = channel_state->texture_buffers[stage][index]; 1238 Binding& binding = channel_state->texture_buffers[stage][index];
1221 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1239 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1222 // Mark buffer as written if needed
1223 if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) {
1224 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1225 }
1226 }); 1240 });
1227} 1241}
1228 1242
@@ -1252,7 +1266,6 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
1252 .size = size, 1266 .size = size,
1253 .buffer_id = buffer_id, 1267 .buffer_id = buffer_id,
1254 }; 1268 };
1255 MarkWrittenBuffer(buffer_id, *cpu_addr, size);
1256} 1269}
1257 1270
1258template <class P> 1271template <class P>
@@ -1279,10 +1292,6 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
1279 // Resolve buffer 1292 // Resolve buffer
1280 Binding& binding = channel_state->compute_storage_buffers[index]; 1293 Binding& binding = channel_state->compute_storage_buffers[index];
1281 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1294 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1282 // Mark as written if needed
1283 if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) {
1284 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1285 }
1286 }); 1295 });
1287} 1296}
1288 1297
@@ -1291,18 +1300,11 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
1291 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { 1300 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
1292 Binding& binding = channel_state->compute_texture_buffers[index]; 1301 Binding& binding = channel_state->compute_texture_buffers[index];
1293 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1302 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1294 // Mark as written if needed
1295 if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) {
1296 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1297 }
1298 }); 1303 });
1299} 1304}
1300 1305
1301template <class P> 1306template <class P>
1302void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { 1307void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
1303 if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) {
1304 SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size);
1305 }
1306 memory_tracker.MarkRegionAsGpuModified(cpu_addr, size); 1308 memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
1307 1309
1308 const IntervalType base_interval{cpu_addr, cpu_addr + size}; 1310 const IntervalType base_interval{cpu_addr, cpu_addr + size};
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index c4f6e8d12..eed267361 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -62,7 +62,11 @@ using BufferId = SlotId;
62using VideoCore::Surface::PixelFormat; 62using VideoCore::Surface::PixelFormat;
63using namespace Common::Literals; 63using namespace Common::Literals;
64 64
65#ifdef __APPLE__
66constexpr u32 NUM_VERTEX_BUFFERS = 16;
67#else
65constexpr u32 NUM_VERTEX_BUFFERS = 32; 68constexpr u32 NUM_VERTEX_BUFFERS = 32;
69#endif
66constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4; 70constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4;
67constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18; 71constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18;
68constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; 72constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;
diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp
index f34090791..d77ff455b 100644
--- a/src/video_core/engines/draw_manager.cpp
+++ b/src/video_core/engines/draw_manager.cpp
@@ -48,8 +48,14 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
48 SetInlineIndexBuffer(regs.inline_index_4x8.index3); 48 SetInlineIndexBuffer(regs.inline_index_4x8.index3);
49 break; 49 break;
50 case MAXWELL3D_REG_INDEX(vertex_array_instance_first): 50 case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
51 DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
52 regs.vertex_array_instance_first.start.Value(),
53 regs.vertex_array_instance_first.count.Value(), false);
54 break;
51 case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): { 55 case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
52 LOG_WARNING(HW_GPU, "(STUBBED) called"); 56 DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
57 regs.vertex_array_instance_subsequent.start.Value(),
58 regs.vertex_array_instance_subsequent.count.Value(), true);
53 break; 59 break;
54 } 60 }
55 case MAXWELL3D_REG_INDEX(draw_texture.src_y0): { 61 case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
@@ -84,6 +90,22 @@ void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 ve
84 ProcessDraw(false, num_instances); 90 ProcessDraw(false, num_instances);
85} 91}
86 92
93void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
94 bool subsequent) {
95 draw_state.topology = topology;
96 draw_state.vertex_buffer.first = vertex_first;
97 draw_state.vertex_buffer.count = vertex_count;
98
99 if (!subsequent) {
100 draw_state.instance_count = 1;
101 }
102
103 draw_state.base_instance = draw_state.instance_count - 1;
104 draw_state.draw_mode = DrawMode::Instance;
105 draw_state.instance_count++;
106 ProcessDraw(false, 1);
107}
108
87void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, 109void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
88 u32 base_index, u32 base_instance, u32 num_instances) { 110 u32 base_index, u32 base_instance, u32 num_instances) {
89 const auto& regs{maxwell3d->regs}; 111 const auto& regs{maxwell3d->regs};
diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h
index 18d959143..cfc8127fc 100644
--- a/src/video_core/engines/draw_manager.h
+++ b/src/video_core/engines/draw_manager.h
@@ -66,6 +66,8 @@ public:
66 66
67 void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, 67 void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
68 u32 base_instance, u32 num_instances); 68 u32 base_instance, u32 num_instances);
69 void DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
70 bool subsequent);
69 71
70 void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, 72 void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index,
71 u32 base_instance, u32 num_instances); 73 u32 base_instance, u32 num_instances);
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp
index 8d7da50fc..dbcf508e5 100644
--- a/src/video_core/host1x/codecs/codec.cpp
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -137,16 +137,6 @@ bool Codec::CreateGpuAvDevice() {
137 break; 137 break;
138 } 138 }
139 if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) { 139 if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) {
140#if defined(__unix__)
141 // Some linux decoding backends are reported to crash with this config method
142 // TODO(ameerj): Properly support this method
143 if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) != 0) {
144 // skip zero-copy decoders, we don't currently support them
145 LOG_DEBUG(Service_NVDRV, "Skipping decoder {} with unsupported capability {}.",
146 av_hwdevice_get_type_name(type), config->methods);
147 continue;
148 }
149#endif
150 LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type)); 140 LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
151 av_codec_ctx->pix_fmt = config->pix_fmt; 141 av_codec_ctx->pix_fmt = config->pix_fmt;
152 return true; 142 return true;
diff --git a/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag b/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag
index d33131d7c..b81a54056 100644
--- a/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag
+++ b/src/video_core/host_shaders/convert_d24s8_to_abgr8.frag
@@ -3,16 +3,16 @@
3 3
4#version 450 4#version 450
5 5
6precision mediump int;
7precision highp float;
8
6layout(binding = 0) uniform sampler2D depth_tex; 9layout(binding = 0) uniform sampler2D depth_tex;
7layout(binding = 1) uniform isampler2D stencil_tex; 10layout(binding = 1) uniform usampler2D stencil_tex;
8 11
9layout(location = 0) out vec4 color; 12layout(location = 0) out vec4 color;
10 13
11void main() { 14void main() {
12 ivec2 coord = ivec2(gl_FragCoord.xy); 15 ivec2 coord = ivec2(gl_FragCoord.xy);
13 uint depth = uint(textureLod(depth_tex, coord, 0).r * (exp2(24.0) - 1.0f));
14 uint stencil = uint(textureLod(stencil_tex, coord, 0).r);
15
16 highp uint depth_val = 16 highp uint depth_val =
17 uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0)); 17 uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0));
18 lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r; 18 lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r;
diff --git a/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag b/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag
index 31db7d426..6a457981d 100644
--- a/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag
+++ b/src/video_core/host_shaders/convert_s8d24_to_abgr8.frag
@@ -3,16 +3,16 @@
3 3
4#version 450 4#version 450
5 5
6precision mediump int;
7precision highp float;
8
6layout(binding = 0) uniform sampler2D depth_tex; 9layout(binding = 0) uniform sampler2D depth_tex;
7layout(binding = 1) uniform isampler2D stencil_tex; 10layout(binding = 1) uniform usampler2D stencil_tex;
8 11
9layout(location = 0) out vec4 color; 12layout(location = 0) out vec4 color;
10 13
11void main() { 14void main() {
12 ivec2 coord = ivec2(gl_FragCoord.xy); 15 ivec2 coord = ivec2(gl_FragCoord.xy);
13 uint depth = uint(textureLod(depth_tex, coord, 0).r * (exp2(24.0) - 1.0f));
14 uint stencil = uint(textureLod(stencil_tex, coord, 0).r);
15
16 highp uint depth_val = 16 highp uint depth_val =
17 uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0)); 17 uint(textureLod(depth_tex, coord, 0).r * (exp2(32.0) - 1.0));
18 lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r; 18 lowp uint stencil_val = textureLod(stencil_tex, coord, 0).r;
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 3e12a8813..78ea5208b 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -89,9 +89,6 @@ public:
89 void RequestScreenshot(void* data, std::function<void(bool)> callback, 89 void RequestScreenshot(void* data, std::function<void(bool)> callback,
90 const Layout::FramebufferLayout& layout); 90 const Layout::FramebufferLayout& layout);
91 91
92 /// This is called to notify the rendering backend of a surface change
93 virtual void NotifySurfaceChanged() {}
94
95protected: 92protected:
96 Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle. 93 Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
97 std::unique_ptr<Core::Frontend::GraphicsContext> context; 94 std::unique_ptr<Core::Frontend::GraphicsContext> context;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 9cafd2983..512eef575 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -1048,6 +1048,10 @@ void Image::Scale(bool up_scale) {
1048} 1048}
1049 1049
1050bool Image::ScaleUp(bool ignore) { 1050bool Image::ScaleUp(bool ignore) {
1051 const auto& resolution = runtime->resolution;
1052 if (!resolution.active) {
1053 return false;
1054 }
1051 if (True(flags & ImageFlagBits::Rescaled)) { 1055 if (True(flags & ImageFlagBits::Rescaled)) {
1052 return false; 1056 return false;
1053 } 1057 }
@@ -1060,9 +1064,6 @@ bool Image::ScaleUp(bool ignore) {
1060 return false; 1064 return false;
1061 } 1065 }
1062 flags |= ImageFlagBits::Rescaled; 1066 flags |= ImageFlagBits::Rescaled;
1063 if (!runtime->resolution.active) {
1064 return false;
1065 }
1066 has_scaled = true; 1067 has_scaled = true;
1067 if (ignore) { 1068 if (ignore) {
1068 current_texture = upscaled_backup.handle; 1069 current_texture = upscaled_backup.handle;
@@ -1073,13 +1074,14 @@ bool Image::ScaleUp(bool ignore) {
1073} 1074}
1074 1075
1075bool Image::ScaleDown(bool ignore) { 1076bool Image::ScaleDown(bool ignore) {
1076 if (False(flags & ImageFlagBits::Rescaled)) { 1077 const auto& resolution = runtime->resolution;
1078 if (!resolution.active) {
1077 return false; 1079 return false;
1078 } 1080 }
1079 flags &= ~ImageFlagBits::Rescaled; 1081 if (False(flags & ImageFlagBits::Rescaled)) {
1080 if (!runtime->resolution.active) {
1081 return false; 1082 return false;
1082 } 1083 }
1084 flags &= ~ImageFlagBits::Rescaled;
1083 if (ignore) { 1085 if (ignore) {
1084 current_texture = texture.handle; 1086 current_texture = texture.handle;
1085 return true; 1087 return true;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 3676eaaa9..e71b87e99 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -118,6 +118,8 @@ public:
118 118
119 void InsertUploadMemoryBarrier(); 119 void InsertUploadMemoryBarrier();
120 120
121 void TransitionImageLayout(Image& image) {}
122
121 FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const; 123 FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
122 124
123 bool HasNativeBgr() const noexcept { 125 bool HasNativeBgr() const noexcept {
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index c7dc7e0a1..5ea9e2378 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -116,6 +116,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
116 {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT 116 {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
117 {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT 117 {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
118 {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM 118 {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
119 {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT_24_8}, // X8_D24_UNORM
119 {GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT 120 {GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT
120 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT 121 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
121 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM 122 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
index 1a40a4d05..c3db09424 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -627,6 +627,8 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
627 const VkPipelineLayout layout = *clear_color_pipeline_layout; 627 const VkPipelineLayout layout = *clear_color_pipeline_layout;
628 scheduler.RequestRenderpass(dst_framebuffer); 628 scheduler.RequestRenderpass(dst_framebuffer);
629 scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) { 629 scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
630 constexpr std::array blend_constants{0.0f, 0.0f, 0.0f, 0.0f};
631 cmdbuf.SetBlendConstants(blend_constants.data());
630 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); 632 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
631 BindBlitState(cmdbuf, dst_region); 633 BindBlitState(cmdbuf, dst_region);
632 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth); 634 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
@@ -883,7 +885,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
883 .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, 885 .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
884 .pNext = nullptr, 886 .pNext = nullptr,
885 .flags = 0, 887 .flags = 0,
886 .depthTestEnable = VK_FALSE, 888 .depthTestEnable = key.depth_clear,
887 .depthWriteEnable = key.depth_clear, 889 .depthWriteEnable = key.depth_clear,
888 .depthCompareOp = VK_COMPARE_OP_ALWAYS, 890 .depthCompareOp = VK_COMPARE_OP_ALWAYS,
889 .depthBoundsTestEnable = VK_FALSE, 891 .depthBoundsTestEnable = VK_FALSE,
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 208e88533..a08f2f67f 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -214,8 +214,9 @@ struct FormatTuple {
214 {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT 214 {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
215 215
216 // Depth formats 216 // Depth formats
217 {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT 217 {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
218 {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM 218 {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
219 {VK_FORMAT_X8_D24_UNORM_PACK32, Attachable}, // X8_D24_UNORM
219 220
220 // Stencil formats 221 // Stencil formats
221 {VK_FORMAT_S8_UINT, Attachable}, // S8_UINT 222 {VK_FORMAT_S8_UINT, Attachable}, // S8_UINT
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 590bc1c64..14e257cf7 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -56,10 +56,6 @@ public:
56 return device.GetDriverName(); 56 return device.GetDriverName();
57 } 57 }
58 58
59 void NotifySurfaceChanged() override {
60 present_manager.NotifySurfaceChanged();
61 }
62
63private: 59private:
64 void Report() const; 60 void Report() const;
65 61
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 31928bb94..52fc142d1 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -96,6 +96,7 @@ std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
96VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { 96VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
97 switch (framebuffer.pixel_format) { 97 switch (framebuffer.pixel_format) {
98 case Service::android::PixelFormat::Rgba8888: 98 case Service::android::PixelFormat::Rgba8888:
99 case Service::android::PixelFormat::Rgbx8888:
99 return VK_FORMAT_A8B8G8R8_UNORM_PACK32; 100 return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
100 case Service::android::PixelFormat::Rgb565: 101 case Service::android::PixelFormat::Rgb565:
101 return VK_FORMAT_R5G6B5_UNORM_PACK16; 102 return VK_FORMAT_R5G6B5_UNORM_PACK16;
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
index d681bd22a..2ef36583b 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -103,8 +103,7 @@ PresentManager::PresentManager(const vk::Instance& instance_,
103 surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(), 103 surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
104 swapchain.GetImageViewFormat())}, 104 swapchain.GetImageViewFormat())},
105 use_present_thread{Settings::values.async_presentation.GetValue()}, 105 use_present_thread{Settings::values.async_presentation.GetValue()},
106 image_count{swapchain.GetImageCount()}, last_render_surface{ 106 image_count{swapchain.GetImageCount()} {
107 render_window_.GetWindowInfo().render_surface} {
108 107
109 auto& dld = device.GetLogical(); 108 auto& dld = device.GetLogical();
110 cmdpool = dld.CreateCommandPool({ 109 cmdpool = dld.CreateCommandPool({
@@ -289,44 +288,36 @@ void PresentManager::PresentThread(std::stop_token token) {
289 } 288 }
290} 289}
291 290
292void PresentManager::NotifySurfaceChanged() { 291void PresentManager::RecreateSwapchain(Frame* frame) {
293#ifdef ANDROID 292 swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
294 std::scoped_lock lock{recreate_surface_mutex}; 293 image_count = swapchain.GetImageCount();
295 recreate_surface_cv.notify_one();
296#endif
297} 294}
298 295
299void PresentManager::CopyToSwapchain(Frame* frame) { 296void PresentManager::CopyToSwapchain(Frame* frame) {
300 MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain); 297 bool requires_recreation = false;
301 298
302 const auto recreate_swapchain = [&] { 299 while (true) {
303 swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb); 300 try {
304 image_count = swapchain.GetImageCount(); 301 // Recreate surface and swapchain if needed.
305 }; 302 if (requires_recreation) {
306 303 surface = CreateSurface(instance, render_window.GetWindowInfo());
307#ifdef ANDROID 304 RecreateSwapchain(frame);
308 std::unique_lock lock{recreate_surface_mutex}; 305 }
309 306
310 const auto needs_recreation = [&] { 307 // Draw to swapchain.
311 if (last_render_surface != render_window.GetWindowInfo().render_surface) { 308 return CopyToSwapchainImpl(frame);
312 return true; 309 } catch (const vk::Exception& except) {
313 } 310 if (except.GetResult() != VK_ERROR_SURFACE_LOST_KHR) {
314 if (swapchain.NeedsRecreation(frame->is_srgb)) { 311 throw;
315 return true; 312 }
313
314 requires_recreation = true;
316 } 315 }
317 return false;
318 };
319
320 recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400),
321 [&]() { return !needs_recreation(); });
322
323 // If the frontend recreated the surface, recreate the renderer surface and swapchain.
324 if (last_render_surface != render_window.GetWindowInfo().render_surface) {
325 last_render_surface = render_window.GetWindowInfo().render_surface;
326 surface = CreateSurface(instance, render_window.GetWindowInfo());
327 recreate_swapchain();
328 } 316 }
329#endif 317}
318
319void PresentManager::CopyToSwapchainImpl(Frame* frame) {
320 MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
330 321
331 // If the size or colorspace of the incoming frames has changed, recreate the swapchain 322 // If the size or colorspace of the incoming frames has changed, recreate the swapchain
332 // to account for that. 323 // to account for that.
@@ -334,11 +325,11 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
334 const bool size_changed = 325 const bool size_changed =
335 swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height; 326 swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
336 if (srgb_changed || size_changed) { 327 if (srgb_changed || size_changed) {
337 recreate_swapchain(); 328 RecreateSwapchain(frame);
338 } 329 }
339 330
340 while (swapchain.AcquireNextImage()) { 331 while (swapchain.AcquireNextImage()) {
341 recreate_swapchain(); 332 RecreateSwapchain(frame);
342 } 333 }
343 334
344 const vk::CommandBuffer cmdbuf{frame->cmdbuf}; 335 const vk::CommandBuffer cmdbuf{frame->cmdbuf};
@@ -488,4 +479,4 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
488 swapchain.Present(render_semaphore); 479 swapchain.Present(render_semaphore);
489} 480}
490 481
491} // namespace Vulkan \ No newline at end of file 482} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h
index 83e859416..a3d825fe6 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.h
+++ b/src/video_core/renderer_vulkan/vk_present_manager.h
@@ -54,14 +54,15 @@ public:
54 /// Waits for the present thread to finish presenting all queued frames. 54 /// Waits for the present thread to finish presenting all queued frames.
55 void WaitPresent(); 55 void WaitPresent();
56 56
57 /// This is called to notify the rendering backend of a surface change
58 void NotifySurfaceChanged();
59
60private: 57private:
61 void PresentThread(std::stop_token token); 58 void PresentThread(std::stop_token token);
62 59
63 void CopyToSwapchain(Frame* frame); 60 void CopyToSwapchain(Frame* frame);
64 61
62 void CopyToSwapchainImpl(Frame* frame);
63
64 void RecreateSwapchain(Frame* frame);
65
65private: 66private:
66 const vk::Instance& instance; 67 const vk::Instance& instance;
67 Core::Frontend::EmuWindow& render_window; 68 Core::Frontend::EmuWindow& render_window;
@@ -76,16 +77,13 @@ private:
76 std::queue<Frame*> free_queue; 77 std::queue<Frame*> free_queue;
77 std::condition_variable_any frame_cv; 78 std::condition_variable_any frame_cv;
78 std::condition_variable free_cv; 79 std::condition_variable free_cv;
79 std::condition_variable recreate_surface_cv;
80 std::mutex swapchain_mutex; 80 std::mutex swapchain_mutex;
81 std::mutex recreate_surface_mutex;
82 std::mutex queue_mutex; 81 std::mutex queue_mutex;
83 std::mutex free_mutex; 82 std::mutex free_mutex;
84 std::jthread present_thread; 83 std::jthread present_thread;
85 bool blit_supported; 84 bool blit_supported;
86 bool use_present_thread; 85 bool use_present_thread;
87 std::size_t image_count{}; 86 std::size_t image_count{};
88 void* last_render_surface{};
89}; 87};
90 88
91} // namespace Vulkan 89} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 2edaafa7e..66c03bf17 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -1436,6 +1436,7 @@ void QueryCacheRuntime::Barriers(bool is_prebarrier) {
1436 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, 1436 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
1437 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, 1437 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
1438 }; 1438 };
1439 impl->scheduler.RequestOutsideRenderPassOperationContext();
1439 if (is_prebarrier) { 1440 if (is_prebarrier) {
1440 impl->scheduler.Record([](vk::CommandBuffer cmdbuf) { 1441 impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
1441 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 1442 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 1628d76d6..61d03daae 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -422,7 +422,8 @@ void RasterizerVulkan::Clear(u32 layer_count) {
422 return; 422 return;
423 } 423 }
424 424
425 if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) { 425 if (use_stencil && framebuffer->HasAspectStencilBit() && regs.stencil_front_mask != 0xFF &&
426 regs.stencil_front_mask != 0) {
426 Region2D dst_region = { 427 Region2D dst_region = {
427 Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, 428 Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y},
428 Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width), 429 Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
@@ -974,6 +975,19 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs
974 if (!state_tracker.TouchScissors()) { 975 if (!state_tracker.TouchScissors()) {
975 return; 976 return;
976 } 977 }
978 if (!regs.viewport_scale_offset_enabled) {
979 const auto x = static_cast<float>(regs.surface_clip.x);
980 const auto y = static_cast<float>(regs.surface_clip.y);
981 const auto width = static_cast<float>(regs.surface_clip.width);
982 const auto height = static_cast<float>(regs.surface_clip.height);
983 VkRect2D scissor;
984 scissor.offset.x = static_cast<u32>(x);
985 scissor.offset.y = static_cast<u32>(y);
986 scissor.extent.width = static_cast<u32>(width != 0.0f ? width : 1.0f);
987 scissor.extent.height = static_cast<u32>(height != 0.0f ? height : 1.0f);
988 scheduler.Record([scissor](vk::CommandBuffer cmdbuf) { cmdbuf.SetScissor(0, scissor); });
989 return;
990 }
977 u32 up_scale = 1; 991 u32 up_scale = 1;
978 u32 down_shift = 0; 992 u32 down_shift = 0;
979 if (texture_cache.IsRescaling()) { 993 if (texture_cache.IsRescaling()) {
diff --git a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp
index ae9f1de64..7746a88d3 100644
--- a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp
@@ -19,7 +19,7 @@ VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat
19 VkSampleCountFlagBits samples) { 19 VkSampleCountFlagBits samples) {
20 using MaxwellToVK::SurfaceFormat; 20 using MaxwellToVK::SurfaceFormat;
21 return { 21 return {
22 .flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT, 22 .flags = {},
23 .format = SurfaceFormat(device, FormatType::Optimal, true, format).format, 23 .format = SurfaceFormat(device, FormatType::Optimal, true, format).format,
24 .samples = samples, 24 .samples = samples,
25 .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, 25 .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
index ce92f66ab..b278614e6 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -24,25 +24,38 @@ using namespace Common::Literals;
24 24
25// Maximum potential alignment of a Vulkan buffer 25// Maximum potential alignment of a Vulkan buffer
26constexpr VkDeviceSize MAX_ALIGNMENT = 256; 26constexpr VkDeviceSize MAX_ALIGNMENT = 256;
27// Maximum size to put elements in the stream buffer
28constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB;
29// Stream buffer size in bytes 27// Stream buffer size in bytes
30constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB; 28constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
31constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;
32 29
33size_t Region(size_t iterator) noexcept { 30size_t GetStreamBufferSize(const Device& device) {
34 return iterator / REGION_SIZE; 31 VkDeviceSize size{0};
32 if (device.HasDebuggingToolAttached()) {
33 ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) {
34 size = std::max(size, heap.size);
35 });
36 // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
37 // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
38 // as the heap will be much larger.
39 if (size <= 256_MiB) {
40 size = size * 40 / 100;
41 }
42 } else {
43 size = MAX_STREAM_BUFFER_SIZE;
44 }
45 return std::min(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE);
35} 46}
36} // Anonymous namespace 47} // Anonymous namespace
37 48
38StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, 49StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
39 Scheduler& scheduler_) 50 Scheduler& scheduler_)
40 : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} { 51 : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
52 stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size /
53 StagingBufferPool::NUM_SYNCS} {
41 VkBufferCreateInfo stream_ci = { 54 VkBufferCreateInfo stream_ci = {
42 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 55 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
43 .pNext = nullptr, 56 .pNext = nullptr,
44 .flags = 0, 57 .flags = 0,
45 .size = STREAM_BUFFER_SIZE, 58 .size = stream_buffer_size,
46 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | 59 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
47 VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 60 VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
48 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 61 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
@@ -63,7 +76,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
63StagingBufferPool::~StagingBufferPool() = default; 76StagingBufferPool::~StagingBufferPool() = default;
64 77
65StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) { 78StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
66 if (!deferred && usage == MemoryUsage::Upload && size <= MAX_STREAM_BUFFER_REQUEST_SIZE) { 79 if (!deferred && usage == MemoryUsage::Upload && size <= region_size) {
67 return GetStreamBuffer(size); 80 return GetStreamBuffer(size);
68 } 81 }
69 return GetStagingBuffer(size, usage, deferred); 82 return GetStagingBuffer(size, usage, deferred);
@@ -101,7 +114,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
101 used_iterator = iterator; 114 used_iterator = iterator;
102 free_iterator = std::max(free_iterator, iterator + size); 115 free_iterator = std::max(free_iterator, iterator + size);
103 116
104 if (iterator + size >= STREAM_BUFFER_SIZE) { 117 if (iterator + size >= stream_buffer_size) {
105 std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, 118 std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
106 current_tick); 119 current_tick);
107 used_iterator = 0; 120 used_iterator = 0;
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
index 5f69f08b1..d3deb9072 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -90,6 +90,9 @@ private:
90 void ReleaseCache(MemoryUsage usage); 90 void ReleaseCache(MemoryUsage usage);
91 91
92 void ReleaseLevel(StagingBuffersCache& cache, size_t log2); 92 void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
93 size_t Region(size_t iter) const noexcept {
94 return iter / region_size;
95 }
93 96
94 const Device& device; 97 const Device& device;
95 MemoryAllocator& memory_allocator; 98 MemoryAllocator& memory_allocator;
@@ -97,6 +100,8 @@ private:
97 100
98 vk::Buffer stream_buffer; 101 vk::Buffer stream_buffer;
99 std::span<u8> stream_pointer; 102 std::span<u8> stream_pointer;
103 VkDeviceSize stream_buffer_size;
104 VkDeviceSize region_size;
100 105
101 size_t iterator = 0; 106 size_t iterator = 0;
102 size_t used_iterator = 0; 107 size_t used_iterator = 0;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index cdc41816f..80efd9517 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -238,6 +238,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
238 return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; 238 return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
239 case PixelFormat::D16_UNORM: 239 case PixelFormat::D16_UNORM:
240 case PixelFormat::D32_FLOAT: 240 case PixelFormat::D32_FLOAT:
241 case PixelFormat::X8_D24_UNORM:
241 return VK_IMAGE_ASPECT_DEPTH_BIT; 242 return VK_IMAGE_ASPECT_DEPTH_BIT;
242 case PixelFormat::S8_UINT: 243 case PixelFormat::S8_UINT:
243 return VK_IMAGE_ASPECT_STENCIL_BIT; 244 return VK_IMAGE_ASPECT_STENCIL_BIT;
@@ -1550,15 +1551,15 @@ bool Image::IsRescaled() const noexcept {
1550} 1551}
1551 1552
1552bool Image::ScaleUp(bool ignore) { 1553bool Image::ScaleUp(bool ignore) {
1554 const auto& resolution = runtime->resolution;
1555 if (!resolution.active) {
1556 return false;
1557 }
1553 if (True(flags & ImageFlagBits::Rescaled)) { 1558 if (True(flags & ImageFlagBits::Rescaled)) {
1554 return false; 1559 return false;
1555 } 1560 }
1556 ASSERT(info.type != ImageType::Linear); 1561 ASSERT(info.type != ImageType::Linear);
1557 flags |= ImageFlagBits::Rescaled; 1562 flags |= ImageFlagBits::Rescaled;
1558 const auto& resolution = runtime->resolution;
1559 if (!resolution.active) {
1560 return false;
1561 }
1562 has_scaled = true; 1563 has_scaled = true;
1563 if (!scaled_image) { 1564 if (!scaled_image) {
1564 const bool is_2d = info.type == ImageType::e2D; 1565 const bool is_2d = info.type == ImageType::e2D;
@@ -1587,15 +1588,15 @@ bool Image::ScaleUp(bool ignore) {
1587} 1588}
1588 1589
1589bool Image::ScaleDown(bool ignore) { 1590bool Image::ScaleDown(bool ignore) {
1591 const auto& resolution = runtime->resolution;
1592 if (!resolution.active) {
1593 return false;
1594 }
1590 if (False(flags & ImageFlagBits::Rescaled)) { 1595 if (False(flags & ImageFlagBits::Rescaled)) {
1591 return false; 1596 return false;
1592 } 1597 }
1593 ASSERT(info.type != ImageType::Linear); 1598 ASSERT(info.type != ImageType::Linear);
1594 flags &= ~ImageFlagBits::Rescaled; 1599 flags &= ~ImageFlagBits::Rescaled;
1595 const auto& resolution = runtime->resolution;
1596 if (!resolution.active) {
1597 return false;
1598 }
1599 current_image = *original_image; 1600 current_image = *original_image;
1600 if (ignore) { 1601 if (ignore) {
1601 return true; 1602 return true;
@@ -2033,4 +2034,32 @@ void TextureCacheRuntime::AccelerateImageUpload(
2033 ASSERT(false); 2034 ASSERT(false);
2034} 2035}
2035 2036
2037void TextureCacheRuntime::TransitionImageLayout(Image& image) {
2038 if (!image.ExchangeInitialization()) {
2039 VkImageMemoryBarrier barrier{
2040 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
2041 .pNext = nullptr,
2042 .srcAccessMask = VK_ACCESS_NONE,
2043 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
2044 .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
2045 .newLayout = VK_IMAGE_LAYOUT_GENERAL,
2046 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
2047 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
2048 .image = image.Handle(),
2049 .subresourceRange{
2050 .aspectMask = image.AspectMask(),
2051 .baseMipLevel = 0,
2052 .levelCount = VK_REMAINING_MIP_LEVELS,
2053 .baseArrayLayer = 0,
2054 .layerCount = VK_REMAINING_ARRAY_LAYERS,
2055 },
2056 };
2057 scheduler.RequestOutsideRenderPassOperationContext();
2058 scheduler.Record([barrier = barrier](vk::CommandBuffer cmdbuf) {
2059 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
2060 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
2061 });
2062 }
2063}
2064
2036} // namespace Vulkan 2065} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index d6c5a15cc..7a0807709 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -92,6 +92,8 @@ public:
92 92
93 void InsertUploadMemoryBarrier() {} 93 void InsertUploadMemoryBarrier() {}
94 94
95 void TransitionImageLayout(Image& image);
96
95 bool HasBrokenTextureViewFormats() const noexcept { 97 bool HasBrokenTextureViewFormats() const noexcept {
96 // No known Vulkan driver has broken image views 98 // No known Vulkan driver has broken image views
97 return false; 99 return false;
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index e16cd5e73..5b3c7aa5a 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -85,6 +85,8 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
85 return PixelFormat::S8_UINT; 85 return PixelFormat::S8_UINT;
86 case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT: 86 case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT:
87 return PixelFormat::D32_FLOAT_S8_UINT; 87 return PixelFormat::D32_FLOAT_S8_UINT;
88 case Tegra::DepthFormat::X8Z24_UNORM:
89 return PixelFormat::X8_D24_UNORM;
88 default: 90 default:
89 UNIMPLEMENTED_MSG("Unimplemented format={}", format); 91 UNIMPLEMENTED_MSG("Unimplemented format={}", format);
90 return PixelFormat::S8_UINT_D24_UNORM; 92 return PixelFormat::S8_UINT_D24_UNORM;
@@ -202,6 +204,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
202PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) { 204PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) {
203 switch (format) { 205 switch (format) {
204 case Service::android::PixelFormat::Rgba8888: 206 case Service::android::PixelFormat::Rgba8888:
207 case Service::android::PixelFormat::Rgbx8888:
205 return PixelFormat::A8B8G8R8_UNORM; 208 return PixelFormat::A8B8G8R8_UNORM;
206 case Service::android::PixelFormat::Rgb565: 209 case Service::android::PixelFormat::Rgb565:
207 return PixelFormat::R5G6B5_UNORM; 210 return PixelFormat::R5G6B5_UNORM;
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 9b9c4d9bc..a5e8e2f62 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -115,6 +115,7 @@ enum class PixelFormat {
115 // Depth formats 115 // Depth formats
116 D32_FLOAT = MaxColorFormat, 116 D32_FLOAT = MaxColorFormat,
117 D16_UNORM, 117 D16_UNORM,
118 X8_D24_UNORM,
118 119
119 MaxDepthFormat, 120 MaxDepthFormat,
120 121
@@ -251,6 +252,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
251 1, // E5B9G9R9_FLOAT 252 1, // E5B9G9R9_FLOAT
252 1, // D32_FLOAT 253 1, // D32_FLOAT
253 1, // D16_UNORM 254 1, // D16_UNORM
255 1, // X8_D24_UNORM
254 1, // S8_UINT 256 1, // S8_UINT
255 1, // D24_UNORM_S8_UINT 257 1, // D24_UNORM_S8_UINT
256 1, // S8_UINT_D24_UNORM 258 1, // S8_UINT_D24_UNORM
@@ -360,6 +362,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
360 1, // E5B9G9R9_FLOAT 362 1, // E5B9G9R9_FLOAT
361 1, // D32_FLOAT 363 1, // D32_FLOAT
362 1, // D16_UNORM 364 1, // D16_UNORM
365 1, // X8_D24_UNORM
363 1, // S8_UINT 366 1, // S8_UINT
364 1, // D24_UNORM_S8_UINT 367 1, // D24_UNORM_S8_UINT
365 1, // S8_UINT_D24_UNORM 368 1, // S8_UINT_D24_UNORM
@@ -469,6 +472,7 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
469 32, // E5B9G9R9_FLOAT 472 32, // E5B9G9R9_FLOAT
470 32, // D32_FLOAT 473 32, // D32_FLOAT
471 16, // D16_UNORM 474 16, // D16_UNORM
475 32, // X8_D24_UNORM
472 8, // S8_UINT 476 8, // S8_UINT
473 32, // D24_UNORM_S8_UINT 477 32, // D24_UNORM_S8_UINT
474 32, // S8_UINT_D24_UNORM 478 32, // S8_UINT_D24_UNORM
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index 56307d030..8c774f512 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -138,10 +138,16 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
138 return PixelFormat::E5B9G9R9_FLOAT; 138 return PixelFormat::E5B9G9R9_FLOAT;
139 case Hash(TextureFormat::Z32, FLOAT): 139 case Hash(TextureFormat::Z32, FLOAT):
140 return PixelFormat::D32_FLOAT; 140 return PixelFormat::D32_FLOAT;
141 case Hash(TextureFormat::Z32, FLOAT, UINT, UINT, UINT, LINEAR):
142 return PixelFormat::D32_FLOAT;
141 case Hash(TextureFormat::Z16, UNORM): 143 case Hash(TextureFormat::Z16, UNORM):
142 return PixelFormat::D16_UNORM; 144 return PixelFormat::D16_UNORM;
143 case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR): 145 case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR):
144 return PixelFormat::D16_UNORM; 146 return PixelFormat::D16_UNORM;
147 case Hash(TextureFormat::X8Z24, UNORM):
148 return PixelFormat::X8_D24_UNORM;
149 case Hash(TextureFormat::X8Z24, UNORM, UINT, UINT, UINT, LINEAR):
150 return PixelFormat::X8_D24_UNORM;
145 case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR): 151 case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR):
146 return PixelFormat::S8_UINT_D24_UNORM; 152 return PixelFormat::S8_UINT_D24_UNORM;
147 case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR): 153 case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR):
diff --git a/src/video_core/texture_cache/formatter.cpp b/src/video_core/texture_cache/formatter.cpp
index 6279d8e9e..2b7e0df72 100644
--- a/src/video_core/texture_cache/formatter.cpp
+++ b/src/video_core/texture_cache/formatter.cpp
@@ -10,19 +10,23 @@
10#include "video_core/texture_cache/image_info.h" 10#include "video_core/texture_cache/image_info.h"
11#include "video_core/texture_cache/image_view_base.h" 11#include "video_core/texture_cache/image_view_base.h"
12#include "video_core/texture_cache/render_targets.h" 12#include "video_core/texture_cache/render_targets.h"
13#include "video_core/texture_cache/samples_helper.h"
13 14
14namespace VideoCommon { 15namespace VideoCommon {
15 16
16std::string Name(const ImageBase& image) { 17std::string Name(const ImageBase& image) {
17 const GPUVAddr gpu_addr = image.gpu_addr; 18 const GPUVAddr gpu_addr = image.gpu_addr;
18 const ImageInfo& info = image.info; 19 const ImageInfo& info = image.info;
19 const u32 width = info.size.width; 20 u32 width = info.size.width;
20 const u32 height = info.size.height; 21 u32 height = info.size.height;
21 const u32 depth = info.size.depth; 22 const u32 depth = info.size.depth;
22 const u32 num_layers = image.info.resources.layers; 23 const u32 num_layers = image.info.resources.layers;
23 const u32 num_levels = image.info.resources.levels; 24 const u32 num_levels = image.info.resources.levels;
24 std::string resource; 25 std::string resource;
25 if (image.info.num_samples > 1) { 26 if (image.info.num_samples > 1) {
27 const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(image.info.num_samples);
28 width >>= samples_x;
29 height >>= samples_y;
26 resource += fmt::format(":{}xMSAA", image.info.num_samples); 30 resource += fmt::format(":{}xMSAA", image.info.num_samples);
27 } 31 }
28 if (num_layers > 1) { 32 if (num_layers > 1) {
diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h
index 9ee57a076..cabbfcb2d 100644
--- a/src/video_core/texture_cache/formatter.h
+++ b/src/video_core/texture_cache/formatter.h
@@ -211,6 +211,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
211 return "D32_FLOAT"; 211 return "D32_FLOAT";
212 case PixelFormat::D16_UNORM: 212 case PixelFormat::D16_UNORM:
213 return "D16_UNORM"; 213 return "D16_UNORM";
214 case PixelFormat::X8_D24_UNORM:
215 return "X8_D24_UNORM";
214 case PixelFormat::S8_UINT: 216 case PixelFormat::S8_UINT:
215 return "S8_UINT"; 217 return "S8_UINT";
216 case PixelFormat::D24_UNORM_S8_UINT: 218 case PixelFormat::D24_UNORM_S8_UINT:
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp
index 0c5f4450d..18b9250f9 100644
--- a/src/video_core/texture_cache/image_view_base.cpp
+++ b/src/video_core/texture_cache/image_view_base.cpp
@@ -85,6 +85,7 @@ bool ImageViewBase::SupportsAnisotropy() const noexcept {
85 // Depth formats 85 // Depth formats
86 case PixelFormat::D32_FLOAT: 86 case PixelFormat::D32_FLOAT:
87 case PixelFormat::D16_UNORM: 87 case PixelFormat::D16_UNORM:
88 case PixelFormat::X8_D24_UNORM:
88 // Stencil formats 89 // Stencil formats
89 case PixelFormat::S8_UINT: 90 case PixelFormat::S8_UINT:
90 // DepthStencil formats 91 // DepthStencil formats
diff --git a/src/video_core/texture_cache/samples_helper.h b/src/video_core/texture_cache/samples_helper.h
index 203ac1b11..2ee2f8312 100644
--- a/src/video_core/texture_cache/samples_helper.h
+++ b/src/video_core/texture_cache/samples_helper.h
@@ -24,7 +24,7 @@ namespace VideoCommon {
24 return {2, 2}; 24 return {2, 2};
25 } 25 }
26 ASSERT_MSG(false, "Invalid number of samples={}", num_samples); 26 ASSERT_MSG(false, "Invalid number of samples={}", num_samples);
27 return {1, 1}; 27 return {0, 0};
28} 28}
29 29
30[[nodiscard]] inline int NumSamples(Tegra::Texture::MsaaMode msaa_mode) { 30[[nodiscard]] inline int NumSamples(Tegra::Texture::MsaaMode msaa_mode) {
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 1bdb0def5..d575c57ca 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -1016,6 +1016,7 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
1016 1016
1017 if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { 1017 if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
1018 LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); 1018 LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
1019 runtime.TransitionImageLayout(image);
1019 return; 1020 return;
1020 } 1021 }
1021 if (True(image.flags & ImageFlagBits::AsynchronousDecode)) { 1022 if (True(image.flags & ImageFlagBits::AsynchronousDecode)) {
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index 0a86ce139..15596c925 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -68,6 +68,7 @@ struct LevelInfo {
68 Extent2D tile_size; 68 Extent2D tile_size;
69 u32 bpp_log2; 69 u32 bpp_log2;
70 u32 tile_width_spacing; 70 u32 tile_width_spacing;
71 u32 num_levels;
71}; 72};
72 73
73[[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) { 74[[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) {
@@ -118,11 +119,11 @@ template <u32 GOB_EXTENT>
118} 119}
119 120
120[[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size, 121[[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size,
121 u32 level) { 122 u32 level, u32 num_levels) {
122 return { 123 return {
123 .width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level), 124 .width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level),
124 .height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level), 125 .height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level),
125 .depth = level == 0 126 .depth = level == 0 && num_levels == 1
126 ? block_size.depth 127 ? block_size.depth
127 : AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level), 128 : AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level),
128 }; 129 };
@@ -166,7 +167,7 @@ template <u32 GOB_EXTENT>
166} 167}
167 168
168[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) { 169[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) {
169 if (level == 0) { 170 if (level == 0 && info.num_levels == 1) {
170 return Extent3D{ 171 return Extent3D{
171 .width = info.block.width, 172 .width = info.block.width,
172 .height = info.block.height, 173 .height = info.block.height,
@@ -257,7 +258,7 @@ template <u32 GOB_EXTENT>
257} 258}
258 259
259[[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block, 260[[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block,
260 u32 tile_width_spacing) { 261 u32 tile_width_spacing, u32 num_levels) {
261 const u32 bytes_per_block = BytesPerBlock(format); 262 const u32 bytes_per_block = BytesPerBlock(format);
262 return { 263 return {
263 .size = 264 .size =
@@ -270,16 +271,18 @@ template <u32 GOB_EXTENT>
270 .tile_size = DefaultBlockSize(format), 271 .tile_size = DefaultBlockSize(format),
271 .bpp_log2 = BytesPerBlockLog2(bytes_per_block), 272 .bpp_log2 = BytesPerBlockLog2(bytes_per_block),
272 .tile_width_spacing = tile_width_spacing, 273 .tile_width_spacing = tile_width_spacing,
274 .num_levels = num_levels,
273 }; 275 };
274} 276}
275 277
276[[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) { 278[[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) {
277 return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing); 279 return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing,
280 info.resources.levels);
278} 281}
279 282
280[[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block, 283[[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block,
281 u32 tile_width_spacing, u32 level) { 284 u32 tile_width_spacing, u32 level) {
282 const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing); 285 const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing, level);
283 u32 offset = 0; 286 u32 offset = 0;
284 for (u32 current_level = 0; current_level < level; ++current_level) { 287 for (u32 current_level = 0; current_level < level; ++current_level) {
285 offset += CalculateLevelSize(info, current_level); 288 offset += CalculateLevelSize(info, current_level);
@@ -466,7 +469,7 @@ template <u32 GOB_EXTENT>
466 }; 469 };
467 const u32 bpp_log2 = BytesPerBlockLog2(info.format); 470 const u32 bpp_log2 = BytesPerBlockLog2(info.format);
468 const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing); 471 const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing);
469 const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0); 472 const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0, info.resources.levels);
470 return Extent3D{ 473 return Extent3D{
471 .width = Common::AlignUpLog2(num_tiles.width, alignment), 474 .width = Common::AlignUpLog2(num_tiles.width, alignment),
472 .height = Common::AlignUpLog2(num_tiles.height, GOB_SIZE_Y_SHIFT + mip_block.height), 475 .height = Common::AlignUpLog2(num_tiles.height, GOB_SIZE_Y_SHIFT + mip_block.height),
@@ -533,7 +536,8 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
533 UNIMPLEMENTED_IF(copy.image_extent != level_size); 536 UNIMPLEMENTED_IF(copy.image_extent != level_size);
534 537
535 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); 538 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
536 const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); 539 const Extent3D block =
540 AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
537 541
538 size_t host_offset = copy.buffer_offset; 542 size_t host_offset = copy.buffer_offset;
539 543
@@ -698,7 +702,7 @@ u32 CalculateLevelStrideAlignment(const ImageInfo& info, u32 level) {
698 const Extent2D tile_size = DefaultBlockSize(info.format); 702 const Extent2D tile_size = DefaultBlockSize(info.format);
699 const Extent3D level_size = AdjustMipSize(info.size, level); 703 const Extent3D level_size = AdjustMipSize(info.size, level);
700 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); 704 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
701 const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level); 705 const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level, info.resources.levels);
702 const u32 bpp_log2 = BytesPerBlockLog2(info.format); 706 const u32 bpp_log2 = BytesPerBlockLog2(info.format);
703 return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing); 707 return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing);
704} 708}
@@ -887,7 +891,8 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
887 .image_extent = level_size, 891 .image_extent = level_size,
888 }; 892 };
889 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); 893 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
890 const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); 894 const Extent3D block =
895 AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
891 const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2); 896 const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2);
892 size_t guest_layer_offset = 0; 897 size_t guest_layer_offset = 0;
893 898
@@ -1041,7 +1046,7 @@ Extent3D MipBlockSize(const ImageInfo& info, u32 level) {
1041 const Extent2D tile_size = DefaultBlockSize(info.format); 1046 const Extent2D tile_size = DefaultBlockSize(info.format);
1042 const Extent3D level_size = AdjustMipSize(info.size, level); 1047 const Extent3D level_size = AdjustMipSize(info.size, level);
1043 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); 1048 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
1044 return AdjustMipBlockSize(num_tiles, level_info.block, level); 1049 return AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
1045} 1050}
1046 1051
1047boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const ImageInfo& info) { 1052boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const ImageInfo& info) {
@@ -1063,7 +1068,8 @@ boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const I
1063 for (s32 level = 0; level < num_levels; ++level) { 1068 for (s32 level = 0; level < num_levels; ++level) {
1064 const Extent3D level_size = AdjustMipSize(size, level); 1069 const Extent3D level_size = AdjustMipSize(size, level);
1065 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); 1070 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
1066 const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); 1071 const Extent3D block =
1072 AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
1067 params[level] = SwizzleParameters{ 1073 params[level] = SwizzleParameters{
1068 .num_tiles = num_tiles, 1074 .num_tiles = num_tiles,
1069 .block = block, 1075 .block = block,
@@ -1292,11 +1298,11 @@ u32 MapSizeBytes(const ImageBase& image) {
1292 } 1298 }
1293} 1299}
1294 1300
1295static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0}, 0) == 1301static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0, 1}, 0) ==
1296 0x7f8000); 1302 0x7f8000);
1297static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0}, 0) == 0x40000); 1303static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0, 1}, 0) == 0x40000);
1298 1304
1299static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0}, 0) == 0x40000); 1305static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0, 1}, 0) == 0x40000);
1300 1306
1301static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) == 1307static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) ==
1302 0x2afc00); 1308 0x2afc00);
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 3960b135a..876cec2e8 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -84,9 +84,12 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
84} // namespace Alternatives 84} // namespace Alternatives
85 85
86enum class NvidiaArchitecture { 86enum class NvidiaArchitecture {
87 AmpereOrNewer, 87 KeplerOrOlder,
88 Maxwell,
89 Pascal,
90 Volta,
88 Turing, 91 Turing,
89 VoltaOrOlder, 92 AmpereOrNewer,
90}; 93};
91 94
92template <typename T> 95template <typename T>
@@ -200,6 +203,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
200 VK_FORMAT_BC7_UNORM_BLOCK, 203 VK_FORMAT_BC7_UNORM_BLOCK,
201 VK_FORMAT_D16_UNORM, 204 VK_FORMAT_D16_UNORM,
202 VK_FORMAT_D16_UNORM_S8_UINT, 205 VK_FORMAT_D16_UNORM_S8_UINT,
206 VK_FORMAT_X8_D24_UNORM_PACK32,
203 VK_FORMAT_D24_UNORM_S8_UINT, 207 VK_FORMAT_D24_UNORM_S8_UINT,
204 VK_FORMAT_D32_SFLOAT, 208 VK_FORMAT_D32_SFLOAT,
205 VK_FORMAT_D32_SFLOAT_S8_UINT, 209 VK_FORMAT_D32_SFLOAT_S8_UINT,
@@ -321,13 +325,38 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
321 physical.GetProperties2(physical_properties); 325 physical.GetProperties2(physical_properties);
322 if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) { 326 if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) {
323 // Only Ampere and newer support this feature 327 // Only Ampere and newer support this feature
328 // TODO: Find a way to differentiate Ampere and Ada
324 return NvidiaArchitecture::AmpereOrNewer; 329 return NvidiaArchitecture::AmpereOrNewer;
325 } 330 }
326 }
327 if (exts.contains(VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) {
328 return NvidiaArchitecture::Turing; 331 return NvidiaArchitecture::Turing;
329 } 332 }
330 return NvidiaArchitecture::VoltaOrOlder; 333
334 if (exts.contains(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME)) {
335 VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT advanced_blending_props{};
336 advanced_blending_props.sType =
337 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT;
338 VkPhysicalDeviceProperties2 physical_properties{};
339 physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
340 physical_properties.pNext = &advanced_blending_props;
341 physical.GetProperties2(physical_properties);
342 if (advanced_blending_props.advancedBlendMaxColorAttachments == 1) {
343 return NvidiaArchitecture::Maxwell;
344 }
345
346 if (exts.contains(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) {
347 VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_props{};
348 conservative_raster_props.sType =
349 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT;
350 physical_properties.pNext = &conservative_raster_props;
351 physical.GetProperties2(physical_properties);
352 if (conservative_raster_props.degenerateLinesRasterized) {
353 return NvidiaArchitecture::Volta;
354 }
355 return NvidiaArchitecture::Pascal;
356 }
357 }
358
359 return NvidiaArchitecture::KeplerOrOlder;
331} 360}
332 361
333std::vector<const char*> ExtensionListForVulkan( 362std::vector<const char*> ExtensionListForVulkan(
@@ -504,19 +533,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
504 if (is_nvidia) { 533 if (is_nvidia) {
505 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; 534 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
506 const auto arch = GetNvidiaArchitecture(physical, supported_extensions); 535 const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
507 switch (arch) { 536 if (arch >= NvidiaArchitecture::AmpereOrNewer) {
508 case NvidiaArchitecture::AmpereOrNewer:
509 LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math"); 537 LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
510 features.shader_float16_int8.shaderFloat16 = false; 538 features.shader_float16_int8.shaderFloat16 = false;
511 break; 539 } else if (arch <= NvidiaArchitecture::Volta) {
512 case NvidiaArchitecture::Turing:
513 break;
514 case NvidiaArchitecture::VoltaOrOlder:
515 if (nv_major_version < 527) { 540 if (nv_major_version < 527) {
516 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); 541 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
517 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); 542 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
518 } 543 }
519 break;
520 } 544 }
521 if (nv_major_version >= 510) { 545 if (nv_major_version >= 510) {
522 LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits"); 546 LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
@@ -661,7 +685,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
661 "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor"); 685 "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
662 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); 686 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
663 } 687 }
688 } else if (extensions.push_descriptor && is_nvidia) {
689 const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
690 if (arch <= NvidiaArchitecture::Pascal) {
691 LOG_WARNING(Render_Vulkan,
692 "Pascal and older architectures have broken VK_KHR_push_descriptor");
693 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
694 }
664 } 695 }
696
665 if (is_mvk) { 697 if (is_mvk) {
666 LOG_WARNING(Render_Vulkan, 698 LOG_WARNING(Render_Vulkan,
667 "MVK driver breaks when using more than 16 vertex attributes/bindings"); 699 "MVK driver breaks when using more than 16 vertex attributes/bindings");
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 3ef381a38..8dd1667f3 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -9,6 +9,7 @@
9#include "common/alignment.h" 9#include "common/alignment.h"
10#include "common/assert.h" 10#include "common/assert.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/literals.h"
12#include "common/logging/log.h" 13#include "common/logging/log.h"
13#include "common/polyfill_ranges.h" 14#include "common/polyfill_ranges.h"
14#include "video_core/vulkan_common/vma.h" 15#include "video_core/vulkan_common/vma.h"
@@ -65,12 +66,12 @@ struct Range {
65 switch (usage) { 66 switch (usage) {
66 case MemoryUsage::Upload: 67 case MemoryUsage::Upload:
67 case MemoryUsage::Stream: 68 case MemoryUsage::Stream:
68 return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; 69 return VMA_ALLOCATION_CREATE_MAPPED_BIT |
70 VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
69 case MemoryUsage::Download: 71 case MemoryUsage::Download:
70 return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; 72 return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
71 case MemoryUsage::DeviceLocal: 73 case MemoryUsage::DeviceLocal:
72 return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | 74 return {};
73 VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;
74 } 75 }
75 return {}; 76 return {};
76} 77}
@@ -212,7 +213,20 @@ MemoryAllocator::MemoryAllocator(const Device& device_)
212 : device{device_}, allocator{device.GetAllocator()}, 213 : device{device_}, allocator{device.GetAllocator()},
213 properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, 214 properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
214 buffer_image_granularity{ 215 buffer_image_granularity{
215 device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {} 216 device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
217 // GPUs not supporting rebar may only have a region with less than 256MB host visible/device
218 // local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to
219 // the heap running out of memory. With RenderDoc attached and only a small host/device region,
220 // only allow the stream buffer in this memory heap.
221 if (device.HasDebuggingToolAttached()) {
222 using namespace Common::Literals;
223 ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) {
224 if (heap.size <= 256_MiB) {
225 valid_memory_types &= ~(1u << index);
226 }
227 });
228 }
229}
216 230
217MemoryAllocator::~MemoryAllocator() = default; 231MemoryAllocator::~MemoryAllocator() = default;
218 232
@@ -239,12 +253,11 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
239 253
240vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const { 254vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
241 const VmaAllocationCreateInfo alloc_ci = { 255 const VmaAllocationCreateInfo alloc_ci = {
242 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT | 256 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
243 MemoryUsageVmaFlags(usage),
244 .usage = MemoryUsageVma(usage), 257 .usage = MemoryUsageVma(usage),
245 .requiredFlags = 0, 258 .requiredFlags = 0,
246 .preferredFlags = MemoryUsagePreferedVmaFlags(usage), 259 .preferredFlags = MemoryUsagePreferedVmaFlags(usage),
247 .memoryTypeBits = 0, 260 .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
248 .pool = VK_NULL_HANDLE, 261 .pool = VK_NULL_HANDLE,
249 .pUserData = nullptr, 262 .pUserData = nullptr,
250 .priority = 0.f, 263 .priority = 0.f,
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
index f449bc8d0..38a182bcb 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.h
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -7,6 +7,7 @@
7#include <span> 7#include <span>
8#include <vector> 8#include <vector>
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "video_core/vulkan_common/vulkan_device.h"
10#include "video_core/vulkan_common/vulkan_wrapper.h" 11#include "video_core/vulkan_common/vulkan_wrapper.h"
11 12
12VK_DEFINE_HANDLE(VmaAllocator) 13VK_DEFINE_HANDLE(VmaAllocator)
@@ -26,6 +27,18 @@ enum class MemoryUsage {
26 Stream, ///< Requests device local host visible buffer, falling back host memory. 27 Stream, ///< Requests device local host visible buffer, falling back host memory.
27}; 28};
28 29
30template <typename F>
31void ForEachDeviceLocalHostVisibleHeap(const Device& device, F&& f) {
32 auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
33 for (size_t i = 0; i < memory_props.memoryTypeCount; i++) {
34 auto& memory_type = memory_props.memoryTypes[i];
35 if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
36 (memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
37 f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]);
38 }
39 }
40}
41
29/// Ownership handle of a memory commitment. 42/// Ownership handle of a memory commitment.
30/// Points to a subregion of a memory allocation. 43/// Points to a subregion of a memory allocation.
31class MemoryCommit { 44class MemoryCommit {
@@ -124,6 +137,7 @@ private:
124 std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations. 137 std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
125 VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers 138 VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
126 // and optimal images 139 // and optimal images
140 u32 valid_memory_types{~0u};
127}; 141};
128 142
129} // namespace Vulkan 143} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 1e3c0fa64..0487cd3b6 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -117,6 +117,9 @@ public:
117 virtual ~Exception() = default; 117 virtual ~Exception() = default;
118 118
119 const char* what() const noexcept override; 119 const char* what() const noexcept override;
120 VkResult GetResult() const noexcept {
121 return result;
122 }
120 123
121private: 124private:
122 VkResult result; 125 VkResult result;
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 8f86a1553..34208ed74 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -195,6 +195,8 @@ add_executable(yuzu
195 multiplayer/state.cpp 195 multiplayer/state.cpp
196 multiplayer/state.h 196 multiplayer/state.h
197 multiplayer/validation.h 197 multiplayer/validation.h
198 play_time_manager.cpp
199 play_time_manager.h
198 precompiled_headers.h 200 precompiled_headers.h
199 qt_common.cpp 201 qt_common.cpp
200 qt_common.h 202 qt_common.h
@@ -382,7 +384,7 @@ if (USE_DISCORD_PRESENCE)
382 discord_impl.cpp 384 discord_impl.cpp
383 discord_impl.h 385 discord_impl.h
384 ) 386 )
385 target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib) 387 target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network)
386 target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) 388 target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
387endif() 389endif()
388 390
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index d15559518..ca0e14fad 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -23,6 +23,7 @@
23#include "yuzu/configuration/configure_vibration.h" 23#include "yuzu/configuration/configure_vibration.h"
24#include "yuzu/configuration/input_profiles.h" 24#include "yuzu/configuration/input_profiles.h"
25#include "yuzu/main.h" 25#include "yuzu/main.h"
26#include "yuzu/util/controller_navigation.h"
26 27
27namespace { 28namespace {
28 29
@@ -132,6 +133,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
132 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, 133 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
133 }; 134 };
134 135
136 ui->labelError->setVisible(false);
137
135 // Setup/load everything prior to setting up connections. 138 // Setup/load everything prior to setting up connections.
136 // This avoids unintentionally changing the states of elements while loading them in. 139 // This avoids unintentionally changing the states of elements while loading them in.
137 SetSupportedControllers(); 140 SetSupportedControllers();
@@ -143,6 +146,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
143 146
144 LoadConfiguration(); 147 LoadConfiguration();
145 148
149 controller_navigation = new ControllerNavigation(system.HIDCore(), this);
150
146 for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { 151 for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
147 SetExplainText(i); 152 SetExplainText(i);
148 UpdateControllerIcon(i); 153 UpdateControllerIcon(i);
@@ -151,6 +156,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
151 156
152 connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { 157 connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
153 if (checked) { 158 if (checked) {
159 // Hide eventual error message about number of controllers
160 ui->labelError->setVisible(false);
154 for (std::size_t index = 0; index <= i; ++index) { 161 for (std::size_t index = 0; index <= i; ++index) {
155 connected_controller_checkboxes[index]->setChecked(checked); 162 connected_controller_checkboxes[index]->setChecked(checked);
156 } 163 }
@@ -199,6 +206,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
199 connect(ui->buttonBox, &QDialogButtonBox::accepted, this, 206 connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
200 &QtControllerSelectorDialog::ApplyConfiguration); 207 &QtControllerSelectorDialog::ApplyConfiguration);
201 208
209 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
210 [this](Qt::Key key) {
211 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
212 QCoreApplication::postEvent(this, event);
213 });
214
202 // Enhancement: Check if the parameters have already been met before disconnecting controllers. 215 // Enhancement: Check if the parameters have already been met before disconnecting controllers.
203 // If all the parameters are met AND only allows a single player, 216 // If all the parameters are met AND only allows a single player,
204 // stop the constructor here as we do not need to continue. 217 // stop the constructor here as we do not need to continue.
@@ -217,6 +230,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
217} 230}
218 231
219QtControllerSelectorDialog::~QtControllerSelectorDialog() { 232QtControllerSelectorDialog::~QtControllerSelectorDialog() {
233 controller_navigation->UnloadController();
220 system.HIDCore().DisableAllControllerConfiguration(); 234 system.HIDCore().DisableAllControllerConfiguration();
221} 235}
222 236
@@ -291,6 +305,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
291 dialog.exec(); 305 dialog.exec();
292} 306}
293 307
308void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
309 const auto num_connected_players = static_cast<int>(
310 std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
311 [](const QGroupBox* player) { return player->isChecked(); }));
312
313 const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
314 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
315
316 if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
317 // Display error message when trying to validate using "Enter" and "OK" button is disabled
318 ui->labelError->setVisible(true);
319 return;
320 } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
321 // Remove a player if possible
322 connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
323 return;
324 } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
325 // Add a player, if possible
326 ui->labelError->setVisible(false);
327 connected_controller_checkboxes[num_connected_players]->setChecked(true);
328 return;
329 }
330 QDialog::keyPressEvent(evt);
331}
332
294bool QtControllerSelectorDialog::CheckIfParametersMet() { 333bool QtControllerSelectorDialog::CheckIfParametersMet() {
295 // Here, we check and validate the current configuration against all applicable parameters. 334 // Here, we check and validate the current configuration against all applicable parameters.
296 const auto num_connected_players = static_cast<int>( 335 const auto num_connected_players = static_cast<int>(
diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h
index 2fdc35857..7f0673d06 100644
--- a/src/yuzu/applets/qt_controller.h
+++ b/src/yuzu/applets/qt_controller.h
@@ -34,6 +34,8 @@ class HIDCore;
34enum class NpadStyleIndex : u8; 34enum class NpadStyleIndex : u8;
35} // namespace Core::HID 35} // namespace Core::HID
36 36
37class ControllerNavigation;
38
37class QtControllerSelectorDialog final : public QDialog { 39class QtControllerSelectorDialog final : public QDialog {
38 Q_OBJECT 40 Q_OBJECT
39 41
@@ -46,6 +48,8 @@ public:
46 48
47 int exec() override; 49 int exec() override;
48 50
51 void keyPressEvent(QKeyEvent* evt) override;
52
49private: 53private:
50 // Applies the current configuration. 54 // Applies the current configuration.
51 void ApplyConfiguration(); 55 void ApplyConfiguration();
@@ -110,6 +114,8 @@ private:
110 114
111 Core::System& system; 115 Core::System& system;
112 116
117 ControllerNavigation* controller_navigation = nullptr;
118
113 // This is true if and only if all parameters are met. Otherwise, this is false. 119 // This is true if and only if all parameters are met. Otherwise, this is false.
114 // This determines whether the "OK" button can be clicked to exit the applet. 120 // This determines whether the "OK" button can be clicked to exit the applet.
115 bool parameters_met{false}; 121 bool parameters_met{false};
diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui
index 729e921ee..6f7cb3c13 100644
--- a/src/yuzu/applets/qt_controller.ui
+++ b/src/yuzu/applets/qt_controller.ui
@@ -2624,13 +2624,53 @@
2624 </spacer> 2624 </spacer>
2625 </item> 2625 </item>
2626 <item alignment="Qt::AlignBottom"> 2626 <item alignment="Qt::AlignBottom">
2627 <widget class="QDialogButtonBox" name="buttonBox"> 2627 <widget class="QWidget" name="closeButtons" native="true">
2628 <property name="enabled"> 2628 <layout class="QVBoxLayout" name="verticalLayout_46">
2629 <bool>true</bool> 2629 <property name="spacing">
2630 </property> 2630 <number>7</number>
2631 <property name="standardButtons"> 2631 </property>
2632 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> 2632 <property name="leftMargin">
2633 </property> 2633 <number>0</number>
2634 </property>
2635 <property name="topMargin">
2636 <number>0</number>
2637 </property>
2638 <property name="rightMargin">
2639 <number>0</number>
2640 </property>
2641 <property name="bottomMargin">
2642 <number>0</number>
2643 </property>
2644 <item>
2645 <widget class="QLabel" name="labelError">
2646 <property name="enabled">
2647 <bool>true</bool>
2648 </property>
2649 <property name="styleSheet">
2650 <string notr="true">QLabel { color : red; }</string>
2651 </property>
2652 <property name="text">
2653 <string>Not enough controllers</string>
2654 </property>
2655 <property name="alignment">
2656 <set>Qt::AlignCenter</set>
2657 </property>
2658 <property name="margin">
2659 <number>0</number>
2660 </property>
2661 </widget>
2662 </item>
2663 <item>
2664 <widget class="QDialogButtonBox" name="buttonBox">
2665 <property name="enabled">
2666 <bool>true</bool>
2667 </property>
2668 <property name="standardButtons">
2669 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
2670 </property>
2671 </widget>
2672 </item>
2673 </layout>
2634 </widget> 2674 </widget>
2635 </item> 2675 </item>
2636 </layout> 2676 </layout>
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 1de093447..d5157c502 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -128,8 +128,8 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
128 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}}, 128 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}},
129 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}}, 129 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}},
130 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, 130 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
131 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}}, 131 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral("R+Plus+Minus"), Qt::WindowShortcut, false}},
132 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}}, 132 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral("L+Plus+Minus"), Qt::WindowShortcut, false}},
133 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, 133 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
134 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, 134 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
135 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, 135 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index 9ccfb2435..81dd51ad3 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -42,6 +42,9 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) {
42 for (auto* setting : Settings::values.linkage.by_category[category]) { 42 for (auto* setting : Settings::values.linkage.by_category[category]) {
43 settings.push_back(setting); 43 settings.push_back(setting);
44 } 44 }
45 for (auto* setting : UISettings::values.linkage.by_category[category]) {
46 settings.push_back(setting);
47 }
45 }; 48 };
46 49
47 push(Settings::Category::Audio); 50 push(Settings::Category::Audio);
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index e8f9ebfd8..5a48e388b 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -115,17 +115,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
115 for (std::size_t i = 0; i < player_tabs.size(); ++i) { 115 for (std::size_t i = 0; i < player_tabs.size(); ++i) {
116 player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); 116 player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
117 player_tabs[i]->layout()->addWidget(player_controllers[i]); 117 player_tabs[i]->layout()->addWidget(player_controllers[i]);
118 connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) { 118 connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) {
119 // Ensures that the controllers are always connected in sequential order 119 // Ensures that the controllers are always connected in sequential order
120 if (is_connected) { 120 this->propagateMouseClickOnPlayers(i, checked, true);
121 for (std::size_t index = 0; index <= i; ++index) {
122 player_connected[index]->setChecked(is_connected);
123 }
124 } else {
125 for (std::size_t index = i; index < player_tabs.size(); ++index) {
126 player_connected[index]->setChecked(is_connected);
127 }
128 }
129 }); 121 });
130 connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, 122 connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
131 &ConfigureInput::UpdateAllInputDevices); 123 &ConfigureInput::UpdateAllInputDevices);
@@ -183,6 +175,30 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
183 LoadConfiguration(); 175 LoadConfiguration();
184} 176}
185 177
178void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) {
179 // Origin has already been toggled
180 if (!origin) {
181 player_connected[player_index]->setChecked(checked);
182 }
183
184 if (checked) {
185 // Check all previous buttons when checked
186 if (player_index > 0) {
187 propagateMouseClickOnPlayers(player_index - 1, checked, false);
188 }
189 } else {
190 // Unchecked all following buttons when unchecked
191 if (player_index < player_tabs.size() - 1) {
192 // Reconnect current player if it was the last one checked
193 // (player number was reduced by more than one)
194 if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) {
195 player_connected[player_index]->setCheckState(Qt::Checked);
196 }
197 propagateMouseClickOnPlayers(player_index + 1, checked, false);
198 }
199 }
200}
201
186QList<QWidget*> ConfigureInput::GetSubTabs() const { 202QList<QWidget*> ConfigureInput::GetSubTabs() const {
187 return { 203 return {
188 ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, 204 ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index c89189c36..abb7f7089 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -56,6 +56,7 @@ private:
56 void UpdateDockedState(bool is_handheld); 56 void UpdateDockedState(bool is_handheld);
57 void UpdateAllInputDevices(); 57 void UpdateAllInputDevices();
58 void UpdateAllInputProfiles(std::size_t player_index); 58 void UpdateAllInputProfiles(std::size_t player_index);
59 void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked);
59 60
60 /// Load configuration settings. 61 /// Load configuration settings.
61 void LoadConfiguration(); 62 void LoadConfiguration();
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index a9fde9f4f..82f3b6e78 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -123,6 +123,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
123 connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); 123 connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
124 connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); 124 connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
125 connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); 125 connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
126 connect(ui->show_play_time, &QCheckBox::stateChanged, this,
127 &ConfigureUi::RequestGameListUpdate);
126 connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 128 connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
127 &ConfigureUi::RequestGameListUpdate); 129 &ConfigureUi::RequestGameListUpdate);
128 connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), 130 connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
@@ -167,6 +169,7 @@ void ConfigureUi::ApplyConfiguration() {
167 UISettings::values.show_compat = ui->show_compat->isChecked(); 169 UISettings::values.show_compat = ui->show_compat->isChecked();
168 UISettings::values.show_size = ui->show_size->isChecked(); 170 UISettings::values.show_size = ui->show_size->isChecked();
169 UISettings::values.show_types = ui->show_types->isChecked(); 171 UISettings::values.show_types = ui->show_types->isChecked();
172 UISettings::values.show_play_time = ui->show_play_time->isChecked();
170 UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); 173 UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
171 UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); 174 UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
172 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); 175 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
@@ -179,6 +182,7 @@ void ConfigureUi::ApplyConfiguration() {
179 const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); 182 const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
180 UISettings::values.screenshot_height.SetValue(height); 183 UISettings::values.screenshot_height.SetValue(height);
181 184
185 RequestGameListUpdate();
182 system.ApplySettings(); 186 system.ApplySettings();
183} 187}
184 188
@@ -194,6 +198,7 @@ void ConfigureUi::SetConfiguration() {
194 ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); 198 ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
195 ui->show_size->setChecked(UISettings::values.show_size.GetValue()); 199 ui->show_size->setChecked(UISettings::values.show_size.GetValue());
196 ui->show_types->setChecked(UISettings::values.show_types.GetValue()); 200 ui->show_types->setChecked(UISettings::values.show_types.GetValue());
201 ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue());
197 ui->game_icon_size_combobox->setCurrentIndex( 202 ui->game_icon_size_combobox->setCurrentIndex(
198 ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); 203 ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
199 ui->folder_icon_size_combobox->setCurrentIndex( 204 ui->folder_icon_size_combobox->setCurrentIndex(
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index cb66ef104..b8e648381 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -105,6 +105,13 @@
105 </widget> 105 </widget>
106 </item> 106 </item>
107 <item> 107 <item>
108 <widget class="QCheckBox" name="show_play_time">
109 <property name="text">
110 <string>Show Play Time Column</string>
111 </property>
112 </widget>
113 </item>
114 <item>
108 <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2"> 115 <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
109 <item> 116 <item>
110 <widget class="QLabel" name="game_icon_size_label"> 117 <widget class="QLabel" name="game_icon_size_label">
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index 276bdbaba..3fe448f27 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -29,9 +29,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
29 INSERT(Settings, sink_id, "Output Engine:", ""); 29 INSERT(Settings, sink_id, "Output Engine:", "");
30 INSERT(Settings, audio_output_device_id, "Output Device:", ""); 30 INSERT(Settings, audio_output_device_id, "Output Device:", "");
31 INSERT(Settings, audio_input_device_id, "Input Device:", ""); 31 INSERT(Settings, audio_input_device_id, "Input Device:", "");
32 INSERT(Settings, audio_muted, "Mute audio when in background", ""); 32 INSERT(Settings, audio_muted, "Mute audio", "");
33 INSERT(Settings, volume, "Volume:", ""); 33 INSERT(Settings, volume, "Volume:", "");
34 INSERT(Settings, dump_audio_commands, "", ""); 34 INSERT(Settings, dump_audio_commands, "", "");
35 INSERT(UISettings, mute_when_in_background, "Mute audio when in background", "");
35 36
36 // Core 37 // Core
37 INSERT(Settings, use_multi_core, "Multicore CPU Emulation", ""); 38 INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
@@ -156,6 +157,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
156 INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", ""); 157 INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", "");
157 INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); 158 INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", "");
158 INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", ""); 159 INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", "");
160 INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", "");
159 INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); 161 INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
160 INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); 162 INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");
161 163
@@ -382,6 +384,13 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
382 translations->insert( 384 translations->insert(
383 {Settings::EnumMetadata<Settings::ConsoleMode>::Index(), 385 {Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
384 {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}}); 386 {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
387 translations->insert(
388 {Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
389 {
390 PAIR(ConfirmStop, Ask_Always, "Always ask (Default)"),
391 PAIR(ConfirmStop, Ask_Based_On_Game, "Only if game specifies not to stop"),
392 PAIR(ConfirmStop, Ask_Never, "Never ask"),
393 }});
385 394
386#undef PAIR 395#undef PAIR
387#undef CTX_PAIR 396#undef CTX_PAIR
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index f254c1e1c..2bb1a0239 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -312,8 +312,10 @@ void GameList::OnFilterCloseClicked() {
312} 312}
313 313
314GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, 314GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
315 Core::System& system_, GMainWindow* parent) 315 PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
316 : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} { 316 GMainWindow* parent)
317 : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_},
318 play_time_manager{play_time_manager_}, system{system_} {
317 watcher = new QFileSystemWatcher(this); 319 watcher = new QFileSystemWatcher(this);
318 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); 320 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
319 321
@@ -340,6 +342,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
340 342
341 tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); 343 tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
342 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); 344 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
345 tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
343 item_model->setSortRole(GameListItemPath::SortRole); 346 item_model->setSortRole(GameListItemPath::SortRole);
344 347
345 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); 348 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
@@ -548,6 +551,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
548 QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); 551 QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
549 QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); 552 QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
550 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); 553 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
554 QAction* remove_play_time_data = remove_menu->addAction(tr("Remove Play Time Data"));
551 QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage")); 555 QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage"));
552 QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache")); 556 QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache"));
553 QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); 557 QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache"));
@@ -560,9 +564,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
560 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); 564 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
561 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 565 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
562 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 566 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
563#ifndef WIN32
564 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); 567 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
565 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); 568 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
569#ifndef WIN32
566 QAction* create_applications_menu_shortcut = 570 QAction* create_applications_menu_shortcut =
567 shortcut_menu->addAction(tr("Add to Applications Menu")); 571 shortcut_menu->addAction(tr("Add to Applications Menu"));
568#endif 572#endif
@@ -622,6 +626,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
622 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { 626 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
623 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); 627 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
624 }); 628 });
629 connect(remove_play_time_data, &QAction::triggered,
630 [this, program_id]() { emit RemovePlayTimeRequested(program_id); });
625 connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { 631 connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
626 emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); 632 emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path);
627 }); 633 });
@@ -638,10 +644,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
638 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { 644 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
639 emit NavigateToGamedbEntryRequested(program_id, compatibility_list); 645 emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
640 }); 646 });
641#ifndef WIN32
642 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { 647 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
643 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); 648 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
644 }); 649 });
650#ifndef WIN32
645 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { 651 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
646 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); 652 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
647 }); 653 });
@@ -790,6 +796,7 @@ void GameList::RetranslateUI() {
790 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); 796 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
791 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); 797 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
792 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); 798 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
799 item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time"));
793} 800}
794 801
795void GameListSearchField::changeEvent(QEvent* event) { 802void GameListSearchField::changeEvent(QEvent* event) {
@@ -817,15 +824,17 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
817 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); 824 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
818 tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); 825 tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
819 tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); 826 tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
827 tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
828
829 // Before deleting rows, cancel the worker so that it is not using them
830 emit ShouldCancelWorker();
820 831
821 // Delete any rows that might already exist if we're repopulating 832 // Delete any rows that might already exist if we're repopulating
822 item_model->removeRows(0, item_model->rowCount()); 833 item_model->removeRows(0, item_model->rowCount());
823 search_field->clear(); 834 search_field->clear();
824 835
825 emit ShouldCancelWorker();
826
827 GameListWorker* worker = 836 GameListWorker* worker =
828 new GameListWorker(vfs, provider, game_dirs, compatibility_list, system); 837 new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
829 838
830 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); 839 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
831 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, 840 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 1fcbbf0ba..712570cea 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -18,6 +18,7 @@
18#include "core/core.h" 18#include "core/core.h"
19#include "uisettings.h" 19#include "uisettings.h"
20#include "yuzu/compatibility_list.h" 20#include "yuzu/compatibility_list.h"
21#include "yuzu/play_time_manager.h"
21 22
22namespace Core { 23namespace Core {
23class System; 24class System;
@@ -75,11 +76,13 @@ public:
75 COLUMN_ADD_ONS, 76 COLUMN_ADD_ONS,
76 COLUMN_FILE_TYPE, 77 COLUMN_FILE_TYPE,
77 COLUMN_SIZE, 78 COLUMN_SIZE,
79 COLUMN_PLAY_TIME,
78 COLUMN_COUNT, // Number of columns 80 COLUMN_COUNT, // Number of columns
79 }; 81 };
80 82
81 explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, 83 explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
82 FileSys::ManualContentProvider* provider_, Core::System& system_, 84 FileSys::ManualContentProvider* provider_,
85 PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
83 GMainWindow* parent = nullptr); 86 GMainWindow* parent = nullptr);
84 ~GameList() override; 87 ~GameList() override;
85 88
@@ -113,6 +116,7 @@ signals:
113 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); 116 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
114 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, 117 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
115 const std::string& game_path); 118 const std::string& game_path);
119 void RemovePlayTimeRequested(u64 program_id);
116 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 120 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
117 void VerifyIntegrityRequested(const std::string& game_path); 121 void VerifyIntegrityRequested(const std::string& game_path);
118 void CopyTIDRequested(u64 program_id); 122 void CopyTIDRequested(u64 program_id);
@@ -168,6 +172,7 @@ private:
168 172
169 friend class GameListSearchField; 173 friend class GameListSearchField;
170 174
175 const PlayTime::PlayTimeManager& play_time_manager;
171 Core::System& system; 176 Core::System& system;
172}; 177};
173 178
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 1800f090f..86a0c41d9 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -18,6 +18,7 @@
18#include "common/common_types.h" 18#include "common/common_types.h"
19#include "common/logging/log.h" 19#include "common/logging/log.h"
20#include "common/string_util.h" 20#include "common/string_util.h"
21#include "yuzu/play_time_manager.h"
21#include "yuzu/uisettings.h" 22#include "yuzu/uisettings.h"
22#include "yuzu/util/util.h" 23#include "yuzu/util/util.h"
23 24
@@ -221,6 +222,31 @@ public:
221 } 222 }
222}; 223};
223 224
225/**
226 * GameListItem for Play Time values.
227 * This object stores the play time of a game in seconds, and its readable
228 * representation in minutes/hours
229 */
230class GameListItemPlayTime : public GameListItem {
231public:
232 static constexpr int PlayTimeRole = SortRole;
233
234 GameListItemPlayTime() = default;
235 explicit GameListItemPlayTime(const qulonglong time_seconds) {
236 setData(time_seconds, PlayTimeRole);
237 }
238
239 void setData(const QVariant& value, int role) override {
240 qulonglong time_seconds = value.toULongLong();
241 GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole);
242 GameListItem::setData(value, PlayTimeRole);
243 }
244
245 bool operator<(const QStandardItem& other) const override {
246 return data(PlayTimeRole).toULongLong() < other.data(PlayTimeRole).toULongLong();
247 }
248};
249
224class GameListDir : public GameListItem { 250class GameListDir : public GameListItem {
225public: 251public:
226 static constexpr int GameDirRole = Qt::UserRole + 2; 252 static constexpr int GameDirRole = Qt::UserRole + 2;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index e7fb8a282..077ced12b 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -194,6 +194,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
194 const std::size_t size, const std::vector<u8>& icon, 194 const std::size_t size, const std::vector<u8>& icon,
195 Loader::AppLoader& loader, u64 program_id, 195 Loader::AppLoader& loader, u64 program_id,
196 const CompatibilityList& compatibility_list, 196 const CompatibilityList& compatibility_list,
197 const PlayTime::PlayTimeManager& play_time_manager,
197 const FileSys::PatchManager& patch) { 198 const FileSys::PatchManager& patch) {
198 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 199 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
199 200
@@ -212,6 +213,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
212 new GameListItemCompat(compatibility), 213 new GameListItemCompat(compatibility),
213 new GameListItem(file_type_string), 214 new GameListItem(file_type_string),
214 new GameListItemSize(size), 215 new GameListItemSize(size),
216 new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
215 }; 217 };
216 218
217 const auto patch_versions = GetGameListCachedObject( 219 const auto patch_versions = GetGameListCachedObject(
@@ -227,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
227GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, 229GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
228 FileSys::ManualContentProvider* provider_, 230 FileSys::ManualContentProvider* provider_,
229 QVector<UISettings::GameDir>& game_dirs_, 231 QVector<UISettings::GameDir>& game_dirs_,
230 const CompatibilityList& compatibility_list_, Core::System& system_) 232 const CompatibilityList& compatibility_list_,
233 const PlayTime::PlayTimeManager& play_time_manager_,
234 Core::System& system_)
231 : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, 235 : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
232 compatibility_list{compatibility_list_}, system{system_} {} 236 compatibility_list{compatibility_list_},
237 play_time_manager{play_time_manager_}, system{system_} {}
233 238
234GameListWorker::~GameListWorker() = default; 239GameListWorker::~GameListWorker() = default;
235 240
@@ -280,7 +285,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
280 } 285 }
281 286
282 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, 287 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
283 program_id, compatibility_list, patch), 288 program_id, compatibility_list, play_time_manager, patch),
284 parent_dir); 289 parent_dir);
285 } 290 }
286} 291}
@@ -288,7 +293,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
288void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, 293void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
289 GameListDir* parent_dir) { 294 GameListDir* parent_dir) {
290 const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { 295 const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool {
291 if (stop_processing) { 296 if (stop_requested) {
292 // Breaks the callback loop. 297 // Breaks the callback loop.
293 return false; 298 return false;
294 } 299 }
@@ -357,7 +362,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
357 362
358 emit EntryReady(MakeGameListEntry(physical_name, name, 363 emit EntryReady(MakeGameListEntry(physical_name, name,
359 Common::FS::GetSize(physical_name), icon, 364 Common::FS::GetSize(physical_name), icon,
360 *loader, id, compatibility_list, patch), 365 *loader, id, compatibility_list,
366 play_time_manager, patch),
361 parent_dir); 367 parent_dir);
362 } 368 }
363 } else { 369 } else {
@@ -370,10 +376,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
370 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), 376 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
371 system.GetContentProvider()}; 377 system.GetContentProvider()};
372 378
373 emit EntryReady( 379 emit EntryReady(MakeGameListEntry(physical_name, name,
374 MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), 380 Common::FS::GetSize(physical_name), icon,
375 icon, *loader, program_id, compatibility_list, patch), 381 *loader, program_id, compatibility_list,
376 parent_dir); 382 play_time_manager, patch),
383 parent_dir);
377 } 384 }
378 } 385 }
379 } else if (is_dir) { 386 } else if (is_dir) {
@@ -392,7 +399,6 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
392} 399}
393 400
394void GameListWorker::run() { 401void GameListWorker::run() {
395 stop_processing = false;
396 provider->ClearAllEntries(); 402 provider->ClearAllEntries();
397 403
398 for (UISettings::GameDir& game_dir : game_dirs) { 404 for (UISettings::GameDir& game_dir : game_dirs) {
@@ -420,9 +426,11 @@ void GameListWorker::run() {
420 } 426 }
421 427
422 emit Finished(watch_list); 428 emit Finished(watch_list);
429 processing_completed.Set();
423} 430}
424 431
425void GameListWorker::Cancel() { 432void GameListWorker::Cancel() {
426 this->disconnect(); 433 this->disconnect();
427 stop_processing = true; 434 stop_requested.store(true);
435 processing_completed.Wait();
428} 436}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 24a4e92c3..54dc05e30 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -12,7 +12,9 @@
12#include <QRunnable> 12#include <QRunnable>
13#include <QString> 13#include <QString>
14 14
15#include "common/thread.h"
15#include "yuzu/compatibility_list.h" 16#include "yuzu/compatibility_list.h"
17#include "yuzu/play_time_manager.h"
16 18
17namespace Core { 19namespace Core {
18class System; 20class System;
@@ -36,7 +38,9 @@ public:
36 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, 38 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
37 FileSys::ManualContentProvider* provider_, 39 FileSys::ManualContentProvider* provider_,
38 QVector<UISettings::GameDir>& game_dirs_, 40 QVector<UISettings::GameDir>& game_dirs_,
39 const CompatibilityList& compatibility_list_, Core::System& system_); 41 const CompatibilityList& compatibility_list_,
42 const PlayTime::PlayTimeManager& play_time_manager_,
43 Core::System& system_);
40 ~GameListWorker() override; 44 ~GameListWorker() override;
41 45
42 /// Starts the processing of directory tree information. 46 /// Starts the processing of directory tree information.
@@ -76,9 +80,12 @@ private:
76 FileSys::ManualContentProvider* provider; 80 FileSys::ManualContentProvider* provider;
77 QVector<UISettings::GameDir>& game_dirs; 81 QVector<UISettings::GameDir>& game_dirs;
78 const CompatibilityList& compatibility_list; 82 const CompatibilityList& compatibility_list;
83 const PlayTime::PlayTimeManager& play_time_manager;
79 84
80 QStringList watch_list; 85 QStringList watch_list;
81 std::atomic_bool stop_processing; 86
87 Common::Event processing_completed;
88 std::atomic_bool stop_requested = false;
82 89
83 Core::System& system; 90 Core::System& system;
84}; 91};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 16fa92e2c..1431cf2fe 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -67,6 +67,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
67#define QT_NO_OPENGL 67#define QT_NO_OPENGL
68#include <QClipboard> 68#include <QClipboard>
69#include <QDesktopServices> 69#include <QDesktopServices>
70#include <QDir>
70#include <QFile> 71#include <QFile>
71#include <QFileDialog> 72#include <QFileDialog>
72#include <QInputDialog> 73#include <QInputDialog>
@@ -76,6 +77,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
76#include <QPushButton> 77#include <QPushButton>
77#include <QScreen> 78#include <QScreen>
78#include <QShortcut> 79#include <QShortcut>
80#include <QStandardPaths>
79#include <QStatusBar> 81#include <QStatusBar>
80#include <QString> 82#include <QString>
81#include <QSysInfo> 83#include <QSysInfo>
@@ -98,6 +100,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
98#include "common/scm_rev.h" 100#include "common/scm_rev.h"
99#include "common/scope_exit.h" 101#include "common/scope_exit.h"
100#ifdef _WIN32 102#ifdef _WIN32
103#include <shlobj.h>
101#include "common/windows/timer_resolution.h" 104#include "common/windows/timer_resolution.h"
102#endif 105#endif
103#ifdef ARCHITECTURE_x86_64 106#ifdef ARCHITECTURE_x86_64
@@ -150,6 +153,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
150#include "yuzu/install_dialog.h" 153#include "yuzu/install_dialog.h"
151#include "yuzu/loading_screen.h" 154#include "yuzu/loading_screen.h"
152#include "yuzu/main.h" 155#include "yuzu/main.h"
156#include "yuzu/play_time_manager.h"
153#include "yuzu/startup_checks.h" 157#include "yuzu/startup_checks.h"
154#include "yuzu/uisettings.h" 158#include "yuzu/uisettings.h"
155#include "yuzu/util/clickable_label.h" 159#include "yuzu/util/clickable_label.h"
@@ -207,7 +211,7 @@ void GMainWindow::ShowTelemetryCallout() {
207 tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous " 211 tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
208 "data is collected</a> to help improve yuzu. " 212 "data is collected</a> to help improve yuzu. "
209 "<br/><br/>Would you like to share your usage data with us?"); 213 "<br/><br/>Would you like to share your usage data with us?");
210 if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) { 214 if (!question(this, tr("Telemetry"), telemetry_message)) {
211 Settings::values.enable_telemetry = false; 215 Settings::values.enable_telemetry = false;
212 system->ApplySettings(); 216 system->ApplySettings();
213 } 217 }
@@ -338,6 +342,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
338 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); 342 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
339 discord_rpc->Update(); 343 discord_rpc->Update();
340 344
345 play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
346
341 system->GetRoomNetwork().Init(); 347 system->GetRoomNetwork().Init();
342 348
343 RegisterMetaTypes(); 349 RegisterMetaTypes();
@@ -986,7 +992,7 @@ void GMainWindow::InitializeWidgets() {
986 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); 992 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system);
987 render_window->hide(); 993 render_window->hide();
988 994
989 game_list = new GameList(vfs, provider.get(), *system, this); 995 game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this);
990 ui->horizontalLayout->addWidget(game_list); 996 ui->horizontalLayout->addWidget(game_list);
991 997
992 game_list_placeholder = new GameListPlaceholder(this); 998 game_list_placeholder = new GameListPlaceholder(this);
@@ -1447,6 +1453,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
1447 Settings::values.audio_muted = false; 1453 Settings::values.audio_muted = false;
1448 auto_muted = false; 1454 auto_muted = false;
1449 } 1455 }
1456 UpdateVolumeUI();
1450 } 1457 }
1451} 1458}
1452 1459
@@ -1460,6 +1467,8 @@ void GMainWindow::ConnectWidgetEvents() {
1460 connect(game_list, &GameList::RemoveInstalledEntryRequested, this, 1467 connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
1461 &GMainWindow::OnGameListRemoveInstalledEntry); 1468 &GMainWindow::OnGameListRemoveInstalledEntry);
1462 connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); 1469 connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
1470 connect(game_list, &GameList::RemovePlayTimeRequested, this,
1471 &GMainWindow::OnGameListRemovePlayTimeData);
1463 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); 1472 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
1464 connect(game_list, &GameList::VerifyIntegrityRequested, this, 1473 connect(game_list, &GameList::VerifyIntegrityRequested, this,
1465 &GMainWindow::OnGameListVerifyIntegrity); 1474 &GMainWindow::OnGameListVerifyIntegrity);
@@ -1553,6 +1562,7 @@ void GMainWindow::ConnectMenuEvents() {
1553 // Tools 1562 // Tools
1554 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, 1563 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
1555 ReinitializeKeyBehavior::Warning)); 1564 ReinitializeKeyBehavior::Warning));
1565 connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum);
1556 connect_menu(ui->action_Load_Cabinet_Nickname_Owner, 1566 connect_menu(ui->action_Load_Cabinet_Nickname_Owner,
1557 [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); }); 1567 [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); });
1558 connect_menu(ui->action_Load_Cabinet_Eraser, 1568 connect_menu(ui->action_Load_Cabinet_Eraser,
@@ -1590,6 +1600,7 @@ void GMainWindow::UpdateMenuState() {
1590 }; 1600 };
1591 1601
1592 const std::array applet_actions{ 1602 const std::array applet_actions{
1603 ui->action_Load_Album,
1593 ui->action_Load_Cabinet_Nickname_Owner, 1604 ui->action_Load_Cabinet_Nickname_Owner,
1594 ui->action_Load_Cabinet_Eraser, 1605 ui->action_Load_Cabinet_Eraser,
1595 ui->action_Load_Cabinet_Restorer, 1606 ui->action_Load_Cabinet_Restorer,
@@ -2409,9 +2420,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
2409 } 2420 }
2410 }(); 2421 }();
2411 2422
2412 if (QMessageBox::question(this, tr("Remove Entry"), entry_question, 2423 if (!question(this, tr("Remove Entry"), entry_question, QMessageBox::Yes | QMessageBox::No,
2413 QMessageBox::Yes | QMessageBox::No, 2424 QMessageBox::No)) {
2414 QMessageBox::No) != QMessageBox::Yes) {
2415 return; 2425 return;
2416 } 2426 }
2417 2427
@@ -2510,8 +2520,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
2510 } 2520 }
2511 }(); 2521 }();
2512 2522
2513 if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, 2523 if (!GMainWindow::question(this, tr("Remove File"), question,
2514 QMessageBox::No) != QMessageBox::Yes) { 2524 QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
2515 return; 2525 return;
2516 } 2526 }
2517 2527
@@ -2534,6 +2544,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
2534 } 2544 }
2535} 2545}
2536 2546
2547void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
2548 if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"),
2549 QMessageBox::Yes | QMessageBox::No,
2550 QMessageBox::No) != QMessageBox::Yes) {
2551 return;
2552 }
2553
2554 play_time_manager->ResetProgramPlayTime(program_id);
2555 game_list->PopulateAsync(UISettings::values.game_dirs);
2556}
2557
2537void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { 2558void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) {
2538 const auto target_file_name = [target] { 2559 const auto target_file_name = [target] {
2539 switch (target) { 2560 switch (target) {
@@ -2825,7 +2846,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2825 const QStringList args = QApplication::arguments(); 2846 const QStringList args = QApplication::arguments();
2826 std::filesystem::path yuzu_command = args[0].toStdString(); 2847 std::filesystem::path yuzu_command = args[0].toStdString();
2827 2848
2828#if defined(__linux__) || defined(__FreeBSD__)
2829 // If relative path, make it an absolute path 2849 // If relative path, make it an absolute path
2830 if (yuzu_command.c_str()[0] == '.') { 2850 if (yuzu_command.c_str()[0] == '.') {
2831 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; 2851 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
@@ -2848,44 +2868,52 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2848 UISettings::values.shortcut_already_warned = true; 2868 UISettings::values.shortcut_already_warned = true;
2849 } 2869 }
2850#endif // __linux__ 2870#endif // __linux__
2851#endif // __linux__ || __FreeBSD__
2852 2871
2853 std::filesystem::path target_directory{}; 2872 std::filesystem::path target_directory{};
2854 // Determine target directory for shortcut
2855#if defined(__linux__) || defined(__FreeBSD__)
2856 const char* home = std::getenv("HOME");
2857 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2858 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2859 2873
2860 if (target == GameListShortcutTarget::Desktop) { 2874 switch (target) {
2861 target_directory = home_path / "Desktop"; 2875 case GameListShortcutTarget::Desktop: {
2862 if (!Common::FS::IsDir(target_directory)) { 2876 const QString desktop_path =
2863 QMessageBox::critical( 2877 QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
2864 this, tr("Create Shortcut"), 2878 target_directory = desktop_path.toUtf8().toStdString();
2865 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") 2879 break;
2866 .arg(QString::fromStdString(target_directory)), 2880 }
2867 QMessageBox::StandardButton::Ok); 2881 case GameListShortcutTarget::Applications: {
2868 return; 2882 const QString applications_path =
2869 } 2883 QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
2870 } else if (target == GameListShortcutTarget::Applications) { 2884 if (applications_path.isEmpty()) {
2871 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / 2885 const char* home = std::getenv("HOME");
2872 "applications"; 2886 if (home != nullptr) {
2873 if (!Common::FS::CreateDirs(target_directory)) { 2887 target_directory = std::filesystem::path(home) / ".local/share/applications";
2874 QMessageBox::critical(this, tr("Create Shortcut"), 2888 }
2875 tr("Cannot create shortcut in applications menu. Path \"%1\" " 2889 } else {
2876 "does not exist and cannot be created.") 2890 target_directory = applications_path.toUtf8().toStdString();
2877 .arg(QString::fromStdString(target_directory)),
2878 QMessageBox::StandardButton::Ok);
2879 return;
2880 } 2891 }
2892 break;
2893 }
2894 default:
2895 return;
2896 }
2897
2898 const QDir dir(QString::fromStdString(target_directory.generic_string()));
2899 if (!dir.exists()) {
2900 QMessageBox::critical(this, tr("Create Shortcut"),
2901 tr("Cannot create shortcut. Path \"%1\" does not exist.")
2902 .arg(QString::fromStdString(target_directory.generic_string())),
2903 QMessageBox::StandardButton::Ok);
2904 return;
2881 } 2905 }
2882#endif
2883 2906
2884 const std::string game_file_name = std::filesystem::path(game_path).filename().string(); 2907 const std::string game_file_name = std::filesystem::path(game_path).filename().string();
2885 // Determine full paths for icon and shortcut 2908 // Determine full paths for icon and shortcut
2886#if defined(__linux__) || defined(__FreeBSD__) 2909#if defined(__linux__) || defined(__FreeBSD__)
2910 const char* home = std::getenv("HOME");
2911 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2912 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2913
2887 std::filesystem::path system_icons_path = 2914 std::filesystem::path system_icons_path =
2888 (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) / 2915 (xdg_data_home == nullptr ? home_path / ".local/share/"
2916 : std::filesystem::path(xdg_data_home)) /
2889 "icons/hicolor/256x256"; 2917 "icons/hicolor/256x256";
2890 if (!Common::FS::CreateDirs(system_icons_path)) { 2918 if (!Common::FS::CreateDirs(system_icons_path)) {
2891 QMessageBox::critical( 2919 QMessageBox::critical(
@@ -2901,9 +2929,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2901 const std::filesystem::path shortcut_path = 2929 const std::filesystem::path shortcut_path =
2902 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) 2930 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
2903 : fmt::format("yuzu-{:016X}.desktop", program_id)); 2931 : fmt::format("yuzu-{:016X}.desktop", program_id));
2932#elif defined(WIN32)
2933 std::filesystem::path icons_path =
2934 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
2935 std::filesystem::path icon_path =
2936 icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
2937 : fmt::format("yuzu-{:016X}.ico", program_id)));
2904#else 2938#else
2905 const std::filesystem::path icon_path{}; 2939 std::string icon_extension;
2906 const std::filesystem::path shortcut_path{};
2907#endif 2940#endif
2908 2941
2909 // Get title from game file 2942 // Get title from game file
@@ -2928,29 +2961,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2928 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); 2961 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
2929 } 2962 }
2930 2963
2931 QImage icon_jpeg = 2964 QImage icon_data =
2932 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); 2965 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
2933#if defined(__linux__) || defined(__FreeBSD__) 2966#if defined(__linux__) || defined(__FreeBSD__)
2934 // Convert and write the icon as a PNG 2967 // Convert and write the icon as a PNG
2935 if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) { 2968 if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
2936 LOG_ERROR(Frontend, "Could not write icon as PNG to file"); 2969 LOG_ERROR(Frontend, "Could not write icon as PNG to file");
2937 } else { 2970 } else {
2938 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); 2971 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
2939 } 2972 }
2973#elif defined(WIN32)
2974 if (!SaveIconToFile(icon_path.string(), icon_data)) {
2975 LOG_ERROR(Frontend, "Could not write icon to file");
2976 return;
2977 }
2940#endif // __linux__ 2978#endif // __linux__
2941 2979
2942#if defined(__linux__) || defined(__FreeBSD__) 2980#ifdef _WIN32
2981 // Replace characters that are illegal in Windows filenames by a dash
2982 const std::string illegal_chars = "<>:\"/\\|?*";
2983 for (char c : illegal_chars) {
2984 std::replace(title.begin(), title.end(), c, '_');
2985 }
2986 const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
2987#endif
2988
2943 const std::string comment = 2989 const std::string comment =
2944 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); 2990 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
2945 const std::string arguments = fmt::format("-g \"{:s}\"", game_path); 2991 const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
2946 const std::string categories = "Game;Emulator;Qt;"; 2992 const std::string categories = "Game;Emulator;Qt;";
2947 const std::string keywords = "Switch;Nintendo;"; 2993 const std::string keywords = "Switch;Nintendo;";
2948#else 2994
2949 const std::string comment{};
2950 const std::string arguments{};
2951 const std::string categories{};
2952 const std::string keywords{};
2953#endif
2954 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), 2995 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
2955 yuzu_command.string(), arguments, categories, keywords)) { 2996 yuzu_command.string(), arguments, categories, keywords)) {
2956 QMessageBox::critical(this, tr("Create Shortcut"), 2997 QMessageBox::critical(this, tr("Create Shortcut"),
@@ -3357,6 +3398,9 @@ void GMainWindow::OnStartGame() {
3357 UpdateMenuState(); 3398 UpdateMenuState();
3358 OnTasStateChanged(); 3399 OnTasStateChanged();
3359 3400
3401 play_time_manager->SetProgramId(system->GetApplicationProcessProgramID());
3402 play_time_manager->Start();
3403
3360 discord_rpc->Update(); 3404 discord_rpc->Update();
3361} 3405}
3362 3406
@@ -3364,14 +3408,18 @@ void GMainWindow::OnRestartGame() {
3364 if (!system->IsPoweredOn()) { 3408 if (!system->IsPoweredOn()) {
3365 return; 3409 return;
3366 } 3410 }
3367 // Make a copy since ShutdownGame edits game_path 3411
3368 const auto current_game = QString(current_game_path); 3412 if (ConfirmShutdownGame()) {
3369 ShutdownGame(); 3413 // Make a copy since ShutdownGame edits game_path
3370 BootGame(current_game); 3414 const auto current_game = QString(current_game_path);
3415 ShutdownGame();
3416 BootGame(current_game);
3417 }
3371} 3418}
3372 3419
3373void GMainWindow::OnPauseGame() { 3420void GMainWindow::OnPauseGame() {
3374 emu_thread->SetRunning(false); 3421 emu_thread->SetRunning(false);
3422 play_time_manager->Stop();
3375 UpdateMenuState(); 3423 UpdateMenuState();
3376 AllowOSSleep(); 3424 AllowOSSleep();
3377} 3425}
@@ -3388,15 +3436,39 @@ void GMainWindow::OnPauseContinueGame() {
3388} 3436}
3389 3437
3390void GMainWindow::OnStopGame() { 3438void GMainWindow::OnStopGame() {
3391 if (system->GetExitLocked() && !ConfirmForceLockedExit()) { 3439 if (ConfirmShutdownGame()) {
3392 return; 3440 play_time_manager->Stop();
3441 // Update game list to show new play time
3442 game_list->PopulateAsync(UISettings::values.game_dirs);
3443 if (OnShutdownBegin()) {
3444 OnShutdownBeginDialog();
3445 } else {
3446 OnEmulationStopped();
3447 }
3393 } 3448 }
3449}
3394 3450
3395 if (OnShutdownBegin()) { 3451bool GMainWindow::ConfirmShutdownGame() {
3396 OnShutdownBeginDialog(); 3452 if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Always) {
3453 if (system->GetExitLocked()) {
3454 if (!ConfirmForceLockedExit()) {
3455 return false;
3456 }
3457 } else {
3458 if (!ConfirmChangeGame()) {
3459 return false;
3460 }
3461 }
3397 } else { 3462 } else {
3398 OnEmulationStopped(); 3463 if (UISettings::values.confirm_before_stopping.GetValue() ==
3464 ConfirmStop::Ask_Based_On_Game &&
3465 system->GetExitLocked()) {
3466 if (!ConfirmForceLockedExit()) {
3467 return false;
3468 }
3469 }
3399 } 3470 }
3471 return true;
3400} 3472}
3401 3473
3402void GMainWindow::OnLoadComplete() { 3474void GMainWindow::OnLoadComplete() {
@@ -3776,22 +3848,11 @@ void GMainWindow::OnTasRecord() {
3776 const bool is_recording = input_subsystem->GetTas()->Record(); 3848 const bool is_recording = input_subsystem->GetTas()->Record();
3777 if (!is_recording) { 3849 if (!is_recording) {
3778 is_tas_recording_dialog_active = true; 3850 is_tas_recording_dialog_active = true;
3779 ControllerNavigation* controller_navigation = 3851
3780 new ControllerNavigation(system->HIDCore(), this); 3852 bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
3781 // Use QMessageBox instead of question so we can link controller navigation 3853 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
3782 QMessageBox* box_dialog = new QMessageBox(); 3854
3783 box_dialog->setWindowTitle(tr("TAS Recording")); 3855 input_subsystem->GetTas()->SaveRecording(answer);
3784 box_dialog->setText(tr("Overwrite file of player 1?"));
3785 box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
3786 box_dialog->setDefaultButton(QMessageBox::Yes);
3787 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
3788 [box_dialog](Qt::Key key) {
3789 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
3790 QCoreApplication::postEvent(box_dialog, event);
3791 });
3792 int res = box_dialog->exec();
3793 controller_navigation->UnloadController();
3794 input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
3795 is_tas_recording_dialog_active = false; 3856 is_tas_recording_dialog_active = false;
3796 } 3857 }
3797 OnTasStateChanged(); 3858 OnTasStateChanged();
@@ -3965,6 +4026,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
3965 shortcut_stream.close(); 4026 shortcut_stream.close();
3966 4027
3967 return true; 4028 return true;
4029#elif defined(WIN32)
4030 IShellLinkW* shell_link;
4031 auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
4032 (void**)&shell_link);
4033 if (FAILED(hres)) {
4034 return false;
4035 }
4036 shell_link->SetPath(
4037 Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
4038 shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
4039 shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
4040 shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
4041
4042 IPersistFile* persist_file;
4043 hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
4044 if (FAILED(hres)) {
4045 return false;
4046 }
4047
4048 hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
4049 if (FAILED(hres)) {
4050 return false;
4051 }
4052
4053 persist_file->Release();
4054 shell_link->Release();
4055
4056 return true;
3968#endif 4057#endif
3969 return false; 4058 return false;
3970} 4059}
@@ -4004,6 +4093,29 @@ void GMainWindow::OnLoadAmiibo() {
4004 LoadAmiibo(filename); 4093 LoadAmiibo(filename);
4005} 4094}
4006 4095
4096bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text,
4097 QMessageBox::StandardButtons buttons,
4098 QMessageBox::StandardButton defaultButton) {
4099
4100 QMessageBox* box_dialog = new QMessageBox(parent);
4101 box_dialog->setWindowTitle(title);
4102 box_dialog->setText(text);
4103 box_dialog->setStandardButtons(buttons);
4104 box_dialog->setDefaultButton(defaultButton);
4105
4106 ControllerNavigation* controller_navigation =
4107 new ControllerNavigation(system->HIDCore(), box_dialog);
4108 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
4109 [box_dialog](Qt::Key key) {
4110 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
4111 QCoreApplication::postEvent(box_dialog, event);
4112 });
4113 int res = box_dialog->exec();
4114
4115 controller_navigation->UnloadController();
4116 return res == QMessageBox::Yes;
4117}
4118
4007void GMainWindow::LoadAmiibo(const QString& filename) { 4119void GMainWindow::LoadAmiibo(const QString& filename) {
4008 auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); 4120 auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
4009 const QString title = tr("Error loading Amiibo data"); 4121 const QString title = tr("Error loading Amiibo data");
@@ -4157,6 +4269,29 @@ void GMainWindow::OnToggleStatusBar() {
4157 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); 4269 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
4158} 4270}
4159 4271
4272void GMainWindow::OnAlbum() {
4273 constexpr u64 AlbumId = 0x010000000000100Dull;
4274 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4275 if (!bis_system) {
4276 QMessageBox::warning(this, tr("No firmware available"),
4277 tr("Please install the firmware to use the Album applet."));
4278 return;
4279 }
4280
4281 auto album_nca = bis_system->GetEntry(AlbumId, FileSys::ContentRecordType::Program);
4282 if (!album_nca) {
4283 QMessageBox::warning(this, tr("Album Applet"),
4284 tr("Album applet is not available. Please reinstall firmware."));
4285 return;
4286 }
4287
4288 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::PhotoViewer);
4289
4290 const auto filename = QString::fromStdString(album_nca->GetFullPath());
4291 UISettings::values.roms_path = QFileInfo(filename).path();
4292 BootGame(filename);
4293}
4294
4160void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) { 4295void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
4161 constexpr u64 CabinetId = 0x0100000000001002ull; 4296 constexpr u64 CabinetId = 0x0100000000001002ull;
4162 auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); 4297 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
@@ -4714,8 +4849,7 @@ bool GMainWindow::ConfirmClose() {
4714 return true; 4849 return true;
4715 } 4850 }
4716 const auto text = tr("Are you sure you want to close yuzu?"); 4851 const auto text = tr("Are you sure you want to close yuzu?");
4717 const auto answer = QMessageBox::question(this, tr("yuzu"), text); 4852 return question(this, tr("yuzu"), text);
4718 return answer != QMessageBox::No;
4719} 4853}
4720 4854
4721void GMainWindow::closeEvent(QCloseEvent* event) { 4855void GMainWindow::closeEvent(QCloseEvent* event) {
@@ -4808,11 +4942,11 @@ bool GMainWindow::ConfirmChangeGame() {
4808 if (emu_thread == nullptr) 4942 if (emu_thread == nullptr)
4809 return true; 4943 return true;
4810 4944
4811 const auto answer = QMessageBox::question( 4945 // Use custom question to link controller navigation
4946 return question(
4812 this, tr("yuzu"), 4947 this, tr("yuzu"),
4813 tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), 4948 tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
4814 QMessageBox::Yes | QMessageBox::No, QMessageBox::No); 4949 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
4815 return answer != QMessageBox::No;
4816} 4950}
4817 4951
4818bool GMainWindow::ConfirmForceLockedExit() { 4952bool GMainWindow::ConfirmForceLockedExit() {
@@ -4822,8 +4956,7 @@ bool GMainWindow::ConfirmForceLockedExit() {
4822 const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" 4956 const auto text = tr("The currently running application has requested yuzu to not exit.\n\n"
4823 "Would you like to bypass this and exit anyway?"); 4957 "Would you like to bypass this and exit anyway?");
4824 4958
4825 const auto answer = QMessageBox::question(this, tr("yuzu"), text); 4959 return question(this, tr("yuzu"), text);
4826 return answer != QMessageBox::No;
4827} 4960}
4828 4961
4829void GMainWindow::RequestGameExit() { 4962void GMainWindow::RequestGameExit() {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 52028234c..270a40c5f 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -7,6 +7,7 @@
7#include <optional> 7#include <optional>
8 8
9#include <QMainWindow> 9#include <QMainWindow>
10#include <QMessageBox>
10#include <QTimer> 11#include <QTimer>
11#include <QTranslator> 12#include <QTranslator>
12 13
@@ -15,6 +16,7 @@
15#include "input_common/drivers/tas_input.h" 16#include "input_common/drivers/tas_input.h"
16#include "yuzu/compatibility_list.h" 17#include "yuzu/compatibility_list.h"
17#include "yuzu/hotkeys.h" 18#include "yuzu/hotkeys.h"
19#include "yuzu/util/controller_navigation.h"
18 20
19#ifdef __unix__ 21#ifdef __unix__
20#include <QVariant> 22#include <QVariant>
@@ -81,6 +83,10 @@ namespace DiscordRPC {
81class DiscordInterface; 83class DiscordInterface;
82} 84}
83 85
86namespace PlayTime {
87class PlayTimeManager;
88}
89
84namespace FileSys { 90namespace FileSys {
85class ContentProvider; 91class ContentProvider;
86class ManualContentProvider; 92class ManualContentProvider;
@@ -323,6 +329,7 @@ private slots:
323 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); 329 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
324 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, 330 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
325 const std::string& game_path); 331 const std::string& game_path);
332 void OnGameListRemovePlayTimeData(u64 program_id);
326 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 333 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
327 void OnGameListVerifyIntegrity(const std::string& game_path); 334 void OnGameListVerifyIntegrity(const std::string& game_path);
328 void OnGameListCopyTID(u64 program_id); 335 void OnGameListCopyTID(u64 program_id);
@@ -369,6 +376,7 @@ private slots:
369 void ResetWindowSize720(); 376 void ResetWindowSize720();
370 void ResetWindowSize900(); 377 void ResetWindowSize900();
371 void ResetWindowSize1080(); 378 void ResetWindowSize1080();
379 void OnAlbum();
372 void OnCabinet(Service::NFP::CabinetMode mode); 380 void OnCabinet(Service::NFP::CabinetMode mode);
373 void OnMiiEdit(); 381 void OnMiiEdit();
374 void OnCaptureScreenshot(); 382 void OnCaptureScreenshot();
@@ -389,6 +397,7 @@ private:
389 void RemoveVulkanDriverPipelineCache(u64 program_id); 397 void RemoveVulkanDriverPipelineCache(u64 program_id);
390 void RemoveAllTransferableShaderCaches(u64 program_id); 398 void RemoveAllTransferableShaderCaches(u64 program_id);
391 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); 399 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
400 void RemovePlayTimeData(u64 program_id);
392 void RemoveCacheStorage(u64 program_id); 401 void RemoveCacheStorage(u64 program_id);
393 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, 402 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
394 u64* selected_title_id, u8* selected_content_record_type); 403 u64* selected_title_id, u8* selected_content_record_type);
@@ -417,6 +426,11 @@ private:
417 bool CheckSystemArchiveDecryption(); 426 bool CheckSystemArchiveDecryption();
418 bool CheckFirmwarePresence(); 427 bool CheckFirmwarePresence();
419 void ConfigureFilesystemProvider(const std::string& filepath); 428 void ConfigureFilesystemProvider(const std::string& filepath);
429 /**
430 * Open (or not) the right confirm dialog based on current setting and game exit lock
431 * @returns true if the player confirmed or the settings do no require it
432 */
433 bool ConfirmShutdownGame();
420 434
421 QString GetTasStateDescription() const; 435 QString GetTasStateDescription() const;
422 bool CreateShortcut(const std::string& shortcut_path, const std::string& title, 436 bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
@@ -424,10 +438,22 @@ private:
424 const std::string& command, const std::string& arguments, 438 const std::string& command, const std::string& arguments,
425 const std::string& categories, const std::string& keywords); 439 const std::string& categories, const std::string& keywords);
426 440
441 /**
442 * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog
443 * The only difference is that it returns a boolean.
444 *
445 * @returns true if buttons contains QMessageBox::Yes and the user clicks on the "Yes" button.
446 */
447 bool question(QWidget* parent, const QString& title, const QString& text,
448 QMessageBox::StandardButtons buttons =
449 QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
450 QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
451
427 std::unique_ptr<Ui::MainWindow> ui; 452 std::unique_ptr<Ui::MainWindow> ui;
428 453
429 std::unique_ptr<Core::System> system; 454 std::unique_ptr<Core::System> system;
430 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; 455 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
456 std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
431 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; 457 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
432 458
433 MultiplayerState* multiplayer_state = nullptr; 459 MultiplayerState* multiplayer_state = nullptr;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 31c3de9ef..88684ffb5 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -160,6 +160,7 @@
160 <addaction name="action_Verify_installed_contents"/> 160 <addaction name="action_Verify_installed_contents"/>
161 <addaction name="separator"/> 161 <addaction name="separator"/>
162 <addaction name="menu_cabinet_applet"/> 162 <addaction name="menu_cabinet_applet"/>
163 <addaction name="action_Load_Album"/>
163 <addaction name="action_Load_Mii_Edit"/> 164 <addaction name="action_Load_Mii_Edit"/>
164 <addaction name="separator"/> 165 <addaction name="separator"/>
165 <addaction name="action_Capture_Screenshot"/> 166 <addaction name="action_Capture_Screenshot"/>
@@ -380,6 +381,11 @@
380 <string>&amp;Capture Screenshot</string> 381 <string>&amp;Capture Screenshot</string>
381 </property> 382 </property>
382 </action> 383 </action>
384 <action name="action_Load_Album">
385 <property name="text">
386 <string>Open &amp;Album</string>
387 </property>
388 </action>
383 <action name="action_Load_Cabinet_Nickname_Owner"> 389 <action name="action_Load_Cabinet_Nickname_Owner">
384 <property name="text"> 390 <property name="text">
385 <string>&amp;Set Nickname and Owner</string> 391 <string>&amp;Set Nickname and Owner</string>
diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp
new file mode 100644
index 000000000..155c36b7d
--- /dev/null
+++ b/src/yuzu/play_time_manager.cpp
@@ -0,0 +1,179 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/alignment.h"
5#include "common/fs/file.h"
6#include "common/fs/fs.h"
7#include "common/fs/path_util.h"
8#include "common/logging/log.h"
9#include "common/settings.h"
10#include "common/thread.h"
11#include "core/hle/service/acc/profile_manager.h"
12#include "yuzu/play_time_manager.h"
13
14namespace PlayTime {
15
16namespace {
17
18struct PlayTimeElement {
19 ProgramId program_id;
20 PlayTime play_time;
21};
22
23std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() {
24 const Service::Account::ProfileManager manager;
25 const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user));
26 if (!uuid.has_value()) {
27 return std::nullopt;
28 }
29 return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) /
30 uuid->RawString().append(".bin");
31}
32
33[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) {
34 const auto filename = GetCurrentUserPlayTimePath();
35
36 if (!filename.has_value()) {
37 LOG_ERROR(Frontend, "Failed to get current user path");
38 return false;
39 }
40
41 out_play_time_db.clear();
42
43 if (Common::FS::Exists(filename.value())) {
44 Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read,
45 Common::FS::FileType::BinaryFile};
46 if (!file.IsOpen()) {
47 LOG_ERROR(Frontend, "Failed to open play time file: {}",
48 Common::FS::PathToUTF8String(filename.value()));
49 return false;
50 }
51
52 const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement);
53 std::vector<PlayTimeElement> elements(num_elements);
54
55 if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) {
56 return false;
57 }
58
59 for (const auto& [program_id, play_time] : elements) {
60 if (program_id != 0) {
61 out_play_time_db[program_id] = play_time;
62 }
63 }
64 }
65
66 return true;
67}
68
69[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) {
70 const auto filename = GetCurrentUserPlayTimePath();
71
72 if (!filename.has_value()) {
73 LOG_ERROR(Frontend, "Failed to get current user path");
74 return false;
75 }
76
77 Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write,
78 Common::FS::FileType::BinaryFile};
79 if (!file.IsOpen()) {
80 LOG_ERROR(Frontend, "Failed to open play time file: {}",
81 Common::FS::PathToUTF8String(filename.value()));
82 return false;
83 }
84
85 std::vector<PlayTimeElement> elements;
86 elements.reserve(play_time_db.size());
87
88 for (auto& [program_id, play_time] : play_time_db) {
89 if (program_id != 0) {
90 elements.push_back(PlayTimeElement{program_id, play_time});
91 }
92 }
93
94 return file.WriteSpan<PlayTimeElement>(elements) == elements.size();
95}
96
97} // namespace
98
99PlayTimeManager::PlayTimeManager() {
100 if (!ReadPlayTimeFile(database)) {
101 LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default.");
102 }
103}
104
105PlayTimeManager::~PlayTimeManager() {
106 Save();
107}
108
109void PlayTimeManager::SetProgramId(u64 program_id) {
110 running_program_id = program_id;
111}
112
113void PlayTimeManager::Start() {
114 play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); });
115}
116
117void PlayTimeManager::Stop() {
118 play_time_thread = {};
119}
120
121void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
122 Common::SetCurrentThreadName("PlayTimeReport");
123
124 using namespace std::literals::chrono_literals;
125 using std::chrono::seconds;
126 using std::chrono::steady_clock;
127
128 auto timestamp = steady_clock::now();
129
130 const auto GetDuration = [&]() -> u64 {
131 const auto last_timestamp = std::exchange(timestamp, steady_clock::now());
132 const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp);
133 return static_cast<u64>(duration.count());
134 };
135
136 while (!stop_token.stop_requested()) {
137 Common::StoppableTimedWait(stop_token, 30s);
138
139 database[running_program_id] += GetDuration();
140 Save();
141 }
142}
143
144void PlayTimeManager::Save() {
145 if (!WritePlayTimeFile(database)) {
146 LOG_ERROR(Frontend, "Failed to update play time database!");
147 }
148}
149
150u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
151 auto it = database.find(program_id);
152 if (it != database.end()) {
153 return it->second;
154 } else {
155 return 0;
156 }
157}
158
159void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
160 database.erase(program_id);
161 Save();
162}
163
164QString ReadablePlayTime(qulonglong time_seconds) {
165 if (time_seconds == 0) {
166 return {};
167 }
168 const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
169 const auto time_hours = static_cast<double>(time_seconds) / 3600;
170 const bool is_minutes = time_minutes < 60;
171 const char* unit = is_minutes ? "m" : "h";
172 const auto value = is_minutes ? time_minutes : time_hours;
173
174 return QStringLiteral("%L1 %2")
175 .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
176 .arg(QString::fromUtf8(unit));
177}
178
179} // namespace PlayTime
diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h
new file mode 100644
index 000000000..5f96f3447
--- /dev/null
+++ b/src/yuzu/play_time_manager.h
@@ -0,0 +1,44 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <QString>
7
8#include <map>
9
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12#include "common/polyfill_thread.h"
13
14namespace PlayTime {
15
16using ProgramId = u64;
17using PlayTime = u64;
18using PlayTimeDatabase = std::map<ProgramId, PlayTime>;
19
20class PlayTimeManager {
21public:
22 explicit PlayTimeManager();
23 ~PlayTimeManager();
24
25 YUZU_NON_COPYABLE(PlayTimeManager);
26 YUZU_NON_MOVEABLE(PlayTimeManager);
27
28 u64 GetPlayTime(u64 program_id) const;
29 void ResetProgramPlayTime(u64 program_id);
30 void SetProgramId(u64 program_id);
31 void Start();
32 void Stop();
33
34private:
35 PlayTimeDatabase database;
36 u64 running_program_id;
37 std::jthread play_time_thread;
38 void AutoTimestamp(std::stop_token stop_token);
39 void Save();
40};
41
42QString ReadablePlayTime(qulonglong time_seconds);
43
44} // namespace PlayTime
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 8efd63f31..b62ff620c 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -16,7 +16,9 @@
16#include "common/settings_enums.h" 16#include "common/settings_enums.h"
17 17
18using Settings::Category; 18using Settings::Category;
19using Settings::ConfirmStop;
19using Settings::Setting; 20using Settings::Setting;
21using Settings::SwitchableSetting;
20 22
21#ifndef CANNOT_EXPLICITLY_INSTANTIATE 23#ifndef CANNOT_EXPLICITLY_INSTANTIATE
22namespace Settings { 24namespace Settings {
@@ -94,6 +96,15 @@ struct Values {
94 Setting<bool> confirm_before_closing{ 96 Setting<bool> confirm_before_closing{
95 linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default, 97 linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
96 true, true}; 98 true, true};
99
100 SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
101 ConfirmStop::Ask_Always,
102 "confirmStop",
103 Category::UiGeneral,
104 Settings::Specialization::Default,
105 true,
106 true};
107
97 Setting<bool> first_start{linkage, true, "firstStart", Category::Ui}; 108 Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
98 Setting<bool> pause_when_in_background{linkage, 109 Setting<bool> pause_when_in_background{linkage,
99 false, 110 false,
@@ -103,7 +114,7 @@ struct Values {
103 true, 114 true,
104 true}; 115 true};
105 Setting<bool> mute_when_in_background{ 116 Setting<bool> mute_when_in_background{
106 linkage, false, "muteWhenInBackground", Category::Ui, Settings::Specialization::Default, 117 linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default,
107 true, true}; 118 true, true};
108 Setting<bool> hide_mouse{ 119 Setting<bool> hide_mouse{
109 linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default, 120 linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
@@ -183,6 +194,9 @@ struct Values {
183 Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList}; 194 Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
184 Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList}; 195 Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
185 196
197 // Play time
198 Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
199
186 bool configuration_applied; 200 bool configuration_applied;
187 bool reset_to_defaults; 201 bool reset_to_defaults;
188 bool shortcut_already_warned{false}; 202 bool shortcut_already_warned{false};
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 5c3e4589e..f2854c8ec 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -5,6 +5,10 @@
5#include <cmath> 5#include <cmath>
6#include <QPainter> 6#include <QPainter>
7#include "yuzu/util/util.h" 7#include "yuzu/util/util.h"
8#ifdef _WIN32
9#include <windows.h>
10#include "common/fs/file.h"
11#endif
8 12
9QFont GetMonospaceFont() { 13QFont GetMonospaceFont() {
10 QFont font(QStringLiteral("monospace")); 14 QFont font(QStringLiteral("monospace"));
@@ -37,3 +41,101 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
37 painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0); 41 painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
38 return circle_pixmap; 42 return circle_pixmap;
39} 43}
44
45bool SaveIconToFile(const std::string_view path, const QImage& image) {
46#if defined(WIN32)
47#pragma pack(push, 2)
48 struct IconDir {
49 WORD id_reserved;
50 WORD id_type;
51 WORD id_count;
52 };
53
54 struct IconDirEntry {
55 BYTE width;
56 BYTE height;
57 BYTE color_count;
58 BYTE reserved;
59 WORD planes;
60 WORD bit_count;
61 DWORD bytes_in_res;
62 DWORD image_offset;
63 };
64#pragma pack(pop)
65
66 const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
67 constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
68 constexpr int bytes_per_pixel = 4;
69
70 const IconDir icon_dir{
71 .id_reserved = 0,
72 .id_type = 1,
73 .id_count = static_cast<WORD>(scale_sizes.size()),
74 };
75
76 Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
77 Common::FS::FileType::BinaryFile);
78 if (!icon_file.IsOpen()) {
79 return false;
80 }
81
82 if (!icon_file.Write(icon_dir)) {
83 return false;
84 }
85
86 std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
87 for (std::size_t i = 0; i < scale_sizes.size(); i++) {
88 const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
89 const IconDirEntry icon_entry{
90 .width = static_cast<BYTE>(scale_sizes[i]),
91 .height = static_cast<BYTE>(scale_sizes[i]),
92 .color_count = 0,
93 .reserved = 0,
94 .planes = 1,
95 .bit_count = bytes_per_pixel * 8,
96 .bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
97 .image_offset = static_cast<DWORD>(image_offset),
98 };
99 image_offset += icon_entry.bytes_in_res;
100 if (!icon_file.Write(icon_entry)) {
101 return false;
102 }
103 }
104
105 for (std::size_t i = 0; i < scale_sizes.size(); i++) {
106 const QImage scaled_image = source_image.scaled(
107 scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
108 const BITMAPINFOHEADER info_header{
109 .biSize = sizeof(BITMAPINFOHEADER),
110 .biWidth = scaled_image.width(),
111 .biHeight = scaled_image.height() * 2,
112 .biPlanes = 1,
113 .biBitCount = bytes_per_pixel * 8,
114 .biCompression = BI_RGB,
115 .biSizeImage{},
116 .biXPelsPerMeter{},
117 .biYPelsPerMeter{},
118 .biClrUsed{},
119 .biClrImportant{},
120 };
121
122 if (!icon_file.Write(info_header)) {
123 return false;
124 }
125
126 for (int y = 0; y < scaled_image.height(); y++) {
127 const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
128 std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
129 std::memcpy(line_data.data(), line, line_data.size());
130 if (!icon_file.Write(line_data)) {
131 return false;
132 }
133 }
134 }
135 icon_file.Close();
136
137 return true;
138#else
139 return false;
140#endif
141}
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index 39dd2d895..09c14ce3f 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -7,14 +7,22 @@
7#include <QString> 7#include <QString>
8 8
9/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc. 9/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
10QFont GetMonospaceFont(); 10[[nodiscard]] QFont GetMonospaceFont();
11 11
12/// Convert a size in bytes into a readable format (KiB, MiB, etc.) 12/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
13QString ReadableByteSize(qulonglong size); 13[[nodiscard]] QString ReadableByteSize(qulonglong size);
14 14
15/** 15/**
16 * Creates a circle pixmap from a specified color 16 * Creates a circle pixmap from a specified color
17 * @param color The color the pixmap shall have 17 * @param color The color the pixmap shall have
18 * @return QPixmap circle pixmap 18 * @return QPixmap circle pixmap
19 */ 19 */
20QPixmap CreateCirclePixmapFromColor(const QColor& color); 20[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
21
22/**
23 * Saves a windows icon to a file
24 * @param path The icons path
25 * @param image The image to save
26 * @return bool If the operation succeeded
27 */
28[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);