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--.codespellrc2
-rw-r--r--.reuse/dep54
-rw-r--r--CMakeLists.txt46
-rw-r--r--CMakeModules/Findstb.cmake31
-rw-r--r--dist/org.yuzu_emu.yuzu.desktop1
-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.txt4
-rw-r--r--externals/nx_tzdb/CMakeLists.txt2
m---------externals/nx_tzdb/tzdb_to_nx0
-rw-r--r--externals/stb/stb_image.h7987
-rw-r--r--externals/stb/stb_image_resize.h2634
-rw-r--r--externals/stb/stb_image_write.h1724
-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.kt46
-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/card_home_option.xml3
-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.txt14
-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/nvidia_flags.cpp1
-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/stb.cpp8
-rw-r--r--src/common/stb.h8
-rw-r--r--src/common/string_util.cpp5
-rw-r--r--src/common/string_util.h1
-rw-r--r--src/common/thread.cpp24
-rw-r--r--src/common/wall_clock.cpp8
-rw-r--r--src/core/CMakeLists.txt8
-rw-r--r--src/core/arm/arm_interface.cpp6
-rw-r--r--src/core/core.cpp22
-rw-r--r--src/core/debugger/debugger.cpp24
-rw-r--r--src/core/debugger/gdbstub.cpp69
-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.cpp18
-rw-r--r--src/core/file_sys/program_metadata.h16
-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/system_archive/system_version.cpp4
-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/board/nintendo/nx/k_system_control.cpp59
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.h13
-rw-r--r--src/core/hle/kernel/init/init_slab_setup.cpp9
-rw-r--r--src/core/hle/kernel/initial_process.h4
-rw-r--r--src/core/hle/kernel/k_capabilities.h4
-rw-r--r--src/core/hle/kernel/k_condition_variable.cpp22
-rw-r--r--src/core/hle/kernel/k_condition_variable.h9
-rw-r--r--src/core/hle/kernel/k_interrupt_manager.cpp2
-rw-r--r--src/core/hle/kernel/k_memory_block.h52
-rw-r--r--src/core/hle/kernel/k_memory_block_manager.cpp70
-rw-r--r--src/core/hle/kernel/k_memory_block_manager.h6
-rw-r--r--src/core/hle/kernel/k_memory_layout.h6
-rw-r--r--src/core/hle/kernel/k_memory_manager.cpp134
-rw-r--r--src/core/hle/kernel/k_memory_manager.h12
-rw-r--r--src/core/hle/kernel/k_memory_region_type.h105
-rw-r--r--src/core/hle/kernel/k_page_group.h11
-rw-r--r--src/core/hle/kernel/k_page_table.cpp350
-rw-r--r--src/core/hle/kernel/k_page_table.h40
-rw-r--r--src/core/hle/kernel/k_process.cpp1442
-rw-r--r--src/core/hle/kernel/k_process.h724
-rw-r--r--src/core/hle/kernel/k_scheduler.cpp4
-rw-r--r--src/core/hle/kernel/k_system_resource.cpp87
-rw-r--r--src/core/hle/kernel/k_thread.cpp28
-rw-r--r--src/core/hle/kernel/k_thread.h1
-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.cpp92
-rw-r--r--src/core/hle/kernel/kernel.h3
-rw-r--r--src/core/hle/kernel/svc.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_info.cpp28
-rw-r--r--src/core/hle/kernel/svc/svc_lock.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_memory.cpp10
-rw-r--r--src/core/hle/kernel/svc/svc_physical_memory.cpp4
-rw-r--r--src/core/hle/kernel/svc/svc_synchronization.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_thread.cpp7
-rw-r--r--src/core/hle/kernel/svc/svc_transfer_memory.cpp54
-rw-r--r--src/core/hle/kernel/svc_generator.py2
-rw-r--r--src/core/hle/kernel/svc_types.h47
-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.cpp153
-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_cabinet.cpp4
-rw-r--r--src/core/hle/service/am/applets/applet_error.cpp5
-rw-r--r--src/core/hle/service/caps/caps.cpp22
-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.cpp469
-rw-r--r--src/core/hle/service/caps/caps_manager.h90
-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.cpp76
-rw-r--r--src/core/hle/service/caps/caps_ss.h12
-rw-r--r--src/core/hle/service/caps/caps_su.cpp74
-rw-r--r--src/core/hle/service/caps/caps_su.h12
-rw-r--r--src/core/hle/service/caps/caps_types.h186
-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/hid/controllers/palma.cpp4
-rw-r--r--src/core/hle/service/hid/hid.cpp4
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.cpp5
-rw-r--r--src/core/hle/service/hle_ipc.cpp64
-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/kernel_helpers.cpp6
-rw-r--r--src/core/hle/service/mii/types/core_data.cpp1
-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/nvnflinger/buffer_queue_core.cpp6
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.cpp5
-rw-r--r--src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp2
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp15
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h3
-rw-r--r--src/core/hle/service/pctl/pctl_module.cpp15
-rw-r--r--src/core/hle/service/pm/pm.cpp2
-rw-r--r--src/core/hle/service/prepo/prepo.cpp40
-rw-r--r--src/core/hle/service/ptm/ts.cpp40
-rw-r--r--src/core/hle/service/ptm/ts.h6
-rw-r--r--src/core/hle/service/set/set_sys.cpp49
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp2
-rw-r--r--src/core/memory/cheat_engine.cpp23
-rw-r--r--src/core/reporter.cpp2
-rw-r--r--src/input_common/drivers/udp_client.cpp1
-rw-r--r--src/input_common/helpers/joycon_driver.cpp16
-rw-r--r--src/input_common/helpers/joycon_driver.h5
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl.cpp1
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp12
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp23
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h36
-rw-r--r--src/shader_recompiler/profile.h4
-rw-r--r--src/tests/common/unique_function.cpp6
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h59
-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/fence_manager.h5
-rw-r--r--src/video_core/host1x/codecs/codec.cpp10
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt2
-rw-r--r--src/video_core/host_shaders/convert_abgr8_to_d32f.frag15
-rw-r--r--src/video_core/host_shaders/convert_d24s8_to_abgr8.frag8
-rw-r--r--src/video_core/host_shaders/convert_d32f_to_abgr8.frag14
-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.cpp22
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h8
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp5
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp14
-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_pipeline_cache.cpp6
-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.cpp28
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h4
-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_state_tracker.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp69
-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.cpp63
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h18
-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.cpp2
-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.cpp87
-rw-r--r--src/yuzu/applets/qt_controller.h10
-rw-r--r--src/yuzu/applets/qt_controller.ui54
-rw-r--r--src/yuzu/configuration/config.cpp7
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure_audio.cpp3
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp7
-rw-r--r--src/yuzu/configuration/configure_input.cpp63
-rw-r--r--src/yuzu/configuration/configure_input.h6
-rw-r--r--src/yuzu/configuration/configure_input_player.h5
-rw-r--r--src/yuzu/configuration/configure_ui.cpp5
-rw-r--r--src/yuzu/configuration/configure_ui.ui7
-rw-r--r--src/yuzu/configuration/configure_vibration.cpp10
-rw-r--r--src/yuzu/configuration/shared_translation.cpp11
-rw-r--r--src/yuzu/debugger/wait_tree.cpp2
-rw-r--r--src/yuzu/game_list.cpp43
-rw-r--r--src/yuzu/game_list.h17
-rw-r--r--src/yuzu/game_list_p.h26
-rw-r--r--src/yuzu/game_list_worker.cpp108
-rw-r--r--src/yuzu/game_list_worker.h44
-rw-r--r--src/yuzu/main.cpp293
-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
274 files changed, 19614 insertions, 2501 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 4b0193565..7bba01d62 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 \
@@ -32,6 +32,19 @@ ccache -s
32 32
33ctest -VV -C Release 33ctest -VV -C Release
34 34
35# Separate debug symbols from specified executables
36for EXE in yuzu; do
37 EXE_PATH="bin/$EXE"
38 # Copy debug symbols out
39 objcopy --only-keep-debug $EXE_PATH $EXE_PATH.debug
40 # Add debug link and strip debug symbols
41 objcopy -g --add-gnu-debuglink=$EXE_PATH.debug $EXE_PATH $EXE_PATH.out
42 # Overwrite original with stripped copy
43 mv $EXE_PATH.out $EXE_PATH
44done
45# Strip debug symbols from all executables
46find bin/ -type f -not -regex '.*.debug' -exec strip -g {} ';'
47
35DESTDIR="$PWD/AppDir" ninja install 48DESTDIR="$PWD/AppDir" ninja install
36rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester 49rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester
37 50
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/.codespellrc b/.codespellrc
index 944194a25..3336d31fe 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -3,4 +3,4 @@
3 3
4[codespell] 4[codespell]
5skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES,./src/android/app/src/main/res 5skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES,./src/android/app/src/main/res
6ignore-words-list = aci,allright,ba,canonicalizations,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink 6ignore-words-list = aci,allright,ba,canonicalizations,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,vas,zink
diff --git a/.reuse/dep5 b/.reuse/dep5
index 31178fc4c..d682fbdba 100644
--- a/.reuse/dep5
+++ b/.reuse/dep5
@@ -147,3 +147,7 @@ License: GPL-3.0-or-later
147Files: src/android/gradle/wrapper/* 147Files: src/android/gradle/wrapper/*
148Copyright: 2023 yuzu Emulator Project 148Copyright: 2023 yuzu Emulator Project
149License: GPL-3.0-or-later 149License: GPL-3.0-or-later
150
151Files: externals/stb/*
152Copyright: Sean Barrett
153License: MIT
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ed757eb0a..9c35e0946 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)
@@ -326,11 +286,12 @@ find_package(Boost 1.79.0 REQUIRED context)
326find_package(enet 1.3 MODULE) 286find_package(enet 1.3 MODULE)
327find_package(fmt 9 REQUIRED) 287find_package(fmt 9 REQUIRED)
328find_package(inih 52 MODULE COMPONENTS INIReader) 288find_package(inih 52 MODULE COMPONENTS INIReader)
329find_package(LLVM 17 MODULE COMPONENTS Demangle) 289find_package(LLVM 17.0.2 MODULE COMPONENTS Demangle)
330find_package(lz4 REQUIRED) 290find_package(lz4 REQUIRED)
331find_package(nlohmann_json 3.8 REQUIRED) 291find_package(nlohmann_json 3.8 REQUIRED)
332find_package(Opus 1.3 MODULE) 292find_package(Opus 1.3 MODULE)
333find_package(RenderDoc MODULE) 293find_package(RenderDoc MODULE)
294find_package(stb MODULE)
334find_package(VulkanMemoryAllocator CONFIG) 295find_package(VulkanMemoryAllocator CONFIG)
335find_package(ZLIB 1.2 REQUIRED) 296find_package(ZLIB 1.2 REQUIRED)
336find_package(zstd 1.5 REQUIRED) 297find_package(zstd 1.5 REQUIRED)
@@ -397,6 +358,9 @@ function(set_yuzu_qt_components)
397 if (ENABLE_QT_TRANSLATION) 358 if (ENABLE_QT_TRANSLATION)
398 list(APPEND YUZU_QT_COMPONENTS2 LinguistTools) 359 list(APPEND YUZU_QT_COMPONENTS2 LinguistTools)
399 endif() 360 endif()
361 if (USE_DISCORD_PRESENCE)
362 list(APPEND YUZU_QT_COMPONENTS2 Network)
363 endif()
400 set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE) 364 set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE)
401endfunction(set_yuzu_qt_components) 365endfunction(set_yuzu_qt_components)
402 366
diff --git a/CMakeModules/Findstb.cmake b/CMakeModules/Findstb.cmake
new file mode 100644
index 000000000..bff998580
--- /dev/null
+++ b/CMakeModules/Findstb.cmake
@@ -0,0 +1,31 @@
1# SPDX-FileCopyrightText: 2023 Alexandre Bouvier <contact@amb.tf>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
4
5find_path(stb_image_INCLUDE_DIR stb_image.h PATH_SUFFIXES stb)
6find_path(stb_image_resize_INCLUDE_DIR stb_image_resize.h PATH_SUFFIXES stb)
7find_path(stb_image_write_INCLUDE_DIR stb_image_write.h PATH_SUFFIXES stb)
8
9include(FindPackageHandleStandardArgs)
10find_package_handle_standard_args(stb
11 REQUIRED_VARS
12 stb_image_INCLUDE_DIR
13 stb_image_resize_INCLUDE_DIR
14 stb_image_write_INCLUDE_DIR
15)
16
17if (stb_FOUND AND NOT TARGET stb::headers)
18 add_library(stb::headers INTERFACE IMPORTED)
19 set_property(TARGET stb::headers PROPERTY
20 INTERFACE_INCLUDE_DIRECTORIES
21 "${stb_image_INCLUDE_DIR}"
22 "${stb_image_resize_INCLUDE_DIR}"
23 "${stb_image_write_INCLUDE_DIR}"
24 )
25endif()
26
27mark_as_advanced(
28 stb_image_INCLUDE_DIR
29 stb_image_resize_INCLUDE_DIR
30 stb_image_write_INCLUDE_DIR
31)
diff --git a/dist/org.yuzu_emu.yuzu.desktop b/dist/org.yuzu_emu.yuzu.desktop
index 008422863..51e191a8e 100644
--- a/dist/org.yuzu_emu.yuzu.desktop
+++ b/dist/org.yuzu_emu.yuzu.desktop
@@ -13,3 +13,4 @@ Exec=yuzu %f
13Categories=Game;Emulator;Qt; 13Categories=Game;Emulator;Qt;
14MimeType=application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci; 14MimeType=application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci;
15Keywords=Nintendo;Switch; 15Keywords=Nintendo;Switch;
16StartupWMClass=yuzu
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 c2009f34b..61baabb03 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -171,6 +171,10 @@ endif()
171add_library(stb stb/stb_dxt.cpp) 171add_library(stb stb/stb_dxt.cpp)
172target_include_directories(stb PUBLIC ./stb) 172target_include_directories(stb PUBLIC ./stb)
173 173
174if (NOT TARGET stb::headers)
175 add_library(stb::headers ALIAS stb)
176endif()
177
174add_library(bc_decoder bc_decoder/bc_decoder.cpp) 178add_library(bc_decoder bc_decoder/bc_decoder.cpp)
175target_include_directories(bc_decoder PUBLIC ./bc_decoder) 179target_include_directories(bc_decoder PUBLIC ./bc_decoder)
176 180
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.h b/externals/stb/stb_image.h
new file mode 100644
index 000000000..5e807a0a6
--- /dev/null
+++ b/externals/stb/stb_image.h
@@ -0,0 +1,7987 @@
1/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb
2 no warranty implied; use at your own risk
3
4 Do this:
5 #define STB_IMAGE_IMPLEMENTATION
6 before you include this file in *one* C or C++ file to create the implementation.
7
8 // i.e. it should look like this:
9 #include ...
10 #include ...
11 #include ...
12 #define STB_IMAGE_IMPLEMENTATION
13 #include "stb_image.h"
14
15 You can #define STBI_ASSERT(x) before the #include to avoid using assert.h.
16 And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free
17
18
19 QUICK NOTES:
20 Primarily of interest to game developers and other people who can
21 avoid problematic images and only need the trivial interface
22
23 JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)
24 PNG 1/2/4/8/16-bit-per-channel
25
26 TGA (not sure what subset, if a subset)
27 BMP non-1bpp, non-RLE
28 PSD (composited view only, no extra channels, 8/16 bit-per-channel)
29
30 GIF (*comp always reports as 4-channel)
31 HDR (radiance rgbE format)
32 PIC (Softimage PIC)
33 PNM (PPM and PGM binary only)
34
35 Animated GIF still needs a proper API, but here's one way to do it:
36 http://gist.github.com/urraka/685d9a6340b26b830d49
37
38 - decode from memory or through FILE (define STBI_NO_STDIO to remove code)
39 - decode from arbitrary I/O callbacks
40 - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)
41
42 Full documentation under "DOCUMENTATION" below.
43
44
45LICENSE
46
47 See end of file for license information.
48
49RECENT REVISION HISTORY:
50
51 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
52 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
53 2.26 (2020-07-13) many minor fixes
54 2.25 (2020-02-02) fix warnings
55 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically
56 2.23 (2019-08-11) fix clang static analysis warning
57 2.22 (2019-03-04) gif fixes, fix warnings
58 2.21 (2019-02-25) fix typo in comment
59 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
60 2.19 (2018-02-11) fix warning
61 2.18 (2018-01-30) fix warnings
62 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings
63 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes
64 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC
65 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
66 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes
67 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
68 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64
69 RGB-format JPEG; remove white matting in PSD;
70 allocate large structures on the stack;
71 correct channel count for PNG & BMP
72 2.10 (2016-01-22) avoid warning introduced in 2.09
73 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED
74
75 See end of file for full revision history.
76
77
78 ============================ Contributors =========================
79
80 Image formats Extensions, features
81 Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info)
82 Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info)
83 Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG)
84 Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks)
85 Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG)
86 Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip)
87 Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD)
88 github:urraka (animated gif) Junggon Kim (PNM comments)
89 Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA)
90 socks-the-fox (16-bit PNG)
91 Jeremy Sawicki (handle all ImageNet JPGs)
92 Optimizations & bugfixes Mikhail Morozov (1-bit BMP)
93 Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query)
94 Arseny Kapoulkine Simon Breuss (16-bit PNM)
95 John-Mark Allen
96 Carmelo J Fdez-Aguera
97
98 Bug & warning fixes
99 Marc LeBlanc David Woo Guillaume George Martins Mozeiko
100 Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski
101 Phil Jordan Dave Moore Roy Eltham
102 Hayaki Saito Nathan Reed Won Chun
103 Luke Graham Johan Duparc Nick Verigakis the Horde3D community
104 Thomas Ruf Ronny Chevalier github:rlyeh
105 Janez Zemva John Bartholomew Michal Cichon github:romigrou
106 Jonathan Blow Ken Hamada Tero Hanninen github:svdijk
107 Eugene Golushkov Laurent Gomila Cort Stratton github:snagar
108 Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex
109 Cass Everitt Ryamond Barbiero github:grim210
110 Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw
111 Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus
112 Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo
113 Julian Raschke Gregory Mullen Christian Floisand github:darealshinji
114 Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007
115 Brad Weinberger Matvey Cherevko github:mosra
116 Luca Sas Alexander Veselov Zack Middleton [reserved]
117 Ryan C. Gordon [reserved] [reserved]
118 DO NOT ADD YOUR NAME HERE
119
120 Jacko Dirks
121
122 To add your name to the credits, pick a random blank space in the middle and fill it.
123 80% of merge conflicts on stb PRs are due to people adding their name at the end
124 of the credits.
125*/
126
127#ifndef STBI_INCLUDE_STB_IMAGE_H
128#define STBI_INCLUDE_STB_IMAGE_H
129
130// DOCUMENTATION
131//
132// Limitations:
133// - no 12-bit-per-channel JPEG
134// - no JPEGs with arithmetic coding
135// - GIF always returns *comp=4
136//
137// Basic usage (see HDR discussion below for HDR usage):
138// int x,y,n;
139// unsigned char *data = stbi_load(filename, &x, &y, &n, 0);
140// // ... process data if not NULL ...
141// // ... x = width, y = height, n = # 8-bit components per pixel ...
142// // ... replace '0' with '1'..'4' to force that many components per pixel
143// // ... but 'n' will always be the number that it would have been if you said 0
144// stbi_image_free(data);
145//
146// Standard parameters:
147// int *x -- outputs image width in pixels
148// int *y -- outputs image height in pixels
149// int *channels_in_file -- outputs # of image components in image file
150// int desired_channels -- if non-zero, # of image components requested in result
151//
152// The return value from an image loader is an 'unsigned char *' which points
153// to the pixel data, or NULL on an allocation failure or if the image is
154// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels,
155// with each pixel consisting of N interleaved 8-bit components; the first
156// pixel pointed to is top-left-most in the image. There is no padding between
157// image scanlines or between pixels, regardless of format. The number of
158// components N is 'desired_channels' if desired_channels is non-zero, or
159// *channels_in_file otherwise. If desired_channels is non-zero,
160// *channels_in_file has the number of components that _would_ have been
161// output otherwise. E.g. if you set desired_channels to 4, you will always
162// get RGBA output, but you can check *channels_in_file to see if it's trivially
163// opaque because e.g. there were only 3 channels in the source image.
164//
165// An output image with N components has the following components interleaved
166// in this order in each pixel:
167//
168// N=#comp components
169// 1 grey
170// 2 grey, alpha
171// 3 red, green, blue
172// 4 red, green, blue, alpha
173//
174// If image loading fails for any reason, the return value will be NULL,
175// and *x, *y, *channels_in_file will be unchanged. The function
176// stbi_failure_reason() can be queried for an extremely brief, end-user
177// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS
178// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly
179// more user-friendly ones.
180//
181// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.
182//
183// To query the width, height and component count of an image without having to
184// decode the full file, you can use the stbi_info family of functions:
185//
186// int x,y,n,ok;
187// ok = stbi_info(filename, &x, &y, &n);
188// // returns ok=1 and sets x, y, n if image is a supported format,
189// // 0 otherwise.
190//
191// Note that stb_image pervasively uses ints in its public API for sizes,
192// including sizes of memory buffers. This is now part of the API and thus
193// hard to change without causing breakage. As a result, the various image
194// loaders all have certain limits on image size; these differ somewhat
195// by format but generally boil down to either just under 2GB or just under
196// 1GB. When the decoded image would be larger than this, stb_image decoding
197// will fail.
198//
199// Additionally, stb_image will reject image files that have any of their
200// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS,
201// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit,
202// the only way to have an image with such dimensions load correctly
203// is for it to have a rather extreme aspect ratio. Either way, the
204// assumption here is that such larger images are likely to be malformed
205// or malicious. If you do need to load an image with individual dimensions
206// larger than that, and it still fits in the overall size limit, you can
207// #define STBI_MAX_DIMENSIONS on your own to be something larger.
208//
209// ===========================================================================
210//
211// UNICODE:
212//
213// If compiling for Windows and you wish to use Unicode filenames, compile
214// with
215// #define STBI_WINDOWS_UTF8
216// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert
217// Windows wchar_t filenames to utf8.
218//
219// ===========================================================================
220//
221// Philosophy
222//
223// stb libraries are designed with the following priorities:
224//
225// 1. easy to use
226// 2. easy to maintain
227// 3. good performance
228//
229// Sometimes I let "good performance" creep up in priority over "easy to maintain",
230// and for best performance I may provide less-easy-to-use APIs that give higher
231// performance, in addition to the easy-to-use ones. Nevertheless, it's important
232// to keep in mind that from the standpoint of you, a client of this library,
233// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all.
234//
235// Some secondary priorities arise directly from the first two, some of which
236// provide more explicit reasons why performance can't be emphasized.
237//
238// - Portable ("ease of use")
239// - Small source code footprint ("easy to maintain")
240// - No dependencies ("ease of use")
241//
242// ===========================================================================
243//
244// I/O callbacks
245//
246// I/O callbacks allow you to read from arbitrary sources, like packaged
247// files or some other source. Data read from callbacks are processed
248// through a small internal buffer (currently 128 bytes) to try to reduce
249// overhead.
250//
251// The three functions you must define are "read" (reads some bytes of data),
252// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end).
253//
254// ===========================================================================
255//
256// SIMD support
257//
258// The JPEG decoder will try to automatically use SIMD kernels on x86 when
259// supported by the compiler. For ARM Neon support, you must explicitly
260// request it.
261//
262// (The old do-it-yourself SIMD API is no longer supported in the current
263// code.)
264//
265// On x86, SSE2 will automatically be used when available based on a run-time
266// test; if not, the generic C versions are used as a fall-back. On ARM targets,
267// the typical path is to have separate builds for NEON and non-NEON devices
268// (at least this is true for iOS and Android). Therefore, the NEON support is
269// toggled by a build flag: define STBI_NEON to get NEON loops.
270//
271// If for some reason you do not want to use any of SIMD code, or if
272// you have issues compiling it, you can disable it entirely by
273// defining STBI_NO_SIMD.
274//
275// ===========================================================================
276//
277// HDR image support (disable by defining STBI_NO_HDR)
278//
279// stb_image supports loading HDR images in general, and currently the Radiance
280// .HDR file format specifically. You can still load any file through the existing
281// interface; if you attempt to load an HDR file, it will be automatically remapped
282// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1;
283// both of these constants can be reconfigured through this interface:
284//
285// stbi_hdr_to_ldr_gamma(2.2f);
286// stbi_hdr_to_ldr_scale(1.0f);
287//
288// (note, do not use _inverse_ constants; stbi_image will invert them
289// appropriately).
290//
291// Additionally, there is a new, parallel interface for loading files as
292// (linear) floats to preserve the full dynamic range:
293//
294// float *data = stbi_loadf(filename, &x, &y, &n, 0);
295//
296// If you load LDR images through this interface, those images will
297// be promoted to floating point values, run through the inverse of
298// constants corresponding to the above:
299//
300// stbi_ldr_to_hdr_scale(1.0f);
301// stbi_ldr_to_hdr_gamma(2.2f);
302//
303// Finally, given a filename (or an open file or memory block--see header
304// file for details) containing image data, you can query for the "most
305// appropriate" interface to use (that is, whether the image is HDR or
306// not), using:
307//
308// stbi_is_hdr(char *filename);
309//
310// ===========================================================================
311//
312// iPhone PNG support:
313//
314// We optionally support converting iPhone-formatted PNGs (which store
315// premultiplied BGRA) back to RGB, even though they're internally encoded
316// differently. To enable this conversion, call
317// stbi_convert_iphone_png_to_rgb(1).
318//
319// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per
320// pixel to remove any premultiplied alpha *only* if the image file explicitly
321// says there's premultiplied data (currently only happens in iPhone images,
322// and only if iPhone convert-to-rgb processing is on).
323//
324// ===========================================================================
325//
326// ADDITIONAL CONFIGURATION
327//
328// - You can suppress implementation of any of the decoders to reduce
329// your code footprint by #defining one or more of the following
330// symbols before creating the implementation.
331//
332// STBI_NO_JPEG
333// STBI_NO_PNG
334// STBI_NO_BMP
335// STBI_NO_PSD
336// STBI_NO_TGA
337// STBI_NO_GIF
338// STBI_NO_HDR
339// STBI_NO_PIC
340// STBI_NO_PNM (.ppm and .pgm)
341//
342// - You can request *only* certain decoders and suppress all other ones
343// (this will be more forward-compatible, as addition of new decoders
344// doesn't require you to disable them explicitly):
345//
346// STBI_ONLY_JPEG
347// STBI_ONLY_PNG
348// STBI_ONLY_BMP
349// STBI_ONLY_PSD
350// STBI_ONLY_TGA
351// STBI_ONLY_GIF
352// STBI_ONLY_HDR
353// STBI_ONLY_PIC
354// STBI_ONLY_PNM (.ppm and .pgm)
355//
356// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still
357// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB
358//
359// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater
360// than that size (in either width or height) without further processing.
361// This is to let programs in the wild set an upper bound to prevent
362// denial-of-service attacks on untrusted data, as one could generate a
363// valid image of gigantic dimensions and force stb_image to allocate a
364// huge block of memory and spend disproportionate time decoding it. By
365// default this is set to (1 << 24), which is 16777216, but that's still
366// very big.
367
368#ifndef STBI_NO_STDIO
369#include <stdio.h>
370#endif // STBI_NO_STDIO
371
372#define STBI_VERSION 1
373
374enum
375{
376 STBI_default = 0, // only used for desired_channels
377
378 STBI_grey = 1,
379 STBI_grey_alpha = 2,
380 STBI_rgb = 3,
381 STBI_rgb_alpha = 4
382};
383
384#include <stdlib.h>
385typedef unsigned char stbi_uc;
386typedef unsigned short stbi_us;
387
388#ifdef __cplusplus
389extern "C" {
390#endif
391
392#ifndef STBIDEF
393#ifdef STB_IMAGE_STATIC
394#define STBIDEF static
395#else
396#define STBIDEF extern
397#endif
398#endif
399
400//////////////////////////////////////////////////////////////////////////////
401//
402// PRIMARY API - works on images of any type
403//
404
405//
406// load image by filename, open file, or memory buffer
407//
408
409typedef struct
410{
411 int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read
412 void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative
413 int (*eof) (void *user); // returns nonzero if we are at end of file/data
414} stbi_io_callbacks;
415
416////////////////////////////////////
417//
418// 8-bits-per-channel interface
419//
420
421STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels);
422STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels);
423
424#ifndef STBI_NO_STDIO
425STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
426STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
427// for stbi_load_from_file, file pointer is left pointing immediately after image
428#endif
429
430#ifndef STBI_NO_GIF
431STBIDEF 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);
432#endif
433
434#ifdef STBI_WINDOWS_UTF8
435STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);
436#endif
437
438////////////////////////////////////
439//
440// 16-bits-per-channel interface
441//
442
443STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);
444STBIDEF 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);
445
446#ifndef STBI_NO_STDIO
447STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
448STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
449#endif
450
451////////////////////////////////////
452//
453// float-per-channel interface
454//
455#ifndef STBI_NO_LINEAR
456 STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);
457 STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels);
458
459 #ifndef STBI_NO_STDIO
460 STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
461 STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
462 #endif
463#endif
464
465#ifndef STBI_NO_HDR
466 STBIDEF void stbi_hdr_to_ldr_gamma(float gamma);
467 STBIDEF void stbi_hdr_to_ldr_scale(float scale);
468#endif // STBI_NO_HDR
469
470#ifndef STBI_NO_LINEAR
471 STBIDEF void stbi_ldr_to_hdr_gamma(float gamma);
472 STBIDEF void stbi_ldr_to_hdr_scale(float scale);
473#endif // STBI_NO_LINEAR
474
475// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR
476STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user);
477STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);
478#ifndef STBI_NO_STDIO
479STBIDEF int stbi_is_hdr (char const *filename);
480STBIDEF int stbi_is_hdr_from_file(FILE *f);
481#endif // STBI_NO_STDIO
482
483
484// get a VERY brief reason for failure
485// on most compilers (and ALL modern mainstream compilers) this is threadsafe
486STBIDEF const char *stbi_failure_reason (void);
487
488// free the loaded image -- this is just free()
489STBIDEF void stbi_image_free (void *retval_from_stbi_load);
490
491// get image dimensions & components without fully decoding
492STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);
493STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp);
494STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len);
495STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user);
496
497#ifndef STBI_NO_STDIO
498STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp);
499STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp);
500STBIDEF int stbi_is_16_bit (char const *filename);
501STBIDEF int stbi_is_16_bit_from_file(FILE *f);
502#endif
503
504
505
506// for image formats that explicitly notate that they have premultiplied alpha,
507// we just return the colors as stored in the file. set this flag to force
508// unpremultiplication. results are undefined if the unpremultiply overflow.
509STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);
510
511// indicate whether we should process iphone images back to canonical format,
512// or just pass them through "as-is"
513STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);
514
515// flip the image vertically, so the first pixel in the output array is the bottom left
516STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);
517
518// as above, but only applies to images loaded on the thread that calls the function
519// this function is only available if your compiler supports thread-local variables;
520// calling it will fail to link if your compiler doesn't
521STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply);
522STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert);
523STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip);
524
525// ZLIB client - used by PNG, available for other purposes
526
527STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);
528STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header);
529STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);
530STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
531
532STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);
533STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
534
535
536#ifdef __cplusplus
537}
538#endif
539
540//
541//
542//// end header file /////////////////////////////////////////////////////
543#endif // STBI_INCLUDE_STB_IMAGE_H
544
545#ifdef STB_IMAGE_IMPLEMENTATION
546
547#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \
548 || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \
549 || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \
550 || defined(STBI_ONLY_ZLIB)
551 #ifndef STBI_ONLY_JPEG
552 #define STBI_NO_JPEG
553 #endif
554 #ifndef STBI_ONLY_PNG
555 #define STBI_NO_PNG
556 #endif
557 #ifndef STBI_ONLY_BMP
558 #define STBI_NO_BMP
559 #endif
560 #ifndef STBI_ONLY_PSD
561 #define STBI_NO_PSD
562 #endif
563 #ifndef STBI_ONLY_TGA
564 #define STBI_NO_TGA
565 #endif
566 #ifndef STBI_ONLY_GIF
567 #define STBI_NO_GIF
568 #endif
569 #ifndef STBI_ONLY_HDR
570 #define STBI_NO_HDR
571 #endif
572 #ifndef STBI_ONLY_PIC
573 #define STBI_NO_PIC
574 #endif
575 #ifndef STBI_ONLY_PNM
576 #define STBI_NO_PNM
577 #endif
578#endif
579
580#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB)
581#define STBI_NO_ZLIB
582#endif
583
584
585#include <stdarg.h>
586#include <stddef.h> // ptrdiff_t on osx
587#include <stdlib.h>
588#include <string.h>
589#include <limits.h>
590
591#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
592#include <math.h> // ldexp, pow
593#endif
594
595#ifndef STBI_NO_STDIO
596#include <stdio.h>
597#endif
598
599#ifndef STBI_ASSERT
600#include <assert.h>
601#define STBI_ASSERT(x) assert(x)
602#endif
603
604#ifdef __cplusplus
605#define STBI_EXTERN extern "C"
606#else
607#define STBI_EXTERN extern
608#endif
609
610
611#ifndef _MSC_VER
612 #ifdef __cplusplus
613 #define stbi_inline inline
614 #else
615 #define stbi_inline
616 #endif
617#else
618 #define stbi_inline __forceinline
619#endif
620
621#ifndef STBI_NO_THREAD_LOCALS
622 #if defined(__cplusplus) && __cplusplus >= 201103L
623 #define STBI_THREAD_LOCAL thread_local
624 #elif defined(__GNUC__) && __GNUC__ < 5
625 #define STBI_THREAD_LOCAL __thread
626 #elif defined(_MSC_VER)
627 #define STBI_THREAD_LOCAL __declspec(thread)
628 #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
629 #define STBI_THREAD_LOCAL _Thread_local
630 #endif
631
632 #ifndef STBI_THREAD_LOCAL
633 #if defined(__GNUC__)
634 #define STBI_THREAD_LOCAL __thread
635 #endif
636 #endif
637#endif
638
639#if defined(_MSC_VER) || defined(__SYMBIAN32__)
640typedef unsigned short stbi__uint16;
641typedef signed short stbi__int16;
642typedef unsigned int stbi__uint32;
643typedef signed int stbi__int32;
644#else
645#include <stdint.h>
646typedef uint16_t stbi__uint16;
647typedef int16_t stbi__int16;
648typedef uint32_t stbi__uint32;
649typedef int32_t stbi__int32;
650#endif
651
652// should produce compiler error if size is wrong
653typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1];
654
655#ifdef _MSC_VER
656#define STBI_NOTUSED(v) (void)(v)
657#else
658#define STBI_NOTUSED(v) (void)sizeof(v)
659#endif
660
661#ifdef _MSC_VER
662#define STBI_HAS_LROTL
663#endif
664
665#ifdef STBI_HAS_LROTL
666 #define stbi_lrot(x,y) _lrotl(x,y)
667#else
668 #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31)))
669#endif
670
671#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED))
672// ok
673#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED)
674// ok
675#else
676#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)."
677#endif
678
679#ifndef STBI_MALLOC
680#define STBI_MALLOC(sz) malloc(sz)
681#define STBI_REALLOC(p,newsz) realloc(p,newsz)
682#define STBI_FREE(p) free(p)
683#endif
684
685#ifndef STBI_REALLOC_SIZED
686#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz)
687#endif
688
689// x86/x64 detection
690#if defined(__x86_64__) || defined(_M_X64)
691#define STBI__X64_TARGET
692#elif defined(__i386) || defined(_M_IX86)
693#define STBI__X86_TARGET
694#endif
695
696#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD)
697// gcc doesn't support sse2 intrinsics unless you compile with -msse2,
698// which in turn means it gets to use SSE2 everywhere. This is unfortunate,
699// but previous attempts to provide the SSE2 functions with runtime
700// detection caused numerous issues. The way architecture extensions are
701// exposed in GCC/Clang is, sadly, not really suited for one-file libs.
702// New behavior: if compiled with -msse2, we use SSE2 without any
703// detection; if not, we don't use it at all.
704#define STBI_NO_SIMD
705#endif
706
707#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD)
708// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET
709//
710// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the
711// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant.
712// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not
713// simultaneously enabling "-mstackrealign".
714//
715// See https://github.com/nothings/stb/issues/81 for more information.
716//
717// So default to no SSE2 on 32-bit MinGW. If you've read this far and added
718// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2.
719#define STBI_NO_SIMD
720#endif
721
722#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET))
723#define STBI_SSE2
724#include <emmintrin.h>
725
726#ifdef _MSC_VER
727
728#if _MSC_VER >= 1400 // not VC6
729#include <intrin.h> // __cpuid
730static int stbi__cpuid3(void)
731{
732 int info[4];
733 __cpuid(info,1);
734 return info[3];
735}
736#else
737static int stbi__cpuid3(void)
738{
739 int res;
740 __asm {
741 mov eax,1
742 cpuid
743 mov res,edx
744 }
745 return res;
746}
747#endif
748
749#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name
750
751#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)
752static int stbi__sse2_available(void)
753{
754 int info3 = stbi__cpuid3();
755 return ((info3 >> 26) & 1) != 0;
756}
757#endif
758
759#else // assume GCC-style if not VC++
760#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
761
762#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)
763static int stbi__sse2_available(void)
764{
765 // If we're even attempting to compile this on GCC/Clang, that means
766 // -msse2 is on, which means the compiler is allowed to use SSE2
767 // instructions at will, and so are we.
768 return 1;
769}
770#endif
771
772#endif
773#endif
774
775// ARM NEON
776#if defined(STBI_NO_SIMD) && defined(STBI_NEON)
777#undef STBI_NEON
778#endif
779
780#ifdef STBI_NEON
781#include <arm_neon.h>
782#ifdef _MSC_VER
783#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name
784#else
785#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
786#endif
787#endif
788
789#ifndef STBI_SIMD_ALIGN
790#define STBI_SIMD_ALIGN(type, name) type name
791#endif
792
793#ifndef STBI_MAX_DIMENSIONS
794#define STBI_MAX_DIMENSIONS (1 << 24)
795#endif
796
797///////////////////////////////////////////////
798//
799// stbi__context struct and start_xxx functions
800
801// stbi__context structure is our basic context used by all images, so it
802// contains all the IO context, plus some basic image information
803typedef struct
804{
805 stbi__uint32 img_x, img_y;
806 int img_n, img_out_n;
807
808 stbi_io_callbacks io;
809 void *io_user_data;
810
811 int read_from_callbacks;
812 int buflen;
813 stbi_uc buffer_start[128];
814 int callback_already_read;
815
816 stbi_uc *img_buffer, *img_buffer_end;
817 stbi_uc *img_buffer_original, *img_buffer_original_end;
818} stbi__context;
819
820
821static void stbi__refill_buffer(stbi__context *s);
822
823// initialize a memory-decode context
824static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len)
825{
826 s->io.read = NULL;
827 s->read_from_callbacks = 0;
828 s->callback_already_read = 0;
829 s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer;
830 s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len;
831}
832
833// initialize a callback-based context
834static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user)
835{
836 s->io = *c;
837 s->io_user_data = user;
838 s->buflen = sizeof(s->buffer_start);
839 s->read_from_callbacks = 1;
840 s->callback_already_read = 0;
841 s->img_buffer = s->img_buffer_original = s->buffer_start;
842 stbi__refill_buffer(s);
843 s->img_buffer_original_end = s->img_buffer_end;
844}
845
846#ifndef STBI_NO_STDIO
847
848static int stbi__stdio_read(void *user, char *data, int size)
849{
850 return (int) fread(data,1,size,(FILE*) user);
851}
852
853static void stbi__stdio_skip(void *user, int n)
854{
855 int ch;
856 fseek((FILE*) user, n, SEEK_CUR);
857 ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */
858 if (ch != EOF) {
859 ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */
860 }
861}
862
863static int stbi__stdio_eof(void *user)
864{
865 return feof((FILE*) user) || ferror((FILE *) user);
866}
867
868static stbi_io_callbacks stbi__stdio_callbacks =
869{
870 stbi__stdio_read,
871 stbi__stdio_skip,
872 stbi__stdio_eof,
873};
874
875static void stbi__start_file(stbi__context *s, FILE *f)
876{
877 stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f);
878}
879
880//static void stop_file(stbi__context *s) { }
881
882#endif // !STBI_NO_STDIO
883
884static void stbi__rewind(stbi__context *s)
885{
886 // conceptually rewind SHOULD rewind to the beginning of the stream,
887 // but we just rewind to the beginning of the initial buffer, because
888 // we only use it after doing 'test', which only ever looks at at most 92 bytes
889 s->img_buffer = s->img_buffer_original;
890 s->img_buffer_end = s->img_buffer_original_end;
891}
892
893enum
894{
895 STBI_ORDER_RGB,
896 STBI_ORDER_BGR
897};
898
899typedef struct
900{
901 int bits_per_channel;
902 int num_channels;
903 int channel_order;
904} stbi__result_info;
905
906#ifndef STBI_NO_JPEG
907static int stbi__jpeg_test(stbi__context *s);
908static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
909static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp);
910#endif
911
912#ifndef STBI_NO_PNG
913static int stbi__png_test(stbi__context *s);
914static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
915static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp);
916static int stbi__png_is16(stbi__context *s);
917#endif
918
919#ifndef STBI_NO_BMP
920static int stbi__bmp_test(stbi__context *s);
921static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
922static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp);
923#endif
924
925#ifndef STBI_NO_TGA
926static int stbi__tga_test(stbi__context *s);
927static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
928static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp);
929#endif
930
931#ifndef STBI_NO_PSD
932static int stbi__psd_test(stbi__context *s);
933static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc);
934static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp);
935static int stbi__psd_is16(stbi__context *s);
936#endif
937
938#ifndef STBI_NO_HDR
939static int stbi__hdr_test(stbi__context *s);
940static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
941static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp);
942#endif
943
944#ifndef STBI_NO_PIC
945static int stbi__pic_test(stbi__context *s);
946static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
947static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp);
948#endif
949
950#ifndef STBI_NO_GIF
951static int stbi__gif_test(stbi__context *s);
952static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
953static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp);
954static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp);
955#endif
956
957#ifndef STBI_NO_PNM
958static int stbi__pnm_test(stbi__context *s);
959static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
960static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);
961static int stbi__pnm_is16(stbi__context *s);
962#endif
963
964static
965#ifdef STBI_THREAD_LOCAL
966STBI_THREAD_LOCAL
967#endif
968const char *stbi__g_failure_reason;
969
970STBIDEF const char *stbi_failure_reason(void)
971{
972 return stbi__g_failure_reason;
973}
974
975#ifndef STBI_NO_FAILURE_STRINGS
976static int stbi__err(const char *str)
977{
978 stbi__g_failure_reason = str;
979 return 0;
980}
981#endif
982
983static void *stbi__malloc(size_t size)
984{
985 return STBI_MALLOC(size);
986}
987
988// stb_image uses ints pervasively, including for offset calculations.
989// therefore the largest decoded image size we can support with the
990// current code, even on 64-bit targets, is INT_MAX. this is not a
991// significant limitation for the intended use case.
992//
993// we do, however, need to make sure our size calculations don't
994// overflow. hence a few helper functions for size calculations that
995// multiply integers together, making sure that they're non-negative
996// and no overflow occurs.
997
998// return 1 if the sum is valid, 0 on overflow.
999// negative terms are considered invalid.
1000static int stbi__addsizes_valid(int a, int b)
1001{
1002 if (b < 0) return 0;
1003 // now 0 <= b <= INT_MAX, hence also
1004 // 0 <= INT_MAX - b <= INTMAX.
1005 // And "a + b <= INT_MAX" (which might overflow) is the
1006 // same as a <= INT_MAX - b (no overflow)
1007 return a <= INT_MAX - b;
1008}
1009
1010// returns 1 if the product is valid, 0 on overflow.
1011// negative factors are considered invalid.
1012static int stbi__mul2sizes_valid(int a, int b)
1013{
1014 if (a < 0 || b < 0) return 0;
1015 if (b == 0) return 1; // mul-by-0 is always safe
1016 // portable way to check for no overflows in a*b
1017 return a <= INT_MAX/b;
1018}
1019
1020#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)
1021// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow
1022static int stbi__mad2sizes_valid(int a, int b, int add)
1023{
1024 return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add);
1025}
1026#endif
1027
1028// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow
1029static int stbi__mad3sizes_valid(int a, int b, int c, int add)
1030{
1031 return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&
1032 stbi__addsizes_valid(a*b*c, add);
1033}
1034
1035// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow
1036#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM)
1037static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add)
1038{
1039 return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&
1040 stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add);
1041}
1042#endif
1043
1044#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)
1045// mallocs with size overflow checking
1046static void *stbi__malloc_mad2(int a, int b, int add)
1047{
1048 if (!stbi__mad2sizes_valid(a, b, add)) return NULL;
1049 return stbi__malloc(a*b + add);
1050}
1051#endif
1052
1053static void *stbi__malloc_mad3(int a, int b, int c, int add)
1054{
1055 if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL;
1056 return stbi__malloc(a*b*c + add);
1057}
1058
1059#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM)
1060static void *stbi__malloc_mad4(int a, int b, int c, int d, int add)
1061{
1062 if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL;
1063 return stbi__malloc(a*b*c*d + add);
1064}
1065#endif
1066
1067// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow.
1068static int stbi__addints_valid(int a, int b)
1069{
1070 if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow
1071 if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0.
1072 return a <= INT_MAX - b;
1073}
1074
1075// returns 1 if the product of two signed shorts is valid, 0 on overflow.
1076static int stbi__mul2shorts_valid(short a, short b)
1077{
1078 if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow
1079 if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid
1080 if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN
1081 return a >= SHRT_MIN / b;
1082}
1083
1084// stbi__err - error
1085// stbi__errpf - error returning pointer to float
1086// stbi__errpuc - error returning pointer to unsigned char
1087
1088#ifdef STBI_NO_FAILURE_STRINGS
1089 #define stbi__err(x,y) 0
1090#elif defined(STBI_FAILURE_USERMSG)
1091 #define stbi__err(x,y) stbi__err(y)
1092#else
1093 #define stbi__err(x,y) stbi__err(x)
1094#endif
1095
1096#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL))
1097#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL))
1098
1099STBIDEF void stbi_image_free(void *retval_from_stbi_load)
1100{
1101 STBI_FREE(retval_from_stbi_load);
1102}
1103
1104#ifndef STBI_NO_LINEAR
1105static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp);
1106#endif
1107
1108#ifndef STBI_NO_HDR
1109static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp);
1110#endif
1111
1112static int stbi__vertically_flip_on_load_global = 0;
1113
1114STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
1115{
1116 stbi__vertically_flip_on_load_global = flag_true_if_should_flip;
1117}
1118
1119#ifndef STBI_THREAD_LOCAL
1120#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global
1121#else
1122static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set;
1123
1124STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip)
1125{
1126 stbi__vertically_flip_on_load_local = flag_true_if_should_flip;
1127 stbi__vertically_flip_on_load_set = 1;
1128}
1129
1130#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \
1131 ? stbi__vertically_flip_on_load_local \
1132 : stbi__vertically_flip_on_load_global)
1133#endif // STBI_THREAD_LOCAL
1134
1135static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
1136{
1137 memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields
1138 ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed
1139 ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order
1140 ri->num_channels = 0;
1141
1142 // test the formats with a very explicit header first (at least a FOURCC
1143 // or distinctive magic number first)
1144 #ifndef STBI_NO_PNG
1145 if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri);
1146 #endif
1147 #ifndef STBI_NO_BMP
1148 if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri);
1149 #endif
1150 #ifndef STBI_NO_GIF
1151 if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri);
1152 #endif
1153 #ifndef STBI_NO_PSD
1154 if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc);
1155 #else
1156 STBI_NOTUSED(bpc);
1157 #endif
1158 #ifndef STBI_NO_PIC
1159 if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri);
1160 #endif
1161
1162 // then the formats that can end up attempting to load with just 1 or 2
1163 // bytes matching expectations; these are prone to false positives, so
1164 // try them later
1165 #ifndef STBI_NO_JPEG
1166 if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri);
1167 #endif
1168 #ifndef STBI_NO_PNM
1169 if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri);
1170 #endif
1171
1172 #ifndef STBI_NO_HDR
1173 if (stbi__hdr_test(s)) {
1174 float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri);
1175 return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp);
1176 }
1177 #endif
1178
1179 #ifndef STBI_NO_TGA
1180 // test tga last because it's a crappy test!
1181 if (stbi__tga_test(s))
1182 return stbi__tga_load(s,x,y,comp,req_comp, ri);
1183 #endif
1184
1185 return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt");
1186}
1187
1188static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels)
1189{
1190 int i;
1191 int img_len = w * h * channels;
1192 stbi_uc *reduced;
1193
1194 reduced = (stbi_uc *) stbi__malloc(img_len);
1195 if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory");
1196
1197 for (i = 0; i < img_len; ++i)
1198 reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling
1199
1200 STBI_FREE(orig);
1201 return reduced;
1202}
1203
1204static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels)
1205{
1206 int i;
1207 int img_len = w * h * channels;
1208 stbi__uint16 *enlarged;
1209
1210 enlarged = (stbi__uint16 *) stbi__malloc(img_len*2);
1211 if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
1212
1213 for (i = 0; i < img_len; ++i)
1214 enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff
1215
1216 STBI_FREE(orig);
1217 return enlarged;
1218}
1219
1220static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel)
1221{
1222 int row;
1223 size_t bytes_per_row = (size_t)w * bytes_per_pixel;
1224 stbi_uc temp[2048];
1225 stbi_uc *bytes = (stbi_uc *)image;
1226
1227 for (row = 0; row < (h>>1); row++) {
1228 stbi_uc *row0 = bytes + row*bytes_per_row;
1229 stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row;
1230 // swap row0 with row1
1231 size_t bytes_left = bytes_per_row;
1232 while (bytes_left) {
1233 size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp);
1234 memcpy(temp, row0, bytes_copy);
1235 memcpy(row0, row1, bytes_copy);
1236 memcpy(row1, temp, bytes_copy);
1237 row0 += bytes_copy;
1238 row1 += bytes_copy;
1239 bytes_left -= bytes_copy;
1240 }
1241 }
1242}
1243
1244#ifndef STBI_NO_GIF
1245static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel)
1246{
1247 int slice;
1248 int slice_size = w * h * bytes_per_pixel;
1249
1250 stbi_uc *bytes = (stbi_uc *)image;
1251 for (slice = 0; slice < z; ++slice) {
1252 stbi__vertical_flip(bytes, w, h, bytes_per_pixel);
1253 bytes += slice_size;
1254 }
1255}
1256#endif
1257
1258static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
1259{
1260 stbi__result_info ri;
1261 void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8);
1262
1263 if (result == NULL)
1264 return NULL;
1265
1266 // it is the responsibility of the loaders to make sure we get either 8 or 16 bit.
1267 STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);
1268
1269 if (ri.bits_per_channel != 8) {
1270 result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
1271 ri.bits_per_channel = 8;
1272 }
1273
1274 // @TODO: move stbi__convert_format to here
1275
1276 if (stbi__vertically_flip_on_load) {
1277 int channels = req_comp ? req_comp : *comp;
1278 stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc));
1279 }
1280
1281 return (unsigned char *) result;
1282}
1283
1284static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
1285{
1286 stbi__result_info ri;
1287 void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16);
1288
1289 if (result == NULL)
1290 return NULL;
1291
1292 // it is the responsibility of the loaders to make sure we get either 8 or 16 bit.
1293 STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);
1294
1295 if (ri.bits_per_channel != 16) {
1296 result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
1297 ri.bits_per_channel = 16;
1298 }
1299
1300 // @TODO: move stbi__convert_format16 to here
1301 // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision
1302
1303 if (stbi__vertically_flip_on_load) {
1304 int channels = req_comp ? req_comp : *comp;
1305 stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16));
1306 }
1307
1308 return (stbi__uint16 *) result;
1309}
1310
1311#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR)
1312static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)
1313{
1314 if (stbi__vertically_flip_on_load && result != NULL) {
1315 int channels = req_comp ? req_comp : *comp;
1316 stbi__vertical_flip(result, *x, *y, channels * sizeof(float));
1317 }
1318}
1319#endif
1320
1321#ifndef STBI_NO_STDIO
1322
1323#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
1324STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);
1325STBI_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);
1326#endif
1327
1328#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
1329STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
1330{
1331 return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
1332}
1333#endif
1334
1335static FILE *stbi__fopen(char const *filename, char const *mode)
1336{
1337 FILE *f;
1338#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
1339 wchar_t wMode[64];
1340 wchar_t wFilename[1024];
1341 if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename)))
1342 return 0;
1343
1344 if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode)))
1345 return 0;
1346
1347#if defined(_MSC_VER) && _MSC_VER >= 1400
1348 if (0 != _wfopen_s(&f, wFilename, wMode))
1349 f = 0;
1350#else
1351 f = _wfopen(wFilename, wMode);
1352#endif
1353
1354#elif defined(_MSC_VER) && _MSC_VER >= 1400
1355 if (0 != fopen_s(&f, filename, mode))
1356 f=0;
1357#else
1358 f = fopen(filename, mode);
1359#endif
1360 return f;
1361}
1362
1363
1364STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)
1365{
1366 FILE *f = stbi__fopen(filename, "rb");
1367 unsigned char *result;
1368 if (!f) return stbi__errpuc("can't fopen", "Unable to open file");
1369 result = stbi_load_from_file(f,x,y,comp,req_comp);
1370 fclose(f);
1371 return result;
1372}
1373
1374STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
1375{
1376 unsigned char *result;
1377 stbi__context s;
1378 stbi__start_file(&s,f);
1379 result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
1380 if (result) {
1381 // need to 'unget' all the characters in the IO buffer
1382 fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
1383 }
1384 return result;
1385}
1386
1387STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp)
1388{
1389 stbi__uint16 *result;
1390 stbi__context s;
1391 stbi__start_file(&s,f);
1392 result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp);
1393 if (result) {
1394 // need to 'unget' all the characters in the IO buffer
1395 fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
1396 }
1397 return result;
1398}
1399
1400STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp)
1401{
1402 FILE *f = stbi__fopen(filename, "rb");
1403 stbi__uint16 *result;
1404 if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file");
1405 result = stbi_load_from_file_16(f,x,y,comp,req_comp);
1406 fclose(f);
1407 return result;
1408}
1409
1410
1411#endif //!STBI_NO_STDIO
1412
1413STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels)
1414{
1415 stbi__context s;
1416 stbi__start_mem(&s,buffer,len);
1417 return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);
1418}
1419
1420STBIDEF 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)
1421{
1422 stbi__context s;
1423 stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user);
1424 return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);
1425}
1426
1427STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
1428{
1429 stbi__context s;
1430 stbi__start_mem(&s,buffer,len);
1431 return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
1432}
1433
1434STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
1435{
1436 stbi__context s;
1437 stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
1438 return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
1439}
1440
1441#ifndef STBI_NO_GIF
1442STBIDEF 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)
1443{
1444 unsigned char *result;
1445 stbi__context s;
1446 stbi__start_mem(&s,buffer,len);
1447
1448 result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp);
1449 if (stbi__vertically_flip_on_load) {
1450 stbi__vertical_flip_slices( result, *x, *y, *z, *comp );
1451 }
1452
1453 return result;
1454}
1455#endif
1456
1457#ifndef STBI_NO_LINEAR
1458static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)
1459{
1460 unsigned char *data;
1461 #ifndef STBI_NO_HDR
1462 if (stbi__hdr_test(s)) {
1463 stbi__result_info ri;
1464 float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri);
1465 if (hdr_data)
1466 stbi__float_postprocess(hdr_data,x,y,comp,req_comp);
1467 return hdr_data;
1468 }
1469 #endif
1470 data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp);
1471 if (data)
1472 return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp);
1473 return stbi__errpf("unknown image type", "Image not of any known type, or corrupt");
1474}
1475
1476STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
1477{
1478 stbi__context s;
1479 stbi__start_mem(&s,buffer,len);
1480 return stbi__loadf_main(&s,x,y,comp,req_comp);
1481}
1482
1483STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
1484{
1485 stbi__context s;
1486 stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
1487 return stbi__loadf_main(&s,x,y,comp,req_comp);
1488}
1489
1490#ifndef STBI_NO_STDIO
1491STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp)
1492{
1493 float *result;
1494 FILE *f = stbi__fopen(filename, "rb");
1495 if (!f) return stbi__errpf("can't fopen", "Unable to open file");
1496 result = stbi_loadf_from_file(f,x,y,comp,req_comp);
1497 fclose(f);
1498 return result;
1499}
1500
1501STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
1502{
1503 stbi__context s;
1504 stbi__start_file(&s,f);
1505 return stbi__loadf_main(&s,x,y,comp,req_comp);
1506}
1507#endif // !STBI_NO_STDIO
1508
1509#endif // !STBI_NO_LINEAR
1510
1511// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is
1512// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always
1513// reports false!
1514
1515STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len)
1516{
1517 #ifndef STBI_NO_HDR
1518 stbi__context s;
1519 stbi__start_mem(&s,buffer,len);
1520 return stbi__hdr_test(&s);
1521 #else
1522 STBI_NOTUSED(buffer);
1523 STBI_NOTUSED(len);
1524 return 0;
1525 #endif
1526}
1527
1528#ifndef STBI_NO_STDIO
1529STBIDEF int stbi_is_hdr (char const *filename)
1530{
1531 FILE *f = stbi__fopen(filename, "rb");
1532 int result=0;
1533 if (f) {
1534 result = stbi_is_hdr_from_file(f);
1535 fclose(f);
1536 }
1537 return result;
1538}
1539
1540STBIDEF int stbi_is_hdr_from_file(FILE *f)
1541{
1542 #ifndef STBI_NO_HDR
1543 long pos = ftell(f);
1544 int res;
1545 stbi__context s;
1546 stbi__start_file(&s,f);
1547 res = stbi__hdr_test(&s);
1548 fseek(f, pos, SEEK_SET);
1549 return res;
1550 #else
1551 STBI_NOTUSED(f);
1552 return 0;
1553 #endif
1554}
1555#endif // !STBI_NO_STDIO
1556
1557STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user)
1558{
1559 #ifndef STBI_NO_HDR
1560 stbi__context s;
1561 stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
1562 return stbi__hdr_test(&s);
1563 #else
1564 STBI_NOTUSED(clbk);
1565 STBI_NOTUSED(user);
1566 return 0;
1567 #endif
1568}
1569
1570#ifndef STBI_NO_LINEAR
1571static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f;
1572
1573STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; }
1574STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; }
1575#endif
1576
1577static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f;
1578
1579STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; }
1580STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; }
1581
1582
1583//////////////////////////////////////////////////////////////////////////////
1584//
1585// Common code used by all image loaders
1586//
1587
1588enum
1589{
1590 STBI__SCAN_load=0,
1591 STBI__SCAN_type,
1592 STBI__SCAN_header
1593};
1594
1595static void stbi__refill_buffer(stbi__context *s)
1596{
1597 int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen);
1598 s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original);
1599 if (n == 0) {
1600 // at end of file, treat same as if from memory, but need to handle case
1601 // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file
1602 s->read_from_callbacks = 0;
1603 s->img_buffer = s->buffer_start;
1604 s->img_buffer_end = s->buffer_start+1;
1605 *s->img_buffer = 0;
1606 } else {
1607 s->img_buffer = s->buffer_start;
1608 s->img_buffer_end = s->buffer_start + n;
1609 }
1610}
1611
1612stbi_inline static stbi_uc stbi__get8(stbi__context *s)
1613{
1614 if (s->img_buffer < s->img_buffer_end)
1615 return *s->img_buffer++;
1616 if (s->read_from_callbacks) {
1617 stbi__refill_buffer(s);
1618 return *s->img_buffer++;
1619 }
1620 return 0;
1621}
1622
1623#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
1624// nothing
1625#else
1626stbi_inline static int stbi__at_eof(stbi__context *s)
1627{
1628 if (s->io.read) {
1629 if (!(s->io.eof)(s->io_user_data)) return 0;
1630 // if feof() is true, check if buffer = end
1631 // special case: we've only got the special 0 character at the end
1632 if (s->read_from_callbacks == 0) return 1;
1633 }
1634
1635 return s->img_buffer >= s->img_buffer_end;
1636}
1637#endif
1638
1639#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)
1640// nothing
1641#else
1642static void stbi__skip(stbi__context *s, int n)
1643{
1644 if (n == 0) return; // already there!
1645 if (n < 0) {
1646 s->img_buffer = s->img_buffer_end;
1647 return;
1648 }
1649 if (s->io.read) {
1650 int blen = (int) (s->img_buffer_end - s->img_buffer);
1651 if (blen < n) {
1652 s->img_buffer = s->img_buffer_end;
1653 (s->io.skip)(s->io_user_data, n - blen);
1654 return;
1655 }
1656 }
1657 s->img_buffer += n;
1658}
1659#endif
1660
1661#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM)
1662// nothing
1663#else
1664static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
1665{
1666 if (s->io.read) {
1667 int blen = (int) (s->img_buffer_end - s->img_buffer);
1668 if (blen < n) {
1669 int res, count;
1670
1671 memcpy(buffer, s->img_buffer, blen);
1672
1673 count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen);
1674 res = (count == (n-blen));
1675 s->img_buffer = s->img_buffer_end;
1676 return res;
1677 }
1678 }
1679
1680 if (s->img_buffer+n <= s->img_buffer_end) {
1681 memcpy(buffer, s->img_buffer, n);
1682 s->img_buffer += n;
1683 return 1;
1684 } else
1685 return 0;
1686}
1687#endif
1688
1689#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)
1690// nothing
1691#else
1692static int stbi__get16be(stbi__context *s)
1693{
1694 int z = stbi__get8(s);
1695 return (z << 8) + stbi__get8(s);
1696}
1697#endif
1698
1699#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)
1700// nothing
1701#else
1702static stbi__uint32 stbi__get32be(stbi__context *s)
1703{
1704 stbi__uint32 z = stbi__get16be(s);
1705 return (z << 16) + stbi__get16be(s);
1706}
1707#endif
1708
1709#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF)
1710// nothing
1711#else
1712static int stbi__get16le(stbi__context *s)
1713{
1714 int z = stbi__get8(s);
1715 return z + (stbi__get8(s) << 8);
1716}
1717#endif
1718
1719#ifndef STBI_NO_BMP
1720static stbi__uint32 stbi__get32le(stbi__context *s)
1721{
1722 stbi__uint32 z = stbi__get16le(s);
1723 z += (stbi__uint32)stbi__get16le(s) << 16;
1724 return z;
1725}
1726#endif
1727
1728#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings
1729
1730#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)
1731// nothing
1732#else
1733//////////////////////////////////////////////////////////////////////////////
1734//
1735// generic converter from built-in img_n to req_comp
1736// individual types do this automatically as much as possible (e.g. jpeg
1737// does all cases internally since it needs to colorspace convert anyway,
1738// and it never has alpha, so very few cases ). png can automatically
1739// interleave an alpha=255 channel, but falls back to this for other cases
1740//
1741// assume data buffer is malloced, so malloc a new one and free that one
1742// only failure mode is malloc failing
1743
1744static stbi_uc stbi__compute_y(int r, int g, int b)
1745{
1746 return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8);
1747}
1748#endif
1749
1750#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)
1751// nothing
1752#else
1753static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y)
1754{
1755 int i,j;
1756 unsigned char *good;
1757
1758 if (req_comp == img_n) return data;
1759 STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
1760
1761 good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0);
1762 if (good == NULL) {
1763 STBI_FREE(data);
1764 return stbi__errpuc("outofmem", "Out of memory");
1765 }
1766
1767 for (j=0; j < (int) y; ++j) {
1768 unsigned char *src = data + j * x * img_n ;
1769 unsigned char *dest = good + j * x * req_comp;
1770
1771 #define STBI__COMBO(a,b) ((a)*8+(b))
1772 #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
1773 // convert source image with img_n components to one with req_comp components;
1774 // avoid switch per pixel, so use switch per scanline and massive macros
1775 switch (STBI__COMBO(img_n, req_comp)) {
1776 STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break;
1777 STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
1778 STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break;
1779 STBI__CASE(2,1) { dest[0]=src[0]; } break;
1780 STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
1781 STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break;
1782 STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break;
1783 STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break;
1784 STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break;
1785 STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break;
1786 STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break;
1787 STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break;
1788 default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion");
1789 }
1790 #undef STBI__CASE
1791 }
1792
1793 STBI_FREE(data);
1794 return good;
1795}
1796#endif
1797
1798#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
1799// nothing
1800#else
1801static stbi__uint16 stbi__compute_y_16(int r, int g, int b)
1802{
1803 return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8);
1804}
1805#endif
1806
1807#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
1808// nothing
1809#else
1810static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y)
1811{
1812 int i,j;
1813 stbi__uint16 *good;
1814
1815 if (req_comp == img_n) return data;
1816 STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
1817
1818 good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2);
1819 if (good == NULL) {
1820 STBI_FREE(data);
1821 return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
1822 }
1823
1824 for (j=0; j < (int) y; ++j) {
1825 stbi__uint16 *src = data + j * x * img_n ;
1826 stbi__uint16 *dest = good + j * x * req_comp;
1827
1828 #define STBI__COMBO(a,b) ((a)*8+(b))
1829 #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
1830 // convert source image with img_n components to one with req_comp components;
1831 // avoid switch per pixel, so use switch per scanline and massive macros
1832 switch (STBI__COMBO(img_n, req_comp)) {
1833 STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break;
1834 STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
1835 STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break;
1836 STBI__CASE(2,1) { dest[0]=src[0]; } break;
1837 STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
1838 STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break;
1839 STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break;
1840 STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break;
1841 STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break;
1842 STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break;
1843 STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break;
1844 STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break;
1845 default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion");
1846 }
1847 #undef STBI__CASE
1848 }
1849
1850 STBI_FREE(data);
1851 return good;
1852}
1853#endif
1854
1855#ifndef STBI_NO_LINEAR
1856static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp)
1857{
1858 int i,k,n;
1859 float *output;
1860 if (!data) return NULL;
1861 output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0);
1862 if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); }
1863 // compute number of non-alpha components
1864 if (comp & 1) n = comp; else n = comp-1;
1865 for (i=0; i < x*y; ++i) {
1866 for (k=0; k < n; ++k) {
1867 output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale);
1868 }
1869 }
1870 if (n < comp) {
1871 for (i=0; i < x*y; ++i) {
1872 output[i*comp + n] = data[i*comp + n]/255.0f;
1873 }
1874 }
1875 STBI_FREE(data);
1876 return output;
1877}
1878#endif
1879
1880#ifndef STBI_NO_HDR
1881#define stbi__float2int(x) ((int) (x))
1882static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp)
1883{
1884 int i,k,n;
1885 stbi_uc *output;
1886 if (!data) return NULL;
1887 output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0);
1888 if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); }
1889 // compute number of non-alpha components
1890 if (comp & 1) n = comp; else n = comp-1;
1891 for (i=0; i < x*y; ++i) {
1892 for (k=0; k < n; ++k) {
1893 float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f;
1894 if (z < 0) z = 0;
1895 if (z > 255) z = 255;
1896 output[i*comp + k] = (stbi_uc) stbi__float2int(z);
1897 }
1898 if (k < comp) {
1899 float z = data[i*comp+k] * 255 + 0.5f;
1900 if (z < 0) z = 0;
1901 if (z > 255) z = 255;
1902 output[i*comp + k] = (stbi_uc) stbi__float2int(z);
1903 }
1904 }
1905 STBI_FREE(data);
1906 return output;
1907}
1908#endif
1909
1910//////////////////////////////////////////////////////////////////////////////
1911//
1912// "baseline" JPEG/JFIF decoder
1913//
1914// simple implementation
1915// - doesn't support delayed output of y-dimension
1916// - simple interface (only one output format: 8-bit interleaved RGB)
1917// - doesn't try to recover corrupt jpegs
1918// - doesn't allow partial loading, loading multiple at once
1919// - still fast on x86 (copying globals into locals doesn't help x86)
1920// - allocates lots of intermediate memory (full size of all components)
1921// - non-interleaved case requires this anyway
1922// - allows good upsampling (see next)
1923// high-quality
1924// - upsampled channels are bilinearly interpolated, even across blocks
1925// - quality integer IDCT derived from IJG's 'slow'
1926// performance
1927// - fast huffman; reasonable integer IDCT
1928// - some SIMD kernels for common paths on targets with SSE2/NEON
1929// - uses a lot of intermediate memory, could cache poorly
1930
1931#ifndef STBI_NO_JPEG
1932
1933// huffman decoding acceleration
1934#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache
1935
1936typedef struct
1937{
1938 stbi_uc fast[1 << FAST_BITS];
1939 // weirdly, repacking this into AoS is a 10% speed loss, instead of a win
1940 stbi__uint16 code[256];
1941 stbi_uc values[256];
1942 stbi_uc size[257];
1943 unsigned int maxcode[18];
1944 int delta[17]; // old 'firstsymbol' - old 'firstcode'
1945} stbi__huffman;
1946
1947typedef struct
1948{
1949 stbi__context *s;
1950 stbi__huffman huff_dc[4];
1951 stbi__huffman huff_ac[4];
1952 stbi__uint16 dequant[4][64];
1953 stbi__int16 fast_ac[4][1 << FAST_BITS];
1954
1955// sizes for components, interleaved MCUs
1956 int img_h_max, img_v_max;
1957 int img_mcu_x, img_mcu_y;
1958 int img_mcu_w, img_mcu_h;
1959
1960// definition of jpeg image component
1961 struct
1962 {
1963 int id;
1964 int h,v;
1965 int tq;
1966 int hd,ha;
1967 int dc_pred;
1968
1969 int x,y,w2,h2;
1970 stbi_uc *data;
1971 void *raw_data, *raw_coeff;
1972 stbi_uc *linebuf;
1973 short *coeff; // progressive only
1974 int coeff_w, coeff_h; // number of 8x8 coefficient blocks
1975 } img_comp[4];
1976
1977 stbi__uint32 code_buffer; // jpeg entropy-coded buffer
1978 int code_bits; // number of valid bits
1979 unsigned char marker; // marker seen while filling entropy buffer
1980 int nomore; // flag if we saw a marker so must stop
1981
1982 int progressive;
1983 int spec_start;
1984 int spec_end;
1985 int succ_high;
1986 int succ_low;
1987 int eob_run;
1988 int jfif;
1989 int app14_color_transform; // Adobe APP14 tag
1990 int rgb;
1991
1992 int scan_n, order[4];
1993 int restart_interval, todo;
1994
1995// kernels
1996 void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]);
1997 void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step);
1998 stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs);
1999} stbi__jpeg;
2000
2001static int stbi__build_huffman(stbi__huffman *h, int *count)
2002{
2003 int i,j,k=0;
2004 unsigned int code;
2005 // build size list for each symbol (from JPEG spec)
2006 for (i=0; i < 16; ++i) {
2007 for (j=0; j < count[i]; ++j) {
2008 h->size[k++] = (stbi_uc) (i+1);
2009 if(k >= 257) return stbi__err("bad size list","Corrupt JPEG");
2010 }
2011 }
2012 h->size[k] = 0;
2013
2014 // compute actual symbols (from jpeg spec)
2015 code = 0;
2016 k = 0;
2017 for(j=1; j <= 16; ++j) {
2018 // compute delta to add to code to compute symbol id
2019 h->delta[j] = k - code;
2020 if (h->size[k] == j) {
2021 while (h->size[k] == j)
2022 h->code[k++] = (stbi__uint16) (code++);
2023 if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG");
2024 }
2025 // compute largest code + 1 for this size, preshifted as needed later
2026 h->maxcode[j] = code << (16-j);
2027 code <<= 1;
2028 }
2029 h->maxcode[j] = 0xffffffff;
2030
2031 // build non-spec acceleration table; 255 is flag for not-accelerated
2032 memset(h->fast, 255, 1 << FAST_BITS);
2033 for (i=0; i < k; ++i) {
2034 int s = h->size[i];
2035 if (s <= FAST_BITS) {
2036 int c = h->code[i] << (FAST_BITS-s);
2037 int m = 1 << (FAST_BITS-s);
2038 for (j=0; j < m; ++j) {
2039 h->fast[c+j] = (stbi_uc) i;
2040 }
2041 }
2042 }
2043 return 1;
2044}
2045
2046// build a table that decodes both magnitude and value of small ACs in
2047// one go.
2048static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h)
2049{
2050 int i;
2051 for (i=0; i < (1 << FAST_BITS); ++i) {
2052 stbi_uc fast = h->fast[i];
2053 fast_ac[i] = 0;
2054 if (fast < 255) {
2055 int rs = h->values[fast];
2056 int run = (rs >> 4) & 15;
2057 int magbits = rs & 15;
2058 int len = h->size[fast];
2059
2060 if (magbits && len + magbits <= FAST_BITS) {
2061 // magnitude code followed by receive_extend code
2062 int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits);
2063 int m = 1 << (magbits - 1);
2064 if (k < m) k += (~0U << magbits) + 1;
2065 // if the result is small enough, we can fit it in fast_ac table
2066 if (k >= -128 && k <= 127)
2067 fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits));
2068 }
2069 }
2070 }
2071}
2072
2073static void stbi__grow_buffer_unsafe(stbi__jpeg *j)
2074{
2075 do {
2076 unsigned int b = j->nomore ? 0 : stbi__get8(j->s);
2077 if (b == 0xff) {
2078 int c = stbi__get8(j->s);
2079 while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes
2080 if (c != 0) {
2081 j->marker = (unsigned char) c;
2082 j->nomore = 1;
2083 return;
2084 }
2085 }
2086 j->code_buffer |= b << (24 - j->code_bits);
2087 j->code_bits += 8;
2088 } while (j->code_bits <= 24);
2089}
2090
2091// (1 << n) - 1
2092static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535};
2093
2094// decode a jpeg huffman value from the bitstream
2095stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h)
2096{
2097 unsigned int temp;
2098 int c,k;
2099
2100 if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
2101
2102 // look at the top FAST_BITS and determine what symbol ID it is,
2103 // if the code is <= FAST_BITS
2104 c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
2105 k = h->fast[c];
2106 if (k < 255) {
2107 int s = h->size[k];
2108 if (s > j->code_bits)
2109 return -1;
2110 j->code_buffer <<= s;
2111 j->code_bits -= s;
2112 return h->values[k];
2113 }
2114
2115 // naive test is to shift the code_buffer down so k bits are
2116 // valid, then test against maxcode. To speed this up, we've
2117 // preshifted maxcode left so that it has (16-k) 0s at the
2118 // end; in other words, regardless of the number of bits, it
2119 // wants to be compared against something shifted to have 16;
2120 // that way we don't need to shift inside the loop.
2121 temp = j->code_buffer >> 16;
2122 for (k=FAST_BITS+1 ; ; ++k)
2123 if (temp < h->maxcode[k])
2124 break;
2125 if (k == 17) {
2126 // error! code not found
2127 j->code_bits -= 16;
2128 return -1;
2129 }
2130
2131 if (k > j->code_bits)
2132 return -1;
2133
2134 // convert the huffman code to the symbol id
2135 c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];
2136 if(c < 0 || c >= 256) // symbol id out of bounds!
2137 return -1;
2138 STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]);
2139
2140 // convert the id to a symbol
2141 j->code_bits -= k;
2142 j->code_buffer <<= k;
2143 return h->values[c];
2144}
2145
2146// bias[n] = (-1<<n) + 1
2147static const int stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767};
2148
2149// combined JPEG 'receive' and JPEG 'extend', since baseline
2150// always extends everything it receives.
2151stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)
2152{
2153 unsigned int k;
2154 int sgn;
2155 if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
2156 if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing
2157
2158 sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative)
2159 k = stbi_lrot(j->code_buffer, n);
2160 j->code_buffer = k & ~stbi__bmask[n];
2161 k &= stbi__bmask[n];
2162 j->code_bits -= n;
2163 return k + (stbi__jbias[n] & (sgn - 1));
2164}
2165
2166// get some unsigned bits
2167stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n)
2168{
2169 unsigned int k;
2170 if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
2171 if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing
2172 k = stbi_lrot(j->code_buffer, n);
2173 j->code_buffer = k & ~stbi__bmask[n];
2174 k &= stbi__bmask[n];
2175 j->code_bits -= n;
2176 return k;
2177}
2178
2179stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j)
2180{
2181 unsigned int k;
2182 if (j->code_bits < 1) stbi__grow_buffer_unsafe(j);
2183 if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing
2184 k = j->code_buffer;
2185 j->code_buffer <<= 1;
2186 --j->code_bits;
2187 return k & 0x80000000;
2188}
2189
2190// given a value that's at position X in the zigzag stream,
2191// where does it appear in the 8x8 matrix coded as row-major?
2192static const stbi_uc stbi__jpeg_dezigzag[64+15] =
2193{
2194 0, 1, 8, 16, 9, 2, 3, 10,
2195 17, 24, 32, 25, 18, 11, 4, 5,
2196 12, 19, 26, 33, 40, 48, 41, 34,
2197 27, 20, 13, 6, 7, 14, 21, 28,
2198 35, 42, 49, 56, 57, 50, 43, 36,
2199 29, 22, 15, 23, 30, 37, 44, 51,
2200 58, 59, 52, 45, 38, 31, 39, 46,
2201 53, 60, 61, 54, 47, 55, 62, 63,
2202 // let corrupt input sample past end
2203 63, 63, 63, 63, 63, 63, 63, 63,
2204 63, 63, 63, 63, 63, 63, 63
2205};
2206
2207// decode one 64-entry block--
2208static 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)
2209{
2210 int diff,dc,k;
2211 int t;
2212
2213 if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
2214 t = stbi__jpeg_huff_decode(j, hdc);
2215 if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG");
2216
2217 // 0 all the ac values now so we can do it 32-bits at a time
2218 memset(data,0,64*sizeof(data[0]));
2219
2220 diff = t ? stbi__extend_receive(j, t) : 0;
2221 if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG");
2222 dc = j->img_comp[b].dc_pred + diff;
2223 j->img_comp[b].dc_pred = dc;
2224 if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
2225 data[0] = (short) (dc * dequant[0]);
2226
2227 // decode AC components, see JPEG spec
2228 k = 1;
2229 do {
2230 unsigned int zig;
2231 int c,r,s;
2232 if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
2233 c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
2234 r = fac[c];
2235 if (r) { // fast-AC path
2236 k += (r >> 4) & 15; // run
2237 s = r & 15; // combined length
2238 if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available");
2239 j->code_buffer <<= s;
2240 j->code_bits -= s;
2241 // decode into unzigzag'd location
2242 zig = stbi__jpeg_dezigzag[k++];
2243 data[zig] = (short) ((r >> 8) * dequant[zig]);
2244 } else {
2245 int rs = stbi__jpeg_huff_decode(j, hac);
2246 if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
2247 s = rs & 15;
2248 r = rs >> 4;
2249 if (s == 0) {
2250 if (rs != 0xf0) break; // end block
2251 k += 16;
2252 } else {
2253 k += r;
2254 // decode into unzigzag'd location
2255 zig = stbi__jpeg_dezigzag[k++];
2256 data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]);
2257 }
2258 }
2259 } while (k < 64);
2260 return 1;
2261}
2262
2263static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b)
2264{
2265 int diff,dc;
2266 int t;
2267 if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
2268
2269 if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
2270
2271 if (j->succ_high == 0) {
2272 // first scan for DC coefficient, must be first
2273 memset(data,0,64*sizeof(data[0])); // 0 all the ac values now
2274 t = stbi__jpeg_huff_decode(j, hdc);
2275 if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
2276 diff = t ? stbi__extend_receive(j, t) : 0;
2277
2278 if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG");
2279 dc = j->img_comp[b].dc_pred + diff;
2280 j->img_comp[b].dc_pred = dc;
2281 if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
2282 data[0] = (short) (dc * (1 << j->succ_low));
2283 } else {
2284 // refinement scan for DC coefficient
2285 if (stbi__jpeg_get_bit(j))
2286 data[0] += (short) (1 << j->succ_low);
2287 }
2288 return 1;
2289}
2290
2291// @OPTIMIZE: store non-zigzagged during the decode passes,
2292// and only de-zigzag when dequantizing
2293static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac)
2294{
2295 int k;
2296 if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
2297
2298 if (j->succ_high == 0) {
2299 int shift = j->succ_low;
2300
2301 if (j->eob_run) {
2302 --j->eob_run;
2303 return 1;
2304 }
2305
2306 k = j->spec_start;
2307 do {
2308 unsigned int zig;
2309 int c,r,s;
2310 if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
2311 c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
2312 r = fac[c];
2313 if (r) { // fast-AC path
2314 k += (r >> 4) & 15; // run
2315 s = r & 15; // combined length
2316 if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available");
2317 j->code_buffer <<= s;
2318 j->code_bits -= s;
2319 zig = stbi__jpeg_dezigzag[k++];
2320 data[zig] = (short) ((r >> 8) * (1 << shift));
2321 } else {
2322 int rs = stbi__jpeg_huff_decode(j, hac);
2323 if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
2324 s = rs & 15;
2325 r = rs >> 4;
2326 if (s == 0) {
2327 if (r < 15) {
2328 j->eob_run = (1 << r);
2329 if (r)
2330 j->eob_run += stbi__jpeg_get_bits(j, r);
2331 --j->eob_run;
2332 break;
2333 }
2334 k += 16;
2335 } else {
2336 k += r;
2337 zig = stbi__jpeg_dezigzag[k++];
2338 data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift));
2339 }
2340 }
2341 } while (k <= j->spec_end);
2342 } else {
2343 // refinement scan for these AC coefficients
2344
2345 short bit = (short) (1 << j->succ_low);
2346
2347 if (j->eob_run) {
2348 --j->eob_run;
2349 for (k = j->spec_start; k <= j->spec_end; ++k) {
2350 short *p = &data[stbi__jpeg_dezigzag[k]];
2351 if (*p != 0)
2352 if (stbi__jpeg_get_bit(j))
2353 if ((*p & bit)==0) {
2354 if (*p > 0)
2355 *p += bit;
2356 else
2357 *p -= bit;
2358 }
2359 }
2360 } else {
2361 k = j->spec_start;
2362 do {
2363 int r,s;
2364 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
2365 if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
2366 s = rs & 15;
2367 r = rs >> 4;
2368 if (s == 0) {
2369 if (r < 15) {
2370 j->eob_run = (1 << r) - 1;
2371 if (r)
2372 j->eob_run += stbi__jpeg_get_bits(j, r);
2373 r = 64; // force end of block
2374 } else {
2375 // r=15 s=0 should write 16 0s, so we just do
2376 // a run of 15 0s and then write s (which is 0),
2377 // so we don't have to do anything special here
2378 }
2379 } else {
2380 if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG");
2381 // sign bit
2382 if (stbi__jpeg_get_bit(j))
2383 s = bit;
2384 else
2385 s = -bit;
2386 }
2387
2388 // advance by r
2389 while (k <= j->spec_end) {
2390 short *p = &data[stbi__jpeg_dezigzag[k++]];
2391 if (*p != 0) {
2392 if (stbi__jpeg_get_bit(j))
2393 if ((*p & bit)==0) {
2394 if (*p > 0)
2395 *p += bit;
2396 else
2397 *p -= bit;
2398 }
2399 } else {
2400 if (r == 0) {
2401 *p = (short) s;
2402 break;
2403 }
2404 --r;
2405 }
2406 }
2407 } while (k <= j->spec_end);
2408 }
2409 }
2410 return 1;
2411}
2412
2413// take a -128..127 value and stbi__clamp it and convert to 0..255
2414stbi_inline static stbi_uc stbi__clamp(int x)
2415{
2416 // trick to use a single test to catch both cases
2417 if ((unsigned int) x > 255) {
2418 if (x < 0) return 0;
2419 if (x > 255) return 255;
2420 }
2421 return (stbi_uc) x;
2422}
2423
2424#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5)))
2425#define stbi__fsh(x) ((x) * 4096)
2426
2427// derived from jidctint -- DCT_ISLOW
2428#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \
2429 int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \
2430 p2 = s2; \
2431 p3 = s6; \
2432 p1 = (p2+p3) * stbi__f2f(0.5411961f); \
2433 t2 = p1 + p3*stbi__f2f(-1.847759065f); \
2434 t3 = p1 + p2*stbi__f2f( 0.765366865f); \
2435 p2 = s0; \
2436 p3 = s4; \
2437 t0 = stbi__fsh(p2+p3); \
2438 t1 = stbi__fsh(p2-p3); \
2439 x0 = t0+t3; \
2440 x3 = t0-t3; \
2441 x1 = t1+t2; \
2442 x2 = t1-t2; \
2443 t0 = s7; \
2444 t1 = s5; \
2445 t2 = s3; \
2446 t3 = s1; \
2447 p3 = t0+t2; \
2448 p4 = t1+t3; \
2449 p1 = t0+t3; \
2450 p2 = t1+t2; \
2451 p5 = (p3+p4)*stbi__f2f( 1.175875602f); \
2452 t0 = t0*stbi__f2f( 0.298631336f); \
2453 t1 = t1*stbi__f2f( 2.053119869f); \
2454 t2 = t2*stbi__f2f( 3.072711026f); \
2455 t3 = t3*stbi__f2f( 1.501321110f); \
2456 p1 = p5 + p1*stbi__f2f(-0.899976223f); \
2457 p2 = p5 + p2*stbi__f2f(-2.562915447f); \
2458 p3 = p3*stbi__f2f(-1.961570560f); \
2459 p4 = p4*stbi__f2f(-0.390180644f); \
2460 t3 += p1+p4; \
2461 t2 += p2+p3; \
2462 t1 += p2+p4; \
2463 t0 += p1+p3;
2464
2465static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64])
2466{
2467 int i,val[64],*v=val;
2468 stbi_uc *o;
2469 short *d = data;
2470
2471 // columns
2472 for (i=0; i < 8; ++i,++d, ++v) {
2473 // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing
2474 if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0
2475 && d[40]==0 && d[48]==0 && d[56]==0) {
2476 // no shortcut 0 seconds
2477 // (1|2|3|4|5|6|7)==0 0 seconds
2478 // all separate -0.047 seconds
2479 // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds
2480 int dcterm = d[0]*4;
2481 v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;
2482 } else {
2483 STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56])
2484 // constants scaled things up by 1<<12; let's bring them back
2485 // down, but keep 2 extra bits of precision
2486 x0 += 512; x1 += 512; x2 += 512; x3 += 512;
2487 v[ 0] = (x0+t3) >> 10;
2488 v[56] = (x0-t3) >> 10;
2489 v[ 8] = (x1+t2) >> 10;
2490 v[48] = (x1-t2) >> 10;
2491 v[16] = (x2+t1) >> 10;
2492 v[40] = (x2-t1) >> 10;
2493 v[24] = (x3+t0) >> 10;
2494 v[32] = (x3-t0) >> 10;
2495 }
2496 }
2497
2498 for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) {
2499 // no fast case since the first 1D IDCT spread components out
2500 STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7])
2501 // constants scaled things up by 1<<12, plus we had 1<<2 from first
2502 // loop, plus horizontal and vertical each scale by sqrt(8) so together
2503 // we've got an extra 1<<3, so 1<<17 total we need to remove.
2504 // so we want to round that, which means adding 0.5 * 1<<17,
2505 // aka 65536. Also, we'll end up with -128 to 127 that we want
2506 // to encode as 0..255 by adding 128, so we'll add that before the shift
2507 x0 += 65536 + (128<<17);
2508 x1 += 65536 + (128<<17);
2509 x2 += 65536 + (128<<17);
2510 x3 += 65536 + (128<<17);
2511 // tried computing the shifts into temps, or'ing the temps to see
2512 // if any were out of range, but that was slower
2513 o[0] = stbi__clamp((x0+t3) >> 17);
2514 o[7] = stbi__clamp((x0-t3) >> 17);
2515 o[1] = stbi__clamp((x1+t2) >> 17);
2516 o[6] = stbi__clamp((x1-t2) >> 17);
2517 o[2] = stbi__clamp((x2+t1) >> 17);
2518 o[5] = stbi__clamp((x2-t1) >> 17);
2519 o[3] = stbi__clamp((x3+t0) >> 17);
2520 o[4] = stbi__clamp((x3-t0) >> 17);
2521 }
2522}
2523
2524#ifdef STBI_SSE2
2525// sse2 integer IDCT. not the fastest possible implementation but it
2526// produces bit-identical results to the generic C version so it's
2527// fully "transparent".
2528static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
2529{
2530 // This is constructed to match our regular (generic) integer IDCT exactly.
2531 __m128i row0, row1, row2, row3, row4, row5, row6, row7;
2532 __m128i tmp;
2533
2534 // dot product constant: even elems=x, odd elems=y
2535 #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y))
2536
2537 // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit)
2538 // out(1) = c1[even]*x + c1[odd]*y
2539 #define dct_rot(out0,out1, x,y,c0,c1) \
2540 __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \
2541 __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \
2542 __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \
2543 __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \
2544 __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \
2545 __m128i out1##_h = _mm_madd_epi16(c0##hi, c1)
2546
2547 // out = in << 12 (in 16-bit, out 32-bit)
2548 #define dct_widen(out, in) \
2549 __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \
2550 __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4)
2551
2552 // wide add
2553 #define dct_wadd(out, a, b) \
2554 __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \
2555 __m128i out##_h = _mm_add_epi32(a##_h, b##_h)
2556
2557 // wide sub
2558 #define dct_wsub(out, a, b) \
2559 __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \
2560 __m128i out##_h = _mm_sub_epi32(a##_h, b##_h)
2561
2562 // butterfly a/b, add bias, then shift by "s" and pack
2563 #define dct_bfly32o(out0, out1, a,b,bias,s) \
2564 { \
2565 __m128i abiased_l = _mm_add_epi32(a##_l, bias); \
2566 __m128i abiased_h = _mm_add_epi32(a##_h, bias); \
2567 dct_wadd(sum, abiased, b); \
2568 dct_wsub(dif, abiased, b); \
2569 out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \
2570 out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \
2571 }
2572
2573 // 8-bit interleave step (for transposes)
2574 #define dct_interleave8(a, b) \
2575 tmp = a; \
2576 a = _mm_unpacklo_epi8(a, b); \
2577 b = _mm_unpackhi_epi8(tmp, b)
2578
2579 // 16-bit interleave step (for transposes)
2580 #define dct_interleave16(a, b) \
2581 tmp = a; \
2582 a = _mm_unpacklo_epi16(a, b); \
2583 b = _mm_unpackhi_epi16(tmp, b)
2584
2585 #define dct_pass(bias,shift) \
2586 { \
2587 /* even part */ \
2588 dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \
2589 __m128i sum04 = _mm_add_epi16(row0, row4); \
2590 __m128i dif04 = _mm_sub_epi16(row0, row4); \
2591 dct_widen(t0e, sum04); \
2592 dct_widen(t1e, dif04); \
2593 dct_wadd(x0, t0e, t3e); \
2594 dct_wsub(x3, t0e, t3e); \
2595 dct_wadd(x1, t1e, t2e); \
2596 dct_wsub(x2, t1e, t2e); \
2597 /* odd part */ \
2598 dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \
2599 dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \
2600 __m128i sum17 = _mm_add_epi16(row1, row7); \
2601 __m128i sum35 = _mm_add_epi16(row3, row5); \
2602 dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \
2603 dct_wadd(x4, y0o, y4o); \
2604 dct_wadd(x5, y1o, y5o); \
2605 dct_wadd(x6, y2o, y5o); \
2606 dct_wadd(x7, y3o, y4o); \
2607 dct_bfly32o(row0,row7, x0,x7,bias,shift); \
2608 dct_bfly32o(row1,row6, x1,x6,bias,shift); \
2609 dct_bfly32o(row2,row5, x2,x5,bias,shift); \
2610 dct_bfly32o(row3,row4, x3,x4,bias,shift); \
2611 }
2612
2613 __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f));
2614 __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f));
2615 __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f));
2616 __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f));
2617 __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f));
2618 __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f));
2619 __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f));
2620 __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f));
2621
2622 // rounding biases in column/row passes, see stbi__idct_block for explanation.
2623 __m128i bias_0 = _mm_set1_epi32(512);
2624 __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17));
2625
2626 // load
2627 row0 = _mm_load_si128((const __m128i *) (data + 0*8));
2628 row1 = _mm_load_si128((const __m128i *) (data + 1*8));
2629 row2 = _mm_load_si128((const __m128i *) (data + 2*8));
2630 row3 = _mm_load_si128((const __m128i *) (data + 3*8));
2631 row4 = _mm_load_si128((const __m128i *) (data + 4*8));
2632 row5 = _mm_load_si128((const __m128i *) (data + 5*8));
2633 row6 = _mm_load_si128((const __m128i *) (data + 6*8));
2634 row7 = _mm_load_si128((const __m128i *) (data + 7*8));
2635
2636 // column pass
2637 dct_pass(bias_0, 10);
2638
2639 {
2640 // 16bit 8x8 transpose pass 1
2641 dct_interleave16(row0, row4);
2642 dct_interleave16(row1, row5);
2643 dct_interleave16(row2, row6);
2644 dct_interleave16(row3, row7);
2645
2646 // transpose pass 2
2647 dct_interleave16(row0, row2);
2648 dct_interleave16(row1, row3);
2649 dct_interleave16(row4, row6);
2650 dct_interleave16(row5, row7);
2651
2652 // transpose pass 3
2653 dct_interleave16(row0, row1);
2654 dct_interleave16(row2, row3);
2655 dct_interleave16(row4, row5);
2656 dct_interleave16(row6, row7);
2657 }
2658
2659 // row pass
2660 dct_pass(bias_1, 17);
2661
2662 {
2663 // pack
2664 __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7
2665 __m128i p1 = _mm_packus_epi16(row2, row3);
2666 __m128i p2 = _mm_packus_epi16(row4, row5);
2667 __m128i p3 = _mm_packus_epi16(row6, row7);
2668
2669 // 8bit 8x8 transpose pass 1
2670 dct_interleave8(p0, p2); // a0e0a1e1...
2671 dct_interleave8(p1, p3); // c0g0c1g1...
2672
2673 // transpose pass 2
2674 dct_interleave8(p0, p1); // a0c0e0g0...
2675 dct_interleave8(p2, p3); // b0d0f0h0...
2676
2677 // transpose pass 3
2678 dct_interleave8(p0, p2); // a0b0c0d0...
2679 dct_interleave8(p1, p3); // a4b4c4d4...
2680
2681 // store
2682 _mm_storel_epi64((__m128i *) out, p0); out += out_stride;
2683 _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride;
2684 _mm_storel_epi64((__m128i *) out, p2); out += out_stride;
2685 _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride;
2686 _mm_storel_epi64((__m128i *) out, p1); out += out_stride;
2687 _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride;
2688 _mm_storel_epi64((__m128i *) out, p3); out += out_stride;
2689 _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e));
2690 }
2691
2692#undef dct_const
2693#undef dct_rot
2694#undef dct_widen
2695#undef dct_wadd
2696#undef dct_wsub
2697#undef dct_bfly32o
2698#undef dct_interleave8
2699#undef dct_interleave16
2700#undef dct_pass
2701}
2702
2703#endif // STBI_SSE2
2704
2705#ifdef STBI_NEON
2706
2707// NEON integer IDCT. should produce bit-identical
2708// results to the generic C version.
2709static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
2710{
2711 int16x8_t row0, row1, row2, row3, row4, row5, row6, row7;
2712
2713 int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f));
2714 int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f));
2715 int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f));
2716 int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f));
2717 int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f));
2718 int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f));
2719 int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f));
2720 int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f));
2721 int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f));
2722 int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f));
2723 int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f));
2724 int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f));
2725
2726#define dct_long_mul(out, inq, coeff) \
2727 int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \
2728 int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff)
2729
2730#define dct_long_mac(out, acc, inq, coeff) \
2731 int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \
2732 int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff)
2733
2734#define dct_widen(out, inq) \
2735 int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \
2736 int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12)
2737
2738// wide add
2739#define dct_wadd(out, a, b) \
2740 int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \
2741 int32x4_t out##_h = vaddq_s32(a##_h, b##_h)
2742
2743// wide sub
2744#define dct_wsub(out, a, b) \
2745 int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \
2746 int32x4_t out##_h = vsubq_s32(a##_h, b##_h)
2747
2748// butterfly a/b, then shift using "shiftop" by "s" and pack
2749#define dct_bfly32o(out0,out1, a,b,shiftop,s) \
2750 { \
2751 dct_wadd(sum, a, b); \
2752 dct_wsub(dif, a, b); \
2753 out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \
2754 out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \
2755 }
2756
2757#define dct_pass(shiftop, shift) \
2758 { \
2759 /* even part */ \
2760 int16x8_t sum26 = vaddq_s16(row2, row6); \
2761 dct_long_mul(p1e, sum26, rot0_0); \
2762 dct_long_mac(t2e, p1e, row6, rot0_1); \
2763 dct_long_mac(t3e, p1e, row2, rot0_2); \
2764 int16x8_t sum04 = vaddq_s16(row0, row4); \
2765 int16x8_t dif04 = vsubq_s16(row0, row4); \
2766 dct_widen(t0e, sum04); \
2767 dct_widen(t1e, dif04); \
2768 dct_wadd(x0, t0e, t3e); \
2769 dct_wsub(x3, t0e, t3e); \
2770 dct_wadd(x1, t1e, t2e); \
2771 dct_wsub(x2, t1e, t2e); \
2772 /* odd part */ \
2773 int16x8_t sum15 = vaddq_s16(row1, row5); \
2774 int16x8_t sum17 = vaddq_s16(row1, row7); \
2775 int16x8_t sum35 = vaddq_s16(row3, row5); \
2776 int16x8_t sum37 = vaddq_s16(row3, row7); \
2777 int16x8_t sumodd = vaddq_s16(sum17, sum35); \
2778 dct_long_mul(p5o, sumodd, rot1_0); \
2779 dct_long_mac(p1o, p5o, sum17, rot1_1); \
2780 dct_long_mac(p2o, p5o, sum35, rot1_2); \
2781 dct_long_mul(p3o, sum37, rot2_0); \
2782 dct_long_mul(p4o, sum15, rot2_1); \
2783 dct_wadd(sump13o, p1o, p3o); \
2784 dct_wadd(sump24o, p2o, p4o); \
2785 dct_wadd(sump23o, p2o, p3o); \
2786 dct_wadd(sump14o, p1o, p4o); \
2787 dct_long_mac(x4, sump13o, row7, rot3_0); \
2788 dct_long_mac(x5, sump24o, row5, rot3_1); \
2789 dct_long_mac(x6, sump23o, row3, rot3_2); \
2790 dct_long_mac(x7, sump14o, row1, rot3_3); \
2791 dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \
2792 dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \
2793 dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \
2794 dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \
2795 }
2796
2797 // load
2798 row0 = vld1q_s16(data + 0*8);
2799 row1 = vld1q_s16(data + 1*8);
2800 row2 = vld1q_s16(data + 2*8);
2801 row3 = vld1q_s16(data + 3*8);
2802 row4 = vld1q_s16(data + 4*8);
2803 row5 = vld1q_s16(data + 5*8);
2804 row6 = vld1q_s16(data + 6*8);
2805 row7 = vld1q_s16(data + 7*8);
2806
2807 // add DC bias
2808 row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0));
2809
2810 // column pass
2811 dct_pass(vrshrn_n_s32, 10);
2812
2813 // 16bit 8x8 transpose
2814 {
2815// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively.
2816// whether compilers actually get this is another story, sadly.
2817#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; }
2818#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]); }
2819#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)); }
2820
2821 // pass 1
2822 dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6
2823 dct_trn16(row2, row3);
2824 dct_trn16(row4, row5);
2825 dct_trn16(row6, row7);
2826
2827 // pass 2
2828 dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4
2829 dct_trn32(row1, row3);
2830 dct_trn32(row4, row6);
2831 dct_trn32(row5, row7);
2832
2833 // pass 3
2834 dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0
2835 dct_trn64(row1, row5);
2836 dct_trn64(row2, row6);
2837 dct_trn64(row3, row7);
2838
2839#undef dct_trn16
2840#undef dct_trn32
2841#undef dct_trn64
2842 }
2843
2844 // row pass
2845 // vrshrn_n_s32 only supports shifts up to 16, we need
2846 // 17. so do a non-rounding shift of 16 first then follow
2847 // up with a rounding shift by 1.
2848 dct_pass(vshrn_n_s32, 16);
2849
2850 {
2851 // pack and round
2852 uint8x8_t p0 = vqrshrun_n_s16(row0, 1);
2853 uint8x8_t p1 = vqrshrun_n_s16(row1, 1);
2854 uint8x8_t p2 = vqrshrun_n_s16(row2, 1);
2855 uint8x8_t p3 = vqrshrun_n_s16(row3, 1);
2856 uint8x8_t p4 = vqrshrun_n_s16(row4, 1);
2857 uint8x8_t p5 = vqrshrun_n_s16(row5, 1);
2858 uint8x8_t p6 = vqrshrun_n_s16(row6, 1);
2859 uint8x8_t p7 = vqrshrun_n_s16(row7, 1);
2860
2861 // again, these can translate into one instruction, but often don't.
2862#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; }
2863#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]); }
2864#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]); }
2865
2866 // sadly can't use interleaved stores here since we only write
2867 // 8 bytes to each scan line!
2868
2869 // 8x8 8-bit transpose pass 1
2870 dct_trn8_8(p0, p1);
2871 dct_trn8_8(p2, p3);
2872 dct_trn8_8(p4, p5);
2873 dct_trn8_8(p6, p7);
2874
2875 // pass 2
2876 dct_trn8_16(p0, p2);
2877 dct_trn8_16(p1, p3);
2878 dct_trn8_16(p4, p6);
2879 dct_trn8_16(p5, p7);
2880
2881 // pass 3
2882 dct_trn8_32(p0, p4);
2883 dct_trn8_32(p1, p5);
2884 dct_trn8_32(p2, p6);
2885 dct_trn8_32(p3, p7);
2886
2887 // store
2888 vst1_u8(out, p0); out += out_stride;
2889 vst1_u8(out, p1); out += out_stride;
2890 vst1_u8(out, p2); out += out_stride;
2891 vst1_u8(out, p3); out += out_stride;
2892 vst1_u8(out, p4); out += out_stride;
2893 vst1_u8(out, p5); out += out_stride;
2894 vst1_u8(out, p6); out += out_stride;
2895 vst1_u8(out, p7);
2896
2897#undef dct_trn8_8
2898#undef dct_trn8_16
2899#undef dct_trn8_32
2900 }
2901
2902#undef dct_long_mul
2903#undef dct_long_mac
2904#undef dct_widen
2905#undef dct_wadd
2906#undef dct_wsub
2907#undef dct_bfly32o
2908#undef dct_pass
2909}
2910
2911#endif // STBI_NEON
2912
2913#define STBI__MARKER_none 0xff
2914// if there's a pending marker from the entropy stream, return that
2915// otherwise, fetch from the stream and get a marker. if there's no
2916// marker, return 0xff, which is never a valid marker value
2917static stbi_uc stbi__get_marker(stbi__jpeg *j)
2918{
2919 stbi_uc x;
2920 if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; }
2921 x = stbi__get8(j->s);
2922 if (x != 0xff) return STBI__MARKER_none;
2923 while (x == 0xff)
2924 x = stbi__get8(j->s); // consume repeated 0xff fill bytes
2925 return x;
2926}
2927
2928// in each scan, we'll have scan_n components, and the order
2929// of the components is specified by order[]
2930#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7)
2931
2932// after a restart interval, stbi__jpeg_reset the entropy decoder and
2933// the dc prediction
2934static void stbi__jpeg_reset(stbi__jpeg *j)
2935{
2936 j->code_bits = 0;
2937 j->code_buffer = 0;
2938 j->nomore = 0;
2939 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;
2940 j->marker = STBI__MARKER_none;
2941 j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff;
2942 j->eob_run = 0;
2943 // no more than 1<<31 MCUs if no restart_interal? that's plenty safe,
2944 // since we don't even allow 1<<30 pixels
2945}
2946
2947static int stbi__parse_entropy_coded_data(stbi__jpeg *z)
2948{
2949 stbi__jpeg_reset(z);
2950 if (!z->progressive) {
2951 if (z->scan_n == 1) {
2952 int i,j;
2953 STBI_SIMD_ALIGN(short, data[64]);
2954 int n = z->order[0];
2955 // non-interleaved data, we just need to process one block at a time,
2956 // in trivial scanline order
2957 // number of blocks to do just depends on how many actual "pixels" this
2958 // component has, independent of interleaved MCU blocking and such
2959 int w = (z->img_comp[n].x+7) >> 3;
2960 int h = (z->img_comp[n].y+7) >> 3;
2961 for (j=0; j < h; ++j) {
2962 for (i=0; i < w; ++i) {
2963 int ha = z->img_comp[n].ha;
2964 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;
2965 z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
2966 // every data block is an MCU, so countdown the restart interval
2967 if (--z->todo <= 0) {
2968 if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
2969 // if it's NOT a restart, then just bail, so we get corrupt data
2970 // rather than no data
2971 if (!STBI__RESTART(z->marker)) return 1;
2972 stbi__jpeg_reset(z);
2973 }
2974 }
2975 }
2976 return 1;
2977 } else { // interleaved
2978 int i,j,k,x,y;
2979 STBI_SIMD_ALIGN(short, data[64]);
2980 for (j=0; j < z->img_mcu_y; ++j) {
2981 for (i=0; i < z->img_mcu_x; ++i) {
2982 // scan an interleaved mcu... process scan_n components in order
2983 for (k=0; k < z->scan_n; ++k) {
2984 int n = z->order[k];
2985 // scan out an mcu's worth of this component; that's just determined
2986 // by the basic H and V specified for the component
2987 for (y=0; y < z->img_comp[n].v; ++y) {
2988 for (x=0; x < z->img_comp[n].h; ++x) {
2989 int x2 = (i*z->img_comp[n].h + x)*8;
2990 int y2 = (j*z->img_comp[n].v + y)*8;
2991 int ha = z->img_comp[n].ha;
2992 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;
2993 z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data);
2994 }
2995 }
2996 }
2997 // after all interleaved components, that's an interleaved MCU,
2998 // so now count down the restart interval
2999 if (--z->todo <= 0) {
3000 if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
3001 if (!STBI__RESTART(z->marker)) return 1;
3002 stbi__jpeg_reset(z);
3003 }
3004 }
3005 }
3006 return 1;
3007 }
3008 } else {
3009 if (z->scan_n == 1) {
3010 int i,j;
3011 int n = z->order[0];
3012 // non-interleaved data, we just need to process one block at a time,
3013 // in trivial scanline order
3014 // number of blocks to do just depends on how many actual "pixels" this
3015 // component has, independent of interleaved MCU blocking and such
3016 int w = (z->img_comp[n].x+7) >> 3;
3017 int h = (z->img_comp[n].y+7) >> 3;
3018 for (j=0; j < h; ++j) {
3019 for (i=0; i < w; ++i) {
3020 short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
3021 if (z->spec_start == 0) {
3022 if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
3023 return 0;
3024 } else {
3025 int ha = z->img_comp[n].ha;
3026 if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha]))
3027 return 0;
3028 }
3029 // every data block is an MCU, so countdown the restart interval
3030 if (--z->todo <= 0) {
3031 if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
3032 if (!STBI__RESTART(z->marker)) return 1;
3033 stbi__jpeg_reset(z);
3034 }
3035 }
3036 }
3037 return 1;
3038 } else { // interleaved
3039 int i,j,k,x,y;
3040 for (j=0; j < z->img_mcu_y; ++j) {
3041 for (i=0; i < z->img_mcu_x; ++i) {
3042 // scan an interleaved mcu... process scan_n components in order
3043 for (k=0; k < z->scan_n; ++k) {
3044 int n = z->order[k];
3045 // scan out an mcu's worth of this component; that's just determined
3046 // by the basic H and V specified for the component
3047 for (y=0; y < z->img_comp[n].v; ++y) {
3048 for (x=0; x < z->img_comp[n].h; ++x) {
3049 int x2 = (i*z->img_comp[n].h + x);
3050 int y2 = (j*z->img_comp[n].v + y);
3051 short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w);
3052 if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
3053 return 0;
3054 }
3055 }
3056 }
3057 // after all interleaved components, that's an interleaved MCU,
3058 // so now count down the restart interval
3059 if (--z->todo <= 0) {
3060 if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
3061 if (!STBI__RESTART(z->marker)) return 1;
3062 stbi__jpeg_reset(z);
3063 }
3064 }
3065 }
3066 return 1;
3067 }
3068 }
3069}
3070
3071static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant)
3072{
3073 int i;
3074 for (i=0; i < 64; ++i)
3075 data[i] *= dequant[i];
3076}
3077
3078static void stbi__jpeg_finish(stbi__jpeg *z)
3079{
3080 if (z->progressive) {
3081 // dequantize and idct the data
3082 int i,j,n;
3083 for (n=0; n < z->s->img_n; ++n) {
3084 int w = (z->img_comp[n].x+7) >> 3;
3085 int h = (z->img_comp[n].y+7) >> 3;
3086 for (j=0; j < h; ++j) {
3087 for (i=0; i < w; ++i) {
3088 short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
3089 stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]);
3090 z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
3091 }
3092 }
3093 }
3094 }
3095}
3096
3097static int stbi__process_marker(stbi__jpeg *z, int m)
3098{
3099 int L;
3100 switch (m) {
3101 case STBI__MARKER_none: // no marker found
3102 return stbi__err("expected marker","Corrupt JPEG");
3103
3104 case 0xDD: // DRI - specify restart interval
3105 if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG");
3106 z->restart_interval = stbi__get16be(z->s);
3107 return 1;
3108
3109 case 0xDB: // DQT - define quantization table
3110 L = stbi__get16be(z->s)-2;
3111 while (L > 0) {
3112 int q = stbi__get8(z->s);
3113 int p = q >> 4, sixteen = (p != 0);
3114 int t = q & 15,i;
3115 if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG");
3116 if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG");
3117
3118 for (i=0; i < 64; ++i)
3119 z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s));
3120 L -= (sixteen ? 129 : 65);
3121 }
3122 return L==0;
3123
3124 case 0xC4: // DHT - define huffman table
3125 L = stbi__get16be(z->s)-2;
3126 while (L > 0) {
3127 stbi_uc *v;
3128 int sizes[16],i,n=0;
3129 int q = stbi__get8(z->s);
3130 int tc = q >> 4;
3131 int th = q & 15;
3132 if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG");
3133 for (i=0; i < 16; ++i) {
3134 sizes[i] = stbi__get8(z->s);
3135 n += sizes[i];
3136 }
3137 if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values!
3138 L -= 17;
3139 if (tc == 0) {
3140 if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0;
3141 v = z->huff_dc[th].values;
3142 } else {
3143 if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0;
3144 v = z->huff_ac[th].values;
3145 }
3146 for (i=0; i < n; ++i)
3147 v[i] = stbi__get8(z->s);
3148 if (tc != 0)
3149 stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th);
3150 L -= n;
3151 }
3152 return L==0;
3153 }
3154
3155 // check for comment block or APP blocks
3156 if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) {
3157 L = stbi__get16be(z->s);
3158 if (L < 2) {
3159 if (m == 0xFE)
3160 return stbi__err("bad COM len","Corrupt JPEG");
3161 else
3162 return stbi__err("bad APP len","Corrupt JPEG");
3163 }
3164 L -= 2;
3165
3166 if (m == 0xE0 && L >= 5) { // JFIF APP0 segment
3167 static const unsigned char tag[5] = {'J','F','I','F','\0'};
3168 int ok = 1;
3169 int i;
3170 for (i=0; i < 5; ++i)
3171 if (stbi__get8(z->s) != tag[i])
3172 ok = 0;
3173 L -= 5;
3174 if (ok)
3175 z->jfif = 1;
3176 } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment
3177 static const unsigned char tag[6] = {'A','d','o','b','e','\0'};
3178 int ok = 1;
3179 int i;
3180 for (i=0; i < 6; ++i)
3181 if (stbi__get8(z->s) != tag[i])
3182 ok = 0;
3183 L -= 6;
3184 if (ok) {
3185 stbi__get8(z->s); // version
3186 stbi__get16be(z->s); // flags0
3187 stbi__get16be(z->s); // flags1
3188 z->app14_color_transform = stbi__get8(z->s); // color transform
3189 L -= 6;
3190 }
3191 }
3192
3193 stbi__skip(z->s, L);
3194 return 1;
3195 }
3196
3197 return stbi__err("unknown marker","Corrupt JPEG");
3198}
3199
3200// after we see SOS
3201static int stbi__process_scan_header(stbi__jpeg *z)
3202{
3203 int i;
3204 int Ls = stbi__get16be(z->s);
3205 z->scan_n = stbi__get8(z->s);
3206 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");
3207 if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG");
3208 for (i=0; i < z->scan_n; ++i) {
3209 int id = stbi__get8(z->s), which;
3210 int q = stbi__get8(z->s);
3211 for (which = 0; which < z->s->img_n; ++which)
3212 if (z->img_comp[which].id == id)
3213 break;
3214 if (which == z->s->img_n) return 0; // no match
3215 z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG");
3216 z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG");
3217 z->order[i] = which;
3218 }
3219
3220 {
3221 int aa;
3222 z->spec_start = stbi__get8(z->s);
3223 z->spec_end = stbi__get8(z->s); // should be 63, but might be 0
3224 aa = stbi__get8(z->s);
3225 z->succ_high = (aa >> 4);
3226 z->succ_low = (aa & 15);
3227 if (z->progressive) {
3228 if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13)
3229 return stbi__err("bad SOS", "Corrupt JPEG");
3230 } else {
3231 if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG");
3232 if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG");
3233 z->spec_end = 63;
3234 }
3235 }
3236
3237 return 1;
3238}
3239
3240static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why)
3241{
3242 int i;
3243 for (i=0; i < ncomp; ++i) {
3244 if (z->img_comp[i].raw_data) {
3245 STBI_FREE(z->img_comp[i].raw_data);
3246 z->img_comp[i].raw_data = NULL;
3247 z->img_comp[i].data = NULL;
3248 }
3249 if (z->img_comp[i].raw_coeff) {
3250 STBI_FREE(z->img_comp[i].raw_coeff);
3251 z->img_comp[i].raw_coeff = 0;
3252 z->img_comp[i].coeff = 0;
3253 }
3254 if (z->img_comp[i].linebuf) {
3255 STBI_FREE(z->img_comp[i].linebuf);
3256 z->img_comp[i].linebuf = NULL;
3257 }
3258 }
3259 return why;
3260}
3261
3262static int stbi__process_frame_header(stbi__jpeg *z, int scan)
3263{
3264 stbi__context *s = z->s;
3265 int Lf,p,i,q, h_max=1,v_max=1,c;
3266 Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG
3267 p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline
3268 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
3269 s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires
3270 if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
3271 if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
3272 c = stbi__get8(s);
3273 if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG");
3274 s->img_n = c;
3275 for (i=0; i < c; ++i) {
3276 z->img_comp[i].data = NULL;
3277 z->img_comp[i].linebuf = NULL;
3278 }
3279
3280 if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG");
3281
3282 z->rgb = 0;
3283 for (i=0; i < s->img_n; ++i) {
3284 static const unsigned char rgb[3] = { 'R', 'G', 'B' };
3285 z->img_comp[i].id = stbi__get8(s);
3286 if (s->img_n == 3 && z->img_comp[i].id == rgb[i])
3287 ++z->rgb;
3288 q = stbi__get8(s);
3289 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");
3290 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");
3291 z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG");
3292 }
3293
3294 if (scan != STBI__SCAN_load) return 1;
3295
3296 if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode");
3297
3298 for (i=0; i < s->img_n; ++i) {
3299 if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h;
3300 if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v;
3301 }
3302
3303 // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios
3304 // and I've never seen a non-corrupted JPEG file actually use them
3305 for (i=0; i < s->img_n; ++i) {
3306 if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG");
3307 if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG");
3308 }
3309
3310 // compute interleaved mcu info
3311 z->img_h_max = h_max;
3312 z->img_v_max = v_max;
3313 z->img_mcu_w = h_max * 8;
3314 z->img_mcu_h = v_max * 8;
3315 // these sizes can't be more than 17 bits
3316 z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w;
3317 z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h;
3318
3319 for (i=0; i < s->img_n; ++i) {
3320 // number of effective pixels (e.g. for non-interleaved MCU)
3321 z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max;
3322 z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max;
3323 // to simplify generation, we'll allocate enough memory to decode
3324 // the bogus oversized data from using interleaved MCUs and their
3325 // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't
3326 // discard the extra data until colorspace conversion
3327 //
3328 // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier)
3329 // so these muls can't overflow with 32-bit ints (which we require)
3330 z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8;
3331 z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8;
3332 z->img_comp[i].coeff = 0;
3333 z->img_comp[i].raw_coeff = 0;
3334 z->img_comp[i].linebuf = NULL;
3335 z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15);
3336 if (z->img_comp[i].raw_data == NULL)
3337 return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory"));
3338 // align blocks for idct using mmx/sse
3339 z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15);
3340 if (z->progressive) {
3341 // w2, h2 are multiples of 8 (see above)
3342 z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8;
3343 z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8;
3344 z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15);
3345 if (z->img_comp[i].raw_coeff == NULL)
3346 return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory"));
3347 z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15);
3348 }
3349 }
3350
3351 return 1;
3352}
3353
3354// use comparisons since in some cases we handle more than one case (e.g. SOF)
3355#define stbi__DNL(x) ((x) == 0xdc)
3356#define stbi__SOI(x) ((x) == 0xd8)
3357#define stbi__EOI(x) ((x) == 0xd9)
3358#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2)
3359#define stbi__SOS(x) ((x) == 0xda)
3360
3361#define stbi__SOF_progressive(x) ((x) == 0xc2)
3362
3363static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
3364{
3365 int m;
3366 z->jfif = 0;
3367 z->app14_color_transform = -1; // valid values are 0,1,2
3368 z->marker = STBI__MARKER_none; // initialize cached marker to empty
3369 m = stbi__get_marker(z);
3370 if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG");
3371 if (scan == STBI__SCAN_type) return 1;
3372 m = stbi__get_marker(z);
3373 while (!stbi__SOF(m)) {
3374 if (!stbi__process_marker(z,m)) return 0;
3375 m = stbi__get_marker(z);
3376 while (m == STBI__MARKER_none) {
3377 // some files have extra padding after their blocks, so ok, we'll scan
3378 if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG");
3379 m = stbi__get_marker(z);
3380 }
3381 }
3382 z->progressive = stbi__SOF_progressive(m);
3383 if (!stbi__process_frame_header(z, scan)) return 0;
3384 return 1;
3385}
3386
3387static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j)
3388{
3389 // some JPEGs have junk at end, skip over it but if we find what looks
3390 // like a valid marker, resume there
3391 while (!stbi__at_eof(j->s)) {
3392 int x = stbi__get8(j->s);
3393 while (x == 255) { // might be a marker
3394 if (stbi__at_eof(j->s)) return STBI__MARKER_none;
3395 x = stbi__get8(j->s);
3396 if (x != 0x00 && x != 0xff) {
3397 // not a stuffed zero or lead-in to another marker, looks
3398 // like an actual marker, return it
3399 return x;
3400 }
3401 // stuffed zero has x=0 now which ends the loop, meaning we go
3402 // back to regular scan loop.
3403 // repeated 0xff keeps trying to read the next byte of the marker.
3404 }
3405 }
3406 return STBI__MARKER_none;
3407}
3408
3409// decode image to YCbCr format
3410static int stbi__decode_jpeg_image(stbi__jpeg *j)
3411{
3412 int m;
3413 for (m = 0; m < 4; m++) {
3414 j->img_comp[m].raw_data = NULL;
3415 j->img_comp[m].raw_coeff = NULL;
3416 }
3417 j->restart_interval = 0;
3418 if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0;
3419 m = stbi__get_marker(j);
3420 while (!stbi__EOI(m)) {
3421 if (stbi__SOS(m)) {
3422 if (!stbi__process_scan_header(j)) return 0;
3423 if (!stbi__parse_entropy_coded_data(j)) return 0;
3424 if (j->marker == STBI__MARKER_none ) {
3425 j->marker = stbi__skip_jpeg_junk_at_end(j);
3426 // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0
3427 }
3428 m = stbi__get_marker(j);
3429 if (STBI__RESTART(m))
3430 m = stbi__get_marker(j);
3431 } else if (stbi__DNL(m)) {
3432 int Ld = stbi__get16be(j->s);
3433 stbi__uint32 NL = stbi__get16be(j->s);
3434 if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG");
3435 if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG");
3436 m = stbi__get_marker(j);
3437 } else {
3438 if (!stbi__process_marker(j, m)) return 1;
3439 m = stbi__get_marker(j);
3440 }
3441 }
3442 if (j->progressive)
3443 stbi__jpeg_finish(j);
3444 return 1;
3445}
3446
3447// static jfif-centered resampling (across block boundaries)
3448
3449typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1,
3450 int w, int hs);
3451
3452#define stbi__div4(x) ((stbi_uc) ((x) >> 2))
3453
3454static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3455{
3456 STBI_NOTUSED(out);
3457 STBI_NOTUSED(in_far);
3458 STBI_NOTUSED(w);
3459 STBI_NOTUSED(hs);
3460 return in_near;
3461}
3462
3463static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3464{
3465 // need to generate two samples vertically for every one in input
3466 int i;
3467 STBI_NOTUSED(hs);
3468 for (i=0; i < w; ++i)
3469 out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2);
3470 return out;
3471}
3472
3473static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3474{
3475 // need to generate two samples horizontally for every one in input
3476 int i;
3477 stbi_uc *input = in_near;
3478
3479 if (w == 1) {
3480 // if only one sample, can't do any interpolation
3481 out[0] = out[1] = input[0];
3482 return out;
3483 }
3484
3485 out[0] = input[0];
3486 out[1] = stbi__div4(input[0]*3 + input[1] + 2);
3487 for (i=1; i < w-1; ++i) {
3488 int n = 3*input[i]+2;
3489 out[i*2+0] = stbi__div4(n+input[i-1]);
3490 out[i*2+1] = stbi__div4(n+input[i+1]);
3491 }
3492 out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2);
3493 out[i*2+1] = input[w-1];
3494
3495 STBI_NOTUSED(in_far);
3496 STBI_NOTUSED(hs);
3497
3498 return out;
3499}
3500
3501#define stbi__div16(x) ((stbi_uc) ((x) >> 4))
3502
3503static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3504{
3505 // need to generate 2x2 samples for every one in input
3506 int i,t0,t1;
3507 if (w == 1) {
3508 out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
3509 return out;
3510 }
3511
3512 t1 = 3*in_near[0] + in_far[0];
3513 out[0] = stbi__div4(t1+2);
3514 for (i=1; i < w; ++i) {
3515 t0 = t1;
3516 t1 = 3*in_near[i]+in_far[i];
3517 out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
3518 out[i*2 ] = stbi__div16(3*t1 + t0 + 8);
3519 }
3520 out[w*2-1] = stbi__div4(t1+2);
3521
3522 STBI_NOTUSED(hs);
3523
3524 return out;
3525}
3526
3527#if defined(STBI_SSE2) || defined(STBI_NEON)
3528static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3529{
3530 // need to generate 2x2 samples for every one in input
3531 int i=0,t0,t1;
3532
3533 if (w == 1) {
3534 out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
3535 return out;
3536 }
3537
3538 t1 = 3*in_near[0] + in_far[0];
3539 // process groups of 8 pixels for as long as we can.
3540 // note we can't handle the last pixel in a row in this loop
3541 // because we need to handle the filter boundary conditions.
3542 for (; i < ((w-1) & ~7); i += 8) {
3543#if defined(STBI_SSE2)
3544 // load and perform the vertical filtering pass
3545 // this uses 3*x + y = 4*x + (y - x)
3546 __m128i zero = _mm_setzero_si128();
3547 __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i));
3548 __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i));
3549 __m128i farw = _mm_unpacklo_epi8(farb, zero);
3550 __m128i nearw = _mm_unpacklo_epi8(nearb, zero);
3551 __m128i diff = _mm_sub_epi16(farw, nearw);
3552 __m128i nears = _mm_slli_epi16(nearw, 2);
3553 __m128i curr = _mm_add_epi16(nears, diff); // current row
3554
3555 // horizontal filter works the same based on shifted vers of current
3556 // row. "prev" is current row shifted right by 1 pixel; we need to
3557 // insert the previous pixel value (from t1).
3558 // "next" is current row shifted left by 1 pixel, with first pixel
3559 // of next block of 8 pixels added in.
3560 __m128i prv0 = _mm_slli_si128(curr, 2);
3561 __m128i nxt0 = _mm_srli_si128(curr, 2);
3562 __m128i prev = _mm_insert_epi16(prv0, t1, 0);
3563 __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7);
3564
3565 // horizontal filter, polyphase implementation since it's convenient:
3566 // even pixels = 3*cur + prev = cur*4 + (prev - cur)
3567 // odd pixels = 3*cur + next = cur*4 + (next - cur)
3568 // note the shared term.
3569 __m128i bias = _mm_set1_epi16(8);
3570 __m128i curs = _mm_slli_epi16(curr, 2);
3571 __m128i prvd = _mm_sub_epi16(prev, curr);
3572 __m128i nxtd = _mm_sub_epi16(next, curr);
3573 __m128i curb = _mm_add_epi16(curs, bias);
3574 __m128i even = _mm_add_epi16(prvd, curb);
3575 __m128i odd = _mm_add_epi16(nxtd, curb);
3576
3577 // interleave even and odd pixels, then undo scaling.
3578 __m128i int0 = _mm_unpacklo_epi16(even, odd);
3579 __m128i int1 = _mm_unpackhi_epi16(even, odd);
3580 __m128i de0 = _mm_srli_epi16(int0, 4);
3581 __m128i de1 = _mm_srli_epi16(int1, 4);
3582
3583 // pack and write output
3584 __m128i outv = _mm_packus_epi16(de0, de1);
3585 _mm_storeu_si128((__m128i *) (out + i*2), outv);
3586#elif defined(STBI_NEON)
3587 // load and perform the vertical filtering pass
3588 // this uses 3*x + y = 4*x + (y - x)
3589 uint8x8_t farb = vld1_u8(in_far + i);
3590 uint8x8_t nearb = vld1_u8(in_near + i);
3591 int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb));
3592 int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2));
3593 int16x8_t curr = vaddq_s16(nears, diff); // current row
3594
3595 // horizontal filter works the same based on shifted vers of current
3596 // row. "prev" is current row shifted right by 1 pixel; we need to
3597 // insert the previous pixel value (from t1).
3598 // "next" is current row shifted left by 1 pixel, with first pixel
3599 // of next block of 8 pixels added in.
3600 int16x8_t prv0 = vextq_s16(curr, curr, 7);
3601 int16x8_t nxt0 = vextq_s16(curr, curr, 1);
3602 int16x8_t prev = vsetq_lane_s16(t1, prv0, 0);
3603 int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7);
3604
3605 // horizontal filter, polyphase implementation since it's convenient:
3606 // even pixels = 3*cur + prev = cur*4 + (prev - cur)
3607 // odd pixels = 3*cur + next = cur*4 + (next - cur)
3608 // note the shared term.
3609 int16x8_t curs = vshlq_n_s16(curr, 2);
3610 int16x8_t prvd = vsubq_s16(prev, curr);
3611 int16x8_t nxtd = vsubq_s16(next, curr);
3612 int16x8_t even = vaddq_s16(curs, prvd);
3613 int16x8_t odd = vaddq_s16(curs, nxtd);
3614
3615 // undo scaling and round, then store with even/odd phases interleaved
3616 uint8x8x2_t o;
3617 o.val[0] = vqrshrun_n_s16(even, 4);
3618 o.val[1] = vqrshrun_n_s16(odd, 4);
3619 vst2_u8(out + i*2, o);
3620#endif
3621
3622 // "previous" value for next iter
3623 t1 = 3*in_near[i+7] + in_far[i+7];
3624 }
3625
3626 t0 = t1;
3627 t1 = 3*in_near[i] + in_far[i];
3628 out[i*2] = stbi__div16(3*t1 + t0 + 8);
3629
3630 for (++i; i < w; ++i) {
3631 t0 = t1;
3632 t1 = 3*in_near[i]+in_far[i];
3633 out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
3634 out[i*2 ] = stbi__div16(3*t1 + t0 + 8);
3635 }
3636 out[w*2-1] = stbi__div4(t1+2);
3637
3638 STBI_NOTUSED(hs);
3639
3640 return out;
3641}
3642#endif
3643
3644static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
3645{
3646 // resample with nearest-neighbor
3647 int i,j;
3648 STBI_NOTUSED(in_far);
3649 for (i=0; i < w; ++i)
3650 for (j=0; j < hs; ++j)
3651 out[i*hs+j] = in_near[i];
3652 return out;
3653}
3654
3655// this is a reduced-precision calculation of YCbCr-to-RGB introduced
3656// to make sure the code produces the same results in both SIMD and scalar
3657#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8)
3658static 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)
3659{
3660 int i;
3661 for (i=0; i < count; ++i) {
3662 int y_fixed = (y[i] << 20) + (1<<19); // rounding
3663 int r,g,b;
3664 int cr = pcr[i] - 128;
3665 int cb = pcb[i] - 128;
3666 r = y_fixed + cr* stbi__float2fixed(1.40200f);
3667 g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);
3668 b = y_fixed + cb* stbi__float2fixed(1.77200f);
3669 r >>= 20;
3670 g >>= 20;
3671 b >>= 20;
3672 if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
3673 if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
3674 if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
3675 out[0] = (stbi_uc)r;
3676 out[1] = (stbi_uc)g;
3677 out[2] = (stbi_uc)b;
3678 out[3] = 255;
3679 out += step;
3680 }
3681}
3682
3683#if defined(STBI_SSE2) || defined(STBI_NEON)
3684static 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)
3685{
3686 int i = 0;
3687
3688#ifdef STBI_SSE2
3689 // step == 3 is pretty ugly on the final interleave, and i'm not convinced
3690 // it's useful in practice (you wouldn't use it for textures, for example).
3691 // so just accelerate step == 4 case.
3692 if (step == 4) {
3693 // this is a fairly straightforward implementation and not super-optimized.
3694 __m128i signflip = _mm_set1_epi8(-0x80);
3695 __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f));
3696 __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f));
3697 __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f));
3698 __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f));
3699 __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128);
3700 __m128i xw = _mm_set1_epi16(255); // alpha channel
3701
3702 for (; i+7 < count; i += 8) {
3703 // load
3704 __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i));
3705 __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i));
3706 __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i));
3707 __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128
3708 __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128
3709
3710 // unpack to short (and left-shift cr, cb by 8)
3711 __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes);
3712 __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased);
3713 __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased);
3714
3715 // color transform
3716 __m128i yws = _mm_srli_epi16(yw, 4);
3717 __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw);
3718 __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw);
3719 __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1);
3720 __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1);
3721 __m128i rws = _mm_add_epi16(cr0, yws);
3722 __m128i gwt = _mm_add_epi16(cb0, yws);
3723 __m128i bws = _mm_add_epi16(yws, cb1);
3724 __m128i gws = _mm_add_epi16(gwt, cr1);
3725
3726 // descale
3727 __m128i rw = _mm_srai_epi16(rws, 4);
3728 __m128i bw = _mm_srai_epi16(bws, 4);
3729 __m128i gw = _mm_srai_epi16(gws, 4);
3730
3731 // back to byte, set up for transpose
3732 __m128i brb = _mm_packus_epi16(rw, bw);
3733 __m128i gxb = _mm_packus_epi16(gw, xw);
3734
3735 // transpose to interleave channels
3736 __m128i t0 = _mm_unpacklo_epi8(brb, gxb);
3737 __m128i t1 = _mm_unpackhi_epi8(brb, gxb);
3738 __m128i o0 = _mm_unpacklo_epi16(t0, t1);
3739 __m128i o1 = _mm_unpackhi_epi16(t0, t1);
3740
3741 // store
3742 _mm_storeu_si128((__m128i *) (out + 0), o0);
3743 _mm_storeu_si128((__m128i *) (out + 16), o1);
3744 out += 32;
3745 }
3746 }
3747#endif
3748
3749#ifdef STBI_NEON
3750 // in this version, step=3 support would be easy to add. but is there demand?
3751 if (step == 4) {
3752 // this is a fairly straightforward implementation and not super-optimized.
3753 uint8x8_t signflip = vdup_n_u8(0x80);
3754 int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f));
3755 int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f));
3756 int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f));
3757 int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f));
3758
3759 for (; i+7 < count; i += 8) {
3760 // load
3761 uint8x8_t y_bytes = vld1_u8(y + i);
3762 uint8x8_t cr_bytes = vld1_u8(pcr + i);
3763 uint8x8_t cb_bytes = vld1_u8(pcb + i);
3764 int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip));
3765 int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip));
3766
3767 // expand to s16
3768 int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4));
3769 int16x8_t crw = vshll_n_s8(cr_biased, 7);
3770 int16x8_t cbw = vshll_n_s8(cb_biased, 7);
3771
3772 // color transform
3773 int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0);
3774 int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0);
3775 int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1);
3776 int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1);
3777 int16x8_t rws = vaddq_s16(yws, cr0);
3778 int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1);
3779 int16x8_t bws = vaddq_s16(yws, cb1);
3780
3781 // undo scaling, round, convert to byte
3782 uint8x8x4_t o;
3783 o.val[0] = vqrshrun_n_s16(rws, 4);
3784 o.val[1] = vqrshrun_n_s16(gws, 4);
3785 o.val[2] = vqrshrun_n_s16(bws, 4);
3786 o.val[3] = vdup_n_u8(255);
3787
3788 // store, interleaving r/g/b/a
3789 vst4_u8(out, o);
3790 out += 8*4;
3791 }
3792 }
3793#endif
3794
3795 for (; i < count; ++i) {
3796 int y_fixed = (y[i] << 20) + (1<<19); // rounding
3797 int r,g,b;
3798 int cr = pcr[i] - 128;
3799 int cb = pcb[i] - 128;
3800 r = y_fixed + cr* stbi__float2fixed(1.40200f);
3801 g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);
3802 b = y_fixed + cb* stbi__float2fixed(1.77200f);
3803 r >>= 20;
3804 g >>= 20;
3805 b >>= 20;
3806 if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
3807 if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
3808 if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
3809 out[0] = (stbi_uc)r;
3810 out[1] = (stbi_uc)g;
3811 out[2] = (stbi_uc)b;
3812 out[3] = 255;
3813 out += step;
3814 }
3815}
3816#endif
3817
3818// set up the kernels
3819static void stbi__setup_jpeg(stbi__jpeg *j)
3820{
3821 j->idct_block_kernel = stbi__idct_block;
3822 j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row;
3823 j->resample_row_hv_2_kernel = stbi__resample_row_hv_2;
3824
3825#ifdef STBI_SSE2
3826 if (stbi__sse2_available()) {
3827 j->idct_block_kernel = stbi__idct_simd;
3828 j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
3829 j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
3830 }
3831#endif
3832
3833#ifdef STBI_NEON
3834 j->idct_block_kernel = stbi__idct_simd;
3835 j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
3836 j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
3837#endif
3838}
3839
3840// clean up the temporary component buffers
3841static void stbi__cleanup_jpeg(stbi__jpeg *j)
3842{
3843 stbi__free_jpeg_components(j, j->s->img_n, 0);
3844}
3845
3846typedef struct
3847{
3848 resample_row_func resample;
3849 stbi_uc *line0,*line1;
3850 int hs,vs; // expansion factor in each axis
3851 int w_lores; // horizontal pixels pre-expansion
3852 int ystep; // how far through vertical expansion we are
3853 int ypos; // which pre-expansion row we're on
3854} stbi__resample;
3855
3856// fast 0..255 * 0..255 => 0..255 rounded multiplication
3857static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y)
3858{
3859 unsigned int t = x*y + 128;
3860 return (stbi_uc) ((t + (t >>8)) >> 8);
3861}
3862
3863static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)
3864{
3865 int n, decode_n, is_rgb;
3866 z->s->img_n = 0; // make stbi__cleanup_jpeg safe
3867
3868 // validate req_comp
3869 if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
3870
3871 // load a jpeg image from whichever source, but leave in YCbCr format
3872 if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; }
3873
3874 // determine actual number of components to generate
3875 n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1;
3876
3877 is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif));
3878
3879 if (z->s->img_n == 3 && n < 3 && !is_rgb)
3880 decode_n = 1;
3881 else
3882 decode_n = z->s->img_n;
3883
3884 // nothing to do if no components requested; check this now to avoid
3885 // accessing uninitialized coutput[0] later
3886 if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; }
3887
3888 // resample and color-convert
3889 {
3890 int k;
3891 unsigned int i,j;
3892 stbi_uc *output;
3893 stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL };
3894
3895 stbi__resample res_comp[4];
3896
3897 for (k=0; k < decode_n; ++k) {
3898 stbi__resample *r = &res_comp[k];
3899
3900 // allocate line buffer big enough for upsampling off the edges
3901 // with upsample factor of 4
3902 z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3);
3903 if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
3904
3905 r->hs = z->img_h_max / z->img_comp[k].h;
3906 r->vs = z->img_v_max / z->img_comp[k].v;
3907 r->ystep = r->vs >> 1;
3908 r->w_lores = (z->s->img_x + r->hs-1) / r->hs;
3909 r->ypos = 0;
3910 r->line0 = r->line1 = z->img_comp[k].data;
3911
3912 if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1;
3913 else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2;
3914 else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2;
3915 else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel;
3916 else r->resample = stbi__resample_row_generic;
3917 }
3918
3919 // can't error after this so, this is safe
3920 output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1);
3921 if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
3922
3923 // now go ahead and resample
3924 for (j=0; j < z->s->img_y; ++j) {
3925 stbi_uc *out = output + n * z->s->img_x * j;
3926 for (k=0; k < decode_n; ++k) {
3927 stbi__resample *r = &res_comp[k];
3928 int y_bot = r->ystep >= (r->vs >> 1);
3929 coutput[k] = r->resample(z->img_comp[k].linebuf,
3930 y_bot ? r->line1 : r->line0,
3931 y_bot ? r->line0 : r->line1,
3932 r->w_lores, r->hs);
3933 if (++r->ystep >= r->vs) {
3934 r->ystep = 0;
3935 r->line0 = r->line1;
3936 if (++r->ypos < z->img_comp[k].y)
3937 r->line1 += z->img_comp[k].w2;
3938 }
3939 }
3940 if (n >= 3) {
3941 stbi_uc *y = coutput[0];
3942 if (z->s->img_n == 3) {
3943 if (is_rgb) {
3944 for (i=0; i < z->s->img_x; ++i) {
3945 out[0] = y[i];
3946 out[1] = coutput[1][i];
3947 out[2] = coutput[2][i];
3948 out[3] = 255;
3949 out += n;
3950 }
3951 } else {
3952 z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
3953 }
3954 } else if (z->s->img_n == 4) {
3955 if (z->app14_color_transform == 0) { // CMYK
3956 for (i=0; i < z->s->img_x; ++i) {
3957 stbi_uc m = coutput[3][i];
3958 out[0] = stbi__blinn_8x8(coutput[0][i], m);
3959 out[1] = stbi__blinn_8x8(coutput[1][i], m);
3960 out[2] = stbi__blinn_8x8(coutput[2][i], m);
3961 out[3] = 255;
3962 out += n;
3963 }
3964 } else if (z->app14_color_transform == 2) { // YCCK
3965 z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
3966 for (i=0; i < z->s->img_x; ++i) {
3967 stbi_uc m = coutput[3][i];
3968 out[0] = stbi__blinn_8x8(255 - out[0], m);
3969 out[1] = stbi__blinn_8x8(255 - out[1], m);
3970 out[2] = stbi__blinn_8x8(255 - out[2], m);
3971 out += n;
3972 }
3973 } else { // YCbCr + alpha? Ignore the fourth channel for now
3974 z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
3975 }
3976 } else
3977 for (i=0; i < z->s->img_x; ++i) {
3978 out[0] = out[1] = out[2] = y[i];
3979 out[3] = 255; // not used if n==3
3980 out += n;
3981 }
3982 } else {
3983 if (is_rgb) {
3984 if (n == 1)
3985 for (i=0; i < z->s->img_x; ++i)
3986 *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);
3987 else {
3988 for (i=0; i < z->s->img_x; ++i, out += 2) {
3989 out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);
3990 out[1] = 255;
3991 }
3992 }
3993 } else if (z->s->img_n == 4 && z->app14_color_transform == 0) {
3994 for (i=0; i < z->s->img_x; ++i) {
3995 stbi_uc m = coutput[3][i];
3996 stbi_uc r = stbi__blinn_8x8(coutput[0][i], m);
3997 stbi_uc g = stbi__blinn_8x8(coutput[1][i], m);
3998 stbi_uc b = stbi__blinn_8x8(coutput[2][i], m);
3999 out[0] = stbi__compute_y(r, g, b);
4000 out[1] = 255;
4001 out += n;
4002 }
4003 } else if (z->s->img_n == 4 && z->app14_color_transform == 2) {
4004 for (i=0; i < z->s->img_x; ++i) {
4005 out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]);
4006 out[1] = 255;
4007 out += n;
4008 }
4009 } else {
4010 stbi_uc *y = coutput[0];
4011 if (n == 1)
4012 for (i=0; i < z->s->img_x; ++i) out[i] = y[i];
4013 else
4014 for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; }
4015 }
4016 }
4017 }
4018 stbi__cleanup_jpeg(z);
4019 *out_x = z->s->img_x;
4020 *out_y = z->s->img_y;
4021 if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output
4022 return output;
4023 }
4024}
4025
4026static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
4027{
4028 unsigned char* result;
4029 stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));
4030 if (!j) return stbi__errpuc("outofmem", "Out of memory");
4031 memset(j, 0, sizeof(stbi__jpeg));
4032 STBI_NOTUSED(ri);
4033 j->s = s;
4034 stbi__setup_jpeg(j);
4035 result = load_jpeg_image(j, x,y,comp,req_comp);
4036 STBI_FREE(j);
4037 return result;
4038}
4039
4040static int stbi__jpeg_test(stbi__context *s)
4041{
4042 int r;
4043 stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg));
4044 if (!j) return stbi__err("outofmem", "Out of memory");
4045 memset(j, 0, sizeof(stbi__jpeg));
4046 j->s = s;
4047 stbi__setup_jpeg(j);
4048 r = stbi__decode_jpeg_header(j, STBI__SCAN_type);
4049 stbi__rewind(s);
4050 STBI_FREE(j);
4051 return r;
4052}
4053
4054static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp)
4055{
4056 if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) {
4057 stbi__rewind( j->s );
4058 return 0;
4059 }
4060 if (x) *x = j->s->img_x;
4061 if (y) *y = j->s->img_y;
4062 if (comp) *comp = j->s->img_n >= 3 ? 3 : 1;
4063 return 1;
4064}
4065
4066static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
4067{
4068 int result;
4069 stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));
4070 if (!j) return stbi__err("outofmem", "Out of memory");
4071 memset(j, 0, sizeof(stbi__jpeg));
4072 j->s = s;
4073 result = stbi__jpeg_info_raw(j, x, y, comp);
4074 STBI_FREE(j);
4075 return result;
4076}
4077#endif
4078
4079// public domain zlib decode v0.2 Sean Barrett 2006-11-18
4080// simple implementation
4081// - all input must be provided in an upfront buffer
4082// - all output is written to a single output buffer (can malloc/realloc)
4083// performance
4084// - fast huffman
4085
4086#ifndef STBI_NO_ZLIB
4087
4088// fast-way is faster to check than jpeg huffman, but slow way is slower
4089#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables
4090#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1)
4091#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet
4092
4093// zlib-style huffman encoding
4094// (jpegs packs from left, zlib from right, so can't share code)
4095typedef struct
4096{
4097 stbi__uint16 fast[1 << STBI__ZFAST_BITS];
4098 stbi__uint16 firstcode[16];
4099 int maxcode[17];
4100 stbi__uint16 firstsymbol[16];
4101 stbi_uc size[STBI__ZNSYMS];
4102 stbi__uint16 value[STBI__ZNSYMS];
4103} stbi__zhuffman;
4104
4105stbi_inline static int stbi__bitreverse16(int n)
4106{
4107 n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1);
4108 n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2);
4109 n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4);
4110 n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8);
4111 return n;
4112}
4113
4114stbi_inline static int stbi__bit_reverse(int v, int bits)
4115{
4116 STBI_ASSERT(bits <= 16);
4117 // to bit reverse n bits, reverse 16 and shift
4118 // e.g. 11 bits, bit reverse and shift away 5
4119 return stbi__bitreverse16(v) >> (16-bits);
4120}
4121
4122static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num)
4123{
4124 int i,k=0;
4125 int code, next_code[16], sizes[17];
4126
4127 // DEFLATE spec for generating codes
4128 memset(sizes, 0, sizeof(sizes));
4129 memset(z->fast, 0, sizeof(z->fast));
4130 for (i=0; i < num; ++i)
4131 ++sizes[sizelist[i]];
4132 sizes[0] = 0;
4133 for (i=1; i < 16; ++i)
4134 if (sizes[i] > (1 << i))
4135 return stbi__err("bad sizes", "Corrupt PNG");
4136 code = 0;
4137 for (i=1; i < 16; ++i) {
4138 next_code[i] = code;
4139 z->firstcode[i] = (stbi__uint16) code;
4140 z->firstsymbol[i] = (stbi__uint16) k;
4141 code = (code + sizes[i]);
4142 if (sizes[i])
4143 if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG");
4144 z->maxcode[i] = code << (16-i); // preshift for inner loop
4145 code <<= 1;
4146 k += sizes[i];
4147 }
4148 z->maxcode[16] = 0x10000; // sentinel
4149 for (i=0; i < num; ++i) {
4150 int s = sizelist[i];
4151 if (s) {
4152 int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s];
4153 stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i);
4154 z->size [c] = (stbi_uc ) s;
4155 z->value[c] = (stbi__uint16) i;
4156 if (s <= STBI__ZFAST_BITS) {
4157 int j = stbi__bit_reverse(next_code[s],s);
4158 while (j < (1 << STBI__ZFAST_BITS)) {
4159 z->fast[j] = fastv;
4160 j += (1 << s);
4161 }
4162 }
4163 ++next_code[s];
4164 }
4165 }
4166 return 1;
4167}
4168
4169// zlib-from-memory implementation for PNG reading
4170// because PNG allows splitting the zlib stream arbitrarily,
4171// and it's annoying structurally to have PNG call ZLIB call PNG,
4172// we require PNG read all the IDATs and combine them into a single
4173// memory buffer
4174
4175typedef struct
4176{
4177 stbi_uc *zbuffer, *zbuffer_end;
4178 int num_bits;
4179 stbi__uint32 code_buffer;
4180
4181 char *zout;
4182 char *zout_start;
4183 char *zout_end;
4184 int z_expandable;
4185
4186 stbi__zhuffman z_length, z_distance;
4187} stbi__zbuf;
4188
4189stbi_inline static int stbi__zeof(stbi__zbuf *z)
4190{
4191 return (z->zbuffer >= z->zbuffer_end);
4192}
4193
4194stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z)
4195{
4196 return stbi__zeof(z) ? 0 : *z->zbuffer++;
4197}
4198
4199static void stbi__fill_bits(stbi__zbuf *z)
4200{
4201 do {
4202 if (z->code_buffer >= (1U << z->num_bits)) {
4203 z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */
4204 return;
4205 }
4206 z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits;
4207 z->num_bits += 8;
4208 } while (z->num_bits <= 24);
4209}
4210
4211stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n)
4212{
4213 unsigned int k;
4214 if (z->num_bits < n) stbi__fill_bits(z);
4215 k = z->code_buffer & ((1 << n) - 1);
4216 z->code_buffer >>= n;
4217 z->num_bits -= n;
4218 return k;
4219}
4220
4221static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z)
4222{
4223 int b,s,k;
4224 // not resolved by fast table, so compute it the slow way
4225 // use jpeg approach, which requires MSbits at top
4226 k = stbi__bit_reverse(a->code_buffer, 16);
4227 for (s=STBI__ZFAST_BITS+1; ; ++s)
4228 if (k < z->maxcode[s])
4229 break;
4230 if (s >= 16) return -1; // invalid code!
4231 // code size is s, so:
4232 b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s];
4233 if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere!
4234 if (z->size[b] != s) return -1; // was originally an assert, but report failure instead.
4235 a->code_buffer >>= s;
4236 a->num_bits -= s;
4237 return z->value[b];
4238}
4239
4240stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
4241{
4242 int b,s;
4243 if (a->num_bits < 16) {
4244 if (stbi__zeof(a)) {
4245 return -1; /* report error for unexpected end of data. */
4246 }
4247 stbi__fill_bits(a);
4248 }
4249 b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
4250 if (b) {
4251 s = b >> 9;
4252 a->code_buffer >>= s;
4253 a->num_bits -= s;
4254 return b & 511;
4255 }
4256 return stbi__zhuffman_decode_slowpath(a, z);
4257}
4258
4259static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes
4260{
4261 char *q;
4262 unsigned int cur, limit, old_limit;
4263 z->zout = zout;
4264 if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG");
4265 cur = (unsigned int) (z->zout - z->zout_start);
4266 limit = old_limit = (unsigned) (z->zout_end - z->zout_start);
4267 if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory");
4268 while (cur + n > limit) {
4269 if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory");
4270 limit *= 2;
4271 }
4272 q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit);
4273 STBI_NOTUSED(old_limit);
4274 if (q == NULL) return stbi__err("outofmem", "Out of memory");
4275 z->zout_start = q;
4276 z->zout = q + cur;
4277 z->zout_end = q + limit;
4278 return 1;
4279}
4280
4281static const int stbi__zlength_base[31] = {
4282 3,4,5,6,7,8,9,10,11,13,
4283 15,17,19,23,27,31,35,43,51,59,
4284 67,83,99,115,131,163,195,227,258,0,0 };
4285
4286static const int stbi__zlength_extra[31]=
4287{ 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 };
4288
4289static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,
4290257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0};
4291
4292static const int stbi__zdist_extra[32] =
4293{ 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};
4294
4295static int stbi__parse_huffman_block(stbi__zbuf *a)
4296{
4297 char *zout = a->zout;
4298 for(;;) {
4299 int z = stbi__zhuffman_decode(a, &a->z_length);
4300 if (z < 256) {
4301 if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes
4302 if (zout >= a->zout_end) {
4303 if (!stbi__zexpand(a, zout, 1)) return 0;
4304 zout = a->zout;
4305 }
4306 *zout++ = (char) z;
4307 } else {
4308 stbi_uc *p;
4309 int len,dist;
4310 if (z == 256) {
4311 a->zout = zout;
4312 return 1;
4313 }
4314 if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data
4315 z -= 257;
4316 len = stbi__zlength_base[z];
4317 if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]);
4318 z = stbi__zhuffman_decode(a, &a->z_distance);
4319 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
4320 dist = stbi__zdist_base[z];
4321 if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
4322 if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG");
4323 if (zout + len > a->zout_end) {
4324 if (!stbi__zexpand(a, zout, len)) return 0;
4325 zout = a->zout;
4326 }
4327 p = (stbi_uc *) (zout - dist);
4328 if (dist == 1) { // run of one byte; common in images.
4329 stbi_uc v = *p;
4330 if (len) { do *zout++ = v; while (--len); }
4331 } else {
4332 if (len) { do *zout++ = *p++; while (--len); }
4333 }
4334 }
4335 }
4336}
4337
4338static int stbi__compute_huffman_codes(stbi__zbuf *a)
4339{
4340 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 };
4341 stbi__zhuffman z_codelength;
4342 stbi_uc lencodes[286+32+137];//padding for maximum single op
4343 stbi_uc codelength_sizes[19];
4344 int i,n;
4345
4346 int hlit = stbi__zreceive(a,5) + 257;
4347 int hdist = stbi__zreceive(a,5) + 1;
4348 int hclen = stbi__zreceive(a,4) + 4;
4349 int ntot = hlit + hdist;
4350
4351 memset(codelength_sizes, 0, sizeof(codelength_sizes));
4352 for (i=0; i < hclen; ++i) {
4353 int s = stbi__zreceive(a,3);
4354 codelength_sizes[length_dezigzag[i]] = (stbi_uc) s;
4355 }
4356 if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0;
4357
4358 n = 0;
4359 while (n < ntot) {
4360 int c = stbi__zhuffman_decode(a, &z_codelength);
4361 if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG");
4362 if (c < 16)
4363 lencodes[n++] = (stbi_uc) c;
4364 else {
4365 stbi_uc fill = 0;
4366 if (c == 16) {
4367 c = stbi__zreceive(a,2)+3;
4368 if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG");
4369 fill = lencodes[n-1];
4370 } else if (c == 17) {
4371 c = stbi__zreceive(a,3)+3;
4372 } else if (c == 18) {
4373 c = stbi__zreceive(a,7)+11;
4374 } else {
4375 return stbi__err("bad codelengths", "Corrupt PNG");
4376 }
4377 if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG");
4378 memset(lencodes+n, fill, c);
4379 n += c;
4380 }
4381 }
4382 if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG");
4383 if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0;
4384 if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0;
4385 return 1;
4386}
4387
4388static int stbi__parse_uncompressed_block(stbi__zbuf *a)
4389{
4390 stbi_uc header[4];
4391 int len,nlen,k;
4392 if (a->num_bits & 7)
4393 stbi__zreceive(a, a->num_bits & 7); // discard
4394 // drain the bit-packed data into header
4395 k = 0;
4396 while (a->num_bits > 0) {
4397 header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check
4398 a->code_buffer >>= 8;
4399 a->num_bits -= 8;
4400 }
4401 if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG");
4402 // now fill header the normal way
4403 while (k < 4)
4404 header[k++] = stbi__zget8(a);
4405 len = header[1] * 256 + header[0];
4406 nlen = header[3] * 256 + header[2];
4407 if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG");
4408 if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG");
4409 if (a->zout + len > a->zout_end)
4410 if (!stbi__zexpand(a, a->zout, len)) return 0;
4411 memcpy(a->zout, a->zbuffer, len);
4412 a->zbuffer += len;
4413 a->zout += len;
4414 return 1;
4415}
4416
4417static int stbi__parse_zlib_header(stbi__zbuf *a)
4418{
4419 int cmf = stbi__zget8(a);
4420 int cm = cmf & 15;
4421 /* int cinfo = cmf >> 4; */
4422 int flg = stbi__zget8(a);
4423 if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec
4424 if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec
4425 if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png
4426 if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png
4427 // window = 1 << (8 + cinfo)... but who cares, we fully buffer output
4428 return 1;
4429}
4430
4431static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] =
4432{
4433 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,
4434 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,
4435 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,
4436 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,
4437 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,
4438 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,
4439 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,
4440 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,
4441 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
4442};
4443static const stbi_uc stbi__zdefault_distance[32] =
4444{
4445 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
4446};
4447/*
4448Init algorithm:
4449{
4450 int i; // use <= to match clearly with spec
4451 for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8;
4452 for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9;
4453 for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7;
4454 for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8;
4455
4456 for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5;
4457}
4458*/
4459
4460static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
4461{
4462 int final, type;
4463 if (parse_header)
4464 if (!stbi__parse_zlib_header(a)) return 0;
4465 a->num_bits = 0;
4466 a->code_buffer = 0;
4467 do {
4468 final = stbi__zreceive(a,1);
4469 type = stbi__zreceive(a,2);
4470 if (type == 0) {
4471 if (!stbi__parse_uncompressed_block(a)) return 0;
4472 } else if (type == 3) {
4473 return 0;
4474 } else {
4475 if (type == 1) {
4476 // use fixed code lengths
4477 if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0;
4478 if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0;
4479 } else {
4480 if (!stbi__compute_huffman_codes(a)) return 0;
4481 }
4482 if (!stbi__parse_huffman_block(a)) return 0;
4483 }
4484 } while (!final);
4485 return 1;
4486}
4487
4488static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header)
4489{
4490 a->zout_start = obuf;
4491 a->zout = obuf;
4492 a->zout_end = obuf + olen;
4493 a->z_expandable = exp;
4494
4495 return stbi__parse_zlib(a, parse_header);
4496}
4497
4498STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen)
4499{
4500 stbi__zbuf a;
4501 char *p = (char *) stbi__malloc(initial_size);
4502 if (p == NULL) return NULL;
4503 a.zbuffer = (stbi_uc *) buffer;
4504 a.zbuffer_end = (stbi_uc *) buffer + len;
4505 if (stbi__do_zlib(&a, p, initial_size, 1, 1)) {
4506 if (outlen) *outlen = (int) (a.zout - a.zout_start);
4507 return a.zout_start;
4508 } else {
4509 STBI_FREE(a.zout_start);
4510 return NULL;
4511 }
4512}
4513
4514STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen)
4515{
4516 return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
4517}
4518
4519STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header)
4520{
4521 stbi__zbuf a;
4522 char *p = (char *) stbi__malloc(initial_size);
4523 if (p == NULL) return NULL;
4524 a.zbuffer = (stbi_uc *) buffer;
4525 a.zbuffer_end = (stbi_uc *) buffer + len;
4526 if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) {
4527 if (outlen) *outlen = (int) (a.zout - a.zout_start);
4528 return a.zout_start;
4529 } else {
4530 STBI_FREE(a.zout_start);
4531 return NULL;
4532 }
4533}
4534
4535STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen)
4536{
4537 stbi__zbuf a;
4538 a.zbuffer = (stbi_uc *) ibuffer;
4539 a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
4540 if (stbi__do_zlib(&a, obuffer, olen, 0, 1))
4541 return (int) (a.zout - a.zout_start);
4542 else
4543 return -1;
4544}
4545
4546STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen)
4547{
4548 stbi__zbuf a;
4549 char *p = (char *) stbi__malloc(16384);
4550 if (p == NULL) return NULL;
4551 a.zbuffer = (stbi_uc *) buffer;
4552 a.zbuffer_end = (stbi_uc *) buffer+len;
4553 if (stbi__do_zlib(&a, p, 16384, 1, 0)) {
4554 if (outlen) *outlen = (int) (a.zout - a.zout_start);
4555 return a.zout_start;
4556 } else {
4557 STBI_FREE(a.zout_start);
4558 return NULL;
4559 }
4560}
4561
4562STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen)
4563{
4564 stbi__zbuf a;
4565 a.zbuffer = (stbi_uc *) ibuffer;
4566 a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
4567 if (stbi__do_zlib(&a, obuffer, olen, 0, 0))
4568 return (int) (a.zout - a.zout_start);
4569 else
4570 return -1;
4571}
4572#endif
4573
4574// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18
4575// simple implementation
4576// - only 8-bit samples
4577// - no CRC checking
4578// - allocates lots of intermediate memory
4579// - avoids problem of streaming data between subsystems
4580// - avoids explicit window management
4581// performance
4582// - uses stb_zlib, a PD zlib implementation with fast huffman decoding
4583
4584#ifndef STBI_NO_PNG
4585typedef struct
4586{
4587 stbi__uint32 length;
4588 stbi__uint32 type;
4589} stbi__pngchunk;
4590
4591static stbi__pngchunk stbi__get_chunk_header(stbi__context *s)
4592{
4593 stbi__pngchunk c;
4594 c.length = stbi__get32be(s);
4595 c.type = stbi__get32be(s);
4596 return c;
4597}
4598
4599static int stbi__check_png_header(stbi__context *s)
4600{
4601 static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 };
4602 int i;
4603 for (i=0; i < 8; ++i)
4604 if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG");
4605 return 1;
4606}
4607
4608typedef struct
4609{
4610 stbi__context *s;
4611 stbi_uc *idata, *expanded, *out;
4612 int depth;
4613} stbi__png;
4614
4615
4616enum {
4617 STBI__F_none=0,
4618 STBI__F_sub=1,
4619 STBI__F_up=2,
4620 STBI__F_avg=3,
4621 STBI__F_paeth=4,
4622 // synthetic filters used for first scanline to avoid needing a dummy row of 0s
4623 STBI__F_avg_first,
4624 STBI__F_paeth_first
4625};
4626
4627static stbi_uc first_row_filter[5] =
4628{
4629 STBI__F_none,
4630 STBI__F_sub,
4631 STBI__F_none,
4632 STBI__F_avg_first,
4633 STBI__F_paeth_first
4634};
4635
4636static int stbi__paeth(int a, int b, int c)
4637{
4638 int p = a + b - c;
4639 int pa = abs(p-a);
4640 int pb = abs(p-b);
4641 int pc = abs(p-c);
4642 if (pa <= pb && pa <= pc) return a;
4643 if (pb <= pc) return b;
4644 return c;
4645}
4646
4647static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
4648
4649// create the png data from post-deflated data
4650static 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)
4651{
4652 int bytes = (depth == 16? 2 : 1);
4653 stbi__context *s = a->s;
4654 stbi__uint32 i,j,stride = x*out_n*bytes;
4655 stbi__uint32 img_len, img_width_bytes;
4656 int k;
4657 int img_n = s->img_n; // copy it into a local for later
4658
4659 int output_bytes = out_n*bytes;
4660 int filter_bytes = img_n*bytes;
4661 int width = x;
4662
4663 STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);
4664 a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into
4665 if (!a->out) return stbi__err("outofmem", "Out of memory");
4666
4667 if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG");
4668 img_width_bytes = (((img_n * x * depth) + 7) >> 3);
4669 img_len = (img_width_bytes + 1) * y;
4670
4671 // we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
4672 // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros),
4673 // so just check for raw_len < img_len always.
4674 if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
4675
4676 for (j=0; j < y; ++j) {
4677 stbi_uc *cur = a->out + stride*j;
4678 stbi_uc *prior;
4679 int filter = *raw++;
4680
4681 if (filter > 4)
4682 return stbi__err("invalid filter","Corrupt PNG");
4683
4684 if (depth < 8) {
4685 if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG");
4686 cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
4687 filter_bytes = 1;
4688 width = img_width_bytes;
4689 }
4690 prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above
4691
4692 // if first row, use special filter that doesn't sample previous row
4693 if (j == 0) filter = first_row_filter[filter];
4694
4695 // handle first byte explicitly
4696 for (k=0; k < filter_bytes; ++k) {
4697 switch (filter) {
4698 case STBI__F_none : cur[k] = raw[k]; break;
4699 case STBI__F_sub : cur[k] = raw[k]; break;
4700 case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
4701 case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
4702 case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;
4703 case STBI__F_avg_first : cur[k] = raw[k]; break;
4704 case STBI__F_paeth_first: cur[k] = raw[k]; break;
4705 }
4706 }
4707
4708 if (depth == 8) {
4709 if (img_n != out_n)
4710 cur[img_n] = 255; // first pixel
4711 raw += img_n;
4712 cur += out_n;
4713 prior += out_n;
4714 } else if (depth == 16) {
4715 if (img_n != out_n) {
4716 cur[filter_bytes] = 255; // first pixel top byte
4717 cur[filter_bytes+1] = 255; // first pixel bottom byte
4718 }
4719 raw += filter_bytes;
4720 cur += output_bytes;
4721 prior += output_bytes;
4722 } else {
4723 raw += 1;
4724 cur += 1;
4725 prior += 1;
4726 }
4727
4728 // this is a little gross, so that we don't switch per-pixel or per-component
4729 if (depth < 8 || img_n == out_n) {
4730 int nk = (width - 1)*filter_bytes;
4731 #define STBI__CASE(f) \
4732 case f: \
4733 for (k=0; k < nk; ++k)
4734 switch (filter) {
4735 // "none" filter turns into a memcpy here; make that explicit.
4736 case STBI__F_none: memcpy(cur, raw, nk); break;
4737 STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;
4738 STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
4739 STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;
4740 STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;
4741 STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;
4742 STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break;
4743 }
4744 #undef STBI__CASE
4745 raw += nk;
4746 } else {
4747 STBI_ASSERT(img_n+1 == out_n);
4748 #define STBI__CASE(f) \
4749 case f: \
4750 for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
4751 for (k=0; k < filter_bytes; ++k)
4752 switch (filter) {
4753 STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break;
4754 STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;
4755 STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
4756 STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;
4757 STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;
4758 STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;
4759 STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break;
4760 }
4761 #undef STBI__CASE
4762
4763 // the loop above sets the high byte of the pixels' alpha, but for
4764 // 16 bit png files we also need the low byte set. we'll do that here.
4765 if (depth == 16) {
4766 cur = a->out + stride*j; // start at the beginning of the row again
4767 for (i=0; i < x; ++i,cur+=output_bytes) {
4768 cur[filter_bytes+1] = 255;
4769 }
4770 }
4771 }
4772 }
4773
4774 // we make a separate pass to expand bits to pixels; for performance,
4775 // this could run two scanlines behind the above code, so it won't
4776 // intefere with filtering but will still be in the cache.
4777 if (depth < 8) {
4778 for (j=0; j < y; ++j) {
4779 stbi_uc *cur = a->out + stride*j;
4780 stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes;
4781 // 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
4782 // 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
4783 stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
4784
4785 // note that the final byte might overshoot and write more data than desired.
4786 // we can allocate enough data that this never writes out of memory, but it
4787 // could also overwrite the next scanline. can it overwrite non-empty data
4788 // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.
4789 // so we need to explicitly clamp the final ones
4790
4791 if (depth == 4) {
4792 for (k=x*img_n; k >= 2; k-=2, ++in) {
4793 *cur++ = scale * ((*in >> 4) );
4794 *cur++ = scale * ((*in ) & 0x0f);
4795 }
4796 if (k > 0) *cur++ = scale * ((*in >> 4) );
4797 } else if (depth == 2) {
4798 for (k=x*img_n; k >= 4; k-=4, ++in) {
4799 *cur++ = scale * ((*in >> 6) );
4800 *cur++ = scale * ((*in >> 4) & 0x03);
4801 *cur++ = scale * ((*in >> 2) & 0x03);
4802 *cur++ = scale * ((*in ) & 0x03);
4803 }
4804 if (k > 0) *cur++ = scale * ((*in >> 6) );
4805 if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
4806 if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
4807 } else if (depth == 1) {
4808 for (k=x*img_n; k >= 8; k-=8, ++in) {
4809 *cur++ = scale * ((*in >> 7) );
4810 *cur++ = scale * ((*in >> 6) & 0x01);
4811 *cur++ = scale * ((*in >> 5) & 0x01);
4812 *cur++ = scale * ((*in >> 4) & 0x01);
4813 *cur++ = scale * ((*in >> 3) & 0x01);
4814 *cur++ = scale * ((*in >> 2) & 0x01);
4815 *cur++ = scale * ((*in >> 1) & 0x01);
4816 *cur++ = scale * ((*in ) & 0x01);
4817 }
4818 if (k > 0) *cur++ = scale * ((*in >> 7) );
4819 if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
4820 if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
4821 if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
4822 if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
4823 if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
4824 if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
4825 }
4826 if (img_n != out_n) {
4827 int q;
4828 // insert alpha = 255
4829 cur = a->out + stride*j;
4830 if (img_n == 1) {
4831 for (q=x-1; q >= 0; --q) {
4832 cur[q*2+1] = 255;
4833 cur[q*2+0] = cur[q];
4834 }
4835 } else {
4836 STBI_ASSERT(img_n == 3);
4837 for (q=x-1; q >= 0; --q) {
4838 cur[q*4+3] = 255;
4839 cur[q*4+2] = cur[q*3+2];
4840 cur[q*4+1] = cur[q*3+1];
4841 cur[q*4+0] = cur[q*3+0];
4842 }
4843 }
4844 }
4845 }
4846 } else if (depth == 16) {
4847 // force the image data from big-endian to platform-native.
4848 // this is done in a separate pass due to the decoding relying
4849 // on the data being untouched, but could probably be done
4850 // per-line during decode if care is taken.
4851 stbi_uc *cur = a->out;
4852 stbi__uint16 *cur16 = (stbi__uint16*)cur;
4853
4854 for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
4855 *cur16 = (cur[0] << 8) | cur[1];
4856 }
4857 }
4858
4859 return 1;
4860}
4861
4862static 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)
4863{
4864 int bytes = (depth == 16 ? 2 : 1);
4865 int out_bytes = out_n * bytes;
4866 stbi_uc *final;
4867 int p;
4868 if (!interlaced)
4869 return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color);
4870
4871 // de-interlacing
4872 final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0);
4873 if (!final) return stbi__err("outofmem", "Out of memory");
4874 for (p=0; p < 7; ++p) {
4875 int xorig[] = { 0,4,0,2,0,1,0 };
4876 int yorig[] = { 0,0,4,0,2,0,1 };
4877 int xspc[] = { 8,8,4,4,2,2,1 };
4878 int yspc[] = { 8,8,8,4,4,2,2 };
4879 int i,j,x,y;
4880 // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1
4881 x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p];
4882 y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p];
4883 if (x && y) {
4884 stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y;
4885 if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) {
4886 STBI_FREE(final);
4887 return 0;
4888 }
4889 for (j=0; j < y; ++j) {
4890 for (i=0; i < x; ++i) {
4891 int out_y = j*yspc[p]+yorig[p];
4892 int out_x = i*xspc[p]+xorig[p];
4893 memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes,
4894 a->out + (j*x+i)*out_bytes, out_bytes);
4895 }
4896 }
4897 STBI_FREE(a->out);
4898 image_data += img_len;
4899 image_data_len -= img_len;
4900 }
4901 }
4902 a->out = final;
4903
4904 return 1;
4905}
4906
4907static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n)
4908{
4909 stbi__context *s = z->s;
4910 stbi__uint32 i, pixel_count = s->img_x * s->img_y;
4911 stbi_uc *p = z->out;
4912
4913 // compute color-based transparency, assuming we've
4914 // already got 255 as the alpha value in the output
4915 STBI_ASSERT(out_n == 2 || out_n == 4);
4916
4917 if (out_n == 2) {
4918 for (i=0; i < pixel_count; ++i) {
4919 p[1] = (p[0] == tc[0] ? 0 : 255);
4920 p += 2;
4921 }
4922 } else {
4923 for (i=0; i < pixel_count; ++i) {
4924 if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
4925 p[3] = 0;
4926 p += 4;
4927 }
4928 }
4929 return 1;
4930}
4931
4932static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n)
4933{
4934 stbi__context *s = z->s;
4935 stbi__uint32 i, pixel_count = s->img_x * s->img_y;
4936 stbi__uint16 *p = (stbi__uint16*) z->out;
4937
4938 // compute color-based transparency, assuming we've
4939 // already got 65535 as the alpha value in the output
4940 STBI_ASSERT(out_n == 2 || out_n == 4);
4941
4942 if (out_n == 2) {
4943 for (i = 0; i < pixel_count; ++i) {
4944 p[1] = (p[0] == tc[0] ? 0 : 65535);
4945 p += 2;
4946 }
4947 } else {
4948 for (i = 0; i < pixel_count; ++i) {
4949 if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
4950 p[3] = 0;
4951 p += 4;
4952 }
4953 }
4954 return 1;
4955}
4956
4957static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n)
4958{
4959 stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;
4960 stbi_uc *p, *temp_out, *orig = a->out;
4961
4962 p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0);
4963 if (p == NULL) return stbi__err("outofmem", "Out of memory");
4964
4965 // between here and free(out) below, exitting would leak
4966 temp_out = p;
4967
4968 if (pal_img_n == 3) {
4969 for (i=0; i < pixel_count; ++i) {
4970 int n = orig[i]*4;
4971 p[0] = palette[n ];
4972 p[1] = palette[n+1];
4973 p[2] = palette[n+2];
4974 p += 3;
4975 }
4976 } else {
4977 for (i=0; i < pixel_count; ++i) {
4978 int n = orig[i]*4;
4979 p[0] = palette[n ];
4980 p[1] = palette[n+1];
4981 p[2] = palette[n+2];
4982 p[3] = palette[n+3];
4983 p += 4;
4984 }
4985 }
4986 STBI_FREE(a->out);
4987 a->out = temp_out;
4988
4989 STBI_NOTUSED(len);
4990
4991 return 1;
4992}
4993
4994static int stbi__unpremultiply_on_load_global = 0;
4995static int stbi__de_iphone_flag_global = 0;
4996
4997STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)
4998{
4999 stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply;
5000}
5001
5002STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
5003{
5004 stbi__de_iphone_flag_global = flag_true_if_should_convert;
5005}
5006
5007#ifndef STBI_THREAD_LOCAL
5008#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global
5009#define stbi__de_iphone_flag stbi__de_iphone_flag_global
5010#else
5011static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set;
5012static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set;
5013
5014STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply)
5015{
5016 stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply;
5017 stbi__unpremultiply_on_load_set = 1;
5018}
5019
5020STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert)
5021{
5022 stbi__de_iphone_flag_local = flag_true_if_should_convert;
5023 stbi__de_iphone_flag_set = 1;
5024}
5025
5026#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \
5027 ? stbi__unpremultiply_on_load_local \
5028 : stbi__unpremultiply_on_load_global)
5029#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \
5030 ? stbi__de_iphone_flag_local \
5031 : stbi__de_iphone_flag_global)
5032#endif // STBI_THREAD_LOCAL
5033
5034static void stbi__de_iphone(stbi__png *z)
5035{
5036 stbi__context *s = z->s;
5037 stbi__uint32 i, pixel_count = s->img_x * s->img_y;
5038 stbi_uc *p = z->out;
5039
5040 if (s->img_out_n == 3) { // convert bgr to rgb
5041 for (i=0; i < pixel_count; ++i) {
5042 stbi_uc t = p[0];
5043 p[0] = p[2];
5044 p[2] = t;
5045 p += 3;
5046 }
5047 } else {
5048 STBI_ASSERT(s->img_out_n == 4);
5049 if (stbi__unpremultiply_on_load) {
5050 // convert bgr to rgb and unpremultiply
5051 for (i=0; i < pixel_count; ++i) {
5052 stbi_uc a = p[3];
5053 stbi_uc t = p[0];
5054 if (a) {
5055 stbi_uc half = a / 2;
5056 p[0] = (p[2] * 255 + half) / a;
5057 p[1] = (p[1] * 255 + half) / a;
5058 p[2] = ( t * 255 + half) / a;
5059 } else {
5060 p[0] = p[2];
5061 p[2] = t;
5062 }
5063 p += 4;
5064 }
5065 } else {
5066 // convert bgr to rgb
5067 for (i=0; i < pixel_count; ++i) {
5068 stbi_uc t = p[0];
5069 p[0] = p[2];
5070 p[2] = t;
5071 p += 4;
5072 }
5073 }
5074 }
5075}
5076
5077#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d))
5078
5079static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
5080{
5081 stbi_uc palette[1024], pal_img_n=0;
5082 stbi_uc has_trans=0, tc[3]={0};
5083 stbi__uint16 tc16[3];
5084 stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;
5085 int first=1,k,interlace=0, color=0, is_iphone=0;
5086 stbi__context *s = z->s;
5087
5088 z->expanded = NULL;
5089 z->idata = NULL;
5090 z->out = NULL;
5091
5092 if (!stbi__check_png_header(s)) return 0;
5093
5094 if (scan == STBI__SCAN_type) return 1;
5095
5096 for (;;) {
5097 stbi__pngchunk c = stbi__get_chunk_header(s);
5098 switch (c.type) {
5099 case STBI__PNG_TYPE('C','g','B','I'):
5100 is_iphone = 1;
5101 stbi__skip(s, c.length);
5102 break;
5103 case STBI__PNG_TYPE('I','H','D','R'): {
5104 int comp,filter;
5105 if (!first) return stbi__err("multiple IHDR","Corrupt PNG");
5106 first = 0;
5107 if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG");
5108 s->img_x = stbi__get32be(s);
5109 s->img_y = stbi__get32be(s);
5110 if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
5111 if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
5112 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");
5113 color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG");
5114 if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG");
5115 if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG");
5116 comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG");
5117 filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG");
5118 interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG");
5119 if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG");
5120 if (!pal_img_n) {
5121 s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
5122 if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode");
5123 } else {
5124 // if paletted, then pal_n is our final components, and
5125 // img_n is # components to decompress/filter.
5126 s->img_n = 1;
5127 if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG");
5128 }
5129 // even with SCAN_header, have to scan to see if we have a tRNS
5130 break;
5131 }
5132
5133 case STBI__PNG_TYPE('P','L','T','E'): {
5134 if (first) return stbi__err("first not IHDR", "Corrupt PNG");
5135 if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG");
5136 pal_len = c.length / 3;
5137 if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG");
5138 for (i=0; i < pal_len; ++i) {
5139 palette[i*4+0] = stbi__get8(s);
5140 palette[i*4+1] = stbi__get8(s);
5141 palette[i*4+2] = stbi__get8(s);
5142 palette[i*4+3] = 255;
5143 }
5144 break;
5145 }
5146
5147 case STBI__PNG_TYPE('t','R','N','S'): {
5148 if (first) return stbi__err("first not IHDR", "Corrupt PNG");
5149 if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG");
5150 if (pal_img_n) {
5151 if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; }
5152 if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG");
5153 if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG");
5154 pal_img_n = 4;
5155 for (i=0; i < c.length; ++i)
5156 palette[i*4+3] = stbi__get8(s);
5157 } else {
5158 if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG");
5159 if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG");
5160 has_trans = 1;
5161 // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now.
5162 if (scan == STBI__SCAN_header) { ++s->img_n; return 1; }
5163 if (z->depth == 16) {
5164 for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
5165 } else {
5166 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
5167 }
5168 }
5169 break;
5170 }
5171
5172 case STBI__PNG_TYPE('I','D','A','T'): {
5173 if (first) return stbi__err("first not IHDR", "Corrupt PNG");
5174 if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG");
5175 if (scan == STBI__SCAN_header) {
5176 // header scan definitely stops at first IDAT
5177 if (pal_img_n)
5178 s->img_n = pal_img_n;
5179 return 1;
5180 }
5181 if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes");
5182 if ((int)(ioff + c.length) < (int)ioff) return 0;
5183 if (ioff + c.length > idata_limit) {
5184 stbi__uint32 idata_limit_old = idata_limit;
5185 stbi_uc *p;
5186 if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;
5187 while (ioff + c.length > idata_limit)
5188 idata_limit *= 2;
5189 STBI_NOTUSED(idata_limit_old);
5190 p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory");
5191 z->idata = p;
5192 }
5193 if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG");
5194 ioff += c.length;
5195 break;
5196 }
5197
5198 case STBI__PNG_TYPE('I','E','N','D'): {
5199 stbi__uint32 raw_len, bpl;
5200 if (first) return stbi__err("first not IHDR", "Corrupt PNG");
5201 if (scan != STBI__SCAN_load) return 1;
5202 if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG");
5203 // initial guess for decoded data size to avoid unnecessary reallocs
5204 bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component
5205 raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */;
5206 z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone);
5207 if (z->expanded == NULL) return 0; // zlib should set error
5208 STBI_FREE(z->idata); z->idata = NULL;
5209 if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)
5210 s->img_out_n = s->img_n+1;
5211 else
5212 s->img_out_n = s->img_n;
5213 if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0;
5214 if (has_trans) {
5215 if (z->depth == 16) {
5216 if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0;
5217 } else {
5218 if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0;
5219 }
5220 }
5221 if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2)
5222 stbi__de_iphone(z);
5223 if (pal_img_n) {
5224 // pal_img_n == 3 or 4
5225 s->img_n = pal_img_n; // record the actual colors we had
5226 s->img_out_n = pal_img_n;
5227 if (req_comp >= 3) s->img_out_n = req_comp;
5228 if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n))
5229 return 0;
5230 } else if (has_trans) {
5231 // non-paletted image with tRNS -> source image has (constant) alpha
5232 ++s->img_n;
5233 }
5234 STBI_FREE(z->expanded); z->expanded = NULL;
5235 // end of PNG chunk, read and skip CRC
5236 stbi__get32be(s);
5237 return 1;
5238 }
5239
5240 default:
5241 // if critical, fail
5242 if (first) return stbi__err("first not IHDR", "Corrupt PNG");
5243 if ((c.type & (1 << 29)) == 0) {
5244 #ifndef STBI_NO_FAILURE_STRINGS
5245 // not threadsafe
5246 static char invalid_chunk[] = "XXXX PNG chunk not known";
5247 invalid_chunk[0] = STBI__BYTECAST(c.type >> 24);
5248 invalid_chunk[1] = STBI__BYTECAST(c.type >> 16);
5249 invalid_chunk[2] = STBI__BYTECAST(c.type >> 8);
5250 invalid_chunk[3] = STBI__BYTECAST(c.type >> 0);
5251 #endif
5252 return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type");
5253 }
5254 stbi__skip(s, c.length);
5255 break;
5256 }
5257 // end of PNG chunk, read and skip CRC
5258 stbi__get32be(s);
5259 }
5260}
5261
5262static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri)
5263{
5264 void *result=NULL;
5265 if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
5266 if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {
5267 if (p->depth <= 8)
5268 ri->bits_per_channel = 8;
5269 else if (p->depth == 16)
5270 ri->bits_per_channel = 16;
5271 else
5272 return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth");
5273 result = p->out;
5274 p->out = NULL;
5275 if (req_comp && req_comp != p->s->img_out_n) {
5276 if (ri->bits_per_channel == 8)
5277 result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
5278 else
5279 result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
5280 p->s->img_out_n = req_comp;
5281 if (result == NULL) return result;
5282 }
5283 *x = p->s->img_x;
5284 *y = p->s->img_y;
5285 if (n) *n = p->s->img_n;
5286 }
5287 STBI_FREE(p->out); p->out = NULL;
5288 STBI_FREE(p->expanded); p->expanded = NULL;
5289 STBI_FREE(p->idata); p->idata = NULL;
5290
5291 return result;
5292}
5293
5294static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
5295{
5296 stbi__png p;
5297 p.s = s;
5298 return stbi__do_png(&p, x,y,comp,req_comp, ri);
5299}
5300
5301static int stbi__png_test(stbi__context *s)
5302{
5303 int r;
5304 r = stbi__check_png_header(s);
5305 stbi__rewind(s);
5306 return r;
5307}
5308
5309static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp)
5310{
5311 if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) {
5312 stbi__rewind( p->s );
5313 return 0;
5314 }
5315 if (x) *x = p->s->img_x;
5316 if (y) *y = p->s->img_y;
5317 if (comp) *comp = p->s->img_n;
5318 return 1;
5319}
5320
5321static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp)
5322{
5323 stbi__png p;
5324 p.s = s;
5325 return stbi__png_info_raw(&p, x, y, comp);
5326}
5327
5328static int stbi__png_is16(stbi__context *s)
5329{
5330 stbi__png p;
5331 p.s = s;
5332 if (!stbi__png_info_raw(&p, NULL, NULL, NULL))
5333 return 0;
5334 if (p.depth != 16) {
5335 stbi__rewind(p.s);
5336 return 0;
5337 }
5338 return 1;
5339}
5340#endif
5341
5342// Microsoft/Windows BMP image
5343
5344#ifndef STBI_NO_BMP
5345static int stbi__bmp_test_raw(stbi__context *s)
5346{
5347 int r;
5348 int sz;
5349 if (stbi__get8(s) != 'B') return 0;
5350 if (stbi__get8(s) != 'M') return 0;
5351 stbi__get32le(s); // discard filesize
5352 stbi__get16le(s); // discard reserved
5353 stbi__get16le(s); // discard reserved
5354 stbi__get32le(s); // discard data offset
5355 sz = stbi__get32le(s);
5356 r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124);
5357 return r;
5358}
5359
5360static int stbi__bmp_test(stbi__context *s)
5361{
5362 int r = stbi__bmp_test_raw(s);
5363 stbi__rewind(s);
5364 return r;
5365}
5366
5367
5368// returns 0..31 for the highest set bit
5369static int stbi__high_bit(unsigned int z)
5370{
5371 int n=0;
5372 if (z == 0) return -1;
5373 if (z >= 0x10000) { n += 16; z >>= 16; }
5374 if (z >= 0x00100) { n += 8; z >>= 8; }
5375 if (z >= 0x00010) { n += 4; z >>= 4; }
5376 if (z >= 0x00004) { n += 2; z >>= 2; }
5377 if (z >= 0x00002) { n += 1;/* >>= 1;*/ }
5378 return n;
5379}
5380
5381static int stbi__bitcount(unsigned int a)
5382{
5383 a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2
5384 a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4
5385 a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits
5386 a = (a + (a >> 8)); // max 16 per 8 bits
5387 a = (a + (a >> 16)); // max 32 per 8 bits
5388 return a & 0xff;
5389}
5390
5391// extract an arbitrarily-aligned N-bit value (N=bits)
5392// from v, and then make it 8-bits long and fractionally
5393// extend it to full full range.
5394static int stbi__shiftsigned(unsigned int v, int shift, int bits)
5395{
5396 static unsigned int mul_table[9] = {
5397 0,
5398 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/,
5399 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/,
5400 };
5401 static unsigned int shift_table[9] = {
5402 0, 0,0,1,0,2,4,6,0,
5403 };
5404 if (shift < 0)
5405 v <<= -shift;
5406 else
5407 v >>= shift;
5408 STBI_ASSERT(v < 256);
5409 v >>= (8-bits);
5410 STBI_ASSERT(bits >= 0 && bits <= 8);
5411 return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits];
5412}
5413
5414typedef struct
5415{
5416 int bpp, offset, hsz;
5417 unsigned int mr,mg,mb,ma, all_a;
5418 int extra_read;
5419} stbi__bmp_data;
5420
5421static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress)
5422{
5423 // BI_BITFIELDS specifies masks explicitly, don't override
5424 if (compress == 3)
5425 return 1;
5426
5427 if (compress == 0) {
5428 if (info->bpp == 16) {
5429 info->mr = 31u << 10;
5430 info->mg = 31u << 5;
5431 info->mb = 31u << 0;
5432 } else if (info->bpp == 32) {
5433 info->mr = 0xffu << 16;
5434 info->mg = 0xffu << 8;
5435 info->mb = 0xffu << 0;
5436 info->ma = 0xffu << 24;
5437 info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0
5438 } else {
5439 // otherwise, use defaults, which is all-0
5440 info->mr = info->mg = info->mb = info->ma = 0;
5441 }
5442 return 1;
5443 }
5444 return 0; // error
5445}
5446
5447static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
5448{
5449 int hsz;
5450 if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP");
5451 stbi__get32le(s); // discard filesize
5452 stbi__get16le(s); // discard reserved
5453 stbi__get16le(s); // discard reserved
5454 info->offset = stbi__get32le(s);
5455 info->hsz = hsz = stbi__get32le(s);
5456 info->mr = info->mg = info->mb = info->ma = 0;
5457 info->extra_read = 14;
5458
5459 if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP");
5460
5461 if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown");
5462 if (hsz == 12) {
5463 s->img_x = stbi__get16le(s);
5464 s->img_y = stbi__get16le(s);
5465 } else {
5466 s->img_x = stbi__get32le(s);
5467 s->img_y = stbi__get32le(s);
5468 }
5469 if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP");
5470 info->bpp = stbi__get16le(s);
5471 if (hsz != 12) {
5472 int compress = stbi__get32le(s);
5473 if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE");
5474 if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes
5475 if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel
5476 stbi__get32le(s); // discard sizeof
5477 stbi__get32le(s); // discard hres
5478 stbi__get32le(s); // discard vres
5479 stbi__get32le(s); // discard colorsused
5480 stbi__get32le(s); // discard max important
5481 if (hsz == 40 || hsz == 56) {
5482 if (hsz == 56) {
5483 stbi__get32le(s);
5484 stbi__get32le(s);
5485 stbi__get32le(s);
5486 stbi__get32le(s);
5487 }
5488 if (info->bpp == 16 || info->bpp == 32) {
5489 if (compress == 0) {
5490 stbi__bmp_set_mask_defaults(info, compress);
5491 } else if (compress == 3) {
5492 info->mr = stbi__get32le(s);
5493 info->mg = stbi__get32le(s);
5494 info->mb = stbi__get32le(s);
5495 info->extra_read += 12;
5496 // not documented, but generated by photoshop and handled by mspaint
5497 if (info->mr == info->mg && info->mg == info->mb) {
5498 // ?!?!?
5499 return stbi__errpuc("bad BMP", "bad BMP");
5500 }
5501 } else
5502 return stbi__errpuc("bad BMP", "bad BMP");
5503 }
5504 } else {
5505 // V4/V5 header
5506 int i;
5507 if (hsz != 108 && hsz != 124)
5508 return stbi__errpuc("bad BMP", "bad BMP");
5509 info->mr = stbi__get32le(s);
5510 info->mg = stbi__get32le(s);
5511 info->mb = stbi__get32le(s);
5512 info->ma = stbi__get32le(s);
5513 if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs
5514 stbi__bmp_set_mask_defaults(info, compress);
5515 stbi__get32le(s); // discard color space
5516 for (i=0; i < 12; ++i)
5517 stbi__get32le(s); // discard color space parameters
5518 if (hsz == 124) {
5519 stbi__get32le(s); // discard rendering intent
5520 stbi__get32le(s); // discard offset of profile data
5521 stbi__get32le(s); // discard size of profile data
5522 stbi__get32le(s); // discard reserved
5523 }
5524 }
5525 }
5526 return (void *) 1;
5527}
5528
5529
5530static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
5531{
5532 stbi_uc *out;
5533 unsigned int mr=0,mg=0,mb=0,ma=0, all_a;
5534 stbi_uc pal[256][4];
5535 int psize=0,i,j,width;
5536 int flip_vertically, pad, target;
5537 stbi__bmp_data info;
5538 STBI_NOTUSED(ri);
5539
5540 info.all_a = 255;
5541 if (stbi__bmp_parse_header(s, &info) == NULL)
5542 return NULL; // error code already set
5543
5544 flip_vertically = ((int) s->img_y) > 0;
5545 s->img_y = abs((int) s->img_y);
5546
5547 if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
5548 if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
5549
5550 mr = info.mr;
5551 mg = info.mg;
5552 mb = info.mb;
5553 ma = info.ma;
5554 all_a = info.all_a;
5555
5556 if (info.hsz == 12) {
5557 if (info.bpp < 24)
5558 psize = (info.offset - info.extra_read - 24) / 3;
5559 } else {
5560 if (info.bpp < 16)
5561 psize = (info.offset - info.extra_read - info.hsz) >> 2;
5562 }
5563 if (psize == 0) {
5564 // accept some number of extra bytes after the header, but if the offset points either to before
5565 // the header ends or implies a large amount of extra data, reject the file as malformed
5566 int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original);
5567 int header_limit = 1024; // max we actually read is below 256 bytes currently.
5568 int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size.
5569 if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) {
5570 return stbi__errpuc("bad header", "Corrupt BMP");
5571 }
5572 // we established that bytes_read_so_far is positive and sensible.
5573 // the first half of this test rejects offsets that are either too small positives, or
5574 // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn
5575 // ensures the number computed in the second half of the test can't overflow.
5576 if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) {
5577 return stbi__errpuc("bad offset", "Corrupt BMP");
5578 } else {
5579 stbi__skip(s, info.offset - bytes_read_so_far);
5580 }
5581 }
5582
5583 if (info.bpp == 24 && ma == 0xff000000)
5584 s->img_n = 3;
5585 else
5586 s->img_n = ma ? 4 : 3;
5587 if (req_comp && req_comp >= 3) // we can directly decode 3 or 4
5588 target = req_comp;
5589 else
5590 target = s->img_n; // if they want monochrome, we'll post-convert
5591
5592 // sanity-check size
5593 if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0))
5594 return stbi__errpuc("too large", "Corrupt BMP");
5595
5596 out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0);
5597 if (!out) return stbi__errpuc("outofmem", "Out of memory");
5598 if (info.bpp < 16) {
5599 int z=0;
5600 if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); }
5601 for (i=0; i < psize; ++i) {
5602 pal[i][2] = stbi__get8(s);
5603 pal[i][1] = stbi__get8(s);
5604 pal[i][0] = stbi__get8(s);
5605 if (info.hsz != 12) stbi__get8(s);
5606 pal[i][3] = 255;
5607 }
5608 stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4));
5609 if (info.bpp == 1) width = (s->img_x + 7) >> 3;
5610 else if (info.bpp == 4) width = (s->img_x + 1) >> 1;
5611 else if (info.bpp == 8) width = s->img_x;
5612 else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); }
5613 pad = (-width)&3;
5614 if (info.bpp == 1) {
5615 for (j=0; j < (int) s->img_y; ++j) {
5616 int bit_offset = 7, v = stbi__get8(s);
5617 for (i=0; i < (int) s->img_x; ++i) {
5618 int color = (v>>bit_offset)&0x1;
5619 out[z++] = pal[color][0];
5620 out[z++] = pal[color][1];
5621 out[z++] = pal[color][2];
5622 if (target == 4) out[z++] = 255;
5623 if (i+1 == (int) s->img_x) break;
5624 if((--bit_offset) < 0) {
5625 bit_offset = 7;
5626 v = stbi__get8(s);
5627 }
5628 }
5629 stbi__skip(s, pad);
5630 }
5631 } else {
5632 for (j=0; j < (int) s->img_y; ++j) {
5633 for (i=0; i < (int) s->img_x; i += 2) {
5634 int v=stbi__get8(s),v2=0;
5635 if (info.bpp == 4) {
5636 v2 = v & 15;
5637 v >>= 4;
5638 }
5639 out[z++] = pal[v][0];
5640 out[z++] = pal[v][1];
5641 out[z++] = pal[v][2];
5642 if (target == 4) out[z++] = 255;
5643 if (i+1 == (int) s->img_x) break;
5644 v = (info.bpp == 8) ? stbi__get8(s) : v2;
5645 out[z++] = pal[v][0];
5646 out[z++] = pal[v][1];
5647 out[z++] = pal[v][2];
5648 if (target == 4) out[z++] = 255;
5649 }
5650 stbi__skip(s, pad);
5651 }
5652 }
5653 } else {
5654 int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0;
5655 int z = 0;
5656 int easy=0;
5657 stbi__skip(s, info.offset - info.extra_read - info.hsz);
5658 if (info.bpp == 24) width = 3 * s->img_x;
5659 else if (info.bpp == 16) width = 2*s->img_x;
5660 else /* bpp = 32 and pad = 0 */ width=0;
5661 pad = (-width) & 3;
5662 if (info.bpp == 24) {
5663 easy = 1;
5664 } else if (info.bpp == 32) {
5665 if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)
5666 easy = 2;
5667 }
5668 if (!easy) {
5669 if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); }
5670 // right shift amt to put high bit in position #7
5671 rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr);
5672 gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg);
5673 bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb);
5674 ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma);
5675 if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); }
5676 }
5677 for (j=0; j < (int) s->img_y; ++j) {
5678 if (easy) {
5679 for (i=0; i < (int) s->img_x; ++i) {
5680 unsigned char a;
5681 out[z+2] = stbi__get8(s);
5682 out[z+1] = stbi__get8(s);
5683 out[z+0] = stbi__get8(s);
5684 z += 3;
5685 a = (easy == 2 ? stbi__get8(s) : 255);
5686 all_a |= a;
5687 if (target == 4) out[z++] = a;
5688 }
5689 } else {
5690 int bpp = info.bpp;
5691 for (i=0; i < (int) s->img_x; ++i) {
5692 stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s));
5693 unsigned int a;
5694 out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount));
5695 out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount));
5696 out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount));
5697 a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);
5698 all_a |= a;
5699 if (target == 4) out[z++] = STBI__BYTECAST(a);
5700 }
5701 }
5702 stbi__skip(s, pad);
5703 }
5704 }
5705
5706 // if alpha channel is all 0s, replace with all 255s
5707 if (target == 4 && all_a == 0)
5708 for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4)
5709 out[i] = 255;
5710
5711 if (flip_vertically) {
5712 stbi_uc t;
5713 for (j=0; j < (int) s->img_y>>1; ++j) {
5714 stbi_uc *p1 = out + j *s->img_x*target;
5715 stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target;
5716 for (i=0; i < (int) s->img_x*target; ++i) {
5717 t = p1[i]; p1[i] = p2[i]; p2[i] = t;
5718 }
5719 }
5720 }
5721
5722 if (req_comp && req_comp != target) {
5723 out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y);
5724 if (out == NULL) return out; // stbi__convert_format frees input on failure
5725 }
5726
5727 *x = s->img_x;
5728 *y = s->img_y;
5729 if (comp) *comp = s->img_n;
5730 return out;
5731}
5732#endif
5733
5734// Targa Truevision - TGA
5735// by Jonathan Dummer
5736#ifndef STBI_NO_TGA
5737// returns STBI_rgb or whatever, 0 on error
5738static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16)
5739{
5740 // only RGB or RGBA (incl. 16bit) or grey allowed
5741 if (is_rgb16) *is_rgb16 = 0;
5742 switch(bits_per_pixel) {
5743 case 8: return STBI_grey;
5744 case 16: if(is_grey) return STBI_grey_alpha;
5745 // fallthrough
5746 case 15: if(is_rgb16) *is_rgb16 = 1;
5747 return STBI_rgb;
5748 case 24: // fallthrough
5749 case 32: return bits_per_pixel/8;
5750 default: return 0;
5751 }
5752}
5753
5754static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp)
5755{
5756 int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp;
5757 int sz, tga_colormap_type;
5758 stbi__get8(s); // discard Offset
5759 tga_colormap_type = stbi__get8(s); // colormap type
5760 if( tga_colormap_type > 1 ) {
5761 stbi__rewind(s);
5762 return 0; // only RGB or indexed allowed
5763 }
5764 tga_image_type = stbi__get8(s); // image type
5765 if ( tga_colormap_type == 1 ) { // colormapped (paletted) image
5766 if (tga_image_type != 1 && tga_image_type != 9) {
5767 stbi__rewind(s);
5768 return 0;
5769 }
5770 stbi__skip(s,4); // skip index of first colormap entry and number of entries
5771 sz = stbi__get8(s); // check bits per palette color entry
5772 if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) {
5773 stbi__rewind(s);
5774 return 0;
5775 }
5776 stbi__skip(s,4); // skip image x and y origin
5777 tga_colormap_bpp = sz;
5778 } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE
5779 if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) {
5780 stbi__rewind(s);
5781 return 0; // only RGB or grey allowed, +/- RLE
5782 }
5783 stbi__skip(s,9); // skip colormap specification and image x/y origin
5784 tga_colormap_bpp = 0;
5785 }
5786 tga_w = stbi__get16le(s);
5787 if( tga_w < 1 ) {
5788 stbi__rewind(s);
5789 return 0; // test width
5790 }
5791 tga_h = stbi__get16le(s);
5792 if( tga_h < 1 ) {
5793 stbi__rewind(s);
5794 return 0; // test height
5795 }
5796 tga_bits_per_pixel = stbi__get8(s); // bits per pixel
5797 stbi__get8(s); // ignore alpha bits
5798 if (tga_colormap_bpp != 0) {
5799 if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) {
5800 // when using a colormap, tga_bits_per_pixel is the size of the indexes
5801 // I don't think anything but 8 or 16bit indexes makes sense
5802 stbi__rewind(s);
5803 return 0;
5804 }
5805 tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL);
5806 } else {
5807 tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL);
5808 }
5809 if(!tga_comp) {
5810 stbi__rewind(s);
5811 return 0;
5812 }
5813 if (x) *x = tga_w;
5814 if (y) *y = tga_h;
5815 if (comp) *comp = tga_comp;
5816 return 1; // seems to have passed everything
5817}
5818
5819static int stbi__tga_test(stbi__context *s)
5820{
5821 int res = 0;
5822 int sz, tga_color_type;
5823 stbi__get8(s); // discard Offset
5824 tga_color_type = stbi__get8(s); // color type
5825 if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed
5826 sz = stbi__get8(s); // image type
5827 if ( tga_color_type == 1 ) { // colormapped (paletted) image
5828 if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9
5829 stbi__skip(s,4); // skip index of first colormap entry and number of entries
5830 sz = stbi__get8(s); // check bits per palette color entry
5831 if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
5832 stbi__skip(s,4); // skip image x and y origin
5833 } else { // "normal" image w/o colormap
5834 if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE
5835 stbi__skip(s,9); // skip colormap specification and image x/y origin
5836 }
5837 if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width
5838 if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height
5839 sz = stbi__get8(s); // bits per pixel
5840 if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index
5841 if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
5842
5843 res = 1; // if we got this far, everything's good and we can return 1 instead of 0
5844
5845errorEnd:
5846 stbi__rewind(s);
5847 return res;
5848}
5849
5850// read 16bit value and convert to 24bit RGB
5851static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out)
5852{
5853 stbi__uint16 px = (stbi__uint16)stbi__get16le(s);
5854 stbi__uint16 fiveBitMask = 31;
5855 // we have 3 channels with 5bits each
5856 int r = (px >> 10) & fiveBitMask;
5857 int g = (px >> 5) & fiveBitMask;
5858 int b = px & fiveBitMask;
5859 // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later
5860 out[0] = (stbi_uc)((r * 255)/31);
5861 out[1] = (stbi_uc)((g * 255)/31);
5862 out[2] = (stbi_uc)((b * 255)/31);
5863
5864 // some people claim that the most significant bit might be used for alpha
5865 // (possibly if an alpha-bit is set in the "image descriptor byte")
5866 // but that only made 16bit test images completely translucent..
5867 // so let's treat all 15 and 16bit TGAs as RGB with no alpha.
5868}
5869
5870static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
5871{
5872 // read in the TGA header stuff
5873 int tga_offset = stbi__get8(s);
5874 int tga_indexed = stbi__get8(s);
5875 int tga_image_type = stbi__get8(s);
5876 int tga_is_RLE = 0;
5877 int tga_palette_start = stbi__get16le(s);
5878 int tga_palette_len = stbi__get16le(s);
5879 int tga_palette_bits = stbi__get8(s);
5880 int tga_x_origin = stbi__get16le(s);
5881 int tga_y_origin = stbi__get16le(s);
5882 int tga_width = stbi__get16le(s);
5883 int tga_height = stbi__get16le(s);
5884 int tga_bits_per_pixel = stbi__get8(s);
5885 int tga_comp, tga_rgb16=0;
5886 int tga_inverted = stbi__get8(s);
5887 // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?)
5888 // image data
5889 unsigned char *tga_data;
5890 unsigned char *tga_palette = NULL;
5891 int i, j;
5892 unsigned char raw_data[4] = {0};
5893 int RLE_count = 0;
5894 int RLE_repeating = 0;
5895 int read_next_pixel = 1;
5896 STBI_NOTUSED(ri);
5897 STBI_NOTUSED(tga_x_origin); // @TODO
5898 STBI_NOTUSED(tga_y_origin); // @TODO
5899
5900 if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
5901 if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
5902
5903 // do a tiny bit of precessing
5904 if ( tga_image_type >= 8 )
5905 {
5906 tga_image_type -= 8;
5907 tga_is_RLE = 1;
5908 }
5909 tga_inverted = 1 - ((tga_inverted >> 5) & 1);
5910
5911 // If I'm paletted, then I'll use the number of bits from the palette
5912 if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16);
5913 else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16);
5914
5915 if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency
5916 return stbi__errpuc("bad format", "Can't find out TGA pixelformat");
5917
5918 // tga info
5919 *x = tga_width;
5920 *y = tga_height;
5921 if (comp) *comp = tga_comp;
5922
5923 if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0))
5924 return stbi__errpuc("too large", "Corrupt TGA");
5925
5926 tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0);
5927 if (!tga_data) return stbi__errpuc("outofmem", "Out of memory");
5928
5929 // skip to the data's starting position (offset usually = 0)
5930 stbi__skip(s, tga_offset );
5931
5932 if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) {
5933 for (i=0; i < tga_height; ++i) {
5934 int row = tga_inverted ? tga_height -i - 1 : i;
5935 stbi_uc *tga_row = tga_data + row*tga_width*tga_comp;
5936 stbi__getn(s, tga_row, tga_width * tga_comp);
5937 }
5938 } else {
5939 // do I need to load a palette?
5940 if ( tga_indexed)
5941 {
5942 if (tga_palette_len == 0) { /* you have to have at least one entry! */
5943 STBI_FREE(tga_data);
5944 return stbi__errpuc("bad palette", "Corrupt TGA");
5945 }
5946
5947 // any data to skip? (offset usually = 0)
5948 stbi__skip(s, tga_palette_start );
5949 // load the palette
5950 tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0);
5951 if (!tga_palette) {
5952 STBI_FREE(tga_data);
5953 return stbi__errpuc("outofmem", "Out of memory");
5954 }
5955 if (tga_rgb16) {
5956 stbi_uc *pal_entry = tga_palette;
5957 STBI_ASSERT(tga_comp == STBI_rgb);
5958 for (i=0; i < tga_palette_len; ++i) {
5959 stbi__tga_read_rgb16(s, pal_entry);
5960 pal_entry += tga_comp;
5961 }
5962 } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) {
5963 STBI_FREE(tga_data);
5964 STBI_FREE(tga_palette);
5965 return stbi__errpuc("bad palette", "Corrupt TGA");
5966 }
5967 }
5968 // load the data
5969 for (i=0; i < tga_width * tga_height; ++i)
5970 {
5971 // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk?
5972 if ( tga_is_RLE )
5973 {
5974 if ( RLE_count == 0 )
5975 {
5976 // yep, get the next byte as a RLE command
5977 int RLE_cmd = stbi__get8(s);
5978 RLE_count = 1 + (RLE_cmd & 127);
5979 RLE_repeating = RLE_cmd >> 7;
5980 read_next_pixel = 1;
5981 } else if ( !RLE_repeating )
5982 {
5983 read_next_pixel = 1;
5984 }
5985 } else
5986 {
5987 read_next_pixel = 1;
5988 }
5989 // OK, if I need to read a pixel, do it now
5990 if ( read_next_pixel )
5991 {
5992 // load however much data we did have
5993 if ( tga_indexed )
5994 {
5995 // read in index, then perform the lookup
5996 int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s);
5997 if ( pal_idx >= tga_palette_len ) {
5998 // invalid index
5999 pal_idx = 0;
6000 }
6001 pal_idx *= tga_comp;
6002 for (j = 0; j < tga_comp; ++j) {
6003 raw_data[j] = tga_palette[pal_idx+j];
6004 }
6005 } else if(tga_rgb16) {
6006 STBI_ASSERT(tga_comp == STBI_rgb);
6007 stbi__tga_read_rgb16(s, raw_data);
6008 } else {
6009 // read in the data raw
6010 for (j = 0; j < tga_comp; ++j) {
6011 raw_data[j] = stbi__get8(s);
6012 }
6013 }
6014 // clear the reading flag for the next pixel
6015 read_next_pixel = 0;
6016 } // end of reading a pixel
6017
6018 // copy data
6019 for (j = 0; j < tga_comp; ++j)
6020 tga_data[i*tga_comp+j] = raw_data[j];
6021
6022 // in case we're in RLE mode, keep counting down
6023 --RLE_count;
6024 }
6025 // do I need to invert the image?
6026 if ( tga_inverted )
6027 {
6028 for (j = 0; j*2 < tga_height; ++j)
6029 {
6030 int index1 = j * tga_width * tga_comp;
6031 int index2 = (tga_height - 1 - j) * tga_width * tga_comp;
6032 for (i = tga_width * tga_comp; i > 0; --i)
6033 {
6034 unsigned char temp = tga_data[index1];
6035 tga_data[index1] = tga_data[index2];
6036 tga_data[index2] = temp;
6037 ++index1;
6038 ++index2;
6039 }
6040 }
6041 }
6042 // clear my palette, if I had one
6043 if ( tga_palette != NULL )
6044 {
6045 STBI_FREE( tga_palette );
6046 }
6047 }
6048
6049 // swap RGB - if the source data was RGB16, it already is in the right order
6050 if (tga_comp >= 3 && !tga_rgb16)
6051 {
6052 unsigned char* tga_pixel = tga_data;
6053 for (i=0; i < tga_width * tga_height; ++i)
6054 {
6055 unsigned char temp = tga_pixel[0];
6056 tga_pixel[0] = tga_pixel[2];
6057 tga_pixel[2] = temp;
6058 tga_pixel += tga_comp;
6059 }
6060 }
6061
6062 // convert to target component count
6063 if (req_comp && req_comp != tga_comp)
6064 tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height);
6065
6066 // the things I do to get rid of an error message, and yet keep
6067 // Microsoft's C compilers happy... [8^(
6068 tga_palette_start = tga_palette_len = tga_palette_bits =
6069 tga_x_origin = tga_y_origin = 0;
6070 STBI_NOTUSED(tga_palette_start);
6071 // OK, done
6072 return tga_data;
6073}
6074#endif
6075
6076// *************************************************************************************************
6077// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB
6078
6079#ifndef STBI_NO_PSD
6080static int stbi__psd_test(stbi__context *s)
6081{
6082 int r = (stbi__get32be(s) == 0x38425053);
6083 stbi__rewind(s);
6084 return r;
6085}
6086
6087static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount)
6088{
6089 int count, nleft, len;
6090
6091 count = 0;
6092 while ((nleft = pixelCount - count) > 0) {
6093 len = stbi__get8(s);
6094 if (len == 128) {
6095 // No-op.
6096 } else if (len < 128) {
6097 // Copy next len+1 bytes literally.
6098 len++;
6099 if (len > nleft) return 0; // corrupt data
6100 count += len;
6101 while (len) {
6102 *p = stbi__get8(s);
6103 p += 4;
6104 len--;
6105 }
6106 } else if (len > 128) {
6107 stbi_uc val;
6108 // Next -len+1 bytes in the dest are replicated from next source byte.
6109 // (Interpret len as a negative 8-bit int.)
6110 len = 257 - len;
6111 if (len > nleft) return 0; // corrupt data
6112 val = stbi__get8(s);
6113 count += len;
6114 while (len) {
6115 *p = val;
6116 p += 4;
6117 len--;
6118 }
6119 }
6120 }
6121
6122 return 1;
6123}
6124
6125static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
6126{
6127 int pixelCount;
6128 int channelCount, compression;
6129 int channel, i;
6130 int bitdepth;
6131 int w,h;
6132 stbi_uc *out;
6133 STBI_NOTUSED(ri);
6134
6135 // Check identifier
6136 if (stbi__get32be(s) != 0x38425053) // "8BPS"
6137 return stbi__errpuc("not PSD", "Corrupt PSD image");
6138
6139 // Check file type version.
6140 if (stbi__get16be(s) != 1)
6141 return stbi__errpuc("wrong version", "Unsupported version of PSD image");
6142
6143 // Skip 6 reserved bytes.
6144 stbi__skip(s, 6 );
6145
6146 // Read the number of channels (R, G, B, A, etc).
6147 channelCount = stbi__get16be(s);
6148 if (channelCount < 0 || channelCount > 16)
6149 return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image");
6150
6151 // Read the rows and columns of the image.
6152 h = stbi__get32be(s);
6153 w = stbi__get32be(s);
6154
6155 if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
6156 if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
6157
6158 // Make sure the depth is 8 bits.
6159 bitdepth = stbi__get16be(s);
6160 if (bitdepth != 8 && bitdepth != 16)
6161 return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit");
6162
6163 // Make sure the color mode is RGB.
6164 // Valid options are:
6165 // 0: Bitmap
6166 // 1: Grayscale
6167 // 2: Indexed color
6168 // 3: RGB color
6169 // 4: CMYK color
6170 // 7: Multichannel
6171 // 8: Duotone
6172 // 9: Lab color
6173 if (stbi__get16be(s) != 3)
6174 return stbi__errpuc("wrong color format", "PSD is not in RGB color format");
6175
6176 // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.)
6177 stbi__skip(s,stbi__get32be(s) );
6178
6179 // Skip the image resources. (resolution, pen tool paths, etc)
6180 stbi__skip(s, stbi__get32be(s) );
6181
6182 // Skip the reserved data.
6183 stbi__skip(s, stbi__get32be(s) );
6184
6185 // Find out if the data is compressed.
6186 // Known values:
6187 // 0: no compression
6188 // 1: RLE compressed
6189 compression = stbi__get16be(s);
6190 if (compression > 1)
6191 return stbi__errpuc("bad compression", "PSD has an unknown compression format");
6192
6193 // Check size
6194 if (!stbi__mad3sizes_valid(4, w, h, 0))
6195 return stbi__errpuc("too large", "Corrupt PSD");
6196
6197 // Create the destination image.
6198
6199 if (!compression && bitdepth == 16 && bpc == 16) {
6200 out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0);
6201 ri->bits_per_channel = 16;
6202 } else
6203 out = (stbi_uc *) stbi__malloc(4 * w*h);
6204
6205 if (!out) return stbi__errpuc("outofmem", "Out of memory");
6206 pixelCount = w*h;
6207
6208 // Initialize the data to zero.
6209 //memset( out, 0, pixelCount * 4 );
6210
6211 // Finally, the image data.
6212 if (compression) {
6213 // RLE as used by .PSD and .TIFF
6214 // Loop until you get the number of unpacked bytes you are expecting:
6215 // Read the next source byte into n.
6216 // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.
6217 // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.
6218 // Else if n is 128, noop.
6219 // Endloop
6220
6221 // The RLE-compressed data is preceded by a 2-byte data count for each row in the data,
6222 // which we're going to just skip.
6223 stbi__skip(s, h * channelCount * 2 );
6224
6225 // Read the RLE data by channel.
6226 for (channel = 0; channel < 4; channel++) {
6227 stbi_uc *p;
6228
6229 p = out+channel;
6230 if (channel >= channelCount) {
6231 // Fill this channel with default data.
6232 for (i = 0; i < pixelCount; i++, p += 4)
6233 *p = (channel == 3 ? 255 : 0);
6234 } else {
6235 // Read the RLE data.
6236 if (!stbi__psd_decode_rle(s, p, pixelCount)) {
6237 STBI_FREE(out);
6238 return stbi__errpuc("corrupt", "bad RLE data");
6239 }
6240 }
6241 }
6242
6243 } else {
6244 // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...)
6245 // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image.
6246
6247 // Read the data by channel.
6248 for (channel = 0; channel < 4; channel++) {
6249 if (channel >= channelCount) {
6250 // Fill this channel with default data.
6251 if (bitdepth == 16 && bpc == 16) {
6252 stbi__uint16 *q = ((stbi__uint16 *) out) + channel;
6253 stbi__uint16 val = channel == 3 ? 65535 : 0;
6254 for (i = 0; i < pixelCount; i++, q += 4)
6255 *q = val;
6256 } else {
6257 stbi_uc *p = out+channel;
6258 stbi_uc val = channel == 3 ? 255 : 0;
6259 for (i = 0; i < pixelCount; i++, p += 4)
6260 *p = val;
6261 }
6262 } else {
6263 if (ri->bits_per_channel == 16) { // output bpc
6264 stbi__uint16 *q = ((stbi__uint16 *) out) + channel;
6265 for (i = 0; i < pixelCount; i++, q += 4)
6266 *q = (stbi__uint16) stbi__get16be(s);
6267 } else {
6268 stbi_uc *p = out+channel;
6269 if (bitdepth == 16) { // input bpc
6270 for (i = 0; i < pixelCount; i++, p += 4)
6271 *p = (stbi_uc) (stbi__get16be(s) >> 8);
6272 } else {
6273 for (i = 0; i < pixelCount; i++, p += 4)
6274 *p = stbi__get8(s);
6275 }
6276 }
6277 }
6278 }
6279 }
6280
6281 // remove weird white matte from PSD
6282 if (channelCount >= 4) {
6283 if (ri->bits_per_channel == 16) {
6284 for (i=0; i < w*h; ++i) {
6285 stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i;
6286 if (pixel[3] != 0 && pixel[3] != 65535) {
6287 float a = pixel[3] / 65535.0f;
6288 float ra = 1.0f / a;
6289 float inv_a = 65535.0f * (1 - ra);
6290 pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a);
6291 pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a);
6292 pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a);
6293 }
6294 }
6295 } else {
6296 for (i=0; i < w*h; ++i) {
6297 unsigned char *pixel = out + 4*i;
6298 if (pixel[3] != 0 && pixel[3] != 255) {
6299 float a = pixel[3] / 255.0f;
6300 float ra = 1.0f / a;
6301 float inv_a = 255.0f * (1 - ra);
6302 pixel[0] = (unsigned char) (pixel[0]*ra + inv_a);
6303 pixel[1] = (unsigned char) (pixel[1]*ra + inv_a);
6304 pixel[2] = (unsigned char) (pixel[2]*ra + inv_a);
6305 }
6306 }
6307 }
6308 }
6309
6310 // convert to desired output format
6311 if (req_comp && req_comp != 4) {
6312 if (ri->bits_per_channel == 16)
6313 out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h);
6314 else
6315 out = stbi__convert_format(out, 4, req_comp, w, h);
6316 if (out == NULL) return out; // stbi__convert_format frees input on failure
6317 }
6318
6319 if (comp) *comp = 4;
6320 *y = h;
6321 *x = w;
6322
6323 return out;
6324}
6325#endif
6326
6327// *************************************************************************************************
6328// Softimage PIC loader
6329// by Tom Seddon
6330//
6331// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format
6332// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/
6333
6334#ifndef STBI_NO_PIC
6335static int stbi__pic_is4(stbi__context *s,const char *str)
6336{
6337 int i;
6338 for (i=0; i<4; ++i)
6339 if (stbi__get8(s) != (stbi_uc)str[i])
6340 return 0;
6341
6342 return 1;
6343}
6344
6345static int stbi__pic_test_core(stbi__context *s)
6346{
6347 int i;
6348
6349 if (!stbi__pic_is4(s,"\x53\x80\xF6\x34"))
6350 return 0;
6351
6352 for(i=0;i<84;++i)
6353 stbi__get8(s);
6354
6355 if (!stbi__pic_is4(s,"PICT"))
6356 return 0;
6357
6358 return 1;
6359}
6360
6361typedef struct
6362{
6363 stbi_uc size,type,channel;
6364} stbi__pic_packet;
6365
6366static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest)
6367{
6368 int mask=0x80, i;
6369
6370 for (i=0; i<4; ++i, mask>>=1) {
6371 if (channel & mask) {
6372 if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short");
6373 dest[i]=stbi__get8(s);
6374 }
6375 }
6376
6377 return dest;
6378}
6379
6380static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src)
6381{
6382 int mask=0x80,i;
6383
6384 for (i=0;i<4; ++i, mask>>=1)
6385 if (channel&mask)
6386 dest[i]=src[i];
6387}
6388
6389static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result)
6390{
6391 int act_comp=0,num_packets=0,y,chained;
6392 stbi__pic_packet packets[10];
6393
6394 // this will (should...) cater for even some bizarre stuff like having data
6395 // for the same channel in multiple packets.
6396 do {
6397 stbi__pic_packet *packet;
6398
6399 if (num_packets==sizeof(packets)/sizeof(packets[0]))
6400 return stbi__errpuc("bad format","too many packets");
6401
6402 packet = &packets[num_packets++];
6403
6404 chained = stbi__get8(s);
6405 packet->size = stbi__get8(s);
6406 packet->type = stbi__get8(s);
6407 packet->channel = stbi__get8(s);
6408
6409 act_comp |= packet->channel;
6410
6411 if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)");
6412 if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp");
6413 } while (chained);
6414
6415 *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel?
6416
6417 for(y=0; y<height; ++y) {
6418 int packet_idx;
6419
6420 for(packet_idx=0; packet_idx < num_packets; ++packet_idx) {
6421 stbi__pic_packet *packet = &packets[packet_idx];
6422 stbi_uc *dest = result+y*width*4;
6423
6424 switch (packet->type) {
6425 default:
6426 return stbi__errpuc("bad format","packet has bad compression type");
6427
6428 case 0: {//uncompressed
6429 int x;
6430
6431 for(x=0;x<width;++x, dest+=4)
6432 if (!stbi__readval(s,packet->channel,dest))
6433 return 0;
6434 break;
6435 }
6436
6437 case 1://Pure RLE
6438 {
6439 int left=width, i;
6440
6441 while (left>0) {
6442 stbi_uc count,value[4];
6443
6444 count=stbi__get8(s);
6445 if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)");
6446
6447 if (count > left)
6448 count = (stbi_uc) left;
6449
6450 if (!stbi__readval(s,packet->channel,value)) return 0;
6451
6452 for(i=0; i<count; ++i,dest+=4)
6453 stbi__copyval(packet->channel,dest,value);
6454 left -= count;
6455 }
6456 }
6457 break;
6458
6459 case 2: {//Mixed RLE
6460 int left=width;
6461 while (left>0) {
6462 int count = stbi__get8(s), i;
6463 if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)");
6464
6465 if (count >= 128) { // Repeated
6466 stbi_uc value[4];
6467
6468 if (count==128)
6469 count = stbi__get16be(s);
6470 else
6471 count -= 127;
6472 if (count > left)
6473 return stbi__errpuc("bad file","scanline overrun");
6474
6475 if (!stbi__readval(s,packet->channel,value))
6476 return 0;
6477
6478 for(i=0;i<count;++i, dest += 4)
6479 stbi__copyval(packet->channel,dest,value);
6480 } else { // Raw
6481 ++count;
6482 if (count>left) return stbi__errpuc("bad file","scanline overrun");
6483
6484 for(i=0;i<count;++i, dest+=4)
6485 if (!stbi__readval(s,packet->channel,dest))
6486 return 0;
6487 }
6488 left-=count;
6489 }
6490 break;
6491 }
6492 }
6493 }
6494 }
6495
6496 return result;
6497}
6498
6499static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri)
6500{
6501 stbi_uc *result;
6502 int i, x,y, internal_comp;
6503 STBI_NOTUSED(ri);
6504
6505 if (!comp) comp = &internal_comp;
6506
6507 for (i=0; i<92; ++i)
6508 stbi__get8(s);
6509
6510 x = stbi__get16be(s);
6511 y = stbi__get16be(s);
6512
6513 if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
6514 if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
6515
6516 if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)");
6517 if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode");
6518
6519 stbi__get32be(s); //skip `ratio'
6520 stbi__get16be(s); //skip `fields'
6521 stbi__get16be(s); //skip `pad'
6522
6523 // intermediate buffer is RGBA
6524 result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0);
6525 if (!result) return stbi__errpuc("outofmem", "Out of memory");
6526 memset(result, 0xff, x*y*4);
6527
6528 if (!stbi__pic_load_core(s,x,y,comp, result)) {
6529 STBI_FREE(result);
6530 result=0;
6531 }
6532 *px = x;
6533 *py = y;
6534 if (req_comp == 0) req_comp = *comp;
6535 result=stbi__convert_format(result,4,req_comp,x,y);
6536
6537 return result;
6538}
6539
6540static int stbi__pic_test(stbi__context *s)
6541{
6542 int r = stbi__pic_test_core(s);
6543 stbi__rewind(s);
6544 return r;
6545}
6546#endif
6547
6548// *************************************************************************************************
6549// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb
6550
6551#ifndef STBI_NO_GIF
6552typedef struct
6553{
6554 stbi__int16 prefix;
6555 stbi_uc first;
6556 stbi_uc suffix;
6557} stbi__gif_lzw;
6558
6559typedef struct
6560{
6561 int w,h;
6562 stbi_uc *out; // output buffer (always 4 components)
6563 stbi_uc *background; // The current "background" as far as a gif is concerned
6564 stbi_uc *history;
6565 int flags, bgindex, ratio, transparent, eflags;
6566 stbi_uc pal[256][4];
6567 stbi_uc lpal[256][4];
6568 stbi__gif_lzw codes[8192];
6569 stbi_uc *color_table;
6570 int parse, step;
6571 int lflags;
6572 int start_x, start_y;
6573 int max_x, max_y;
6574 int cur_x, cur_y;
6575 int line_size;
6576 int delay;
6577} stbi__gif;
6578
6579static int stbi__gif_test_raw(stbi__context *s)
6580{
6581 int sz;
6582 if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0;
6583 sz = stbi__get8(s);
6584 if (sz != '9' && sz != '7') return 0;
6585 if (stbi__get8(s) != 'a') return 0;
6586 return 1;
6587}
6588
6589static int stbi__gif_test(stbi__context *s)
6590{
6591 int r = stbi__gif_test_raw(s);
6592 stbi__rewind(s);
6593 return r;
6594}
6595
6596static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp)
6597{
6598 int i;
6599 for (i=0; i < num_entries; ++i) {
6600 pal[i][2] = stbi__get8(s);
6601 pal[i][1] = stbi__get8(s);
6602 pal[i][0] = stbi__get8(s);
6603 pal[i][3] = transp == i ? 0 : 255;
6604 }
6605}
6606
6607static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info)
6608{
6609 stbi_uc version;
6610 if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8')
6611 return stbi__err("not GIF", "Corrupt GIF");
6612
6613 version = stbi__get8(s);
6614 if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF");
6615 if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF");
6616
6617 stbi__g_failure_reason = "";
6618 g->w = stbi__get16le(s);
6619 g->h = stbi__get16le(s);
6620 g->flags = stbi__get8(s);
6621 g->bgindex = stbi__get8(s);
6622 g->ratio = stbi__get8(s);
6623 g->transparent = -1;
6624
6625 if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
6626 if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
6627
6628 if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments
6629
6630 if (is_info) return 1;
6631
6632 if (g->flags & 0x80)
6633 stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1);
6634
6635 return 1;
6636}
6637
6638static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)
6639{
6640 stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));
6641 if (!g) return stbi__err("outofmem", "Out of memory");
6642 if (!stbi__gif_header(s, g, comp, 1)) {
6643 STBI_FREE(g);
6644 stbi__rewind( s );
6645 return 0;
6646 }
6647 if (x) *x = g->w;
6648 if (y) *y = g->h;
6649 STBI_FREE(g);
6650 return 1;
6651}
6652
6653static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
6654{
6655 stbi_uc *p, *c;
6656 int idx;
6657
6658 // recurse to decode the prefixes, since the linked-list is backwards,
6659 // and working backwards through an interleaved image would be nasty
6660 if (g->codes[code].prefix >= 0)
6661 stbi__out_gif_code(g, g->codes[code].prefix);
6662
6663 if (g->cur_y >= g->max_y) return;
6664
6665 idx = g->cur_x + g->cur_y;
6666 p = &g->out[idx];
6667 g->history[idx / 4] = 1;
6668
6669 c = &g->color_table[g->codes[code].suffix * 4];
6670 if (c[3] > 128) { // don't render transparent pixels;
6671 p[0] = c[2];
6672 p[1] = c[1];
6673 p[2] = c[0];
6674 p[3] = c[3];
6675 }
6676 g->cur_x += 4;
6677
6678 if (g->cur_x >= g->max_x) {
6679 g->cur_x = g->start_x;
6680 g->cur_y += g->step;
6681
6682 while (g->cur_y >= g->max_y && g->parse > 0) {
6683 g->step = (1 << g->parse) * g->line_size;
6684 g->cur_y = g->start_y + (g->step >> 1);
6685 --g->parse;
6686 }
6687 }
6688}
6689
6690static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
6691{
6692 stbi_uc lzw_cs;
6693 stbi__int32 len, init_code;
6694 stbi__uint32 first;
6695 stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;
6696 stbi__gif_lzw *p;
6697
6698 lzw_cs = stbi__get8(s);
6699 if (lzw_cs > 12) return NULL;
6700 clear = 1 << lzw_cs;
6701 first = 1;
6702 codesize = lzw_cs + 1;
6703 codemask = (1 << codesize) - 1;
6704 bits = 0;
6705 valid_bits = 0;
6706 for (init_code = 0; init_code < clear; init_code++) {
6707 g->codes[init_code].prefix = -1;
6708 g->codes[init_code].first = (stbi_uc) init_code;
6709 g->codes[init_code].suffix = (stbi_uc) init_code;
6710 }
6711
6712 // support no starting clear code
6713 avail = clear+2;
6714 oldcode = -1;
6715
6716 len = 0;
6717 for(;;) {
6718 if (valid_bits < codesize) {
6719 if (len == 0) {
6720 len = stbi__get8(s); // start new block
6721 if (len == 0)
6722 return g->out;
6723 }
6724 --len;
6725 bits |= (stbi__int32) stbi__get8(s) << valid_bits;
6726 valid_bits += 8;
6727 } else {
6728 stbi__int32 code = bits & codemask;
6729 bits >>= codesize;
6730 valid_bits -= codesize;
6731 // @OPTIMIZE: is there some way we can accelerate the non-clear path?
6732 if (code == clear) { // clear code
6733 codesize = lzw_cs + 1;
6734 codemask = (1 << codesize) - 1;
6735 avail = clear + 2;
6736 oldcode = -1;
6737 first = 0;
6738 } else if (code == clear + 1) { // end of stream code
6739 stbi__skip(s, len);
6740 while ((len = stbi__get8(s)) > 0)
6741 stbi__skip(s,len);
6742 return g->out;
6743 } else if (code <= avail) {
6744 if (first) {
6745 return stbi__errpuc("no clear code", "Corrupt GIF");
6746 }
6747
6748 if (oldcode >= 0) {
6749 p = &g->codes[avail++];
6750 if (avail > 8192) {
6751 return stbi__errpuc("too many codes", "Corrupt GIF");
6752 }
6753
6754 p->prefix = (stbi__int16) oldcode;
6755 p->first = g->codes[oldcode].first;
6756 p->suffix = (code == avail) ? p->first : g->codes[code].first;
6757 } else if (code == avail)
6758 return stbi__errpuc("illegal code in raster", "Corrupt GIF");
6759
6760 stbi__out_gif_code(g, (stbi__uint16) code);
6761
6762 if ((avail & codemask) == 0 && avail <= 0x0FFF) {
6763 codesize++;
6764 codemask = (1 << codesize) - 1;
6765 }
6766
6767 oldcode = code;
6768 } else {
6769 return stbi__errpuc("illegal code in raster", "Corrupt GIF");
6770 }
6771 }
6772 }
6773}
6774
6775// this function is designed to support animated gifs, although stb_image doesn't support it
6776// two back is the image from two frames ago, used for a very specific disposal format
6777static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back)
6778{
6779 int dispose;
6780 int first_frame;
6781 int pi;
6782 int pcount;
6783 STBI_NOTUSED(req_comp);
6784
6785 // on first frame, any non-written pixels get the background colour (non-transparent)
6786 first_frame = 0;
6787 if (g->out == 0) {
6788 if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header
6789 if (!stbi__mad3sizes_valid(4, g->w, g->h, 0))
6790 return stbi__errpuc("too large", "GIF image is too large");
6791 pcount = g->w * g->h;
6792 g->out = (stbi_uc *) stbi__malloc(4 * pcount);
6793 g->background = (stbi_uc *) stbi__malloc(4 * pcount);
6794 g->history = (stbi_uc *) stbi__malloc(pcount);
6795 if (!g->out || !g->background || !g->history)
6796 return stbi__errpuc("outofmem", "Out of memory");
6797
6798 // image is treated as "transparent" at the start - ie, nothing overwrites the current background;
6799 // background colour is only used for pixels that are not rendered first frame, after that "background"
6800 // color refers to the color that was there the previous frame.
6801 memset(g->out, 0x00, 4 * pcount);
6802 memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent)
6803 memset(g->history, 0x00, pcount); // pixels that were affected previous frame
6804 first_frame = 1;
6805 } else {
6806 // second frame - how do we dispose of the previous one?
6807 dispose = (g->eflags & 0x1C) >> 2;
6808 pcount = g->w * g->h;
6809
6810 if ((dispose == 3) && (two_back == 0)) {
6811 dispose = 2; // if I don't have an image to revert back to, default to the old background
6812 }
6813
6814 if (dispose == 3) { // use previous graphic
6815 for (pi = 0; pi < pcount; ++pi) {
6816 if (g->history[pi]) {
6817 memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 );
6818 }
6819 }
6820 } else if (dispose == 2) {
6821 // restore what was changed last frame to background before that frame;
6822 for (pi = 0; pi < pcount; ++pi) {
6823 if (g->history[pi]) {
6824 memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 );
6825 }
6826 }
6827 } else {
6828 // This is a non-disposal case eithe way, so just
6829 // leave the pixels as is, and they will become the new background
6830 // 1: do not dispose
6831 // 0: not specified.
6832 }
6833
6834 // background is what out is after the undoing of the previou frame;
6835 memcpy( g->background, g->out, 4 * g->w * g->h );
6836 }
6837
6838 // clear my history;
6839 memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame
6840
6841 for (;;) {
6842 int tag = stbi__get8(s);
6843 switch (tag) {
6844 case 0x2C: /* Image Descriptor */
6845 {
6846 stbi__int32 x, y, w, h;
6847 stbi_uc *o;
6848
6849 x = stbi__get16le(s);
6850 y = stbi__get16le(s);
6851 w = stbi__get16le(s);
6852 h = stbi__get16le(s);
6853 if (((x + w) > (g->w)) || ((y + h) > (g->h)))
6854 return stbi__errpuc("bad Image Descriptor", "Corrupt GIF");
6855
6856 g->line_size = g->w * 4;
6857 g->start_x = x * 4;
6858 g->start_y = y * g->line_size;
6859 g->max_x = g->start_x + w * 4;
6860 g->max_y = g->start_y + h * g->line_size;
6861 g->cur_x = g->start_x;
6862 g->cur_y = g->start_y;
6863
6864 // if the width of the specified rectangle is 0, that means
6865 // we may not see *any* pixels or the image is malformed;
6866 // to make sure this is caught, move the current y down to
6867 // max_y (which is what out_gif_code checks).
6868 if (w == 0)
6869 g->cur_y = g->max_y;
6870
6871 g->lflags = stbi__get8(s);
6872
6873 if (g->lflags & 0x40) {
6874 g->step = 8 * g->line_size; // first interlaced spacing
6875 g->parse = 3;
6876 } else {
6877 g->step = g->line_size;
6878 g->parse = 0;
6879 }
6880
6881 if (g->lflags & 0x80) {
6882 stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);
6883 g->color_table = (stbi_uc *) g->lpal;
6884 } else if (g->flags & 0x80) {
6885 g->color_table = (stbi_uc *) g->pal;
6886 } else
6887 return stbi__errpuc("missing color table", "Corrupt GIF");
6888
6889 o = stbi__process_gif_raster(s, g);
6890 if (!o) return NULL;
6891
6892 // if this was the first frame,
6893 pcount = g->w * g->h;
6894 if (first_frame && (g->bgindex > 0)) {
6895 // if first frame, any pixel not drawn to gets the background color
6896 for (pi = 0; pi < pcount; ++pi) {
6897 if (g->history[pi] == 0) {
6898 g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be;
6899 memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 );
6900 }
6901 }
6902 }
6903
6904 return o;
6905 }
6906
6907 case 0x21: // Comment Extension.
6908 {
6909 int len;
6910 int ext = stbi__get8(s);
6911 if (ext == 0xF9) { // Graphic Control Extension.
6912 len = stbi__get8(s);
6913 if (len == 4) {
6914 g->eflags = stbi__get8(s);
6915 g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths.
6916
6917 // unset old transparent
6918 if (g->transparent >= 0) {
6919 g->pal[g->transparent][3] = 255;
6920 }
6921 if (g->eflags & 0x01) {
6922 g->transparent = stbi__get8(s);
6923 if (g->transparent >= 0) {
6924 g->pal[g->transparent][3] = 0;
6925 }
6926 } else {
6927 // don't need transparent
6928 stbi__skip(s, 1);
6929 g->transparent = -1;
6930 }
6931 } else {
6932 stbi__skip(s, len);
6933 break;
6934 }
6935 }
6936 while ((len = stbi__get8(s)) != 0) {
6937 stbi__skip(s, len);
6938 }
6939 break;
6940 }
6941
6942 case 0x3B: // gif stream termination code
6943 return (stbi_uc *) s; // using '1' causes warning on some compilers
6944
6945 default:
6946 return stbi__errpuc("unknown code", "Corrupt GIF");
6947 }
6948 }
6949}
6950
6951static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays)
6952{
6953 STBI_FREE(g->out);
6954 STBI_FREE(g->history);
6955 STBI_FREE(g->background);
6956
6957 if (out) STBI_FREE(out);
6958 if (delays && *delays) STBI_FREE(*delays);
6959 return stbi__errpuc("outofmem", "Out of memory");
6960}
6961
6962static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
6963{
6964 if (stbi__gif_test(s)) {
6965 int layers = 0;
6966 stbi_uc *u = 0;
6967 stbi_uc *out = 0;
6968 stbi_uc *two_back = 0;
6969 stbi__gif g;
6970 int stride;
6971 int out_size = 0;
6972 int delays_size = 0;
6973
6974 STBI_NOTUSED(out_size);
6975 STBI_NOTUSED(delays_size);
6976
6977 memset(&g, 0, sizeof(g));
6978 if (delays) {
6979 *delays = 0;
6980 }
6981
6982 do {
6983 u = stbi__gif_load_next(s, &g, comp, req_comp, two_back);
6984 if (u == (stbi_uc *) s) u = 0; // end of animated gif marker
6985
6986 if (u) {
6987 *x = g.w;
6988 *y = g.h;
6989 ++layers;
6990 stride = g.w * g.h * 4;
6991
6992 if (out) {
6993 void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride );
6994 if (!tmp)
6995 return stbi__load_gif_main_outofmem(&g, out, delays);
6996 else {
6997 out = (stbi_uc*) tmp;
6998 out_size = layers * stride;
6999 }
7000
7001 if (delays) {
7002 int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers );
7003 if (!new_delays)
7004 return stbi__load_gif_main_outofmem(&g, out, delays);
7005 *delays = new_delays;
7006 delays_size = layers * sizeof(int);
7007 }
7008 } else {
7009 out = (stbi_uc*)stbi__malloc( layers * stride );
7010 if (!out)
7011 return stbi__load_gif_main_outofmem(&g, out, delays);
7012 out_size = layers * stride;
7013 if (delays) {
7014 *delays = (int*) stbi__malloc( layers * sizeof(int) );
7015 if (!*delays)
7016 return stbi__load_gif_main_outofmem(&g, out, delays);
7017 delays_size = layers * sizeof(int);
7018 }
7019 }
7020 memcpy( out + ((layers - 1) * stride), u, stride );
7021 if (layers >= 2) {
7022 two_back = out - 2 * stride;
7023 }
7024
7025 if (delays) {
7026 (*delays)[layers - 1U] = g.delay;
7027 }
7028 }
7029 } while (u != 0);
7030
7031 // free temp buffer;
7032 STBI_FREE(g.out);
7033 STBI_FREE(g.history);
7034 STBI_FREE(g.background);
7035
7036 // do the final conversion after loading everything;
7037 if (req_comp && req_comp != 4)
7038 out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h);
7039
7040 *z = layers;
7041 return out;
7042 } else {
7043 return stbi__errpuc("not GIF", "Image was not as a gif type.");
7044 }
7045}
7046
7047static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
7048{
7049 stbi_uc *u = 0;
7050 stbi__gif g;
7051 memset(&g, 0, sizeof(g));
7052 STBI_NOTUSED(ri);
7053
7054 u = stbi__gif_load_next(s, &g, comp, req_comp, 0);
7055 if (u == (stbi_uc *) s) u = 0; // end of animated gif marker
7056 if (u) {
7057 *x = g.w;
7058 *y = g.h;
7059
7060 // moved conversion to after successful load so that the same
7061 // can be done for multiple frames.
7062 if (req_comp && req_comp != 4)
7063 u = stbi__convert_format(u, 4, req_comp, g.w, g.h);
7064 } else if (g.out) {
7065 // if there was an error and we allocated an image buffer, free it!
7066 STBI_FREE(g.out);
7067 }
7068
7069 // free buffers needed for multiple frame loading;
7070 STBI_FREE(g.history);
7071 STBI_FREE(g.background);
7072
7073 return u;
7074}
7075
7076static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp)
7077{
7078 return stbi__gif_info_raw(s,x,y,comp);
7079}
7080#endif
7081
7082// *************************************************************************************************
7083// Radiance RGBE HDR loader
7084// originally by Nicolas Schulz
7085#ifndef STBI_NO_HDR
7086static int stbi__hdr_test_core(stbi__context *s, const char *signature)
7087{
7088 int i;
7089 for (i=0; signature[i]; ++i)
7090 if (stbi__get8(s) != signature[i])
7091 return 0;
7092 stbi__rewind(s);
7093 return 1;
7094}
7095
7096static int stbi__hdr_test(stbi__context* s)
7097{
7098 int r = stbi__hdr_test_core(s, "#?RADIANCE\n");
7099 stbi__rewind(s);
7100 if(!r) {
7101 r = stbi__hdr_test_core(s, "#?RGBE\n");
7102 stbi__rewind(s);
7103 }
7104 return r;
7105}
7106
7107#define STBI__HDR_BUFLEN 1024
7108static char *stbi__hdr_gettoken(stbi__context *z, char *buffer)
7109{
7110 int len=0;
7111 char c = '\0';
7112
7113 c = (char) stbi__get8(z);
7114
7115 while (!stbi__at_eof(z) && c != '\n') {
7116 buffer[len++] = c;
7117 if (len == STBI__HDR_BUFLEN-1) {
7118 // flush to end of line
7119 while (!stbi__at_eof(z) && stbi__get8(z) != '\n')
7120 ;
7121 break;
7122 }
7123 c = (char) stbi__get8(z);
7124 }
7125
7126 buffer[len] = 0;
7127 return buffer;
7128}
7129
7130static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp)
7131{
7132 if ( input[3] != 0 ) {
7133 float f1;
7134 // Exponent
7135 f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8));
7136 if (req_comp <= 2)
7137 output[0] = (input[0] + input[1] + input[2]) * f1 / 3;
7138 else {
7139 output[0] = input[0] * f1;
7140 output[1] = input[1] * f1;
7141 output[2] = input[2] * f1;
7142 }
7143 if (req_comp == 2) output[1] = 1;
7144 if (req_comp == 4) output[3] = 1;
7145 } else {
7146 switch (req_comp) {
7147 case 4: output[3] = 1; /* fallthrough */
7148 case 3: output[0] = output[1] = output[2] = 0;
7149 break;
7150 case 2: output[1] = 1; /* fallthrough */
7151 case 1: output[0] = 0;
7152 break;
7153 }
7154 }
7155}
7156
7157static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
7158{
7159 char buffer[STBI__HDR_BUFLEN];
7160 char *token;
7161 int valid = 0;
7162 int width, height;
7163 stbi_uc *scanline;
7164 float *hdr_data;
7165 int len;
7166 unsigned char count, value;
7167 int i, j, k, c1,c2, z;
7168 const char *headerToken;
7169 STBI_NOTUSED(ri);
7170
7171 // Check identifier
7172 headerToken = stbi__hdr_gettoken(s,buffer);
7173 if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0)
7174 return stbi__errpf("not HDR", "Corrupt HDR image");
7175
7176 // Parse header
7177 for(;;) {
7178 token = stbi__hdr_gettoken(s,buffer);
7179 if (token[0] == 0) break;
7180 if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
7181 }
7182
7183 if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format");
7184
7185 // Parse width and height
7186 // can't use sscanf() if we're not using stdio!
7187 token = stbi__hdr_gettoken(s,buffer);
7188 if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format");
7189 token += 3;
7190 height = (int) strtol(token, &token, 10);
7191 while (*token == ' ') ++token;
7192 if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format");
7193 token += 3;
7194 width = (int) strtol(token, NULL, 10);
7195
7196 if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)");
7197 if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)");
7198
7199 *x = width;
7200 *y = height;
7201
7202 if (comp) *comp = 3;
7203 if (req_comp == 0) req_comp = 3;
7204
7205 if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0))
7206 return stbi__errpf("too large", "HDR image is too large");
7207
7208 // Read data
7209 hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0);
7210 if (!hdr_data)
7211 return stbi__errpf("outofmem", "Out of memory");
7212
7213 // Load image data
7214 // image data is stored as some number of sca
7215 if ( width < 8 || width >= 32768) {
7216 // Read flat data
7217 for (j=0; j < height; ++j) {
7218 for (i=0; i < width; ++i) {
7219 stbi_uc rgbe[4];
7220 main_decode_loop:
7221 stbi__getn(s, rgbe, 4);
7222 stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);
7223 }
7224 }
7225 } else {
7226 // Read RLE-encoded data
7227 scanline = NULL;
7228
7229 for (j = 0; j < height; ++j) {
7230 c1 = stbi__get8(s);
7231 c2 = stbi__get8(s);
7232 len = stbi__get8(s);
7233 if (c1 != 2 || c2 != 2 || (len & 0x80)) {
7234 // not run-length encoded, so we have to actually use THIS data as a decoded
7235 // pixel (note this can't be a valid pixel--one of RGB must be >= 128)
7236 stbi_uc rgbe[4];
7237 rgbe[0] = (stbi_uc) c1;
7238 rgbe[1] = (stbi_uc) c2;
7239 rgbe[2] = (stbi_uc) len;
7240 rgbe[3] = (stbi_uc) stbi__get8(s);
7241 stbi__hdr_convert(hdr_data, rgbe, req_comp);
7242 i = 1;
7243 j = 0;
7244 STBI_FREE(scanline);
7245 goto main_decode_loop; // yes, this makes no sense
7246 }
7247 len <<= 8;
7248 len |= stbi__get8(s);
7249 if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); }
7250 if (scanline == NULL) {
7251 scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0);
7252 if (!scanline) {
7253 STBI_FREE(hdr_data);
7254 return stbi__errpf("outofmem", "Out of memory");
7255 }
7256 }
7257
7258 for (k = 0; k < 4; ++k) {
7259 int nleft;
7260 i = 0;
7261 while ((nleft = width - i) > 0) {
7262 count = stbi__get8(s);
7263 if (count > 128) {
7264 // Run
7265 value = stbi__get8(s);
7266 count -= 128;
7267 if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
7268 for (z = 0; z < count; ++z)
7269 scanline[i++ * 4 + k] = value;
7270 } else {
7271 // Dump
7272 if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
7273 for (z = 0; z < count; ++z)
7274 scanline[i++ * 4 + k] = stbi__get8(s);
7275 }
7276 }
7277 }
7278 for (i=0; i < width; ++i)
7279 stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp);
7280 }
7281 if (scanline)
7282 STBI_FREE(scanline);
7283 }
7284
7285 return hdr_data;
7286}
7287
7288static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp)
7289{
7290 char buffer[STBI__HDR_BUFLEN];
7291 char *token;
7292 int valid = 0;
7293 int dummy;
7294
7295 if (!x) x = &dummy;
7296 if (!y) y = &dummy;
7297 if (!comp) comp = &dummy;
7298
7299 if (stbi__hdr_test(s) == 0) {
7300 stbi__rewind( s );
7301 return 0;
7302 }
7303
7304 for(;;) {
7305 token = stbi__hdr_gettoken(s,buffer);
7306 if (token[0] == 0) break;
7307 if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
7308 }
7309
7310 if (!valid) {
7311 stbi__rewind( s );
7312 return 0;
7313 }
7314 token = stbi__hdr_gettoken(s,buffer);
7315 if (strncmp(token, "-Y ", 3)) {
7316 stbi__rewind( s );
7317 return 0;
7318 }
7319 token += 3;
7320 *y = (int) strtol(token, &token, 10);
7321 while (*token == ' ') ++token;
7322 if (strncmp(token, "+X ", 3)) {
7323 stbi__rewind( s );
7324 return 0;
7325 }
7326 token += 3;
7327 *x = (int) strtol(token, NULL, 10);
7328 *comp = 3;
7329 return 1;
7330}
7331#endif // STBI_NO_HDR
7332
7333#ifndef STBI_NO_BMP
7334static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)
7335{
7336 void *p;
7337 stbi__bmp_data info;
7338
7339 info.all_a = 255;
7340 p = stbi__bmp_parse_header(s, &info);
7341 if (p == NULL) {
7342 stbi__rewind( s );
7343 return 0;
7344 }
7345 if (x) *x = s->img_x;
7346 if (y) *y = s->img_y;
7347 if (comp) {
7348 if (info.bpp == 24 && info.ma == 0xff000000)
7349 *comp = 3;
7350 else
7351 *comp = info.ma ? 4 : 3;
7352 }
7353 return 1;
7354}
7355#endif
7356
7357#ifndef STBI_NO_PSD
7358static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp)
7359{
7360 int channelCount, dummy, depth;
7361 if (!x) x = &dummy;
7362 if (!y) y = &dummy;
7363 if (!comp) comp = &dummy;
7364 if (stbi__get32be(s) != 0x38425053) {
7365 stbi__rewind( s );
7366 return 0;
7367 }
7368 if (stbi__get16be(s) != 1) {
7369 stbi__rewind( s );
7370 return 0;
7371 }
7372 stbi__skip(s, 6);
7373 channelCount = stbi__get16be(s);
7374 if (channelCount < 0 || channelCount > 16) {
7375 stbi__rewind( s );
7376 return 0;
7377 }
7378 *y = stbi__get32be(s);
7379 *x = stbi__get32be(s);
7380 depth = stbi__get16be(s);
7381 if (depth != 8 && depth != 16) {
7382 stbi__rewind( s );
7383 return 0;
7384 }
7385 if (stbi__get16be(s) != 3) {
7386 stbi__rewind( s );
7387 return 0;
7388 }
7389 *comp = 4;
7390 return 1;
7391}
7392
7393static int stbi__psd_is16(stbi__context *s)
7394{
7395 int channelCount, depth;
7396 if (stbi__get32be(s) != 0x38425053) {
7397 stbi__rewind( s );
7398 return 0;
7399 }
7400 if (stbi__get16be(s) != 1) {
7401 stbi__rewind( s );
7402 return 0;
7403 }
7404 stbi__skip(s, 6);
7405 channelCount = stbi__get16be(s);
7406 if (channelCount < 0 || channelCount > 16) {
7407 stbi__rewind( s );
7408 return 0;
7409 }
7410 STBI_NOTUSED(stbi__get32be(s));
7411 STBI_NOTUSED(stbi__get32be(s));
7412 depth = stbi__get16be(s);
7413 if (depth != 16) {
7414 stbi__rewind( s );
7415 return 0;
7416 }
7417 return 1;
7418}
7419#endif
7420
7421#ifndef STBI_NO_PIC
7422static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)
7423{
7424 int act_comp=0,num_packets=0,chained,dummy;
7425 stbi__pic_packet packets[10];
7426
7427 if (!x) x = &dummy;
7428 if (!y) y = &dummy;
7429 if (!comp) comp = &dummy;
7430
7431 if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) {
7432 stbi__rewind(s);
7433 return 0;
7434 }
7435
7436 stbi__skip(s, 88);
7437
7438 *x = stbi__get16be(s);
7439 *y = stbi__get16be(s);
7440 if (stbi__at_eof(s)) {
7441 stbi__rewind( s);
7442 return 0;
7443 }
7444 if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {
7445 stbi__rewind( s );
7446 return 0;
7447 }
7448
7449 stbi__skip(s, 8);
7450
7451 do {
7452 stbi__pic_packet *packet;
7453
7454 if (num_packets==sizeof(packets)/sizeof(packets[0]))
7455 return 0;
7456
7457 packet = &packets[num_packets++];
7458 chained = stbi__get8(s);
7459 packet->size = stbi__get8(s);
7460 packet->type = stbi__get8(s);
7461 packet->channel = stbi__get8(s);
7462 act_comp |= packet->channel;
7463
7464 if (stbi__at_eof(s)) {
7465 stbi__rewind( s );
7466 return 0;
7467 }
7468 if (packet->size != 8) {
7469 stbi__rewind( s );
7470 return 0;
7471 }
7472 } while (chained);
7473
7474 *comp = (act_comp & 0x10 ? 4 : 3);
7475
7476 return 1;
7477}
7478#endif
7479
7480// *************************************************************************************************
7481// Portable Gray Map and Portable Pixel Map loader
7482// by Ken Miller
7483//
7484// PGM: http://netpbm.sourceforge.net/doc/pgm.html
7485// PPM: http://netpbm.sourceforge.net/doc/ppm.html
7486//
7487// Known limitations:
7488// Does not support comments in the header section
7489// Does not support ASCII image data (formats P2 and P3)
7490
7491#ifndef STBI_NO_PNM
7492
7493static int stbi__pnm_test(stbi__context *s)
7494{
7495 char p, t;
7496 p = (char) stbi__get8(s);
7497 t = (char) stbi__get8(s);
7498 if (p != 'P' || (t != '5' && t != '6')) {
7499 stbi__rewind( s );
7500 return 0;
7501 }
7502 return 1;
7503}
7504
7505static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
7506{
7507 stbi_uc *out;
7508 STBI_NOTUSED(ri);
7509
7510 ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n);
7511 if (ri->bits_per_channel == 0)
7512 return 0;
7513
7514 if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
7515 if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
7516
7517 *x = s->img_x;
7518 *y = s->img_y;
7519 if (comp) *comp = s->img_n;
7520
7521 if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0))
7522 return stbi__errpuc("too large", "PNM too large");
7523
7524 out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0);
7525 if (!out) return stbi__errpuc("outofmem", "Out of memory");
7526 if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) {
7527 STBI_FREE(out);
7528 return stbi__errpuc("bad PNM", "PNM file truncated");
7529 }
7530
7531 if (req_comp && req_comp != s->img_n) {
7532 if (ri->bits_per_channel == 16) {
7533 out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y);
7534 } else {
7535 out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
7536 }
7537 if (out == NULL) return out; // stbi__convert_format frees input on failure
7538 }
7539 return out;
7540}
7541
7542static int stbi__pnm_isspace(char c)
7543{
7544 return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
7545}
7546
7547static void stbi__pnm_skip_whitespace(stbi__context *s, char *c)
7548{
7549 for (;;) {
7550 while (!stbi__at_eof(s) && stbi__pnm_isspace(*c))
7551 *c = (char) stbi__get8(s);
7552
7553 if (stbi__at_eof(s) || *c != '#')
7554 break;
7555
7556 while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' )
7557 *c = (char) stbi__get8(s);
7558 }
7559}
7560
7561static int stbi__pnm_isdigit(char c)
7562{
7563 return c >= '0' && c <= '9';
7564}
7565
7566static int stbi__pnm_getinteger(stbi__context *s, char *c)
7567{
7568 int value = 0;
7569
7570 while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) {
7571 value = value*10 + (*c - '0');
7572 *c = (char) stbi__get8(s);
7573 if((value > 214748364) || (value == 214748364 && *c > '7'))
7574 return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int");
7575 }
7576
7577 return value;
7578}
7579
7580static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)
7581{
7582 int maxv, dummy;
7583 char c, p, t;
7584
7585 if (!x) x = &dummy;
7586 if (!y) y = &dummy;
7587 if (!comp) comp = &dummy;
7588
7589 stbi__rewind(s);
7590
7591 // Get identifier
7592 p = (char) stbi__get8(s);
7593 t = (char) stbi__get8(s);
7594 if (p != 'P' || (t != '5' && t != '6')) {
7595 stbi__rewind(s);
7596 return 0;
7597 }
7598
7599 *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm
7600
7601 c = (char) stbi__get8(s);
7602 stbi__pnm_skip_whitespace(s, &c);
7603
7604 *x = stbi__pnm_getinteger(s, &c); // read width
7605 if(*x == 0)
7606 return stbi__err("invalid width", "PPM image header had zero or overflowing width");
7607 stbi__pnm_skip_whitespace(s, &c);
7608
7609 *y = stbi__pnm_getinteger(s, &c); // read height
7610 if (*y == 0)
7611 return stbi__err("invalid width", "PPM image header had zero or overflowing width");
7612 stbi__pnm_skip_whitespace(s, &c);
7613
7614 maxv = stbi__pnm_getinteger(s, &c); // read max value
7615 if (maxv > 65535)
7616 return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images");
7617 else if (maxv > 255)
7618 return 16;
7619 else
7620 return 8;
7621}
7622
7623static int stbi__pnm_is16(stbi__context *s)
7624{
7625 if (stbi__pnm_info(s, NULL, NULL, NULL) == 16)
7626 return 1;
7627 return 0;
7628}
7629#endif
7630
7631static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp)
7632{
7633 #ifndef STBI_NO_JPEG
7634 if (stbi__jpeg_info(s, x, y, comp)) return 1;
7635 #endif
7636
7637 #ifndef STBI_NO_PNG
7638 if (stbi__png_info(s, x, y, comp)) return 1;
7639 #endif
7640
7641 #ifndef STBI_NO_GIF
7642 if (stbi__gif_info(s, x, y, comp)) return 1;
7643 #endif
7644
7645 #ifndef STBI_NO_BMP
7646 if (stbi__bmp_info(s, x, y, comp)) return 1;
7647 #endif
7648
7649 #ifndef STBI_NO_PSD
7650 if (stbi__psd_info(s, x, y, comp)) return 1;
7651 #endif
7652
7653 #ifndef STBI_NO_PIC
7654 if (stbi__pic_info(s, x, y, comp)) return 1;
7655 #endif
7656
7657 #ifndef STBI_NO_PNM
7658 if (stbi__pnm_info(s, x, y, comp)) return 1;
7659 #endif
7660
7661 #ifndef STBI_NO_HDR
7662 if (stbi__hdr_info(s, x, y, comp)) return 1;
7663 #endif
7664
7665 // test tga last because it's a crappy test!
7666 #ifndef STBI_NO_TGA
7667 if (stbi__tga_info(s, x, y, comp))
7668 return 1;
7669 #endif
7670 return stbi__err("unknown image type", "Image not of any known type, or corrupt");
7671}
7672
7673static int stbi__is_16_main(stbi__context *s)
7674{
7675 #ifndef STBI_NO_PNG
7676 if (stbi__png_is16(s)) return 1;
7677 #endif
7678
7679 #ifndef STBI_NO_PSD
7680 if (stbi__psd_is16(s)) return 1;
7681 #endif
7682
7683 #ifndef STBI_NO_PNM
7684 if (stbi__pnm_is16(s)) return 1;
7685 #endif
7686 return 0;
7687}
7688
7689#ifndef STBI_NO_STDIO
7690STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp)
7691{
7692 FILE *f = stbi__fopen(filename, "rb");
7693 int result;
7694 if (!f) return stbi__err("can't fopen", "Unable to open file");
7695 result = stbi_info_from_file(f, x, y, comp);
7696 fclose(f);
7697 return result;
7698}
7699
7700STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp)
7701{
7702 int r;
7703 stbi__context s;
7704 long pos = ftell(f);
7705 stbi__start_file(&s, f);
7706 r = stbi__info_main(&s,x,y,comp);
7707 fseek(f,pos,SEEK_SET);
7708 return r;
7709}
7710
7711STBIDEF int stbi_is_16_bit(char const *filename)
7712{
7713 FILE *f = stbi__fopen(filename, "rb");
7714 int result;
7715 if (!f) return stbi__err("can't fopen", "Unable to open file");
7716 result = stbi_is_16_bit_from_file(f);
7717 fclose(f);
7718 return result;
7719}
7720
7721STBIDEF int stbi_is_16_bit_from_file(FILE *f)
7722{
7723 int r;
7724 stbi__context s;
7725 long pos = ftell(f);
7726 stbi__start_file(&s, f);
7727 r = stbi__is_16_main(&s);
7728 fseek(f,pos,SEEK_SET);
7729 return r;
7730}
7731#endif // !STBI_NO_STDIO
7732
7733STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp)
7734{
7735 stbi__context s;
7736 stbi__start_mem(&s,buffer,len);
7737 return stbi__info_main(&s,x,y,comp);
7738}
7739
7740STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp)
7741{
7742 stbi__context s;
7743 stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
7744 return stbi__info_main(&s,x,y,comp);
7745}
7746
7747STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len)
7748{
7749 stbi__context s;
7750 stbi__start_mem(&s,buffer,len);
7751 return stbi__is_16_main(&s);
7752}
7753
7754STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user)
7755{
7756 stbi__context s;
7757 stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
7758 return stbi__is_16_main(&s);
7759}
7760
7761#endif // STB_IMAGE_IMPLEMENTATION
7762
7763/*
7764 revision history:
7765 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
7766 2.19 (2018-02-11) fix warning
7767 2.18 (2018-01-30) fix warnings
7768 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug
7769 1-bit BMP
7770 *_is_16_bit api
7771 avoid warnings
7772 2.16 (2017-07-23) all functions have 16-bit variants;
7773 STBI_NO_STDIO works again;
7774 compilation fixes;
7775 fix rounding in unpremultiply;
7776 optimize vertical flip;
7777 disable raw_len validation;
7778 documentation fixes
7779 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode;
7780 warning fixes; disable run-time SSE detection on gcc;
7781 uniform handling of optional "return" values;
7782 thread-safe initialization of zlib tables
7783 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
7784 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now
7785 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
7786 2.11 (2016-04-02) allocate large structures on the stack
7787 remove white matting for transparent PSD
7788 fix reported channel count for PNG & BMP
7789 re-enable SSE2 in non-gcc 64-bit
7790 support RGB-formatted JPEG
7791 read 16-bit PNGs (only as 8-bit)
7792 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED
7793 2.09 (2016-01-16) allow comments in PNM files
7794 16-bit-per-pixel TGA (not bit-per-component)
7795 info() for TGA could break due to .hdr handling
7796 info() for BMP to shares code instead of sloppy parse
7797 can use STBI_REALLOC_SIZED if allocator doesn't support realloc
7798 code cleanup
7799 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA
7800 2.07 (2015-09-13) fix compiler warnings
7801 partial animated GIF support
7802 limited 16-bpc PSD support
7803 #ifdef unused functions
7804 bug with < 92 byte PIC,PNM,HDR,TGA
7805 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value
7806 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning
7807 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit
7808 2.03 (2015-04-12) extra corruption checking (mmozeiko)
7809 stbi_set_flip_vertically_on_load (nguillemot)
7810 fix NEON support; fix mingw support
7811 2.02 (2015-01-19) fix incorrect assert, fix warning
7812 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2
7813 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG
7814 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg)
7815 progressive JPEG (stb)
7816 PGM/PPM support (Ken Miller)
7817 STBI_MALLOC,STBI_REALLOC,STBI_FREE
7818 GIF bugfix -- seemingly never worked
7819 STBI_NO_*, STBI_ONLY_*
7820 1.48 (2014-12-14) fix incorrectly-named assert()
7821 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb)
7822 optimize PNG (ryg)
7823 fix bug in interlaced PNG with user-specified channel count (stb)
7824 1.46 (2014-08-26)
7825 fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG
7826 1.45 (2014-08-16)
7827 fix MSVC-ARM internal compiler error by wrapping malloc
7828 1.44 (2014-08-07)
7829 various warning fixes from Ronny Chevalier
7830 1.43 (2014-07-15)
7831 fix MSVC-only compiler problem in code changed in 1.42
7832 1.42 (2014-07-09)
7833 don't define _CRT_SECURE_NO_WARNINGS (affects user code)
7834 fixes to stbi__cleanup_jpeg path
7835 added STBI_ASSERT to avoid requiring assert.h
7836 1.41 (2014-06-25)
7837 fix search&replace from 1.36 that messed up comments/error messages
7838 1.40 (2014-06-22)
7839 fix gcc struct-initialization warning
7840 1.39 (2014-06-15)
7841 fix to TGA optimization when req_comp != number of components in TGA;
7842 fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite)
7843 add support for BMP version 5 (more ignored fields)
7844 1.38 (2014-06-06)
7845 suppress MSVC warnings on integer casts truncating values
7846 fix accidental rename of 'skip' field of I/O
7847 1.37 (2014-06-04)
7848 remove duplicate typedef
7849 1.36 (2014-06-03)
7850 convert to header file single-file library
7851 if de-iphone isn't set, load iphone images color-swapped instead of returning NULL
7852 1.35 (2014-05-27)
7853 various warnings
7854 fix broken STBI_SIMD path
7855 fix bug where stbi_load_from_file no longer left file pointer in correct place
7856 fix broken non-easy path for 32-bit BMP (possibly never used)
7857 TGA optimization by Arseny Kapoulkine
7858 1.34 (unknown)
7859 use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case
7860 1.33 (2011-07-14)
7861 make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements
7862 1.32 (2011-07-13)
7863 support for "info" function for all supported filetypes (SpartanJ)
7864 1.31 (2011-06-20)
7865 a few more leak fixes, bug in PNG handling (SpartanJ)
7866 1.30 (2011-06-11)
7867 added ability to load files via callbacks to accomidate custom input streams (Ben Wenger)
7868 removed deprecated format-specific test/load functions
7869 removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway
7870 error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha)
7871 fix inefficiency in decoding 32-bit BMP (David Woo)
7872 1.29 (2010-08-16)
7873 various warning fixes from Aurelien Pocheville
7874 1.28 (2010-08-01)
7875 fix bug in GIF palette transparency (SpartanJ)
7876 1.27 (2010-08-01)
7877 cast-to-stbi_uc to fix warnings
7878 1.26 (2010-07-24)
7879 fix bug in file buffering for PNG reported by SpartanJ
7880 1.25 (2010-07-17)
7881 refix trans_data warning (Won Chun)
7882 1.24 (2010-07-12)
7883 perf improvements reading from files on platforms with lock-heavy fgetc()
7884 minor perf improvements for jpeg
7885 deprecated type-specific functions so we'll get feedback if they're needed
7886 attempt to fix trans_data warning (Won Chun)
7887 1.23 fixed bug in iPhone support
7888 1.22 (2010-07-10)
7889 removed image *writing* support
7890 stbi_info support from Jetro Lauha
7891 GIF support from Jean-Marc Lienher
7892 iPhone PNG-extensions from James Brown
7893 warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva)
7894 1.21 fix use of 'stbi_uc' in header (reported by jon blow)
7895 1.20 added support for Softimage PIC, by Tom Seddon
7896 1.19 bug in interlaced PNG corruption check (found by ryg)
7897 1.18 (2008-08-02)
7898 fix a threading bug (local mutable static)
7899 1.17 support interlaced PNG
7900 1.16 major bugfix - stbi__convert_format converted one too many pixels
7901 1.15 initialize some fields for thread safety
7902 1.14 fix threadsafe conversion bug
7903 header-file-only version (#define STBI_HEADER_FILE_ONLY before including)
7904 1.13 threadsafe
7905 1.12 const qualifiers in the API
7906 1.11 Support installable IDCT, colorspace conversion routines
7907 1.10 Fixes for 64-bit (don't use "unsigned long")
7908 optimized upsampling by Fabian "ryg" Giesen
7909 1.09 Fix format-conversion for PSD code (bad global variables!)
7910 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz
7911 1.07 attempt to fix C++ warning/errors again
7912 1.06 attempt to fix C++ warning/errors again
7913 1.05 fix TGA loading to return correct *comp and use good luminance calc
7914 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free
7915 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR
7916 1.02 support for (subset of) HDR files, float interface for preferred access to them
7917 1.01 fix bug: possible bug in handling right-side up bmps... not sure
7918 fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all
7919 1.00 interface to zlib that skips zlib header
7920 0.99 correct handling of alpha in palette
7921 0.98 TGA loader by lonesock; dynamically add loaders (untested)
7922 0.97 jpeg errors on too large a file; also catch another malloc failure
7923 0.96 fix detection of invalid v value - particleman@mollyrocket forum
7924 0.95 during header scan, seek to markers in case of padding
7925 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same
7926 0.93 handle jpegtran output; verbose errors
7927 0.92 read 4,8,16,24,32-bit BMP files of several formats
7928 0.91 output 24-bit Windows 3.0 BMP files
7929 0.90 fix a few more warnings; bump version number to approach 1.0
7930 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd
7931 0.60 fix compiling as c++
7932 0.59 fix warnings: merge Dave Moore's -Wall fixes
7933 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian
7934 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available
7935 0.56 fix bug: zlib uncompressed mode len vs. nlen
7936 0.55 fix bug: restart_interval not initialized to 0
7937 0.54 allow NULL for 'int *comp'
7938 0.53 fix bug in png 3->4; speedup png decoding
7939 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments
7940 0.51 obey req_comp requests, 1-component jpegs return as 1-component,
7941 on 'test' only check type, not whether we support this variant
7942 0.50 (2006-11-19)
7943 first released version
7944*/
7945
7946
7947/*
7948------------------------------------------------------------------------------
7949This software is available under 2 licenses -- choose whichever you prefer.
7950------------------------------------------------------------------------------
7951ALTERNATIVE A - MIT License
7952Copyright (c) 2017 Sean Barrett
7953Permission is hereby granted, free of charge, to any person obtaining a copy of
7954this software and associated documentation files (the "Software"), to deal in
7955the Software without restriction, including without limitation the rights to
7956use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7957of the Software, and to permit persons to whom the Software is furnished to do
7958so, subject to the following conditions:
7959The above copyright notice and this permission notice shall be included in all
7960copies or substantial portions of the Software.
7961THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
7962IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7963FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7964AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
7965LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
7966OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
7967SOFTWARE.
7968------------------------------------------------------------------------------
7969ALTERNATIVE B - Public Domain (www.unlicense.org)
7970This is free and unencumbered software released into the public domain.
7971Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
7972software, either in source code form or as a compiled binary, for any purpose,
7973commercial or non-commercial, and by any means.
7974In jurisdictions that recognize copyright laws, the author or authors of this
7975software dedicate any and all copyright interest in the software to the public
7976domain. We make this dedication for the benefit of the public at large and to
7977the detriment of our heirs and successors. We intend this dedication to be an
7978overt act of relinquishment in perpetuity of all present and future rights to
7979this software under copyright law.
7980THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
7981IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7982FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7983AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
7984ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
7985WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7986------------------------------------------------------------------------------
7987*/
diff --git a/externals/stb/stb_image_resize.h b/externals/stb/stb_image_resize.h
new file mode 100644
index 000000000..ef9e6fe87
--- /dev/null
+++ b/externals/stb/stb_image_resize.h
@@ -0,0 +1,2634 @@
1/* stb_image_resize - v0.97 - public domain image resizing
2 by Jorge L Rodriguez (@VinoBS) - 2014
3 http://github.com/nothings/stb
4
5 Written with emphasis on usability, portability, and efficiency. (No
6 SIMD or threads, so it be easily outperformed by libs that use those.)
7 Only scaling and translation is supported, no rotations or shears.
8 Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation.
9
10 COMPILING & LINKING
11 In one C/C++ file that #includes this file, do this:
12 #define STB_IMAGE_RESIZE_IMPLEMENTATION
13 before the #include. That will create the implementation in that file.
14
15 QUICKSTART
16 stbir_resize_uint8( input_pixels , in_w , in_h , 0,
17 output_pixels, out_w, out_h, 0, num_channels)
18 stbir_resize_float(...)
19 stbir_resize_uint8_srgb( input_pixels , in_w , in_h , 0,
20 output_pixels, out_w, out_h, 0,
21 num_channels , alpha_chan , 0)
22 stbir_resize_uint8_srgb_edgemode(
23 input_pixels , in_w , in_h , 0,
24 output_pixels, out_w, out_h, 0,
25 num_channels , alpha_chan , 0, STBIR_EDGE_CLAMP)
26 // WRAP/REFLECT/ZERO
27
28 FULL API
29 See the "header file" section of the source for API documentation.
30
31 ADDITIONAL DOCUMENTATION
32
33 SRGB & FLOATING POINT REPRESENTATION
34 The sRGB functions presume IEEE floating point. If you do not have
35 IEEE floating point, define STBIR_NON_IEEE_FLOAT. This will use
36 a slower implementation.
37
38 MEMORY ALLOCATION
39 The resize functions here perform a single memory allocation using
40 malloc. To control the memory allocation, before the #include that
41 triggers the implementation, do:
42
43 #define STBIR_MALLOC(size,context) ...
44 #define STBIR_FREE(ptr,context) ...
45
46 Each resize function makes exactly one call to malloc/free, so to use
47 temp memory, store the temp memory in the context and return that.
48
49 ASSERT
50 Define STBIR_ASSERT(boolval) to override assert() and not use assert.h
51
52 OPTIMIZATION
53 Define STBIR_SATURATE_INT to compute clamp values in-range using
54 integer operations instead of float operations. This may be faster
55 on some platforms.
56
57 DEFAULT FILTERS
58 For functions which don't provide explicit control over what filters
59 to use, you can change the compile-time defaults with
60
61 #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something
62 #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something
63
64 See stbir_filter in the header-file section for the list of filters.
65
66 NEW FILTERS
67 A number of 1D filter kernels are used. For a list of
68 supported filters see the stbir_filter enum. To add a new filter,
69 write a filter function and add it to stbir__filter_info_table.
70
71 PROGRESS
72 For interactive use with slow resize operations, you can install
73 a progress-report callback:
74
75 #define STBIR_PROGRESS_REPORT(val) some_func(val)
76
77 The parameter val is a float which goes from 0 to 1 as progress is made.
78
79 For example:
80
81 static void my_progress_report(float progress);
82 #define STBIR_PROGRESS_REPORT(val) my_progress_report(val)
83
84 #define STB_IMAGE_RESIZE_IMPLEMENTATION
85 #include "stb_image_resize.h"
86
87 static void my_progress_report(float progress)
88 {
89 printf("Progress: %f%%\n", progress*100);
90 }
91
92 MAX CHANNELS
93 If your image has more than 64 channels, define STBIR_MAX_CHANNELS
94 to the max you'll have.
95
96 ALPHA CHANNEL
97 Most of the resizing functions provide the ability to control how
98 the alpha channel of an image is processed. The important things
99 to know about this:
100
101 1. The best mathematically-behaved version of alpha to use is
102 called "premultiplied alpha", in which the other color channels
103 have had the alpha value multiplied in. If you use premultiplied
104 alpha, linear filtering (such as image resampling done by this
105 library, or performed in texture units on GPUs) does the "right
106 thing". While premultiplied alpha is standard in the movie CGI
107 industry, it is still uncommon in the videogame/real-time world.
108
109 If you linearly filter non-premultiplied alpha, strange effects
110 occur. (For example, the 50/50 average of 99% transparent bright green
111 and 1% transparent black produces 50% transparent dark green when
112 non-premultiplied, whereas premultiplied it produces 50%
113 transparent near-black. The former introduces green energy
114 that doesn't exist in the source image.)
115
116 2. Artists should not edit premultiplied-alpha images; artists
117 want non-premultiplied alpha images. Thus, art tools generally output
118 non-premultiplied alpha images.
119
120 3. You will get best results in most cases by converting images
121 to premultiplied alpha before processing them mathematically.
122
123 4. If you pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, the
124 resizer does not do anything special for the alpha channel;
125 it is resampled identically to other channels. This produces
126 the correct results for premultiplied-alpha images, but produces
127 less-than-ideal results for non-premultiplied-alpha images.
128
129 5. If you do not pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED,
130 then the resizer weights the contribution of input pixels
131 based on their alpha values, or, equivalently, it multiplies
132 the alpha value into the color channels, resamples, then divides
133 by the resultant alpha value. Input pixels which have alpha=0 do
134 not contribute at all to output pixels unless _all_ of the input
135 pixels affecting that output pixel have alpha=0, in which case
136 the result for that pixel is the same as it would be without
137 STBIR_FLAG_ALPHA_PREMULTIPLIED. However, this is only true for
138 input images in integer formats. For input images in float format,
139 input pixels with alpha=0 have no effect, and output pixels
140 which have alpha=0 will be 0 in all channels. (For float images,
141 you can manually achieve the same result by adding a tiny epsilon
142 value to the alpha channel of every image, and then subtracting
143 or clamping it at the end.)
144
145 6. You can suppress the behavior described in #5 and make
146 all-0-alpha pixels have 0 in all channels by #defining
147 STBIR_NO_ALPHA_EPSILON.
148
149 7. You can separately control whether the alpha channel is
150 interpreted as linear or affected by the colorspace. By default
151 it is linear; you almost never want to apply the colorspace.
152 (For example, graphics hardware does not apply sRGB conversion
153 to the alpha channel.)
154
155 CONTRIBUTORS
156 Jorge L Rodriguez: Implementation
157 Sean Barrett: API design, optimizations
158 Aras Pranckevicius: bugfix
159 Nathan Reed: warning fixes
160
161 REVISIONS
162 0.97 (2020-02-02) fixed warning
163 0.96 (2019-03-04) fixed warnings
164 0.95 (2017-07-23) fixed warnings
165 0.94 (2017-03-18) fixed warnings
166 0.93 (2017-03-03) fixed bug with certain combinations of heights
167 0.92 (2017-01-02) fix integer overflow on large (>2GB) images
168 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions
169 0.90 (2014-09-17) first released version
170
171 LICENSE
172 See end of file for license information.
173
174 TODO
175 Don't decode all of the image data when only processing a partial tile
176 Don't use full-width decode buffers when only processing a partial tile
177 When processing wide images, break processing into tiles so data fits in L1 cache
178 Installable filters?
179 Resize that respects alpha test coverage
180 (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage:
181 https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp )
182*/
183
184#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H
185#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H
186
187#ifdef _MSC_VER
188typedef unsigned char stbir_uint8;
189typedef unsigned short stbir_uint16;
190typedef unsigned int stbir_uint32;
191#else
192#include <stdint.h>
193typedef uint8_t stbir_uint8;
194typedef uint16_t stbir_uint16;
195typedef uint32_t stbir_uint32;
196#endif
197
198#ifndef STBIRDEF
199#ifdef STB_IMAGE_RESIZE_STATIC
200#define STBIRDEF static
201#else
202#ifdef __cplusplus
203#define STBIRDEF extern "C"
204#else
205#define STBIRDEF extern
206#endif
207#endif
208#endif
209
210//////////////////////////////////////////////////////////////////////////////
211//
212// Easy-to-use API:
213//
214// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4)
215// * input_w is input image width (x-axis), input_h is input image height (y-axis)
216// * stride is the offset between successive rows of image data in memory, in bytes. you can
217// specify 0 to mean packed continuously in memory
218// * alpha channel is treated identically to other channels.
219// * colorspace is linear or sRGB as specified by function name
220// * returned result is 1 for success or 0 in case of an error.
221// #define STBIR_ASSERT() to trigger an assert on parameter validation errors.
222// * Memory required grows approximately linearly with input and output size, but with
223// discontinuities at input_w == output_w and input_h == output_h.
224// * These functions use a "default" resampling filter defined at compile time. To change the filter,
225// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE
226// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API.
227
228STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
229 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
230 int num_channels);
231
232STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
233 float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
234 int num_channels);
235
236
237// The following functions interpret image data as gamma-corrected sRGB.
238// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel,
239// or otherwise provide the index of the alpha channel. Flags value
240// of 0 will probably do the right thing if you're not sure what
241// the flags mean.
242
243#define STBIR_ALPHA_CHANNEL_NONE -1
244
245// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will
246// use alpha-weighted resampling (effectively premultiplying, resampling,
247// then unpremultiplying).
248#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0)
249// The specified alpha channel should be handled as gamma-corrected value even
250// when doing sRGB operations.
251#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1)
252
253STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
254 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
255 int num_channels, int alpha_channel, int flags);
256
257
258typedef enum
259{
260 STBIR_EDGE_CLAMP = 1,
261 STBIR_EDGE_REFLECT = 2,
262 STBIR_EDGE_WRAP = 3,
263 STBIR_EDGE_ZERO = 4,
264} stbir_edge;
265
266// This function adds the ability to specify how requests to sample off the edge of the image are handled.
267STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
268 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
269 int num_channels, int alpha_channel, int flags,
270 stbir_edge edge_wrap_mode);
271
272//////////////////////////////////////////////////////////////////////////////
273//
274// Medium-complexity API
275//
276// This extends the easy-to-use API as follows:
277//
278// * Alpha-channel can be processed separately
279// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE
280// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT)
281// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)
282// * Filter can be selected explicitly
283// * uint16 image type
284// * sRGB colorspace available for all types
285// * context parameter for passing to STBIR_MALLOC
286
287typedef enum
288{
289 STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses
290 STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios
291 STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering
292 STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque
293 STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline
294 STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3
295} stbir_filter;
296
297typedef enum
298{
299 STBIR_COLORSPACE_LINEAR,
300 STBIR_COLORSPACE_SRGB,
301
302 STBIR_MAX_COLORSPACES,
303} stbir_colorspace;
304
305// The following functions are all identical except for the type of the image data
306
307STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
308 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
309 int num_channels, int alpha_channel, int flags,
310 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
311 void *alloc_context);
312
313STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
314 stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
315 int num_channels, int alpha_channel, int flags,
316 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
317 void *alloc_context);
318
319STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
320 float *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
321 int num_channels, int alpha_channel, int flags,
322 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
323 void *alloc_context);
324
325
326
327//////////////////////////////////////////////////////////////////////////////
328//
329// Full-complexity API
330//
331// This extends the medium API as follows:
332//
333// * uint32 image type
334// * not typesafe
335// * separate filter types for each axis
336// * separate edge modes for each axis
337// * can specify scale explicitly for subpixel correctness
338// * can specify image source tile using texture coordinates
339
340typedef enum
341{
342 STBIR_TYPE_UINT8 ,
343 STBIR_TYPE_UINT16,
344 STBIR_TYPE_UINT32,
345 STBIR_TYPE_FLOAT ,
346
347 STBIR_MAX_TYPES
348} stbir_datatype;
349
350STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
351 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
352 stbir_datatype datatype,
353 int num_channels, int alpha_channel, int flags,
354 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
355 stbir_filter filter_horizontal, stbir_filter filter_vertical,
356 stbir_colorspace space, void *alloc_context);
357
358STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
359 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
360 stbir_datatype datatype,
361 int num_channels, int alpha_channel, int flags,
362 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
363 stbir_filter filter_horizontal, stbir_filter filter_vertical,
364 stbir_colorspace space, void *alloc_context,
365 float x_scale, float y_scale,
366 float x_offset, float y_offset);
367
368STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
369 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
370 stbir_datatype datatype,
371 int num_channels, int alpha_channel, int flags,
372 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
373 stbir_filter filter_horizontal, stbir_filter filter_vertical,
374 stbir_colorspace space, void *alloc_context,
375 float s0, float t0, float s1, float t1);
376// (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.
377
378//
379//
380//// end header file /////////////////////////////////////////////////////
381#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H
382
383
384
385
386
387#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION
388
389#ifndef STBIR_ASSERT
390#include <assert.h>
391#define STBIR_ASSERT(x) assert(x)
392#endif
393
394// For memset
395#include <string.h>
396
397#include <math.h>
398
399#ifndef STBIR_MALLOC
400#include <stdlib.h>
401// use comma operator to evaluate c, to avoid "unused parameter" warnings
402#define STBIR_MALLOC(size,c) ((void)(c), malloc(size))
403#define STBIR_FREE(ptr,c) ((void)(c), free(ptr))
404#endif
405
406#ifndef _MSC_VER
407#ifdef __cplusplus
408#define stbir__inline inline
409#else
410#define stbir__inline
411#endif
412#else
413#define stbir__inline __forceinline
414#endif
415
416
417// should produce compiler error if size is wrong
418typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1];
419
420#ifdef _MSC_VER
421#define STBIR__NOTUSED(v) (void)(v)
422#else
423#define STBIR__NOTUSED(v) (void)sizeof(v)
424#endif
425
426#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0]))
427
428#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE
429#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM
430#endif
431
432#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE
433#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL
434#endif
435
436#ifndef STBIR_PROGRESS_REPORT
437#define STBIR_PROGRESS_REPORT(float_0_to_1)
438#endif
439
440#ifndef STBIR_MAX_CHANNELS
441#define STBIR_MAX_CHANNELS 64
442#endif
443
444#if STBIR_MAX_CHANNELS > 65536
445#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536."
446// because we store the indices in 16-bit variables
447#endif
448
449// This value is added to alpha just before premultiplication to avoid
450// zeroing out color values. It is equivalent to 2^-80. If you don't want
451// that behavior (it may interfere if you have floating point images with
452// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to
453// disable it.
454#ifndef STBIR_ALPHA_EPSILON
455#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20))
456#endif
457
458
459
460#ifdef _MSC_VER
461#define STBIR__UNUSED_PARAM(v) (void)(v)
462#else
463#define STBIR__UNUSED_PARAM(v) (void)sizeof(v)
464#endif
465
466// must match stbir_datatype
467static unsigned char stbir__type_size[] = {
468 1, // STBIR_TYPE_UINT8
469 2, // STBIR_TYPE_UINT16
470 4, // STBIR_TYPE_UINT32
471 4, // STBIR_TYPE_FLOAT
472};
473
474// Kernel function centered at 0
475typedef float (stbir__kernel_fn)(float x, float scale);
476typedef float (stbir__support_fn)(float scale);
477
478typedef struct
479{
480 stbir__kernel_fn* kernel;
481 stbir__support_fn* support;
482} stbir__filter_info;
483
484// When upsampling, the contributors are which source pixels contribute.
485// When downsampling, the contributors are which destination pixels are contributed to.
486typedef struct
487{
488 int n0; // First contributing pixel
489 int n1; // Last contributing pixel
490} stbir__contributors;
491
492typedef struct
493{
494 const void* input_data;
495 int input_w;
496 int input_h;
497 int input_stride_bytes;
498
499 void* output_data;
500 int output_w;
501 int output_h;
502 int output_stride_bytes;
503
504 float s0, t0, s1, t1;
505
506 float horizontal_shift; // Units: output pixels
507 float vertical_shift; // Units: output pixels
508 float horizontal_scale;
509 float vertical_scale;
510
511 int channels;
512 int alpha_channel;
513 stbir_uint32 flags;
514 stbir_datatype type;
515 stbir_filter horizontal_filter;
516 stbir_filter vertical_filter;
517 stbir_edge edge_horizontal;
518 stbir_edge edge_vertical;
519 stbir_colorspace colorspace;
520
521 stbir__contributors* horizontal_contributors;
522 float* horizontal_coefficients;
523
524 stbir__contributors* vertical_contributors;
525 float* vertical_coefficients;
526
527 int decode_buffer_pixels;
528 float* decode_buffer;
529
530 float* horizontal_buffer;
531
532 // cache these because ceil/floor are inexplicably showing up in profile
533 int horizontal_coefficient_width;
534 int vertical_coefficient_width;
535 int horizontal_filter_pixel_width;
536 int vertical_filter_pixel_width;
537 int horizontal_filter_pixel_margin;
538 int vertical_filter_pixel_margin;
539 int horizontal_num_contributors;
540 int vertical_num_contributors;
541
542 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)
543 int ring_buffer_num_entries; // Total number of entries in the ring buffer.
544 int ring_buffer_first_scanline;
545 int ring_buffer_last_scanline;
546 int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer
547 float* ring_buffer;
548
549 float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds.
550
551 int horizontal_contributors_size;
552 int horizontal_coefficients_size;
553 int vertical_contributors_size;
554 int vertical_coefficients_size;
555 int decode_buffer_size;
556 int horizontal_buffer_size;
557 int ring_buffer_size;
558 int encode_buffer_size;
559} stbir__info;
560
561
562static const float stbir__max_uint8_as_float = 255.0f;
563static const float stbir__max_uint16_as_float = 65535.0f;
564static const double stbir__max_uint32_as_float = 4294967295.0;
565
566
567static stbir__inline int stbir__min(int a, int b)
568{
569 return a < b ? a : b;
570}
571
572static stbir__inline float stbir__saturate(float x)
573{
574 if (x < 0)
575 return 0;
576
577 if (x > 1)
578 return 1;
579
580 return x;
581}
582
583#ifdef STBIR_SATURATE_INT
584static stbir__inline stbir_uint8 stbir__saturate8(int x)
585{
586 if ((unsigned int) x <= 255)
587 return x;
588
589 if (x < 0)
590 return 0;
591
592 return 255;
593}
594
595static stbir__inline stbir_uint16 stbir__saturate16(int x)
596{
597 if ((unsigned int) x <= 65535)
598 return x;
599
600 if (x < 0)
601 return 0;
602
603 return 65535;
604}
605#endif
606
607static float stbir__srgb_uchar_to_linear_float[256] = {
608 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f,
609 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f,
610 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f,
611 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f,
612 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f,
613 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f,
614 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f,
615 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f,
616 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f,
617 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f,
618 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f,
619 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f,
620 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f,
621 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f,
622 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f,
623 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f,
624 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f,
625 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f,
626 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f,
627 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f,
628 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f,
629 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f,
630 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f,
631 0.982251f, 0.991102f, 1.0f
632};
633
634static float stbir__srgb_to_linear(float f)
635{
636 if (f <= 0.04045f)
637 return f / 12.92f;
638 else
639 return (float)pow((f + 0.055f) / 1.055f, 2.4f);
640}
641
642static float stbir__linear_to_srgb(float f)
643{
644 if (f <= 0.0031308f)
645 return f * 12.92f;
646 else
647 return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f;
648}
649
650#ifndef STBIR_NON_IEEE_FLOAT
651// From https://gist.github.com/rygorous/2203834
652
653typedef union
654{
655 stbir_uint32 u;
656 float f;
657} stbir__FP32;
658
659static const stbir_uint32 fp32_to_srgb8_tab4[104] = {
660 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d,
661 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a,
662 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033,
663 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067,
664 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5,
665 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2,
666 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143,
667 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af,
668 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240,
669 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300,
670 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401,
671 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559,
672 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723,
673};
674
675static stbir_uint8 stbir__linear_to_srgb_uchar(float in)
676{
677 static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps
678 static const stbir__FP32 minval = { (127-13) << 23 };
679 stbir_uint32 tab,bias,scale,t;
680 stbir__FP32 f;
681
682 // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively.
683 // The tests are carefully written so that NaNs map to 0, same as in the reference
684 // implementation.
685 if (!(in > minval.f)) // written this way to catch NaNs
686 in = minval.f;
687 if (in > almostone.f)
688 in = almostone.f;
689
690 // Do the table lookup and unpack bias, scale
691 f.f = in;
692 tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20];
693 bias = (tab >> 16) << 9;
694 scale = tab & 0xffff;
695
696 // Grab next-highest mantissa bits and perform linear interpolation
697 t = (f.u >> 12) & 0xff;
698 return (unsigned char) ((bias + scale*t) >> 16);
699}
700
701#else
702// sRGB transition values, scaled by 1<<28
703static int stbir__srgb_offset_to_linear_scaled[256] =
704{
705 0, 40738, 122216, 203693, 285170, 366648, 448125, 529603,
706 611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926,
707 1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148,
708 2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856,
709 3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731,
710 5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369,
711 7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021,
712 10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073,
713 13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389,
714 17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552,
715 21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066,
716 25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490,
717 31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568,
718 36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316,
719 43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096,
720 49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700,
721 57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376,
722 65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912,
723 74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648,
724 83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512,
725 93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072,
726 104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544,
727 115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832,
728 127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528,
729 140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968,
730 154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184,
731 168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992,
732 183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968,
733 199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480,
734 215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656,
735 232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464,
736 250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664,
737};
738
739static stbir_uint8 stbir__linear_to_srgb_uchar(float f)
740{
741 int x = (int) (f * (1 << 28)); // has headroom so you don't need to clamp
742 int v = 0;
743 int i;
744
745 // Refine the guess with a short binary search.
746 i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
747 i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
748 i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
749 i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
750 i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
751 i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
752 i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
753 i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
754
755 return (stbir_uint8) v;
756}
757#endif
758
759static float stbir__filter_trapezoid(float x, float scale)
760{
761 float halfscale = scale / 2;
762 float t = 0.5f + halfscale;
763 STBIR_ASSERT(scale <= 1);
764
765 x = (float)fabs(x);
766
767 if (x >= t)
768 return 0;
769 else
770 {
771 float r = 0.5f - halfscale;
772 if (x <= r)
773 return 1;
774 else
775 return (t - x) / scale;
776 }
777}
778
779static float stbir__support_trapezoid(float scale)
780{
781 STBIR_ASSERT(scale <= 1);
782 return 0.5f + scale / 2;
783}
784
785static float stbir__filter_triangle(float x, float s)
786{
787 STBIR__UNUSED_PARAM(s);
788
789 x = (float)fabs(x);
790
791 if (x <= 1.0f)
792 return 1 - x;
793 else
794 return 0;
795}
796
797static float stbir__filter_cubic(float x, float s)
798{
799 STBIR__UNUSED_PARAM(s);
800
801 x = (float)fabs(x);
802
803 if (x < 1.0f)
804 return (4 + x*x*(3*x - 6))/6;
805 else if (x < 2.0f)
806 return (8 + x*(-12 + x*(6 - x)))/6;
807
808 return (0.0f);
809}
810
811static float stbir__filter_catmullrom(float x, float s)
812{
813 STBIR__UNUSED_PARAM(s);
814
815 x = (float)fabs(x);
816
817 if (x < 1.0f)
818 return 1 - x*x*(2.5f - 1.5f*x);
819 else if (x < 2.0f)
820 return 2 - x*(4 + x*(0.5f*x - 2.5f));
821
822 return (0.0f);
823}
824
825static float stbir__filter_mitchell(float x, float s)
826{
827 STBIR__UNUSED_PARAM(s);
828
829 x = (float)fabs(x);
830
831 if (x < 1.0f)
832 return (16 + x*x*(21 * x - 36))/18;
833 else if (x < 2.0f)
834 return (32 + x*(-60 + x*(36 - 7*x)))/18;
835
836 return (0.0f);
837}
838
839static float stbir__support_zero(float s)
840{
841 STBIR__UNUSED_PARAM(s);
842 return 0;
843}
844
845static float stbir__support_one(float s)
846{
847 STBIR__UNUSED_PARAM(s);
848 return 1;
849}
850
851static float stbir__support_two(float s)
852{
853 STBIR__UNUSED_PARAM(s);
854 return 2;
855}
856
857static stbir__filter_info stbir__filter_info_table[] = {
858 { NULL, stbir__support_zero },
859 { stbir__filter_trapezoid, stbir__support_trapezoid },
860 { stbir__filter_triangle, stbir__support_one },
861 { stbir__filter_cubic, stbir__support_two },
862 { stbir__filter_catmullrom, stbir__support_two },
863 { stbir__filter_mitchell, stbir__support_two },
864};
865
866stbir__inline static int stbir__use_upsampling(float ratio)
867{
868 return ratio > 1;
869}
870
871stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info)
872{
873 return stbir__use_upsampling(stbir_info->horizontal_scale);
874}
875
876stbir__inline static int stbir__use_height_upsampling(stbir__info* stbir_info)
877{
878 return stbir__use_upsampling(stbir_info->vertical_scale);
879}
880
881// This is the maximum number of input samples that can affect an output sample
882// with the given filter
883static int stbir__get_filter_pixel_width(stbir_filter filter, float scale)
884{
885 STBIR_ASSERT(filter != 0);
886 STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));
887
888 if (stbir__use_upsampling(scale))
889 return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2);
890 else
891 return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale);
892}
893
894// This is how much to expand buffers to account for filters seeking outside
895// the image boundaries.
896static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale)
897{
898 return stbir__get_filter_pixel_width(filter, scale) / 2;
899}
900
901static int stbir__get_coefficient_width(stbir_filter filter, float scale)
902{
903 if (stbir__use_upsampling(scale))
904 return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2);
905 else
906 return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2);
907}
908
909static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size)
910{
911 if (stbir__use_upsampling(scale))
912 return output_size;
913 else
914 return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2);
915}
916
917static int stbir__get_total_horizontal_coefficients(stbir__info* info)
918{
919 return info->horizontal_num_contributors
920 * stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale);
921}
922
923static int stbir__get_total_vertical_coefficients(stbir__info* info)
924{
925 return info->vertical_num_contributors
926 * stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale);
927}
928
929static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n)
930{
931 return &contributors[n];
932}
933
934// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample,
935// if you change it here change it there too.
936static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c)
937{
938 int width = stbir__get_coefficient_width(filter, scale);
939 return &coefficients[width*n + c];
940}
941
942static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max)
943{
944 switch (edge)
945 {
946 case STBIR_EDGE_ZERO:
947 return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later
948
949 case STBIR_EDGE_CLAMP:
950 if (n < 0)
951 return 0;
952
953 if (n >= max)
954 return max - 1;
955
956 return n; // NOTREACHED
957
958 case STBIR_EDGE_REFLECT:
959 {
960 if (n < 0)
961 {
962 if (n < max)
963 return -n;
964 else
965 return max - 1;
966 }
967
968 if (n >= max)
969 {
970 int max2 = max * 2;
971 if (n >= max2)
972 return 0;
973 else
974 return max2 - n - 1;
975 }
976
977 return n; // NOTREACHED
978 }
979
980 case STBIR_EDGE_WRAP:
981 if (n >= 0)
982 return (n % max);
983 else
984 {
985 int m = (-n) % max;
986
987 if (m != 0)
988 m = max - m;
989
990 return (m);
991 }
992 // NOTREACHED
993
994 default:
995 STBIR_ASSERT(!"Unimplemented edge type");
996 return 0;
997 }
998}
999
1000stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max)
1001{
1002 // avoid per-pixel switch
1003 if (n >= 0 && n < max)
1004 return n;
1005 return stbir__edge_wrap_slow(edge, n, max);
1006}
1007
1008// What input pixels contribute to this output pixel?
1009static 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)
1010{
1011 float out_pixel_center = (float)n + 0.5f;
1012 float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius;
1013 float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius;
1014
1015 float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio;
1016 float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio;
1017
1018 *in_center_of_out = (out_pixel_center + out_shift) / scale_ratio;
1019 *in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5));
1020 *in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5));
1021}
1022
1023// What output pixels does this input pixel contribute to?
1024static 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)
1025{
1026 float in_pixel_center = (float)n + 0.5f;
1027 float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius;
1028 float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius;
1029
1030 float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift;
1031 float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift;
1032
1033 *out_center_of_in = in_pixel_center * scale_ratio - out_shift;
1034 *out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5));
1035 *out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5));
1036}
1037
1038static 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)
1039{
1040 int i;
1041 float total_filter = 0;
1042 float filter_scale;
1043
1044 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.
1045
1046 contributor->n0 = in_first_pixel;
1047 contributor->n1 = in_last_pixel;
1048
1049 STBIR_ASSERT(contributor->n1 >= contributor->n0);
1050
1051 for (i = 0; i <= in_last_pixel - in_first_pixel; i++)
1052 {
1053 float in_pixel_center = (float)(i + in_first_pixel) + 0.5f;
1054 coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale);
1055
1056 // If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.)
1057 if (i == 0 && !coefficient_group[i])
1058 {
1059 contributor->n0 = ++in_first_pixel;
1060 i--;
1061 continue;
1062 }
1063
1064 total_filter += coefficient_group[i];
1065 }
1066
1067 // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be.
1068 // It would be true in exact math but is at best approximately true in floating-point math,
1069 // and it would not make sense to try and put actual bounds on this here because it depends
1070 // on the image aspect ratio which can get pretty extreme.
1071 //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0);
1072
1073 STBIR_ASSERT(total_filter > 0.9);
1074 STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off.
1075
1076 // Make sure the sum of all coefficients is 1.
1077 filter_scale = 1 / total_filter;
1078
1079 for (i = 0; i <= in_last_pixel - in_first_pixel; i++)
1080 coefficient_group[i] *= filter_scale;
1081
1082 for (i = in_last_pixel - in_first_pixel; i >= 0; i--)
1083 {
1084 if (coefficient_group[i])
1085 break;
1086
1087 // This line has no weight. We can skip it.
1088 contributor->n1 = contributor->n0 + i - 1;
1089 }
1090}
1091
1092static 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)
1093{
1094 int i;
1095
1096 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.
1097
1098 contributor->n0 = out_first_pixel;
1099 contributor->n1 = out_last_pixel;
1100
1101 STBIR_ASSERT(contributor->n1 >= contributor->n0);
1102
1103 for (i = 0; i <= out_last_pixel - out_first_pixel; i++)
1104 {
1105 float out_pixel_center = (float)(i + out_first_pixel) + 0.5f;
1106 float x = out_pixel_center - out_center_of_in;
1107 coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio;
1108 }
1109
1110 // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be.
1111 // It would be true in exact math but is at best approximately true in floating-point math,
1112 // and it would not make sense to try and put actual bounds on this here because it depends
1113 // on the image aspect ratio which can get pretty extreme.
1114 //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0);
1115
1116 for (i = out_last_pixel - out_first_pixel; i >= 0; i--)
1117 {
1118 if (coefficient_group[i])
1119 break;
1120
1121 // This line has no weight. We can skip it.
1122 contributor->n1 = contributor->n0 + i - 1;
1123 }
1124}
1125
1126static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size)
1127{
1128 int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size);
1129 int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio);
1130 int i, j;
1131 int skip;
1132
1133 for (i = 0; i < output_size; i++)
1134 {
1135 float scale;
1136 float total = 0;
1137
1138 for (j = 0; j < num_contributors; j++)
1139 {
1140 if (i >= contributors[j].n0 && i <= contributors[j].n1)
1141 {
1142 float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0);
1143 total += coefficient;
1144 }
1145 else if (i < contributors[j].n0)
1146 break;
1147 }
1148
1149 STBIR_ASSERT(total > 0.9f);
1150 STBIR_ASSERT(total < 1.1f);
1151
1152 scale = 1 / total;
1153
1154 for (j = 0; j < num_contributors; j++)
1155 {
1156 if (i >= contributors[j].n0 && i <= contributors[j].n1)
1157 *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale;
1158 else if (i < contributors[j].n0)
1159 break;
1160 }
1161 }
1162
1163 // Optimize: Skip zero coefficients and contributions outside of image bounds.
1164 // Do this after normalizing because normalization depends on the n0/n1 values.
1165 for (j = 0; j < num_contributors; j++)
1166 {
1167 int range, max, width;
1168
1169 skip = 0;
1170 while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0)
1171 skip++;
1172
1173 contributors[j].n0 += skip;
1174
1175 while (contributors[j].n0 < 0)
1176 {
1177 contributors[j].n0++;
1178 skip++;
1179 }
1180
1181 range = contributors[j].n1 - contributors[j].n0 + 1;
1182 max = stbir__min(num_coefficients, range);
1183
1184 width = stbir__get_coefficient_width(filter, scale_ratio);
1185 for (i = 0; i < max; i++)
1186 {
1187 if (i + skip >= width)
1188 break;
1189
1190 *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip);
1191 }
1192
1193 continue;
1194 }
1195
1196 // Using min to avoid writing into invalid pixels.
1197 for (i = 0; i < num_contributors; i++)
1198 contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1);
1199}
1200
1201// Each scan line uses the same kernel values so we should calculate the kernel
1202// values once and then we can use them for every scan line.
1203static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size)
1204{
1205 int n;
1206 int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size);
1207
1208 if (stbir__use_upsampling(scale_ratio))
1209 {
1210 float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio;
1211
1212 // Looping through out pixels
1213 for (n = 0; n < total_contributors; n++)
1214 {
1215 float in_center_of_out; // Center of the current out pixel in the in pixel space
1216 int in_first_pixel, in_last_pixel;
1217
1218 stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out);
1219
1220 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));
1221 }
1222 }
1223 else
1224 {
1225 float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio;
1226
1227 // Looping through in pixels
1228 for (n = 0; n < total_contributors; n++)
1229 {
1230 float out_center_of_in; // Center of the current out pixel in the in pixel space
1231 int out_first_pixel, out_last_pixel;
1232 int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio);
1233
1234 stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in);
1235
1236 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));
1237 }
1238
1239 stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size);
1240 }
1241}
1242
1243static float* stbir__get_decode_buffer(stbir__info* stbir_info)
1244{
1245 // The 0 index of the decode buffer starts after the margin. This makes
1246 // it okay to use negative indexes on the decode buffer.
1247 return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels];
1248}
1249
1250#define STBIR__DECODE(type, colorspace) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace))
1251
1252static void stbir__decode_scanline(stbir__info* stbir_info, int n)
1253{
1254 int c;
1255 int channels = stbir_info->channels;
1256 int alpha_channel = stbir_info->alpha_channel;
1257 int type = stbir_info->type;
1258 int colorspace = stbir_info->colorspace;
1259 int input_w = stbir_info->input_w;
1260 size_t input_stride_bytes = stbir_info->input_stride_bytes;
1261 float* decode_buffer = stbir__get_decode_buffer(stbir_info);
1262 stbir_edge edge_horizontal = stbir_info->edge_horizontal;
1263 stbir_edge edge_vertical = stbir_info->edge_vertical;
1264 size_t in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes;
1265 const void* input_data = (char *) stbir_info->input_data + in_buffer_row_offset;
1266 int max_x = input_w + stbir_info->horizontal_filter_pixel_margin;
1267 int decode = STBIR__DECODE(type, colorspace);
1268
1269 int x = -stbir_info->horizontal_filter_pixel_margin;
1270
1271 // special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input,
1272 // and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO
1273 if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h))
1274 {
1275 for (; x < max_x; x++)
1276 for (c = 0; c < channels; c++)
1277 decode_buffer[x*channels + c] = 0;
1278 return;
1279 }
1280
1281 switch (decode)
1282 {
1283 case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR):
1284 for (; x < max_x; x++)
1285 {
1286 int decode_pixel_index = x * channels;
1287 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
1288 for (c = 0; c < channels; c++)
1289 decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float;
1290 }
1291 break;
1292
1293 case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB):
1294 for (; x < max_x; x++)
1295 {
1296 int decode_pixel_index = x * channels;
1297 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
1298 for (c = 0; c < channels; c++)
1299 decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]];
1300
1301 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1302 decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float;
1303 }
1304 break;
1305
1306 case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR):
1307 for (; x < max_x; x++)
1308 {
1309 int decode_pixel_index = x * channels;
1310 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
1311 for (c = 0; c < channels; c++)
1312 decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float;
1313 }
1314 break;
1315
1316 case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB):
1317 for (; x < max_x; x++)
1318 {
1319 int decode_pixel_index = x * channels;
1320 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
1321 for (c = 0; c < channels; c++)
1322 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);
1323
1324 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1325 decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float;
1326 }
1327 break;
1328
1329 case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR):
1330 for (; x < max_x; x++)
1331 {
1332 int decode_pixel_index = x * channels;
1333 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
1334 for (c = 0; c < channels; c++)
1335 decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float);
1336 }
1337 break;
1338
1339 case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB):
1340 for (; x < max_x; x++)
1341 {
1342 int decode_pixel_index = x * channels;
1343 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
1344 for (c = 0; c < channels; c++)
1345 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));
1346
1347 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1348 decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float);
1349 }
1350 break;
1351
1352 case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR):
1353 for (; x < max_x; x++)
1354 {
1355 int decode_pixel_index = x * channels;
1356 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
1357 for (c = 0; c < channels; c++)
1358 decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c];
1359 }
1360 break;
1361
1362 case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB):
1363 for (; x < max_x; x++)
1364 {
1365 int decode_pixel_index = x * channels;
1366 int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
1367 for (c = 0; c < channels; c++)
1368 decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]);
1369
1370 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1371 decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel];
1372 }
1373
1374 break;
1375
1376 default:
1377 STBIR_ASSERT(!"Unknown type/colorspace/channels combination.");
1378 break;
1379 }
1380
1381 if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED))
1382 {
1383 for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++)
1384 {
1385 int decode_pixel_index = x * channels;
1386
1387 // If the alpha value is 0 it will clobber the color values. Make sure it's not.
1388 float alpha = decode_buffer[decode_pixel_index + alpha_channel];
1389#ifndef STBIR_NO_ALPHA_EPSILON
1390 if (stbir_info->type != STBIR_TYPE_FLOAT) {
1391 alpha += STBIR_ALPHA_EPSILON;
1392 decode_buffer[decode_pixel_index + alpha_channel] = alpha;
1393 }
1394#endif
1395 for (c = 0; c < channels; c++)
1396 {
1397 if (c == alpha_channel)
1398 continue;
1399
1400 decode_buffer[decode_pixel_index + c] *= alpha;
1401 }
1402 }
1403 }
1404
1405 if (edge_horizontal == STBIR_EDGE_ZERO)
1406 {
1407 for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++)
1408 {
1409 for (c = 0; c < channels; c++)
1410 decode_buffer[x*channels + c] = 0;
1411 }
1412 for (x = input_w; x < max_x; x++)
1413 {
1414 for (c = 0; c < channels; c++)
1415 decode_buffer[x*channels + c] = 0;
1416 }
1417 }
1418}
1419
1420static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length)
1421{
1422 return &ring_buffer[index * ring_buffer_length];
1423}
1424
1425static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n)
1426{
1427 int ring_buffer_index;
1428 float* ring_buffer;
1429
1430 stbir_info->ring_buffer_last_scanline = n;
1431
1432 if (stbir_info->ring_buffer_begin_index < 0)
1433 {
1434 ring_buffer_index = stbir_info->ring_buffer_begin_index = 0;
1435 stbir_info->ring_buffer_first_scanline = n;
1436 }
1437 else
1438 {
1439 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;
1440 STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index);
1441 }
1442
1443 ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float));
1444 memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes);
1445
1446 return ring_buffer;
1447}
1448
1449
1450static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer)
1451{
1452 int x, k;
1453 int output_w = stbir_info->output_w;
1454 int channels = stbir_info->channels;
1455 float* decode_buffer = stbir__get_decode_buffer(stbir_info);
1456 stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors;
1457 float* horizontal_coefficients = stbir_info->horizontal_coefficients;
1458 int coefficient_width = stbir_info->horizontal_coefficient_width;
1459
1460 for (x = 0; x < output_w; x++)
1461 {
1462 int n0 = horizontal_contributors[x].n0;
1463 int n1 = horizontal_contributors[x].n1;
1464
1465 int out_pixel_index = x * channels;
1466 int coefficient_group = coefficient_width * x;
1467 int coefficient_counter = 0;
1468
1469 STBIR_ASSERT(n1 >= n0);
1470 STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin);
1471 STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin);
1472 STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
1473 STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
1474
1475 switch (channels) {
1476 case 1:
1477 for (k = n0; k <= n1; k++)
1478 {
1479 int in_pixel_index = k * 1;
1480 float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
1481 STBIR_ASSERT(coefficient != 0);
1482 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1483 }
1484 break;
1485 case 2:
1486 for (k = n0; k <= n1; k++)
1487 {
1488 int in_pixel_index = k * 2;
1489 float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
1490 STBIR_ASSERT(coefficient != 0);
1491 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1492 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1493 }
1494 break;
1495 case 3:
1496 for (k = n0; k <= n1; k++)
1497 {
1498 int in_pixel_index = k * 3;
1499 float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
1500 STBIR_ASSERT(coefficient != 0);
1501 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1502 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1503 output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
1504 }
1505 break;
1506 case 4:
1507 for (k = n0; k <= n1; k++)
1508 {
1509 int in_pixel_index = k * 4;
1510 float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
1511 STBIR_ASSERT(coefficient != 0);
1512 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1513 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1514 output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
1515 output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient;
1516 }
1517 break;
1518 default:
1519 for (k = n0; k <= n1; k++)
1520 {
1521 int in_pixel_index = k * channels;
1522 float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
1523 int c;
1524 STBIR_ASSERT(coefficient != 0);
1525 for (c = 0; c < channels; c++)
1526 output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
1527 }
1528 break;
1529 }
1530 }
1531}
1532
1533static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer)
1534{
1535 int x, k;
1536 int input_w = stbir_info->input_w;
1537 int channels = stbir_info->channels;
1538 float* decode_buffer = stbir__get_decode_buffer(stbir_info);
1539 stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors;
1540 float* horizontal_coefficients = stbir_info->horizontal_coefficients;
1541 int coefficient_width = stbir_info->horizontal_coefficient_width;
1542 int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin;
1543 int max_x = input_w + filter_pixel_margin * 2;
1544
1545 STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info));
1546
1547 switch (channels) {
1548 case 1:
1549 for (x = 0; x < max_x; x++)
1550 {
1551 int n0 = horizontal_contributors[x].n0;
1552 int n1 = horizontal_contributors[x].n1;
1553
1554 int in_x = x - filter_pixel_margin;
1555 int in_pixel_index = in_x * 1;
1556 int max_n = n1;
1557 int coefficient_group = coefficient_width * x;
1558
1559 for (k = n0; k <= max_n; k++)
1560 {
1561 int out_pixel_index = k * 1;
1562 float coefficient = horizontal_coefficients[coefficient_group + k - n0];
1563 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1564 }
1565 }
1566 break;
1567
1568 case 2:
1569 for (x = 0; x < max_x; x++)
1570 {
1571 int n0 = horizontal_contributors[x].n0;
1572 int n1 = horizontal_contributors[x].n1;
1573
1574 int in_x = x - filter_pixel_margin;
1575 int in_pixel_index = in_x * 2;
1576 int max_n = n1;
1577 int coefficient_group = coefficient_width * x;
1578
1579 for (k = n0; k <= max_n; k++)
1580 {
1581 int out_pixel_index = k * 2;
1582 float coefficient = horizontal_coefficients[coefficient_group + k - n0];
1583 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1584 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1585 }
1586 }
1587 break;
1588
1589 case 3:
1590 for (x = 0; x < max_x; x++)
1591 {
1592 int n0 = horizontal_contributors[x].n0;
1593 int n1 = horizontal_contributors[x].n1;
1594
1595 int in_x = x - filter_pixel_margin;
1596 int in_pixel_index = in_x * 3;
1597 int max_n = n1;
1598 int coefficient_group = coefficient_width * x;
1599
1600 for (k = n0; k <= max_n; k++)
1601 {
1602 int out_pixel_index = k * 3;
1603 float coefficient = horizontal_coefficients[coefficient_group + k - n0];
1604 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1605 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1606 output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
1607 }
1608 }
1609 break;
1610
1611 case 4:
1612 for (x = 0; x < max_x; x++)
1613 {
1614 int n0 = horizontal_contributors[x].n0;
1615 int n1 = horizontal_contributors[x].n1;
1616
1617 int in_x = x - filter_pixel_margin;
1618 int in_pixel_index = in_x * 4;
1619 int max_n = n1;
1620 int coefficient_group = coefficient_width * x;
1621
1622 for (k = n0; k <= max_n; k++)
1623 {
1624 int out_pixel_index = k * 4;
1625 float coefficient = horizontal_coefficients[coefficient_group + k - n0];
1626 output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
1627 output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
1628 output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
1629 output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient;
1630 }
1631 }
1632 break;
1633
1634 default:
1635 for (x = 0; x < max_x; x++)
1636 {
1637 int n0 = horizontal_contributors[x].n0;
1638 int n1 = horizontal_contributors[x].n1;
1639
1640 int in_x = x - filter_pixel_margin;
1641 int in_pixel_index = in_x * channels;
1642 int max_n = n1;
1643 int coefficient_group = coefficient_width * x;
1644
1645 for (k = n0; k <= max_n; k++)
1646 {
1647 int c;
1648 int out_pixel_index = k * channels;
1649 float coefficient = horizontal_coefficients[coefficient_group + k - n0];
1650 for (c = 0; c < channels; c++)
1651 output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
1652 }
1653 }
1654 break;
1655 }
1656}
1657
1658static void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n)
1659{
1660 // Decode the nth scanline from the source image into the decode buffer.
1661 stbir__decode_scanline(stbir_info, n);
1662
1663 // Now resample it into the ring buffer.
1664 if (stbir__use_width_upsampling(stbir_info))
1665 stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n));
1666 else
1667 stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n));
1668
1669 // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling.
1670}
1671
1672static void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n)
1673{
1674 // Decode the nth scanline from the source image into the decode buffer.
1675 stbir__decode_scanline(stbir_info, n);
1676
1677 memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float));
1678
1679 // Now resample it into the horizontal buffer.
1680 if (stbir__use_width_upsampling(stbir_info))
1681 stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer);
1682 else
1683 stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer);
1684
1685 // Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers.
1686}
1687
1688// Get the specified scan line from the ring buffer.
1689static 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)
1690{
1691 int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries;
1692 return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length);
1693}
1694
1695
1696static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode)
1697{
1698 int x;
1699 int n;
1700 int num_nonalpha;
1701 stbir_uint16 nonalpha[STBIR_MAX_CHANNELS];
1702
1703 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_PREMULTIPLIED))
1704 {
1705 for (x=0; x < num_pixels; ++x)
1706 {
1707 int pixel_index = x*channels;
1708
1709 float alpha = encode_buffer[pixel_index + alpha_channel];
1710 float reciprocal_alpha = alpha ? 1.0f / alpha : 0;
1711
1712 // unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb
1713 for (n = 0; n < channels; n++)
1714 if (n != alpha_channel)
1715 encode_buffer[pixel_index + n] *= reciprocal_alpha;
1716
1717 // We added in a small epsilon to prevent the color channel from being deleted with zero alpha.
1718 // Because we only add it for integer types, it will automatically be discarded on integer
1719 // conversion, so we don't need to subtract it back out (which would be problematic for
1720 // numeric precision reasons).
1721 }
1722 }
1723
1724 // build a table of all channels that need colorspace correction, so
1725 // we don't perform colorspace correction on channels that don't need it.
1726 for (x = 0, num_nonalpha = 0; x < channels; ++x)
1727 {
1728 if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE))
1729 {
1730 nonalpha[num_nonalpha++] = (stbir_uint16)x;
1731 }
1732 }
1733
1734 #define STBIR__ROUND_INT(f) ((int) ((f)+0.5))
1735 #define STBIR__ROUND_UINT(f) ((stbir_uint32) ((f)+0.5))
1736
1737 #ifdef STBIR__SATURATE_INT
1738 #define STBIR__ENCODE_LINEAR8(f) stbir__saturate8 (STBIR__ROUND_INT((f) * stbir__max_uint8_as_float ))
1739 #define STBIR__ENCODE_LINEAR16(f) stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float))
1740 #else
1741 #define STBIR__ENCODE_LINEAR8(f) (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float )
1742 #define STBIR__ENCODE_LINEAR16(f) (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float)
1743 #endif
1744
1745 switch (decode)
1746 {
1747 case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR):
1748 for (x=0; x < num_pixels; ++x)
1749 {
1750 int pixel_index = x*channels;
1751
1752 for (n = 0; n < channels; n++)
1753 {
1754 int index = pixel_index + n;
1755 ((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]);
1756 }
1757 }
1758 break;
1759
1760 case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB):
1761 for (x=0; x < num_pixels; ++x)
1762 {
1763 int pixel_index = x*channels;
1764
1765 for (n = 0; n < num_nonalpha; n++)
1766 {
1767 int index = pixel_index + nonalpha[n];
1768 ((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]);
1769 }
1770
1771 if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE))
1772 ((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]);
1773 }
1774 break;
1775
1776 case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR):
1777 for (x=0; x < num_pixels; ++x)
1778 {
1779 int pixel_index = x*channels;
1780
1781 for (n = 0; n < channels; n++)
1782 {
1783 int index = pixel_index + n;
1784 ((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]);
1785 }
1786 }
1787 break;
1788
1789 case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB):
1790 for (x=0; x < num_pixels; ++x)
1791 {
1792 int pixel_index = x*channels;
1793
1794 for (n = 0; n < num_nonalpha; n++)
1795 {
1796 int index = pixel_index + nonalpha[n];
1797 ((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float);
1798 }
1799
1800 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1801 ((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]);
1802 }
1803
1804 break;
1805
1806 case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR):
1807 for (x=0; x < num_pixels; ++x)
1808 {
1809 int pixel_index = x*channels;
1810
1811 for (n = 0; n < channels; n++)
1812 {
1813 int index = pixel_index + n;
1814 ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float);
1815 }
1816 }
1817 break;
1818
1819 case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB):
1820 for (x=0; x < num_pixels; ++x)
1821 {
1822 int pixel_index = x*channels;
1823
1824 for (n = 0; n < num_nonalpha; n++)
1825 {
1826 int index = pixel_index + nonalpha[n];
1827 ((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);
1828 }
1829
1830 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1831 ((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);
1832 }
1833 break;
1834
1835 case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR):
1836 for (x=0; x < num_pixels; ++x)
1837 {
1838 int pixel_index = x*channels;
1839
1840 for (n = 0; n < channels; n++)
1841 {
1842 int index = pixel_index + n;
1843 ((float*)output_buffer)[index] = encode_buffer[index];
1844 }
1845 }
1846 break;
1847
1848 case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB):
1849 for (x=0; x < num_pixels; ++x)
1850 {
1851 int pixel_index = x*channels;
1852
1853 for (n = 0; n < num_nonalpha; n++)
1854 {
1855 int index = pixel_index + nonalpha[n];
1856 ((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]);
1857 }
1858
1859 if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
1860 ((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel];
1861 }
1862 break;
1863
1864 default:
1865 STBIR_ASSERT(!"Unknown type/colorspace/channels combination.");
1866 break;
1867 }
1868}
1869
1870static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n)
1871{
1872 int x, k;
1873 int output_w = stbir_info->output_w;
1874 stbir__contributors* vertical_contributors = stbir_info->vertical_contributors;
1875 float* vertical_coefficients = stbir_info->vertical_coefficients;
1876 int channels = stbir_info->channels;
1877 int alpha_channel = stbir_info->alpha_channel;
1878 int type = stbir_info->type;
1879 int colorspace = stbir_info->colorspace;
1880 int ring_buffer_entries = stbir_info->ring_buffer_num_entries;
1881 void* output_data = stbir_info->output_data;
1882 float* encode_buffer = stbir_info->encode_buffer;
1883 int decode = STBIR__DECODE(type, colorspace);
1884 int coefficient_width = stbir_info->vertical_coefficient_width;
1885 int coefficient_counter;
1886 int contributor = n;
1887
1888 float* ring_buffer = stbir_info->ring_buffer;
1889 int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index;
1890 int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline;
1891 int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float);
1892
1893 int n0,n1, output_row_start;
1894 int coefficient_group = coefficient_width * contributor;
1895
1896 n0 = vertical_contributors[contributor].n0;
1897 n1 = vertical_contributors[contributor].n1;
1898
1899 output_row_start = n * stbir_info->output_stride_bytes;
1900
1901 STBIR_ASSERT(stbir__use_height_upsampling(stbir_info));
1902
1903 memset(encode_buffer, 0, output_w * sizeof(float) * channels);
1904
1905 // I tried reblocking this for better cache usage of encode_buffer
1906 // (using x_outer, k, x_inner), but it lost speed. -- stb
1907
1908 coefficient_counter = 0;
1909 switch (channels) {
1910 case 1:
1911 for (k = n0; k <= n1; k++)
1912 {
1913 int coefficient_index = coefficient_counter++;
1914 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);
1915 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1916 for (x = 0; x < output_w; ++x)
1917 {
1918 int in_pixel_index = x * 1;
1919 encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
1920 }
1921 }
1922 break;
1923 case 2:
1924 for (k = n0; k <= n1; k++)
1925 {
1926 int coefficient_index = coefficient_counter++;
1927 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);
1928 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1929 for (x = 0; x < output_w; ++x)
1930 {
1931 int in_pixel_index = x * 2;
1932 encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
1933 encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
1934 }
1935 }
1936 break;
1937 case 3:
1938 for (k = n0; k <= n1; k++)
1939 {
1940 int coefficient_index = coefficient_counter++;
1941 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);
1942 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1943 for (x = 0; x < output_w; ++x)
1944 {
1945 int in_pixel_index = x * 3;
1946 encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
1947 encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
1948 encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient;
1949 }
1950 }
1951 break;
1952 case 4:
1953 for (k = n0; k <= n1; k++)
1954 {
1955 int coefficient_index = coefficient_counter++;
1956 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);
1957 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1958 for (x = 0; x < output_w; ++x)
1959 {
1960 int in_pixel_index = x * 4;
1961 encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
1962 encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
1963 encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient;
1964 encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient;
1965 }
1966 }
1967 break;
1968 default:
1969 for (k = n0; k <= n1; k++)
1970 {
1971 int coefficient_index = coefficient_counter++;
1972 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);
1973 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
1974 for (x = 0; x < output_w; ++x)
1975 {
1976 int in_pixel_index = x * channels;
1977 int c;
1978 for (c = 0; c < channels; c++)
1979 encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient;
1980 }
1981 }
1982 break;
1983 }
1984 stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, encode_buffer, channels, alpha_channel, decode);
1985}
1986
1987static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n)
1988{
1989 int x, k;
1990 int output_w = stbir_info->output_w;
1991 stbir__contributors* vertical_contributors = stbir_info->vertical_contributors;
1992 float* vertical_coefficients = stbir_info->vertical_coefficients;
1993 int channels = stbir_info->channels;
1994 int ring_buffer_entries = stbir_info->ring_buffer_num_entries;
1995 float* horizontal_buffer = stbir_info->horizontal_buffer;
1996 int coefficient_width = stbir_info->vertical_coefficient_width;
1997 int contributor = n + stbir_info->vertical_filter_pixel_margin;
1998
1999 float* ring_buffer = stbir_info->ring_buffer;
2000 int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index;
2001 int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline;
2002 int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float);
2003 int n0,n1;
2004
2005 n0 = vertical_contributors[contributor].n0;
2006 n1 = vertical_contributors[contributor].n1;
2007
2008 STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info));
2009
2010 for (k = n0; k <= n1; k++)
2011 {
2012 int coefficient_index = k - n0;
2013 int coefficient_group = coefficient_width * contributor;
2014 float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
2015
2016 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);
2017
2018 switch (channels) {
2019 case 1:
2020 for (x = 0; x < output_w; x++)
2021 {
2022 int in_pixel_index = x * 1;
2023 ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
2024 }
2025 break;
2026 case 2:
2027 for (x = 0; x < output_w; x++)
2028 {
2029 int in_pixel_index = x * 2;
2030 ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
2031 ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
2032 }
2033 break;
2034 case 3:
2035 for (x = 0; x < output_w; x++)
2036 {
2037 int in_pixel_index = x * 3;
2038 ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
2039 ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
2040 ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient;
2041 }
2042 break;
2043 case 4:
2044 for (x = 0; x < output_w; x++)
2045 {
2046 int in_pixel_index = x * 4;
2047 ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
2048 ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
2049 ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient;
2050 ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient;
2051 }
2052 break;
2053 default:
2054 for (x = 0; x < output_w; x++)
2055 {
2056 int in_pixel_index = x * channels;
2057
2058 int c;
2059 for (c = 0; c < channels; c++)
2060 ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient;
2061 }
2062 break;
2063 }
2064 }
2065}
2066
2067static void stbir__buffer_loop_upsample(stbir__info* stbir_info)
2068{
2069 int y;
2070 float scale_ratio = stbir_info->vertical_scale;
2071 float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio;
2072
2073 STBIR_ASSERT(stbir__use_height_upsampling(stbir_info));
2074
2075 for (y = 0; y < stbir_info->output_h; y++)
2076 {
2077 float in_center_of_out = 0; // Center of the current out scanline in the in scanline space
2078 int in_first_scanline = 0, in_last_scanline = 0;
2079
2080 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);
2081
2082 STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries);
2083
2084 if (stbir_info->ring_buffer_begin_index >= 0)
2085 {
2086 // Get rid of whatever we don't need anymore.
2087 while (in_first_scanline > stbir_info->ring_buffer_first_scanline)
2088 {
2089 if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline)
2090 {
2091 // We just popped the last scanline off the ring buffer.
2092 // Reset it to the empty state.
2093 stbir_info->ring_buffer_begin_index = -1;
2094 stbir_info->ring_buffer_first_scanline = 0;
2095 stbir_info->ring_buffer_last_scanline = 0;
2096 break;
2097 }
2098 else
2099 {
2100 stbir_info->ring_buffer_first_scanline++;
2101 stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries;
2102 }
2103 }
2104 }
2105
2106 // Load in new ones.
2107 if (stbir_info->ring_buffer_begin_index < 0)
2108 stbir__decode_and_resample_upsample(stbir_info, in_first_scanline);
2109
2110 while (in_last_scanline > stbir_info->ring_buffer_last_scanline)
2111 stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1);
2112
2113 // Now all buffers should be ready to write a row of vertical sampling.
2114 stbir__resample_vertical_upsample(stbir_info, y);
2115
2116 STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h);
2117 }
2118}
2119
2120static void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline)
2121{
2122 int output_stride_bytes = stbir_info->output_stride_bytes;
2123 int channels = stbir_info->channels;
2124 int alpha_channel = stbir_info->alpha_channel;
2125 int type = stbir_info->type;
2126 int colorspace = stbir_info->colorspace;
2127 int output_w = stbir_info->output_w;
2128 void* output_data = stbir_info->output_data;
2129 int decode = STBIR__DECODE(type, colorspace);
2130
2131 float* ring_buffer = stbir_info->ring_buffer;
2132 int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float);
2133
2134 if (stbir_info->ring_buffer_begin_index >= 0)
2135 {
2136 // Get rid of whatever we don't need anymore.
2137 while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline)
2138 {
2139 if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h)
2140 {
2141 int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes;
2142 float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length);
2143 stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode);
2144 STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h);
2145 }
2146
2147 if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline)
2148 {
2149 // We just popped the last scanline off the ring buffer.
2150 // Reset it to the empty state.
2151 stbir_info->ring_buffer_begin_index = -1;
2152 stbir_info->ring_buffer_first_scanline = 0;
2153 stbir_info->ring_buffer_last_scanline = 0;
2154 break;
2155 }
2156 else
2157 {
2158 stbir_info->ring_buffer_first_scanline++;
2159 stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries;
2160 }
2161 }
2162 }
2163}
2164
2165static void stbir__buffer_loop_downsample(stbir__info* stbir_info)
2166{
2167 int y;
2168 float scale_ratio = stbir_info->vertical_scale;
2169 int output_h = stbir_info->output_h;
2170 float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio;
2171 int pixel_margin = stbir_info->vertical_filter_pixel_margin;
2172 int max_y = stbir_info->input_h + pixel_margin;
2173
2174 STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info));
2175
2176 for (y = -pixel_margin; y < max_y; y++)
2177 {
2178 float out_center_of_in; // Center of the current out scanline in the in scanline space
2179 int out_first_scanline, out_last_scanline;
2180
2181 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);
2182
2183 STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries);
2184
2185 if (out_last_scanline < 0 || out_first_scanline >= output_h)
2186 continue;
2187
2188 stbir__empty_ring_buffer(stbir_info, out_first_scanline);
2189
2190 stbir__decode_and_resample_downsample(stbir_info, y);
2191
2192 // Load in new ones.
2193 if (stbir_info->ring_buffer_begin_index < 0)
2194 stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline);
2195
2196 while (out_last_scanline > stbir_info->ring_buffer_last_scanline)
2197 stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1);
2198
2199 // Now the horizontal buffer is ready to write to all ring buffer rows.
2200 stbir__resample_vertical_downsample(stbir_info, y);
2201 }
2202
2203 stbir__empty_ring_buffer(stbir_info, stbir_info->output_h);
2204}
2205
2206static void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels)
2207{
2208 info->input_w = input_w;
2209 info->input_h = input_h;
2210 info->output_w = output_w;
2211 info->output_h = output_h;
2212 info->channels = channels;
2213}
2214
2215static void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform)
2216{
2217 info->s0 = s0;
2218 info->t0 = t0;
2219 info->s1 = s1;
2220 info->t1 = t1;
2221
2222 if (transform)
2223 {
2224 info->horizontal_scale = transform[0];
2225 info->vertical_scale = transform[1];
2226 info->horizontal_shift = transform[2];
2227 info->vertical_shift = transform[3];
2228 }
2229 else
2230 {
2231 info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0);
2232 info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0);
2233
2234 info->horizontal_shift = s0 * info->output_w / (s1 - s0);
2235 info->vertical_shift = t0 * info->output_h / (t1 - t0);
2236 }
2237}
2238
2239static void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter)
2240{
2241 if (h_filter == 0)
2242 h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE;
2243 if (v_filter == 0)
2244 v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE;
2245 info->horizontal_filter = h_filter;
2246 info->vertical_filter = v_filter;
2247}
2248
2249static stbir_uint32 stbir__calculate_memory(stbir__info *info)
2250{
2251 int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale);
2252 int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale);
2253
2254 info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w);
2255 info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale , info->vertical_filter , info->input_h, info->output_h);
2256
2257 // One extra entry because floating point precision problems sometimes cause an extra to be necessary.
2258 info->ring_buffer_num_entries = filter_height + 1;
2259
2260 info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors);
2261 info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float);
2262 info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors);
2263 info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float);
2264 info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float);
2265 info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float);
2266 info->ring_buffer_size = info->output_w * info->channels * info->ring_buffer_num_entries * sizeof(float);
2267 info->encode_buffer_size = info->output_w * info->channels * sizeof(float);
2268
2269 STBIR_ASSERT(info->horizontal_filter != 0);
2270 STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late
2271 STBIR_ASSERT(info->vertical_filter != 0);
2272 STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late
2273
2274 if (stbir__use_height_upsampling(info))
2275 // The horizontal buffer is for when we're downsampling the height and we
2276 // can't output the result of sampling the decode buffer directly into the
2277 // ring buffers.
2278 info->horizontal_buffer_size = 0;
2279 else
2280 // The encode buffer is to retain precision in the height upsampling method
2281 // and isn't used when height downsampling.
2282 info->encode_buffer_size = 0;
2283
2284 return info->horizontal_contributors_size + info->horizontal_coefficients_size
2285 + info->vertical_contributors_size + info->vertical_coefficients_size
2286 + info->decode_buffer_size + info->horizontal_buffer_size
2287 + info->ring_buffer_size + info->encode_buffer_size;
2288}
2289
2290static int stbir__resize_allocated(stbir__info *info,
2291 const void* input_data, int input_stride_in_bytes,
2292 void* output_data, int output_stride_in_bytes,
2293 int alpha_channel, stbir_uint32 flags, stbir_datatype type,
2294 stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace,
2295 void* tempmem, size_t tempmem_size_in_bytes)
2296{
2297 size_t memory_required = stbir__calculate_memory(info);
2298
2299 int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type];
2300 int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type];
2301
2302#ifdef STBIR_DEBUG_OVERWRITE_TEST
2303#define OVERWRITE_ARRAY_SIZE 8
2304 unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE];
2305 unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE];
2306 unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE];
2307 unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE];
2308
2309 size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type];
2310 memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE);
2311 memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE);
2312 memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE);
2313 memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE);
2314#endif
2315
2316 STBIR_ASSERT(info->channels >= 0);
2317 STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS);
2318
2319 if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS)
2320 return 0;
2321
2322 STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));
2323 STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));
2324
2325 if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table))
2326 return 0;
2327 if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table))
2328 return 0;
2329
2330 if (alpha_channel < 0)
2331 flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED;
2332
2333 if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) {
2334 STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels);
2335 }
2336
2337 if (alpha_channel >= info->channels)
2338 return 0;
2339
2340 STBIR_ASSERT(tempmem);
2341
2342 if (!tempmem)
2343 return 0;
2344
2345 STBIR_ASSERT(tempmem_size_in_bytes >= memory_required);
2346
2347 if (tempmem_size_in_bytes < memory_required)
2348 return 0;
2349
2350 memset(tempmem, 0, tempmem_size_in_bytes);
2351
2352 info->input_data = input_data;
2353 info->input_stride_bytes = width_stride_input;
2354
2355 info->output_data = output_data;
2356 info->output_stride_bytes = width_stride_output;
2357
2358 info->alpha_channel = alpha_channel;
2359 info->flags = flags;
2360 info->type = type;
2361 info->edge_horizontal = edge_horizontal;
2362 info->edge_vertical = edge_vertical;
2363 info->colorspace = colorspace;
2364
2365 info->horizontal_coefficient_width = stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale);
2366 info->vertical_coefficient_width = stbir__get_coefficient_width (info->vertical_filter , info->vertical_scale );
2367 info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width (info->horizontal_filter, info->horizontal_scale);
2368 info->vertical_filter_pixel_width = stbir__get_filter_pixel_width (info->vertical_filter , info->vertical_scale );
2369 info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale);
2370 info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter , info->vertical_scale );
2371
2372 info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float);
2373 info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2;
2374
2375#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size)
2376
2377 info->horizontal_contributors = (stbir__contributors *) tempmem;
2378 info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float);
2379 info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors);
2380 info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float);
2381 info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float);
2382
2383 if (stbir__use_height_upsampling(info))
2384 {
2385 info->horizontal_buffer = NULL;
2386 info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
2387 info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float);
2388
2389 STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
2390 }
2391 else
2392 {
2393 info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
2394 info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float);
2395 info->encode_buffer = NULL;
2396
2397 STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
2398 }
2399
2400#undef STBIR__NEXT_MEMPTR
2401
2402 // This signals that the ring buffer is empty
2403 info->ring_buffer_begin_index = -1;
2404
2405 stbir__calculate_filters(info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w);
2406 stbir__calculate_filters(info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h);
2407
2408 STBIR_PROGRESS_REPORT(0);
2409
2410 if (stbir__use_height_upsampling(info))
2411 stbir__buffer_loop_upsample(info);
2412 else
2413 stbir__buffer_loop_downsample(info);
2414
2415 STBIR_PROGRESS_REPORT(1);
2416
2417#ifdef STBIR_DEBUG_OVERWRITE_TEST
2418 STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
2419 STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0);
2420 STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
2421 STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0);
2422#endif
2423
2424 return 1;
2425}
2426
2427
2428static int stbir__resize_arbitrary(
2429 void *alloc_context,
2430 const void* input_data, int input_w, int input_h, int input_stride_in_bytes,
2431 void* output_data, int output_w, int output_h, int output_stride_in_bytes,
2432 float s0, float t0, float s1, float t1, float *transform,
2433 int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type,
2434 stbir_filter h_filter, stbir_filter v_filter,
2435 stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace)
2436{
2437 stbir__info info;
2438 int result;
2439 size_t memory_required;
2440 void* extra_memory;
2441
2442 stbir__setup(&info, input_w, input_h, output_w, output_h, channels);
2443 stbir__calculate_transform(&info, s0,t0,s1,t1,transform);
2444 stbir__choose_filter(&info, h_filter, v_filter);
2445 memory_required = stbir__calculate_memory(&info);
2446 extra_memory = STBIR_MALLOC(memory_required, alloc_context);
2447
2448 if (!extra_memory)
2449 return 0;
2450
2451 result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes,
2452 output_data, output_stride_in_bytes,
2453 alpha_channel, flags, type,
2454 edge_horizontal, edge_vertical,
2455 colorspace, extra_memory, memory_required);
2456
2457 STBIR_FREE(extra_memory, alloc_context);
2458
2459 return result;
2460}
2461
2462STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2463 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2464 int num_channels)
2465{
2466 return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
2467 output_pixels, output_w, output_h, output_stride_in_bytes,
2468 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
2469 STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR);
2470}
2471
2472STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2473 float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2474 int num_channels)
2475{
2476 return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
2477 output_pixels, output_w, output_h, output_stride_in_bytes,
2478 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
2479 STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR);
2480}
2481
2482STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2483 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2484 int num_channels, int alpha_channel, int flags)
2485{
2486 return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
2487 output_pixels, output_w, output_h, output_stride_in_bytes,
2488 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
2489 STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB);
2490}
2491
2492STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2493 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2494 int num_channels, int alpha_channel, int flags,
2495 stbir_edge edge_wrap_mode)
2496{
2497 return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
2498 output_pixels, output_w, output_h, output_stride_in_bytes,
2499 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
2500 edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB);
2501}
2502
2503STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2504 unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2505 int num_channels, int alpha_channel, int flags,
2506 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
2507 void *alloc_context)
2508{
2509 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2510 output_pixels, output_w, output_h, output_stride_in_bytes,
2511 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter,
2512 edge_wrap_mode, edge_wrap_mode, space);
2513}
2514
2515STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2516 stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
2517 int num_channels, int alpha_channel, int flags,
2518 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
2519 void *alloc_context)
2520{
2521 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2522 output_pixels, output_w, output_h, output_stride_in_bytes,
2523 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter,
2524 edge_wrap_mode, edge_wrap_mode, space);
2525}
2526
2527
2528STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2529 float *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
2530 int num_channels, int alpha_channel, int flags,
2531 stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
2532 void *alloc_context)
2533{
2534 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2535 output_pixels, output_w, output_h, output_stride_in_bytes,
2536 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter,
2537 edge_wrap_mode, edge_wrap_mode, space);
2538}
2539
2540
2541STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2542 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2543 stbir_datatype datatype,
2544 int num_channels, int alpha_channel, int flags,
2545 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
2546 stbir_filter filter_horizontal, stbir_filter filter_vertical,
2547 stbir_colorspace space, void *alloc_context)
2548{
2549 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2550 output_pixels, output_w, output_h, output_stride_in_bytes,
2551 0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
2552 edge_mode_horizontal, edge_mode_vertical, space);
2553}
2554
2555
2556STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2557 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2558 stbir_datatype datatype,
2559 int num_channels, int alpha_channel, int flags,
2560 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
2561 stbir_filter filter_horizontal, stbir_filter filter_vertical,
2562 stbir_colorspace space, void *alloc_context,
2563 float x_scale, float y_scale,
2564 float x_offset, float y_offset)
2565{
2566 float transform[4];
2567 transform[0] = x_scale;
2568 transform[1] = y_scale;
2569 transform[2] = x_offset;
2570 transform[3] = y_offset;
2571 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2572 output_pixels, output_w, output_h, output_stride_in_bytes,
2573 0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
2574 edge_mode_horizontal, edge_mode_vertical, space);
2575}
2576
2577STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
2578 void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
2579 stbir_datatype datatype,
2580 int num_channels, int alpha_channel, int flags,
2581 stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
2582 stbir_filter filter_horizontal, stbir_filter filter_vertical,
2583 stbir_colorspace space, void *alloc_context,
2584 float s0, float t0, float s1, float t1)
2585{
2586 return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
2587 output_pixels, output_w, output_h, output_stride_in_bytes,
2588 s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
2589 edge_mode_horizontal, edge_mode_vertical, space);
2590}
2591
2592#endif // STB_IMAGE_RESIZE_IMPLEMENTATION
2593
2594/*
2595------------------------------------------------------------------------------
2596This software is available under 2 licenses -- choose whichever you prefer.
2597------------------------------------------------------------------------------
2598ALTERNATIVE A - MIT License
2599Copyright (c) 2017 Sean Barrett
2600Permission is hereby granted, free of charge, to any person obtaining a copy of
2601this software and associated documentation files (the "Software"), to deal in
2602the Software without restriction, including without limitation the rights to
2603use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
2604of the Software, and to permit persons to whom the Software is furnished to do
2605so, subject to the following conditions:
2606The above copyright notice and this permission notice shall be included in all
2607copies or substantial portions of the Software.
2608THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2609IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2610FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2611AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2612LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2613OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2614SOFTWARE.
2615------------------------------------------------------------------------------
2616ALTERNATIVE B - Public Domain (www.unlicense.org)
2617This is free and unencumbered software released into the public domain.
2618Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
2619software, either in source code form or as a compiled binary, for any purpose,
2620commercial or non-commercial, and by any means.
2621In jurisdictions that recognize copyright laws, the author or authors of this
2622software dedicate any and all copyright interest in the software to the public
2623domain. We make this dedication for the benefit of the public at large and to
2624the detriment of our heirs and successors. We intend this dedication to be an
2625overt act of relinquishment in perpetuity of all present and future rights to
2626this software under copyright law.
2627THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2628IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2629FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2630AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
2631ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2632WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2633------------------------------------------------------------------------------
2634*/
diff --git a/externals/stb/stb_image_write.h b/externals/stb/stb_image_write.h
new file mode 100644
index 000000000..e4b32ed1b
--- /dev/null
+++ b/externals/stb/stb_image_write.h
@@ -0,0 +1,1724 @@
1/* stb_image_write - v1.16 - public domain - http://nothings.org/stb
2 writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015
3 no warranty implied; use at your own risk
4
5 Before #including,
6
7 #define STB_IMAGE_WRITE_IMPLEMENTATION
8
9 in the file that you want to have the implementation.
10
11 Will probably not work correctly with strict-aliasing optimizations.
12
13ABOUT:
14
15 This header file is a library for writing images to C stdio or a callback.
16
17 The PNG output is not optimal; it is 20-50% larger than the file
18 written by a decent optimizing implementation; though providing a custom
19 zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that.
20 This library is designed for source code compactness and simplicity,
21 not optimal image file size or run-time performance.
22
23BUILDING:
24
25 You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h.
26 You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace
27 malloc,realloc,free.
28 You can #define STBIW_MEMMOVE() to replace memmove()
29 You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function
30 for PNG compression (instead of the builtin one), it must have the following signature:
31 unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality);
32 The returned data will be freed with STBIW_FREE() (free() by default),
33 so it must be heap allocated with STBIW_MALLOC() (malloc() by default),
34
35UNICODE:
36
37 If compiling for Windows and you wish to use Unicode filenames, compile
38 with
39 #define STBIW_WINDOWS_UTF8
40 and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert
41 Windows wchar_t filenames to utf8.
42
43USAGE:
44
45 There are five functions, one for each image file format:
46
47 int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
48 int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
49 int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
50 int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality);
51 int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
52
53 void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically
54
55 There are also five equivalent functions that use an arbitrary write function. You are
56 expected to open/close your file-equivalent before and after calling these:
57
58 int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes);
59 int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
60 int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
61 int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);
62 int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality);
63
64 where the callback is:
65 void stbi_write_func(void *context, void *data, int size);
66
67 You can configure it with these global variables:
68 int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE
69 int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression
70 int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode
71
72
73 You can define STBI_WRITE_NO_STDIO to disable the file variant of these
74 functions, so the library will not use stdio.h at all. However, this will
75 also disable HDR writing, because it requires stdio for formatted output.
76
77 Each function returns 0 on failure and non-0 on success.
78
79 The functions create an image file defined by the parameters. The image
80 is a rectangle of pixels stored from left-to-right, top-to-bottom.
81 Each pixel contains 'comp' channels of data stored interleaved with 8-bits
82 per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is
83 monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall.
84 The *data pointer points to the first byte of the top-left-most pixel.
85 For PNG, "stride_in_bytes" is the distance in bytes from the first byte of
86 a row of pixels to the first byte of the next row of pixels.
87
88 PNG creates output files with the same number of components as the input.
89 The BMP format expands Y to RGB in the file format and does not
90 output alpha.
91
92 PNG supports writing rectangles of data even when the bytes storing rows of
93 data are not consecutive in memory (e.g. sub-rectangles of a larger image),
94 by supplying the stride between the beginning of adjacent rows. The other
95 formats do not. (Thus you cannot write a native-format BMP through the BMP
96 writer, both because it is in BGR order and because it may have padding
97 at the end of the line.)
98
99 PNG allows you to set the deflate compression level by setting the global
100 variable 'stbi_write_png_compression_level' (it defaults to 8).
101
102 HDR expects linear float data. Since the format is always 32-bit rgb(e)
103 data, alpha (if provided) is discarded, and for monochrome data it is
104 replicated across all three channels.
105
106 TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed
107 data, set the global variable 'stbi_write_tga_with_rle' to 0.
108
109 JPEG does ignore alpha channels in input data; quality is between 1 and 100.
110 Higher quality looks better but results in a bigger image.
111 JPEG baseline (no JPEG progressive).
112
113CREDITS:
114
115
116 Sean Barrett - PNG/BMP/TGA
117 Baldur Karlsson - HDR
118 Jean-Sebastien Guay - TGA monochrome
119 Tim Kelsey - misc enhancements
120 Alan Hickman - TGA RLE
121 Emmanuel Julien - initial file IO callback implementation
122 Jon Olick - original jo_jpeg.cpp code
123 Daniel Gibson - integrate JPEG, allow external zlib
124 Aarni Koskela - allow choosing PNG filter
125
126 bugfixes:
127 github:Chribba
128 Guillaume Chereau
129 github:jry2
130 github:romigrou
131 Sergio Gonzalez
132 Jonas Karlsson
133 Filip Wasil
134 Thatcher Ulrich
135 github:poppolopoppo
136 Patrick Boettcher
137 github:xeekworx
138 Cap Petschulat
139 Simon Rodriguez
140 Ivan Tikhonov
141 github:ignotion
142 Adam Schackart
143 Andrew Kensler
144
145LICENSE
146
147 See end of file for license information.
148
149*/
150
151#ifndef INCLUDE_STB_IMAGE_WRITE_H
152#define INCLUDE_STB_IMAGE_WRITE_H
153
154#include <stdlib.h>
155
156// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline'
157#ifndef STBIWDEF
158#ifdef STB_IMAGE_WRITE_STATIC
159#define STBIWDEF static
160#else
161#ifdef __cplusplus
162#define STBIWDEF extern "C"
163#else
164#define STBIWDEF extern
165#endif
166#endif
167#endif
168
169#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations
170STBIWDEF int stbi_write_tga_with_rle;
171STBIWDEF int stbi_write_png_compression_level;
172STBIWDEF int stbi_write_force_png_filter;
173#endif
174
175#ifndef STBI_WRITE_NO_STDIO
176STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
177STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
178STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
179STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
180STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality);
181
182#ifdef STBIW_WINDOWS_UTF8
183STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);
184#endif
185#endif
186
187typedef void stbi_write_func(void *context, void *data, int size);
188
189STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes);
190STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
191STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
192STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);
193STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality);
194
195STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean);
196
197#endif//INCLUDE_STB_IMAGE_WRITE_H
198
199#ifdef STB_IMAGE_WRITE_IMPLEMENTATION
200
201#ifdef _WIN32
202 #ifndef _CRT_SECURE_NO_WARNINGS
203 #define _CRT_SECURE_NO_WARNINGS
204 #endif
205 #ifndef _CRT_NONSTDC_NO_DEPRECATE
206 #define _CRT_NONSTDC_NO_DEPRECATE
207 #endif
208#endif
209
210#ifndef STBI_WRITE_NO_STDIO
211#include <stdio.h>
212#endif // STBI_WRITE_NO_STDIO
213
214#include <stdarg.h>
215#include <stdlib.h>
216#include <string.h>
217#include <math.h>
218
219#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED))
220// ok
221#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED)
222// ok
223#else
224#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)."
225#endif
226
227#ifndef STBIW_MALLOC
228#define STBIW_MALLOC(sz) malloc(sz)
229#define STBIW_REALLOC(p,newsz) realloc(p,newsz)
230#define STBIW_FREE(p) free(p)
231#endif
232
233#ifndef STBIW_REALLOC_SIZED
234#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz)
235#endif
236
237
238#ifndef STBIW_MEMMOVE
239#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz)
240#endif
241
242
243#ifndef STBIW_ASSERT
244#include <assert.h>
245#define STBIW_ASSERT(x) assert(x)
246#endif
247
248#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff)
249
250#ifdef STB_IMAGE_WRITE_STATIC
251static int stbi_write_png_compression_level = 8;
252static int stbi_write_tga_with_rle = 1;
253static int stbi_write_force_png_filter = -1;
254#else
255int stbi_write_png_compression_level = 8;
256int stbi_write_tga_with_rle = 1;
257int stbi_write_force_png_filter = -1;
258#endif
259
260static int stbi__flip_vertically_on_write = 0;
261
262STBIWDEF void stbi_flip_vertically_on_write(int flag)
263{
264 stbi__flip_vertically_on_write = flag;
265}
266
267typedef struct
268{
269 stbi_write_func *func;
270 void *context;
271 unsigned char buffer[64];
272 int buf_used;
273} stbi__write_context;
274
275// initialize a callback-based context
276static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context)
277{
278 s->func = c;
279 s->context = context;
280}
281
282#ifndef STBI_WRITE_NO_STDIO
283
284static void stbi__stdio_write(void *context, void *data, int size)
285{
286 fwrite(data,1,size,(FILE*) context);
287}
288
289#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8)
290#ifdef __cplusplus
291#define STBIW_EXTERN extern "C"
292#else
293#define STBIW_EXTERN extern
294#endif
295STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);
296STBIW_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);
297
298STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
299{
300 return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
301}
302#endif
303
304static FILE *stbiw__fopen(char const *filename, char const *mode)
305{
306 FILE *f;
307#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8)
308 wchar_t wMode[64];
309 wchar_t wFilename[1024];
310 if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename)))
311 return 0;
312
313 if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode)))
314 return 0;
315
316#if defined(_MSC_VER) && _MSC_VER >= 1400
317 if (0 != _wfopen_s(&f, wFilename, wMode))
318 f = 0;
319#else
320 f = _wfopen(wFilename, wMode);
321#endif
322
323#elif defined(_MSC_VER) && _MSC_VER >= 1400
324 if (0 != fopen_s(&f, filename, mode))
325 f=0;
326#else
327 f = fopen(filename, mode);
328#endif
329 return f;
330}
331
332static int stbi__start_write_file(stbi__write_context *s, const char *filename)
333{
334 FILE *f = stbiw__fopen(filename, "wb");
335 stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f);
336 return f != NULL;
337}
338
339static void stbi__end_write_file(stbi__write_context *s)
340{
341 fclose((FILE *)s->context);
342}
343
344#endif // !STBI_WRITE_NO_STDIO
345
346typedef unsigned int stbiw_uint32;
347typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1];
348
349static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v)
350{
351 while (*fmt) {
352 switch (*fmt++) {
353 case ' ': break;
354 case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int));
355 s->func(s->context,&x,1);
356 break; }
357 case '2': { int x = va_arg(v,int);
358 unsigned char b[2];
359 b[0] = STBIW_UCHAR(x);
360 b[1] = STBIW_UCHAR(x>>8);
361 s->func(s->context,b,2);
362 break; }
363 case '4': { stbiw_uint32 x = va_arg(v,int);
364 unsigned char b[4];
365 b[0]=STBIW_UCHAR(x);
366 b[1]=STBIW_UCHAR(x>>8);
367 b[2]=STBIW_UCHAR(x>>16);
368 b[3]=STBIW_UCHAR(x>>24);
369 s->func(s->context,b,4);
370 break; }
371 default:
372 STBIW_ASSERT(0);
373 return;
374 }
375 }
376}
377
378static void stbiw__writef(stbi__write_context *s, const char *fmt, ...)
379{
380 va_list v;
381 va_start(v, fmt);
382 stbiw__writefv(s, fmt, v);
383 va_end(v);
384}
385
386static void stbiw__write_flush(stbi__write_context *s)
387{
388 if (s->buf_used) {
389 s->func(s->context, &s->buffer, s->buf_used);
390 s->buf_used = 0;
391 }
392}
393
394static void stbiw__putc(stbi__write_context *s, unsigned char c)
395{
396 s->func(s->context, &c, 1);
397}
398
399static void stbiw__write1(stbi__write_context *s, unsigned char a)
400{
401 if ((size_t)s->buf_used + 1 > sizeof(s->buffer))
402 stbiw__write_flush(s);
403 s->buffer[s->buf_used++] = a;
404}
405
406static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c)
407{
408 int n;
409 if ((size_t)s->buf_used + 3 > sizeof(s->buffer))
410 stbiw__write_flush(s);
411 n = s->buf_used;
412 s->buf_used = n+3;
413 s->buffer[n+0] = a;
414 s->buffer[n+1] = b;
415 s->buffer[n+2] = c;
416}
417
418static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d)
419{
420 unsigned char bg[3] = { 255, 0, 255}, px[3];
421 int k;
422
423 if (write_alpha < 0)
424 stbiw__write1(s, d[comp - 1]);
425
426 switch (comp) {
427 case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case
428 case 1:
429 if (expand_mono)
430 stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp
431 else
432 stbiw__write1(s, d[0]); // monochrome TGA
433 break;
434 case 4:
435 if (!write_alpha) {
436 // composite against pink background
437 for (k = 0; k < 3; ++k)
438 px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255;
439 stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]);
440 break;
441 }
442 /* FALLTHROUGH */
443 case 3:
444 stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]);
445 break;
446 }
447 if (write_alpha > 0)
448 stbiw__write1(s, d[comp - 1]);
449}
450
451static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono)
452{
453 stbiw_uint32 zero = 0;
454 int i,j, j_end;
455
456 if (y <= 0)
457 return;
458
459 if (stbi__flip_vertically_on_write)
460 vdir *= -1;
461
462 if (vdir < 0) {
463 j_end = -1; j = y-1;
464 } else {
465 j_end = y; j = 0;
466 }
467
468 for (; j != j_end; j += vdir) {
469 for (i=0; i < x; ++i) {
470 unsigned char *d = (unsigned char *) data + (j*x+i)*comp;
471 stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d);
472 }
473 stbiw__write_flush(s);
474 s->func(s->context, &zero, scanline_pad);
475 }
476}
477
478static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...)
479{
480 if (y < 0 || x < 0) {
481 return 0;
482 } else {
483 va_list v;
484 va_start(v, fmt);
485 stbiw__writefv(s, fmt, v);
486 va_end(v);
487 stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono);
488 return 1;
489 }
490}
491
492static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data)
493{
494 if (comp != 4) {
495 // write RGB bitmap
496 int pad = (-x*3) & 3;
497 return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad,
498 "11 4 22 4" "4 44 22 444444",
499 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header
500 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header
501 } else {
502 // RGBA bitmaps need a v4 header
503 // use BI_BITFIELDS mode with 32bpp and alpha mask
504 // (straight BI_RGB with alpha mask doesn't work in most readers)
505 return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0,
506 "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444",
507 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header
508 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header
509 }
510}
511
512STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
513{
514 stbi__write_context s = { 0 };
515 stbi__start_write_callbacks(&s, func, context);
516 return stbi_write_bmp_core(&s, x, y, comp, data);
517}
518
519#ifndef STBI_WRITE_NO_STDIO
520STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data)
521{
522 stbi__write_context s = { 0 };
523 if (stbi__start_write_file(&s,filename)) {
524 int r = stbi_write_bmp_core(&s, x, y, comp, data);
525 stbi__end_write_file(&s);
526 return r;
527 } else
528 return 0;
529}
530#endif //!STBI_WRITE_NO_STDIO
531
532static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data)
533{
534 int has_alpha = (comp == 2 || comp == 4);
535 int colorbytes = has_alpha ? comp-1 : comp;
536 int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3
537
538 if (y < 0 || x < 0)
539 return 0;
540
541 if (!stbi_write_tga_with_rle) {
542 return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0,
543 "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8);
544 } else {
545 int i,j,k;
546 int jend, jdir;
547
548 stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8);
549
550 if (stbi__flip_vertically_on_write) {
551 j = 0;
552 jend = y;
553 jdir = 1;
554 } else {
555 j = y-1;
556 jend = -1;
557 jdir = -1;
558 }
559 for (; j != jend; j += jdir) {
560 unsigned char *row = (unsigned char *) data + j * x * comp;
561 int len;
562
563 for (i = 0; i < x; i += len) {
564 unsigned char *begin = row + i * comp;
565 int diff = 1;
566 len = 1;
567
568 if (i < x - 1) {
569 ++len;
570 diff = memcmp(begin, row + (i + 1) * comp, comp);
571 if (diff) {
572 const unsigned char *prev = begin;
573 for (k = i + 2; k < x && len < 128; ++k) {
574 if (memcmp(prev, row + k * comp, comp)) {
575 prev += comp;
576 ++len;
577 } else {
578 --len;
579 break;
580 }
581 }
582 } else {
583 for (k = i + 2; k < x && len < 128; ++k) {
584 if (!memcmp(begin, row + k * comp, comp)) {
585 ++len;
586 } else {
587 break;
588 }
589 }
590 }
591 }
592
593 if (diff) {
594 unsigned char header = STBIW_UCHAR(len - 1);
595 stbiw__write1(s, header);
596 for (k = 0; k < len; ++k) {
597 stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp);
598 }
599 } else {
600 unsigned char header = STBIW_UCHAR(len - 129);
601 stbiw__write1(s, header);
602 stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin);
603 }
604 }
605 }
606 stbiw__write_flush(s);
607 }
608 return 1;
609}
610
611STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
612{
613 stbi__write_context s = { 0 };
614 stbi__start_write_callbacks(&s, func, context);
615 return stbi_write_tga_core(&s, x, y, comp, (void *) data);
616}
617
618#ifndef STBI_WRITE_NO_STDIO
619STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data)
620{
621 stbi__write_context s = { 0 };
622 if (stbi__start_write_file(&s,filename)) {
623 int r = stbi_write_tga_core(&s, x, y, comp, (void *) data);
624 stbi__end_write_file(&s);
625 return r;
626 } else
627 return 0;
628}
629#endif
630
631// *************************************************************************************************
632// Radiance RGBE HDR writer
633// by Baldur Karlsson
634
635#define stbiw__max(a, b) ((a) > (b) ? (a) : (b))
636
637#ifndef STBI_WRITE_NO_STDIO
638
639static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear)
640{
641 int exponent;
642 float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2]));
643
644 if (maxcomp < 1e-32f) {
645 rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
646 } else {
647 float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp;
648
649 rgbe[0] = (unsigned char)(linear[0] * normalize);
650 rgbe[1] = (unsigned char)(linear[1] * normalize);
651 rgbe[2] = (unsigned char)(linear[2] * normalize);
652 rgbe[3] = (unsigned char)(exponent + 128);
653 }
654}
655
656static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte)
657{
658 unsigned char lengthbyte = STBIW_UCHAR(length+128);
659 STBIW_ASSERT(length+128 <= 255);
660 s->func(s->context, &lengthbyte, 1);
661 s->func(s->context, &databyte, 1);
662}
663
664static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data)
665{
666 unsigned char lengthbyte = STBIW_UCHAR(length);
667 STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code
668 s->func(s->context, &lengthbyte, 1);
669 s->func(s->context, data, length);
670}
671
672static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline)
673{
674 unsigned char scanlineheader[4] = { 2, 2, 0, 0 };
675 unsigned char rgbe[4];
676 float linear[3];
677 int x;
678
679 scanlineheader[2] = (width&0xff00)>>8;
680 scanlineheader[3] = (width&0x00ff);
681
682 /* skip RLE for images too small or large */
683 if (width < 8 || width >= 32768) {
684 for (x=0; x < width; x++) {
685 switch (ncomp) {
686 case 4: /* fallthrough */
687 case 3: linear[2] = scanline[x*ncomp + 2];
688 linear[1] = scanline[x*ncomp + 1];
689 linear[0] = scanline[x*ncomp + 0];
690 break;
691 default:
692 linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0];
693 break;
694 }
695 stbiw__linear_to_rgbe(rgbe, linear);
696 s->func(s->context, rgbe, 4);
697 }
698 } else {
699 int c,r;
700 /* encode into scratch buffer */
701 for (x=0; x < width; x++) {
702 switch(ncomp) {
703 case 4: /* fallthrough */
704 case 3: linear[2] = scanline[x*ncomp + 2];
705 linear[1] = scanline[x*ncomp + 1];
706 linear[0] = scanline[x*ncomp + 0];
707 break;
708 default:
709 linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0];
710 break;
711 }
712 stbiw__linear_to_rgbe(rgbe, linear);
713 scratch[x + width*0] = rgbe[0];
714 scratch[x + width*1] = rgbe[1];
715 scratch[x + width*2] = rgbe[2];
716 scratch[x + width*3] = rgbe[3];
717 }
718
719 s->func(s->context, scanlineheader, 4);
720
721 /* RLE each component separately */
722 for (c=0; c < 4; c++) {
723 unsigned char *comp = &scratch[width*c];
724
725 x = 0;
726 while (x < width) {
727 // find first run
728 r = x;
729 while (r+2 < width) {
730 if (comp[r] == comp[r+1] && comp[r] == comp[r+2])
731 break;
732 ++r;
733 }
734 if (r+2 >= width)
735 r = width;
736 // dump up to first run
737 while (x < r) {
738 int len = r-x;
739 if (len > 128) len = 128;
740 stbiw__write_dump_data(s, len, &comp[x]);
741 x += len;
742 }
743 // if there's a run, output it
744 if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd
745 // find next byte after run
746 while (r < width && comp[r] == comp[x])
747 ++r;
748 // output run up to r
749 while (x < r) {
750 int len = r-x;
751 if (len > 127) len = 127;
752 stbiw__write_run_data(s, len, comp[x]);
753 x += len;
754 }
755 }
756 }
757 }
758 }
759}
760
761static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data)
762{
763 if (y <= 0 || x <= 0 || data == NULL)
764 return 0;
765 else {
766 // Each component is stored separately. Allocate scratch space for full output scanline.
767 unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4);
768 int i, len;
769 char buffer[128];
770 char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n";
771 s->func(s->context, header, sizeof(header)-1);
772
773#ifdef __STDC_LIB_EXT1__
774 len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
775#else
776 len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
777#endif
778 s->func(s->context, buffer, len);
779
780 for(i=0; i < y; i++)
781 stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i));
782 STBIW_FREE(scratch);
783 return 1;
784 }
785}
786
787STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data)
788{
789 stbi__write_context s = { 0 };
790 stbi__start_write_callbacks(&s, func, context);
791 return stbi_write_hdr_core(&s, x, y, comp, (float *) data);
792}
793
794STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data)
795{
796 stbi__write_context s = { 0 };
797 if (stbi__start_write_file(&s,filename)) {
798 int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data);
799 stbi__end_write_file(&s);
800 return r;
801 } else
802 return 0;
803}
804#endif // STBI_WRITE_NO_STDIO
805
806
807//////////////////////////////////////////////////////////////////////////////
808//
809// PNG writer
810//
811
812#ifndef STBIW_ZLIB_COMPRESS
813// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size()
814#define stbiw__sbraw(a) ((int *) (void *) (a) - 2)
815#define stbiw__sbm(a) stbiw__sbraw(a)[0]
816#define stbiw__sbn(a) stbiw__sbraw(a)[1]
817
818#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a))
819#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0)
820#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a)))
821
822#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v))
823#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0)
824#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0)
825
826static void *stbiw__sbgrowf(void **arr, int increment, int itemsize)
827{
828 int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1;
829 void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2);
830 STBIW_ASSERT(p);
831 if (p) {
832 if (!*arr) ((int *) p)[1] = 0;
833 *arr = (void *) ((int *) p + 2);
834 stbiw__sbm(*arr) = m;
835 }
836 return *arr;
837}
838
839static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount)
840{
841 while (*bitcount >= 8) {
842 stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer));
843 *bitbuffer >>= 8;
844 *bitcount -= 8;
845 }
846 return data;
847}
848
849static int stbiw__zlib_bitrev(int code, int codebits)
850{
851 int res=0;
852 while (codebits--) {
853 res = (res << 1) | (code & 1);
854 code >>= 1;
855 }
856 return res;
857}
858
859static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit)
860{
861 int i;
862 for (i=0; i < limit && i < 258; ++i)
863 if (a[i] != b[i]) break;
864 return i;
865}
866
867static unsigned int stbiw__zhash(unsigned char *data)
868{
869 stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16);
870 hash ^= hash << 3;
871 hash += hash >> 5;
872 hash ^= hash << 4;
873 hash += hash >> 17;
874 hash ^= hash << 25;
875 hash += hash >> 6;
876 return hash;
877}
878
879#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount))
880#define stbiw__zlib_add(code,codebits) \
881 (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush())
882#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c)
883// default huffman tables
884#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8)
885#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9)
886#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7)
887#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8)
888#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n))
889#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n))
890
891#define stbiw__ZHASH 16384
892
893#endif // STBIW_ZLIB_COMPRESS
894
895STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality)
896{
897#ifdef STBIW_ZLIB_COMPRESS
898 // user provided a zlib compress implementation, use that
899 return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality);
900#else // use builtin
901 static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 };
902 static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 };
903 static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 };
904 static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 };
905 unsigned int bitbuf=0;
906 int i,j, bitcount=0;
907 unsigned char *out = NULL;
908 unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**));
909 if (hash_table == NULL)
910 return NULL;
911 if (quality < 5) quality = 5;
912
913 stbiw__sbpush(out, 0x78); // DEFLATE 32K window
914 stbiw__sbpush(out, 0x5e); // FLEVEL = 1
915 stbiw__zlib_add(1,1); // BFINAL = 1
916 stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman
917
918 for (i=0; i < stbiw__ZHASH; ++i)
919 hash_table[i] = NULL;
920
921 i=0;
922 while (i < data_len-3) {
923 // hash next 3 bytes of data to be compressed
924 int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3;
925 unsigned char *bestloc = 0;
926 unsigned char **hlist = hash_table[h];
927 int n = stbiw__sbcount(hlist);
928 for (j=0; j < n; ++j) {
929 if (hlist[j]-data > i-32768) { // if entry lies within window
930 int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i);
931 if (d >= best) { best=d; bestloc=hlist[j]; }
932 }
933 }
934 // when hash table entry is too long, delete half the entries
935 if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) {
936 STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality);
937 stbiw__sbn(hash_table[h]) = quality;
938 }
939 stbiw__sbpush(hash_table[h],data+i);
940
941 if (bestloc) {
942 // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal
943 h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1);
944 hlist = hash_table[h];
945 n = stbiw__sbcount(hlist);
946 for (j=0; j < n; ++j) {
947 if (hlist[j]-data > i-32767) {
948 int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1);
949 if (e > best) { // if next match is better, bail on current match
950 bestloc = NULL;
951 break;
952 }
953 }
954 }
955 }
956
957 if (bestloc) {
958 int d = (int) (data+i - bestloc); // distance back
959 STBIW_ASSERT(d <= 32767 && best <= 258);
960 for (j=0; best > lengthc[j+1]-1; ++j);
961 stbiw__zlib_huff(j+257);
962 if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]);
963 for (j=0; d > distc[j+1]-1; ++j);
964 stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5);
965 if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]);
966 i += best;
967 } else {
968 stbiw__zlib_huffb(data[i]);
969 ++i;
970 }
971 }
972 // write out final bytes
973 for (;i < data_len; ++i)
974 stbiw__zlib_huffb(data[i]);
975 stbiw__zlib_huff(256); // end of block
976 // pad with 0 bits to byte boundary
977 while (bitcount)
978 stbiw__zlib_add(0,1);
979
980 for (i=0; i < stbiw__ZHASH; ++i)
981 (void) stbiw__sbfree(hash_table[i]);
982 STBIW_FREE(hash_table);
983
984 // store uncompressed instead if compression was worse
985 if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) {
986 stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1
987 for (j = 0; j < data_len;) {
988 int blocklen = data_len - j;
989 if (blocklen > 32767) blocklen = 32767;
990 stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression
991 stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN
992 stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8));
993 stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN
994 stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8));
995 memcpy(out+stbiw__sbn(out), data+j, blocklen);
996 stbiw__sbn(out) += blocklen;
997 j += blocklen;
998 }
999 }
1000
1001 {
1002 // compute adler32 on input
1003 unsigned int s1=1, s2=0;
1004 int blocklen = (int) (data_len % 5552);
1005 j=0;
1006 while (j < data_len) {
1007 for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; }
1008 s1 %= 65521; s2 %= 65521;
1009 j += blocklen;
1010 blocklen = 5552;
1011 }
1012 stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8));
1013 stbiw__sbpush(out, STBIW_UCHAR(s2));
1014 stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8));
1015 stbiw__sbpush(out, STBIW_UCHAR(s1));
1016 }
1017 *out_len = stbiw__sbn(out);
1018 // make returned pointer freeable
1019 STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len);
1020 return (unsigned char *) stbiw__sbraw(out);
1021#endif // STBIW_ZLIB_COMPRESS
1022}
1023
1024static unsigned int stbiw__crc32(unsigned char *buffer, int len)
1025{
1026#ifdef STBIW_CRC32
1027 return STBIW_CRC32(buffer, len);
1028#else
1029 static unsigned int crc_table[256] =
1030 {
1031 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
1032 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
1033 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
1034 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
1035 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
1036 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
1037 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
1038 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
1039 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
1040 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
1041 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
1042 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
1043 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
1044 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
1045 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
1046 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
1047 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
1048 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
1049 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
1050 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
1051 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
1052 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
1053 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
1054 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
1055 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
1056 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
1057 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
1058 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
1059 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
1060 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
1061 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
1062 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
1063 };
1064
1065 unsigned int crc = ~0u;
1066 int i;
1067 for (i=0; i < len; ++i)
1068 crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)];
1069 return ~crc;
1070#endif
1071}
1072
1073#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4)
1074#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v));
1075#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3])
1076
1077static void stbiw__wpcrc(unsigned char **data, int len)
1078{
1079 unsigned int crc = stbiw__crc32(*data - len - 4, len+4);
1080 stbiw__wp32(*data, crc);
1081}
1082
1083static unsigned char stbiw__paeth(int a, int b, int c)
1084{
1085 int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c);
1086 if (pa <= pb && pa <= pc) return STBIW_UCHAR(a);
1087 if (pb <= pc) return STBIW_UCHAR(b);
1088 return STBIW_UCHAR(c);
1089}
1090
1091// @OPTIMIZE: provide an option that always forces left-predict or paeth predict
1092static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer)
1093{
1094 static int mapping[] = { 0,1,2,3,4 };
1095 static int firstmap[] = { 0,1,0,5,6 };
1096 int *mymap = (y != 0) ? mapping : firstmap;
1097 int i;
1098 int type = mymap[filter_type];
1099 unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y);
1100 int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes;
1101
1102 if (type==0) {
1103 memcpy(line_buffer, z, width*n);
1104 return;
1105 }
1106
1107 // first loop isn't optimized since it's just one pixel
1108 for (i = 0; i < n; ++i) {
1109 switch (type) {
1110 case 1: line_buffer[i] = z[i]; break;
1111 case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break;
1112 case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break;
1113 case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break;
1114 case 5: line_buffer[i] = z[i]; break;
1115 case 6: line_buffer[i] = z[i]; break;
1116 }
1117 }
1118 switch (type) {
1119 case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break;
1120 case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break;
1121 case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break;
1122 case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break;
1123 case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break;
1124 case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break;
1125 }
1126}
1127
1128STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
1129{
1130 int force_filter = stbi_write_force_png_filter;
1131 int ctype[5] = { -1, 0, 4, 2, 6 };
1132 unsigned char sig[8] = { 137,80,78,71,13,10,26,10 };
1133 unsigned char *out,*o, *filt, *zlib;
1134 signed char *line_buffer;
1135 int j,zlen;
1136
1137 if (stride_bytes == 0)
1138 stride_bytes = x * n;
1139
1140 if (force_filter >= 5) {
1141 force_filter = -1;
1142 }
1143
1144 filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0;
1145 line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; }
1146 for (j=0; j < y; ++j) {
1147 int filter_type;
1148 if (force_filter > -1) {
1149 filter_type = force_filter;
1150 stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer);
1151 } else { // Estimate the best filter by running through all of them:
1152 int best_filter = 0, best_filter_val = 0x7fffffff, est, i;
1153 for (filter_type = 0; filter_type < 5; filter_type++) {
1154 stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer);
1155
1156 // Estimate the entropy of the line using this filter; the less, the better.
1157 est = 0;
1158 for (i = 0; i < x*n; ++i) {
1159 est += abs((signed char) line_buffer[i]);
1160 }
1161 if (est < best_filter_val) {
1162 best_filter_val = est;
1163 best_filter = filter_type;
1164 }
1165 }
1166 if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it
1167 stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer);
1168 filter_type = best_filter;
1169 }
1170 }
1171 // when we get here, filter_type contains the filter type, and line_buffer contains the data
1172 filt[j*(x*n+1)] = (unsigned char) filter_type;
1173 STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n);
1174 }
1175 STBIW_FREE(line_buffer);
1176 zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level);
1177 STBIW_FREE(filt);
1178 if (!zlib) return 0;
1179
1180 // each tag requires 12 bytes of overhead
1181 out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12);
1182 if (!out) return 0;
1183 *out_len = 8 + 12+13 + 12+zlen + 12;
1184
1185 o=out;
1186 STBIW_MEMMOVE(o,sig,8); o+= 8;
1187 stbiw__wp32(o, 13); // header length
1188 stbiw__wptag(o, "IHDR");
1189 stbiw__wp32(o, x);
1190 stbiw__wp32(o, y);
1191 *o++ = 8;
1192 *o++ = STBIW_UCHAR(ctype[n]);
1193 *o++ = 0;
1194 *o++ = 0;
1195 *o++ = 0;
1196 stbiw__wpcrc(&o,13);
1197
1198 stbiw__wp32(o, zlen);
1199 stbiw__wptag(o, "IDAT");
1200 STBIW_MEMMOVE(o, zlib, zlen);
1201 o += zlen;
1202 STBIW_FREE(zlib);
1203 stbiw__wpcrc(&o, zlen);
1204
1205 stbiw__wp32(o,0);
1206 stbiw__wptag(o, "IEND");
1207 stbiw__wpcrc(&o,0);
1208
1209 STBIW_ASSERT(o == out + *out_len);
1210
1211 return out;
1212}
1213
1214#ifndef STBI_WRITE_NO_STDIO
1215STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
1216{
1217 FILE *f;
1218 int len;
1219 unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
1220 if (png == NULL) return 0;
1221
1222 f = stbiw__fopen(filename, "wb");
1223 if (!f) { STBIW_FREE(png); return 0; }
1224 fwrite(png, 1, len, f);
1225 fclose(f);
1226 STBIW_FREE(png);
1227 return 1;
1228}
1229#endif
1230
1231STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes)
1232{
1233 int len;
1234 unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
1235 if (png == NULL) return 0;
1236 func(context, png, len);
1237 STBIW_FREE(png);
1238 return 1;
1239}
1240
1241
1242/* ***************************************************************************
1243 *
1244 * JPEG writer
1245 *
1246 * This is based on Jon Olick's jo_jpeg.cpp:
1247 * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html
1248 */
1249
1250static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,
1251 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 };
1252
1253static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) {
1254 int bitBuf = *bitBufP, bitCnt = *bitCntP;
1255 bitCnt += bs[1];
1256 bitBuf |= bs[0] << (24 - bitCnt);
1257 while(bitCnt >= 8) {
1258 unsigned char c = (bitBuf >> 16) & 255;
1259 stbiw__putc(s, c);
1260 if(c == 255) {
1261 stbiw__putc(s, 0);
1262 }
1263 bitBuf <<= 8;
1264 bitCnt -= 8;
1265 }
1266 *bitBufP = bitBuf;
1267 *bitCntP = bitCnt;
1268}
1269
1270static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) {
1271 float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p;
1272 float z1, z2, z3, z4, z5, z11, z13;
1273
1274 float tmp0 = d0 + d7;
1275 float tmp7 = d0 - d7;
1276 float tmp1 = d1 + d6;
1277 float tmp6 = d1 - d6;
1278 float tmp2 = d2 + d5;
1279 float tmp5 = d2 - d5;
1280 float tmp3 = d3 + d4;
1281 float tmp4 = d3 - d4;
1282
1283 // Even part
1284 float tmp10 = tmp0 + tmp3; // phase 2
1285 float tmp13 = tmp0 - tmp3;
1286 float tmp11 = tmp1 + tmp2;
1287 float tmp12 = tmp1 - tmp2;
1288
1289 d0 = tmp10 + tmp11; // phase 3
1290 d4 = tmp10 - tmp11;
1291
1292 z1 = (tmp12 + tmp13) * 0.707106781f; // c4
1293 d2 = tmp13 + z1; // phase 5
1294 d6 = tmp13 - z1;
1295
1296 // Odd part
1297 tmp10 = tmp4 + tmp5; // phase 2
1298 tmp11 = tmp5 + tmp6;
1299 tmp12 = tmp6 + tmp7;
1300
1301 // The rotator is modified from fig 4-8 to avoid extra negations.
1302 z5 = (tmp10 - tmp12) * 0.382683433f; // c6
1303 z2 = tmp10 * 0.541196100f + z5; // c2-c6
1304 z4 = tmp12 * 1.306562965f + z5; // c2+c6
1305 z3 = tmp11 * 0.707106781f; // c4
1306
1307 z11 = tmp7 + z3; // phase 5
1308 z13 = tmp7 - z3;
1309
1310 *d5p = z13 + z2; // phase 6
1311 *d3p = z13 - z2;
1312 *d1p = z11 + z4;
1313 *d7p = z11 - z4;
1314
1315 *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6;
1316}
1317
1318static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) {
1319 int tmp1 = val < 0 ? -val : val;
1320 val = val < 0 ? val-1 : val;
1321 bits[1] = 1;
1322 while(tmp1 >>= 1) {
1323 ++bits[1];
1324 }
1325 bits[0] = val & ((1<<bits[1])-1);
1326}
1327
1328static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, int *bitCnt, float *CDU, int du_stride, float *fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) {
1329 const unsigned short EOB[2] = { HTAC[0x00][0], HTAC[0x00][1] };
1330 const unsigned short M16zeroes[2] = { HTAC[0xF0][0], HTAC[0xF0][1] };
1331 int dataOff, i, j, n, diff, end0pos, x, y;
1332 int DU[64];
1333
1334 // DCT rows
1335 for(dataOff=0, n=du_stride*8; dataOff<n; dataOff+=du_stride) {
1336 stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+1], &CDU[dataOff+2], &CDU[dataOff+3], &CDU[dataOff+4], &CDU[dataOff+5], &CDU[dataOff+6], &CDU[dataOff+7]);
1337 }
1338 // DCT columns
1339 for(dataOff=0; dataOff<8; ++dataOff) {
1340 stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+du_stride], &CDU[dataOff+du_stride*2], &CDU[dataOff+du_stride*3], &CDU[dataOff+du_stride*4],
1341 &CDU[dataOff+du_stride*5], &CDU[dataOff+du_stride*6], &CDU[dataOff+du_stride*7]);
1342 }
1343 // Quantize/descale/zigzag the coefficients
1344 for(y = 0, j=0; y < 8; ++y) {
1345 for(x = 0; x < 8; ++x,++j) {
1346 float v;
1347 i = y*du_stride+x;
1348 v = CDU[i]*fdtbl[j];
1349 // DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
1350 // ceilf() and floorf() are C99, not C89, but I /think/ they're not needed here anyway?
1351 DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? v - 0.5f : v + 0.5f);
1352 }
1353 }
1354
1355 // Encode DC
1356 diff = DU[0] - DC;
1357 if (diff == 0) {
1358 stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[0]);
1359 } else {
1360 unsigned short bits[2];
1361 stbiw__jpg_calcBits(diff, bits);
1362 stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[bits[1]]);
1363 stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits);
1364 }
1365 // Encode ACs
1366 end0pos = 63;
1367 for(; (end0pos>0)&&(DU[end0pos]==0); --end0pos) {
1368 }
1369 // end0pos = first element in reverse order !=0
1370 if(end0pos == 0) {
1371 stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB);
1372 return DU[0];
1373 }
1374 for(i = 1; i <= end0pos; ++i) {
1375 int startpos = i;
1376 int nrzeroes;
1377 unsigned short bits[2];
1378 for (; DU[i]==0 && i<=end0pos; ++i) {
1379 }
1380 nrzeroes = i-startpos;
1381 if ( nrzeroes >= 16 ) {
1382 int lng = nrzeroes>>4;
1383 int nrmarker;
1384 for (nrmarker=1; nrmarker <= lng; ++nrmarker)
1385 stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes);
1386 nrzeroes &= 15;
1387 }
1388 stbiw__jpg_calcBits(DU[i], bits);
1389 stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]);
1390 stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits);
1391 }
1392 if(end0pos != 63) {
1393 stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB);
1394 }
1395 return DU[0];
1396}
1397
1398static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) {
1399 // Constants that don't pollute global namespace
1400 static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0};
1401 static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
1402 static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d};
1403 static const unsigned char std_ac_luminance_values[] = {
1404 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
1405 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
1406 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
1407 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
1408 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
1409 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
1410 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa
1411 };
1412 static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0};
1413 static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
1414 static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77};
1415 static const unsigned char std_ac_chrominance_values[] = {
1416 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
1417 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
1418 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
1419 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
1420 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
1421 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
1422 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa
1423 };
1424 // Huffman tables
1425 static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}};
1426 static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}};
1427 static const unsigned short YAC_HT[256][2] = {
1428 {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1429 {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1430 {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1431 {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1432 {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1433 {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1434 {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1435 {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1436 {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1437 {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1438 {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1439 {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1440 {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1441 {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1442 {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0},
1443 {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0}
1444 };
1445 static const unsigned short UVAC_HT[256][2] = {
1446 {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1447 {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1448 {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1449 {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1450 {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1451 {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1452 {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1453 {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1454 {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1455 {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1456 {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1457 {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1458 {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1459 {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1460 {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0},
1461 {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0}
1462 };
1463 static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,
1464 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99};
1465 static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,
1466 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99};
1467 static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f,
1468 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f };
1469
1470 int row, col, i, k, subsample;
1471 float fdtbl_Y[64], fdtbl_UV[64];
1472 unsigned char YTable[64], UVTable[64];
1473
1474 if(!data || !width || !height || comp > 4 || comp < 1) {
1475 return 0;
1476 }
1477
1478 quality = quality ? quality : 90;
1479 subsample = quality <= 90 ? 1 : 0;
1480 quality = quality < 1 ? 1 : quality > 100 ? 100 : quality;
1481 quality = quality < 50 ? 5000 / quality : 200 - quality * 2;
1482
1483 for(i = 0; i < 64; ++i) {
1484 int uvti, yti = (YQT[i]*quality+50)/100;
1485 YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti);
1486 uvti = (UVQT[i]*quality+50)/100;
1487 UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti);
1488 }
1489
1490 for(row = 0, k = 0; row < 8; ++row) {
1491 for(col = 0; col < 8; ++col, ++k) {
1492 fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]);
1493 fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]);
1494 }
1495 }
1496
1497 // Write Headers
1498 {
1499 static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 };
1500 static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 };
1501 const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width),
1502 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
1503 s->func(s->context, (void*)head0, sizeof(head0));
1504 s->func(s->context, (void*)YTable, sizeof(YTable));
1505 stbiw__putc(s, 1);
1506 s->func(s->context, UVTable, sizeof(UVTable));
1507 s->func(s->context, (void*)head1, sizeof(head1));
1508 s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1);
1509 s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values));
1510 stbiw__putc(s, 0x10); // HTYACinfo
1511 s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1);
1512 s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values));
1513 stbiw__putc(s, 1); // HTUDCinfo
1514 s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1);
1515 s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values));
1516 stbiw__putc(s, 0x11); // HTUACinfo
1517 s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1);
1518 s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values));
1519 s->func(s->context, (void*)head2, sizeof(head2));
1520 }
1521
1522 // Encode 8x8 macroblocks
1523 {
1524 static const unsigned short fillBits[] = {0x7F, 7};
1525 int DCY=0, DCU=0, DCV=0;
1526 int bitBuf=0, bitCnt=0;
1527 // comp == 2 is grey+alpha (alpha is ignored)
1528 int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0;
1529 const unsigned char *dataR = (const unsigned char *)data;
1530 const unsigned char *dataG = dataR + ofsG;
1531 const unsigned char *dataB = dataR + ofsB;
1532 int x, y, pos;
1533 if(subsample) {
1534 for(y = 0; y < height; y += 16) {
1535 for(x = 0; x < width; x += 16) {
1536 float Y[256], U[256], V[256];
1537 for(row = y, pos = 0; row < y+16; ++row) {
1538 // row >= height => use last input row
1539 int clamped_row = (row < height) ? row : height - 1;
1540 int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
1541 for(col = x; col < x+16; ++col, ++pos) {
1542 // if col >= width => use pixel from last input column
1543 int p = base_p + ((col < width) ? col : (width-1))*comp;
1544 float r = dataR[p], g = dataG[p], b = dataB[p];
1545 Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
1546 U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
1547 V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
1548 }
1549 }
1550 DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
1551 DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
1552 DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
1553 DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
1554
1555 // subsample U,V
1556 {
1557 float subU[64], subV[64];
1558 int yy, xx;
1559 for(yy = 0, pos = 0; yy < 8; ++yy) {
1560 for(xx = 0; xx < 8; ++xx, ++pos) {
1561 int j = yy*32+xx*2;
1562 subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f;
1563 subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f;
1564 }
1565 }
1566 DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
1567 DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
1568 }
1569 }
1570 }
1571 } else {
1572 for(y = 0; y < height; y += 8) {
1573 for(x = 0; x < width; x += 8) {
1574 float Y[64], U[64], V[64];
1575 for(row = y, pos = 0; row < y+8; ++row) {
1576 // row >= height => use last input row
1577 int clamped_row = (row < height) ? row : height - 1;
1578 int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
1579 for(col = x; col < x+8; ++col, ++pos) {
1580 // if col >= width => use pixel from last input column
1581 int p = base_p + ((col < width) ? col : (width-1))*comp;
1582 float r = dataR[p], g = dataG[p], b = dataB[p];
1583 Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
1584 U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
1585 V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
1586 }
1587 }
1588
1589 DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT);
1590 DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
1591 DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
1592 }
1593 }
1594 }
1595
1596 // Do the bit alignment of the EOI marker
1597 stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits);
1598 }
1599
1600 // EOI
1601 stbiw__putc(s, 0xFF);
1602 stbiw__putc(s, 0xD9);
1603
1604 return 1;
1605}
1606
1607STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality)
1608{
1609 stbi__write_context s = { 0 };
1610 stbi__start_write_callbacks(&s, func, context);
1611 return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality);
1612}
1613
1614
1615#ifndef STBI_WRITE_NO_STDIO
1616STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality)
1617{
1618 stbi__write_context s = { 0 };
1619 if (stbi__start_write_file(&s,filename)) {
1620 int r = stbi_write_jpg_core(&s, x, y, comp, data, quality);
1621 stbi__end_write_file(&s);
1622 return r;
1623 } else
1624 return 0;
1625}
1626#endif
1627
1628#endif // STB_IMAGE_WRITE_IMPLEMENTATION
1629
1630/* Revision history
1631 1.16 (2021-07-11)
1632 make Deflate code emit uncompressed blocks when it would otherwise expand
1633 support writing BMPs with alpha channel
1634 1.15 (2020-07-13) unknown
1635 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels
1636 1.13
1637 1.12
1638 1.11 (2019-08-11)
1639
1640 1.10 (2019-02-07)
1641 support utf8 filenames in Windows; fix warnings and platform ifdefs
1642 1.09 (2018-02-11)
1643 fix typo in zlib quality API, improve STB_I_W_STATIC in C++
1644 1.08 (2018-01-29)
1645 add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter
1646 1.07 (2017-07-24)
1647 doc fix
1648 1.06 (2017-07-23)
1649 writing JPEG (using Jon Olick's code)
1650 1.05 ???
1651 1.04 (2017-03-03)
1652 monochrome BMP expansion
1653 1.03 ???
1654 1.02 (2016-04-02)
1655 avoid allocating large structures on the stack
1656 1.01 (2016-01-16)
1657 STBIW_REALLOC_SIZED: support allocators with no realloc support
1658 avoid race-condition in crc initialization
1659 minor compile issues
1660 1.00 (2015-09-14)
1661 installable file IO function
1662 0.99 (2015-09-13)
1663 warning fixes; TGA rle support
1664 0.98 (2015-04-08)
1665 added STBIW_MALLOC, STBIW_ASSERT etc
1666 0.97 (2015-01-18)
1667 fixed HDR asserts, rewrote HDR rle logic
1668 0.96 (2015-01-17)
1669 add HDR output
1670 fix monochrome BMP
1671 0.95 (2014-08-17)
1672 add monochrome TGA output
1673 0.94 (2014-05-31)
1674 rename private functions to avoid conflicts with stb_image.h
1675 0.93 (2014-05-27)
1676 warning fixes
1677 0.92 (2010-08-01)
1678 casts to unsigned char to fix warnings
1679 0.91 (2010-07-17)
1680 first public release
1681 0.90 first internal release
1682*/
1683
1684/*
1685------------------------------------------------------------------------------
1686This software is available under 2 licenses -- choose whichever you prefer.
1687------------------------------------------------------------------------------
1688ALTERNATIVE A - MIT License
1689Copyright (c) 2017 Sean Barrett
1690Permission is hereby granted, free of charge, to any person obtaining a copy of
1691this software and associated documentation files (the "Software"), to deal in
1692the Software without restriction, including without limitation the rights to
1693use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
1694of the Software, and to permit persons to whom the Software is furnished to do
1695so, subject to the following conditions:
1696The above copyright notice and this permission notice shall be included in all
1697copies or substantial portions of the Software.
1698THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1699IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1700FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1701AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1702LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1703OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1704SOFTWARE.
1705------------------------------------------------------------------------------
1706ALTERNATIVE B - Public Domain (www.unlicense.org)
1707This is free and unencumbered software released into the public domain.
1708Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
1709software, either in source code form or as a compiled binary, for any purpose,
1710commercial or non-commercial, and by any means.
1711In jurisdictions that recognize copyright laws, the author or authors of this
1712software dedicate any and all copyright interest in the software to the public
1713domain. We make this dedication for the benefit of the public at large and to
1714the detriment of our heirs and successors. We intend this dedication to be an
1715overt act of relinquishment in perpetuity of all present and future rights to
1716this software under copyright law.
1717THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1718IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1719FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1720AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1721ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1722WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1723------------------------------------------------------------------------------
1724*/
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..f273c880a 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
@@ -27,8 +26,7 @@ import androidx.fragment.app.Fragment
27import androidx.fragment.app.activityViewModels 26import 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.GridLayoutManager
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(
@@ -182,7 +186,8 @@ class HomeSettingsFragment : Fragment() {
182 } 186 }
183 187
184 binding.homeSettingsList.apply { 188 binding.homeSettingsList.apply {
185 layoutManager = LinearLayoutManager(requireContext()) 189 layoutManager =
190 GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns))
186 adapter = HomeSettingAdapter( 191 adapter = HomeSettingAdapter(
187 requireActivity() as AppCompatActivity, 192 requireActivity() as AppCompatActivity,
188 viewLifecycleOwner, 193 viewLifecycleOwner,
@@ -292,31 +297,6 @@ class HomeSettingsFragment : Fragment() {
292 } 297 }
293 } 298 }
294 299
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() { 300 private fun shareLog() {
321 val file = DocumentFile.fromSingleUri( 301 val file = DocumentFile.fromSingleUri(
322 mainActivity, 302 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/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml
index f9f1d89fb..6e8a232f9 100644
--- a/src/android/app/src/main/res/layout/card_home_option.xml
+++ b/src/android/app/src/main/res/layout/card_home_option.xml
@@ -16,7 +16,8 @@
16 <LinearLayout 16 <LinearLayout
17 android:id="@+id/option_layout" 17 android:id="@+id/option_layout"
18 android:layout_width="match_parent" 18 android:layout_width="match_parent"
19 android:layout_height="wrap_content"> 19 android:layout_height="wrap_content"
20 android:layout_gravity="center_vertical">
20 21
21 <ImageView 22 <ImageView
22 android:id="@+id/option_icon" 23 android:id="@+id/option_icon"
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..e216eb3de 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -120,6 +120,8 @@ add_library(common STATIC
120 socket_types.h 120 socket_types.h
121 spin_lock.cpp 121 spin_lock.cpp
122 spin_lock.h 122 spin_lock.h
123 stb.cpp
124 stb.h
123 steady_clock.cpp 125 steady_clock.cpp
124 steady_clock.h 126 steady_clock.h
125 stream.cpp 127 stream.cpp
@@ -189,6 +191,14 @@ if(ARCHITECTURE_x86_64)
189 target_link_libraries(common PRIVATE xbyak::xbyak) 191 target_link_libraries(common PRIVATE xbyak::xbyak)
190endif() 192endif()
191 193
194if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
195 target_sources(common
196 PRIVATE
197 arm64/native_clock.cpp
198 arm64/native_clock.h
199 )
200endif()
201
192if (MSVC) 202if (MSVC)
193 target_compile_definitions(common PRIVATE 203 target_compile_definitions(common PRIVATE
194 # The standard library doesn't provide any replacement for codecvt yet 204 # The standard library doesn't provide any replacement for codecvt yet
@@ -200,6 +210,8 @@ if (MSVC)
200 /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 210 /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
201 /we4800 # Implicit conversion from 'type' to bool. Possible information loss 211 /we4800 # Implicit conversion from 'type' to bool. Possible information loss
202 ) 212 )
213else()
214 set_source_files_properties(stb.cpp PROPERTIES COMPILE_OPTIONS "-Wno-implicit-fallthrough;-Wno-missing-declarations;-Wno-missing-field-initializers")
203endif() 215endif()
204 216
205if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 217if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
@@ -215,7 +227,7 @@ endif()
215 227
216create_target_directory_groups(common) 228create_target_directory_groups(common)
217 229
218target_link_libraries(common PUBLIC Boost::context Boost::headers fmt::fmt microprofile Threads::Threads) 230target_link_libraries(common PUBLIC Boost::context Boost::headers fmt::fmt microprofile stb::headers Threads::Threads)
219target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd LLVM::Demangle) 231target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd LLVM::Demangle)
220 232
221if (ANDROID) 233if (ANDROID)
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 1a3f6ab45..bcf447089 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -19,10 +19,12 @@
19#define LOAD_DIR "load" 19#define LOAD_DIR "load"
20#define LOG_DIR "log" 20#define LOG_DIR "log"
21#define NAND_DIR "nand" 21#define NAND_DIR "nand"
22#define PLAY_TIME_DIR "play_time"
22#define SCREENSHOTS_DIR "screenshots" 23#define SCREENSHOTS_DIR "screenshots"
23#define SDMC_DIR "sdmc" 24#define SDMC_DIR "sdmc"
24#define SHADER_DIR "shader" 25#define SHADER_DIR "shader"
25#define TAS_DIR "tas" 26#define TAS_DIR "tas"
27#define ICONS_DIR "icons"
26 28
27// yuzu-specific files 29// yuzu-specific files
28 30
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index fbac4d80c..0c4c88cde 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -125,10 +125,12 @@ public:
125 GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); 125 GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
126 GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); 126 GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
127 GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); 127 GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
128 GenerateYuzuPath(YuzuPath::PlayTimeDir, yuzu_path / PLAY_TIME_DIR);
128 GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); 129 GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
129 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); 130 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
130 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); 131 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
131 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); 132 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
133 GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
132 } 134 }
133 135
134private: 136private:
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 036e475aa..2874ea738 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -21,10 +21,12 @@ enum class YuzuPath {
21 LoadDir, // Where cheat/mod files are stored. 21 LoadDir, // Where cheat/mod files are stored.
22 LogDir, // Where log files are stored. 22 LogDir, // Where log files are stored.
23 NANDDir, // Where the emulated NAND is stored. 23 NANDDir, // Where the emulated NAND is stored.
24 PlayTimeDir, // Where play time data is stored.
24 ScreenshotsDir, // Where yuzu screenshots are stored. 25 ScreenshotsDir, // Where yuzu screenshots are stored.
25 SDMCDir, // Where the emulated SDMC is stored. 26 SDMCDir, // Where the emulated SDMC is stored.
26 ShaderDir, // Where shaders are stored. 27 ShaderDir, // Where shaders are stored.
27 TASDir, // Where TAS scripts are stored. 28 TASDir, // Where TAS scripts are stored.
29 IconsDir, // Where Icons for Windows shortcuts are stored.
28}; 30};
29 31
30/** 32/**
diff --git a/src/common/nvidia_flags.cpp b/src/common/nvidia_flags.cpp
index 7ed7690ee..fa3747782 100644
--- a/src/common/nvidia_flags.cpp
+++ b/src/common/nvidia_flags.cpp
@@ -25,6 +25,7 @@ void ConfigureNvidiaEnvironmentFlags() {
25 25
26 void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path_string).c_str())); 26 void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path_string).c_str()));
27 void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1")); 27 void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1"));
28 void(_putenv("__GL_THREADED_OPTIMIZATIONS=1"));
28#endif 29#endif
29} 30}
30 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 6a3fe47c9..9317075f7 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/stb.cpp b/src/common/stb.cpp
new file mode 100644
index 000000000..d3b16665d
--- /dev/null
+++ b/src/common/stb.cpp
@@ -0,0 +1,8 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#define STB_IMAGE_IMPLEMENTATION
5#define STB_IMAGE_RESIZE_IMPLEMENTATION
6#define STB_IMAGE_WRITE_IMPLEMENTATION
7
8#include "common/stb.h"
diff --git a/src/common/stb.h b/src/common/stb.h
new file mode 100644
index 000000000..e5c197c11
--- /dev/null
+++ b/src/common/stb.h
@@ -0,0 +1,8 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <stb_image.h>
7#include <stb_image_resize.h>
8#include <stb_image_write.h>
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/thread.cpp b/src/common/thread.cpp
index 919e33af9..34cc1527b 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -11,6 +11,7 @@
11#include <mach/mach.h> 11#include <mach/mach.h>
12#elif defined(_WIN32) 12#elif defined(_WIN32)
13#include <windows.h> 13#include <windows.h>
14#include "common/string_util.h"
14#else 15#else
15#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) 16#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
16#include <pthread_np.h> 17#include <pthread_np.h>
@@ -82,29 +83,8 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
82#ifdef _MSC_VER 83#ifdef _MSC_VER
83 84
84// Sets the debugger-visible name of the current thread. 85// Sets the debugger-visible name of the current thread.
85// Uses trick documented in:
86// https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code
87void SetCurrentThreadName(const char* name) { 86void SetCurrentThreadName(const char* name) {
88 static const DWORD MS_VC_EXCEPTION = 0x406D1388; 87 SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
89
90#pragma pack(push, 8)
91 struct THREADNAME_INFO {
92 DWORD dwType; // must be 0x1000
93 LPCSTR szName; // pointer to name (in user addr space)
94 DWORD dwThreadID; // thread ID (-1=caller thread)
95 DWORD dwFlags; // reserved for future use, must be zero
96 } info;
97#pragma pack(pop)
98
99 info.dwType = 0x1000;
100 info.szName = name;
101 info.dwThreadID = std::numeric_limits<DWORD>::max();
102 info.dwFlags = 0;
103
104 __try {
105 RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
106 } __except (EXCEPTION_CONTINUE_EXECUTION) {
107 }
108} 88}
109 89
110#else // !MSVC_VER, so must be POSIX threads 90#else // !MSVC_VER, so must be POSIX threads
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/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index 0c012f094..5e27dde58 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -86,9 +86,9 @@ void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<Backt
86 86
87 std::map<std::string, Symbols::Symbols> symbols; 87 std::map<std::string, Symbols::Symbols> symbols;
88 for (const auto& module : modules) { 88 for (const auto& module : modules) {
89 symbols.insert_or_assign( 89 symbols.insert_or_assign(module.second,
90 module.second, Symbols::GetSymbols(module.first, system.ApplicationMemory(), 90 Symbols::GetSymbols(module.first, system.ApplicationMemory(),
91 system.ApplicationProcess()->Is64BitProcess())); 91 system.ApplicationProcess()->Is64Bit()));
92 } 92 }
93 93
94 for (auto& entry : out) { 94 for (auto& entry : out) {
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 08cbb8978..14d6c8c27 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)) {
@@ -312,17 +309,10 @@ struct System::Impl {
312 309
313 telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider); 310 telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
314 311
315 // Create a resource limit for the process.
316 const auto physical_memory_size =
317 kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
318 auto* resource_limit = Kernel::CreateResourceLimitForProcess(system, physical_memory_size);
319
320 // Create the process. 312 // Create the process.
321 auto main_process = Kernel::KProcess::Create(system.Kernel()); 313 auto main_process = Kernel::KProcess::Create(system.Kernel());
322 ASSERT(Kernel::KProcess::Initialize(main_process, system, "main",
323 Kernel::KProcess::ProcessType::Userland, resource_limit)
324 .IsSuccess());
325 Kernel::KProcess::Register(system.Kernel(), main_process); 314 Kernel::KProcess::Register(system.Kernel(), main_process);
315 kernel.AppendNewProcess(main_process);
326 kernel.MakeApplicationProcess(main_process); 316 kernel.MakeApplicationProcess(main_process);
327 const auto [load_result, load_parameters] = app_loader->Load(*main_process, system); 317 const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
328 if (load_result != Loader::ResultStatus::Success) { 318 if (load_result != Loader::ResultStatus::Success) {
@@ -421,6 +411,7 @@ struct System::Impl {
421 services->KillNVNFlinger(); 411 services->KillNVNFlinger();
422 } 412 }
423 kernel.CloseServices(); 413 kernel.CloseServices();
414 kernel.ShutdownCores();
424 services.reset(); 415 services.reset();
425 service_manager.reset(); 416 service_manager.reset();
426 cheat_engine.reset(); 417 cheat_engine.reset();
@@ -432,7 +423,6 @@ struct System::Impl {
432 gpu_core.reset(); 423 gpu_core.reset();
433 host1x_core.reset(); 424 host1x_core.reset();
434 perf_stats.reset(); 425 perf_stats.reset();
435 kernel.ShutdownCores();
436 cpu_manager.Shutdown(); 426 cpu_manager.Shutdown();
437 debugger.reset(); 427 debugger.reset();
438 kernel.Shutdown(); 428 kernel.Shutdown();
@@ -1078,6 +1068,10 @@ void System::ApplySettings() {
1078 impl->RefreshTime(); 1068 impl->RefreshTime();
1079 1069
1080 if (IsPoweredOn()) { 1070 if (IsPoweredOn()) {
1071 if (Settings::values.custom_rtc_enabled) {
1072 const s64 posix_time{Settings::values.custom_rtc.GetValue()};
1073 GetTimeManager().UpdateLocalSystemClockTime(posix_time);
1074 }
1081 Renderer().RefreshBaseSettings(); 1075 Renderer().RefreshBaseSettings();
1082 } 1076 }
1083} 1077}
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index a1589fecb..0e270eb50 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -258,20 +258,20 @@ private:
258 Kernel::KScopedSchedulerLock sl{system.Kernel()}; 258 Kernel::KScopedSchedulerLock sl{system.Kernel()};
259 259
260 // Put all threads to sleep on next scheduler round. 260 // Put all threads to sleep on next scheduler round.
261 for (auto* thread : ThreadList()) { 261 for (auto& thread : ThreadList()) {
262 thread->RequestSuspend(Kernel::SuspendType::Debug); 262 thread.RequestSuspend(Kernel::SuspendType::Debug);
263 } 263 }
264 } 264 }
265 265
266 void ResumeEmulation(Kernel::KThread* except = nullptr) { 266 void ResumeEmulation(Kernel::KThread* except = nullptr) {
267 // Wake up all threads. 267 // Wake up all threads.
268 for (auto* thread : ThreadList()) { 268 for (auto& thread : ThreadList()) {
269 if (thread == except) { 269 if (std::addressof(thread) == except) {
270 continue; 270 continue;
271 } 271 }
272 272
273 thread->SetStepState(Kernel::StepState::NotStepping); 273 thread.SetStepState(Kernel::StepState::NotStepping);
274 thread->Resume(Kernel::SuspendType::Debug); 274 thread.Resume(Kernel::SuspendType::Debug);
275 } 275 }
276 } 276 }
277 277
@@ -283,13 +283,17 @@ private:
283 } 283 }
284 284
285 void UpdateActiveThread() { 285 void UpdateActiveThread() {
286 const auto& threads{ThreadList()}; 286 auto& threads{ThreadList()};
287 if (std::find(threads.begin(), threads.end(), state->active_thread) == threads.end()) { 287 for (auto& thread : threads) {
288 state->active_thread = threads.front(); 288 if (std::addressof(thread) == state->active_thread) {
289 // Thread is still alive, no need to update.
290 return;
291 }
289 } 292 }
293 state->active_thread = std::addressof(threads.front());
290 } 294 }
291 295
292 const std::list<Kernel::KThread*>& ThreadList() { 296 Kernel::KProcess::ThreadList& ThreadList() {
293 return system.ApplicationProcess()->GetThreadList(); 297 return system.ApplicationProcess()->GetThreadList();
294 } 298 }
295 299
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index e55831f27..6f5f5156b 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 }
@@ -96,7 +109,7 @@ static std::string EscapeXML(std::string_view data) {
96 109
97GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_) 110GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
98 : DebuggerFrontend(backend_), system{system_} { 111 : DebuggerFrontend(backend_), system{system_} {
99 if (system.ApplicationProcess()->Is64BitProcess()) { 112 if (system.ApplicationProcess()->Is64Bit()) {
100 arch = std::make_unique<GDBStubA64>(); 113 arch = std::make_unique<GDBStubA64>();
101 } else { 114 } else {
102 arch = std::make_unique<GDBStubA32>(); 115 arch = std::make_unique<GDBStubA32>();
@@ -433,10 +446,10 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) {
433// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp 446// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
434 447
435static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory, 448static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory,
436 const Kernel::KThread* thread) { 449 const Kernel::KThread& thread) {
437 // Read thread type from TLS 450 // Read thread type from TLS
438 const VAddr tls_thread_type{memory.Read32(thread->GetTlsAddress() + 0x1fc)}; 451 const VAddr tls_thread_type{memory.Read32(thread.GetTlsAddress() + 0x1fc)};
439 const VAddr argument_thread_type{thread->GetArgument()}; 452 const VAddr argument_thread_type{thread.GetArgument()};
440 453
441 if (argument_thread_type && tls_thread_type != argument_thread_type) { 454 if (argument_thread_type && tls_thread_type != argument_thread_type) {
442 // Probably not created by nnsdk, no name available. 455 // Probably not created by nnsdk, no name available.
@@ -464,10 +477,10 @@ static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory&
464} 477}
465 478
466static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory, 479static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory,
467 const Kernel::KThread* thread) { 480 const Kernel::KThread& thread) {
468 // Read thread type from TLS 481 // Read thread type from TLS
469 const VAddr tls_thread_type{memory.Read64(thread->GetTlsAddress() + 0x1f8)}; 482 const VAddr tls_thread_type{memory.Read64(thread.GetTlsAddress() + 0x1f8)};
470 const VAddr argument_thread_type{thread->GetArgument()}; 483 const VAddr argument_thread_type{thread.GetArgument()};
471 484
472 if (argument_thread_type && tls_thread_type != argument_thread_type) { 485 if (argument_thread_type && tls_thread_type != argument_thread_type) {
473 // Probably not created by nnsdk, no name available. 486 // Probably not created by nnsdk, no name available.
@@ -495,16 +508,16 @@ static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory&
495} 508}
496 509
497static std::optional<std::string> GetThreadName(Core::System& system, 510static std::optional<std::string> GetThreadName(Core::System& system,
498 const Kernel::KThread* thread) { 511 const Kernel::KThread& thread) {
499 if (system.ApplicationProcess()->Is64BitProcess()) { 512 if (system.ApplicationProcess()->Is64Bit()) {
500 return GetNameFromThreadType64(system.ApplicationMemory(), thread); 513 return GetNameFromThreadType64(system.ApplicationMemory(), thread);
501 } else { 514 } else {
502 return GetNameFromThreadType32(system.ApplicationMemory(), thread); 515 return GetNameFromThreadType32(system.ApplicationMemory(), thread);
503 } 516 }
504} 517}
505 518
506static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) { 519static std::string_view GetThreadWaitReason(const Kernel::KThread& thread) {
507 switch (thread->GetWaitReasonForDebugging()) { 520 switch (thread.GetWaitReasonForDebugging()) {
508 case Kernel::ThreadWaitReasonForDebugging::Sleep: 521 case Kernel::ThreadWaitReasonForDebugging::Sleep:
509 return "Sleep"; 522 return "Sleep";
510 case Kernel::ThreadWaitReasonForDebugging::IPC: 523 case Kernel::ThreadWaitReasonForDebugging::IPC:
@@ -522,8 +535,8 @@ static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) {
522 } 535 }
523} 536}
524 537
525static std::string GetThreadState(const Kernel::KThread* thread) { 538static std::string GetThreadState(const Kernel::KThread& thread) {
526 switch (thread->GetState()) { 539 switch (thread.GetState()) {
527 case Kernel::ThreadState::Initialized: 540 case Kernel::ThreadState::Initialized:
528 return "Initialized"; 541 return "Initialized";
529 case Kernel::ThreadState::Waiting: 542 case Kernel::ThreadState::Waiting:
@@ -591,7 +604,7 @@ void GDBStub::HandleQuery(std::string_view command) {
591 const auto& threads = system.ApplicationProcess()->GetThreadList(); 604 const auto& threads = system.ApplicationProcess()->GetThreadList();
592 std::vector<std::string> thread_ids; 605 std::vector<std::string> thread_ids;
593 for (const auto& thread : threads) { 606 for (const auto& thread : threads) {
594 thread_ids.push_back(fmt::format("{:x}", thread->GetThreadId())); 607 thread_ids.push_back(fmt::format("{:x}", thread.GetThreadId()));
595 } 608 }
596 SendReply(fmt::format("m{}", fmt::join(thread_ids, ","))); 609 SendReply(fmt::format("m{}", fmt::join(thread_ids, ",")));
597 } else if (command.starts_with("sThreadInfo")) { 610 } else if (command.starts_with("sThreadInfo")) {
@@ -603,14 +616,14 @@ void GDBStub::HandleQuery(std::string_view command) {
603 buffer += "<threads>"; 616 buffer += "<threads>";
604 617
605 const auto& threads = system.ApplicationProcess()->GetThreadList(); 618 const auto& threads = system.ApplicationProcess()->GetThreadList();
606 for (const auto* thread : threads) { 619 for (const auto& thread : threads) {
607 auto thread_name{GetThreadName(system, thread)}; 620 auto thread_name{GetThreadName(system, thread)};
608 if (!thread_name) { 621 if (!thread_name) {
609 thread_name = fmt::format("Thread {:d}", thread->GetThreadId()); 622 thread_name = fmt::format("Thread {:d}", thread.GetThreadId());
610 } 623 }
611 624
612 buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)", 625 buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)",
613 thread->GetThreadId(), thread->GetActiveCore(), 626 thread.GetThreadId(), thread.GetActiveCore(),
614 EscapeXML(*thread_name), GetThreadState(thread)); 627 EscapeXML(*thread_name), GetThreadState(thread));
615 } 628 }
616 629
@@ -809,11 +822,13 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
809 const char i = True(mem_info.attribute & MemoryAttribute::IpcLocked) ? 'I' : '-'; 822 const char i = True(mem_info.attribute & MemoryAttribute::IpcLocked) ? 'I' : '-';
810 const char d = True(mem_info.attribute & MemoryAttribute::DeviceShared) ? 'D' : '-'; 823 const char d = True(mem_info.attribute & MemoryAttribute::DeviceShared) ? 'D' : '-';
811 const char u = True(mem_info.attribute & MemoryAttribute::Uncached) ? 'U' : '-'; 824 const char u = True(mem_info.attribute & MemoryAttribute::Uncached) ? 'U' : '-';
825 const char p =
826 True(mem_info.attribute & MemoryAttribute::PermissionLocked) ? 'P' : '-';
812 827
813 reply += 828 reply += fmt::format(" {:#012x} - {:#012x} {} {} {}{}{}{}{} [{}, {}]\n",
814 fmt::format(" {:#012x} - {:#012x} {} {} {}{}{}{} [{}, {}]\n", 829 mem_info.base_address,
815 mem_info.base_address, mem_info.base_address + mem_info.size - 1, 830 mem_info.base_address + mem_info.size - 1, perm, state, l, i,
816 perm, state, l, i, d, u, mem_info.ipc_count, mem_info.device_count); 831 d, u, p, mem_info.ipc_count, mem_info.device_count);
817 } 832 }
818 833
819 const uintptr_t next_address = mem_info.base_address + mem_info.size; 834 const uintptr_t next_address = mem_info.base_address + mem_info.size;
@@ -835,10 +850,10 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
835} 850}
836 851
837Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { 852Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
838 const auto& threads{system.ApplicationProcess()->GetThreadList()}; 853 auto& threads{system.ApplicationProcess()->GetThreadList()};
839 for (auto* thread : threads) { 854 for (auto& thread : threads) {
840 if (thread->GetThreadId() == thread_id) { 855 if (thread.GetThreadId() == thread_id) {
841 return thread; 856 return std::addressof(thread);
842 } 857 }
843 } 858 }
844 859
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..763a44fee 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,17 +96,24 @@ 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 16~63.
100 constexpr u32 default_thread_info_capability = 0x30007F7; 108 constexpr u32 default_thread_info_capability = 0x30043F7;
101 109
102 ProgramMetadata result; 110 ProgramMetadata result;
103 111
104 result.LoadManual( 112 result.LoadManual(
105 true /*is_64_bit*/, FileSys::ProgramAddressSpaceType::Is39Bit /*address_space*/, 113 true /*is_64_bit*/, FileSys::ProgramAddressSpaceType::Is39Bit /*address_space*/,
106 0x2c /*main_thread_prio*/, 0 /*main_thread_core*/, 0x00100000 /*main_thread_stack_size*/, 114 0x2c /*main_thread_prio*/, 0 /*main_thread_core*/, 0x100000 /*main_thread_stack_size*/,
107 0 /*title_id*/, 0xFFFFFFFFFFFFFFFF /*filesystem_permissions*/, 115 0 /*title_id*/, 0xFFFFFFFFFFFFFFFF /*filesystem_permissions*/, 0 /*system_resource_size*/,
108 0x1FE00000 /*system_resource_size*/, {default_thread_info_capability} /*capabilities*/); 116 {default_thread_info_capability} /*capabilities*/);
109 117
110 return result; 118 return result;
111} 119}
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 2e8960b07..76ee97d78 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,
@@ -72,6 +73,9 @@ public:
72 u64 GetFilesystemPermissions() const; 73 u64 GetFilesystemPermissions() const;
73 u32 GetSystemResourceSize() const; 74 u32 GetSystemResourceSize() const;
74 const KernelCapabilityDescriptors& GetKernelCapabilities() const; 75 const KernelCapabilityDescriptors& GetKernelCapabilities() const;
76 const std::array<u8, 0x10>& GetName() const {
77 return npdm_header.application_name;
78 }
75 79
76 void Print() const; 80 void Print() const;
77 81
@@ -163,14 +167,14 @@ private:
163 u32_le unk_size_2; 167 u32_le unk_size_2;
164 }; 168 };
165 169
166 Header npdm_header; 170 Header npdm_header{};
167 AciHeader aci_header; 171 AciHeader aci_header{};
168 AcidHeader acid_header; 172 AcidHeader acid_header{};
169 173
170 FileAccessControl acid_file_access; 174 FileAccessControl acid_file_access{};
171 FileAccessHeader aci_file_access; 175 FileAccessHeader aci_file_access{};
172 176
173 KernelCapabilityDescriptors aci_kernel_capabilities; 177 KernelCapabilityDescriptors aci_kernel_capabilities{};
174}; 178};
175 179
176} // namespace FileSys 180} // namespace FileSys
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/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp
index bd493ecca..e4751c2b4 100644
--- a/src/core/file_sys/system_archive/system_version.cpp
+++ b/src/core/file_sys/system_archive/system_version.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2019 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/file_sys/system_archive/system_version.h" 5#include "core/file_sys/system_archive/system_version.h"
5#include "core/file_sys/vfs_vector.h" 6#include "core/file_sys/vfs_vector.h"
6#include "core/hle/api_version.h" 7#include "core/hle/api_version.h"
@@ -12,6 +13,9 @@ std::string GetLongDisplayVersion() {
12} 13}
13 14
14VirtualDir SystemVersion() { 15VirtualDir SystemVersion() {
16 LOG_WARNING(Common_Filesystem, "called - Using hardcoded firmware version '{}'",
17 GetLongDisplayVersion());
18
15 VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file"); 19 VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file");
16 file->WriteObject(HLE::ApiVersion::HOS_VERSION_MAJOR, 0); 20 file->WriteObject(HLE::ApiVersion::HOS_VERSION_MAJOR, 0);
17 file->WriteObject(HLE::ApiVersion::HOS_VERSION_MINOR, 1); 21 file->WriteObject(HLE::ApiVersion::HOS_VERSION_MINOR, 1);
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/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
index 4cfdf4558..59364efa1 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
@@ -8,7 +8,11 @@
8 8
9#include "core/hle/kernel/board/nintendo/nx/k_system_control.h" 9#include "core/hle/kernel/board/nintendo/nx/k_system_control.h"
10#include "core/hle/kernel/board/nintendo/nx/secure_monitor.h" 10#include "core/hle/kernel/board/nintendo/nx/secure_monitor.h"
11#include "core/hle/kernel/k_memory_manager.h"
12#include "core/hle/kernel/k_page_table.h"
11#include "core/hle/kernel/k_trace.h" 13#include "core/hle/kernel/k_trace.h"
14#include "core/hle/kernel/kernel.h"
15#include "core/hle/kernel/svc_results.h"
12 16
13namespace Kernel::Board::Nintendo::Nx { 17namespace Kernel::Board::Nintendo::Nx {
14 18
@@ -30,6 +34,8 @@ constexpr const std::size_t RequiredNonSecureSystemMemorySize =
30constexpr const std::size_t RequiredNonSecureSystemMemorySizeWithFatal = 34constexpr const std::size_t RequiredNonSecureSystemMemorySizeWithFatal =
31 RequiredNonSecureSystemMemorySize + impl::RequiredNonSecureSystemMemorySizeViFatal; 35 RequiredNonSecureSystemMemorySize + impl::RequiredNonSecureSystemMemorySizeViFatal;
32 36
37constexpr const std::size_t SecureAlignment = 128_KiB;
38
33namespace { 39namespace {
34 40
35using namespace Common::Literals; 41using namespace Common::Literals;
@@ -183,4 +189,57 @@ u64 KSystemControl::GenerateRandomRange(u64 min, u64 max) {
183 return GenerateUniformRange(min, max, GenerateRandomU64); 189 return GenerateUniformRange(min, max, GenerateRandomU64);
184} 190}
185 191
192size_t KSystemControl::CalculateRequiredSecureMemorySize(size_t size, u32 pool) {
193 if (pool == static_cast<u32>(KMemoryManager::Pool::Applet)) {
194 return 0;
195 } else {
196 // return KSystemControlBase::CalculateRequiredSecureMemorySize(size, pool);
197 return size;
198 }
199}
200
201Result KSystemControl::AllocateSecureMemory(KernelCore& kernel, KVirtualAddress* out, size_t size,
202 u32 pool) {
203 // Applet secure memory is handled separately.
204 UNIMPLEMENTED_IF(pool == static_cast<u32>(KMemoryManager::Pool::Applet));
205
206 // Ensure the size is aligned.
207 const size_t alignment =
208 (pool == static_cast<u32>(KMemoryManager::Pool::System) ? PageSize : SecureAlignment);
209 R_UNLESS(Common::IsAligned(size, alignment), ResultInvalidSize);
210
211 // Allocate the memory.
212 const size_t num_pages = size / PageSize;
213 const KPhysicalAddress paddr = kernel.MemoryManager().AllocateAndOpenContinuous(
214 num_pages, alignment / PageSize,
215 KMemoryManager::EncodeOption(static_cast<KMemoryManager::Pool>(pool),
216 KMemoryManager::Direction::FromFront));
217 R_UNLESS(paddr != 0, ResultOutOfMemory);
218
219 // Ensure we don't leak references to the memory on error.
220 ON_RESULT_FAILURE {
221 kernel.MemoryManager().Close(paddr, num_pages);
222 };
223
224 // We succeeded.
225 *out = KPageTable::GetHeapVirtualAddress(kernel.MemoryLayout(), paddr);
226 R_SUCCEED();
227}
228
229void KSystemControl::FreeSecureMemory(KernelCore& kernel, KVirtualAddress address, size_t size,
230 u32 pool) {
231 // Applet secure memory is handled separately.
232 UNIMPLEMENTED_IF(pool == static_cast<u32>(KMemoryManager::Pool::Applet));
233
234 // Ensure the size is aligned.
235 const size_t alignment =
236 (pool == static_cast<u32>(KMemoryManager::Pool::System) ? PageSize : SecureAlignment);
237 ASSERT(Common::IsAligned(GetInteger(address), alignment));
238 ASSERT(Common::IsAligned(size, alignment));
239
240 // Close the secure region's pages.
241 kernel.MemoryManager().Close(KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), address),
242 size / PageSize);
243}
244
186} // namespace Kernel::Board::Nintendo::Nx 245} // namespace Kernel::Board::Nintendo::Nx
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
index b477e8193..ff1feec70 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
@@ -4,6 +4,11 @@
4#pragma once 4#pragma once
5 5
6#include "core/hle/kernel/k_typed_address.h" 6#include "core/hle/kernel/k_typed_address.h"
7#include "core/hle/result.h"
8
9namespace Kernel {
10class KernelCore;
11}
7 12
8namespace Kernel::Board::Nintendo::Nx { 13namespace Kernel::Board::Nintendo::Nx {
9 14
@@ -25,8 +30,16 @@ public:
25 static std::size_t GetMinimumNonSecureSystemPoolSize(); 30 static std::size_t GetMinimumNonSecureSystemPoolSize();
26 }; 31 };
27 32
33 // Randomness.
28 static u64 GenerateRandomRange(u64 min, u64 max); 34 static u64 GenerateRandomRange(u64 min, u64 max);
29 static u64 GenerateRandomU64(); 35 static u64 GenerateRandomU64();
36
37 // Secure Memory.
38 static size_t CalculateRequiredSecureMemorySize(size_t size, u32 pool);
39 static Result AllocateSecureMemory(KernelCore& kernel, KVirtualAddress* out, size_t size,
40 u32 pool);
41 static void FreeSecureMemory(KernelCore& kernel, KVirtualAddress address, size_t size,
42 u32 pool);
30}; 43};
31 44
32} // namespace Kernel::Board::Nintendo::Nx 45} // namespace Kernel::Board::Nintendo::Nx
diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp
index 1f2db673c..a0e20bbbb 100644
--- a/src/core/hle/kernel/init/init_slab_setup.cpp
+++ b/src/core/hle/kernel/init/init_slab_setup.cpp
@@ -106,7 +106,7 @@ static_assert(KernelPageBufferAdditionalSize ==
106/// memory. 106/// memory.
107static KPhysicalAddress TranslateSlabAddrToPhysical(KMemoryLayout& memory_layout, 107static KPhysicalAddress TranslateSlabAddrToPhysical(KMemoryLayout& memory_layout,
108 KVirtualAddress slab_addr) { 108 KVirtualAddress slab_addr) {
109 slab_addr -= GetInteger(memory_layout.GetSlabRegionAddress()); 109 slab_addr -= memory_layout.GetSlabRegion().GetAddress();
110 return GetInteger(slab_addr) + Core::DramMemoryMap::SlabHeapBase; 110 return GetInteger(slab_addr) + Core::DramMemoryMap::SlabHeapBase;
111} 111}
112 112
@@ -196,7 +196,12 @@ void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {
196 auto& kernel = system.Kernel(); 196 auto& kernel = system.Kernel();
197 197
198 // Get the start of the slab region, since that's where we'll be working. 198 // Get the start of the slab region, since that's where we'll be working.
199 KVirtualAddress address = memory_layout.GetSlabRegionAddress(); 199 const KMemoryRegion& slab_region = memory_layout.GetSlabRegion();
200 KVirtualAddress address = slab_region.GetAddress();
201
202 // Clear the slab region.
203 // TODO: implement access to kernel VAs.
204 // std::memset(device_ptr, 0, slab_region.GetSize());
200 205
201 // Initialize slab type array to be in sorted order. 206 // Initialize slab type array to be in sorted order.
202 std::array<KSlabType, KSlabType_Count> slab_types; 207 std::array<KSlabType, KSlabType_Count> slab_types;
diff --git a/src/core/hle/kernel/initial_process.h b/src/core/hle/kernel/initial_process.h
index 82195f4f7..2c95269fc 100644
--- a/src/core/hle/kernel/initial_process.h
+++ b/src/core/hle/kernel/initial_process.h
@@ -19,4 +19,8 @@ static inline KPhysicalAddress GetInitialProcessBinaryPhysicalAddress() {
19 MainMemoryAddress); 19 MainMemoryAddress);
20} 20}
21 21
22static inline size_t GetInitialProcessBinarySize() {
23 return InitialProcessBinarySizeMax;
24}
25
22} // namespace Kernel 26} // namespace Kernel
diff --git a/src/core/hle/kernel/k_capabilities.h b/src/core/hle/kernel/k_capabilities.h
index de766c811..ebd4eedb1 100644
--- a/src/core/hle/kernel/k_capabilities.h
+++ b/src/core/hle/kernel/k_capabilities.h
@@ -200,8 +200,8 @@ private:
200 200
201 RawCapabilityValue raw; 201 RawCapabilityValue raw;
202 BitField<0, 15, CapabilityType> id; 202 BitField<0, 15, CapabilityType> id;
203 BitField<15, 4, u32> major_version; 203 BitField<15, 4, u32> minor_version;
204 BitField<19, 13, u32> minor_version; 204 BitField<19, 13, u32> major_version;
205 }; 205 };
206 206
207 union HandleTable { 207 union HandleTable {
diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp
index efbac0e6a..7633a51fb 100644
--- a/src/core/hle/kernel/k_condition_variable.cpp
+++ b/src/core/hle/kernel/k_condition_variable.cpp
@@ -107,12 +107,12 @@ KConditionVariable::KConditionVariable(Core::System& system)
107 107
108KConditionVariable::~KConditionVariable() = default; 108KConditionVariable::~KConditionVariable() = default;
109 109
110Result KConditionVariable::SignalToAddress(KProcessAddress addr) { 110Result KConditionVariable::SignalToAddress(KernelCore& kernel, KProcessAddress addr) {
111 KThread* owner_thread = GetCurrentThreadPointer(m_kernel); 111 KThread* owner_thread = GetCurrentThreadPointer(kernel);
112 112
113 // Signal the address. 113 // Signal the address.
114 { 114 {
115 KScopedSchedulerLock sl(m_kernel); 115 KScopedSchedulerLock sl(kernel);
116 116
117 // Remove waiter thread. 117 // Remove waiter thread.
118 bool has_waiters{}; 118 bool has_waiters{};
@@ -133,7 +133,7 @@ Result KConditionVariable::SignalToAddress(KProcessAddress addr) {
133 133
134 // Write the value to userspace. 134 // Write the value to userspace.
135 Result result{ResultSuccess}; 135 Result result{ResultSuccess};
136 if (WriteToUser(m_kernel, addr, std::addressof(next_value))) [[likely]] { 136 if (WriteToUser(kernel, addr, std::addressof(next_value))) [[likely]] {
137 result = ResultSuccess; 137 result = ResultSuccess;
138 } else { 138 } else {
139 result = ResultInvalidCurrentMemory; 139 result = ResultInvalidCurrentMemory;
@@ -148,28 +148,28 @@ Result KConditionVariable::SignalToAddress(KProcessAddress addr) {
148 } 148 }
149} 149}
150 150
151Result KConditionVariable::WaitForAddress(Handle handle, KProcessAddress addr, u32 value) { 151Result KConditionVariable::WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr,
152 KThread* cur_thread = GetCurrentThreadPointer(m_kernel); 152 u32 value) {
153 ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(m_kernel); 153 KThread* cur_thread = GetCurrentThreadPointer(kernel);
154 ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(kernel);
154 155
155 // Wait for the address. 156 // Wait for the address.
156 KThread* owner_thread{}; 157 KThread* owner_thread{};
157 { 158 {
158 KScopedSchedulerLock sl(m_kernel); 159 KScopedSchedulerLock sl(kernel);
159 160
160 // Check if the thread should terminate. 161 // Check if the thread should terminate.
161 R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested); 162 R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested);
162 163
163 // Read the tag from userspace. 164 // Read the tag from userspace.
164 u32 test_tag{}; 165 u32 test_tag{};
165 R_UNLESS(ReadFromUser(m_kernel, std::addressof(test_tag), addr), 166 R_UNLESS(ReadFromUser(kernel, std::addressof(test_tag), addr), ResultInvalidCurrentMemory);
166 ResultInvalidCurrentMemory);
167 167
168 // If the tag isn't the handle (with wait mask), we're done. 168 // If the tag isn't the handle (with wait mask), we're done.
169 R_SUCCEED_IF(test_tag != (handle | Svc::HandleWaitMask)); 169 R_SUCCEED_IF(test_tag != (handle | Svc::HandleWaitMask));
170 170
171 // Get the lock owner thread. 171 // Get the lock owner thread.
172 owner_thread = GetCurrentProcess(m_kernel) 172 owner_thread = GetCurrentProcess(kernel)
173 .GetHandleTable() 173 .GetHandleTable()
174 .GetObjectWithoutPseudoHandle<KThread>(handle) 174 .GetObjectWithoutPseudoHandle<KThread>(handle)
175 .ReleasePointerUnsafe(); 175 .ReleasePointerUnsafe();
diff --git a/src/core/hle/kernel/k_condition_variable.h b/src/core/hle/kernel/k_condition_variable.h
index 8c2f3ae51..2620c8e39 100644
--- a/src/core/hle/kernel/k_condition_variable.h
+++ b/src/core/hle/kernel/k_condition_variable.h
@@ -24,11 +24,12 @@ public:
24 explicit KConditionVariable(Core::System& system); 24 explicit KConditionVariable(Core::System& system);
25 ~KConditionVariable(); 25 ~KConditionVariable();
26 26
27 // Arbitration 27 // Arbitration.
28 Result SignalToAddress(KProcessAddress addr); 28 static Result SignalToAddress(KernelCore& kernel, KProcessAddress addr);
29 Result WaitForAddress(Handle handle, KProcessAddress addr, u32 value); 29 static Result WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr,
30 u32 value);
30 31
31 // Condition variable 32 // Condition variable.
32 void Signal(u64 cv_key, s32 count); 33 void Signal(u64 cv_key, s32 count);
33 Result Wait(KProcessAddress addr, u64 key, u32 value, s64 timeout); 34 Result Wait(KProcessAddress addr, u64 key, u32 value, s64 timeout);
34 35
diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp
index fe6a20168..22d79569a 100644
--- a/src/core/hle/kernel/k_interrupt_manager.cpp
+++ b/src/core/hle/kernel/k_interrupt_manager.cpp
@@ -22,7 +22,7 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) {
22 KScopedSchedulerLock sl{kernel}; 22 KScopedSchedulerLock sl{kernel};
23 23
24 // Pin the current thread. 24 // Pin the current thread.
25 process->PinCurrentThread(core_id); 25 process->PinCurrentThread();
26 26
27 // Set the interrupt flag for the thread. 27 // Set the interrupt flag for the thread.
28 GetCurrentThread(kernel).SetInterruptFlag(); 28 GetCurrentThread(kernel).SetInterruptFlag();
diff --git a/src/core/hle/kernel/k_memory_block.h b/src/core/hle/kernel/k_memory_block.h
index 41a29da24..ef3f61321 100644
--- a/src/core/hle/kernel/k_memory_block.h
+++ b/src/core/hle/kernel/k_memory_block.h
@@ -36,6 +36,7 @@ enum class KMemoryState : u32 {
36 FlagCanChangeAttribute = (1 << 24), 36 FlagCanChangeAttribute = (1 << 24),
37 FlagCanCodeMemory = (1 << 25), 37 FlagCanCodeMemory = (1 << 25),
38 FlagLinearMapped = (1 << 26), 38 FlagLinearMapped = (1 << 26),
39 FlagCanPermissionLock = (1 << 27),
39 40
40 FlagsData = FlagCanReprotect | FlagCanUseIpc | FlagCanUseNonDeviceIpc | FlagCanUseNonSecureIpc | 41 FlagsData = FlagCanReprotect | FlagCanUseIpc | FlagCanUseNonDeviceIpc | FlagCanUseNonSecureIpc |
41 FlagMapped | FlagCanAlias | FlagCanTransfer | FlagCanQueryPhysical | 42 FlagMapped | FlagCanAlias | FlagCanTransfer | FlagCanQueryPhysical |
@@ -50,12 +51,16 @@ enum class KMemoryState : u32 {
50 FlagLinearMapped, 51 FlagLinearMapped,
51 52
52 Free = static_cast<u32>(Svc::MemoryState::Free), 53 Free = static_cast<u32>(Svc::MemoryState::Free),
53 Io = static_cast<u32>(Svc::MemoryState::Io) | FlagMapped | FlagCanDeviceMap | 54
54 FlagCanAlignedDeviceMap, 55 IoMemory = static_cast<u32>(Svc::MemoryState::Io) | FlagMapped | FlagCanDeviceMap |
56 FlagCanAlignedDeviceMap,
57 IoRegister =
58 static_cast<u32>(Svc::MemoryState::Io) | FlagCanDeviceMap | FlagCanAlignedDeviceMap,
59
55 Static = static_cast<u32>(Svc::MemoryState::Static) | FlagMapped | FlagCanQueryPhysical, 60 Static = static_cast<u32>(Svc::MemoryState::Static) | FlagMapped | FlagCanQueryPhysical,
56 Code = static_cast<u32>(Svc::MemoryState::Code) | FlagsCode | FlagCanMapProcess, 61 Code = static_cast<u32>(Svc::MemoryState::Code) | FlagsCode | FlagCanMapProcess,
57 CodeData = static_cast<u32>(Svc::MemoryState::CodeData) | FlagsData | FlagCanMapProcess | 62 CodeData = static_cast<u32>(Svc::MemoryState::CodeData) | FlagsData | FlagCanMapProcess |
58 FlagCanCodeMemory, 63 FlagCanCodeMemory | FlagCanPermissionLock,
59 Normal = static_cast<u32>(Svc::MemoryState::Normal) | FlagsData | FlagCanCodeMemory, 64 Normal = static_cast<u32>(Svc::MemoryState::Normal) | FlagsData | FlagCanCodeMemory,
60 Shared = static_cast<u32>(Svc::MemoryState::Shared) | FlagMapped | FlagReferenceCounted | 65 Shared = static_cast<u32>(Svc::MemoryState::Shared) | FlagMapped | FlagReferenceCounted |
61 FlagLinearMapped, 66 FlagLinearMapped,
@@ -65,7 +70,8 @@ enum class KMemoryState : u32 {
65 AliasCode = static_cast<u32>(Svc::MemoryState::AliasCode) | FlagsCode | FlagCanMapProcess | 70 AliasCode = static_cast<u32>(Svc::MemoryState::AliasCode) | FlagsCode | FlagCanMapProcess |
66 FlagCanCodeAlias, 71 FlagCanCodeAlias,
67 AliasCodeData = static_cast<u32>(Svc::MemoryState::AliasCodeData) | FlagsData | 72 AliasCodeData = static_cast<u32>(Svc::MemoryState::AliasCodeData) | FlagsData |
68 FlagCanMapProcess | FlagCanCodeAlias | FlagCanCodeMemory, 73 FlagCanMapProcess | FlagCanCodeAlias | FlagCanCodeMemory |
74 FlagCanPermissionLock,
69 75
70 Ipc = static_cast<u32>(Svc::MemoryState::Ipc) | FlagsMisc | FlagCanAlignedDeviceMap | 76 Ipc = static_cast<u32>(Svc::MemoryState::Ipc) | FlagsMisc | FlagCanAlignedDeviceMap |
71 FlagCanUseIpc | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, 77 FlagCanUseIpc | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
@@ -73,7 +79,7 @@ enum class KMemoryState : u32 {
73 Stack = static_cast<u32>(Svc::MemoryState::Stack) | FlagsMisc | FlagCanAlignedDeviceMap | 79 Stack = static_cast<u32>(Svc::MemoryState::Stack) | FlagsMisc | FlagCanAlignedDeviceMap |
74 FlagCanUseIpc | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, 80 FlagCanUseIpc | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
75 81
76 ThreadLocal = static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagLinearMapped, 82 ThreadLocal = static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagLinearMapped,
77 83
78 Transfered = static_cast<u32>(Svc::MemoryState::Transfered) | FlagsMisc | 84 Transfered = static_cast<u32>(Svc::MemoryState::Transfered) | FlagsMisc |
79 FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc | 85 FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc |
@@ -94,7 +100,7 @@ enum class KMemoryState : u32 {
94 NonDeviceIpc = 100 NonDeviceIpc =
95 static_cast<u32>(Svc::MemoryState::NonDeviceIpc) | FlagsMisc | FlagCanUseNonDeviceIpc, 101 static_cast<u32>(Svc::MemoryState::NonDeviceIpc) | FlagsMisc | FlagCanUseNonDeviceIpc,
96 102
97 Kernel = static_cast<u32>(Svc::MemoryState::Kernel) | FlagMapped, 103 Kernel = static_cast<u32>(Svc::MemoryState::Kernel),
98 104
99 GeneratedCode = static_cast<u32>(Svc::MemoryState::GeneratedCode) | FlagMapped | 105 GeneratedCode = static_cast<u32>(Svc::MemoryState::GeneratedCode) | FlagMapped |
100 FlagReferenceCounted | FlagCanDebug | FlagLinearMapped, 106 FlagReferenceCounted | FlagCanDebug | FlagLinearMapped,
@@ -105,34 +111,36 @@ enum class KMemoryState : u32 {
105 111
106 Insecure = static_cast<u32>(Svc::MemoryState::Insecure) | FlagMapped | FlagReferenceCounted | 112 Insecure = static_cast<u32>(Svc::MemoryState::Insecure) | FlagMapped | FlagReferenceCounted |
107 FlagLinearMapped | FlagCanChangeAttribute | FlagCanDeviceMap | 113 FlagLinearMapped | FlagCanChangeAttribute | FlagCanDeviceMap |
108 FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, 114 FlagCanAlignedDeviceMap | FlagCanQueryPhysical | FlagCanUseNonSecureIpc |
115 FlagCanUseNonDeviceIpc,
109}; 116};
110DECLARE_ENUM_FLAG_OPERATORS(KMemoryState); 117DECLARE_ENUM_FLAG_OPERATORS(KMemoryState);
111 118
112static_assert(static_cast<u32>(KMemoryState::Free) == 0x00000000); 119static_assert(static_cast<u32>(KMemoryState::Free) == 0x00000000);
113static_assert(static_cast<u32>(KMemoryState::Io) == 0x00182001); 120static_assert(static_cast<u32>(KMemoryState::IoMemory) == 0x00182001);
121static_assert(static_cast<u32>(KMemoryState::IoRegister) == 0x00180001);
114static_assert(static_cast<u32>(KMemoryState::Static) == 0x00042002); 122static_assert(static_cast<u32>(KMemoryState::Static) == 0x00042002);
115static_assert(static_cast<u32>(KMemoryState::Code) == 0x04DC7E03); 123static_assert(static_cast<u32>(KMemoryState::Code) == 0x04DC7E03);
116static_assert(static_cast<u32>(KMemoryState::CodeData) == 0x07FEBD04); 124static_assert(static_cast<u32>(KMemoryState::CodeData) == 0x0FFEBD04);
117static_assert(static_cast<u32>(KMemoryState::Normal) == 0x077EBD05); 125static_assert(static_cast<u32>(KMemoryState::Normal) == 0x077EBD05);
118static_assert(static_cast<u32>(KMemoryState::Shared) == 0x04402006); 126static_assert(static_cast<u32>(KMemoryState::Shared) == 0x04402006);
119 127
120static_assert(static_cast<u32>(KMemoryState::AliasCode) == 0x04DD7E08); 128static_assert(static_cast<u32>(KMemoryState::AliasCode) == 0x04DD7E08);
121static_assert(static_cast<u32>(KMemoryState::AliasCodeData) == 0x07FFBD09); 129static_assert(static_cast<u32>(KMemoryState::AliasCodeData) == 0x0FFFBD09);
122static_assert(static_cast<u32>(KMemoryState::Ipc) == 0x045C3C0A); 130static_assert(static_cast<u32>(KMemoryState::Ipc) == 0x045C3C0A);
123static_assert(static_cast<u32>(KMemoryState::Stack) == 0x045C3C0B); 131static_assert(static_cast<u32>(KMemoryState::Stack) == 0x045C3C0B);
124static_assert(static_cast<u32>(KMemoryState::ThreadLocal) == 0x0400200C); 132static_assert(static_cast<u32>(KMemoryState::ThreadLocal) == 0x0400000C);
125static_assert(static_cast<u32>(KMemoryState::Transfered) == 0x055C3C0D); 133static_assert(static_cast<u32>(KMemoryState::Transfered) == 0x055C3C0D);
126static_assert(static_cast<u32>(KMemoryState::SharedTransfered) == 0x045C380E); 134static_assert(static_cast<u32>(KMemoryState::SharedTransfered) == 0x045C380E);
127static_assert(static_cast<u32>(KMemoryState::SharedCode) == 0x0440380F); 135static_assert(static_cast<u32>(KMemoryState::SharedCode) == 0x0440380F);
128static_assert(static_cast<u32>(KMemoryState::Inaccessible) == 0x00000010); 136static_assert(static_cast<u32>(KMemoryState::Inaccessible) == 0x00000010);
129static_assert(static_cast<u32>(KMemoryState::NonSecureIpc) == 0x045C3811); 137static_assert(static_cast<u32>(KMemoryState::NonSecureIpc) == 0x045C3811);
130static_assert(static_cast<u32>(KMemoryState::NonDeviceIpc) == 0x044C2812); 138static_assert(static_cast<u32>(KMemoryState::NonDeviceIpc) == 0x044C2812);
131static_assert(static_cast<u32>(KMemoryState::Kernel) == 0x00002013); 139static_assert(static_cast<u32>(KMemoryState::Kernel) == 0x00000013);
132static_assert(static_cast<u32>(KMemoryState::GeneratedCode) == 0x04402214); 140static_assert(static_cast<u32>(KMemoryState::GeneratedCode) == 0x04402214);
133static_assert(static_cast<u32>(KMemoryState::CodeOut) == 0x04402015); 141static_assert(static_cast<u32>(KMemoryState::CodeOut) == 0x04402015);
134static_assert(static_cast<u32>(KMemoryState::Coverage) == 0x00002016); 142static_assert(static_cast<u32>(KMemoryState::Coverage) == 0x00002016);
135static_assert(static_cast<u32>(KMemoryState::Insecure) == 0x05583817); 143static_assert(static_cast<u32>(KMemoryState::Insecure) == 0x055C3817);
136 144
137enum class KMemoryPermission : u8 { 145enum class KMemoryPermission : u8 {
138 None = 0, 146 None = 0,
@@ -182,8 +190,9 @@ enum class KMemoryAttribute : u8 {
182 IpcLocked = static_cast<u8>(Svc::MemoryAttribute::IpcLocked), 190 IpcLocked = static_cast<u8>(Svc::MemoryAttribute::IpcLocked),
183 DeviceShared = static_cast<u8>(Svc::MemoryAttribute::DeviceShared), 191 DeviceShared = static_cast<u8>(Svc::MemoryAttribute::DeviceShared),
184 Uncached = static_cast<u8>(Svc::MemoryAttribute::Uncached), 192 Uncached = static_cast<u8>(Svc::MemoryAttribute::Uncached),
193 PermissionLocked = static_cast<u8>(Svc::MemoryAttribute::PermissionLocked),
185 194
186 SetMask = Uncached, 195 SetMask = Uncached | PermissionLocked,
187}; 196};
188DECLARE_ENUM_FLAG_OPERATORS(KMemoryAttribute); 197DECLARE_ENUM_FLAG_OPERATORS(KMemoryAttribute);
189 198
@@ -261,6 +270,10 @@ struct KMemoryInfo {
261 return m_state; 270 return m_state;
262 } 271 }
263 272
273 constexpr Svc::MemoryState GetSvcState() const {
274 return static_cast<Svc::MemoryState>(m_state & KMemoryState::Mask);
275 }
276
264 constexpr KMemoryPermission GetPermission() const { 277 constexpr KMemoryPermission GetPermission() const {
265 return m_permission; 278 return m_permission;
266 } 279 }
@@ -326,6 +339,10 @@ public:
326 return this->GetEndAddress() - 1; 339 return this->GetEndAddress() - 1;
327 } 340 }
328 341
342 constexpr KMemoryState GetState() const {
343 return m_memory_state;
344 }
345
329 constexpr u16 GetIpcLockCount() const { 346 constexpr u16 GetIpcLockCount() const {
330 return m_ipc_lock_count; 347 return m_ipc_lock_count;
331 } 348 }
@@ -443,6 +460,13 @@ public:
443 } 460 }
444 } 461 }
445 462
463 constexpr void UpdateAttribute(KMemoryAttribute mask, KMemoryAttribute attr) {
464 ASSERT(False(mask & KMemoryAttribute::IpcLocked));
465 ASSERT(False(mask & KMemoryAttribute::DeviceShared));
466
467 m_attribute = (m_attribute & ~mask) | attr;
468 }
469
446 constexpr void Split(KMemoryBlock* block, KProcessAddress addr) { 470 constexpr void Split(KMemoryBlock* block, KProcessAddress addr) {
447 ASSERT(this->GetAddress() < addr); 471 ASSERT(this->GetAddress() < addr);
448 ASSERT(this->Contains(addr)); 472 ASSERT(this->Contains(addr));
diff --git a/src/core/hle/kernel/k_memory_block_manager.cpp b/src/core/hle/kernel/k_memory_block_manager.cpp
index ab75f550e..58a1e7216 100644
--- a/src/core/hle/kernel/k_memory_block_manager.cpp
+++ b/src/core/hle/kernel/k_memory_block_manager.cpp
@@ -160,8 +160,8 @@ void KMemoryBlockManager::Update(KMemoryBlockManagerUpdateAllocator* allocator,
160 } 160 }
161 161
162 // Update block state. 162 // Update block state.
163 it->Update(state, perm, attr, cur_address == address, static_cast<u8>(set_disable_attr), 163 it->Update(state, perm, attr, it->GetAddress() == address,
164 static_cast<u8>(clear_disable_attr)); 164 static_cast<u8>(set_disable_attr), static_cast<u8>(clear_disable_attr));
165 cur_address += cur_info.GetSize(); 165 cur_address += cur_info.GetSize();
166 remaining_pages -= cur_info.GetNumPages(); 166 remaining_pages -= cur_info.GetNumPages();
167 } 167 }
@@ -175,7 +175,9 @@ void KMemoryBlockManager::UpdateIfMatch(KMemoryBlockManagerUpdateAllocator* allo
175 KProcessAddress address, size_t num_pages, 175 KProcessAddress address, size_t num_pages,
176 KMemoryState test_state, KMemoryPermission test_perm, 176 KMemoryState test_state, KMemoryPermission test_perm,
177 KMemoryAttribute test_attr, KMemoryState state, 177 KMemoryAttribute test_attr, KMemoryState state,
178 KMemoryPermission perm, KMemoryAttribute attr) { 178 KMemoryPermission perm, KMemoryAttribute attr,
179 KMemoryBlockDisableMergeAttribute set_disable_attr,
180 KMemoryBlockDisableMergeAttribute clear_disable_attr) {
179 // Ensure for auditing that we never end up with an invalid tree. 181 // Ensure for auditing that we never end up with an invalid tree.
180 KScopedMemoryBlockManagerAuditor auditor(this); 182 KScopedMemoryBlockManagerAuditor auditor(this);
181 ASSERT(Common::IsAligned(GetInteger(address), PageSize)); 183 ASSERT(Common::IsAligned(GetInteger(address), PageSize));
@@ -214,7 +216,8 @@ void KMemoryBlockManager::UpdateIfMatch(KMemoryBlockManagerUpdateAllocator* allo
214 } 216 }
215 217
216 // Update block state. 218 // Update block state.
217 it->Update(state, perm, attr, false, 0, 0); 219 it->Update(state, perm, attr, false, static_cast<u8>(set_disable_attr),
220 static_cast<u8>(clear_disable_attr));
218 cur_address += cur_info.GetSize(); 221 cur_address += cur_info.GetSize();
219 remaining_pages -= cur_info.GetNumPages(); 222 remaining_pages -= cur_info.GetNumPages();
220 } else { 223 } else {
@@ -284,6 +287,65 @@ void KMemoryBlockManager::UpdateLock(KMemoryBlockManagerUpdateAllocator* allocat
284 this->CoalesceForUpdate(allocator, address, num_pages); 287 this->CoalesceForUpdate(allocator, address, num_pages);
285} 288}
286 289
290void KMemoryBlockManager::UpdateAttribute(KMemoryBlockManagerUpdateAllocator* allocator,
291 KProcessAddress address, size_t num_pages,
292 KMemoryAttribute mask, KMemoryAttribute attr) {
293 // Ensure for auditing that we never end up with an invalid tree.
294 KScopedMemoryBlockManagerAuditor auditor(this);
295 ASSERT(Common::IsAligned(GetInteger(address), PageSize));
296
297 KProcessAddress cur_address = address;
298 size_t remaining_pages = num_pages;
299 iterator it = this->FindIterator(address);
300
301 while (remaining_pages > 0) {
302 const size_t remaining_size = remaining_pages * PageSize;
303 KMemoryInfo cur_info = it->GetMemoryInfo();
304
305 if ((it->GetAttribute() & mask) != attr) {
306 // If we need to, create a new block before and insert it.
307 if (cur_info.GetAddress() != GetInteger(cur_address)) {
308 KMemoryBlock* new_block = allocator->Allocate();
309
310 it->Split(new_block, cur_address);
311 it = m_memory_block_tree.insert(*new_block);
312 it++;
313
314 cur_info = it->GetMemoryInfo();
315 cur_address = cur_info.GetAddress();
316 }
317
318 // If we need to, create a new block after and insert it.
319 if (cur_info.GetSize() > remaining_size) {
320 KMemoryBlock* new_block = allocator->Allocate();
321
322 it->Split(new_block, cur_address + remaining_size);
323 it = m_memory_block_tree.insert(*new_block);
324
325 cur_info = it->GetMemoryInfo();
326 }
327
328 // Update block state.
329 it->UpdateAttribute(mask, attr);
330 cur_address += cur_info.GetSize();
331 remaining_pages -= cur_info.GetNumPages();
332 } else {
333 // If we already have the right attributes, just advance.
334 if (cur_address + remaining_size < cur_info.GetEndAddress()) {
335 remaining_pages = 0;
336 cur_address += remaining_size;
337 } else {
338 remaining_pages =
339 (cur_address + remaining_size - cur_info.GetEndAddress()) / PageSize;
340 cur_address = cur_info.GetEndAddress();
341 }
342 }
343 it++;
344 }
345
346 this->CoalesceForUpdate(allocator, address, num_pages);
347}
348
287// Debug. 349// Debug.
288bool KMemoryBlockManager::CheckState() const { 350bool KMemoryBlockManager::CheckState() const {
289 // Loop over every block, ensuring that we are sorted and coalesced. 351 // Loop over every block, ensuring that we are sorted and coalesced.
diff --git a/src/core/hle/kernel/k_memory_block_manager.h b/src/core/hle/kernel/k_memory_block_manager.h
index 96496e990..cb7b6f430 100644
--- a/src/core/hle/kernel/k_memory_block_manager.h
+++ b/src/core/hle/kernel/k_memory_block_manager.h
@@ -115,7 +115,11 @@ public:
115 void UpdateIfMatch(KMemoryBlockManagerUpdateAllocator* allocator, KProcessAddress address, 115 void UpdateIfMatch(KMemoryBlockManagerUpdateAllocator* allocator, KProcessAddress address,
116 size_t num_pages, KMemoryState test_state, KMemoryPermission test_perm, 116 size_t num_pages, KMemoryState test_state, KMemoryPermission test_perm,
117 KMemoryAttribute test_attr, KMemoryState state, KMemoryPermission perm, 117 KMemoryAttribute test_attr, KMemoryState state, KMemoryPermission perm,
118 KMemoryAttribute attr); 118 KMemoryAttribute attr, KMemoryBlockDisableMergeAttribute set_disable_attr,
119 KMemoryBlockDisableMergeAttribute clear_disable_attr);
120
121 void UpdateAttribute(KMemoryBlockManagerUpdateAllocator* allocator, KProcessAddress address,
122 size_t num_pages, KMemoryAttribute mask, KMemoryAttribute attr);
119 123
120 iterator FindIterator(KProcessAddress address) const { 124 iterator FindIterator(KProcessAddress address) const {
121 return m_memory_block_tree.find(KMemoryBlock( 125 return m_memory_block_tree.find(KMemoryBlock(
diff --git a/src/core/hle/kernel/k_memory_layout.h b/src/core/hle/kernel/k_memory_layout.h
index 54a71df56..c8122644f 100644
--- a/src/core/hle/kernel/k_memory_layout.h
+++ b/src/core/hle/kernel/k_memory_layout.h
@@ -137,11 +137,9 @@ public:
137 return GetStackTopAddress(core_id, KMemoryRegionType_KernelMiscExceptionStack); 137 return GetStackTopAddress(core_id, KMemoryRegionType_KernelMiscExceptionStack);
138 } 138 }
139 139
140 KVirtualAddress GetSlabRegionAddress() const { 140 const KMemoryRegion& GetSlabRegion() const {
141 return Dereference(GetVirtualMemoryRegionTree().FindByType(KMemoryRegionType_KernelSlab)) 141 return Dereference(GetVirtualMemoryRegionTree().FindByType(KMemoryRegionType_KernelSlab));
142 .GetAddress();
143 } 142 }
144
145 const KMemoryRegion& GetDeviceRegion(KMemoryRegionType type) const { 143 const KMemoryRegion& GetDeviceRegion(KMemoryRegionType type) const {
146 return Dereference(GetPhysicalMemoryRegionTree().FindFirstDerived(type)); 144 return Dereference(GetPhysicalMemoryRegionTree().FindFirstDerived(type));
147 } 145 }
diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp
index 74d8169e0..cdc5572d8 100644
--- a/src/core/hle/kernel/k_memory_manager.cpp
+++ b/src/core/hle/kernel/k_memory_manager.cpp
@@ -11,6 +11,7 @@
11#include "core/hle/kernel/initial_process.h" 11#include "core/hle/kernel/initial_process.h"
12#include "core/hle/kernel/k_memory_manager.h" 12#include "core/hle/kernel/k_memory_manager.h"
13#include "core/hle/kernel/k_page_group.h" 13#include "core/hle/kernel/k_page_group.h"
14#include "core/hle/kernel/k_page_table.h"
14#include "core/hle/kernel/kernel.h" 15#include "core/hle/kernel/kernel.h"
15#include "core/hle/kernel/svc_results.h" 16#include "core/hle/kernel/svc_results.h"
16 17
@@ -119,7 +120,8 @@ void KMemoryManager::Initialize(KVirtualAddress management_region, size_t manage
119 // Free each region to its corresponding heap. 120 // Free each region to its corresponding heap.
120 size_t reserved_sizes[MaxManagerCount] = {}; 121 size_t reserved_sizes[MaxManagerCount] = {};
121 const KPhysicalAddress ini_start = GetInitialProcessBinaryPhysicalAddress(); 122 const KPhysicalAddress ini_start = GetInitialProcessBinaryPhysicalAddress();
122 const KPhysicalAddress ini_end = ini_start + InitialProcessBinarySizeMax; 123 const size_t ini_size = GetInitialProcessBinarySize();
124 const KPhysicalAddress ini_end = ini_start + ini_size;
123 const KPhysicalAddress ini_last = ini_end - 1; 125 const KPhysicalAddress ini_last = ini_end - 1;
124 for (const auto& it : m_system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) { 126 for (const auto& it : m_system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
125 if (it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) { 127 if (it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) {
@@ -137,13 +139,13 @@ void KMemoryManager::Initialize(KVirtualAddress management_region, size_t manage
137 } 139 }
138 140
139 // Open/reserve the ini memory. 141 // Open/reserve the ini memory.
140 manager.OpenFirst(ini_start, InitialProcessBinarySizeMax / PageSize); 142 manager.OpenFirst(ini_start, ini_size / PageSize);
141 reserved_sizes[it.GetAttributes()] += InitialProcessBinarySizeMax; 143 reserved_sizes[it.GetAttributes()] += ini_size;
142 144
143 // Free memory after the ini to the heap. 145 // Free memory after the ini to the heap.
144 if (ini_last != cur_last) { 146 if (ini_last != cur_last) {
145 ASSERT(cur_end != 0); 147 ASSERT(cur_end != 0);
146 manager.Free(ini_end, cur_end - ini_end); 148 manager.Free(ini_end, (cur_end - ini_end) / PageSize);
147 } 149 }
148 } else { 150 } else {
149 // Ensure there's no partial overlap with the ini image. 151 // Ensure there's no partial overlap with the ini image.
@@ -167,11 +169,37 @@ void KMemoryManager::Initialize(KVirtualAddress management_region, size_t manage
167} 169}
168 170
169Result KMemoryManager::InitializeOptimizedMemory(u64 process_id, Pool pool) { 171Result KMemoryManager::InitializeOptimizedMemory(u64 process_id, Pool pool) {
170 UNREACHABLE(); 172 const u32 pool_index = static_cast<u32>(pool);
173
174 // Lock the pool.
175 KScopedLightLock lk(m_pool_locks[pool_index]);
176
177 // Check that we don't already have an optimized process.
178 R_UNLESS(!m_has_optimized_process[pool_index], ResultBusy);
179
180 // Set the optimized process id.
181 m_optimized_process_ids[pool_index] = process_id;
182 m_has_optimized_process[pool_index] = true;
183
184 // Clear the management area for the optimized process.
185 for (auto* manager = this->GetFirstManager(pool, Direction::FromFront); manager != nullptr;
186 manager = this->GetNextManager(manager, Direction::FromFront)) {
187 manager->InitializeOptimizedMemory(m_system.Kernel());
188 }
189
190 R_SUCCEED();
171} 191}
172 192
173void KMemoryManager::FinalizeOptimizedMemory(u64 process_id, Pool pool) { 193void KMemoryManager::FinalizeOptimizedMemory(u64 process_id, Pool pool) {
174 UNREACHABLE(); 194 const u32 pool_index = static_cast<u32>(pool);
195
196 // Lock the pool.
197 KScopedLightLock lk(m_pool_locks[pool_index]);
198
199 // If the process was optimized, clear it.
200 if (m_has_optimized_process[pool_index] && m_optimized_process_ids[pool_index] == process_id) {
201 m_has_optimized_process[pool_index] = false;
202 }
175} 203}
176 204
177KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, 205KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_pages,
@@ -206,7 +234,7 @@ KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, siz
206 234
207 // Maintain the optimized memory bitmap, if we should. 235 // Maintain the optimized memory bitmap, if we should.
208 if (m_has_optimized_process[static_cast<size_t>(pool)]) { 236 if (m_has_optimized_process[static_cast<size_t>(pool)]) {
209 UNIMPLEMENTED(); 237 chosen_manager->TrackUnoptimizedAllocation(m_system.Kernel(), allocated_block, num_pages);
210 } 238 }
211 239
212 // Open the first reference to the pages. 240 // Open the first reference to the pages.
@@ -254,7 +282,8 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages,
254 282
255 // Maintain the optimized memory bitmap, if we should. 283 // Maintain the optimized memory bitmap, if we should.
256 if (unoptimized) { 284 if (unoptimized) {
257 UNIMPLEMENTED(); 285 cur_manager->TrackUnoptimizedAllocation(m_system.Kernel(), allocated_block,
286 pages_per_alloc);
258 } 287 }
259 288
260 num_pages -= pages_per_alloc; 289 num_pages -= pages_per_alloc;
@@ -357,8 +386,8 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
357 // Process part or all of the block. 386 // Process part or all of the block.
358 const size_t cur_pages = 387 const size_t cur_pages =
359 std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address)); 388 std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
360 any_new = 389 any_new = manager.ProcessOptimizedAllocation(m_system.Kernel(), cur_address,
361 manager.ProcessOptimizedAllocation(cur_address, cur_pages, fill_pattern); 390 cur_pages, fill_pattern);
362 391
363 // Advance. 392 // Advance.
364 cur_address += cur_pages * PageSize; 393 cur_address += cur_pages * PageSize;
@@ -381,7 +410,7 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
381 // Track some or all of the current pages. 410 // Track some or all of the current pages.
382 const size_t cur_pages = 411 const size_t cur_pages =
383 std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address)); 412 std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
384 manager.TrackOptimizedAllocation(cur_address, cur_pages); 413 manager.TrackOptimizedAllocation(m_system.Kernel(), cur_address, cur_pages);
385 414
386 // Advance. 415 // Advance.
387 cur_address += cur_pages * PageSize; 416 cur_address += cur_pages * PageSize;
@@ -426,17 +455,86 @@ size_t KMemoryManager::Impl::Initialize(KPhysicalAddress address, size_t size,
426 return total_management_size; 455 return total_management_size;
427} 456}
428 457
429void KMemoryManager::Impl::TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages) { 458void KMemoryManager::Impl::InitializeOptimizedMemory(KernelCore& kernel) {
430 UNREACHABLE(); 459 auto optimize_pa =
460 KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region);
461 auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa);
462
463 std::memset(optimize_map, 0, CalculateOptimizedProcessOverheadSize(m_heap.GetSize()));
431} 464}
432 465
433void KMemoryManager::Impl::TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages) { 466void KMemoryManager::Impl::TrackUnoptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
434 UNREACHABLE(); 467 size_t num_pages) {
468 auto optimize_pa =
469 KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region);
470 auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa);
471
472 // Get the range we're tracking.
473 size_t offset = this->GetPageOffset(block);
474 const size_t last = offset + num_pages - 1;
475
476 // Track.
477 while (offset <= last) {
478 // Mark the page as not being optimized-allocated.
479 optimize_map[offset / Common::BitSize<u64>()] &=
480 ~(u64(1) << (offset % Common::BitSize<u64>()));
481
482 offset++;
483 }
484}
485
486void KMemoryManager::Impl::TrackOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
487 size_t num_pages) {
488 auto optimize_pa =
489 KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region);
490 auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa);
491
492 // Get the range we're tracking.
493 size_t offset = this->GetPageOffset(block);
494 const size_t last = offset + num_pages - 1;
495
496 // Track.
497 while (offset <= last) {
498 // Mark the page as being optimized-allocated.
499 optimize_map[offset / Common::BitSize<u64>()] |=
500 (u64(1) << (offset % Common::BitSize<u64>()));
501
502 offset++;
503 }
435} 504}
436 505
437bool KMemoryManager::Impl::ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages, 506bool KMemoryManager::Impl::ProcessOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
438 u8 fill_pattern) { 507 size_t num_pages, u8 fill_pattern) {
439 UNREACHABLE(); 508 auto& device_memory = kernel.System().DeviceMemory();
509 auto optimize_pa =
510 KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region);
511 auto* optimize_map = device_memory.GetPointer<u64>(optimize_pa);
512
513 // We want to return whether any pages were newly allocated.
514 bool any_new = false;
515
516 // Get the range we're processing.
517 size_t offset = this->GetPageOffset(block);
518 const size_t last = offset + num_pages - 1;
519
520 // Process.
521 while (offset <= last) {
522 // Check if the page has been optimized-allocated before.
523 if ((optimize_map[offset / Common::BitSize<u64>()] &
524 (u64(1) << (offset % Common::BitSize<u64>()))) == 0) {
525 // If not, it's new.
526 any_new = true;
527
528 // Fill the page.
529 auto* ptr = device_memory.GetPointer<u8>(m_heap.GetAddress());
530 std::memset(ptr + offset * PageSize, fill_pattern, PageSize);
531 }
532
533 offset++;
534 }
535
536 // Return the number of pages we processed.
537 return any_new;
440} 538}
441 539
442size_t KMemoryManager::Impl::CalculateManagementOverheadSize(size_t region_size) { 540size_t KMemoryManager::Impl::CalculateManagementOverheadSize(size_t region_size) {
diff --git a/src/core/hle/kernel/k_memory_manager.h b/src/core/hle/kernel/k_memory_manager.h
index 7e4b41319..c5a487af9 100644
--- a/src/core/hle/kernel/k_memory_manager.h
+++ b/src/core/hle/kernel/k_memory_manager.h
@@ -216,14 +216,14 @@ private:
216 m_heap.SetInitialUsedSize(reserved_size); 216 m_heap.SetInitialUsedSize(reserved_size);
217 } 217 }
218 218
219 void InitializeOptimizedMemory() { 219 void InitializeOptimizedMemory(KernelCore& kernel);
220 UNIMPLEMENTED();
221 }
222 220
223 void TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages); 221 void TrackUnoptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
224 void TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages); 222 size_t num_pages);
223 void TrackOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block, size_t num_pages);
225 224
226 bool ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages, u8 fill_pattern); 225 bool ProcessOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block,
226 size_t num_pages, u8 fill_pattern);
227 227
228 constexpr Pool GetPool() const { 228 constexpr Pool GetPool() const {
229 return m_pool; 229 return m_pool;
diff --git a/src/core/hle/kernel/k_memory_region_type.h b/src/core/hle/kernel/k_memory_region_type.h
index e5630c1ac..bcbf450f0 100644
--- a/src/core/hle/kernel/k_memory_region_type.h
+++ b/src/core/hle/kernel/k_memory_region_type.h
@@ -190,9 +190,15 @@ static_assert(KMemoryRegionType_DramKernelInitPt.GetValue() ==
190constexpr inline auto KMemoryRegionType_DramKernelSecureAppletMemory = 190constexpr inline auto KMemoryRegionType_DramKernelSecureAppletMemory =
191 KMemoryRegionType_DramKernelBase.DeriveSparse(1, 3, 0).SetAttribute( 191 KMemoryRegionType_DramKernelBase.DeriveSparse(1, 3, 0).SetAttribute(
192 KMemoryRegionAttr_LinearMapped); 192 KMemoryRegionAttr_LinearMapped);
193constexpr inline const auto KMemoryRegionType_DramKernelSecureUnknown =
194 KMemoryRegionType_DramKernelBase.DeriveSparse(1, 3, 1).SetAttribute(
195 KMemoryRegionAttr_LinearMapped);
193static_assert(KMemoryRegionType_DramKernelSecureAppletMemory.GetValue() == 196static_assert(KMemoryRegionType_DramKernelSecureAppletMemory.GetValue() ==
194 (0x18E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap | 197 (0x18E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap |
195 KMemoryRegionAttr_LinearMapped)); 198 KMemoryRegionAttr_LinearMapped));
199static_assert(KMemoryRegionType_DramKernelSecureUnknown.GetValue() ==
200 (0x28E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap |
201 KMemoryRegionAttr_LinearMapped));
196 202
197constexpr inline auto KMemoryRegionType_DramReservedEarly = 203constexpr inline auto KMemoryRegionType_DramReservedEarly =
198 KMemoryRegionType_DramReservedBase.DeriveAttribute(KMemoryRegionAttr_NoUserMap); 204 KMemoryRegionType_DramReservedBase.DeriveAttribute(KMemoryRegionAttr_NoUserMap);
@@ -217,16 +223,18 @@ constexpr inline auto KMemoryRegionType_DramPoolPartition =
217static_assert(KMemoryRegionType_DramPoolPartition.GetValue() == 223static_assert(KMemoryRegionType_DramPoolPartition.GetValue() ==
218 (0x26 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap)); 224 (0x26 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
219 225
220constexpr inline auto KMemoryRegionType_DramPoolManagement = 226// UNUSED: .Derive(4, 1);
221 KMemoryRegionType_DramPoolPartition.DeriveTransition(0, 2).DeriveTransition().SetAttribute( 227// UNUSED: .Derive(4, 2);
228constexpr inline const auto KMemoryRegionType_DramPoolManagement =
229 KMemoryRegionType_DramPoolPartition.Derive(4, 0).SetAttribute(
222 KMemoryRegionAttr_CarveoutProtected); 230 KMemoryRegionAttr_CarveoutProtected);
223constexpr inline auto KMemoryRegionType_DramUserPool = 231constexpr inline const auto KMemoryRegionType_DramUserPool =
224 KMemoryRegionType_DramPoolPartition.DeriveTransition(1, 2).DeriveTransition(); 232 KMemoryRegionType_DramPoolPartition.Derive(4, 3);
225static_assert(KMemoryRegionType_DramPoolManagement.GetValue() == 233static_assert(KMemoryRegionType_DramPoolManagement.GetValue() ==
226 (0x166 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap | 234 (0xE6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap |
227 KMemoryRegionAttr_CarveoutProtected)); 235 KMemoryRegionAttr_CarveoutProtected));
228static_assert(KMemoryRegionType_DramUserPool.GetValue() == 236static_assert(KMemoryRegionType_DramUserPool.GetValue() ==
229 (0x1A6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap)); 237 (0x266 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
230 238
231constexpr inline auto KMemoryRegionType_DramApplicationPool = 239constexpr inline auto KMemoryRegionType_DramApplicationPool =
232 KMemoryRegionType_DramUserPool.Derive(4, 0); 240 KMemoryRegionType_DramUserPool.Derive(4, 0);
@@ -237,60 +245,63 @@ constexpr inline auto KMemoryRegionType_DramSystemNonSecurePool =
237constexpr inline auto KMemoryRegionType_DramSystemPool = 245constexpr inline auto KMemoryRegionType_DramSystemPool =
238 KMemoryRegionType_DramUserPool.Derive(4, 3).SetAttribute(KMemoryRegionAttr_CarveoutProtected); 246 KMemoryRegionType_DramUserPool.Derive(4, 3).SetAttribute(KMemoryRegionAttr_CarveoutProtected);
239static_assert(KMemoryRegionType_DramApplicationPool.GetValue() == 247static_assert(KMemoryRegionType_DramApplicationPool.GetValue() ==
240 (0x7A6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap)); 248 (0xE66 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
241static_assert(KMemoryRegionType_DramAppletPool.GetValue() == 249static_assert(KMemoryRegionType_DramAppletPool.GetValue() ==
242 (0xBA6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap)); 250 (0x1666 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
243static_assert(KMemoryRegionType_DramSystemNonSecurePool.GetValue() == 251static_assert(KMemoryRegionType_DramSystemNonSecurePool.GetValue() ==
244 (0xDA6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap)); 252 (0x1A66 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
245static_assert(KMemoryRegionType_DramSystemPool.GetValue() == 253static_assert(KMemoryRegionType_DramSystemPool.GetValue() ==
246 (0x13A6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap | 254 (0x2666 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap |
247 KMemoryRegionAttr_CarveoutProtected)); 255 KMemoryRegionAttr_CarveoutProtected));
248 256
249constexpr inline auto KMemoryRegionType_VirtualDramHeapBase = 257constexpr inline auto KMemoryRegionType_VirtualDramHeapBase =
250 KMemoryRegionType_Dram.DeriveSparse(1, 3, 0); 258 KMemoryRegionType_Dram.DeriveSparse(1, 4, 0);
251constexpr inline auto KMemoryRegionType_VirtualDramKernelPtHeap = 259constexpr inline auto KMemoryRegionType_VirtualDramKernelPtHeap =
252 KMemoryRegionType_Dram.DeriveSparse(1, 3, 1); 260 KMemoryRegionType_Dram.DeriveSparse(1, 4, 1);
253constexpr inline auto KMemoryRegionType_VirtualDramKernelTraceBuffer = 261constexpr inline auto KMemoryRegionType_VirtualDramKernelTraceBuffer =
254 KMemoryRegionType_Dram.DeriveSparse(1, 3, 2); 262 KMemoryRegionType_Dram.DeriveSparse(1, 4, 2);
255static_assert(KMemoryRegionType_VirtualDramHeapBase.GetValue() == 0x1A); 263static_assert(KMemoryRegionType_VirtualDramHeapBase.GetValue() == 0x1A);
256static_assert(KMemoryRegionType_VirtualDramKernelPtHeap.GetValue() == 0x2A); 264static_assert(KMemoryRegionType_VirtualDramKernelPtHeap.GetValue() == 0x2A);
257static_assert(KMemoryRegionType_VirtualDramKernelTraceBuffer.GetValue() == 0x4A); 265static_assert(KMemoryRegionType_VirtualDramKernelTraceBuffer.GetValue() == 0x4A);
258 266
259// UNUSED: .DeriveSparse(2, 2, 0); 267// UNUSED: .Derive(4, 2);
260constexpr inline auto KMemoryRegionType_VirtualDramUnknownDebug = 268constexpr inline const auto KMemoryRegionType_VirtualDramUnknownDebug =
261 KMemoryRegionType_Dram.DeriveSparse(2, 2, 1); 269 KMemoryRegionType_Dram.Advance(2).Derive(4, 0);
262static_assert(KMemoryRegionType_VirtualDramUnknownDebug.GetValue() == (0x52)); 270constexpr inline const auto KMemoryRegionType_VirtualDramKernelSecureAppletMemory =
263 271 KMemoryRegionType_Dram.Advance(2).Derive(4, 1);
264constexpr inline auto KMemoryRegionType_VirtualDramKernelSecureAppletMemory = 272constexpr inline const auto KMemoryRegionType_VirtualDramKernelSecureUnknown =
265 KMemoryRegionType_Dram.DeriveSparse(3, 1, 0); 273 KMemoryRegionType_Dram.Advance(2).Derive(4, 3);
266static_assert(KMemoryRegionType_VirtualDramKernelSecureAppletMemory.GetValue() == (0x62)); 274static_assert(KMemoryRegionType_VirtualDramUnknownDebug.GetValue() == (0x32));
267 275static_assert(KMemoryRegionType_VirtualDramKernelSecureAppletMemory.GetValue() == (0x52));
268constexpr inline auto KMemoryRegionType_VirtualDramKernelInitPt = 276static_assert(KMemoryRegionType_VirtualDramKernelSecureUnknown.GetValue() == (0x92));
269 KMemoryRegionType_VirtualDramHeapBase.Derive(3, 0); 277
270constexpr inline auto KMemoryRegionType_VirtualDramPoolManagement = 278// UNUSED: .Derive(4, 3);
271 KMemoryRegionType_VirtualDramHeapBase.Derive(3, 1); 279constexpr inline const auto KMemoryRegionType_VirtualDramKernelInitPt =
272constexpr inline auto KMemoryRegionType_VirtualDramUserPool = 280 KMemoryRegionType_VirtualDramHeapBase.Derive(4, 0);
273 KMemoryRegionType_VirtualDramHeapBase.Derive(3, 2); 281constexpr inline const auto KMemoryRegionType_VirtualDramPoolManagement =
274static_assert(KMemoryRegionType_VirtualDramKernelInitPt.GetValue() == 0x19A); 282 KMemoryRegionType_VirtualDramHeapBase.Derive(4, 1);
275static_assert(KMemoryRegionType_VirtualDramPoolManagement.GetValue() == 0x29A); 283constexpr inline const auto KMemoryRegionType_VirtualDramUserPool =
276static_assert(KMemoryRegionType_VirtualDramUserPool.GetValue() == 0x31A); 284 KMemoryRegionType_VirtualDramHeapBase.Derive(4, 2);
285static_assert(KMemoryRegionType_VirtualDramKernelInitPt.GetValue() == 0x31A);
286static_assert(KMemoryRegionType_VirtualDramPoolManagement.GetValue() == 0x51A);
287static_assert(KMemoryRegionType_VirtualDramUserPool.GetValue() == 0x61A);
277 288
278// NOTE: For unknown reason, the pools are derived out-of-order here. 289// NOTE: For unknown reason, the pools are derived out-of-order here.
279// It's worth eventually trying to understand why Nintendo made this choice. 290// It's worth eventually trying to understand why Nintendo made this choice.
280// UNUSED: .Derive(6, 0); 291// UNUSED: .Derive(6, 0);
281// UNUSED: .Derive(6, 1); 292// UNUSED: .Derive(6, 1);
282constexpr inline auto KMemoryRegionType_VirtualDramAppletPool = 293constexpr inline const auto KMemoryRegionType_VirtualDramApplicationPool =
283 KMemoryRegionType_VirtualDramUserPool.Derive(6, 2); 294 KMemoryRegionType_VirtualDramUserPool.Derive(4, 0);
284constexpr inline auto KMemoryRegionType_VirtualDramApplicationPool = 295constexpr inline const auto KMemoryRegionType_VirtualDramAppletPool =
285 KMemoryRegionType_VirtualDramUserPool.Derive(6, 3); 296 KMemoryRegionType_VirtualDramUserPool.Derive(4, 1);
286constexpr inline auto KMemoryRegionType_VirtualDramSystemNonSecurePool = 297constexpr inline const auto KMemoryRegionType_VirtualDramSystemNonSecurePool =
287 KMemoryRegionType_VirtualDramUserPool.Derive(6, 4); 298 KMemoryRegionType_VirtualDramUserPool.Derive(4, 2);
288constexpr inline auto KMemoryRegionType_VirtualDramSystemPool = 299constexpr inline const auto KMemoryRegionType_VirtualDramSystemPool =
289 KMemoryRegionType_VirtualDramUserPool.Derive(6, 5); 300 KMemoryRegionType_VirtualDramUserPool.Derive(4, 3);
290static_assert(KMemoryRegionType_VirtualDramAppletPool.GetValue() == 0x1B1A); 301static_assert(KMemoryRegionType_VirtualDramApplicationPool.GetValue() == 0x361A);
291static_assert(KMemoryRegionType_VirtualDramApplicationPool.GetValue() == 0x271A); 302static_assert(KMemoryRegionType_VirtualDramAppletPool.GetValue() == 0x561A);
292static_assert(KMemoryRegionType_VirtualDramSystemNonSecurePool.GetValue() == 0x2B1A); 303static_assert(KMemoryRegionType_VirtualDramSystemNonSecurePool.GetValue() == 0x661A);
293static_assert(KMemoryRegionType_VirtualDramSystemPool.GetValue() == 0x331A); 304static_assert(KMemoryRegionType_VirtualDramSystemPool.GetValue() == 0x961A);
294 305
295constexpr inline auto KMemoryRegionType_ArchDeviceBase = 306constexpr inline auto KMemoryRegionType_ArchDeviceBase =
296 KMemoryRegionType_Kernel.DeriveTransition(0, 1).SetSparseOnly(); 307 KMemoryRegionType_Kernel.DeriveTransition(0, 1).SetSparseOnly();
@@ -354,12 +365,14 @@ constexpr inline auto KMemoryRegionType_KernelTemp =
354static_assert(KMemoryRegionType_KernelTemp.GetValue() == 0x31); 365static_assert(KMemoryRegionType_KernelTemp.GetValue() == 0x31);
355 366
356constexpr KMemoryRegionType GetTypeForVirtualLinearMapping(u32 type_id) { 367constexpr KMemoryRegionType GetTypeForVirtualLinearMapping(u32 type_id) {
357 if (KMemoryRegionType_KernelTraceBuffer.IsAncestorOf(type_id)) { 368 if (KMemoryRegionType_DramKernelPtHeap.IsAncestorOf(type_id)) {
358 return KMemoryRegionType_VirtualDramKernelTraceBuffer;
359 } else if (KMemoryRegionType_DramKernelPtHeap.IsAncestorOf(type_id)) {
360 return KMemoryRegionType_VirtualDramKernelPtHeap; 369 return KMemoryRegionType_VirtualDramKernelPtHeap;
361 } else if (KMemoryRegionType_DramKernelSecureAppletMemory.IsAncestorOf(type_id)) { 370 } else if (KMemoryRegionType_DramKernelSecureAppletMemory.IsAncestorOf(type_id)) {
362 return KMemoryRegionType_VirtualDramKernelSecureAppletMemory; 371 return KMemoryRegionType_VirtualDramKernelSecureAppletMemory;
372 } else if (KMemoryRegionType_DramKernelSecureUnknown.IsAncestorOf(type_id)) {
373 return KMemoryRegionType_VirtualDramKernelSecureUnknown;
374 } else if (KMemoryRegionType_KernelTraceBuffer.IsAncestorOf(type_id)) {
375 return KMemoryRegionType_VirtualDramKernelTraceBuffer;
363 } else if ((type_id | KMemoryRegionAttr_ShouldKernelMap) == type_id) { 376 } else if ((type_id | KMemoryRegionAttr_ShouldKernelMap) == type_id) {
364 return KMemoryRegionType_VirtualDramUnknownDebug; 377 return KMemoryRegionType_VirtualDramUnknownDebug;
365 } else { 378 } else {
diff --git a/src/core/hle/kernel/k_page_group.h b/src/core/hle/kernel/k_page_group.h
index b32909f05..de9d63a8d 100644
--- a/src/core/hle/kernel/k_page_group.h
+++ b/src/core/hle/kernel/k_page_group.h
@@ -183,12 +183,17 @@ private:
183 183
184class KScopedPageGroup { 184class KScopedPageGroup {
185public: 185public:
186 explicit KScopedPageGroup(const KPageGroup* gp) : m_pg(gp) { 186 explicit KScopedPageGroup(const KPageGroup* gp, bool not_first = true) : m_pg(gp) {
187 if (m_pg) { 187 if (m_pg) {
188 m_pg->Open(); 188 if (not_first) {
189 m_pg->Open();
190 } else {
191 m_pg->OpenFirst();
192 }
189 } 193 }
190 } 194 }
191 explicit KScopedPageGroup(const KPageGroup& gp) : KScopedPageGroup(std::addressof(gp)) {} 195 explicit KScopedPageGroup(const KPageGroup& gp, bool not_first = true)
196 : KScopedPageGroup(std::addressof(gp), not_first) {}
192 ~KScopedPageGroup() { 197 ~KScopedPageGroup() {
193 if (m_pg) { 198 if (m_pg) {
194 m_pg->Close(); 199 m_pg->Close();
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 5b51edf30..1d47bdf6b 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -82,14 +82,14 @@ public:
82 82
83using namespace Common::Literals; 83using namespace Common::Literals;
84 84
85constexpr size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType as_type) { 85constexpr size_t GetAddressSpaceWidthFromType(Svc::CreateProcessFlag as_type) {
86 switch (as_type) { 86 switch (as_type) {
87 case FileSys::ProgramAddressSpaceType::Is32Bit: 87 case Svc::CreateProcessFlag::AddressSpace32Bit:
88 case FileSys::ProgramAddressSpaceType::Is32BitNoMap: 88 case Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias:
89 return 32; 89 return 32;
90 case FileSys::ProgramAddressSpaceType::Is36Bit: 90 case Svc::CreateProcessFlag::AddressSpace64BitDeprecated:
91 return 36; 91 return 36;
92 case FileSys::ProgramAddressSpaceType::Is39Bit: 92 case Svc::CreateProcessFlag::AddressSpace64Bit:
93 return 39; 93 return 39;
94 default: 94 default:
95 ASSERT(false); 95 ASSERT(false);
@@ -105,7 +105,7 @@ KPageTable::KPageTable(Core::System& system_)
105 105
106KPageTable::~KPageTable() = default; 106KPageTable::~KPageTable() = default;
107 107
108Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr, 108Result KPageTable::InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr,
109 bool enable_das_merge, bool from_back, 109 bool enable_das_merge, bool from_back,
110 KMemoryManager::Pool pool, KProcessAddress code_addr, 110 KMemoryManager::Pool pool, KProcessAddress code_addr,
111 size_t code_size, KSystemResource* system_resource, 111 size_t code_size, KSystemResource* system_resource,
@@ -133,7 +133,7 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type
133 ASSERT(code_addr + code_size - 1 <= end - 1); 133 ASSERT(code_addr + code_size - 1 <= end - 1);
134 134
135 // Adjust heap/alias size if we don't have an alias region 135 // Adjust heap/alias size if we don't have an alias region
136 if (as_type == FileSys::ProgramAddressSpaceType::Is32BitNoMap) { 136 if (as_type == Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias) {
137 heap_region_size += alias_region_size; 137 heap_region_size += alias_region_size;
138 alias_region_size = 0; 138 alias_region_size = 0;
139 } 139 }
@@ -505,7 +505,7 @@ Result KPageTable::UnmapCodeMemory(KProcessAddress dst_address, KProcessAddress
505 R_TRY(this->CheckMemoryStateContiguous( 505 R_TRY(this->CheckMemoryStateContiguous(
506 std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState::FlagCanCodeAlias, 506 std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState::FlagCanCodeAlias,
507 KMemoryState::FlagCanCodeAlias, KMemoryPermission::None, KMemoryPermission::None, 507 KMemoryState::FlagCanCodeAlias, KMemoryPermission::None, KMemoryPermission::None,
508 KMemoryAttribute::All, KMemoryAttribute::None)); 508 KMemoryAttribute::All & ~KMemoryAttribute::PermissionLocked, KMemoryAttribute::None));
509 509
510 // Determine whether any pages being unmapped are code. 510 // Determine whether any pages being unmapped are code.
511 bool any_code_pages = false; 511 bool any_code_pages = false;
@@ -1724,29 +1724,43 @@ Result KPageTable::MapPhysicalMemory(KProcessAddress address, size_t size) {
1724 PageSize; 1724 PageSize;
1725 1725
1726 // While we have pages to map, map them. 1726 // While we have pages to map, map them.
1727 while (map_pages > 0) { 1727 {
1728 // Check if we're at the end of the physical block. 1728 // Create a page group for the current mapping range.
1729 if (pg_pages == 0) { 1729 KPageGroup cur_pg(m_kernel, m_block_info_manager);
1730 // Ensure there are more pages to map. 1730 {
1731 ASSERT(pg_it != pg.end()); 1731 ON_RESULT_FAILURE_2 {
1732 1732 cur_pg.OpenFirst();
1733 // Advance our physical block. 1733 cur_pg.Close();
1734 ++pg_it; 1734 };
1735 pg_phys_addr = pg_it->GetAddress(); 1735
1736 pg_pages = pg_it->GetNumPages(); 1736 size_t remain_pages = map_pages;
1737 while (remain_pages > 0) {
1738 // Check if we're at the end of the physical block.
1739 if (pg_pages == 0) {
1740 // Ensure there are more pages to map.
1741 ASSERT(pg_it != pg.end());
1742
1743 // Advance our physical block.
1744 ++pg_it;
1745 pg_phys_addr = pg_it->GetAddress();
1746 pg_pages = pg_it->GetNumPages();
1747 }
1748
1749 // Add whatever we can to the current block.
1750 const size_t cur_pages = std::min(pg_pages, remain_pages);
1751 R_TRY(cur_pg.AddBlock(pg_phys_addr +
1752 ((pg_pages - cur_pages) * PageSize),
1753 cur_pages));
1754
1755 // Advance.
1756 remain_pages -= cur_pages;
1757 pg_pages -= cur_pages;
1758 }
1737 } 1759 }
1738 1760
1739 // Map whatever we can. 1761 // Map the pages.
1740 const size_t cur_pages = std::min(pg_pages, map_pages); 1762 R_TRY(this->Operate(cur_address, map_pages, cur_pg,
1741 R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::UserReadWrite, 1763 OperationType::MapFirstGroup));
1742 OperationType::MapFirst, pg_phys_addr));
1743
1744 // Advance.
1745 cur_address += cur_pages * PageSize;
1746 map_pages -= cur_pages;
1747
1748 pg_phys_addr += cur_pages * PageSize;
1749 pg_pages -= cur_pages;
1750 } 1764 }
1751 } 1765 }
1752 1766
@@ -1770,7 +1784,11 @@ Result KPageTable::MapPhysicalMemory(KProcessAddress address, size_t size) {
1770 m_memory_block_manager.UpdateIfMatch( 1784 m_memory_block_manager.UpdateIfMatch(
1771 std::addressof(allocator), address, size / PageSize, KMemoryState::Free, 1785 std::addressof(allocator), address, size / PageSize, KMemoryState::Free,
1772 KMemoryPermission::None, KMemoryAttribute::None, KMemoryState::Normal, 1786 KMemoryPermission::None, KMemoryAttribute::None, KMemoryState::Normal,
1773 KMemoryPermission::UserReadWrite, KMemoryAttribute::None); 1787 KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
1788 address == this->GetAliasRegionStart()
1789 ? KMemoryBlockDisableMergeAttribute::Normal
1790 : KMemoryBlockDisableMergeAttribute::None,
1791 KMemoryBlockDisableMergeAttribute::None);
1774 1792
1775 R_SUCCEED(); 1793 R_SUCCEED();
1776 } 1794 }
@@ -1868,6 +1886,13 @@ Result KPageTable::UnmapPhysicalMemory(KProcessAddress address, size_t size) {
1868 1886
1869 // Iterate over the memory, unmapping as we go. 1887 // Iterate over the memory, unmapping as we go.
1870 auto it = m_memory_block_manager.FindIterator(cur_address); 1888 auto it = m_memory_block_manager.FindIterator(cur_address);
1889
1890 const auto clear_merge_attr =
1891 (it->GetState() == KMemoryState::Normal &&
1892 it->GetAddress() == this->GetAliasRegionStart() && it->GetAddress() == address)
1893 ? KMemoryBlockDisableMergeAttribute::Normal
1894 : KMemoryBlockDisableMergeAttribute::None;
1895
1871 while (true) { 1896 while (true) {
1872 // Check that the iterator is valid. 1897 // Check that the iterator is valid.
1873 ASSERT(it != m_memory_block_manager.end()); 1898 ASSERT(it != m_memory_block_manager.end());
@@ -1905,7 +1930,7 @@ Result KPageTable::UnmapPhysicalMemory(KProcessAddress address, size_t size) {
1905 m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize, 1930 m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize,
1906 KMemoryState::Free, KMemoryPermission::None, 1931 KMemoryState::Free, KMemoryPermission::None,
1907 KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, 1932 KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
1908 KMemoryBlockDisableMergeAttribute::None); 1933 clear_merge_attr);
1909 1934
1910 // We succeeded. 1935 // We succeeded.
1911 R_SUCCEED(); 1936 R_SUCCEED();
@@ -2379,8 +2404,7 @@ Result KPageTable::MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg,
2379 KScopedPageTableUpdater updater(this); 2404 KScopedPageTableUpdater updater(this);
2380 2405
2381 // Perform mapping operation. 2406 // Perform mapping operation.
2382 const KPageProperties properties = {perm, state == KMemoryState::Io, false, 2407 const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead};
2383 DisableMergeAttribute::DisableHead};
2384 R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false)); 2408 R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false));
2385 2409
2386 // Update the blocks. 2410 // Update the blocks.
@@ -2422,8 +2446,7 @@ Result KPageTable::MapPageGroup(KProcessAddress addr, const KPageGroup& pg, KMem
2422 KScopedPageTableUpdater updater(this); 2446 KScopedPageTableUpdater updater(this);
2423 2447
2424 // Perform mapping operation. 2448 // Perform mapping operation.
2425 const KPageProperties properties = {perm, state == KMemoryState::Io, false, 2449 const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead};
2426 DisableMergeAttribute::DisableHead};
2427 R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false)); 2450 R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false));
2428 2451
2429 // Update the blocks. 2452 // Update the blocks.
@@ -2652,11 +2675,18 @@ Result KPageTable::SetMemoryAttribute(KProcessAddress addr, size_t size, u32 mas
2652 size_t num_allocator_blocks; 2675 size_t num_allocator_blocks;
2653 constexpr auto AttributeTestMask = 2676 constexpr auto AttributeTestMask =
2654 ~(KMemoryAttribute::SetMask | KMemoryAttribute::DeviceShared); 2677 ~(KMemoryAttribute::SetMask | KMemoryAttribute::DeviceShared);
2655 R_TRY(this->CheckMemoryState( 2678 const KMemoryState state_test_mask =
2656 std::addressof(old_state), std::addressof(old_perm), std::addressof(old_attr), 2679 static_cast<KMemoryState>(((mask & static_cast<u32>(KMemoryAttribute::Uncached))
2657 std::addressof(num_allocator_blocks), addr, size, KMemoryState::FlagCanChangeAttribute, 2680 ? static_cast<u32>(KMemoryState::FlagCanChangeAttribute)
2658 KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None, 2681 : 0) |
2659 AttributeTestMask, KMemoryAttribute::None, ~AttributeTestMask)); 2682 ((mask & static_cast<u32>(KMemoryAttribute::PermissionLocked))
2683 ? static_cast<u32>(KMemoryState::FlagCanPermissionLock)
2684 : 0));
2685 R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm),
2686 std::addressof(old_attr), std::addressof(num_allocator_blocks),
2687 addr, size, state_test_mask, state_test_mask,
2688 KMemoryPermission::None, KMemoryPermission::None,
2689 AttributeTestMask, KMemoryAttribute::None, ~AttributeTestMask));
2660 2690
2661 // Create an update allocator. 2691 // Create an update allocator.
2662 Result allocator_result{ResultSuccess}; 2692 Result allocator_result{ResultSuccess};
@@ -2664,18 +2694,17 @@ Result KPageTable::SetMemoryAttribute(KProcessAddress addr, size_t size, u32 mas
2664 m_memory_block_slab_manager, num_allocator_blocks); 2694 m_memory_block_slab_manager, num_allocator_blocks);
2665 R_TRY(allocator_result); 2695 R_TRY(allocator_result);
2666 2696
2667 // Determine the new attribute. 2697 // If we need to, perform a change attribute operation.
2668 const KMemoryAttribute new_attr = 2698 if (True(KMemoryAttribute::Uncached & static_cast<KMemoryAttribute>(mask))) {
2669 static_cast<KMemoryAttribute>(((old_attr & static_cast<KMemoryAttribute>(~mask)) | 2699 // Perform operation.
2670 static_cast<KMemoryAttribute>(attr & mask))); 2700 R_TRY(this->Operate(addr, num_pages, old_perm,
2671 2701 OperationType::ChangePermissionsAndRefreshAndFlush, 0));
2672 // Perform operation. 2702 }
2673 this->Operate(addr, num_pages, old_perm, OperationType::ChangePermissionsAndRefresh);
2674 2703
2675 // Update the blocks. 2704 // Update the blocks.
2676 m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, old_perm, 2705 m_memory_block_manager.UpdateAttribute(std::addressof(allocator), addr, num_pages,
2677 new_attr, KMemoryBlockDisableMergeAttribute::None, 2706 static_cast<KMemoryAttribute>(mask),
2678 KMemoryBlockDisableMergeAttribute::None); 2707 static_cast<KMemoryAttribute>(attr));
2679 2708
2680 R_SUCCEED(); 2709 R_SUCCEED();
2681} 2710}
@@ -2863,7 +2892,8 @@ Result KPageTable::LockForMapDeviceAddressSpace(bool* out_is_io, KProcessAddress
2863 &KMemoryBlock::ShareToDevice, KMemoryPermission::None); 2892 &KMemoryBlock::ShareToDevice, KMemoryPermission::None);
2864 2893
2865 // Set whether the locked memory was io. 2894 // Set whether the locked memory was io.
2866 *out_is_io = old_state == KMemoryState::Io; 2895 *out_is_io =
2896 static_cast<Svc::MemoryState>(old_state & KMemoryState::Mask) == Svc::MemoryState::Io;
2867 2897
2868 R_SUCCEED(); 2898 R_SUCCEED();
2869} 2899}
@@ -2949,6 +2979,23 @@ Result KPageTable::UnlockForIpcUserBuffer(KProcessAddress address, size_t size)
2949 KMemoryAttribute::Locked, nullptr)); 2979 KMemoryAttribute::Locked, nullptr));
2950} 2980}
2951 2981
2982Result KPageTable::LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
2983 KMemoryPermission perm) {
2984 R_RETURN(this->LockMemoryAndOpen(out, nullptr, address, size, KMemoryState::FlagCanTransfer,
2985 KMemoryState::FlagCanTransfer, KMemoryPermission::All,
2986 KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
2987 KMemoryAttribute::None, perm, KMemoryAttribute::Locked));
2988}
2989
2990Result KPageTable::UnlockForTransferMemory(KProcessAddress address, size_t size,
2991 const KPageGroup& pg) {
2992 R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanTransfer,
2993 KMemoryState::FlagCanTransfer, KMemoryPermission::None,
2994 KMemoryPermission::None, KMemoryAttribute::All,
2995 KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
2996 KMemoryAttribute::Locked, std::addressof(pg)));
2997}
2998
2952Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) { 2999Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) {
2953 R_RETURN(this->LockMemoryAndOpen( 3000 R_RETURN(this->LockMemoryAndOpen(
2954 out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, 3001 out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
@@ -3004,9 +3051,10 @@ Result KPageTable::Operate(KProcessAddress addr, size_t num_pages, const KPageGr
3004 ASSERT(num_pages == page_group.GetNumPages()); 3051 ASSERT(num_pages == page_group.GetNumPages());
3005 3052
3006 switch (operation) { 3053 switch (operation) {
3007 case OperationType::MapGroup: { 3054 case OperationType::MapGroup:
3055 case OperationType::MapFirstGroup: {
3008 // We want to maintain a new reference to every page in the group. 3056 // We want to maintain a new reference to every page in the group.
3009 KScopedPageGroup spg(page_group); 3057 KScopedPageGroup spg(page_group, operation != OperationType::MapFirstGroup);
3010 3058
3011 for (const auto& node : page_group) { 3059 for (const auto& node : page_group) {
3012 const size_t size{node.GetNumPages() * PageSize}; 3060 const size_t size{node.GetNumPages() * PageSize};
@@ -3048,7 +3096,6 @@ Result KPageTable::Operate(KProcessAddress addr, size_t num_pages, KMemoryPermis
3048 m_memory->UnmapRegion(*m_page_table_impl, addr, num_pages * PageSize); 3096 m_memory->UnmapRegion(*m_page_table_impl, addr, num_pages * PageSize);
3049 break; 3097 break;
3050 } 3098 }
3051 case OperationType::MapFirst:
3052 case OperationType::Map: { 3099 case OperationType::Map: {
3053 ASSERT(map_addr); 3100 ASSERT(map_addr);
3054 ASSERT(Common::IsAligned(GetInteger(map_addr), PageSize)); 3101 ASSERT(Common::IsAligned(GetInteger(map_addr), PageSize));
@@ -3056,11 +3103,7 @@ Result KPageTable::Operate(KProcessAddress addr, size_t num_pages, KMemoryPermis
3056 3103
3057 // Open references to pages, if we should. 3104 // Open references to pages, if we should.
3058 if (IsHeapPhysicalAddress(m_kernel.MemoryLayout(), map_addr)) { 3105 if (IsHeapPhysicalAddress(m_kernel.MemoryLayout(), map_addr)) {
3059 if (operation == OperationType::MapFirst) { 3106 m_kernel.MemoryManager().Open(map_addr, num_pages);
3060 m_kernel.MemoryManager().OpenFirst(map_addr, num_pages);
3061 } else {
3062 m_kernel.MemoryManager().Open(map_addr, num_pages);
3063 }
3064 } 3107 }
3065 break; 3108 break;
3066 } 3109 }
@@ -3070,6 +3113,7 @@ Result KPageTable::Operate(KProcessAddress addr, size_t num_pages, KMemoryPermis
3070 } 3113 }
3071 case OperationType::ChangePermissions: 3114 case OperationType::ChangePermissions:
3072 case OperationType::ChangePermissionsAndRefresh: 3115 case OperationType::ChangePermissionsAndRefresh:
3116 case OperationType::ChangePermissionsAndRefreshAndFlush:
3073 break; 3117 break;
3074 default: 3118 default:
3075 ASSERT(false); 3119 ASSERT(false);
@@ -3089,79 +3133,79 @@ void KPageTable::FinalizeUpdate(PageLinkedList* page_list) {
3089 } 3133 }
3090} 3134}
3091 3135
3092KProcessAddress KPageTable::GetRegionAddress(KMemoryState state) const { 3136KProcessAddress KPageTable::GetRegionAddress(Svc::MemoryState state) const {
3093 switch (state) { 3137 switch (state) {
3094 case KMemoryState::Free: 3138 case Svc::MemoryState::Free:
3095 case KMemoryState::Kernel: 3139 case Svc::MemoryState::Kernel:
3096 return m_address_space_start; 3140 return m_address_space_start;
3097 case KMemoryState::Normal: 3141 case Svc::MemoryState::Normal:
3098 return m_heap_region_start; 3142 return m_heap_region_start;
3099 case KMemoryState::Ipc: 3143 case Svc::MemoryState::Ipc:
3100 case KMemoryState::NonSecureIpc: 3144 case Svc::MemoryState::NonSecureIpc:
3101 case KMemoryState::NonDeviceIpc: 3145 case Svc::MemoryState::NonDeviceIpc:
3102 return m_alias_region_start; 3146 return m_alias_region_start;
3103 case KMemoryState::Stack: 3147 case Svc::MemoryState::Stack:
3104 return m_stack_region_start; 3148 return m_stack_region_start;
3105 case KMemoryState::Static: 3149 case Svc::MemoryState::Static:
3106 case KMemoryState::ThreadLocal: 3150 case Svc::MemoryState::ThreadLocal:
3107 return m_kernel_map_region_start; 3151 return m_kernel_map_region_start;
3108 case KMemoryState::Io: 3152 case Svc::MemoryState::Io:
3109 case KMemoryState::Shared: 3153 case Svc::MemoryState::Shared:
3110 case KMemoryState::AliasCode: 3154 case Svc::MemoryState::AliasCode:
3111 case KMemoryState::AliasCodeData: 3155 case Svc::MemoryState::AliasCodeData:
3112 case KMemoryState::Transfered: 3156 case Svc::MemoryState::Transfered:
3113 case KMemoryState::SharedTransfered: 3157 case Svc::MemoryState::SharedTransfered:
3114 case KMemoryState::SharedCode: 3158 case Svc::MemoryState::SharedCode:
3115 case KMemoryState::GeneratedCode: 3159 case Svc::MemoryState::GeneratedCode:
3116 case KMemoryState::CodeOut: 3160 case Svc::MemoryState::CodeOut:
3117 case KMemoryState::Coverage: 3161 case Svc::MemoryState::Coverage:
3118 case KMemoryState::Insecure: 3162 case Svc::MemoryState::Insecure:
3119 return m_alias_code_region_start; 3163 return m_alias_code_region_start;
3120 case KMemoryState::Code: 3164 case Svc::MemoryState::Code:
3121 case KMemoryState::CodeData: 3165 case Svc::MemoryState::CodeData:
3122 return m_code_region_start; 3166 return m_code_region_start;
3123 default: 3167 default:
3124 UNREACHABLE(); 3168 UNREACHABLE();
3125 } 3169 }
3126} 3170}
3127 3171
3128size_t KPageTable::GetRegionSize(KMemoryState state) const { 3172size_t KPageTable::GetRegionSize(Svc::MemoryState state) const {
3129 switch (state) { 3173 switch (state) {
3130 case KMemoryState::Free: 3174 case Svc::MemoryState::Free:
3131 case KMemoryState::Kernel: 3175 case Svc::MemoryState::Kernel:
3132 return m_address_space_end - m_address_space_start; 3176 return m_address_space_end - m_address_space_start;
3133 case KMemoryState::Normal: 3177 case Svc::MemoryState::Normal:
3134 return m_heap_region_end - m_heap_region_start; 3178 return m_heap_region_end - m_heap_region_start;
3135 case KMemoryState::Ipc: 3179 case Svc::MemoryState::Ipc:
3136 case KMemoryState::NonSecureIpc: 3180 case Svc::MemoryState::NonSecureIpc:
3137 case KMemoryState::NonDeviceIpc: 3181 case Svc::MemoryState::NonDeviceIpc:
3138 return m_alias_region_end - m_alias_region_start; 3182 return m_alias_region_end - m_alias_region_start;
3139 case KMemoryState::Stack: 3183 case Svc::MemoryState::Stack:
3140 return m_stack_region_end - m_stack_region_start; 3184 return m_stack_region_end - m_stack_region_start;
3141 case KMemoryState::Static: 3185 case Svc::MemoryState::Static:
3142 case KMemoryState::ThreadLocal: 3186 case Svc::MemoryState::ThreadLocal:
3143 return m_kernel_map_region_end - m_kernel_map_region_start; 3187 return m_kernel_map_region_end - m_kernel_map_region_start;
3144 case KMemoryState::Io: 3188 case Svc::MemoryState::Io:
3145 case KMemoryState::Shared: 3189 case Svc::MemoryState::Shared:
3146 case KMemoryState::AliasCode: 3190 case Svc::MemoryState::AliasCode:
3147 case KMemoryState::AliasCodeData: 3191 case Svc::MemoryState::AliasCodeData:
3148 case KMemoryState::Transfered: 3192 case Svc::MemoryState::Transfered:
3149 case KMemoryState::SharedTransfered: 3193 case Svc::MemoryState::SharedTransfered:
3150 case KMemoryState::SharedCode: 3194 case Svc::MemoryState::SharedCode:
3151 case KMemoryState::GeneratedCode: 3195 case Svc::MemoryState::GeneratedCode:
3152 case KMemoryState::CodeOut: 3196 case Svc::MemoryState::CodeOut:
3153 case KMemoryState::Coverage: 3197 case Svc::MemoryState::Coverage:
3154 case KMemoryState::Insecure: 3198 case Svc::MemoryState::Insecure:
3155 return m_alias_code_region_end - m_alias_code_region_start; 3199 return m_alias_code_region_end - m_alias_code_region_start;
3156 case KMemoryState::Code: 3200 case Svc::MemoryState::Code:
3157 case KMemoryState::CodeData: 3201 case Svc::MemoryState::CodeData:
3158 return m_code_region_end - m_code_region_start; 3202 return m_code_region_end - m_code_region_start;
3159 default: 3203 default:
3160 UNREACHABLE(); 3204 UNREACHABLE();
3161 } 3205 }
3162} 3206}
3163 3207
3164bool KPageTable::CanContain(KProcessAddress addr, size_t size, KMemoryState state) const { 3208bool KPageTable::CanContain(KProcessAddress addr, size_t size, Svc::MemoryState state) const {
3165 const KProcessAddress end = addr + size; 3209 const KProcessAddress end = addr + size;
3166 const KProcessAddress last = end - 1; 3210 const KProcessAddress last = end - 1;
3167 3211
@@ -3175,32 +3219,32 @@ bool KPageTable::CanContain(KProcessAddress addr, size_t size, KMemoryState stat
3175 const bool is_in_alias = !(end <= m_alias_region_start || m_alias_region_end <= addr || 3219 const bool is_in_alias = !(end <= m_alias_region_start || m_alias_region_end <= addr ||
3176 m_alias_region_start == m_alias_region_end); 3220 m_alias_region_start == m_alias_region_end);
3177 switch (state) { 3221 switch (state) {
3178 case KMemoryState::Free: 3222 case Svc::MemoryState::Free:
3179 case KMemoryState::Kernel: 3223 case Svc::MemoryState::Kernel:
3180 return is_in_region; 3224 return is_in_region;
3181 case KMemoryState::Io: 3225 case Svc::MemoryState::Io:
3182 case KMemoryState::Static: 3226 case Svc::MemoryState::Static:
3183 case KMemoryState::Code: 3227 case Svc::MemoryState::Code:
3184 case KMemoryState::CodeData: 3228 case Svc::MemoryState::CodeData:
3185 case KMemoryState::Shared: 3229 case Svc::MemoryState::Shared:
3186 case KMemoryState::AliasCode: 3230 case Svc::MemoryState::AliasCode:
3187 case KMemoryState::AliasCodeData: 3231 case Svc::MemoryState::AliasCodeData:
3188 case KMemoryState::Stack: 3232 case Svc::MemoryState::Stack:
3189 case KMemoryState::ThreadLocal: 3233 case Svc::MemoryState::ThreadLocal:
3190 case KMemoryState::Transfered: 3234 case Svc::MemoryState::Transfered:
3191 case KMemoryState::SharedTransfered: 3235 case Svc::MemoryState::SharedTransfered:
3192 case KMemoryState::SharedCode: 3236 case Svc::MemoryState::SharedCode:
3193 case KMemoryState::GeneratedCode: 3237 case Svc::MemoryState::GeneratedCode:
3194 case KMemoryState::CodeOut: 3238 case Svc::MemoryState::CodeOut:
3195 case KMemoryState::Coverage: 3239 case Svc::MemoryState::Coverage:
3196 case KMemoryState::Insecure: 3240 case Svc::MemoryState::Insecure:
3197 return is_in_region && !is_in_heap && !is_in_alias; 3241 return is_in_region && !is_in_heap && !is_in_alias;
3198 case KMemoryState::Normal: 3242 case Svc::MemoryState::Normal:
3199 ASSERT(is_in_heap); 3243 ASSERT(is_in_heap);
3200 return is_in_region && !is_in_alias; 3244 return is_in_region && !is_in_alias;
3201 case KMemoryState::Ipc: 3245 case Svc::MemoryState::Ipc:
3202 case KMemoryState::NonSecureIpc: 3246 case Svc::MemoryState::NonSecureIpc:
3203 case KMemoryState::NonDeviceIpc: 3247 case Svc::MemoryState::NonDeviceIpc:
3204 ASSERT(is_in_alias); 3248 ASSERT(is_in_alias);
3205 return is_in_region && !is_in_heap; 3249 return is_in_region && !is_in_heap;
3206 default: 3250 default:
@@ -3264,21 +3308,16 @@ Result KPageTable::CheckMemoryStateContiguous(size_t* out_blocks_needed, KProces
3264 3308
3265Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm, 3309Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
3266 KMemoryAttribute* out_attr, size_t* out_blocks_needed, 3310 KMemoryAttribute* out_attr, size_t* out_blocks_needed,
3267 KProcessAddress addr, size_t size, KMemoryState state_mask, 3311 KMemoryBlockManager::const_iterator it,
3312 KProcessAddress last_addr, KMemoryState state_mask,
3268 KMemoryState state, KMemoryPermission perm_mask, 3313 KMemoryState state, KMemoryPermission perm_mask,
3269 KMemoryPermission perm, KMemoryAttribute attr_mask, 3314 KMemoryPermission perm, KMemoryAttribute attr_mask,
3270 KMemoryAttribute attr, KMemoryAttribute ignore_attr) const { 3315 KMemoryAttribute attr, KMemoryAttribute ignore_attr) const {
3271 ASSERT(this->IsLockedByCurrentThread()); 3316 ASSERT(this->IsLockedByCurrentThread());
3272 3317
3273 // Get information about the first block. 3318 // Get information about the first block.
3274 const KProcessAddress last_addr = addr + size - 1;
3275 KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr);
3276 KMemoryInfo info = it->GetMemoryInfo(); 3319 KMemoryInfo info = it->GetMemoryInfo();
3277 3320
3278 // If the start address isn't aligned, we need a block.
3279 const size_t blocks_for_start_align =
3280 (Common::AlignDown(GetInteger(addr), PageSize) != info.GetAddress()) ? 1 : 0;
3281
3282 // Validate all blocks in the range have correct state. 3321 // Validate all blocks in the range have correct state.
3283 const KMemoryState first_state = info.m_state; 3322 const KMemoryState first_state = info.m_state;
3284 const KMemoryPermission first_perm = info.m_permission; 3323 const KMemoryPermission first_perm = info.m_permission;
@@ -3304,10 +3343,6 @@ Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission*
3304 info = it->GetMemoryInfo(); 3343 info = it->GetMemoryInfo();
3305 } 3344 }
3306 3345
3307 // If the end address isn't aligned, we need a block.
3308 const size_t blocks_for_end_align =
3309 (Common::AlignUp(GetInteger(addr) + size, PageSize) != info.GetEndAddress()) ? 1 : 0;
3310
3311 // Write output state. 3346 // Write output state.
3312 if (out_state != nullptr) { 3347 if (out_state != nullptr) {
3313 *out_state = first_state; 3348 *out_state = first_state;
@@ -3318,9 +3353,39 @@ Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission*
3318 if (out_attr != nullptr) { 3353 if (out_attr != nullptr) {
3319 *out_attr = static_cast<KMemoryAttribute>(first_attr & ~ignore_attr); 3354 *out_attr = static_cast<KMemoryAttribute>(first_attr & ~ignore_attr);
3320 } 3355 }
3356
3357 // If the end address isn't aligned, we need a block.
3321 if (out_blocks_needed != nullptr) { 3358 if (out_blocks_needed != nullptr) {
3322 *out_blocks_needed = blocks_for_start_align + blocks_for_end_align; 3359 const size_t blocks_for_end_align =
3360 (Common::AlignDown(GetInteger(last_addr), PageSize) + PageSize != info.GetEndAddress())
3361 ? 1
3362 : 0;
3363 *out_blocks_needed = blocks_for_end_align;
3323 } 3364 }
3365
3366 R_SUCCEED();
3367}
3368
3369Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
3370 KMemoryAttribute* out_attr, size_t* out_blocks_needed,
3371 KProcessAddress addr, size_t size, KMemoryState state_mask,
3372 KMemoryState state, KMemoryPermission perm_mask,
3373 KMemoryPermission perm, KMemoryAttribute attr_mask,
3374 KMemoryAttribute attr, KMemoryAttribute ignore_attr) const {
3375 ASSERT(this->IsLockedByCurrentThread());
3376
3377 // Check memory state.
3378 const KProcessAddress last_addr = addr + size - 1;
3379 KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr);
3380 R_TRY(this->CheckMemoryState(out_state, out_perm, out_attr, out_blocks_needed, it, last_addr,
3381 state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr));
3382
3383 // If the start address isn't aligned, we need a block.
3384 if (out_blocks_needed != nullptr &&
3385 Common::AlignDown(GetInteger(addr), PageSize) != it->GetAddress()) {
3386 ++(*out_blocks_needed);
3387 }
3388
3324 R_SUCCEED(); 3389 R_SUCCEED();
3325} 3390}
3326 3391
@@ -3388,6 +3453,11 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_K
3388 new_attr, KMemoryBlockDisableMergeAttribute::Locked, 3453 new_attr, KMemoryBlockDisableMergeAttribute::Locked,
3389 KMemoryBlockDisableMergeAttribute::None); 3454 KMemoryBlockDisableMergeAttribute::None);
3390 3455
3456 // If we have an output page group, open.
3457 if (out_pg) {
3458 out_pg->Open();
3459 }
3460
3391 R_SUCCEED(); 3461 R_SUCCEED();
3392} 3462}
3393 3463
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index b9e8c6042..66f16faaf 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -63,7 +63,7 @@ public:
63 explicit KPageTable(Core::System& system_); 63 explicit KPageTable(Core::System& system_);
64 ~KPageTable(); 64 ~KPageTable();
65 65
66 Result InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr, 66 Result InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr,
67 bool enable_das_merge, bool from_back, KMemoryManager::Pool pool, 67 bool enable_das_merge, bool from_back, KMemoryManager::Pool pool,
68 KProcessAddress code_addr, size_t code_size, 68 KProcessAddress code_addr, size_t code_size,
69 KSystemResource* system_resource, KResourceLimit* resource_limit, 69 KSystemResource* system_resource, KResourceLimit* resource_limit,
@@ -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,
@@ -123,8 +126,6 @@ public:
123 return m_block_info_manager; 126 return m_block_info_manager;
124 } 127 }
125 128
126 bool CanContain(KProcessAddress addr, size_t size, KMemoryState state) const;
127
128 Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment, 129 Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
129 KPhysicalAddress phys_addr, KProcessAddress region_start, 130 KPhysicalAddress phys_addr, KProcessAddress region_start,
130 size_t region_num_pages, KMemoryState state, KMemoryPermission perm) { 131 size_t region_num_pages, KMemoryState state, KMemoryPermission perm) {
@@ -159,6 +160,21 @@ public:
159 void RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size, 160 void RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size,
160 const KPageGroup& pg); 161 const KPageGroup& pg);
161 162
163 KProcessAddress GetRegionAddress(Svc::MemoryState state) const;
164 size_t GetRegionSize(Svc::MemoryState state) const;
165 bool CanContain(KProcessAddress addr, size_t size, Svc::MemoryState state) const;
166
167 KProcessAddress GetRegionAddress(KMemoryState state) const {
168 return this->GetRegionAddress(static_cast<Svc::MemoryState>(state & KMemoryState::Mask));
169 }
170 size_t GetRegionSize(KMemoryState state) const {
171 return this->GetRegionSize(static_cast<Svc::MemoryState>(state & KMemoryState::Mask));
172 }
173 bool CanContain(KProcessAddress addr, size_t size, KMemoryState state) const {
174 return this->CanContain(addr, size,
175 static_cast<Svc::MemoryState>(state & KMemoryState::Mask));
176 }
177
162protected: 178protected:
163 struct PageLinkedList { 179 struct PageLinkedList {
164 private: 180 private:
@@ -201,12 +217,13 @@ protected:
201private: 217private:
202 enum class OperationType : u32 { 218 enum class OperationType : u32 {
203 Map = 0, 219 Map = 0,
204 MapFirst = 1, 220 MapGroup = 1,
205 MapGroup = 2, 221 MapFirstGroup = 2,
206 Unmap = 3, 222 Unmap = 3,
207 ChangePermissions = 4, 223 ChangePermissions = 4,
208 ChangePermissionsAndRefresh = 5, 224 ChangePermissionsAndRefresh = 5,
209 Separate = 6, 225 ChangePermissionsAndRefreshAndFlush = 6,
226 Separate = 7,
210 }; 227 };
211 228
212 static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr = 229 static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr =
@@ -225,8 +242,6 @@ private:
225 Result Operate(KProcessAddress addr, size_t num_pages, KMemoryPermission perm, 242 Result Operate(KProcessAddress addr, size_t num_pages, KMemoryPermission perm,
226 OperationType operation, KPhysicalAddress map_addr = 0); 243 OperationType operation, KPhysicalAddress map_addr = 0);
227 void FinalizeUpdate(PageLinkedList* page_list); 244 void FinalizeUpdate(PageLinkedList* page_list);
228 KProcessAddress GetRegionAddress(KMemoryState state) const;
229 size_t GetRegionSize(KMemoryState state) const;
230 245
231 KProcessAddress FindFreeArea(KProcessAddress region_start, size_t region_num_pages, 246 KProcessAddress FindFreeArea(KProcessAddress region_start, size_t region_num_pages,
232 size_t num_pages, size_t alignment, size_t offset, 247 size_t num_pages, size_t alignment, size_t offset,
@@ -249,6 +264,13 @@ private:
249 KMemoryAttribute attr_mask, KMemoryAttribute attr) const; 264 KMemoryAttribute attr_mask, KMemoryAttribute attr) const;
250 Result CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm, 265 Result CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
251 KMemoryAttribute* out_attr, size_t* out_blocks_needed, 266 KMemoryAttribute* out_attr, size_t* out_blocks_needed,
267 KMemoryBlockManager::const_iterator it, KProcessAddress last_addr,
268 KMemoryState state_mask, KMemoryState state,
269 KMemoryPermission perm_mask, KMemoryPermission perm,
270 KMemoryAttribute attr_mask, KMemoryAttribute attr,
271 KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const;
272 Result CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm,
273 KMemoryAttribute* out_attr, size_t* out_blocks_needed,
252 KProcessAddress addr, size_t size, KMemoryState state_mask, 274 KProcessAddress addr, size_t size, KMemoryState state_mask,
253 KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, 275 KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm,
254 KMemoryAttribute attr_mask, KMemoryAttribute attr, 276 KMemoryAttribute attr_mask, KMemoryAttribute attr,
@@ -378,7 +400,7 @@ public:
378 constexpr size_t GetAliasCodeRegionSize() const { 400 constexpr size_t GetAliasCodeRegionSize() const {
379 return m_alias_code_region_end - m_alias_code_region_start; 401 return m_alias_code_region_end - m_alias_code_region_start;
380 } 402 }
381 size_t GetNormalMemorySize() { 403 size_t GetNormalMemorySize() const {
382 KScopedLightLock lk(m_general_lock); 404 KScopedLightLock lk(m_general_lock);
383 return GetHeapSize() + m_mapped_physical_memory_size; 405 return GetHeapSize() + m_mapped_physical_memory_size;
384 } 406 }
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 4a099286b..1f4b0755d 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -1,515 +1,598 @@
1// SPDX-FileCopyrightText: 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 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 <algorithm>
5#include <bitset>
6#include <ctime>
7#include <memory>
8#include <random> 4#include <random>
9#include "common/alignment.h"
10#include "common/assert.h"
11#include "common/logging/log.h"
12#include "common/scope_exit.h" 5#include "common/scope_exit.h"
13#include "common/settings.h" 6#include "common/settings.h"
14#include "core/core.h" 7#include "core/core.h"
15#include "core/file_sys/program_metadata.h"
16#include "core/hle/kernel/code_set.h"
17#include "core/hle/kernel/k_memory_block_manager.h"
18#include "core/hle/kernel/k_page_table.h"
19#include "core/hle/kernel/k_process.h" 8#include "core/hle/kernel/k_process.h"
20#include "core/hle/kernel/k_resource_limit.h"
21#include "core/hle/kernel/k_scheduler.h"
22#include "core/hle/kernel/k_scoped_resource_reservation.h" 9#include "core/hle/kernel/k_scoped_resource_reservation.h"
23#include "core/hle/kernel/k_shared_memory.h" 10#include "core/hle/kernel/k_shared_memory.h"
24#include "core/hle/kernel/k_shared_memory_info.h" 11#include "core/hle/kernel/k_shared_memory_info.h"
25#include "core/hle/kernel/k_thread.h" 12#include "core/hle/kernel/k_thread_local_page.h"
26#include "core/hle/kernel/kernel.h" 13#include "core/hle/kernel/k_thread_queue.h"
27#include "core/hle/kernel/svc_results.h" 14#include "core/hle/kernel/k_worker_task_manager.h"
28#include "core/memory.h"
29 15
30namespace Kernel { 16namespace Kernel {
31namespace {
32/**
33 * Sets up the primary application thread
34 *
35 * @param system The system instance to create the main thread under.
36 * @param owner_process The parent process for the main thread
37 * @param priority The priority to give the main thread
38 */
39void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority,
40 KProcessAddress stack_top) {
41 const KProcessAddress entry_point = owner_process.GetEntryPoint();
42 ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1));
43
44 KThread* thread = KThread::Create(system.Kernel());
45 SCOPE_EXIT({ thread->Close(); });
46
47 ASSERT(KThread::InitializeUserThread(system, thread, entry_point, 0, stack_top, priority,
48 owner_process.GetIdealCoreId(),
49 std::addressof(owner_process))
50 .IsSuccess());
51
52 // Register 1 must be a handle to the main thread
53 Handle thread_handle{};
54 owner_process.GetHandleTable().Add(std::addressof(thread_handle), thread);
55
56 thread->GetContext32().cpu_registers[0] = 0;
57 thread->GetContext64().cpu_registers[0] = 0;
58 thread->GetContext32().cpu_registers[1] = thread_handle;
59 thread->GetContext64().cpu_registers[1] = thread_handle;
60
61 if (system.DebuggerEnabled()) {
62 thread->RequestSuspend(SuspendType::Debug);
63 }
64 17
65 // Run our thread. 18namespace {
66 void(thread->Run());
67}
68} // Anonymous namespace
69 19
70Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process_name, 20Result TerminateChildren(KernelCore& kernel, KProcess* process,
71 ProcessType type, KResourceLimit* res_limit) { 21 const KThread* thread_to_not_terminate) {
72 auto& kernel = system.Kernel(); 22 // Request that all children threads terminate.
23 {
24 KScopedLightLock proc_lk(process->GetListLock());
25 KScopedSchedulerLock sl(kernel);
26
27 if (thread_to_not_terminate != nullptr &&
28 process->GetPinnedThread(GetCurrentCoreId(kernel)) == thread_to_not_terminate) {
29 // NOTE: Here Nintendo unpins the current thread instead of the thread_to_not_terminate.
30 // This is valid because the only caller which uses non-nullptr as argument uses
31 // GetCurrentThreadPointer(), but it's still notable because it seems incorrect at
32 // first glance.
33 process->UnpinCurrentThread();
34 }
73 35
74 process->name = std::move(process_name); 36 auto& thread_list = process->GetThreadList();
75 process->m_resource_limit = res_limit; 37 for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
76 process->m_system_resource_address = 0; 38 if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
77 process->m_state = State::Created; 39 if (thread->GetState() != ThreadState::Terminated) {
78 process->m_program_id = 0; 40 thread->RequestTerminate();
79 process->m_process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID() 41 }
80 : kernel.CreateNewUserProcessID(); 42 }
81 process->m_capabilities.InitializeForMetadatalessProcess(); 43 }
82 process->m_is_initialized = true; 44 }
83 45
84 std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue() 46 // Wait for all children threads to terminate.
85 : static_cast<u32>(std::time(nullptr))); 47 while (true) {
86 std::uniform_int_distribution<u64> distribution; 48 // Get the next child.
87 std::generate(process->m_random_entropy.begin(), process->m_random_entropy.end(), 49 KThread* cur_child = nullptr;
88 [&] { return distribution(rng); }); 50 {
51 KScopedLightLock proc_lk(process->GetListLock());
52
53 auto& thread_list = process->GetThreadList();
54 for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
55 if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
56 if (thread->GetState() != ThreadState::Terminated) {
57 if (thread->Open()) {
58 cur_child = thread;
59 break;
60 }
61 }
62 }
63 }
64 }
89 65
90 kernel.AppendNewProcess(process); 66 // If we didn't find any non-terminated children, we're done.
67 if (cur_child == nullptr) {
68 break;
69 }
91 70
92 // Clear remaining fields. 71 // Terminate and close the thread.
93 process->m_num_running_threads = 0; 72 SCOPE_EXIT({ cur_child->Close(); });
94 process->m_is_signaled = false;
95 process->m_exception_thread = nullptr;
96 process->m_is_suspended = false;
97 process->m_schedule_count = 0;
98 process->m_is_handle_table_initialized = false;
99 process->m_is_hbl = false;
100 73
101 // Open a reference to the resource limit. 74 if (const Result terminate_result = cur_child->Terminate();
102 process->m_resource_limit->Open(); 75 ResultTerminationRequested == terminate_result) {
76 R_THROW(terminate_result);
77 }
78 }
103 79
104 R_SUCCEED(); 80 R_SUCCEED();
105} 81}
106 82
107void KProcess::DoWorkerTaskImpl() { 83class ThreadQueueImplForKProcessEnterUserException final : public KThreadQueue {
108 UNIMPLEMENTED(); 84private:
109} 85 KThread** m_exception_thread;
110
111KResourceLimit* KProcess::GetResourceLimit() const {
112 return m_resource_limit;
113}
114 86
115void KProcess::IncrementRunningThreadCount() { 87public:
116 ASSERT(m_num_running_threads.load() >= 0); 88 explicit ThreadQueueImplForKProcessEnterUserException(KernelCore& kernel, KThread** t)
117 ++m_num_running_threads; 89 : KThreadQueue(kernel), m_exception_thread(t) {}
118}
119 90
120void KProcess::DecrementRunningThreadCount() { 91 virtual void EndWait(KThread* waiting_thread, Result wait_result) override {
121 ASSERT(m_num_running_threads.load() > 0); 92 // Set the exception thread.
93 *m_exception_thread = waiting_thread;
122 94
123 if (const auto prev = m_num_running_threads--; prev == 1) { 95 // Invoke the base end wait handler.
124 // TODO(bunnei): Process termination to be implemented when multiprocess is supported. 96 KThreadQueue::EndWait(waiting_thread, wait_result);
125 } 97 }
126}
127 98
128u64 KProcess::GetTotalPhysicalMemoryAvailable() { 99 virtual void CancelWait(KThread* waiting_thread, Result wait_result,
129 const u64 capacity{m_resource_limit->GetFreeValue(LimitableResource::PhysicalMemoryMax) + 100 bool cancel_timer_task) override {
130 m_page_table.GetNormalMemorySize() + GetSystemResourceSize() + m_image_size + 101 // Remove the thread as a waiter on its mutex owner.
131 m_main_thread_stack_size}; 102 waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread);
132 if (const auto pool_size = m_kernel.MemoryManager().GetSize(KMemoryManager::Pool::Application); 103
133 capacity != pool_size) { 104 // Invoke the base cancel wait handler.
134 LOG_WARNING(Kernel, "capacity {} != application pool size {}", capacity, pool_size); 105 KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
135 }
136 if (capacity < m_memory_usage_capacity) {
137 return capacity;
138 } 106 }
139 return m_memory_usage_capacity; 107};
140}
141 108
142u64 KProcess::GetTotalPhysicalMemoryAvailableWithoutSystemResource() { 109void GenerateRandom(std::span<u64> out_random) {
143 return this->GetTotalPhysicalMemoryAvailable() - this->GetSystemResourceSize(); 110 std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue()
111 : static_cast<u32>(std::time(nullptr)));
112 std::uniform_int_distribution<u64> distribution;
113 std::generate(out_random.begin(), out_random.end(), [&] { return distribution(rng); });
144} 114}
145 115
146u64 KProcess::GetTotalPhysicalMemoryUsed() { 116} // namespace
147 return m_image_size + m_main_thread_stack_size + m_page_table.GetNormalMemorySize() +
148 this->GetSystemResourceSize();
149}
150 117
151u64 KProcess::GetTotalPhysicalMemoryUsedWithoutSystemResource() { 118void KProcess::Finalize() {
152 return this->GetTotalPhysicalMemoryUsed() - this->GetSystemResourceUsage(); 119 // Delete the process local region.
153} 120 this->DeleteThreadLocalRegion(m_plr_address);
154 121
155bool KProcess::ReleaseUserException(KThread* thread) { 122 // Get the used memory size.
156 KScopedSchedulerLock sl{m_kernel}; 123 const size_t used_memory_size = this->GetUsedNonSystemUserPhysicalMemorySize();
157 124
158 if (m_exception_thread == thread) { 125 // Finalize the page table.
159 m_exception_thread = nullptr; 126 m_page_table.Finalize();
160 127
161 // Remove waiter thread. 128 // Finish using our system resource.
162 bool has_waiters{}; 129 if (m_system_resource) {
163 if (KThread* next = thread->RemoveKernelWaiterByKey( 130 if (m_system_resource->IsSecureResource()) {
164 std::addressof(has_waiters), 131 // Finalize optimized memory. If memory wasn't optimized, this is a no-op.
165 reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread))); 132 m_kernel.MemoryManager().FinalizeOptimizedMemory(this->GetId(), m_memory_pool);
166 next != nullptr) {
167 next->EndWait(ResultSuccess);
168 } 133 }
169 134
170 KScheduler::SetSchedulerUpdateNeeded(m_kernel); 135 m_system_resource->Close();
171 136 m_system_resource = nullptr;
172 return true;
173 } else {
174 return false;
175 } 137 }
176}
177
178void KProcess::PinCurrentThread(s32 core_id) {
179 ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
180 138
181 // Get the current thread. 139 // Free all shared memory infos.
182 KThread* cur_thread = 140 {
183 m_kernel.Scheduler(static_cast<std::size_t>(core_id)).GetSchedulerCurrentThread(); 141 auto it = m_shared_memory_list.begin();
142 while (it != m_shared_memory_list.end()) {
143 KSharedMemoryInfo* info = std::addressof(*it);
144 KSharedMemory* shmem = info->GetSharedMemory();
184 145
185 // If the thread isn't terminated, pin it. 146 while (!info->Close()) {
186 if (!cur_thread->IsTerminationRequested()) { 147 shmem->Close();
187 // Pin it. 148 }
188 this->PinThread(core_id, cur_thread); 149 shmem->Close();
189 cur_thread->Pin(core_id);
190 150
191 // An update is needed. 151 it = m_shared_memory_list.erase(it);
192 KScheduler::SetSchedulerUpdateNeeded(m_kernel); 152 KSharedMemoryInfo::Free(m_kernel, info);
153 }
193 } 154 }
194}
195 155
196void KProcess::UnpinCurrentThread(s32 core_id) { 156 // Our thread local page list must be empty at this point.
197 ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); 157 ASSERT(m_partially_used_tlp_tree.empty());
198 158 ASSERT(m_fully_used_tlp_tree.empty());
199 // Get the current thread.
200 KThread* cur_thread =
201 m_kernel.Scheduler(static_cast<std::size_t>(core_id)).GetSchedulerCurrentThread();
202 159
203 // Unpin it. 160 // Release memory to the resource limit.
204 cur_thread->Unpin(); 161 if (m_resource_limit != nullptr) {
205 this->UnpinThread(core_id, cur_thread); 162 ASSERT(used_memory_size >= m_memory_release_hint);
163 m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, used_memory_size,
164 used_memory_size - m_memory_release_hint);
165 m_resource_limit->Close();
166 }
206 167
207 // An update is needed. 168 // Perform inherited finalization.
208 KScheduler::SetSchedulerUpdateNeeded(m_kernel); 169 KSynchronizationObject::Finalize();
209} 170}
210 171
211void KProcess::UnpinThread(KThread* thread) { 172Result KProcess::Initialize(const Svc::CreateProcessParameter& params, KResourceLimit* res_limit,
212 ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); 173 bool is_real) {
213 174 // TODO: remove this special case
214 // Get the thread's core id. 175 if (is_real) {
215 const auto core_id = thread->GetActiveCore(); 176 // Create and clear the process local region.
177 R_TRY(this->CreateThreadLocalRegion(std::addressof(m_plr_address)));
178 this->GetMemory().ZeroBlock(m_plr_address, Svc::ThreadLocalRegionSize);
179 }
216 180
217 // Unpin it. 181 // Copy in the name from parameters.
218 this->UnpinThread(core_id, thread); 182 static_assert(sizeof(params.name) < sizeof(m_name));
219 thread->Unpin(); 183 std::memcpy(m_name.data(), params.name.data(), sizeof(params.name));
184 m_name[sizeof(params.name)] = 0;
185
186 // Set misc fields.
187 m_state = State::Created;
188 m_main_thread_stack_size = 0;
189 m_used_kernel_memory_size = 0;
190 m_ideal_core_id = 0;
191 m_flags = params.flags;
192 m_version = params.version;
193 m_program_id = params.program_id;
194 m_code_address = params.code_address;
195 m_code_size = params.code_num_pages * PageSize;
196 m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication);
197
198 // Set thread fields.
199 for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
200 m_running_threads[i] = nullptr;
201 m_pinned_threads[i] = nullptr;
202 m_running_thread_idle_counts[i] = 0;
203 m_running_thread_switch_counts[i] = 0;
204 }
220 205
221 // An update is needed. 206 // Set max memory based on address space type.
222 KScheduler::SetSchedulerUpdateNeeded(m_kernel); 207 switch ((params.flags & Svc::CreateProcessFlag::AddressSpaceMask)) {
223} 208 case Svc::CreateProcessFlag::AddressSpace32Bit:
209 case Svc::CreateProcessFlag::AddressSpace64BitDeprecated:
210 case Svc::CreateProcessFlag::AddressSpace64Bit:
211 m_max_process_memory = m_page_table.GetHeapRegionSize();
212 break;
213 case Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias:
214 m_max_process_memory = m_page_table.GetHeapRegionSize() + m_page_table.GetAliasRegionSize();
215 break;
216 default:
217 UNREACHABLE();
218 }
224 219
225Result KProcess::AddSharedMemory(KSharedMemory* shmem, [[maybe_unused]] KProcessAddress address, 220 // Generate random entropy.
226 [[maybe_unused]] size_t size) { 221 GenerateRandom(m_entropy);
227 // Lock ourselves, to prevent concurrent access.
228 KScopedLightLock lk(m_state_lock);
229 222
230 // Try to find an existing info for the memory. 223 // Clear remaining fields.
231 KSharedMemoryInfo* shemen_info = nullptr; 224 m_num_running_threads = 0;
232 const auto iter = std::find_if( 225 m_num_process_switches = 0;
233 m_shared_memory_list.begin(), m_shared_memory_list.end(), 226 m_num_thread_switches = 0;
234 [shmem](const KSharedMemoryInfo* info) { return info->GetSharedMemory() == shmem; }); 227 m_num_fpu_switches = 0;
235 if (iter != m_shared_memory_list.end()) { 228 m_num_supervisor_calls = 0;
236 shemen_info = *iter; 229 m_num_ipc_messages = 0;
237 }
238 230
239 if (shemen_info == nullptr) { 231 m_is_signaled = false;
240 shemen_info = KSharedMemoryInfo::Allocate(m_kernel); 232 m_exception_thread = nullptr;
241 R_UNLESS(shemen_info != nullptr, ResultOutOfMemory); 233 m_is_suspended = false;
234 m_memory_release_hint = 0;
235 m_schedule_count = 0;
236 m_is_handle_table_initialized = false;
242 237
243 shemen_info->Initialize(shmem); 238 // Open a reference to our resource limit.
244 m_shared_memory_list.push_back(shemen_info); 239 m_resource_limit = res_limit;
245 } 240 m_resource_limit->Open();
246 241
247 // Open a reference to the shared memory and its info. 242 // We're initialized!
248 shmem->Open(); 243 m_is_initialized = true;
249 shemen_info->Open();
250 244
251 R_SUCCEED(); 245 R_SUCCEED();
252} 246}
253 247
254void KProcess::RemoveSharedMemory(KSharedMemory* shmem, [[maybe_unused]] KProcessAddress address, 248Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPageGroup& pg,
255 [[maybe_unused]] size_t size) { 249 std::span<const u32> caps, KResourceLimit* res_limit,
256 // Lock ourselves, to prevent concurrent access. 250 KMemoryManager::Pool pool, bool immortal) {
257 KScopedLightLock lk(m_state_lock); 251 ASSERT(res_limit != nullptr);
252 ASSERT((params.code_num_pages * PageSize) / PageSize ==
253 static_cast<size_t>(params.code_num_pages));
254
255 // Set members.
256 m_memory_pool = pool;
257 m_is_default_application_system_resource = false;
258 m_is_immortal = immortal;
259
260 // Setup our system resource.
261 if (const size_t system_resource_num_pages = params.system_resource_num_pages;
262 system_resource_num_pages != 0) {
263 // Create a secure system resource.
264 KSecureSystemResource* secure_resource = KSecureSystemResource::Create(m_kernel);
265 R_UNLESS(secure_resource != nullptr, ResultOutOfResource);
266
267 ON_RESULT_FAILURE {
268 secure_resource->Close();
269 };
270
271 // Initialize the secure resource.
272 R_TRY(secure_resource->Initialize(system_resource_num_pages * PageSize, res_limit,
273 m_memory_pool));
274
275 // Set our system resource.
276 m_system_resource = secure_resource;
277 } else {
278 // Use the system-wide system resource.
279 const bool is_app = True(params.flags & Svc::CreateProcessFlag::IsApplication);
280 m_system_resource = std::addressof(is_app ? m_kernel.GetAppSystemResource()
281 : m_kernel.GetSystemSystemResource());
258 282
259 KSharedMemoryInfo* shemen_info = nullptr; 283 m_is_default_application_system_resource = is_app;
260 const auto iter = std::find_if( 284
261 m_shared_memory_list.begin(), m_shared_memory_list.end(), 285 // Open reference to the system resource.
262 [shmem](const KSharedMemoryInfo* info) { return info->GetSharedMemory() == shmem; }); 286 m_system_resource->Open();
263 if (iter != m_shared_memory_list.end()) {
264 shemen_info = *iter;
265 } 287 }
266 288
267 ASSERT(shemen_info != nullptr); 289 // Ensure we clean up our secure resource, if we fail.
290 ON_RESULT_FAILURE {
291 m_system_resource->Close();
292 m_system_resource = nullptr;
293 };
268 294
269 if (shemen_info->Close()) { 295 // Setup page table.
270 m_shared_memory_list.erase(iter); 296 {
271 KSharedMemoryInfo::Free(m_kernel, shemen_info); 297 const auto as_type = params.flags & Svc::CreateProcessFlag::AddressSpaceMask;
298 const bool enable_aslr = True(params.flags & Svc::CreateProcessFlag::EnableAslr);
299 const bool enable_das_merge =
300 False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
301 R_TRY(m_page_table.InitializeForProcess(
302 as_type, enable_aslr, enable_das_merge, !enable_aslr, pool, params.code_address,
303 params.code_num_pages * PageSize, m_system_resource, res_limit, this->GetMemory()));
272 } 304 }
305 ON_RESULT_FAILURE_2 {
306 m_page_table.Finalize();
307 };
273 308
274 // Close a reference to the shared memory. 309 // Ensure we can insert the code region.
275 shmem->Close(); 310 R_UNLESS(m_page_table.CanContain(params.code_address, params.code_num_pages * PageSize,
276} 311 KMemoryState::Code),
312 ResultInvalidMemoryRegion);
277 313
278void KProcess::RegisterThread(KThread* thread) { 314 // Map the code region.
279 KScopedLightLock lk{m_list_lock}; 315 R_TRY(m_page_table.MapPageGroup(params.code_address, pg, KMemoryState::Code,
316 KMemoryPermission::KernelRead));
280 317
281 m_thread_list.push_back(thread); 318 // Initialize capabilities.
282} 319 R_TRY(m_capabilities.InitializeForKip(caps, std::addressof(m_page_table)));
283 320
284void KProcess::UnregisterThread(KThread* thread) { 321 // Initialize the process id.
285 KScopedLightLock lk{m_list_lock}; 322 m_process_id = m_kernel.CreateNewUserProcessID();
323 ASSERT(InitialProcessIdMin <= m_process_id);
324 ASSERT(m_process_id <= InitialProcessIdMax);
286 325
287 m_thread_list.remove(thread); 326 // Initialize the rest of the process.
288} 327 R_TRY(this->Initialize(params, res_limit, true));
289 328
290u64 KProcess::GetFreeThreadCount() const { 329 // We succeeded!
291 if (m_resource_limit != nullptr) { 330 R_SUCCEED();
292 const auto current_value =
293 m_resource_limit->GetCurrentValue(LimitableResource::ThreadCountMax);
294 const auto limit_value = m_resource_limit->GetLimitValue(LimitableResource::ThreadCountMax);
295 return limit_value - current_value;
296 } else {
297 return 0;
298 }
299} 331}
300 332
301Result KProcess::Reset() { 333Result KProcess::Initialize(const Svc::CreateProcessParameter& params,
302 // Lock the process and the scheduler. 334 std::span<const u32> user_caps, KResourceLimit* res_limit,
303 KScopedLightLock lk(m_state_lock); 335 KMemoryManager::Pool pool) {
304 KScopedSchedulerLock sl{m_kernel}; 336 ASSERT(res_limit != nullptr);
305 337
306 // Validate that we're in a state that we can reset. 338 // Set members.
307 R_UNLESS(m_state != State::Terminated, ResultInvalidState); 339 m_memory_pool = pool;
308 R_UNLESS(m_is_signaled, ResultInvalidState); 340 m_is_default_application_system_resource = false;
341 m_is_immortal = false;
309 342
310 // Clear signaled. 343 // Get the memory sizes.
311 m_is_signaled = false; 344 const size_t code_num_pages = params.code_num_pages;
312 R_SUCCEED(); 345 const size_t system_resource_num_pages = params.system_resource_num_pages;
313} 346 const size_t code_size = code_num_pages * PageSize;
347 const size_t system_resource_size = system_resource_num_pages * PageSize;
314 348
315Result KProcess::SetActivity(ProcessActivity activity) { 349 // Reserve memory for our code resource.
316 // Lock ourselves and the scheduler. 350 KScopedResourceReservation memory_reservation(
317 KScopedLightLock lk{m_state_lock}; 351 res_limit, Svc::LimitableResource::PhysicalMemoryMax, code_size);
318 KScopedLightLock list_lk{m_list_lock}; 352 R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
319 KScopedSchedulerLock sl{m_kernel};
320 353
321 // Validate our state. 354 // Setup our system resource.
322 R_UNLESS(m_state != State::Terminating, ResultInvalidState); 355 if (system_resource_num_pages != 0) {
323 R_UNLESS(m_state != State::Terminated, ResultInvalidState); 356 // Create a secure system resource.
357 KSecureSystemResource* secure_resource = KSecureSystemResource::Create(m_kernel);
358 R_UNLESS(secure_resource != nullptr, ResultOutOfResource);
324 359
325 // Either pause or resume. 360 ON_RESULT_FAILURE {
326 if (activity == ProcessActivity::Paused) { 361 secure_resource->Close();
327 // Verify that we're not suspended. 362 };
328 R_UNLESS(!m_is_suspended, ResultInvalidState);
329 363
330 // Suspend all threads. 364 // Initialize the secure resource.
331 for (auto* thread : this->GetThreadList()) { 365 R_TRY(secure_resource->Initialize(system_resource_size, res_limit, m_memory_pool));
332 thread->RequestSuspend(SuspendType::Process); 366
333 } 367 // Set our system resource.
368 m_system_resource = secure_resource;
334 369
335 // Set ourselves as suspended.
336 this->SetSuspended(true);
337 } else { 370 } else {
338 ASSERT(activity == ProcessActivity::Runnable); 371 // Use the system-wide system resource.
372 const bool is_app = True(params.flags & Svc::CreateProcessFlag::IsApplication);
373 m_system_resource = std::addressof(is_app ? m_kernel.GetAppSystemResource()
374 : m_kernel.GetSystemSystemResource());
339 375
340 // Verify that we're suspended. 376 m_is_default_application_system_resource = is_app;
341 R_UNLESS(m_is_suspended, ResultInvalidState);
342 377
343 // Resume all threads. 378 // Open reference to the system resource.
344 for (auto* thread : this->GetThreadList()) { 379 m_system_resource->Open();
345 thread->Resume(SuspendType::Process); 380 }
346 }
347 381
348 // Set ourselves as resumed. 382 // Ensure we clean up our secure resource, if we fail.
349 this->SetSuspended(false); 383 ON_RESULT_FAILURE {
384 m_system_resource->Close();
385 m_system_resource = nullptr;
386 };
387
388 // Setup page table.
389 {
390 const auto as_type = params.flags & Svc::CreateProcessFlag::AddressSpaceMask;
391 const bool enable_aslr = True(params.flags & Svc::CreateProcessFlag::EnableAslr);
392 const bool enable_das_merge =
393 False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
394 R_TRY(m_page_table.InitializeForProcess(as_type, enable_aslr, enable_das_merge,
395 !enable_aslr, pool, params.code_address, code_size,
396 m_system_resource, res_limit, this->GetMemory()));
397 }
398 ON_RESULT_FAILURE_2 {
399 m_page_table.Finalize();
400 };
401
402 // Ensure we can insert the code region.
403 R_UNLESS(m_page_table.CanContain(params.code_address, code_size, KMemoryState::Code),
404 ResultInvalidMemoryRegion);
405
406 // Map the code region.
407 R_TRY(m_page_table.MapPages(params.code_address, code_num_pages, KMemoryState::Code,
408 KMemoryPermission::KernelRead | KMemoryPermission::NotMapped));
409
410 // Initialize capabilities.
411 R_TRY(m_capabilities.InitializeForUser(user_caps, std::addressof(m_page_table)));
412
413 // Initialize the process id.
414 m_process_id = m_kernel.CreateNewUserProcessID();
415 ASSERT(ProcessIdMin <= m_process_id);
416 ASSERT(m_process_id <= ProcessIdMax);
417
418 // If we should optimize memory allocations, do so.
419 if (m_system_resource->IsSecureResource() &&
420 True(params.flags & Svc::CreateProcessFlag::OptimizeMemoryAllocation)) {
421 R_TRY(m_kernel.MemoryManager().InitializeOptimizedMemory(m_process_id, pool));
350 } 422 }
351 423
424 // Initialize the rest of the process.
425 R_TRY(this->Initialize(params, res_limit, true));
426
427 // We succeeded, so commit our memory reservation.
428 memory_reservation.Commit();
352 R_SUCCEED(); 429 R_SUCCEED();
353} 430}
354 431
355Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, 432void KProcess::DoWorkerTaskImpl() {
356 bool is_hbl) { 433 // Terminate child threads.
357 m_program_id = metadata.GetTitleID(); 434 TerminateChildren(m_kernel, this, nullptr);
358 m_ideal_core = metadata.GetMainThreadCore();
359 m_is_64bit_process = metadata.Is64BitProgram();
360 m_system_resource_size = metadata.GetSystemResourceSize();
361 m_image_size = code_size;
362 m_is_hbl = is_hbl;
363 435
364 if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { 436 // Finalize the handle table, if we're not immortal.
365 // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. 437 if (!m_is_immortal && m_is_handle_table_initialized) {
366 // However, some (buggy) programs/libraries like skyline incorrectly depend on the 438 this->FinalizeHandleTable();
367 // existence of ASLR pages before the entry point, so we will adjust the load address
368 // to point to about 2GiB into the ASLR region.
369 m_code_address = 0x8000'0000;
370 } else {
371 // All other processes can be mapped at the beginning of the code region.
372 if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is36Bit) {
373 m_code_address = 0x800'0000;
374 } else {
375 m_code_address = 0x20'0000;
376 }
377 } 439 }
378 440
379 KScopedResourceReservation memory_reservation( 441 // Finish termination.
380 m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size); 442 this->FinishTermination();
381 if (!memory_reservation.Succeeded()) { 443}
382 LOG_ERROR(Kernel, "Could not reserve process memory requirements of size {:X} bytes",
383 code_size + m_system_resource_size);
384 R_RETURN(ResultLimitReached);
385 }
386 // Initialize process address space
387 if (const Result result{m_page_table.InitializeForProcess(
388 metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application,
389 this->GetEntryPoint(), code_size, std::addressof(m_kernel.GetAppSystemResource()),
390 m_resource_limit, m_kernel.System().ApplicationMemory())};
391 result.IsError()) {
392 R_RETURN(result);
393 }
394
395 // Map process code region
396 if (const Result result{m_page_table.MapProcessCode(this->GetEntryPoint(), code_size / PageSize,
397 KMemoryState::Code,
398 KMemoryPermission::None)};
399 result.IsError()) {
400 R_RETURN(result);
401 }
402
403 // Initialize process capabilities
404 const auto& caps{metadata.GetKernelCapabilities()};
405 if (const Result result{
406 m_capabilities.InitializeForUserProcess(caps.data(), caps.size(), m_page_table)};
407 result.IsError()) {
408 R_RETURN(result);
409 }
410
411 // Set memory usage capacity
412 switch (metadata.GetAddressSpaceType()) {
413 case FileSys::ProgramAddressSpaceType::Is32Bit:
414 case FileSys::ProgramAddressSpaceType::Is36Bit:
415 case FileSys::ProgramAddressSpaceType::Is39Bit:
416 m_memory_usage_capacity =
417 m_page_table.GetHeapRegionEnd() - m_page_table.GetHeapRegionStart();
418 break;
419 444
420 case FileSys::ProgramAddressSpaceType::Is32BitNoMap: 445Result KProcess::StartTermination() {
421 m_memory_usage_capacity = 446 // Finalize the handle table when we're done, if the process isn't immortal.
422 (m_page_table.GetHeapRegionEnd() - m_page_table.GetHeapRegionStart()) + 447 SCOPE_EXIT({
423 (m_page_table.GetAliasRegionEnd() - m_page_table.GetAliasRegionStart()); 448 if (!m_is_immortal) {
424 break; 449 this->FinalizeHandleTable();
450 }
451 });
425 452
426 default: 453 // Terminate child threads other than the current one.
427 ASSERT(false); 454 R_RETURN(TerminateChildren(m_kernel, this, GetCurrentThreadPointer(m_kernel)));
428 break; 455}
429 }
430 456
431 // Create TLS region 457void KProcess::FinishTermination() {
432 R_TRY(this->CreateThreadLocalRegion(std::addressof(m_plr_address))); 458 // Only allow termination to occur if the process isn't immortal.
433 memory_reservation.Commit(); 459 if (!m_is_immortal) {
460 // Release resource limit hint.
461 if (m_resource_limit != nullptr) {
462 m_memory_release_hint = this->GetUsedNonSystemUserPhysicalMemorySize();
463 m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, 0,
464 m_memory_release_hint);
465 }
466
467 // Change state.
468 {
469 KScopedSchedulerLock sl(m_kernel);
470 this->ChangeState(State::Terminated);
471 }
434 472
435 R_RETURN(m_handle_table.Initialize(m_capabilities.GetHandleTableSize())); 473 // Close.
474 this->Close();
475 }
436} 476}
437 477
438void KProcess::Run(s32 main_thread_priority, u64 stack_size) { 478void KProcess::Exit() {
439 ASSERT(this->AllocateMainThreadStack(stack_size) == ResultSuccess); 479 // Determine whether we need to start terminating
440 m_resource_limit->Reserve(LimitableResource::ThreadCountMax, 1); 480 bool needs_terminate = false;
481 {
482 KScopedLightLock lk(m_state_lock);
483 KScopedSchedulerLock sl(m_kernel);
484
485 ASSERT(m_state != State::Created);
486 ASSERT(m_state != State::CreatedAttached);
487 ASSERT(m_state != State::Crashed);
488 ASSERT(m_state != State::Terminated);
489 if (m_state == State::Running || m_state == State::RunningAttached ||
490 m_state == State::DebugBreak) {
491 this->ChangeState(State::Terminating);
492 needs_terminate = true;
493 }
494 }
441 495
442 const std::size_t heap_capacity{m_memory_usage_capacity - 496 // If we need to start termination, do so.
443 (m_main_thread_stack_size + m_image_size)}; 497 if (needs_terminate) {
444 ASSERT(!m_page_table.SetMaxHeapSize(heap_capacity).IsError()); 498 this->StartTermination();
445 499
446 this->ChangeState(State::Running); 500 // Register the process as a work task.
501 m_kernel.WorkerTaskManager().AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit, this);
502 }
447 503
448 SetupMainThread(m_kernel.System(), *this, main_thread_priority, m_main_thread_stack_top); 504 // Exit the current thread.
505 GetCurrentThread(m_kernel).Exit();
449} 506}
450 507
451void KProcess::PrepareForTermination() { 508Result KProcess::Terminate() {
452 this->ChangeState(State::Terminating); 509 // Determine whether we need to start terminating.
510 bool needs_terminate = false;
511 {
512 KScopedLightLock lk(m_state_lock);
453 513
454 const auto stop_threads = [this](const std::vector<KThread*>& in_thread_list) { 514 // Check whether we're allowed to terminate.
455 for (auto* thread : in_thread_list) { 515 R_UNLESS(m_state != State::Created, ResultInvalidState);
456 if (thread->GetOwnerProcess() != this) 516 R_UNLESS(m_state != State::CreatedAttached, ResultInvalidState);
457 continue;
458 517
459 if (thread == GetCurrentThreadPointer(m_kernel)) 518 KScopedSchedulerLock sl(m_kernel);
460 continue;
461 519
462 // TODO(Subv): When are the other running/ready threads terminated? 520 if (m_state == State::Running || m_state == State::RunningAttached ||
463 ASSERT_MSG(thread->GetState() == ThreadState::Waiting, 521 m_state == State::Crashed || m_state == State::DebugBreak) {
464 "Exiting processes with non-waiting threads is currently unimplemented"); 522 this->ChangeState(State::Terminating);
523 needs_terminate = true;
524 }
525 }
465 526
466 thread->Exit(); 527 // If we need to terminate, do so.
528 if (needs_terminate) {
529 // Start termination.
530 if (R_SUCCEEDED(this->StartTermination())) {
531 // Finish termination.
532 this->FinishTermination();
533 } else {
534 // Register the process as a work task.
535 m_kernel.WorkerTaskManager().AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit,
536 this);
467 } 537 }
468 }; 538 }
469 539
470 stop_threads(m_kernel.System().GlobalSchedulerContext().GetThreadList()); 540 R_SUCCEED();
541}
471 542
472 this->DeleteThreadLocalRegion(m_plr_address); 543Result KProcess::AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size) {
473 m_plr_address = 0; 544 // Lock ourselves, to prevent concurrent access.
545 KScopedLightLock lk(m_state_lock);
474 546
475 if (m_resource_limit) { 547 // Try to find an existing info for the memory.
476 m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, 548 KSharedMemoryInfo* info = nullptr;
477 m_main_thread_stack_size + m_image_size); 549 for (auto it = m_shared_memory_list.begin(); it != m_shared_memory_list.end(); ++it) {
550 if (it->GetSharedMemory() == shmem) {
551 info = std::addressof(*it);
552 break;
553 }
478 } 554 }
479 555
480 this->ChangeState(State::Terminated); 556 // If we didn't find an info, create one.
481} 557 if (info == nullptr) {
558 // Allocate a new info.
559 info = KSharedMemoryInfo::Allocate(m_kernel);
560 R_UNLESS(info != nullptr, ResultOutOfResource);
482 561
483void KProcess::Finalize() { 562 // Initialize the info and add it to our list.
484 // Free all shared memory infos. 563 info->Initialize(shmem);
485 { 564 m_shared_memory_list.push_back(*info);
486 auto it = m_shared_memory_list.begin(); 565 }
487 while (it != m_shared_memory_list.end()) {
488 KSharedMemoryInfo* info = *it;
489 KSharedMemory* shmem = info->GetSharedMemory();
490 566
491 while (!info->Close()) { 567 // Open a reference to the shared memory and its info.
492 shmem->Close(); 568 shmem->Open();
493 } 569 info->Open();
494 570
495 shmem->Close(); 571 R_SUCCEED();
572}
496 573
497 it = m_shared_memory_list.erase(it); 574void KProcess::RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size) {
498 KSharedMemoryInfo::Free(m_kernel, info); 575 // Lock ourselves, to prevent concurrent access.
576 KScopedLightLock lk(m_state_lock);
577
578 // Find an existing info for the memory.
579 KSharedMemoryInfo* info = nullptr;
580 auto it = m_shared_memory_list.begin();
581 for (; it != m_shared_memory_list.end(); ++it) {
582 if (it->GetSharedMemory() == shmem) {
583 info = std::addressof(*it);
584 break;
499 } 585 }
500 } 586 }
587 ASSERT(info != nullptr);
501 588
502 // Release memory to the resource limit. 589 // Close a reference to the info and its memory.
503 if (m_resource_limit != nullptr) { 590 if (info->Close()) {
504 m_resource_limit->Close(); 591 m_shared_memory_list.erase(it);
505 m_resource_limit = nullptr; 592 KSharedMemoryInfo::Free(m_kernel, info);
506 } 593 }
507 594
508 // Finalize the page table. 595 shmem->Close();
509 m_page_table.Finalize();
510
511 // Perform inherited finalization.
512 KSynchronizationObject::Finalize();
513} 596}
514 597
515Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) { 598Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
@@ -518,7 +601,7 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
518 601
519 // See if we can get a region from a partially used TLP. 602 // See if we can get a region from a partially used TLP.
520 { 603 {
521 KScopedSchedulerLock sl{m_kernel}; 604 KScopedSchedulerLock sl(m_kernel);
522 605
523 if (auto it = m_partially_used_tlp_tree.begin(); it != m_partially_used_tlp_tree.end()) { 606 if (auto it = m_partially_used_tlp_tree.begin(); it != m_partially_used_tlp_tree.end()) {
524 tlr = it->Reserve(); 607 tlr = it->Reserve();
@@ -538,7 +621,9 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
538 // Allocate a new page. 621 // Allocate a new page.
539 tlp = KThreadLocalPage::Allocate(m_kernel); 622 tlp = KThreadLocalPage::Allocate(m_kernel);
540 R_UNLESS(tlp != nullptr, ResultOutOfMemory); 623 R_UNLESS(tlp != nullptr, ResultOutOfMemory);
541 auto tlp_guard = SCOPE_GUARD({ KThreadLocalPage::Free(m_kernel, tlp); }); 624 ON_RESULT_FAILURE {
625 KThreadLocalPage::Free(m_kernel, tlp);
626 };
542 627
543 // Initialize the new page. 628 // Initialize the new page.
544 R_TRY(tlp->Initialize(m_kernel, this)); 629 R_TRY(tlp->Initialize(m_kernel, this));
@@ -549,7 +634,7 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
549 634
550 // Insert into our tree. 635 // Insert into our tree.
551 { 636 {
552 KScopedSchedulerLock sl{m_kernel}; 637 KScopedSchedulerLock sl(m_kernel);
553 if (tlp->IsAllUsed()) { 638 if (tlp->IsAllUsed()) {
554 m_fully_used_tlp_tree.insert(*tlp); 639 m_fully_used_tlp_tree.insert(*tlp);
555 } else { 640 } else {
@@ -558,7 +643,6 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) {
558 } 643 }
559 644
560 // We succeeded! 645 // We succeeded!
561 tlp_guard.Cancel();
562 *out = tlr; 646 *out = tlr;
563 R_SUCCEED(); 647 R_SUCCEED();
564} 648}
@@ -568,7 +652,7 @@ Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) {
568 652
569 // Release the region. 653 // Release the region.
570 { 654 {
571 KScopedSchedulerLock sl{m_kernel}; 655 KScopedSchedulerLock sl(m_kernel);
572 656
573 // Try to find the page in the partially used list. 657 // Try to find the page in the partially used list.
574 auto it = m_partially_used_tlp_tree.find_key(Common::AlignDown(GetInteger(addr), PageSize)); 658 auto it = m_partially_used_tlp_tree.find_key(Common::AlignDown(GetInteger(addr), PageSize));
@@ -611,95 +695,213 @@ Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) {
611 R_SUCCEED(); 695 R_SUCCEED();
612} 696}
613 697
614bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) { 698bool KProcess::ReserveResource(Svc::LimitableResource which, s64 value) {
615 const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) { 699 if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
616 return wp.type == DebugWatchpointType::None; 700 return rl->Reserve(which, value);
617 })}; 701 } else {
702 return true;
703 }
704}
618 705
619 if (watch == m_watchpoints.end()) { 706bool KProcess::ReserveResource(Svc::LimitableResource which, s64 value, s64 timeout) {
620 return false; 707 if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
708 return rl->Reserve(which, value, timeout);
709 } else {
710 return true;
621 } 711 }
712}
622 713
623 watch->start_address = addr; 714void KProcess::ReleaseResource(Svc::LimitableResource which, s64 value) {
624 watch->end_address = addr + size; 715 if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
625 watch->type = type; 716 rl->Release(which, value);
717 }
718}
626 719
627 for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size; 720void KProcess::ReleaseResource(Svc::LimitableResource which, s64 value, s64 hint) {
628 page += PageSize) { 721 if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) {
629 m_debug_page_refcounts[page]++; 722 rl->Release(which, value, hint);
630 this->GetMemory().MarkRegionDebug(page, PageSize, true);
631 } 723 }
724}
632 725
633 return true; 726void KProcess::IncrementRunningThreadCount() {
727 ASSERT(m_num_running_threads.load() >= 0);
728
729 ++m_num_running_threads;
634} 730}
635 731
636bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) { 732void KProcess::DecrementRunningThreadCount() {
637 const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) { 733 ASSERT(m_num_running_threads.load() > 0);
638 return wp.start_address == addr && wp.end_address == addr + size && wp.type == type;
639 })};
640 734
641 if (watch == m_watchpoints.end()) { 735 if (const auto prev = m_num_running_threads--; prev == 1) {
736 this->Terminate();
737 }
738}
739
740bool KProcess::EnterUserException() {
741 // Get the current thread.
742 KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
743 ASSERT(this == cur_thread->GetOwnerProcess());
744
745 // Check that we haven't already claimed the exception thread.
746 if (m_exception_thread == cur_thread) {
642 return false; 747 return false;
643 } 748 }
644 749
645 watch->start_address = 0; 750 // Create the wait queue we'll be using.
646 watch->end_address = 0; 751 ThreadQueueImplForKProcessEnterUserException wait_queue(m_kernel,
647 watch->type = DebugWatchpointType::None; 752 std::addressof(m_exception_thread));
648 753
649 for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size; 754 // Claim the exception thread.
650 page += PageSize) { 755 {
651 m_debug_page_refcounts[page]--; 756 // Lock the scheduler.
652 if (!m_debug_page_refcounts[page]) { 757 KScopedSchedulerLock sl(m_kernel);
653 this->GetMemory().MarkRegionDebug(page, PageSize, false); 758
759 // Check that we're not terminating.
760 if (cur_thread->IsTerminationRequested()) {
761 return false;
762 }
763
764 // If we don't have an exception thread, we can just claim it directly.
765 if (m_exception_thread == nullptr) {
766 m_exception_thread = cur_thread;
767 KScheduler::SetSchedulerUpdateNeeded(m_kernel);
768 return true;
654 } 769 }
770
771 // Otherwise, we need to wait until we don't have an exception thread.
772
773 // Add the current thread as a waiter on the current exception thread.
774 cur_thread->SetKernelAddressKey(
775 reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)) | 1);
776 m_exception_thread->AddWaiter(cur_thread);
777
778 // Wait to claim the exception thread.
779 cur_thread->BeginWait(std::addressof(wait_queue));
655 } 780 }
656 781
657 return true; 782 // If our wait didn't end due to thread termination, we succeeded.
783 return ResultTerminationRequested != cur_thread->GetWaitResult();
658} 784}
659 785
660void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) { 786bool KProcess::LeaveUserException() {
661 const auto ReprotectSegment = [&](const CodeSet::Segment& segment, 787 return this->ReleaseUserException(GetCurrentThreadPointer(m_kernel));
662 Svc::MemoryPermission permission) { 788}
663 m_page_table.SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission);
664 };
665 789
666 this->GetMemory().WriteBlock(base_addr, code_set.memory.data(), code_set.memory.size()); 790bool KProcess::ReleaseUserException(KThread* thread) {
791 KScopedSchedulerLock sl(m_kernel);
667 792
668 ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute); 793 if (m_exception_thread == thread) {
669 ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read); 794 m_exception_thread = nullptr;
670 ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite); 795
796 // Remove waiter thread.
797 bool has_waiters;
798 if (KThread* next = thread->RemoveKernelWaiterByKey(
799 std::addressof(has_waiters),
800 reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)) | 1);
801 next != nullptr) {
802 next->EndWait(ResultSuccess);
803 }
804
805 KScheduler::SetSchedulerUpdateNeeded(m_kernel);
806
807 return true;
808 } else {
809 return false;
810 }
671} 811}
672 812
673bool KProcess::IsSignaled() const { 813void KProcess::RegisterThread(KThread* thread) {
674 ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); 814 KScopedLightLock lk(m_list_lock);
675 return m_is_signaled; 815
816 m_thread_list.push_back(*thread);
676} 817}
677 818
678KProcess::KProcess(KernelCore& kernel) 819void KProcess::UnregisterThread(KThread* thread) {
679 : KAutoObjectWithSlabHeapAndContainer{kernel}, m_page_table{m_kernel.System()}, 820 KScopedLightLock lk(m_list_lock);
680 m_handle_table{m_kernel}, m_address_arbiter{m_kernel.System()},
681 m_condition_var{m_kernel.System()}, m_state_lock{m_kernel}, m_list_lock{m_kernel} {}
682 821
683KProcess::~KProcess() = default; 822 m_thread_list.erase(m_thread_list.iterator_to(*thread));
823}
824
825size_t KProcess::GetUsedUserPhysicalMemorySize() const {
826 const size_t norm_size = m_page_table.GetNormalMemorySize();
827 const size_t other_size = m_code_size + m_main_thread_stack_size;
828 const size_t sec_size = this->GetRequiredSecureMemorySizeNonDefault();
684 829
685void KProcess::ChangeState(State new_state) { 830 return norm_size + other_size + sec_size;
686 if (m_state == new_state) { 831}
687 return; 832
833size_t KProcess::GetTotalUserPhysicalMemorySize() const {
834 // Get the amount of free and used size.
835 const size_t free_size =
836 m_resource_limit->GetFreeValue(Svc::LimitableResource::PhysicalMemoryMax);
837 const size_t max_size = m_max_process_memory;
838
839 // Determine used size.
840 // NOTE: This does *not* check this->IsDefaultApplicationSystemResource(), unlike
841 // GetUsedUserPhysicalMemorySize().
842 const size_t norm_size = m_page_table.GetNormalMemorySize();
843 const size_t other_size = m_code_size + m_main_thread_stack_size;
844 const size_t sec_size = this->GetRequiredSecureMemorySize();
845 const size_t used_size = norm_size + other_size + sec_size;
846
847 // NOTE: These function calls will recalculate, introducing a race...it is unclear why Nintendo
848 // does it this way.
849 if (used_size + free_size > max_size) {
850 return max_size;
851 } else {
852 return free_size + this->GetUsedUserPhysicalMemorySize();
688 } 853 }
854}
689 855
690 m_state = new_state; 856size_t KProcess::GetUsedNonSystemUserPhysicalMemorySize() const {
691 m_is_signaled = true; 857 const size_t norm_size = m_page_table.GetNormalMemorySize();
692 this->NotifyAvailable(); 858 const size_t other_size = m_code_size + m_main_thread_stack_size;
859
860 return norm_size + other_size;
861}
862
863size_t KProcess::GetTotalNonSystemUserPhysicalMemorySize() const {
864 // Get the amount of free and used size.
865 const size_t free_size =
866 m_resource_limit->GetFreeValue(Svc::LimitableResource::PhysicalMemoryMax);
867 const size_t max_size = m_max_process_memory;
868
869 // Determine used size.
870 // NOTE: This does *not* check this->IsDefaultApplicationSystemResource(), unlike
871 // GetUsedUserPhysicalMemorySize().
872 const size_t norm_size = m_page_table.GetNormalMemorySize();
873 const size_t other_size = m_code_size + m_main_thread_stack_size;
874 const size_t sec_size = this->GetRequiredSecureMemorySize();
875 const size_t used_size = norm_size + other_size + sec_size;
876
877 // NOTE: These function calls will recalculate, introducing a race...it is unclear why Nintendo
878 // does it this way.
879 if (used_size + free_size > max_size) {
880 return max_size - this->GetRequiredSecureMemorySizeNonDefault();
881 } else {
882 return free_size + this->GetUsedNonSystemUserPhysicalMemorySize();
883 }
693} 884}
694 885
695Result KProcess::AllocateMainThreadStack(std::size_t stack_size) { 886Result KProcess::Run(s32 priority, size_t stack_size) {
887 // Lock ourselves, to prevent concurrent access.
888 KScopedLightLock lk(m_state_lock);
889
890 // Validate that we're in a state where we can initialize.
891 const auto state = m_state;
892 R_UNLESS(state == State::Created || state == State::CreatedAttached, ResultInvalidState);
893
894 // Place a tentative reservation of a thread for this process.
895 KScopedResourceReservation thread_reservation(this, Svc::LimitableResource::ThreadCountMax);
896 R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached);
897
696 // Ensure that we haven't already allocated stack. 898 // Ensure that we haven't already allocated stack.
697 ASSERT(m_main_thread_stack_size == 0); 899 ASSERT(m_main_thread_stack_size == 0);
698 900
699 // Ensure that we're allocating a valid stack. 901 // Ensure that we're allocating a valid stack.
700 stack_size = Common::AlignUp(stack_size, PageSize); 902 stack_size = Common::AlignUp(stack_size, PageSize);
701 // R_UNLESS(stack_size + image_size <= m_max_process_memory, ResultOutOfMemory); 903 R_UNLESS(stack_size + m_code_size <= m_max_process_memory, ResultOutOfMemory);
702 R_UNLESS(stack_size + m_image_size >= m_image_size, ResultOutOfMemory); 904 R_UNLESS(stack_size + m_code_size >= m_code_size, ResultOutOfMemory);
703 905
704 // Place a tentative reservation of memory for our new stack. 906 // Place a tentative reservation of memory for our new stack.
705 KScopedResourceReservation mem_reservation(this, Svc::LimitableResource::PhysicalMemoryMax, 907 KScopedResourceReservation mem_reservation(this, Svc::LimitableResource::PhysicalMemoryMax,
@@ -707,21 +909,359 @@ Result KProcess::AllocateMainThreadStack(std::size_t stack_size) {
707 R_UNLESS(mem_reservation.Succeeded(), ResultLimitReached); 909 R_UNLESS(mem_reservation.Succeeded(), ResultLimitReached);
708 910
709 // Allocate and map our stack. 911 // Allocate and map our stack.
912 KProcessAddress stack_top = 0;
710 if (stack_size) { 913 if (stack_size) {
711 KProcessAddress stack_bottom; 914 KProcessAddress stack_bottom;
712 R_TRY(m_page_table.MapPages(std::addressof(stack_bottom), stack_size / PageSize, 915 R_TRY(m_page_table.MapPages(std::addressof(stack_bottom), stack_size / PageSize,
713 KMemoryState::Stack, KMemoryPermission::UserReadWrite)); 916 KMemoryState::Stack, KMemoryPermission::UserReadWrite));
714 917
715 m_main_thread_stack_top = stack_bottom + stack_size; 918 stack_top = stack_bottom + stack_size;
716 m_main_thread_stack_size = stack_size; 919 m_main_thread_stack_size = stack_size;
717 } 920 }
718 921
922 // Ensure our stack is safe to clean up on exit.
923 ON_RESULT_FAILURE {
924 if (m_main_thread_stack_size) {
925 ASSERT(R_SUCCEEDED(m_page_table.UnmapPages(stack_top - m_main_thread_stack_size,
926 m_main_thread_stack_size / PageSize,
927 KMemoryState::Stack)));
928 m_main_thread_stack_size = 0;
929 }
930 };
931
932 // Set our maximum heap size.
933 R_TRY(m_page_table.SetMaxHeapSize(m_max_process_memory -
934 (m_main_thread_stack_size + m_code_size)));
935
936 // Initialize our handle table.
937 R_TRY(this->InitializeHandleTable(m_capabilities.GetHandleTableSize()));
938 ON_RESULT_FAILURE_2 {
939 this->FinalizeHandleTable();
940 };
941
942 // Create a new thread for the process.
943 KThread* main_thread = KThread::Create(m_kernel);
944 R_UNLESS(main_thread != nullptr, ResultOutOfResource);
945 SCOPE_EXIT({ main_thread->Close(); });
946
947 // Initialize the thread.
948 R_TRY(KThread::InitializeUserThread(m_kernel.System(), main_thread, this->GetEntryPoint(), 0,
949 stack_top, priority, m_ideal_core_id, this));
950
951 // Register the thread, and commit our reservation.
952 KThread::Register(m_kernel, main_thread);
953 thread_reservation.Commit();
954
955 // Add the thread to our handle table.
956 Handle thread_handle;
957 R_TRY(m_handle_table.Add(std::addressof(thread_handle), main_thread));
958
959 // Set the thread arguments.
960 main_thread->GetContext32().cpu_registers[0] = 0;
961 main_thread->GetContext64().cpu_registers[0] = 0;
962 main_thread->GetContext32().cpu_registers[1] = thread_handle;
963 main_thread->GetContext64().cpu_registers[1] = thread_handle;
964
965 // Update our state.
966 this->ChangeState((state == State::Created) ? State::Running : State::RunningAttached);
967 ON_RESULT_FAILURE_2 {
968 this->ChangeState(state);
969 };
970
971 // Suspend for debug, if we should.
972 if (m_kernel.System().DebuggerEnabled()) {
973 main_thread->RequestSuspend(SuspendType::Debug);
974 }
975
976 // Run our thread.
977 R_TRY(main_thread->Run());
978
979 // Open a reference to represent that we're running.
980 this->Open();
981
719 // We succeeded! Commit our memory reservation. 982 // We succeeded! Commit our memory reservation.
720 mem_reservation.Commit(); 983 mem_reservation.Commit();
721 984
722 R_SUCCEED(); 985 R_SUCCEED();
723} 986}
724 987
988Result KProcess::Reset() {
989 // Lock the process and the scheduler.
990 KScopedLightLock lk(m_state_lock);
991 KScopedSchedulerLock sl(m_kernel);
992
993 // Validate that we're in a state that we can reset.
994 R_UNLESS(m_state != State::Terminated, ResultInvalidState);
995 R_UNLESS(m_is_signaled, ResultInvalidState);
996
997 // Clear signaled.
998 m_is_signaled = false;
999 R_SUCCEED();
1000}
1001
1002Result KProcess::SetActivity(Svc::ProcessActivity activity) {
1003 // Lock ourselves and the scheduler.
1004 KScopedLightLock lk(m_state_lock);
1005 KScopedLightLock list_lk(m_list_lock);
1006 KScopedSchedulerLock sl(m_kernel);
1007
1008 // Validate our state.
1009 R_UNLESS(m_state != State::Terminating, ResultInvalidState);
1010 R_UNLESS(m_state != State::Terminated, ResultInvalidState);
1011
1012 // Either pause or resume.
1013 if (activity == Svc::ProcessActivity::Paused) {
1014 // Verify that we're not suspended.
1015 R_UNLESS(!m_is_suspended, ResultInvalidState);
1016
1017 // Suspend all threads.
1018 auto end = this->GetThreadList().end();
1019 for (auto it = this->GetThreadList().begin(); it != end; ++it) {
1020 it->RequestSuspend(SuspendType::Process);
1021 }
1022
1023 // Set ourselves as suspended.
1024 this->SetSuspended(true);
1025 } else {
1026 ASSERT(activity == Svc::ProcessActivity::Runnable);
1027
1028 // Verify that we're suspended.
1029 R_UNLESS(m_is_suspended, ResultInvalidState);
1030
1031 // Resume all threads.
1032 auto end = this->GetThreadList().end();
1033 for (auto it = this->GetThreadList().begin(); it != end; ++it) {
1034 it->Resume(SuspendType::Process);
1035 }
1036
1037 // Set ourselves as resumed.
1038 this->SetSuspended(false);
1039 }
1040
1041 R_SUCCEED();
1042}
1043
1044void KProcess::PinCurrentThread() {
1045 ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
1046
1047 // Get the current thread.
1048 const s32 core_id = GetCurrentCoreId(m_kernel);
1049 KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
1050
1051 // If the thread isn't terminated, pin it.
1052 if (!cur_thread->IsTerminationRequested()) {
1053 // Pin it.
1054 this->PinThread(core_id, cur_thread);
1055 cur_thread->Pin(core_id);
1056
1057 // An update is needed.
1058 KScheduler::SetSchedulerUpdateNeeded(m_kernel);
1059 }
1060}
1061
1062void KProcess::UnpinCurrentThread() {
1063 ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
1064
1065 // Get the current thread.
1066 const s32 core_id = GetCurrentCoreId(m_kernel);
1067 KThread* cur_thread = GetCurrentThreadPointer(m_kernel);
1068
1069 // Unpin it.
1070 cur_thread->Unpin();
1071 this->UnpinThread(core_id, cur_thread);
1072
1073 // An update is needed.
1074 KScheduler::SetSchedulerUpdateNeeded(m_kernel);
1075}
1076
1077void KProcess::UnpinThread(KThread* thread) {
1078 ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
1079
1080 // Get the thread's core id.
1081 const auto core_id = thread->GetActiveCore();
1082
1083 // Unpin it.
1084 this->UnpinThread(core_id, thread);
1085 thread->Unpin();
1086
1087 // An update is needed.
1088 KScheduler::SetSchedulerUpdateNeeded(m_kernel);
1089}
1090
1091Result KProcess::GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids,
1092 s32 max_out_count) {
1093 // TODO: use current memory reference
1094 auto& memory = m_kernel.System().ApplicationMemory();
1095
1096 // Lock the list.
1097 KScopedLightLock lk(m_list_lock);
1098
1099 // Iterate over the list.
1100 s32 count = 0;
1101 auto end = this->GetThreadList().end();
1102 for (auto it = this->GetThreadList().begin(); it != end; ++it) {
1103 // If we're within array bounds, write the id.
1104 if (count < max_out_count) {
1105 // Get the thread id.
1106 KThread* thread = std::addressof(*it);
1107 const u64 id = thread->GetId();
1108
1109 // Copy the id to userland.
1110 memory.Write64(out_thread_ids + count * sizeof(u64), id);
1111 }
1112
1113 // Increment the count.
1114 ++count;
1115 }
1116
1117 // We successfully iterated the list.
1118 *out_num_threads = count;
1119 R_SUCCEED();
1120}
1121
1122void KProcess::Switch(KProcess* cur_process, KProcess* next_process) {}
1123
1124KProcess::KProcess(KernelCore& kernel)
1125 : KAutoObjectWithSlabHeapAndContainer(kernel), m_page_table{kernel.System()},
1126 m_state_lock{kernel}, m_list_lock{kernel}, m_cond_var{kernel.System()},
1127 m_address_arbiter{kernel.System()}, m_handle_table{kernel} {}
1128KProcess::~KProcess() = default;
1129
1130Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
1131 bool is_hbl) {
1132 // Create a resource limit for the process.
1133 const auto physical_memory_size =
1134 m_kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
1135 auto* res_limit =
1136 Kernel::CreateResourceLimitForProcess(m_kernel.System(), physical_memory_size);
1137
1138 // Ensure we maintain a clean state on exit.
1139 SCOPE_EXIT({ res_limit->Close(); });
1140
1141 // Declare flags and code address.
1142 Svc::CreateProcessFlag flag{};
1143 u64 code_address{};
1144
1145 // We are an application.
1146 flag |= Svc::CreateProcessFlag::IsApplication;
1147
1148 // If we are 64-bit, create as such.
1149 if (metadata.Is64BitProgram()) {
1150 flag |= Svc::CreateProcessFlag::Is64Bit;
1151 }
1152
1153 // Set the address space type and code address.
1154 switch (metadata.GetAddressSpaceType()) {
1155 case FileSys::ProgramAddressSpaceType::Is39Bit:
1156 flag |= Svc::CreateProcessFlag::AddressSpace64Bit;
1157
1158 // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
1159 // However, some (buggy) programs/libraries like skyline incorrectly depend on the
1160 // existence of ASLR pages before the entry point, so we will adjust the load address
1161 // to point to about 2GiB into the ASLR region.
1162 code_address = 0x8000'0000;
1163 break;
1164 case FileSys::ProgramAddressSpaceType::Is36Bit:
1165 flag |= Svc::CreateProcessFlag::AddressSpace64BitDeprecated;
1166 code_address = 0x800'0000;
1167 break;
1168 case FileSys::ProgramAddressSpaceType::Is32Bit:
1169 flag |= Svc::CreateProcessFlag::AddressSpace32Bit;
1170 code_address = 0x20'0000;
1171 break;
1172 case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
1173 flag |= Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias;
1174 code_address = 0x20'0000;
1175 break;
1176 }
1177
1178 Svc::CreateProcessParameter params{
1179 .name = {},
1180 .version = {},
1181 .program_id = metadata.GetTitleID(),
1182 .code_address = code_address,
1183 .code_num_pages = static_cast<s32>(code_size / PageSize),
1184 .flags = flag,
1185 .reslimit = Svc::InvalidHandle,
1186 .system_resource_num_pages = static_cast<s32>(metadata.GetSystemResourceSize() / PageSize),
1187 };
1188
1189 // Set the process name.
1190 const auto& name = metadata.GetName();
1191 static_assert(sizeof(params.name) <= sizeof(name));
1192 std::memcpy(params.name.data(), name.data(), sizeof(params.name));
1193
1194 // Initialize for application process.
1195 R_TRY(this->Initialize(params, metadata.GetKernelCapabilities(), res_limit,
1196 KMemoryManager::Pool::Application));
1197
1198 // Assign remaining properties.
1199 m_is_hbl = is_hbl;
1200 m_ideal_core_id = metadata.GetMainThreadCore();
1201
1202 // We succeeded.
1203 R_SUCCEED();
1204}
1205
1206void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
1207 const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
1208 Svc::MemoryPermission permission) {
1209 m_page_table.SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission);
1210 };
1211
1212 this->GetMemory().WriteBlock(base_addr, code_set.memory.data(), code_set.memory.size());
1213
1214 ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute);
1215 ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read);
1216 ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
1217}
1218
1219bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
1220 const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) {
1221 return wp.type == DebugWatchpointType::None;
1222 })};
1223
1224 if (watch == m_watchpoints.end()) {
1225 return false;
1226 }
1227
1228 watch->start_address = addr;
1229 watch->end_address = addr + size;
1230 watch->type = type;
1231
1232 for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size;
1233 page += PageSize) {
1234 m_debug_page_refcounts[page]++;
1235 this->GetMemory().MarkRegionDebug(page, PageSize, true);
1236 }
1237
1238 return true;
1239}
1240
1241bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
1242 const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) {
1243 return wp.start_address == addr && wp.end_address == addr + size && wp.type == type;
1244 })};
1245
1246 if (watch == m_watchpoints.end()) {
1247 return false;
1248 }
1249
1250 watch->start_address = 0;
1251 watch->end_address = 0;
1252 watch->type = DebugWatchpointType::None;
1253
1254 for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size;
1255 page += PageSize) {
1256 m_debug_page_refcounts[page]--;
1257 if (!m_debug_page_refcounts[page]) {
1258 this->GetMemory().MarkRegionDebug(page, PageSize, false);
1259 }
1260 }
1261
1262 return true;
1263}
1264
725Core::Memory::Memory& KProcess::GetMemory() const { 1265Core::Memory::Memory& KProcess::GetMemory() const {
726 // TODO: per-process memory 1266 // TODO: per-process memory
727 return m_kernel.System().ApplicationMemory(); 1267 return m_kernel.System().ApplicationMemory();
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 146e07a57..f9f755afa 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -1,59 +1,23 @@
1// SPDX-FileCopyrightText: 2015 Citra Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#pragma once 4#pragma once
5 5
6#include <array>
7#include <cstddef>
8#include <list>
9#include <map> 6#include <map>
10#include <string> 7
8#include "core/hle/kernel/code_set.h"
11#include "core/hle/kernel/k_address_arbiter.h" 9#include "core/hle/kernel/k_address_arbiter.h"
12#include "core/hle/kernel/k_auto_object.h" 10#include "core/hle/kernel/k_capabilities.h"
13#include "core/hle/kernel/k_condition_variable.h" 11#include "core/hle/kernel/k_condition_variable.h"
14#include "core/hle/kernel/k_handle_table.h" 12#include "core/hle/kernel/k_handle_table.h"
15#include "core/hle/kernel/k_page_table.h" 13#include "core/hle/kernel/k_page_table.h"
16#include "core/hle/kernel/k_synchronization_object.h" 14#include "core/hle/kernel/k_page_table_manager.h"
15#include "core/hle/kernel/k_system_resource.h"
16#include "core/hle/kernel/k_thread.h"
17#include "core/hle/kernel/k_thread_local_page.h" 17#include "core/hle/kernel/k_thread_local_page.h"
18#include "core/hle/kernel/k_typed_address.h"
19#include "core/hle/kernel/k_worker_task.h"
20#include "core/hle/kernel/process_capability.h"
21#include "core/hle/kernel/slab_helpers.h"
22#include "core/hle/result.h"
23
24namespace Core {
25namespace Memory {
26class Memory;
27};
28
29class System;
30} // namespace Core
31
32namespace FileSys {
33class ProgramMetadata;
34}
35 18
36namespace Kernel { 19namespace Kernel {
37 20
38class KernelCore;
39class KResourceLimit;
40class KThread;
41class KSharedMemoryInfo;
42class TLSPage;
43
44struct CodeSet;
45
46enum class MemoryRegion : u16 {
47 APPLICATION = 1,
48 SYSTEM = 2,
49 BASE = 3,
50};
51
52enum class ProcessActivity : u32 {
53 Runnable,
54 Paused,
55};
56
57enum class DebugWatchpointType : u8 { 21enum class DebugWatchpointType : u8 {
58 None = 0, 22 None = 0,
59 Read = 1 << 0, 23 Read = 1 << 0,
@@ -72,9 +36,6 @@ class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWor
72 KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject); 36 KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject);
73 37
74public: 38public:
75 explicit KProcess(KernelCore& kernel);
76 ~KProcess() override;
77
78 enum class State { 39 enum class State {
79 Created = static_cast<u32>(Svc::ProcessState::Created), 40 Created = static_cast<u32>(Svc::ProcessState::Created),
80 CreatedAttached = static_cast<u32>(Svc::ProcessState::CreatedAttached), 41 CreatedAttached = static_cast<u32>(Svc::ProcessState::CreatedAttached),
@@ -86,470 +47,493 @@ public:
86 DebugBreak = static_cast<u32>(Svc::ProcessState::DebugBreak), 47 DebugBreak = static_cast<u32>(Svc::ProcessState::DebugBreak),
87 }; 48 };
88 49
89 enum : u64 { 50 using ThreadList = Common::IntrusiveListMemberTraits<&KThread::m_process_list_node>::ListType;
90 /// Lowest allowed process ID for a kernel initial process.
91 InitialKIPIDMin = 1,
92 /// Highest allowed process ID for a kernel initial process.
93 InitialKIPIDMax = 80,
94
95 /// Lowest allowed process ID for a userland process.
96 ProcessIDMin = 81,
97 /// Highest allowed process ID for a userland process.
98 ProcessIDMax = 0xFFFFFFFFFFFFFFFF,
99 };
100 51
101 // Used to determine how process IDs are assigned. 52 static constexpr size_t AslrAlignment = 2_MiB;
102 enum class ProcessType {
103 KernelInternal,
104 Userland,
105 };
106 53
107 static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4; 54public:
55 static constexpr u64 InitialProcessIdMin = 1;
56 static constexpr u64 InitialProcessIdMax = 0x50;
108 57
109 static Result Initialize(KProcess* process, Core::System& system, std::string process_name, 58 static constexpr u64 ProcessIdMin = InitialProcessIdMax + 1;
110 ProcessType type, KResourceLimit* res_limit); 59 static constexpr u64 ProcessIdMax = std::numeric_limits<u64>::max();
111 60
112 /// Gets a reference to the process' page table. 61private:
113 KPageTable& GetPageTable() { 62 using SharedMemoryInfoList = Common::IntrusiveListBaseTraits<KSharedMemoryInfo>::ListType;
114 return m_page_table; 63 using TLPTree =
115 } 64 Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
65 using TLPIterator = TLPTree::iterator;
116 66
117 /// Gets const a reference to the process' page table. 67private:
118 const KPageTable& GetPageTable() const { 68 KPageTable m_page_table;
119 return m_page_table; 69 std::atomic<size_t> m_used_kernel_memory_size{};
120 } 70 TLPTree m_fully_used_tlp_tree{};
71 TLPTree m_partially_used_tlp_tree{};
72 s32 m_ideal_core_id{};
73 KResourceLimit* m_resource_limit{};
74 KSystemResource* m_system_resource{};
75 size_t m_memory_release_hint{};
76 State m_state{};
77 KLightLock m_state_lock;
78 KLightLock m_list_lock;
79 KConditionVariable m_cond_var;
80 KAddressArbiter m_address_arbiter;
81 std::array<u64, 4> m_entropy{};
82 bool m_is_signaled{};
83 bool m_is_initialized{};
84 bool m_is_application{};
85 bool m_is_default_application_system_resource{};
86 bool m_is_hbl{};
87 std::array<char, 13> m_name{};
88 std::atomic<u16> m_num_running_threads{};
89 Svc::CreateProcessFlag m_flags{};
90 KMemoryManager::Pool m_memory_pool{};
91 s64 m_schedule_count{};
92 KCapabilities m_capabilities{};
93 u64 m_program_id{};
94 u64 m_process_id{};
95 KProcessAddress m_code_address{};
96 size_t m_code_size{};
97 size_t m_main_thread_stack_size{};
98 size_t m_max_process_memory{};
99 u32 m_version{};
100 KHandleTable m_handle_table;
101 KProcessAddress m_plr_address{};
102 KThread* m_exception_thread{};
103 ThreadList m_thread_list{};
104 SharedMemoryInfoList m_shared_memory_list{};
105 bool m_is_suspended{};
106 bool m_is_immortal{};
107 bool m_is_handle_table_initialized{};
108 std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_running_threads{};
109 std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_idle_counts{};
110 std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_switch_counts{};
111 std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_pinned_threads{};
112 std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{};
113 std::map<KProcessAddress, u64> m_debug_page_refcounts{};
114 std::atomic<s64> m_cpu_time{};
115 std::atomic<s64> m_num_process_switches{};
116 std::atomic<s64> m_num_thread_switches{};
117 std::atomic<s64> m_num_fpu_switches{};
118 std::atomic<s64> m_num_supervisor_calls{};
119 std::atomic<s64> m_num_ipc_messages{};
120 std::atomic<s64> m_num_ipc_replies{};
121 std::atomic<s64> m_num_ipc_receives{};
121 122
122 /// Gets a reference to the process' handle table. 123private:
123 KHandleTable& GetHandleTable() { 124 Result StartTermination();
124 return m_handle_table; 125 void FinishTermination();
125 }
126 126
127 /// Gets a const reference to the process' handle table. 127 void PinThread(s32 core_id, KThread* thread) {
128 const KHandleTable& GetHandleTable() const { 128 ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
129 return m_handle_table; 129 ASSERT(thread != nullptr);
130 ASSERT(m_pinned_threads[core_id] == nullptr);
131 m_pinned_threads[core_id] = thread;
130 } 132 }
131 133
132 /// Gets a reference to process's memory. 134 void UnpinThread(s32 core_id, KThread* thread) {
133 Core::Memory::Memory& GetMemory() const; 135 ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
134 136 ASSERT(thread != nullptr);
135 Result SignalToAddress(KProcessAddress address) { 137 ASSERT(m_pinned_threads[core_id] == thread);
136 return m_condition_var.SignalToAddress(address); 138 m_pinned_threads[core_id] = nullptr;
137 } 139 }
138 140
139 Result WaitForAddress(Handle handle, KProcessAddress address, u32 tag) { 141public:
140 return m_condition_var.WaitForAddress(handle, address, tag); 142 explicit KProcess(KernelCore& kernel);
141 } 143 ~KProcess() override;
142 144
143 void SignalConditionVariable(u64 cv_key, int32_t count) { 145 Result Initialize(const Svc::CreateProcessParameter& params, KResourceLimit* res_limit,
144 return m_condition_var.Signal(cv_key, count); 146 bool is_real);
145 }
146 147
147 Result WaitConditionVariable(KProcessAddress address, u64 cv_key, u32 tag, s64 ns) { 148 Result Initialize(const Svc::CreateProcessParameter& params, const KPageGroup& pg,
148 R_RETURN(m_condition_var.Wait(address, cv_key, tag, ns)); 149 std::span<const u32> caps, KResourceLimit* res_limit,
149 } 150 KMemoryManager::Pool pool, bool immortal);
151 Result Initialize(const Svc::CreateProcessParameter& params, std::span<const u32> user_caps,
152 KResourceLimit* res_limit, KMemoryManager::Pool pool);
153 void Exit();
150 154
151 Result SignalAddressArbiter(uint64_t address, Svc::SignalType signal_type, s32 value, 155 const char* GetName() const {
152 s32 count) { 156 return m_name.data();
153 R_RETURN(m_address_arbiter.SignalToAddress(address, signal_type, value, count));
154 } 157 }
155 158
156 Result WaitAddressArbiter(uint64_t address, Svc::ArbitrationType arb_type, s32 value, 159 u64 GetProgramId() const {
157 s64 timeout) { 160 return m_program_id;
158 R_RETURN(m_address_arbiter.WaitForAddress(address, arb_type, value, timeout));
159 } 161 }
160 162
161 KProcessAddress GetProcessLocalRegionAddress() const { 163 u64 GetProcessId() const {
162 return m_plr_address; 164 return m_process_id;
163 } 165 }
164 166
165 /// Gets the current status of the process
166 State GetState() const { 167 State GetState() const {
167 return m_state; 168 return m_state;
168 } 169 }
169 170
170 /// Gets the unique ID that identifies this particular process. 171 u64 GetCoreMask() const {
171 u64 GetProcessId() const { 172 return m_capabilities.GetCoreMask();
172 return m_process_id; 173 }
174 u64 GetPhysicalCoreMask() const {
175 return m_capabilities.GetPhysicalCoreMask();
176 }
177 u64 GetPriorityMask() const {
178 return m_capabilities.GetPriorityMask();
173 } 179 }
174 180
175 /// Gets the program ID corresponding to this process. 181 s32 GetIdealCoreId() const {
176 u64 GetProgramId() const { 182 return m_ideal_core_id;
177 return m_program_id; 183 }
184 void SetIdealCoreId(s32 core_id) {
185 m_ideal_core_id = core_id;
178 } 186 }
179 187
180 KProcessAddress GetEntryPoint() const { 188 bool CheckThreadPriority(s32 prio) const {
181 return m_code_address; 189 return ((1ULL << prio) & this->GetPriorityMask()) != 0;
182 } 190 }
183 191
184 /// Gets the resource limit descriptor for this process 192 u32 GetCreateProcessFlags() const {
185 KResourceLimit* GetResourceLimit() const; 193 return static_cast<u32>(m_flags);
194 }
186 195
187 /// Gets the ideal CPU core ID for this process 196 bool Is64Bit() const {
188 u8 GetIdealCoreId() const { 197 return True(m_flags & Svc::CreateProcessFlag::Is64Bit);
189 return m_ideal_core;
190 } 198 }
191 199
192 /// Checks if the specified thread priority is valid. 200 KProcessAddress GetEntryPoint() const {
193 bool CheckThreadPriority(s32 prio) const { 201 return m_code_address;
194 return ((1ULL << prio) & GetPriorityMask()) != 0;
195 } 202 }
196 203
197 /// Gets the bitmask of allowed cores that this process' threads can run on. 204 size_t GetMainStackSize() const {
198 u64 GetCoreMask() const { 205 return m_main_thread_stack_size;
199 return m_capabilities.GetCoreMask();
200 } 206 }
201 207
202 /// Gets the bitmask of allowed thread priorities. 208 KMemoryManager::Pool GetMemoryPool() const {
203 u64 GetPriorityMask() const { 209 return m_memory_pool;
204 return m_capabilities.GetPriorityMask();
205 } 210 }
206 211
207 /// Gets the amount of secure memory to allocate for memory management. 212 u64 GetRandomEntropy(size_t i) const {
208 u32 GetSystemResourceSize() const { 213 return m_entropy[i];
209 return m_system_resource_size;
210 } 214 }
211 215
212 /// Gets the amount of secure memory currently in use for memory management. 216 bool IsApplication() const {
213 u32 GetSystemResourceUsage() const { 217 return m_is_application;
214 // On hardware, this returns the amount of system resource memory that has
215 // been used by the kernel. This is problematic for Yuzu to emulate, because
216 // system resource memory is used for page tables -- and yuzu doesn't really
217 // have a way to calculate how much memory is required for page tables for
218 // the current process at any given time.
219 // TODO: Is this even worth implementing? Games may retrieve this value via
220 // an SDK function that gets used + available system resource size for debug
221 // or diagnostic purposes. However, it seems unlikely that a game would make
222 // decisions based on how much system memory is dedicated to its page tables.
223 // Is returning a value other than zero wise?
224 return 0;
225 } 218 }
226 219
227 /// Whether this process is an AArch64 or AArch32 process. 220 bool IsDefaultApplicationSystemResource() const {
228 bool Is64BitProcess() const { 221 return m_is_default_application_system_resource;
229 return m_is_64bit_process;
230 } 222 }
231 223
232 bool IsSuspended() const { 224 bool IsSuspended() const {
233 return m_is_suspended; 225 return m_is_suspended;
234 } 226 }
235
236 void SetSuspended(bool suspended) { 227 void SetSuspended(bool suspended) {
237 m_is_suspended = suspended; 228 m_is_suspended = suspended;
238 } 229 }
239 230
240 /// Gets the total running time of the process instance in ticks. 231 Result Terminate();
241 u64 GetCPUTimeTicks() const { 232
242 return m_total_process_running_time_ticks; 233 bool IsTerminated() const {
234 return m_state == State::Terminated;
243 } 235 }
244 236
245 /// Updates the total running time, adding the given ticks to it. 237 bool IsPermittedSvc(u32 svc_id) const {
246 void UpdateCPUTimeTicks(u64 ticks) { 238 return m_capabilities.IsPermittedSvc(svc_id);
247 m_total_process_running_time_ticks += ticks;
248 } 239 }
249 240
250 /// Gets the process schedule count, used for thread yielding 241 bool IsPermittedInterrupt(s32 interrupt_id) const {
251 s64 GetScheduledCount() const { 242 return m_capabilities.IsPermittedInterrupt(interrupt_id);
252 return m_schedule_count;
253 } 243 }
254 244
255 /// Increments the process schedule count, used for thread yielding. 245 bool IsPermittedDebug() const {
256 void IncrementScheduledCount() { 246 return m_capabilities.IsPermittedDebug();
257 ++m_schedule_count;
258 } 247 }
259 248
260 void IncrementRunningThreadCount(); 249 bool CanForceDebug() const {
261 void DecrementRunningThreadCount(); 250 return m_capabilities.CanForceDebug();
251 }
262 252
263 void SetRunningThread(s32 core, KThread* thread, u64 idle_count) { 253 bool IsHbl() const {
264 m_running_threads[core] = thread; 254 return m_is_hbl;
265 m_running_thread_idle_counts[core] = idle_count;
266 } 255 }
267 256
268 void ClearRunningThread(KThread* thread) { 257 Kernel::KMemoryManager::Direction GetAllocateOption() const {
269 for (size_t i = 0; i < m_running_threads.size(); ++i) { 258 // TODO: property of the KPageTableBase
270 if (m_running_threads[i] == thread) { 259 return KMemoryManager::Direction::FromFront;
271 m_running_threads[i] = nullptr;
272 }
273 }
274 } 260 }
275 261
276 [[nodiscard]] KThread* GetRunningThread(s32 core) const { 262 ThreadList& GetThreadList() {
277 return m_running_threads[core]; 263 return m_thread_list;
264 }
265 const ThreadList& GetThreadList() const {
266 return m_thread_list;
278 } 267 }
279 268
269 bool EnterUserException();
270 bool LeaveUserException();
280 bool ReleaseUserException(KThread* thread); 271 bool ReleaseUserException(KThread* thread);
281 272
282 [[nodiscard]] KThread* GetPinnedThread(s32 core_id) const { 273 KThread* GetPinnedThread(s32 core_id) const {
283 ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); 274 ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
284 return m_pinned_threads[core_id]; 275 return m_pinned_threads[core_id];
285 } 276 }
286 277
287 /// Gets 8 bytes of random data for svcGetInfo RandomEntropy 278 const Svc::SvcAccessFlagSet& GetSvcPermissions() const {
288 u64 GetRandomEntropy(std::size_t index) const { 279 return m_capabilities.GetSvcPermissions();
289 return m_random_entropy.at(index);
290 } 280 }
291 281
292 /// Retrieves the total physical memory available to this process in bytes. 282 KResourceLimit* GetResourceLimit() const {
293 u64 GetTotalPhysicalMemoryAvailable(); 283 return m_resource_limit;
294
295 /// Retrieves the total physical memory available to this process in bytes,
296 /// without the size of the personal system resource heap added to it.
297 u64 GetTotalPhysicalMemoryAvailableWithoutSystemResource();
298
299 /// Retrieves the total physical memory used by this process in bytes.
300 u64 GetTotalPhysicalMemoryUsed();
301
302 /// Retrieves the total physical memory used by this process in bytes,
303 /// without the size of the personal system resource heap added to it.
304 u64 GetTotalPhysicalMemoryUsedWithoutSystemResource();
305
306 /// Gets the list of all threads created with this process as their owner.
307 std::list<KThread*>& GetThreadList() {
308 return m_thread_list;
309 } 284 }
310 285
311 /// Registers a thread as being created under this process, 286 bool ReserveResource(Svc::LimitableResource which, s64 value);
312 /// adding it to this process' thread list. 287 bool ReserveResource(Svc::LimitableResource which, s64 value, s64 timeout);
313 void RegisterThread(KThread* thread); 288 void ReleaseResource(Svc::LimitableResource which, s64 value);
289 void ReleaseResource(Svc::LimitableResource which, s64 value, s64 hint);
314 290
315 /// Unregisters a thread from this process, removing it 291 KLightLock& GetStateLock() {
316 /// from this process' thread list. 292 return m_state_lock;
317 void UnregisterThread(KThread* thread); 293 }
294 KLightLock& GetListLock() {
295 return m_list_lock;
296 }
318 297
319 /// Retrieves the number of available threads for this process. 298 KPageTable& GetPageTable() {
320 u64 GetFreeThreadCount() const; 299 return m_page_table;
321 300 }
322 /// Clears the signaled state of the process if and only if it's signaled. 301 const KPageTable& GetPageTable() const {
323 /// 302 return m_page_table;
324 /// @pre The process must not be already terminated. If this is called on a 303 }
325 /// terminated process, then ResultInvalidState will be returned.
326 ///
327 /// @pre The process must be in a signaled state. If this is called on a
328 /// process instance that is not signaled, ResultInvalidState will be
329 /// returned.
330 Result Reset();
331 304
332 /** 305 KHandleTable& GetHandleTable() {
333 * Loads process-specifics configuration info with metadata provided 306 return m_handle_table;
334 * by an executable. 307 }
335 * 308 const KHandleTable& GetHandleTable() const {
336 * @param metadata The provided metadata to load process specific info from. 309 return m_handle_table;
337 * 310 }
338 * @returns ResultSuccess if all relevant metadata was able to be
339 * loaded and parsed. Otherwise, an error code is returned.
340 */
341 Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
342 bool is_hbl);
343 311
344 /** 312 size_t GetUsedUserPhysicalMemorySize() const;
345 * Starts the main application thread for this process. 313 size_t GetTotalUserPhysicalMemorySize() const;
346 * 314 size_t GetUsedNonSystemUserPhysicalMemorySize() const;
347 * @param main_thread_priority The priority for the main thread. 315 size_t GetTotalNonSystemUserPhysicalMemorySize() const;
348 * @param stack_size The stack size for the main thread in bytes.
349 */
350 void Run(s32 main_thread_priority, u64 stack_size);
351 316
352 /** 317 Result AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size);
353 * Prepares a process for termination by stopping all of its threads 318 void RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size);
354 * and clearing any other resources.
355 */
356 void PrepareForTermination();
357 319
358 void LoadModule(CodeSet code_set, KProcessAddress base_addr); 320 Result CreateThreadLocalRegion(KProcessAddress* out);
321 Result DeleteThreadLocalRegion(KProcessAddress addr);
359 322
360 bool IsInitialized() const override { 323 KProcessAddress GetProcessLocalRegionAddress() const {
361 return m_is_initialized; 324 return m_plr_address;
362 } 325 }
363 326
364 static void PostDestroy(uintptr_t arg) {} 327 KThread* GetExceptionThread() const {
365 328 return m_exception_thread;
366 void Finalize() override;
367
368 u64 GetId() const override {
369 return GetProcessId();
370 } 329 }
371 330
372 bool IsHbl() const { 331 void AddCpuTime(s64 diff) {
373 return m_is_hbl; 332 m_cpu_time += diff;
333 }
334 s64 GetCpuTime() {
335 return m_cpu_time.load();
374 } 336 }
375 337
376 bool IsSignaled() const override; 338 s64 GetScheduledCount() const {
377 339 return m_schedule_count;
378 void DoWorkerTaskImpl(); 340 }
341 void IncrementScheduledCount() {
342 ++m_schedule_count;
343 }
379 344
380 Result SetActivity(ProcessActivity activity); 345 void IncrementRunningThreadCount();
346 void DecrementRunningThreadCount();
381 347
382 void PinCurrentThread(s32 core_id); 348 size_t GetRequiredSecureMemorySizeNonDefault() const {
383 void UnpinCurrentThread(s32 core_id); 349 if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) {
384 void UnpinThread(KThread* thread); 350 auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
351 return secure_system_resource->CalculateRequiredSecureMemorySize();
352 }
385 353
386 KLightLock& GetStateLock() { 354 return 0;
387 return m_state_lock;
388 } 355 }
389 356
390 Result AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size); 357 size_t GetRequiredSecureMemorySize() const {
391 void RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size); 358 if (m_system_resource->IsSecureResource()) {
392 359 auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
393 /////////////////////////////////////////////////////////////////////////////////////////////// 360 return secure_system_resource->CalculateRequiredSecureMemorySize();
394 // Thread-local storage management 361 }
395
396 // Marks the next available region as used and returns the address of the slot.
397 [[nodiscard]] Result CreateThreadLocalRegion(KProcessAddress* out);
398 362
399 // Frees a used TLS slot identified by the given address 363 return 0;
400 Result DeleteThreadLocalRegion(KProcessAddress addr); 364 }
401 365
402 /////////////////////////////////////////////////////////////////////////////////////////////// 366 size_t GetTotalSystemResourceSize() const {
403 // Debug watchpoint management 367 if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) {
368 auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
369 return secure_system_resource->GetSize();
370 }
404 371
405 // Attempts to insert a watchpoint into a free slot. Returns false if none are available. 372 return 0;
406 bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type); 373 }
407 374
408 // Attempts to remove the watchpoint specified by the given parameters. 375 size_t GetUsedSystemResourceSize() const {
409 bool RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type); 376 if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) {
377 auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource);
378 return secure_system_resource->GetUsedSize();
379 }
410 380
411 const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const { 381 return 0;
412 return m_watchpoints;
413 } 382 }
414 383
415 const std::string& GetName() { 384 void SetRunningThread(s32 core, KThread* thread, u64 idle_count, u64 switch_count) {
416 return name; 385 m_running_threads[core] = thread;
386 m_running_thread_idle_counts[core] = idle_count;
387 m_running_thread_switch_counts[core] = switch_count;
417 } 388 }
418 389
419private: 390 void ClearRunningThread(KThread* thread) {
420 void PinThread(s32 core_id, KThread* thread) { 391 for (size_t i = 0; i < m_running_threads.size(); ++i) {
421 ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); 392 if (m_running_threads[i] == thread) {
422 ASSERT(thread != nullptr); 393 m_running_threads[i] = nullptr;
423 ASSERT(m_pinned_threads[core_id] == nullptr); 394 }
424 m_pinned_threads[core_id] = thread; 395 }
425 } 396 }
426 397
427 void UnpinThread(s32 core_id, KThread* thread) { 398 const KSystemResource& GetSystemResource() const {
428 ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); 399 return *m_system_resource;
429 ASSERT(thread != nullptr);
430 ASSERT(m_pinned_threads[core_id] == thread);
431 m_pinned_threads[core_id] = nullptr;
432 } 400 }
433 401
434 void FinalizeHandleTable() { 402 const KMemoryBlockSlabManager& GetMemoryBlockSlabManager() const {
435 // Finalize the table. 403 return m_system_resource->GetMemoryBlockSlabManager();
436 m_handle_table.Finalize(); 404 }
437 405 const KBlockInfoManager& GetBlockInfoManager() const {
438 // Note that the table is finalized. 406 return m_system_resource->GetBlockInfoManager();
439 m_is_handle_table_initialized = false; 407 }
408 const KPageTableManager& GetPageTableManager() const {
409 return m_system_resource->GetPageTableManager();
440 } 410 }
441 411
442 void ChangeState(State new_state); 412 KThread* GetRunningThread(s32 core) const {
443 413 return m_running_threads[core];
444 /// Allocates the main thread stack for the process, given the stack size in bytes. 414 }
445 Result AllocateMainThreadStack(std::size_t stack_size); 415 u64 GetRunningThreadIdleCount(s32 core) const {
446 416 return m_running_thread_idle_counts[core];
447 /// Memory manager for this process 417 }
448 KPageTable m_page_table; 418 u64 GetRunningThreadSwitchCount(s32 core) const {
449 419 return m_running_thread_switch_counts[core];
450 /// Current status of the process 420 }
451 State m_state{};
452 421
453 /// The ID of this process 422 void RegisterThread(KThread* thread);
454 u64 m_process_id = 0; 423 void UnregisterThread(KThread* thread);
455 424
456 /// Title ID corresponding to the process 425 Result Run(s32 priority, size_t stack_size);
457 u64 m_program_id = 0;
458 426
459 /// Specifies additional memory to be reserved for the process's memory management by the 427 Result Reset();
460 /// system. When this is non-zero, secure memory is allocated and used for page table allocation
461 /// instead of using the normal global page tables/memory block management.
462 u32 m_system_resource_size = 0;
463 428
464 /// Resource limit descriptor for this process 429 void SetDebugBreak() {
465 KResourceLimit* m_resource_limit{}; 430 if (m_state == State::RunningAttached) {
431 this->ChangeState(State::DebugBreak);
432 }
433 }
466 434
467 KVirtualAddress m_system_resource_address{}; 435 void SetAttached() {
436 if (m_state == State::DebugBreak) {
437 this->ChangeState(State::RunningAttached);
438 }
439 }
468 440
469 /// The ideal CPU core for this process, threads are scheduled on this core by default. 441 Result SetActivity(Svc::ProcessActivity activity);
470 u8 m_ideal_core = 0;
471 442
472 /// Contains the parsed process capability descriptors. 443 void PinCurrentThread();
473 ProcessCapabilities m_capabilities; 444 void UnpinCurrentThread();
445 void UnpinThread(KThread* thread);
474 446
475 /// Whether or not this process is AArch64, or AArch32. 447 void SignalConditionVariable(uintptr_t cv_key, int32_t count) {
476 /// By default, we currently assume this is true, unless otherwise 448 return m_cond_var.Signal(cv_key, count);
477 /// specified by metadata provided to the process during loading. 449 }
478 bool m_is_64bit_process = true;
479 450
480 /// Total running time for the process in ticks. 451 Result WaitConditionVariable(KProcessAddress address, uintptr_t cv_key, u32 tag, s64 ns) {
481 std::atomic<u64> m_total_process_running_time_ticks = 0; 452 R_RETURN(m_cond_var.Wait(address, cv_key, tag, ns));
453 }
482 454
483 /// Per-process handle table for storing created object handles in. 455 Result SignalAddressArbiter(uintptr_t address, Svc::SignalType signal_type, s32 value,
484 KHandleTable m_handle_table; 456 s32 count) {
457 R_RETURN(m_address_arbiter.SignalToAddress(address, signal_type, value, count));
458 }
485 459
486 /// Per-process address arbiter. 460 Result WaitAddressArbiter(uintptr_t address, Svc::ArbitrationType arb_type, s32 value,
487 KAddressArbiter m_address_arbiter; 461 s64 timeout) {
462 R_RETURN(m_address_arbiter.WaitForAddress(address, arb_type, value, timeout));
463 }
488 464
489 /// The per-process mutex lock instance used for handling various 465 Result GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids, s32 max_out_count);
490 /// forms of services, such as lock arbitration, and condition
491 /// variable related facilities.
492 KConditionVariable m_condition_var;
493 466
494 /// Address indicating the location of the process' dedicated TLS region. 467 static void Switch(KProcess* cur_process, KProcess* next_process);
495 KProcessAddress m_plr_address = 0;
496 468
497 /// Address indicating the location of the process's entry point. 469public:
498 KProcessAddress m_code_address = 0; 470 // Attempts to insert a watchpoint into a free slot. Returns false if none are available.
471 bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
499 472
500 /// Random values for svcGetInfo RandomEntropy 473 // Attempts to remove the watchpoint specified by the given parameters.
501 std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{}; 474 bool RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
502 475
503 /// List of threads that are running with this process as their owner. 476 const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const {
504 std::list<KThread*> m_thread_list; 477 return m_watchpoints;
478 }
505 479
506 /// List of shared memory that are running with this process as their owner. 480public:
507 std::list<KSharedMemoryInfo*> m_shared_memory_list; 481 Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
482 bool is_hbl);
508 483
509 /// Address of the top of the main thread's stack 484 void LoadModule(CodeSet code_set, KProcessAddress base_addr);
510 KProcessAddress m_main_thread_stack_top{};
511 485
512 /// Size of the main thread's stack 486 Core::Memory::Memory& GetMemory() const;
513 std::size_t m_main_thread_stack_size{};
514 487
515 /// Memory usage capacity for the process 488public:
516 std::size_t m_memory_usage_capacity{}; 489 // Overridden parent functions.
490 bool IsInitialized() const override {
491 return m_is_initialized;
492 }
517 493
518 /// Process total image size 494 static void PostDestroy(uintptr_t arg) {}
519 std::size_t m_image_size{};
520 495
521 /// Schedule count of this process 496 void Finalize() override;
522 s64 m_schedule_count{};
523 497
524 size_t m_memory_release_hint{}; 498 u64 GetIdImpl() const {
499 return this->GetProcessId();
500 }
501 u64 GetId() const override {
502 return this->GetIdImpl();
503 }
525 504
526 std::string name{}; 505 virtual bool IsSignaled() const override {
506 ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel));
507 return m_is_signaled;
508 }
527 509
528 bool m_is_signaled{}; 510 void DoWorkerTaskImpl();
529 bool m_is_suspended{};
530 bool m_is_immortal{};
531 bool m_is_handle_table_initialized{};
532 bool m_is_initialized{};
533 bool m_is_hbl{};
534 511
535 std::atomic<u16> m_num_running_threads{}; 512private:
513 void ChangeState(State new_state) {
514 if (m_state != new_state) {
515 m_state = new_state;
516 m_is_signaled = true;
517 this->NotifyAvailable();
518 }
519 }
536 520
537 std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_running_threads{}; 521 Result InitializeHandleTable(s32 size) {
538 std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_idle_counts{}; 522 // Try to initialize the handle table.
539 std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_pinned_threads{}; 523 R_TRY(m_handle_table.Initialize(size));
540 std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{};
541 std::map<KProcessAddress, u64> m_debug_page_refcounts;
542 524
543 KThread* m_exception_thread{}; 525 // We succeeded, so note that we did.
526 m_is_handle_table_initialized = true;
527 R_SUCCEED();
528 }
544 529
545 KLightLock m_state_lock; 530 void FinalizeHandleTable() {
546 KLightLock m_list_lock; 531 // Finalize the table.
532 m_handle_table.Finalize();
547 533
548 using TLPTree = 534 // Note that the table is finalized.
549 Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>; 535 m_is_handle_table_initialized = false;
550 using TLPIterator = TLPTree::iterator; 536 }
551 TLPTree m_fully_used_tlp_tree;
552 TLPTree m_partially_used_tlp_tree;
553}; 537};
554 538
555} // namespace Kernel 539} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp
index d8143c650..1bce63a56 100644
--- a/src/core/hle/kernel/k_scheduler.cpp
+++ b/src/core/hle/kernel/k_scheduler.cpp
@@ -190,7 +190,7 @@ u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) {
190 if (m_state.should_count_idle) { 190 if (m_state.should_count_idle) {
191 if (highest_thread != nullptr) [[likely]] { 191 if (highest_thread != nullptr) [[likely]] {
192 if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) { 192 if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) {
193 process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count); 193 process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count, 0);
194 } 194 }
195 } else { 195 } else {
196 m_state.idle_count++; 196 m_state.idle_count++;
@@ -356,7 +356,7 @@ void KScheduler::SwitchThread(KThread* next_thread) {
356 const s64 tick_diff = cur_tick - prev_tick; 356 const s64 tick_diff = cur_tick - prev_tick;
357 cur_thread->AddCpuTime(m_core_id, tick_diff); 357 cur_thread->AddCpuTime(m_core_id, tick_diff);
358 if (cur_process != nullptr) { 358 if (cur_process != nullptr) {
359 cur_process->UpdateCPUTimeTicks(tick_diff); 359 cur_process->AddCpuTime(tick_diff);
360 } 360 }
361 m_last_context_switch_time = cur_tick; 361 m_last_context_switch_time = cur_tick;
362 362
diff --git a/src/core/hle/kernel/k_system_resource.cpp b/src/core/hle/kernel/k_system_resource.cpp
index e6c8d589a..07e92aa80 100644
--- a/src/core/hle/kernel/k_system_resource.cpp
+++ b/src/core/hle/kernel/k_system_resource.cpp
@@ -1,25 +1,100 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 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 "core/core.h"
5#include "core/hle/kernel/k_scoped_resource_reservation.h"
4#include "core/hle/kernel/k_system_resource.h" 6#include "core/hle/kernel/k_system_resource.h"
5 7
6namespace Kernel { 8namespace Kernel {
7 9
8Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_limit, 10Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_limit,
9 KMemoryManager::Pool pool) { 11 KMemoryManager::Pool pool) {
10 // Unimplemented 12 // Set members.
11 UNREACHABLE(); 13 m_resource_limit = resource_limit;
14 m_resource_size = size;
15 m_resource_pool = pool;
16
17 // Determine required size for our secure resource.
18 const size_t secure_size = this->CalculateRequiredSecureMemorySize();
19
20 // Reserve memory for our secure resource.
21 KScopedResourceReservation memory_reservation(
22 m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, secure_size);
23 R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
24
25 // Allocate secure memory.
26 R_TRY(KSystemControl::AllocateSecureMemory(m_kernel, std::addressof(m_resource_address),
27 m_resource_size, static_cast<u32>(m_resource_pool)));
28 ASSERT(m_resource_address != 0);
29
30 // Ensure we clean up the secure memory, if we fail past this point.
31 ON_RESULT_FAILURE {
32 KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size,
33 static_cast<u32>(m_resource_pool));
34 };
35
36 // Check that our allocation is bigger than the reference counts needed for it.
37 const size_t rc_size =
38 Common::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(m_resource_size), PageSize);
39 R_UNLESS(m_resource_size > rc_size, ResultOutOfMemory);
40
41 // Get resource pointer.
42 KPhysicalAddress resource_paddr =
43 KPageTable::GetHeapPhysicalAddress(m_kernel.MemoryLayout(), m_resource_address);
44 auto* resource =
45 m_kernel.System().DeviceMemory().GetPointer<KPageTableManager::RefCount>(resource_paddr);
46
47 // Initialize slab heaps.
48 m_dynamic_page_manager.Initialize(m_resource_address + rc_size, m_resource_size - rc_size,
49 PageSize);
50 m_page_table_heap.Initialize(std::addressof(m_dynamic_page_manager), 0, resource);
51 m_memory_block_heap.Initialize(std::addressof(m_dynamic_page_manager), 0);
52 m_block_info_heap.Initialize(std::addressof(m_dynamic_page_manager), 0);
53
54 // Initialize managers.
55 m_page_table_manager.Initialize(std::addressof(m_dynamic_page_manager),
56 std::addressof(m_page_table_heap));
57 m_memory_block_slab_manager.Initialize(std::addressof(m_dynamic_page_manager),
58 std::addressof(m_memory_block_heap));
59 m_block_info_manager.Initialize(std::addressof(m_dynamic_page_manager),
60 std::addressof(m_block_info_heap));
61
62 // Set our managers.
63 this->SetManagers(m_memory_block_slab_manager, m_block_info_manager, m_page_table_manager);
64
65 // Commit the memory reservation.
66 memory_reservation.Commit();
67
68 // Open reference to our resource limit.
69 m_resource_limit->Open();
70
71 // Set ourselves as initialized.
72 m_is_initialized = true;
73
74 R_SUCCEED();
12} 75}
13 76
14void KSecureSystemResource::Finalize() { 77void KSecureSystemResource::Finalize() {
15 // Unimplemented 78 // Check that we have no outstanding allocations.
16 UNREACHABLE(); 79 ASSERT(m_memory_block_slab_manager.GetUsed() == 0);
80 ASSERT(m_block_info_manager.GetUsed() == 0);
81 ASSERT(m_page_table_manager.GetUsed() == 0);
82
83 // Free our secure memory.
84 KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size,
85 static_cast<u32>(m_resource_pool));
86
87 // Release the memory reservation.
88 m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
89 this->CalculateRequiredSecureMemorySize());
90
91 // Close reference to our resource limit.
92 m_resource_limit->Close();
17} 93}
18 94
19size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(size_t size, 95size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(size_t size,
20 KMemoryManager::Pool pool) { 96 KMemoryManager::Pool pool) {
21 // Unimplemented 97 return KSystemControl::CalculateRequiredSecureMemorySize(size, static_cast<u32>(pool));
22 UNREACHABLE();
23} 98}
24 99
25} // namespace Kernel 100} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index 7df8fd7f7..a6deb50ec 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -122,16 +122,15 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress
122 case ThreadType::Main: 122 case ThreadType::Main:
123 ASSERT(arg == 0); 123 ASSERT(arg == 0);
124 [[fallthrough]]; 124 [[fallthrough]];
125 case ThreadType::HighPriority:
126 [[fallthrough]];
127 case ThreadType::Dummy:
128 [[fallthrough]];
129 case ThreadType::User: 125 case ThreadType::User:
130 ASSERT(((owner == nullptr) || 126 ASSERT(((owner == nullptr) ||
131 (owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask())); 127 (owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask()));
132 ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) || 128 ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) ||
133 (owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask())); 129 (owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask()));
134 break; 130 break;
131 case ThreadType::HighPriority:
132 case ThreadType::Dummy:
133 break;
135 case ThreadType::Kernel: 134 case ThreadType::Kernel:
136 UNIMPLEMENTED(); 135 UNIMPLEMENTED();
137 break; 136 break;
@@ -216,6 +215,7 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress
216 // Setup the TLS, if needed. 215 // Setup the TLS, if needed.
217 if (type == ThreadType::User) { 216 if (type == ThreadType::User) {
218 R_TRY(owner->CreateThreadLocalRegion(std::addressof(m_tls_address))); 217 R_TRY(owner->CreateThreadLocalRegion(std::addressof(m_tls_address)));
218 owner->GetMemory().ZeroBlock(m_tls_address, Svc::ThreadLocalRegionSize);
219 } 219 }
220 220
221 m_parent = owner; 221 m_parent = owner;
@@ -403,7 +403,7 @@ void KThread::StartTermination() {
403 if (m_parent != nullptr) { 403 if (m_parent != nullptr) {
404 m_parent->ReleaseUserException(this); 404 m_parent->ReleaseUserException(this);
405 if (m_parent->GetPinnedThread(GetCurrentCoreId(m_kernel)) == this) { 405 if (m_parent->GetPinnedThread(GetCurrentCoreId(m_kernel)) == this) {
406 m_parent->UnpinCurrentThread(m_core_id); 406 m_parent->UnpinCurrentThread();
407 } 407 }
408 } 408 }
409 409
@@ -415,10 +415,6 @@ void KThread::StartTermination() {
415 m_parent->ClearRunningThread(this); 415 m_parent->ClearRunningThread(this);
416 } 416 }
417 417
418 // Signal.
419 m_signaled = true;
420 KSynchronizationObject::NotifyAvailable();
421
422 // Clear previous thread in KScheduler. 418 // Clear previous thread in KScheduler.
423 KScheduler::ClearPreviousThread(m_kernel, this); 419 KScheduler::ClearPreviousThread(m_kernel, this);
424 420
@@ -437,6 +433,13 @@ void KThread::FinishTermination() {
437 } 433 }
438 } 434 }
439 435
436 // Acquire the scheduler lock.
437 KScopedSchedulerLock sl{m_kernel};
438
439 // Signal.
440 m_signaled = true;
441 KSynchronizationObject::NotifyAvailable();
442
440 // Close the thread. 443 // Close the thread.
441 this->Close(); 444 this->Close();
442} 445}
@@ -820,7 +823,7 @@ void KThread::CloneFpuStatus() {
820 ASSERT(this->GetOwnerProcess() != nullptr); 823 ASSERT(this->GetOwnerProcess() != nullptr);
821 ASSERT(this->GetOwnerProcess() == GetCurrentProcessPointer(m_kernel)); 824 ASSERT(this->GetOwnerProcess() == GetCurrentProcessPointer(m_kernel));
822 825
823 if (this->GetOwnerProcess()->Is64BitProcess()) { 826 if (this->GetOwnerProcess()->Is64Bit()) {
824 // Clone FPSR and FPCR. 827 // Clone FPSR and FPCR.
825 ThreadContext64 cur_ctx{}; 828 ThreadContext64 cur_ctx{};
826 m_kernel.System().CurrentArmInterface().SaveContext(cur_ctx); 829 m_kernel.System().CurrentArmInterface().SaveContext(cur_ctx);
@@ -923,7 +926,7 @@ Result KThread::GetThreadContext3(Common::ScratchBuffer<u8>& out) {
923 926
924 // If we're not terminating, get the thread's user context. 927 // If we're not terminating, get the thread's user context.
925 if (!this->IsTerminationRequested()) { 928 if (!this->IsTerminationRequested()) {
926 if (m_parent->Is64BitProcess()) { 929 if (m_parent->Is64Bit()) {
927 // Mask away mode bits, interrupt bits, IL bit, and other reserved bits. 930 // Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
928 auto context = GetContext64(); 931 auto context = GetContext64();
929 context.pstate &= 0xFF0FFE20; 932 context.pstate &= 0xFF0FFE20;
@@ -1174,6 +1177,9 @@ Result KThread::Run() {
1174 owner->IncrementRunningThreadCount(); 1177 owner->IncrementRunningThreadCount();
1175 } 1178 }
1176 1179
1180 // Open a reference, now that we're running.
1181 this->Open();
1182
1177 // Set our state and finish. 1183 // Set our state and finish.
1178 this->SetState(ThreadState::Runnable); 1184 this->SetState(ThreadState::Runnable);
1179 1185
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index d178c2453..e1f80b04f 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -721,6 +721,7 @@ private:
721 // For core KThread implementation 721 // For core KThread implementation
722 ThreadContext32 m_thread_context_32{}; 722 ThreadContext32 m_thread_context_32{};
723 ThreadContext64 m_thread_context_64{}; 723 ThreadContext64 m_thread_context_64{};
724 Common::IntrusiveListNode m_process_list_node;
724 Common::IntrusiveRedBlackTreeNode m_condvar_arbiter_tree_node{}; 725 Common::IntrusiveRedBlackTreeNode m_condvar_arbiter_tree_node{};
725 s32 m_priority{}; 726 s32 m_priority{};
726 using ConditionVariableThreadTreeTraits = 727 using ConditionVariableThreadTreeTraits =
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..4a1559291 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -101,35 +101,31 @@ struct KernelCore::Impl {
101 101
102 void InitializeCores() { 102 void InitializeCores() {
103 for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { 103 for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
104 cores[core_id]->Initialize((*application_process).Is64BitProcess()); 104 cores[core_id]->Initialize((*application_process).Is64Bit());
105 system.ApplicationMemory().SetCurrentPageTable(*application_process, core_id); 105 system.ApplicationMemory().SetCurrentPageTable(*application_process, core_id);
106 } 106 }
107 } 107 }
108 108
109 void CloseApplicationProcess() { 109 void TerminateApplicationProcess() {
110 KProcess* old_process = application_process.exchange(nullptr); 110 application_process.load()->Terminate();
111 if (old_process == nullptr) {
112 return;
113 }
114
115 // old_process->Close();
116 // TODO: The process should be destroyed based on accurate ref counting after
117 // calling Close(). Adding a manual Destroy() call instead to avoid a memory leak.
118 old_process->Finalize();
119 old_process->Destroy();
120 } 111 }
121 112
122 void Shutdown() { 113 void Shutdown() {
123 is_shutting_down.store(true, std::memory_order_relaxed); 114 is_shutting_down.store(true, std::memory_order_relaxed);
124 SCOPE_EXIT({ is_shutting_down.store(false, std::memory_order_relaxed); }); 115 SCOPE_EXIT({ is_shutting_down.store(false, std::memory_order_relaxed); });
125 116
126 process_list.clear();
127
128 CloseServices(); 117 CloseServices();
129 118
119 auto* old_process = application_process.exchange(nullptr);
120 if (old_process) {
121 old_process->Close();
122 }
123
124 process_list.clear();
125
130 next_object_id = 0; 126 next_object_id = 0;
131 next_kernel_process_id = KProcess::InitialKIPIDMin; 127 next_kernel_process_id = KProcess::InitialProcessIdMin;
132 next_user_process_id = KProcess::ProcessIDMin; 128 next_user_process_id = KProcess::ProcessIdMin;
133 next_thread_id = 1; 129 next_thread_id = 1;
134 130
135 global_handle_table->Finalize(); 131 global_handle_table->Finalize();
@@ -176,8 +172,6 @@ struct KernelCore::Impl {
176 } 172 }
177 } 173 }
178 174
179 CloseApplicationProcess();
180
181 // Track kernel objects that were not freed on shutdown 175 // Track kernel objects that were not freed on shutdown
182 { 176 {
183 std::scoped_lock lk{registered_objects_lock}; 177 std::scoped_lock lk{registered_objects_lock};
@@ -344,6 +338,8 @@ struct KernelCore::Impl {
344 // Create the system page table managers. 338 // Create the system page table managers.
345 app_system_resource = std::make_unique<KSystemResource>(kernel); 339 app_system_resource = std::make_unique<KSystemResource>(kernel);
346 sys_system_resource = std::make_unique<KSystemResource>(kernel); 340 sys_system_resource = std::make_unique<KSystemResource>(kernel);
341 KAutoObject::Create(std::addressof(*app_system_resource));
342 KAutoObject::Create(std::addressof(*sys_system_resource));
347 343
348 // Set the managers for the system resources. 344 // Set the managers for the system resources.
349 app_system_resource->SetManagers(*app_memory_block_manager, *app_block_info_manager, 345 app_system_resource->SetManagers(*app_memory_block_manager, *app_block_info_manager,
@@ -373,7 +369,7 @@ struct KernelCore::Impl {
373 static inline thread_local u8 host_thread_id = UINT8_MAX; 369 static inline thread_local u8 host_thread_id = UINT8_MAX;
374 370
375 /// Sets the host thread ID for the caller. 371 /// Sets the host thread ID for the caller.
376 u32 SetHostThreadId(std::size_t core_id) { 372 LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) {
377 // This should only be called during core init. 373 // This should only be called during core init.
378 ASSERT(host_thread_id == UINT8_MAX); 374 ASSERT(host_thread_id == UINT8_MAX);
379 375
@@ -384,13 +380,13 @@ struct KernelCore::Impl {
384 } 380 }
385 381
386 /// Gets the host thread ID for the caller 382 /// Gets the host thread ID for the caller
387 u32 GetHostThreadId() const { 383 LTO_NOINLINE u32 GetHostThreadId() const {
388 return host_thread_id; 384 return host_thread_id;
389 } 385 }
390 386
391 // Gets the dummy KThread for the caller, allocating a new one if this is the first time 387 // Gets the dummy KThread for the caller, allocating a new one if this is the first time
392 KThread* GetHostDummyThread(KThread* existing_thread) { 388 LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) {
393 const auto initialize{[](KThread* thread) { 389 const auto initialize{[](KThread* thread) LTO_NOINLINE {
394 ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); 390 ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
395 return thread; 391 return thread;
396 }}; 392 }};
@@ -424,11 +420,11 @@ struct KernelCore::Impl {
424 420
425 static inline thread_local bool is_phantom_mode_for_singlecore{false}; 421 static inline thread_local bool is_phantom_mode_for_singlecore{false};
426 422
427 bool IsPhantomModeForSingleCore() const { 423 LTO_NOINLINE bool IsPhantomModeForSingleCore() const {
428 return is_phantom_mode_for_singlecore; 424 return is_phantom_mode_for_singlecore;
429 } 425 }
430 426
431 void SetIsPhantomModeForSingleCore(bool value) { 427 LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) {
432 ASSERT(!is_multicore); 428 ASSERT(!is_multicore);
433 is_phantom_mode_for_singlecore = value; 429 is_phantom_mode_for_singlecore = value;
434 } 430 }
@@ -439,14 +435,14 @@ struct KernelCore::Impl {
439 435
440 static inline thread_local KThread* current_thread{nullptr}; 436 static inline thread_local KThread* current_thread{nullptr};
441 437
442 KThread* GetCurrentEmuThread() { 438 LTO_NOINLINE KThread* GetCurrentEmuThread() {
443 if (!current_thread) { 439 if (!current_thread) {
444 current_thread = GetHostDummyThread(nullptr); 440 current_thread = GetHostDummyThread(nullptr);
445 } 441 }
446 return current_thread; 442 return current_thread;
447 } 443 }
448 444
449 void SetCurrentEmuThread(KThread* thread) { 445 LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) {
450 current_thread = thread; 446 current_thread = thread;
451 } 447 }
452 448
@@ -623,14 +619,33 @@ struct KernelCore::Impl {
623 ASSERT(memory_layout->GetPhysicalMemoryRegionTree().Insert( 619 ASSERT(memory_layout->GetPhysicalMemoryRegionTree().Insert(
624 GetInteger(slab_start_phys_addr), slab_region_size, KMemoryRegionType_DramKernelSlab)); 620 GetInteger(slab_start_phys_addr), slab_region_size, KMemoryRegionType_DramKernelSlab));
625 621
622 // Insert a physical region for the secure applet memory.
623 const auto secure_applet_end_phys_addr =
624 slab_end_phys_addr + KSystemControl::SecureAppletMemorySize;
625 if constexpr (KSystemControl::SecureAppletMemorySize > 0) {
626 ASSERT(memory_layout->GetPhysicalMemoryRegionTree().Insert(
627 GetInteger(slab_end_phys_addr), KSystemControl::SecureAppletMemorySize,
628 KMemoryRegionType_DramKernelSecureAppletMemory));
629 }
630
631 // Insert a physical region for the unknown debug2 region.
632 constexpr size_t SecureUnknownRegionSize = 0;
633 const size_t secure_unknown_size = SecureUnknownRegionSize;
634 const auto secure_unknown_end_phys_addr = secure_applet_end_phys_addr + secure_unknown_size;
635 if constexpr (SecureUnknownRegionSize > 0) {
636 ASSERT(memory_layout->GetPhysicalMemoryRegionTree().Insert(
637 GetInteger(secure_applet_end_phys_addr), secure_unknown_size,
638 KMemoryRegionType_DramKernelSecureUnknown));
639 }
640
626 // Determine size available for kernel page table heaps, requiring > 8 MB. 641 // Determine size available for kernel page table heaps, requiring > 8 MB.
627 const KPhysicalAddress resource_end_phys_addr = slab_start_phys_addr + resource_region_size; 642 const KPhysicalAddress resource_end_phys_addr = slab_start_phys_addr + resource_region_size;
628 const size_t page_table_heap_size = resource_end_phys_addr - slab_end_phys_addr; 643 const size_t page_table_heap_size = resource_end_phys_addr - secure_unknown_end_phys_addr;
629 ASSERT(page_table_heap_size / 4_MiB > 2); 644 ASSERT(page_table_heap_size / 4_MiB > 2);
630 645
631 // Insert a physical region for the kernel page table heap region 646 // Insert a physical region for the kernel page table heap region
632 ASSERT(memory_layout->GetPhysicalMemoryRegionTree().Insert( 647 ASSERT(memory_layout->GetPhysicalMemoryRegionTree().Insert(
633 GetInteger(slab_end_phys_addr), page_table_heap_size, 648 GetInteger(secure_unknown_end_phys_addr), page_table_heap_size,
634 KMemoryRegionType_DramKernelPtHeap)); 649 KMemoryRegionType_DramKernelPtHeap));
635 650
636 // All DRAM regions that we haven't tagged by this point will be mapped under the linear 651 // All DRAM regions that we haven't tagged by this point will be mapped under the linear
@@ -773,8 +788,8 @@ struct KernelCore::Impl {
773 std::mutex registered_in_use_objects_lock; 788 std::mutex registered_in_use_objects_lock;
774 789
775 std::atomic<u32> next_object_id{0}; 790 std::atomic<u32> next_object_id{0};
776 std::atomic<u64> next_kernel_process_id{KProcess::InitialKIPIDMin}; 791 std::atomic<u64> next_kernel_process_id{KProcess::InitialProcessIdMin};
777 std::atomic<u64> next_user_process_id{KProcess::ProcessIDMin}; 792 std::atomic<u64> next_user_process_id{KProcess::ProcessIdMin};
778 std::atomic<u64> next_thread_id{1}; 793 std::atomic<u64> next_thread_id{1};
779 794
780 // Lists all processes that exist in the current session. 795 // Lists all processes that exist in the current session.
@@ -905,10 +920,6 @@ const KProcess* KernelCore::ApplicationProcess() const {
905 return impl->application_process; 920 return impl->application_process;
906} 921}
907 922
908void KernelCore::CloseApplicationProcess() {
909 impl->CloseApplicationProcess();
910}
911
912const std::vector<KProcess*>& KernelCore::GetProcessList() const { 923const std::vector<KProcess*>& KernelCore::GetProcessList() const {
913 return impl->process_list; 924 return impl->process_list;
914} 925}
@@ -1109,8 +1120,8 @@ std::jthread KernelCore::RunOnHostCoreProcess(std::string&& process_name,
1109 std::function<void()> func) { 1120 std::function<void()> func) {
1110 // Make a new process. 1121 // Make a new process.
1111 KProcess* process = KProcess::Create(*this); 1122 KProcess* process = KProcess::Create(*this);
1112 ASSERT(R_SUCCEEDED(KProcess::Initialize(process, System(), "", KProcess::ProcessType::Userland, 1123 ASSERT(R_SUCCEEDED(
1113 GetSystemResourceLimit()))); 1124 process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false)));
1114 1125
1115 // Ensure that we don't hold onto any extra references. 1126 // Ensure that we don't hold onto any extra references.
1116 SCOPE_EXIT({ process->Close(); }); 1127 SCOPE_EXIT({ process->Close(); });
@@ -1137,8 +1148,8 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function
1137 1148
1138 // Make a new process. 1149 // Make a new process.
1139 KProcess* process = KProcess::Create(*this); 1150 KProcess* process = KProcess::Create(*this);
1140 ASSERT(R_SUCCEEDED(KProcess::Initialize(process, System(), "", KProcess::ProcessType::Userland, 1151 ASSERT(R_SUCCEEDED(
1141 GetSystemResourceLimit()))); 1152 process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false)));
1142 1153
1143 // Ensure that we don't hold onto any extra references. 1154 // Ensure that we don't hold onto any extra references.
1144 SCOPE_EXIT({ process->Close(); }); 1155 SCOPE_EXIT({ process->Close(); });
@@ -1247,7 +1258,8 @@ const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const {
1247 1258
1248void KernelCore::SuspendApplication(bool suspended) { 1259void KernelCore::SuspendApplication(bool suspended) {
1249 const bool should_suspend{exception_exited || suspended}; 1260 const bool should_suspend{exception_exited || suspended};
1250 const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable; 1261 const auto activity =
1262 should_suspend ? Svc::ProcessActivity::Paused : Svc::ProcessActivity::Runnable;
1251 1263
1252 // Get the application process. 1264 // Get the application process.
1253 KScopedAutoObject<KProcess> process = ApplicationProcess(); 1265 KScopedAutoObject<KProcess> process = ApplicationProcess();
@@ -1281,6 +1293,8 @@ void KernelCore::SuspendApplication(bool suspended) {
1281} 1293}
1282 1294
1283void KernelCore::ShutdownCores() { 1295void KernelCore::ShutdownCores() {
1296 impl->TerminateApplicationProcess();
1297
1284 KScopedSchedulerLock lk{*this}; 1298 KScopedSchedulerLock lk{*this};
1285 1299
1286 for (auto* thread : impl->shutdown_threads) { 1300 for (auto* thread : impl->shutdown_threads) {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index d5b08eeb5..d8086c0ea 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -134,9 +134,6 @@ public:
134 /// Retrieves a const pointer to the application process. 134 /// Retrieves a const pointer to the application process.
135 const KProcess* ApplicationProcess() const; 135 const KProcess* ApplicationProcess() const;
136 136
137 /// Closes the application process.
138 void CloseApplicationProcess();
139
140 /// Retrieves the list of processes. 137 /// Retrieves the list of processes.
141 const std::vector<KProcess*>& GetProcessList() const; 138 const std::vector<KProcess*>& GetProcessList() const;
142 139
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 871d541d4..b76683969 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -4426,7 +4426,7 @@ void Call(Core::System& system, u32 imm) {
4426 auto& kernel = system.Kernel(); 4426 auto& kernel = system.Kernel();
4427 kernel.EnterSVCProfile(); 4427 kernel.EnterSVCProfile();
4428 4428
4429 if (GetCurrentProcess(system.Kernel()).Is64BitProcess()) { 4429 if (GetCurrentProcess(system.Kernel()).Is64Bit()) {
4430 Call64(system, imm); 4430 Call64(system, imm);
4431 } else { 4431 } else {
4432 Call32(system, imm); 4432 Call32(system, imm);
diff --git a/src/core/hle/kernel/svc/svc_info.cpp b/src/core/hle/kernel/svc/svc_info.cpp
index f99964028..ada998772 100644
--- a/src/core/hle/kernel/svc/svc_info.cpp
+++ b/src/core/hle/kernel/svc/svc_info.cpp
@@ -86,20 +86,19 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
86 R_SUCCEED(); 86 R_SUCCEED();
87 87
88 case InfoType::TotalMemorySize: 88 case InfoType::TotalMemorySize:
89 *result = process->GetTotalPhysicalMemoryAvailable(); 89 *result = process->GetTotalUserPhysicalMemorySize();
90 R_SUCCEED(); 90 R_SUCCEED();
91 91
92 case InfoType::UsedMemorySize: 92 case InfoType::UsedMemorySize:
93 *result = process->GetTotalPhysicalMemoryUsed(); 93 *result = process->GetUsedUserPhysicalMemorySize();
94 R_SUCCEED(); 94 R_SUCCEED();
95 95
96 case InfoType::SystemResourceSizeTotal: 96 case InfoType::SystemResourceSizeTotal:
97 *result = process->GetSystemResourceSize(); 97 *result = process->GetTotalSystemResourceSize();
98 R_SUCCEED(); 98 R_SUCCEED();
99 99
100 case InfoType::SystemResourceSizeUsed: 100 case InfoType::SystemResourceSizeUsed:
101 LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage"); 101 *result = process->GetUsedSystemResourceSize();
102 *result = process->GetSystemResourceUsage();
103 R_SUCCEED(); 102 R_SUCCEED();
104 103
105 case InfoType::ProgramId: 104 case InfoType::ProgramId:
@@ -111,20 +110,29 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
111 R_SUCCEED(); 110 R_SUCCEED();
112 111
113 case InfoType::TotalNonSystemMemorySize: 112 case InfoType::TotalNonSystemMemorySize:
114 *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource(); 113 *result = process->GetTotalNonSystemUserPhysicalMemorySize();
115 R_SUCCEED(); 114 R_SUCCEED();
116 115
117 case InfoType::UsedNonSystemMemorySize: 116 case InfoType::UsedNonSystemMemorySize:
118 *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource(); 117 *result = process->GetUsedNonSystemUserPhysicalMemorySize();
119 R_SUCCEED(); 118 R_SUCCEED();
120 119
121 case InfoType::IsApplication: 120 case InfoType::IsApplication:
122 LOG_WARNING(Kernel_SVC, "(STUBBED) Assuming process is application"); 121 LOG_WARNING(Kernel_SVC, "(STUBBED) Assuming process is application");
123 *result = true; 122 *result = process->IsApplication();
124 R_SUCCEED(); 123 R_SUCCEED();
125 124
126 case InfoType::FreeThreadCount: 125 case InfoType::FreeThreadCount:
127 *result = process->GetFreeThreadCount(); 126 if (KResourceLimit* resource_limit = process->GetResourceLimit();
127 resource_limit != nullptr) {
128 const auto current_value =
129 resource_limit->GetCurrentValue(Svc::LimitableResource::ThreadCountMax);
130 const auto limit_value =
131 resource_limit->GetLimitValue(Svc::LimitableResource::ThreadCountMax);
132 *result = limit_value - current_value;
133 } else {
134 *result = 0;
135 }
128 R_SUCCEED(); 136 R_SUCCEED();
129 137
130 default: 138 default:
@@ -161,7 +169,7 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
161 169
162 case InfoType::RandomEntropy: 170 case InfoType::RandomEntropy:
163 R_UNLESS(handle == 0, ResultInvalidHandle); 171 R_UNLESS(handle == 0, ResultInvalidHandle);
164 R_UNLESS(info_sub_id < KProcess::RANDOM_ENTROPY_SIZE, ResultInvalidCombination); 172 R_UNLESS(info_sub_id < 4, ResultInvalidCombination);
165 173
166 *result = GetCurrentProcess(system.Kernel()).GetRandomEntropy(info_sub_id); 174 *result = GetCurrentProcess(system.Kernel()).GetRandomEntropy(info_sub_id);
167 R_SUCCEED(); 175 R_SUCCEED();
diff --git a/src/core/hle/kernel/svc/svc_lock.cpp b/src/core/hle/kernel/svc/svc_lock.cpp
index 1d7bc4246..5f0833fcb 100644
--- a/src/core/hle/kernel/svc/svc_lock.cpp
+++ b/src/core/hle/kernel/svc/svc_lock.cpp
@@ -17,7 +17,7 @@ Result ArbitrateLock(Core::System& system, Handle thread_handle, u64 address, u3
17 R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory); 17 R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory);
18 R_UNLESS(Common::IsAligned(address, sizeof(u32)), ResultInvalidAddress); 18 R_UNLESS(Common::IsAligned(address, sizeof(u32)), ResultInvalidAddress);
19 19
20 R_RETURN(GetCurrentProcess(system.Kernel()).WaitForAddress(thread_handle, address, tag)); 20 R_RETURN(KConditionVariable::WaitForAddress(system.Kernel(), thread_handle, address, tag));
21} 21}
22 22
23/// Unlock a mutex 23/// Unlock a mutex
@@ -28,7 +28,7 @@ Result ArbitrateUnlock(Core::System& system, u64 address) {
28 R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory); 28 R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory);
29 R_UNLESS(Common::IsAligned(address, sizeof(u32)), ResultInvalidAddress); 29 R_UNLESS(Common::IsAligned(address, sizeof(u32)), ResultInvalidAddress);
30 30
31 R_RETURN(GetCurrentProcess(system.Kernel()).SignalToAddress(address)); 31 R_RETURN(KConditionVariable::SignalToAddress(system.Kernel(), address));
32} 32}
33 33
34Result ArbitrateLock64(Core::System& system, Handle thread_handle, uint64_t address, uint32_t tag) { 34Result ArbitrateLock64(Core::System& system, Handle thread_handle, uint64_t address, uint32_t tag) {
diff --git a/src/core/hle/kernel/svc/svc_memory.cpp b/src/core/hle/kernel/svc/svc_memory.cpp
index 2cab74127..97f1210de 100644
--- a/src/core/hle/kernel/svc/svc_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_memory.cpp
@@ -76,7 +76,7 @@ Result MapUnmapMemorySanityChecks(const KPageTable& manager, u64 dst_addr, u64 s
76} // namespace 76} // namespace
77 77
78Result SetMemoryPermission(Core::System& system, u64 address, u64 size, MemoryPermission perm) { 78Result SetMemoryPermission(Core::System& system, u64 address, u64 size, MemoryPermission perm) {
79 LOG_DEBUG(Kernel_SVC, "called, address=0x{:016X}, size=0x{:X}, perm=0x{:08X", address, size, 79 LOG_DEBUG(Kernel_SVC, "called, address=0x{:016X}, size=0x{:X}, perm=0x{:08X}", address, size,
80 perm); 80 perm);
81 81
82 // Validate address / size. 82 // Validate address / size.
@@ -108,10 +108,16 @@ Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask,
108 R_UNLESS((address < address + size), ResultInvalidCurrentMemory); 108 R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
109 109
110 // Validate the attribute and mask. 110 // Validate the attribute and mask.
111 constexpr u32 SupportedMask = static_cast<u32>(MemoryAttribute::Uncached); 111 constexpr u32 SupportedMask =
112 static_cast<u32>(MemoryAttribute::Uncached | MemoryAttribute::PermissionLocked);
112 R_UNLESS((mask | attr) == mask, ResultInvalidCombination); 113 R_UNLESS((mask | attr) == mask, ResultInvalidCombination);
113 R_UNLESS((mask | attr | SupportedMask) == SupportedMask, ResultInvalidCombination); 114 R_UNLESS((mask | attr | SupportedMask) == SupportedMask, ResultInvalidCombination);
114 115
116 // Check that permission locked is either being set or not masked.
117 R_UNLESS((static_cast<Svc::MemoryAttribute>(mask) & Svc::MemoryAttribute::PermissionLocked) ==
118 (static_cast<Svc::MemoryAttribute>(attr) & Svc::MemoryAttribute::PermissionLocked),
119 ResultInvalidCombination);
120
115 // Validate that the region is in range for the current process. 121 // Validate that the region is in range for the current process.
116 auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()}; 122 auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
117 R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory); 123 R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory);
diff --git a/src/core/hle/kernel/svc/svc_physical_memory.cpp b/src/core/hle/kernel/svc/svc_physical_memory.cpp
index d3545f232..99330d02a 100644
--- a/src/core/hle/kernel/svc/svc_physical_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_physical_memory.cpp
@@ -46,7 +46,7 @@ Result MapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
46 KProcess* const current_process{GetCurrentProcessPointer(system.Kernel())}; 46 KProcess* const current_process{GetCurrentProcessPointer(system.Kernel())};
47 auto& page_table{current_process->GetPageTable()}; 47 auto& page_table{current_process->GetPageTable()};
48 48
49 if (current_process->GetSystemResourceSize() == 0) { 49 if (current_process->GetTotalSystemResourceSize() == 0) {
50 LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); 50 LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
51 R_THROW(ResultInvalidState); 51 R_THROW(ResultInvalidState);
52 } 52 }
@@ -95,7 +95,7 @@ Result UnmapPhysicalMemory(Core::System& system, u64 addr, u64 size) {
95 KProcess* const current_process{GetCurrentProcessPointer(system.Kernel())}; 95 KProcess* const current_process{GetCurrentProcessPointer(system.Kernel())};
96 auto& page_table{current_process->GetPageTable()}; 96 auto& page_table{current_process->GetPageTable()};
97 97
98 if (current_process->GetSystemResourceSize() == 0) { 98 if (current_process->GetTotalSystemResourceSize() == 0) {
99 LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); 99 LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
100 R_THROW(ResultInvalidState); 100 R_THROW(ResultInvalidState);
101 } 101 }
diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp
index 8ebc1bd1c..6c79cfd8d 100644
--- a/src/core/hle/kernel/svc/svc_synchronization.cpp
+++ b/src/core/hle/kernel/svc/svc_synchronization.cpp
@@ -132,7 +132,7 @@ void SynchronizePreemptionState(Core::System& system) {
132 GetCurrentThread(kernel).ClearInterruptFlag(); 132 GetCurrentThread(kernel).ClearInterruptFlag();
133 133
134 // Unpin the current thread. 134 // Unpin the current thread.
135 cur_process->UnpinCurrentThread(core_id); 135 cur_process->UnpinCurrentThread();
136 } 136 }
137} 137}
138 138
diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp
index 933b82e30..755fd62b5 100644
--- a/src/core/hle/kernel/svc/svc_thread.cpp
+++ b/src/core/hle/kernel/svc/svc_thread.cpp
@@ -85,10 +85,6 @@ Result StartThread(Core::System& system, Handle thread_handle) {
85 // Try to start the thread. 85 // Try to start the thread.
86 R_TRY(thread->Run()); 86 R_TRY(thread->Run());
87 87
88 // If we succeeded, persist a reference to the thread.
89 thread->Open();
90 system.Kernel().RegisterInUseObject(thread.GetPointerUnsafe());
91
92 R_SUCCEED(); 88 R_SUCCEED();
93} 89}
94 90
@@ -99,7 +95,6 @@ void ExitThread(Core::System& system) {
99 auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); 95 auto* const current_thread = GetCurrentThreadPointer(system.Kernel());
100 system.GlobalSchedulerContext().RemoveThread(current_thread); 96 system.GlobalSchedulerContext().RemoveThread(current_thread);
101 current_thread->Exit(); 97 current_thread->Exit();
102 system.Kernel().UnregisterInUseObject(current_thread);
103} 98}
104 99
105/// Sleep the current thread 100/// Sleep the current thread
@@ -260,7 +255,7 @@ Result GetThreadList(Core::System& system, s32* out_num_threads, u64 out_thread_
260 255
261 auto list_iter = thread_list.cbegin(); 256 auto list_iter = thread_list.cbegin();
262 for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) { 257 for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) {
263 memory.Write64(out_thread_ids, (*list_iter)->GetThreadId()); 258 memory.Write64(out_thread_ids, list_iter->GetThreadId());
264 out_thread_ids += sizeof(u64); 259 out_thread_ids += sizeof(u64);
265 } 260 }
266 261
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/kernel/svc_generator.py b/src/core/hle/kernel/svc_generator.py
index 7fcbb1ba1..5531faac6 100644
--- a/src/core/hle/kernel/svc_generator.py
+++ b/src/core/hle/kernel/svc_generator.py
@@ -592,7 +592,7 @@ void Call(Core::System& system, u32 imm) {
592 auto& kernel = system.Kernel(); 592 auto& kernel = system.Kernel();
593 kernel.EnterSVCProfile(); 593 kernel.EnterSVCProfile();
594 594
595 if (GetCurrentProcess(system.Kernel()).Is64BitProcess()) { 595 if (GetCurrentProcess(system.Kernel()).Is64Bit()) {
596 Call64(system, imm); 596 Call64(system, imm);
597 } else { 597 } else {
598 Call32(system, imm); 598 Call32(system, imm);
diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h
index 7f380ca4f..50de02e36 100644
--- a/src/core/hle/kernel/svc_types.h
+++ b/src/core/hle/kernel/svc_types.h
@@ -46,6 +46,7 @@ enum class MemoryAttribute : u32 {
46 IpcLocked = (1 << 1), 46 IpcLocked = (1 << 1),
47 DeviceShared = (1 << 2), 47 DeviceShared = (1 << 2),
48 Uncached = (1 << 3), 48 Uncached = (1 << 3),
49 PermissionLocked = (1 << 4),
49}; 50};
50DECLARE_ENUM_FLAG_OPERATORS(MemoryAttribute); 51DECLARE_ENUM_FLAG_OPERATORS(MemoryAttribute);
51 52
@@ -603,13 +604,57 @@ enum class ProcessActivity : u32 {
603 Paused, 604 Paused,
604}; 605};
605 606
607enum class CreateProcessFlag : u32 {
608 // Is 64 bit?
609 Is64Bit = (1 << 0),
610
611 // What kind of address space?
612 AddressSpaceShift = 1,
613 AddressSpaceMask = (7 << AddressSpaceShift),
614 AddressSpace32Bit = (0 << AddressSpaceShift),
615 AddressSpace64BitDeprecated = (1 << AddressSpaceShift),
616 AddressSpace32BitWithoutAlias = (2 << AddressSpaceShift),
617 AddressSpace64Bit = (3 << AddressSpaceShift),
618
619 // Should JIT debug be done on crash?
620 EnableDebug = (1 << 4),
621
622 // Should ASLR be enabled for the process?
623 EnableAslr = (1 << 5),
624
625 // Is the process an application?
626 IsApplication = (1 << 6),
627
628 // 4.x deprecated: Should use secure memory?
629 DeprecatedUseSecureMemory = (1 << 7),
630
631 // 5.x+ Pool partition type.
632 PoolPartitionShift = 7,
633 PoolPartitionMask = (0xF << PoolPartitionShift),
634 PoolPartitionApplication = (0 << PoolPartitionShift),
635 PoolPartitionApplet = (1 << PoolPartitionShift),
636 PoolPartitionSystem = (2 << PoolPartitionShift),
637 PoolPartitionSystemNonSecure = (3 << PoolPartitionShift),
638
639 // 7.x+ Should memory allocation be optimized? This requires IsApplication.
640 OptimizeMemoryAllocation = (1 << 11),
641
642 // 11.x+ DisableDeviceAddressSpaceMerge.
643 DisableDeviceAddressSpaceMerge = (1 << 12),
644
645 // Mask of all flags.
646 All = Is64Bit | AddressSpaceMask | EnableDebug | EnableAslr | IsApplication |
647 PoolPartitionMask | OptimizeMemoryAllocation | DisableDeviceAddressSpaceMerge,
648};
649DECLARE_ENUM_FLAG_OPERATORS(CreateProcessFlag);
650
606struct CreateProcessParameter { 651struct CreateProcessParameter {
607 std::array<char, 12> name; 652 std::array<char, 12> name;
608 u32 version; 653 u32 version;
609 u64 program_id; 654 u64 program_id;
610 u64 code_address; 655 u64 code_address;
611 s32 code_num_pages; 656 s32 code_num_pages;
612 u32 flags; 657 CreateProcessFlag flags;
613 Handle reslimit; 658 Handle reslimit;
614 s32 system_resource_num_pages; 659 s32 system_resource_num_pages;
615}; 660};
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..ff067c8d9 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,68 @@ 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() {
800 service_context.CloseEvent(lock_event);
801};
802
803void ILockAccessor::TryLock(HLERequestContext& ctx) {
804 IPC::RequestParser rp{ctx};
805 const auto return_handle = rp.Pop<bool>();
806
807 LOG_WARNING(Service_AM, "(STUBBED) called, return_handle={}", return_handle);
808
809 // TODO: When return_handle is true this function should return the lock handle
810
811 is_locked = true;
812
813 IPC::ResponseBuilder rb{ctx, 3};
814 rb.Push(ResultSuccess);
815 rb.Push<u8>(is_locked);
816}
817
818void ILockAccessor::Unlock(HLERequestContext& ctx) {
819 LOG_INFO(Service_AM, "called");
820
821 is_locked = false;
822
823 IPC::ResponseBuilder rb{ctx, 2};
824 rb.Push(ResultSuccess);
825}
826
827void ILockAccessor::GetEvent(HLERequestContext& ctx) {
828 LOG_INFO(Service_AM, "called");
829
830 lock_event->Signal();
831
832 IPC::ResponseBuilder rb{ctx, 2, 1};
833 rb.Push(ResultSuccess);
834 rb.PushCopyObjects(lock_event->GetReadableEvent());
835}
836
837void ILockAccessor::IsLocked(HLERequestContext& ctx) {
838 LOG_INFO(Service_AM, "called");
839
840 IPC::ResponseBuilder rb{ctx, 2};
841 rb.Push(ResultSuccess);
842 rb.Push<u8>(is_locked);
843}
844
767ICommonStateGetter::ICommonStateGetter(Core::System& system_, 845ICommonStateGetter::ICommonStateGetter(Core::System& system_,
768 std::shared_ptr<AppletMessageQueue> msg_queue_) 846 std::shared_ptr<AppletMessageQueue> msg_queue_)
769 : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)}, 847 : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)},
@@ -787,7 +865,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
787 {14, nullptr, "GetWakeupCount"}, 865 {14, nullptr, "GetWakeupCount"},
788 {20, nullptr, "PushToGeneralChannel"}, 866 {20, nullptr, "PushToGeneralChannel"},
789 {30, nullptr, "GetHomeButtonReaderLockAccessor"}, 867 {30, nullptr, "GetHomeButtonReaderLockAccessor"},
790 {31, nullptr, "GetReaderLockAccessorEx"}, 868 {31, &ICommonStateGetter::GetReaderLockAccessorEx, "GetReaderLockAccessorEx"},
791 {32, nullptr, "GetWriterLockAccessorEx"}, 869 {32, nullptr, "GetWriterLockAccessorEx"},
792 {40, nullptr, "GetCradleFwVersion"}, 870 {40, nullptr, "GetCradleFwVersion"},
793 {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"}, 871 {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"},
@@ -805,7 +883,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
805 {65, nullptr, "GetApplicationIdByContentActionName"}, 883 {65, nullptr, "GetApplicationIdByContentActionName"},
806 {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"}, 884 {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
807 {67, nullptr, "CancelCpuBoostMode"}, 885 {67, nullptr, "CancelCpuBoostMode"},
808 {68, nullptr, "GetBuiltInDisplayType"}, 886 {68, &ICommonStateGetter::GetBuiltInDisplayType, "GetBuiltInDisplayType"},
809 {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"}, 887 {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"},
810 {90, nullptr, "SetPerformanceConfigurationChangedNotification"}, 888 {90, nullptr, "SetPerformanceConfigurationChangedNotification"},
811 {91, nullptr, "GetCurrentPerformanceConfiguration"}, 889 {91, nullptr, "GetCurrentPerformanceConfiguration"},
@@ -833,7 +911,9 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
833 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground); 911 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
834} 912}
835 913
836ICommonStateGetter::~ICommonStateGetter() = default; 914ICommonStateGetter::~ICommonStateGetter() {
915 service_context.CloseEvent(sleep_lock_event);
916};
837 917
838void ICommonStateGetter::GetBootMode(HLERequestContext& ctx) { 918void ICommonStateGetter::GetBootMode(HLERequestContext& ctx) {
839 LOG_DEBUG(Service_AM, "called"); 919 LOG_DEBUG(Service_AM, "called");
@@ -886,6 +966,18 @@ void ICommonStateGetter::RequestToAcquireSleepLock(HLERequestContext& ctx) {
886 rb.Push(ResultSuccess); 966 rb.Push(ResultSuccess);
887} 967}
888 968
969void ICommonStateGetter::GetReaderLockAccessorEx(HLERequestContext& ctx) {
970 IPC::RequestParser rp{ctx};
971 const auto unknown = rp.Pop<u32>();
972
973 LOG_INFO(Service_AM, "called, unknown={}", unknown);
974
975 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
976
977 rb.Push(ResultSuccess);
978 rb.PushIpcInterface<ILockAccessor>(system);
979}
980
889void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) { 981void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) {
890 LOG_WARNING(Service_AM, "called"); 982 LOG_WARNING(Service_AM, "called");
891 983
@@ -970,6 +1062,14 @@ void ICommonStateGetter::SetCpuBoostMode(HLERequestContext& ctx) {
970 apm_sys->SetCpuBoostMode(ctx); 1062 apm_sys->SetCpuBoostMode(ctx);
971} 1063}
972 1064
1065void ICommonStateGetter::GetBuiltInDisplayType(HLERequestContext& ctx) {
1066 LOG_WARNING(Service_AM, "(STUBBED) called");
1067
1068 IPC::ResponseBuilder rb{ctx, 3};
1069 rb.Push(ResultSuccess);
1070 rb.Push(0);
1071}
1072
973void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) { 1073void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) {
974 IPC::RequestParser rp{ctx}; 1074 IPC::RequestParser rp{ctx};
975 const auto system_button{rp.PopEnum<SystemButtonType>()}; 1075 const auto system_button{rp.PopEnum<SystemButtonType>()};
@@ -1477,7 +1577,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1477 {100, nullptr, "CreateGameMovieTrimmer"}, 1577 {100, nullptr, "CreateGameMovieTrimmer"},
1478 {101, nullptr, "ReserveResourceForMovieOperation"}, 1578 {101, nullptr, "ReserveResourceForMovieOperation"},
1479 {102, nullptr, "UnreserveResourceForMovieOperation"}, 1579 {102, nullptr, "UnreserveResourceForMovieOperation"},
1480 {110, nullptr, "GetMainAppletAvailableUsers"}, 1580 {110, &ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers, "GetMainAppletAvailableUsers"},
1481 {120, nullptr, "GetLaunchStorageInfoForDebug"}, 1581 {120, nullptr, "GetLaunchStorageInfoForDebug"},
1482 {130, nullptr, "GetGpuErrorDetectedSystemEvent"}, 1582 {130, nullptr, "GetGpuErrorDetectedSystemEvent"},
1483 {140, nullptr, "SetApplicationMemoryReservation"}, 1583 {140, nullptr, "SetApplicationMemoryReservation"},
@@ -1493,6 +1593,9 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1493 case Applets::AppletId::MiiEdit: 1593 case Applets::AppletId::MiiEdit:
1494 PushInShowMiiEditData(); 1594 PushInShowMiiEditData();
1495 break; 1595 break;
1596 case Applets::AppletId::PhotoViewer:
1597 PushInShowAlbum();
1598 break;
1496 default: 1599 default:
1497 break; 1600 break;
1498 } 1601 }
@@ -1569,6 +1672,42 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext&
1569 rb.PushRaw(applet_info); 1672 rb.PushRaw(applet_info);
1570} 1673}
1571 1674
1675void ILibraryAppletSelfAccessor::GetMainAppletAvailableUsers(HLERequestContext& ctx) {
1676 const Service::Account::ProfileManager manager{};
1677 bool is_empty{true};
1678 s32 user_count{-1};
1679
1680 LOG_INFO(Service_AM, "called");
1681
1682 if (manager.GetUserCount() > 0) {
1683 is_empty = false;
1684 user_count = static_cast<s32>(manager.GetUserCount());
1685 ctx.WriteBuffer(manager.GetAllUsers());
1686 }
1687
1688 IPC::ResponseBuilder rb{ctx, 4};
1689 rb.Push(ResultSuccess);
1690 rb.Push<u8>(is_empty);
1691 rb.Push(user_count);
1692}
1693
1694void ILibraryAppletSelfAccessor::PushInShowAlbum() {
1695 const Applets::CommonArguments arguments{
1696 .arguments_version = Applets::CommonArgumentVersion::Version3,
1697 .size = Applets::CommonArgumentSize::Version3,
1698 .library_version = 1,
1699 .theme_color = Applets::ThemeColor::BasicBlack,
1700 .play_startup_sound = true,
1701 .system_tick = system.CoreTiming().GetClockTicks(),
1702 };
1703
1704 std::vector<u8> argument_data(sizeof(arguments));
1705 std::vector<u8> settings_data{2};
1706 std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
1707 queue_data.emplace_back(std::move(argument_data));
1708 queue_data.emplace_back(std::move(settings_data));
1709}
1710
1572void ILibraryAppletSelfAccessor::PushInShowCabinetData() { 1711void ILibraryAppletSelfAccessor::PushInShowCabinetData() {
1573 const Applets::CommonArguments arguments{ 1712 const Applets::CommonArguments arguments{
1574 .arguments_version = Applets::CommonArgumentVersion::Version3, 1713 .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_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp
index 19ed184e8..b379dadeb 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.cpp
+++ b/src/core/hle/service/am/applets/applet_cabinet.cpp
@@ -25,7 +25,9 @@ Cabinet::Cabinet(Core::System& system_, LibraryAppletMode applet_mode_,
25 service_context.CreateEvent("CabinetApplet:AvailabilityChangeEvent"); 25 service_context.CreateEvent("CabinetApplet:AvailabilityChangeEvent");
26} 26}
27 27
28Cabinet::~Cabinet() = default; 28Cabinet::~Cabinet() {
29 service_context.CloseEvent(availability_change_event);
30};
29 31
30void Cabinet::Initialize() { 32void Cabinet::Initialize() {
31 Applet::Initialize(); 33 Applet::Initialize();
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..cd1dfe993 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,22 @@ 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(
29 "caps:ss", std::make_shared<IScreenShotService>(system, album_manager));
30 server_manager->RegisterNamedService("caps:sc",
31 std::make_shared<IScreenShotControlService>(system));
32 server_manager->RegisterNamedService(
33 "caps:su", std::make_shared<IScreenShotApplicationService>(system, album_manager));
18 34
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)); 35 ServerManager::RunServer(std::move(server_manager));
26} 36}
27 37
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..7d733eb54
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -0,0 +1,469 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <sstream>
5
6#include "common/fs/file.h"
7#include "common/fs/path_util.h"
8#include "common/logging/log.h"
9#include "common/stb.h"
10#include "core/core.h"
11#include "core/hle/service/caps/caps_manager.h"
12#include "core/hle/service/caps/caps_result.h"
13#include "core/hle/service/time/time_manager.h"
14#include "core/hle/service/time/time_zone_content_manager.h"
15
16namespace Service::Capture {
17
18AlbumManager::AlbumManager(Core::System& system_) : system{system_} {}
19
20AlbumManager::~AlbumManager() = default;
21
22Result AlbumManager::DeleteAlbumFile(const AlbumFileId& file_id) {
23 if (file_id.storage > AlbumStorage::Sd) {
24 return ResultInvalidStorage;
25 }
26
27 if (!is_mounted) {
28 return ResultIsNotMounted;
29 }
30
31 std::filesystem::path path;
32 const auto result = GetFile(path, file_id);
33
34 if (result.IsError()) {
35 return result;
36 }
37
38 if (!Common::FS::RemoveFile(path)) {
39 return ResultFileNotFound;
40 }
41
42 return ResultSuccess;
43}
44
45Result AlbumManager::IsAlbumMounted(AlbumStorage storage) {
46 if (storage > AlbumStorage::Sd) {
47 return ResultInvalidStorage;
48 }
49
50 is_mounted = true;
51
52 if (storage == AlbumStorage::Sd) {
53 FindScreenshots();
54 }
55
56 return is_mounted ? ResultSuccess : ResultIsNotMounted;
57}
58
59Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
60 u8 flags) const {
61 if (storage > AlbumStorage::Sd) {
62 return ResultInvalidStorage;
63 }
64
65 if (!is_mounted) {
66 return ResultIsNotMounted;
67 }
68
69 for (auto& [file_id, path] : album_files) {
70 if (file_id.storage != storage) {
71 continue;
72 }
73 if (out_entries.size() >= SdAlbumFileLimit) {
74 break;
75 }
76
77 const auto entry_size = Common::FS::GetSize(path);
78 out_entries.push_back({
79 .entry_size = entry_size,
80 .file_id = file_id,
81 });
82 }
83
84 return ResultSuccess;
85}
86
87Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
88 ContentType contex_type, s64 start_posix_time,
89 s64 end_posix_time, u64 aruid) const {
90 if (!is_mounted) {
91 return ResultIsNotMounted;
92 }
93
94 std::vector<ApplicationAlbumEntry> album_entries;
95 const auto start_date = ConvertToAlbumDateTime(start_posix_time);
96 const auto end_date = ConvertToAlbumDateTime(end_posix_time);
97 const auto result = GetAlbumFileList(album_entries, contex_type, start_date, end_date, aruid);
98
99 if (result.IsError()) {
100 return result;
101 }
102
103 for (const auto& album_entry : album_entries) {
104 ApplicationAlbumFileEntry entry{
105 .entry = album_entry,
106 .datetime = album_entry.datetime,
107 .unknown = {},
108 };
109 out_entries.push_back(entry);
110 }
111
112 return ResultSuccess;
113}
114
115Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
116 ContentType contex_type, AlbumFileDateTime start_date,
117 AlbumFileDateTime end_date, u64 aruid) const {
118 if (!is_mounted) {
119 return ResultIsNotMounted;
120 }
121
122 for (auto& [file_id, path] : album_files) {
123 if (file_id.type != contex_type) {
124 continue;
125 }
126 if (file_id.date > start_date) {
127 continue;
128 }
129 if (file_id.date < end_date) {
130 continue;
131 }
132 if (out_entries.size() >= SdAlbumFileLimit) {
133 break;
134 }
135
136 const auto entry_size = Common::FS::GetSize(path);
137 ApplicationAlbumEntry entry{
138 .size = entry_size,
139 .hash{},
140 .datetime = file_id.date,
141 .storage = file_id.storage,
142 .content = contex_type,
143 .unknown = 1,
144 };
145 out_entries.push_back(entry);
146 }
147
148 return ResultSuccess;
149}
150
151Result AlbumManager::GetAutoSavingStorage(bool& out_is_autosaving) const {
152 out_is_autosaving = false;
153 return ResultSuccess;
154}
155
156Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
157 std::vector<u8>& out_image,
158 const AlbumFileId& file_id,
159 const ScreenShotDecodeOption& decoder_options) const {
160 if (file_id.storage > AlbumStorage::Sd) {
161 return ResultInvalidStorage;
162 }
163
164 if (!is_mounted) {
165 return ResultIsNotMounted;
166 }
167
168 out_image_output = {
169 .width = 1280,
170 .height = 720,
171 .attribute =
172 {
173 .unknown_0{},
174 .orientation = AlbumImageOrientation::None,
175 .unknown_1{},
176 .unknown_2{},
177 },
178 };
179
180 std::filesystem::path path;
181 const auto result = GetFile(path, file_id);
182
183 if (result.IsError()) {
184 return result;
185 }
186
187 out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
188
189 return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
190 +static_cast<int>(out_image_output.height), decoder_options.flags);
191}
192
193Result AlbumManager::LoadAlbumScreenShotThumbnail(
194 LoadAlbumScreenShotImageOutput& out_image_output, std::vector<u8>& out_image,
195 const AlbumFileId& file_id, const ScreenShotDecodeOption& decoder_options) const {
196 if (file_id.storage > AlbumStorage::Sd) {
197 return ResultInvalidStorage;
198 }
199
200 if (!is_mounted) {
201 return ResultIsNotMounted;
202 }
203
204 out_image_output = {
205 .width = 320,
206 .height = 180,
207 .attribute =
208 {
209 .unknown_0{},
210 .orientation = AlbumImageOrientation::None,
211 .unknown_1{},
212 .unknown_2{},
213 },
214 };
215
216 std::filesystem::path path;
217 const auto result = GetFile(path, file_id);
218
219 if (result.IsError()) {
220 return result;
221 }
222
223 out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
224
225 return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
226 +static_cast<int>(out_image_output.height), decoder_options.flags);
227}
228
229Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
230 const ScreenShotAttribute& attribute,
231 std::span<const u8> image_data, u64 aruid) {
232 return SaveScreenShot(out_entry, attribute, {}, image_data, aruid);
233}
234
235Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
236 const ScreenShotAttribute& attribute,
237 const ApplicationData& app_data, std::span<const u8> image_data,
238 u64 aruid) {
239 const u64 title_id = system.GetApplicationProcessProgramID();
240 const auto& user_clock = system.GetTimeManager().GetStandardUserSystemClockCore();
241
242 s64 posix_time{};
243 Result result = user_clock.GetCurrentTime(system, posix_time);
244
245 if (result.IsError()) {
246 return result;
247 }
248
249 const auto date = ConvertToAlbumDateTime(posix_time);
250
251 return SaveImage(out_entry, image_data, title_id, date);
252}
253
254Result AlbumManager::SaveEditedScreenShot(ApplicationAlbumEntry& out_entry,
255 const ScreenShotAttribute& attribute,
256 const AlbumFileId& file_id,
257 std::span<const u8> image_data) {
258 const auto& user_clock = system.GetTimeManager().GetStandardUserSystemClockCore();
259
260 s64 posix_time{};
261 Result result = user_clock.GetCurrentTime(system, posix_time);
262
263 if (result.IsError()) {
264 return result;
265 }
266
267 const auto date = ConvertToAlbumDateTime(posix_time);
268
269 return SaveImage(out_entry, image_data, file_id.application_id, date);
270}
271
272Result AlbumManager::GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const {
273 const auto file = album_files.find(file_id);
274
275 if (file == album_files.end()) {
276 return ResultFileNotFound;
277 }
278
279 out_path = file->second;
280 return ResultSuccess;
281}
282
283void AlbumManager::FindScreenshots() {
284 is_mounted = false;
285 album_files.clear();
286
287 // TODO: Swap this with a blocking operation.
288 const auto screenshots_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir);
289 Common::FS::IterateDirEntries(
290 screenshots_dir,
291 [this](const std::filesystem::path& full_path) {
292 AlbumEntry entry;
293 if (GetAlbumEntry(entry, full_path).IsError()) {
294 return true;
295 }
296 while (album_files.contains(entry.file_id)) {
297 if (++entry.file_id.date.unique_id == 0) {
298 break;
299 }
300 }
301 album_files[entry.file_id] = full_path;
302 return true;
303 },
304 Common::FS::DirEntryFilter::File);
305
306 is_mounted = true;
307}
308
309Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const {
310 std::istringstream line_stream(path.filename().string());
311 std::string date;
312 std::string application;
313 std::string time;
314
315 // Parse filename to obtain entry properties
316 std::getline(line_stream, application, '_');
317 std::getline(line_stream, date, '_');
318 std::getline(line_stream, time, '_');
319
320 std::istringstream date_stream(date);
321 std::istringstream time_stream(time);
322 std::string year;
323 std::string month;
324 std::string day;
325 std::string hour;
326 std::string minute;
327 std::string second;
328
329 std::getline(date_stream, year, '-');
330 std::getline(date_stream, month, '-');
331 std::getline(date_stream, day, '-');
332
333 std::getline(time_stream, hour, '-');
334 std::getline(time_stream, minute, '-');
335 std::getline(time_stream, second, '-');
336
337 try {
338 out_entry = {
339 .entry_size = 1,
340 .file_id{
341 .application_id = static_cast<u64>(std::stoll(application, 0, 16)),
342 .date =
343 {
344 .year = static_cast<s16>(std::stoi(year)),
345 .month = static_cast<s8>(std::stoi(month)),
346 .day = static_cast<s8>(std::stoi(day)),
347 .hour = static_cast<s8>(std::stoi(hour)),
348 .minute = static_cast<s8>(std::stoi(minute)),
349 .second = static_cast<s8>(std::stoi(second)),
350 .unique_id = 0,
351 },
352 .storage = AlbumStorage::Sd,
353 .type = ContentType::Screenshot,
354 .unknown = 1,
355 },
356 };
357 } catch (const std::invalid_argument&) {
358 return ResultUnknown;
359 } catch (const std::out_of_range&) {
360 return ResultUnknown;
361 } catch (const std::exception&) {
362 return ResultUnknown;
363 }
364
365 return ResultSuccess;
366}
367
368Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::path& path,
369 int width, int height, ScreenShotDecoderFlag flag) const {
370 if (out_image.size() != static_cast<std::size_t>(width * height * STBI_rgb_alpha)) {
371 return ResultUnknown;
372 }
373
374 const Common::FS::IOFile db_file{path, Common::FS::FileAccessMode::Read,
375 Common::FS::FileType::BinaryFile};
376
377 std::vector<u8> raw_file(db_file.GetSize());
378 if (db_file.Read(raw_file) != raw_file.size()) {
379 return ResultUnknown;
380 }
381
382 int filter_flag = STBIR_FILTER_DEFAULT;
383 int original_width, original_height, color_channels;
384 const auto dbi_image =
385 stbi_load_from_memory(raw_file.data(), static_cast<int>(raw_file.size()), &original_width,
386 &original_height, &color_channels, STBI_rgb_alpha);
387
388 if (dbi_image == nullptr) {
389 return ResultUnknown;
390 }
391
392 switch (flag) {
393 case ScreenShotDecoderFlag::EnableFancyUpsampling:
394 filter_flag = STBIR_FILTER_TRIANGLE;
395 break;
396 case ScreenShotDecoderFlag::EnableBlockSmoothing:
397 filter_flag = STBIR_FILTER_BOX;
398 break;
399 default:
400 filter_flag = STBIR_FILTER_DEFAULT;
401 break;
402 }
403
404 stbir_resize_uint8_srgb(dbi_image, original_width, original_height, 0, out_image.data(), width,
405 height, 0, STBI_rgb_alpha, 3, filter_flag);
406
407 return ResultSuccess;
408}
409
410static void PNGToMemory(void* context, void* png, int len) {
411 std::vector<u8>* png_image = static_cast<std::vector<u8>*>(context);
412 png_image->reserve(len);
413 std::memcpy(png_image->data(), png, len);
414}
415
416Result AlbumManager::SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image,
417 u64 title_id, const AlbumFileDateTime& date) const {
418 const auto screenshot_path =
419 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir);
420 const std::string formatted_date =
421 fmt::format("{:04}-{:02}-{:02}_{:02}-{:02}-{:02}-{:03}", date.year, date.month, date.day,
422 date.hour, date.minute, date.second, 0);
423 const std::string file_path =
424 fmt::format("{}/{:016x}_{}.png", screenshot_path, title_id, formatted_date);
425
426 const Common::FS::IOFile db_file{file_path, Common::FS::FileAccessMode::Write,
427 Common::FS::FileType::BinaryFile};
428
429 std::vector<u8> png_image;
430 if (!stbi_write_png_to_func(PNGToMemory, &png_image, 1280, 720, STBI_rgb_alpha, image.data(),
431 0)) {
432 return ResultFileCountLimit;
433 }
434
435 if (db_file.Write(png_image) != png_image.size()) {
436 return ResultFileCountLimit;
437 }
438
439 out_entry = {
440 .size = png_image.size(),
441 .hash = {},
442 .datetime = date,
443 .storage = AlbumStorage::Sd,
444 .content = ContentType::Screenshot,
445 .unknown = 1,
446 };
447
448 return ResultSuccess;
449}
450
451AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
452 Time::TimeZone::CalendarInfo calendar_date{};
453 const auto& time_zone_manager =
454 system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
455
456 time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date);
457
458 return {
459 .year = calendar_date.time.year,
460 .month = calendar_date.time.month,
461 .day = calendar_date.time.day,
462 .hour = calendar_date.time.hour,
463 .minute = calendar_date.time.minute,
464 .second = calendar_date.time.second,
465 .unique_id = 0,
466 };
467}
468
469} // 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..44d85117f
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -0,0 +1,90 @@
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
61 Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
62 std::span<const u8> image_data, u64 aruid);
63 Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute,
64 const ApplicationData& app_data, std::span<const u8> image_data,
65 u64 aruid);
66 Result SaveEditedScreenShot(ApplicationAlbumEntry& out_entry,
67 const ScreenShotAttribute& attribute, const AlbumFileId& file_id,
68 std::span<const u8> image_data);
69
70private:
71 static constexpr std::size_t NandAlbumFileLimit = 1000;
72 static constexpr std::size_t SdAlbumFileLimit = 10000;
73
74 void FindScreenshots();
75 Result GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const;
76 Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const;
77 Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
78 int height, ScreenShotDecoderFlag flag) const;
79 Result SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image, u64 title_id,
80 const AlbumFileDateTime& date) const;
81
82 AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
83
84 bool is_mounted{};
85 std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
86
87 Core::System& system;
88};
89
90} // 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..1ba2b7972 100644
--- a/src/core/hle/service/caps/caps_ss.cpp
+++ b/src/core/hle/service/caps/caps_ss.cpp
@@ -1,18 +1,25 @@
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"
5#include "core/hle/service/caps/caps_manager.h"
6#include "core/hle/service/caps/caps_types.h"
7#include "core/hle/service/ipc_helpers.h"
8
4#include "core/hle/service/caps/caps_ss.h" 9#include "core/hle/service/caps/caps_ss.h"
5 10
6namespace Service::Capture { 11namespace Service::Capture {
7 12
8CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} { 13IScreenShotService::IScreenShotService(Core::System& system_,
14 std::shared_ptr<AlbumManager> album_manager)
15 : ServiceFramework{system_, "caps:ss"}, manager{album_manager} {
9 // clang-format off 16 // clang-format off
10 static const FunctionInfo functions[] = { 17 static const FunctionInfo functions[] = {
11 {201, nullptr, "SaveScreenShot"}, 18 {201, nullptr, "SaveScreenShot"},
12 {202, nullptr, "SaveEditedScreenShot"}, 19 {202, nullptr, "SaveEditedScreenShot"},
13 {203, nullptr, "SaveScreenShotEx0"}, 20 {203, &IScreenShotService::SaveScreenShotEx0, "SaveScreenShotEx0"},
14 {204, nullptr, "SaveEditedScreenShotEx0"}, 21 {204, nullptr, "SaveEditedScreenShotEx0"},
15 {206, nullptr, "Unknown206"}, 22 {206, &IScreenShotService::SaveEditedScreenShotEx1, "SaveEditedScreenShotEx1"},
16 {208, nullptr, "SaveScreenShotOfMovieEx1"}, 23 {208, nullptr, "SaveScreenShotOfMovieEx1"},
17 {1000, nullptr, "Unknown1000"}, 24 {1000, nullptr, "Unknown1000"},
18 }; 25 };
@@ -21,6 +28,67 @@ CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
21 RegisterHandlers(functions); 28 RegisterHandlers(functions);
22} 29}
23 30
24CAPS_SS::~CAPS_SS() = default; 31IScreenShotService::~IScreenShotService() = default;
32
33void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) {
34 IPC::RequestParser rp{ctx};
35 struct Parameters {
36 ScreenShotAttribute attribute{};
37 u32 report_option{};
38 INSERT_PADDING_BYTES(0x4);
39 u64 applet_resource_user_id{};
40 };
41 static_assert(sizeof(Parameters) == 0x50, "Parameters has incorrect size.");
42
43 const auto parameters{rp.PopRaw<Parameters>()};
44 const auto image_data_buffer = ctx.ReadBuffer();
45
46 LOG_INFO(Service_Capture,
47 "called, report_option={}, image_data_buffer_size={}, applet_resource_user_id={}",
48 parameters.report_option, image_data_buffer.size(),
49 parameters.applet_resource_user_id);
50
51 ApplicationAlbumEntry entry{};
52 const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer,
53 parameters.applet_resource_user_id);
54
55 IPC::ResponseBuilder rb{ctx, 10};
56 rb.Push(result);
57 rb.PushRaw(entry);
58}
59void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) {
60 IPC::RequestParser rp{ctx};
61 struct Parameters {
62 ScreenShotAttribute attribute;
63 u64 width;
64 u64 height;
65 u64 thumbnail_width;
66 u64 thumbnail_height;
67 AlbumFileId file_id;
68 };
69 static_assert(sizeof(Parameters) == 0x78, "Parameters has incorrect size.");
70
71 const auto parameters{rp.PopRaw<Parameters>()};
72 const auto application_data_buffer = ctx.ReadBuffer(0);
73 const auto image_data_buffer = ctx.ReadBuffer(1);
74 const auto thumbnail_image_data_buffer = ctx.ReadBuffer(2);
75
76 LOG_INFO(Service_Capture,
77 "called, width={}, height={}, thumbnail_width={}, thumbnail_height={}, "
78 "application_id={:016x}, storage={}, type={}, app_data_buffer_size={}, "
79 "image_data_buffer_size={}, thumbnail_image_buffer_size={}",
80 parameters.width, parameters.height, parameters.thumbnail_width,
81 parameters.thumbnail_height, parameters.file_id.application_id,
82 parameters.file_id.storage, parameters.file_id.type, application_data_buffer.size(),
83 image_data_buffer.size(), thumbnail_image_data_buffer.size());
84
85 ApplicationAlbumEntry entry{};
86 const auto result = manager->SaveEditedScreenShot(entry, parameters.attribute,
87 parameters.file_id, image_data_buffer);
88
89 IPC::ResponseBuilder rb{ctx, 10};
90 rb.Push(result);
91 rb.PushRaw(entry);
92}
25 93
26} // namespace Service::Capture 94} // 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..a7e9972ab 100644
--- a/src/core/hle/service/caps/caps_ss.h
+++ b/src/core/hle/service/caps/caps_ss.h
@@ -11,10 +11,16 @@ 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_, std::shared_ptr<AlbumManager> album_manager);
17 ~CAPS_SS() override; 17 ~IScreenShotService() override;
18
19private:
20 void SaveScreenShotEx0(HLERequestContext& ctx);
21 void SaveEditedScreenShotEx1(HLERequestContext& ctx);
22
23 std::shared_ptr<AlbumManager> manager;
18}; 24};
19 25
20} // namespace Service::Capture 26} // 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..e85625ee4 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -2,18 +2,22 @@
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_manager.h"
5#include "core/hle/service/caps/caps_su.h" 6#include "core/hle/service/caps/caps_su.h"
7#include "core/hle/service/caps/caps_types.h"
6#include "core/hle/service/ipc_helpers.h" 8#include "core/hle/service/ipc_helpers.h"
7 9
8namespace Service::Capture { 10namespace Service::Capture {
9 11
10CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} { 12IScreenShotApplicationService::IScreenShotApplicationService(
13 Core::System& system_, std::shared_ptr<AlbumManager> album_manager)
14 : ServiceFramework{system_, "caps:su"}, manager{album_manager} {
11 // clang-format off 15 // clang-format off
12 static const FunctionInfo functions[] = { 16 static const FunctionInfo functions[] = {
13 {32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"}, 17 {32, &IScreenShotApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
14 {201, nullptr, "SaveScreenShot"}, 18 {201, nullptr, "SaveScreenShot"},
15 {203, nullptr, "SaveScreenShotEx0"}, 19 {203, &IScreenShotApplicationService::SaveScreenShotEx0, "SaveScreenShotEx0"},
16 {205, nullptr, "SaveScreenShotEx1"}, 20 {205, &IScreenShotApplicationService::SaveScreenShotEx1, "SaveScreenShotEx1"},
17 {210, nullptr, "SaveScreenShotEx2"}, 21 {210, nullptr, "SaveScreenShotEx2"},
18 }; 22 };
19 // clang-format on 23 // clang-format on
@@ -21,9 +25,9 @@ CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
21 RegisterHandlers(functions); 25 RegisterHandlers(functions);
22} 26}
23 27
24CAPS_SU::~CAPS_SU() = default; 28IScreenShotApplicationService::~IScreenShotApplicationService() = default;
25 29
26void CAPS_SU::SetShimLibraryVersion(HLERequestContext& ctx) { 30void IScreenShotApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
27 IPC::RequestParser rp{ctx}; 31 IPC::RequestParser rp{ctx};
28 const auto library_version{rp.Pop<u64>()}; 32 const auto library_version{rp.Pop<u64>()};
29 const auto applet_resource_user_id{rp.Pop<u64>()}; 33 const auto applet_resource_user_id{rp.Pop<u64>()};
@@ -35,4 +39,62 @@ void CAPS_SU::SetShimLibraryVersion(HLERequestContext& ctx) {
35 rb.Push(ResultSuccess); 39 rb.Push(ResultSuccess);
36} 40}
37 41
42void IScreenShotApplicationService::SaveScreenShotEx0(HLERequestContext& ctx) {
43 IPC::RequestParser rp{ctx};
44 struct Parameters {
45 ScreenShotAttribute attribute{};
46 AlbumReportOption report_option{};
47 INSERT_PADDING_BYTES(0x4);
48 u64 applet_resource_user_id{};
49 };
50 static_assert(sizeof(Parameters) == 0x50, "Parameters has incorrect size.");
51
52 const auto parameters{rp.PopRaw<Parameters>()};
53 const auto image_data_buffer = ctx.ReadBuffer();
54
55 LOG_INFO(Service_Capture,
56 "called, report_option={}, image_data_buffer_size={}, applet_resource_user_id={}",
57 parameters.report_option, image_data_buffer.size(),
58 parameters.applet_resource_user_id);
59
60 ApplicationAlbumEntry entry{};
61 const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer,
62 parameters.applet_resource_user_id);
63
64 IPC::ResponseBuilder rb{ctx, 10};
65 rb.Push(result);
66 rb.PushRaw(entry);
67}
68
69void IScreenShotApplicationService::SaveScreenShotEx1(HLERequestContext& ctx) {
70 IPC::RequestParser rp{ctx};
71 struct Parameters {
72 ScreenShotAttribute attribute{};
73 AlbumReportOption report_option{};
74 INSERT_PADDING_BYTES(0x4);
75 u64 applet_resource_user_id{};
76 };
77 static_assert(sizeof(Parameters) == 0x50, "Parameters has incorrect size.");
78
79 const auto parameters{rp.PopRaw<Parameters>()};
80 const auto app_data_buffer = ctx.ReadBuffer(0);
81 const auto image_data_buffer = ctx.ReadBuffer(1);
82
83 LOG_INFO(Service_Capture,
84 "called, report_option={}, image_data_buffer_size={}, applet_resource_user_id={}",
85 parameters.report_option, image_data_buffer.size(),
86 parameters.applet_resource_user_id);
87
88 ApplicationAlbumEntry entry{};
89 ApplicationData app_data{};
90 std::memcpy(&app_data, app_data_buffer.data(), sizeof(ApplicationData));
91 const auto result =
92 manager->SaveScreenShot(entry, parameters.attribute, app_data, image_data_buffer,
93 parameters.applet_resource_user_id);
94
95 IPC::ResponseBuilder rb{ctx, 10};
96 rb.Push(result);
97 rb.PushRaw(entry);
98}
99
38} // namespace Service::Capture 100} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h
index c6398858d..89e71f506 100644
--- a/src/core/hle/service/caps/caps_su.h
+++ b/src/core/hle/service/caps/caps_su.h
@@ -10,14 +10,20 @@ class System;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13class AlbumManager;
13 14
14class CAPS_SU final : public ServiceFramework<CAPS_SU> { 15class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
15public: 16public:
16 explicit CAPS_SU(Core::System& system_); 17 explicit IScreenShotApplicationService(Core::System& system_,
17 ~CAPS_SU() override; 18 std::shared_ptr<AlbumManager> album_manager);
19 ~IScreenShotApplicationService() override;
18 20
19private: 21private:
20 void SetShimLibraryVersion(HLERequestContext& ctx); 22 void SetShimLibraryVersion(HLERequestContext& ctx);
23 void SaveScreenShotEx0(HLERequestContext& ctx);
24 void SaveScreenShotEx1(HLERequestContext& ctx);
25
26 std::shared_ptr<AlbumManager> manager;
21}; 27};
22 28
23} // namespace Service::Capture 29} // namespace Service::Capture
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..589ac28d3
--- /dev/null
+++ b/src/core/hle/service/caps/caps_types.h
@@ -0,0 +1,186 @@
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 Unknown2,
24 Unknown3,
25};
26
27enum class ContentType : u8 {
28 Screenshot = 0,
29 Movie = 1,
30 ExtraMovie = 3,
31};
32
33enum class AlbumStorage : u8 {
34 Nand,
35 Sd,
36};
37
38enum class ScreenShotDecoderFlag : u64 {
39 None = 0,
40 EnableFancyUpsampling = 1 << 0,
41 EnableBlockSmoothing = 1 << 1,
42};
43
44// This is nn::capsrv::AlbumFileDateTime
45struct AlbumFileDateTime {
46 s16 year{};
47 s8 month{};
48 s8 day{};
49 s8 hour{};
50 s8 minute{};
51 s8 second{};
52 s8 unique_id{};
53
54 friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
55 friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
56 if (a.year > b.year) {
57 return true;
58 }
59 if (a.month > b.month) {
60 return true;
61 }
62 if (a.day > b.day) {
63 return true;
64 }
65 if (a.hour > b.hour) {
66 return true;
67 }
68 if (a.minute > b.minute) {
69 return true;
70 }
71 return a.second > b.second;
72 };
73 friend constexpr bool operator<(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
74 if (a.year < b.year) {
75 return true;
76 }
77 if (a.month < b.month) {
78 return true;
79 }
80 if (a.day < b.day) {
81 return true;
82 }
83 if (a.hour < b.hour) {
84 return true;
85 }
86 if (a.minute < b.minute) {
87 return true;
88 }
89 return a.second < b.second;
90 };
91};
92static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
93
94// This is nn::album::AlbumEntry
95struct AlbumFileEntry {
96 u64 size{}; // Size of the entry
97 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
98 AlbumFileDateTime datetime{};
99 AlbumStorage storage{};
100 ContentType content{};
101 INSERT_PADDING_BYTES(5);
102 u8 unknown{}; // Set to 1 on official SW
103};
104static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
105
106struct AlbumFileId {
107 u64 application_id{};
108 AlbumFileDateTime date{};
109 AlbumStorage storage{};
110 ContentType type{};
111 INSERT_PADDING_BYTES(0x5);
112 u8 unknown{};
113
114 friend constexpr bool operator==(const AlbumFileId&, const AlbumFileId&) = default;
115};
116static_assert(sizeof(AlbumFileId) == 0x18, "AlbumFileId is an invalid size");
117
118// This is nn::capsrv::AlbumEntry
119struct AlbumEntry {
120 u64 entry_size{};
121 AlbumFileId file_id{};
122};
123static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
124
125// This is nn::capsrv::ApplicationAlbumEntry
126struct ApplicationAlbumEntry {
127 u64 size{}; // Size of the entry
128 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
129 AlbumFileDateTime datetime{};
130 AlbumStorage storage{};
131 ContentType content{};
132 INSERT_PADDING_BYTES(5);
133 u8 unknown{1}; // Set to 1 on official SW
134};
135static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
136
137// This is nn::capsrv::ApplicationAlbumFileEntry
138struct ApplicationAlbumFileEntry {
139 ApplicationAlbumEntry entry{};
140 AlbumFileDateTime datetime{};
141 u64 unknown{};
142};
143static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
144 "ApplicationAlbumFileEntry has incorrect size.");
145
146struct ApplicationData {
147 std::array<u8, 0x400> data{};
148 u32 data_size{};
149};
150static_assert(sizeof(ApplicationData) == 0x404, "ApplicationData is an invalid size");
151
152struct ScreenShotAttribute {
153 u32 unknown_0{};
154 AlbumImageOrientation orientation{};
155 u32 unknown_1{};
156 u32 unknown_2{};
157 INSERT_PADDING_BYTES(0x30);
158};
159static_assert(sizeof(ScreenShotAttribute) == 0x40, "ScreenShotAttribute is an invalid size");
160
161struct ScreenShotDecodeOption {
162 ScreenShotDecoderFlag flags{};
163 INSERT_PADDING_BYTES(0x18);
164};
165static_assert(sizeof(ScreenShotDecodeOption) == 0x20, "ScreenShotDecodeOption is an invalid size");
166
167struct LoadAlbumScreenShotImageOutput {
168 s64 width{};
169 s64 height{};
170 ScreenShotAttribute attribute{};
171 INSERT_PADDING_BYTES(0x400);
172};
173static_assert(sizeof(LoadAlbumScreenShotImageOutput) == 0x450,
174 "LoadAlbumScreenShotImageOutput is an invalid size");
175
176struct LoadAlbumScreenShotImageOutputForApplication {
177 s64 width{};
178 s64 height{};
179 ScreenShotAttribute attribute{};
180 ApplicationData data{};
181 INSERT_PADDING_BYTES(0xAC);
182};
183static_assert(sizeof(LoadAlbumScreenShotImageOutputForApplication) == 0x500,
184 "LoadAlbumScreenShotImageOutput is an invalid size");
185
186} // 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/hid/controllers/palma.cpp b/src/core/hle/service/hid/controllers/palma.cpp
index 14c67e454..73a2a2b91 100644
--- a/src/core/hle/service/hid/controllers/palma.cpp
+++ b/src/core/hle/service/hid/controllers/palma.cpp
@@ -19,7 +19,9 @@ Controller_Palma::Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared
19 operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent"); 19 operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent");
20} 20}
21 21
22Controller_Palma::~Controller_Palma() = default; 22Controller_Palma::~Controller_Palma() {
23 service_context.CloseEvent(operation_complete_event);
24};
23 25
24void Controller_Palma::OnInit() {} 26void Controller_Palma::OnInit() {}
25 27
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 4d70006c1..929dd5f03 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -2757,6 +2757,10 @@ public:
2757 joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent"); 2757 joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent");
2758 } 2758 }
2759 2759
2760 ~HidSys() {
2761 service_context.CloseEvent(joy_detach_event);
2762 };
2763
2760private: 2764private:
2761 void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { 2765 void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
2762 LOG_WARNING(Service_HID, "called"); 2766 LOG_WARNING(Service_HID, "called");
diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.cpp b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
index ee522c36e..8c44f93e8 100644
--- a/src/core/hle/service/hid/hidbus/hidbus_base.cpp
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
@@ -13,7 +13,10 @@ HidbusBase::HidbusBase(Core::System& system_, KernelHelpers::ServiceContext& ser
13 : system(system_), service_context(service_context_) { 13 : system(system_), service_context(service_context_) {
14 send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent"); 14 send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent");
15} 15}
16HidbusBase::~HidbusBase() = default; 16
17HidbusBase::~HidbusBase() {
18 service_context.CloseEvent(send_command_async_event);
19};
17 20
18void HidbusBase::ActivateDevice() { 21void HidbusBase::ActivateDevice() {
19 if (is_activated) { 22 if (is_activated) {
diff --git a/src/core/hle/service/hle_ipc.cpp b/src/core/hle/service/hle_ipc.cpp
index f6a1e54f2..ff374ae39 100644
--- a/src/core/hle/service/hle_ipc.cpp
+++ b/src/core/hle/service/hle_ipc.cpp
@@ -23,6 +23,19 @@
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 Common::ScratchBuffer<u8>(),
31};
32static thread_local std::array read_buffer_data_x{
33 Common::ScratchBuffer<u8>(),
34 Common::ScratchBuffer<u8>(),
35 Common::ScratchBuffer<u8>(),
36};
37} // Anonymous namespace
38
26namespace Service { 39namespace Service {
27 40
28SessionRequestHandler::SessionRequestHandler(Kernel::KernelCore& kernel_, const char* service_name_) 41SessionRequestHandler::SessionRequestHandler(Kernel::KernelCore& kernel_, const char* service_name_)
@@ -328,26 +341,61 @@ std::vector<u8> HLERequestContext::ReadBufferCopy(std::size_t buffer_index) cons
328 } 341 }
329} 342}
330 343
331std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const { 344std::span<const u8> HLERequestContext::ReadBufferA(std::size_t buffer_index) const {
332 static thread_local std::array read_buffer_a{ 345 static thread_local std::array read_buffer_a{
333 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), 346 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
334 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), 347 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
348 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
335 }; 349 };
336 static thread_local std::array read_buffer_data_a{ 350
337 Common::ScratchBuffer<u8>(), 351 ASSERT_OR_EXECUTE_MSG(
338 Common::ScratchBuffer<u8>(), 352 BufferDescriptorA().size() > buffer_index, { return {}; },
339 }; 353 "BufferDescriptorA invalid buffer_index {}", buffer_index);
354 auto& read_buffer = read_buffer_a[buffer_index];
355 return read_buffer.Read(BufferDescriptorA()[buffer_index].Address(),
356 BufferDescriptorA()[buffer_index].Size(),
357 &read_buffer_data_a[buffer_index]);
358}
359
360std::span<const u8> HLERequestContext::ReadBufferX(std::size_t buffer_index) const {
340 static thread_local std::array read_buffer_x{ 361 static thread_local std::array read_buffer_x{
341 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), 362 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
342 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), 363 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
364 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
365 };
366
367 ASSERT_OR_EXECUTE_MSG(
368 BufferDescriptorX().size() > buffer_index, { return {}; },
369 "BufferDescriptorX invalid buffer_index {}", buffer_index);
370 auto& read_buffer = read_buffer_x[buffer_index];
371 return read_buffer.Read(BufferDescriptorX()[buffer_index].Address(),
372 BufferDescriptorX()[buffer_index].Size(),
373 &read_buffer_data_x[buffer_index]);
374}
375
376std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const {
377 static thread_local std::array read_buffer_a{
378 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
379 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
380 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
343 }; 381 };
344 static thread_local std::array read_buffer_data_x{ 382 static thread_local std::array read_buffer_x{
345 Common::ScratchBuffer<u8>(), 383 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
346 Common::ScratchBuffer<u8>(), 384 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
385 Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
347 }; 386 };
348 387
349 const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && 388 const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
350 BufferDescriptorA()[buffer_index].Size()}; 389 BufferDescriptorA()[buffer_index].Size()};
390 const bool is_buffer_x{BufferDescriptorX().size() > buffer_index &&
391 BufferDescriptorX()[buffer_index].Size()};
392
393 if (is_buffer_a && is_buffer_x) {
394 LOG_WARNING(Input, "Both buffer descriptors are available a.size={}, x.size={}",
395 BufferDescriptorA()[buffer_index].Size(),
396 BufferDescriptorX()[buffer_index].Size());
397 }
398
351 if (is_buffer_a) { 399 if (is_buffer_a) {
352 ASSERT_OR_EXECUTE_MSG( 400 ASSERT_OR_EXECUTE_MSG(
353 BufferDescriptorA().size() > buffer_index, { return {}; }, 401 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/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp
index 6a313a03b..f51e63564 100644
--- a/src/core/hle/service/kernel_helpers.cpp
+++ b/src/core/hle/service/kernel_helpers.cpp
@@ -21,10 +21,8 @@ ServiceContext::ServiceContext(Core::System& system_, std::string name_)
21 21
22 // Create the process. 22 // Create the process.
23 process = Kernel::KProcess::Create(kernel); 23 process = Kernel::KProcess::Create(kernel);
24 ASSERT(Kernel::KProcess::Initialize(process, system_, std::move(name_), 24 ASSERT(R_SUCCEEDED(process->Initialize(Kernel::Svc::CreateProcessParameter{},
25 Kernel::KProcess::ProcessType::KernelInternal, 25 kernel.GetSystemResourceLimit(), false)));
26 kernel.GetSystemResourceLimit())
27 .IsSuccess());
28 26
29 // Register the process. 27 // Register the process.
30 Kernel::KProcess::Register(kernel, process); 28 Kernel::KProcess::Register(kernel, process);
diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp
index 970c748ca..ba1da76ba 100644
--- a/src/core/hle/service/mii/types/core_data.cpp
+++ b/src/core/hle/service/mii/types/core_data.cpp
@@ -41,6 +41,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
41 } 41 }
42 } 42 }
43 43
44 SetDefault();
44 SetGender(gender); 45 SetGender(gender);
45 SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max)); 46 SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max));
46 SetRegionMove(0); 47 SetRegionMove(0);
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/nvnflinger/buffer_queue_core.cpp b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
index 2dbe29616..ed66f6f5b 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp
@@ -41,7 +41,7 @@ bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk)
41s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const { 41s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
42 // If DequeueBuffer is allowed to error out, we don't have to add an extra buffer. 42 // If DequeueBuffer is allowed to error out, we don't have to add an extra buffer.
43 if (!use_async_buffer) { 43 if (!use_async_buffer) {
44 return max_acquired_buffer_count; 44 return 0;
45 } 45 }
46 46
47 if (dequeue_buffer_cannot_block || async) { 47 if (dequeue_buffer_cannot_block || async) {
@@ -52,7 +52,7 @@ s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
52} 52}
53 53
54s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const { 54s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const {
55 return GetMinUndequeuedBufferCountLocked(async) + 1; 55 return GetMinUndequeuedBufferCountLocked(async);
56} 56}
57 57
58s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const { 58s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
@@ -61,7 +61,7 @@ s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
61 61
62 if (override_max_buffer_count != 0) { 62 if (override_max_buffer_count != 0) {
63 ASSERT(override_max_buffer_count >= min_buffer_count); 63 ASSERT(override_max_buffer_count >= min_buffer_count);
64 max_buffer_count = override_max_buffer_count; 64 return override_max_buffer_count;
65 } 65 }
66 66
67 // Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed 67 // Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
index dc6917d5d..6e7a49658 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
@@ -134,7 +134,7 @@ Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, St
134 const s32 max_buffer_count = core->GetMaxBufferCountLocked(async); 134 const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
135 if (async && core->override_max_buffer_count) { 135 if (async && core->override_max_buffer_count) {
136 if (core->override_max_buffer_count < max_buffer_count) { 136 if (core->override_max_buffer_count < max_buffer_count) {
137 LOG_ERROR(Service_Nvnflinger, "async mode is invalid with buffer count override"); 137 *found = BufferQueueCore::INVALID_BUFFER_SLOT;
138 return Status::BadValue; 138 return Status::BadValue;
139 } 139 }
140 } 140 }
@@ -142,7 +142,8 @@ Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, St
142 // Free up any buffers that are in slots beyond the max buffer count 142 // Free up any buffers that are in slots beyond the max buffer count
143 for (s32 s = max_buffer_count; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { 143 for (s32 s = max_buffer_count; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
144 ASSERT(slots[s].buffer_state == BufferState::Free); 144 ASSERT(slots[s].buffer_state == BufferState::Free);
145 if (slots[s].graphic_buffer != nullptr) { 145 if (slots[s].graphic_buffer != nullptr && slots[s].buffer_state == BufferState::Free &&
146 !slots[s].is_preallocated) {
146 core->FreeBufferLocked(s); 147 core->FreeBufferLocked(s);
147 *return_flags |= Status::ReleaseAllBuffers; 148 *return_flags |= Status::ReleaseAllBuffers;
148 } 149 }
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
index 469a53244..2e29bc848 100644
--- a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
@@ -46,7 +46,7 @@ Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
46 // Get bounds of where mapping is possible. 46 // Get bounds of where mapping is possible.
47 const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart()); 47 const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart());
48 const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE; 48 const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE;
49 const auto state = Kernel::KMemoryState::Io; 49 const auto state = Kernel::KMemoryState::IoMemory;
50 const auto perm = Kernel::KMemoryPermission::UserReadWrite; 50 const auto perm = Kernel::KMemoryPermission::UserReadWrite;
51 std::mt19937_64 rng{process->GetRandomEntropy(0)}; 51 std::mt19937_64 rng{process->GetRandomEntropy(0)};
52 52
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index a07c621d9..bebb45eae 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -66,7 +66,6 @@ Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_
66 "ScreenComposition", 66 "ScreenComposition",
67 [this](std::uintptr_t, s64 time, 67 [this](std::uintptr_t, s64 time,
68 std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { 68 std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
69 { const auto lock_guard = Lock(); }
70 vsync_signal.Set(); 69 vsync_signal.Set();
71 return std::chrono::nanoseconds(GetNextTicks()); 70 return std::chrono::nanoseconds(GetNextTicks());
72 }); 71 });
@@ -99,6 +98,7 @@ Nvnflinger::~Nvnflinger() {
99 } 98 }
100 99
101 ShutdownLayers(); 100 ShutdownLayers();
101 vsync_thread = {};
102 102
103 if (nvdrv) { 103 if (nvdrv) {
104 nvdrv->Close(disp_fd); 104 nvdrv->Close(disp_fd);
@@ -106,6 +106,7 @@ Nvnflinger::~Nvnflinger() {
106} 106}
107 107
108void Nvnflinger::ShutdownLayers() { 108void Nvnflinger::ShutdownLayers() {
109 const auto lock_guard = Lock();
109 for (auto& display : displays) { 110 for (auto& display : displays) {
110 for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) { 111 for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) {
111 display.GetLayer(layer).Core().NotifyShutdown(); 112 display.GetLayer(layer).Core().NotifyShutdown();
@@ -229,16 +230,6 @@ VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) {
229 return display->FindLayer(layer_id); 230 return display->FindLayer(layer_id);
230} 231}
231 232
232const VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) const {
233 const auto* const display = FindDisplay(display_id);
234
235 if (display == nullptr) {
236 return nullptr;
237 }
238
239 return display->FindLayer(layer_id);
240}
241
242VI::Layer* Nvnflinger::FindOrCreateLayer(u64 display_id, u64 layer_id) { 233VI::Layer* Nvnflinger::FindOrCreateLayer(u64 display_id, u64 layer_id) {
243 auto* const display = FindDisplay(display_id); 234 auto* const display = FindDisplay(display_id);
244 235
@@ -288,7 +279,6 @@ void Nvnflinger::Compose() {
288 auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd); 279 auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
289 ASSERT(nvdisp); 280 ASSERT(nvdisp);
290 281
291 guard->unlock();
292 Common::Rectangle<int> crop_rect{ 282 Common::Rectangle<int> crop_rect{
293 static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()), 283 static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()),
294 static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())}; 284 static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())};
@@ -299,7 +289,6 @@ void Nvnflinger::Compose() {
299 buffer.fence.fences, buffer.fence.num_fences); 289 buffer.fence.fences, buffer.fence.num_fences);
300 290
301 MicroProfileFlip(); 291 MicroProfileFlip();
302 guard->lock();
303 292
304 swap_interval = buffer.swap_interval; 293 swap_interval = buffer.swap_interval;
305 294
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index 14c783582..959d8b46b 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -117,9 +117,6 @@ private:
117 /// Finds the layer identified by the specified ID in the desired display. 117 /// Finds the layer identified by the specified ID in the desired display.
118 [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id); 118 [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
119 119
120 /// Finds the layer identified by the specified ID in the desired display.
121 [[nodiscard]] const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const;
122
123 /// Finds the layer identified by the specified ID in the desired display, 120 /// Finds the layer identified by the specified ID in the desired display,
124 /// or creates the layer if it is not found. 121 /// or creates the layer if it is not found.
125 /// To be used when the system expects the specified ID to already exist. 122 /// To be used when the system expects the specified ID to already exist.
diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp
index 5db1703d1..6a7fd72bc 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"},
@@ -141,6 +141,12 @@ public:
141 service_context.CreateEvent("IParentalControlService::RequestSuspensionEvent"); 141 service_context.CreateEvent("IParentalControlService::RequestSuspensionEvent");
142 } 142 }
143 143
144 ~IParentalControlService() {
145 service_context.CloseEvent(synchronization_event);
146 service_context.CloseEvent(unlinked_event);
147 service_context.CloseEvent(request_suspension_event);
148 };
149
144private: 150private:
145 bool CheckFreeCommunicationPermissionImpl() const { 151 bool CheckFreeCommunicationPermissionImpl() const {
146 if (states.temporary_unlocked) { 152 if (states.temporary_unlocked) {
@@ -236,6 +242,13 @@ private:
236 states.free_communication = true; 242 states.free_communication = true;
237 } 243 }
238 244
245 void ConfirmSnsPostPermission(HLERequestContext& ctx) {
246 LOG_WARNING(Service_PCTL, "(STUBBED) called");
247
248 IPC::ResponseBuilder rb{ctx, 2};
249 rb.Push(Error::ResultNoFreeCommunication);
250 }
251
239 void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) { 252 void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) {
240 const bool is_temporary_unlocked = false; 253 const bool is_temporary_unlocked = false;
241 254
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index f9cf2dda3..d92499f05 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -37,7 +37,7 @@ std::optional<Kernel::KProcess*> SearchProcessList(
37void GetApplicationPidGeneric(HLERequestContext& ctx, 37void GetApplicationPidGeneric(HLERequestContext& ctx,
38 const std::vector<Kernel::KProcess*>& process_list) { 38 const std::vector<Kernel::KProcess*>& process_list) {
39 const auto process = SearchProcessList(process_list, [](const auto& proc) { 39 const auto process = SearchProcessList(process_list, [](const auto& proc) {
40 return proc->GetProcessId() == Kernel::KProcess::ProcessIDMin; 40 return proc->GetProcessId() == Kernel::KProcess::ProcessIdMin;
41 }); 41 });
42 42
43 IPC::ResponseBuilder rb{ctx, 4}; 43 IPC::ResponseBuilder rb{ctx, 4};
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/hle/service/ptm/ts.cpp b/src/core/hle/service/ptm/ts.cpp
index ca064dd90..652f38b97 100644
--- a/src/core/hle/service/ptm/ts.cpp
+++ b/src/core/hle/service/ptm/ts.cpp
@@ -9,6 +9,35 @@
9 9
10namespace Service::PTM { 10namespace Service::PTM {
11 11
12enum class Location : u8 {
13 Internal,
14 External,
15};
16
17class ISession : public ServiceFramework<ISession> {
18public:
19 explicit ISession(Core::System& system_) : ServiceFramework{system_, "ISession"} {
20 // clang-format off
21 static const FunctionInfo functions[] = {
22 {0, nullptr, "GetTemperatureRange"},
23 {2, nullptr, "SetMeasurementMode"},
24 {4, &ISession::GetTemperature, "GetTemperature"},
25 };
26 // clang-format on
27
28 RegisterHandlers(functions);
29 }
30
31private:
32 void GetTemperature(HLERequestContext& ctx) {
33 constexpr f32 temperature = 35;
34
35 IPC::ResponseBuilder rb{ctx, 3};
36 rb.Push(ResultSuccess);
37 rb.Push(temperature);
38 }
39};
40
12TS::TS(Core::System& system_) : ServiceFramework{system_, "ts"} { 41TS::TS(Core::System& system_) : ServiceFramework{system_, "ts"} {
13 // clang-format off 42 // clang-format off
14 static const FunctionInfo functions[] = { 43 static const FunctionInfo functions[] = {
@@ -16,7 +45,7 @@ TS::TS(Core::System& system_) : ServiceFramework{system_, "ts"} {
16 {1, &TS::GetTemperature, "GetTemperature"}, 45 {1, &TS::GetTemperature, "GetTemperature"},
17 {2, nullptr, "SetMeasurementMode"}, 46 {2, nullptr, "SetMeasurementMode"},
18 {3, &TS::GetTemperatureMilliC, "GetTemperatureMilliC"}, 47 {3, &TS::GetTemperatureMilliC, "GetTemperatureMilliC"},
19 {4, nullptr, "OpenSession"}, 48 {4, &TS::OpenSession, "OpenSession"},
20 }; 49 };
21 // clang-format on 50 // clang-format on
22 51
@@ -47,4 +76,13 @@ void TS::GetTemperatureMilliC(HLERequestContext& ctx) {
47 rb.Push(temperature); 76 rb.Push(temperature);
48} 77}
49 78
79void TS::OpenSession(HLERequestContext& ctx) {
80 IPC::RequestParser rp{ctx};
81 [[maybe_unused]] const u32 device_code = rp.Pop<u32>();
82
83 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
84 rb.Push(ResultSuccess);
85 rb.PushIpcInterface<ISession>(system);
86}
87
50} // namespace Service::PTM 88} // namespace Service::PTM
diff --git a/src/core/hle/service/ptm/ts.h b/src/core/hle/service/ptm/ts.h
index c3f43d5a3..a10a91a64 100644
--- a/src/core/hle/service/ptm/ts.h
+++ b/src/core/hle/service/ptm/ts.h
@@ -14,13 +14,9 @@ public:
14 ~TS() override; 14 ~TS() override;
15 15
16private: 16private:
17 enum class Location : u8 {
18 Internal,
19 External,
20 };
21
22 void GetTemperature(HLERequestContext& ctx); 17 void GetTemperature(HLERequestContext& ctx);
23 void GetTemperatureMilliC(HLERequestContext& ctx); 18 void GetTemperatureMilliC(HLERequestContext& ctx);
19 void OpenSession(HLERequestContext& ctx);
24}; 20};
25 21
26} // namespace Service::PTM 22} // namespace Service::PTM
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index 165b97dad..ec3af80af 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -5,8 +5,13 @@
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "common/settings.h" 6#include "common/settings.h"
7#include "common/string_util.h" 7#include "common/string_util.h"
8#include "core/core.h"
9#include "core/file_sys/content_archive.h"
8#include "core/file_sys/errors.h" 10#include "core/file_sys/errors.h"
9#include "core/file_sys/system_archive/system_version.h" 11#include "core/file_sys/nca_metadata.h"
12#include "core/file_sys/registered_cache.h"
13#include "core/file_sys/romfs.h"
14#include "core/file_sys/system_archive/system_archive.h"
10#include "core/hle/service/filesystem/filesystem.h" 15#include "core/hle/service/filesystem/filesystem.h"
11#include "core/hle/service/ipc_helpers.h" 16#include "core/hle/service/ipc_helpers.h"
12#include "core/hle/service/set/set.h" 17#include "core/hle/service/set/set.h"
@@ -22,18 +27,30 @@ enum class GetFirmwareVersionType {
22 Version2, 27 Version2,
23}; 28};
24 29
25void GetFirmwareVersionImpl(HLERequestContext& ctx, GetFirmwareVersionType type) { 30void GetFirmwareVersionImpl(Core::System& system, HLERequestContext& ctx,
26 LOG_WARNING(Service_SET, "called - Using hardcoded firmware version '{}'", 31 GetFirmwareVersionType type) {
27 FileSys::SystemArchive::GetLongDisplayVersion());
28
29 ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100, 32 ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100,
30 "FirmwareVersion output buffer must be 0x100 bytes in size!"); 33 "FirmwareVersion output buffer must be 0x100 bytes in size!");
31 34
32 // Instead of using the normal procedure of checking for the real system archive and if it 35 constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809;
33 // doesn't exist, synthesizing one, I feel that that would lead to strange bugs because a 36 auto& fsc = system.GetFileSystemController();
34 // used is using a really old or really new SystemVersion title. The synthesized one ensures 37
35 // consistence (currently reports as 5.1.0-0.0) 38 // Attempt to load version data from disk
36 const auto archive = FileSys::SystemArchive::SystemVersion(); 39 const FileSys::RegisteredCache* bis_system{};
40 std::unique_ptr<FileSys::NCA> nca{};
41 FileSys::VirtualDir romfs{};
42
43 bis_system = fsc.GetSystemNANDContents();
44 if (bis_system) {
45 nca = bis_system->GetEntry(FirmwareVersionSystemDataId, FileSys::ContentRecordType::Data);
46 }
47 if (nca) {
48 romfs = FileSys::ExtractRomFS(nca->GetRomFS());
49 }
50 if (!romfs) {
51 romfs = FileSys::ExtractRomFS(
52 FileSys::SystemArchive::SynthesizeSystemArchive(FirmwareVersionSystemDataId));
53 }
37 54
38 const auto early_exit_failure = [&ctx](std::string_view desc, Result code) { 55 const auto early_exit_failure = [&ctx](std::string_view desc, Result code) {
39 LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).", 56 LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
@@ -42,13 +59,7 @@ void GetFirmwareVersionImpl(HLERequestContext& ctx, GetFirmwareVersionType type)
42 rb.Push(code); 59 rb.Push(code);
43 }; 60 };
44 61
45 if (archive == nullptr) { 62 const auto ver_file = romfs->GetFile("file");
46 early_exit_failure("The system version archive couldn't be synthesized.",
47 FileSys::ERROR_FAILED_MOUNT_ARCHIVE);
48 return;
49 }
50
51 const auto ver_file = archive->GetFile("file");
52 if (ver_file == nullptr) { 63 if (ver_file == nullptr) {
53 early_exit_failure("The system version archive didn't contain the file 'file'.", 64 early_exit_failure("The system version archive didn't contain the file 'file'.",
54 FileSys::ERROR_INVALID_ARGUMENT); 65 FileSys::ERROR_INVALID_ARGUMENT);
@@ -87,12 +98,12 @@ void SET_SYS::SetLanguageCode(HLERequestContext& ctx) {
87 98
88void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) { 99void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) {
89 LOG_DEBUG(Service_SET, "called"); 100 LOG_DEBUG(Service_SET, "called");
90 GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version1); 101 GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version1);
91} 102}
92 103
93void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) { 104void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) {
94 LOG_DEBUG(Service_SET, "called"); 105 LOG_DEBUG(Service_SET, "called");
95 GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version2); 106 GetFirmwareVersionImpl(system, ctx, GetFirmwareVersionType::Version2);
96} 107}
97 108
98void SET_SYS::GetAccountSettings(HLERequestContext& ctx) { 109void SET_SYS::GetAccountSettings(HLERequestContext& ctx) {
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/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index a06e99166..53a89cc8f 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -19,16 +19,23 @@ namespace Core::Memory {
19namespace { 19namespace {
20constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12}; 20constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
21 21
22std::string_view ExtractName(std::string_view data, std::size_t start_index, char match) { 22std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
23 std::size_t start_index, char match) {
23 auto end_index = start_index; 24 auto end_index = start_index;
24 while (data[end_index] != match) { 25 while (data[end_index] != match) {
25 ++end_index; 26 ++end_index;
26 if (end_index > data.size() || 27 if (end_index > data.size()) {
27 (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
28 return {}; 28 return {};
29 } 29 }
30 } 30 }
31 31
32 out_name_size = end_index - start_index;
33
34 // Clamp name if it's too big
35 if (out_name_size > sizeof(CheatDefinition::readable_name)) {
36 end_index = start_index + sizeof(CheatDefinition::readable_name);
37 }
38
32 return data.substr(start_index, end_index - start_index); 39 return data.substr(start_index, end_index - start_index);
33} 40}
34} // Anonymous namespace 41} // Anonymous namespace
@@ -113,7 +120,8 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
113 return {}; 120 return {};
114 } 121 }
115 122
116 const auto name = ExtractName(data, i + 1, '}'); 123 std::size_t name_size{};
124 const auto name = ExtractName(name_size, data, i + 1, '}');
117 if (name.empty()) { 125 if (name.empty()) {
118 return {}; 126 return {};
119 } 127 }
@@ -125,12 +133,13 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
125 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = 133 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
126 '\0'; 134 '\0';
127 135
128 i += name.length() + 1; 136 i += name_size + 1;
129 } else if (data[i] == '[') { 137 } else if (data[i] == '[') {
130 current_entry = out.size(); 138 current_entry = out.size();
131 out.emplace_back(); 139 out.emplace_back();
132 140
133 const auto name = ExtractName(data, i + 1, ']'); 141 std::size_t name_size{};
142 const auto name = ExtractName(name_size, data, i + 1, ']');
134 if (name.empty()) { 143 if (name.empty()) {
135 return {}; 144 return {};
136 } 145 }
@@ -142,7 +151,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
142 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = 151 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
143 '\0'; 152 '\0';
144 153
145 i += name.length() + 1; 154 i += name_size + 1;
146 } else if (::isxdigit(data[i])) { 155 } else if (::isxdigit(data[i])) {
147 if (!current_entry || out[*current_entry].definition.num_opcodes >= 156 if (!current_entry || out[*current_entry].definition.num_opcodes >=
148 out[*current_entry].definition.opcodes.size()) { 157 out[*current_entry].definition.opcodes.size()) {
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index ed875d444..5d168cbc1 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -116,7 +116,7 @@ json GetProcessorStateDataAuto(Core::System& system) {
116 Core::ARM_Interface::ThreadContext64 context{}; 116 Core::ARM_Interface::ThreadContext64 context{};
117 arm.SaveContext(context); 117 arm.SaveContext(context);
118 118
119 return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32", 119 return GetProcessorStateData(process->Is64Bit() ? "AArch64" : "AArch32",
120 GetInteger(process->GetEntryPoint()), context.sp, context.pc, 120 GetInteger(process->GetEntryPoint()), context.sp, context.pc,
121 context.pstate, context.cpu_registers); 121 context.pstate, context.cpu_registers);
122} 122}
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/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index cf51f3481..c9f903213 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -139,7 +139,7 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
139 input_thread_running = true; 139 input_thread_running = true;
140 140
141 // Max update rate is 5ms, ensure we are always able to read a bit faster 141 // Max update rate is 5ms, ensure we are always able to read a bit faster
142 constexpr int ThreadDelay = 2; 142 constexpr int ThreadDelay = 3;
143 std::vector<u8> buffer(MaxBufferSize); 143 std::vector<u8> buffer(MaxBufferSize);
144 144
145 while (!stop_token.stop_requested()) { 145 while (!stop_token.stop_requested()) {
@@ -163,6 +163,17 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
163 OnNewData(buffer); 163 OnNewData(buffer);
164 } 164 }
165 165
166 if (!vibration_queue.Empty()) {
167 VibrationValue vibration_value;
168 vibration_queue.Pop(vibration_value);
169 last_vibration_result = rumble_protocol->SendVibration(vibration_value);
170 }
171
172 // We can't keep up with vibrations. Start skipping.
173 while (vibration_queue.Size() > 6) {
174 vibration_queue.Pop();
175 }
176
166 std::this_thread::yield(); 177 std::this_thread::yield();
167 } 178 }
168 179
@@ -402,7 +413,8 @@ Common::Input::DriverResult JoyconDriver::SetVibration(const VibrationValue& vib
402 if (disable_input_thread) { 413 if (disable_input_thread) {
403 return Common::Input::DriverResult::HandleInUse; 414 return Common::Input::DriverResult::HandleInUse;
404 } 415 }
405 return rumble_protocol->SendVibration(vibration); 416 vibration_queue.Push(vibration);
417 return last_vibration_result;
406} 418}
407 419
408Common::Input::DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { 420Common::Input::DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index 335e12cc3..5355780fb 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -9,6 +9,7 @@
9#include <span> 9#include <span>
10#include <thread> 10#include <thread>
11 11
12#include "common/threadsafe_queue.h"
12#include "input_common/helpers/joycon_protocol/joycon_types.h" 13#include "input_common/helpers/joycon_protocol/joycon_types.h"
13 14
14namespace Common::Input { 15namespace Common::Input {
@@ -152,6 +153,10 @@ private:
152 SerialNumber handle_serial_number{}; // Serial number type reported by hidapi 153 SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
153 SupportedFeatures supported_features{}; 154 SupportedFeatures supported_features{};
154 155
156 /// Queue of vibration request to controllers
157 Common::Input::DriverResult last_vibration_result{Common::Input::DriverResult::Success};
158 Common::SPSCQueue<VibrationValue> vibration_queue;
159
155 // Thread related 160 // Thread related
156 mutable std::mutex mutex; 161 mutable std::mutex mutex;
157 std::jthread input_thread; 162 std::jthread input_thread;
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl.cpp b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
index d91e04446..66ecfc9f7 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
@@ -242,6 +242,7 @@ std::string EmitGLSL(const Profile& profile, const RuntimeInfo& runtime_info, IR
242 } 242 }
243 if (program.info.uses_subgroup_shuffles) { 243 if (program.info.uses_subgroup_shuffles) {
244 ctx.header += "bool shfl_in_bounds;"; 244 ctx.header += "bool shfl_in_bounds;";
245 ctx.header += "uint shfl_result;";
245 } 246 }
246 ctx.code.insert(0, ctx.header); 247 ctx.code.insert(0, ctx.header);
247 ctx.code += '}'; 248 ctx.code += '}';
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp
index 1245c9429..f9be5de1c 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp
@@ -141,7 +141,8 @@ void EmitShuffleIndex(EmitContext& ctx, IR::Inst& inst, std::string_view value,
141 const auto src_thread_id{fmt::format("({})|({})", lhs, min_thread_id)}; 141 const auto src_thread_id{fmt::format("({})|({})", lhs, min_thread_id)};
142 ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id); 142 ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id);
143 SetInBoundsFlag(ctx, inst); 143 SetInBoundsFlag(ctx, inst);
144 ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); 144 ctx.Add("shfl_result=readInvocationARB({},{});", value, src_thread_id);
145 ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value);
145} 146}
146 147
147void EmitShuffleUp(EmitContext& ctx, IR::Inst& inst, std::string_view value, std::string_view index, 148void EmitShuffleUp(EmitContext& ctx, IR::Inst& inst, std::string_view value, std::string_view index,
@@ -158,7 +159,8 @@ void EmitShuffleUp(EmitContext& ctx, IR::Inst& inst, std::string_view value, std
158 const auto src_thread_id{fmt::format("({}-{})", THREAD_ID, index)}; 159 const auto src_thread_id{fmt::format("({}-{})", THREAD_ID, index)};
159 ctx.Add("shfl_in_bounds=int({})>=int({});", src_thread_id, max_thread_id); 160 ctx.Add("shfl_in_bounds=int({})>=int({});", src_thread_id, max_thread_id);
160 SetInBoundsFlag(ctx, inst); 161 SetInBoundsFlag(ctx, inst);
161 ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); 162 ctx.Add("shfl_result=readInvocationARB({},{});", value, src_thread_id);
163 ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value);
162} 164}
163 165
164void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value, 166void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value,
@@ -175,7 +177,8 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value,
175 const auto src_thread_id{fmt::format("({}+{})", THREAD_ID, index)}; 177 const auto src_thread_id{fmt::format("({}+{})", THREAD_ID, index)};
176 ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id); 178 ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id);
177 SetInBoundsFlag(ctx, inst); 179 SetInBoundsFlag(ctx, inst);
178 ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); 180 ctx.Add("shfl_result=readInvocationARB({},{});", value, src_thread_id);
181 ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value);
179} 182}
180 183
181void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value, 184void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value,
@@ -193,7 +196,8 @@ void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view val
193 const auto src_thread_id{fmt::format("({}^{})", THREAD_ID, index)}; 196 const auto src_thread_id{fmt::format("({}^{})", THREAD_ID, index)};
194 ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id); 197 ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id);
195 SetInBoundsFlag(ctx, inst); 198 SetInBoundsFlag(ctx, inst);
196 ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); 199 ctx.Add("shfl_result=readInvocationARB({},{});", value, src_thread_id);
200 ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value);
197} 201}
198 202
199void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b, 203void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b,
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
index 2868fc57d..1d77426e0 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
@@ -111,16 +111,33 @@ Id GetCbuf(EmitContext& ctx, Id result_type, Id UniformDefinitions::*member_ptr,
111 } else if (element_size > 1) { 111 } else if (element_size > 1) {
112 const u32 log2_element_size{static_cast<u32>(std::countr_zero(element_size))}; 112 const u32 log2_element_size{static_cast<u32>(std::countr_zero(element_size))};
113 const Id shift{ctx.Const(log2_element_size)}; 113 const Id shift{ctx.Const(log2_element_size)};
114 buffer_offset = ctx.OpShiftRightArithmetic(ctx.U32[1], ctx.Def(offset), shift); 114 buffer_offset = ctx.OpShiftRightLogical(ctx.U32[1], ctx.Def(offset), shift);
115 } else { 115 } else {
116 buffer_offset = ctx.Def(offset); 116 buffer_offset = ctx.Def(offset);
117 } 117 }
118 if (!binding.IsImmediate()) { 118 if (!binding.IsImmediate()) {
119 return ctx.OpFunctionCall(result_type, indirect_func, ctx.Def(binding), buffer_offset); 119 return ctx.OpFunctionCall(result_type, indirect_func, ctx.Def(binding), buffer_offset);
120 } 120 }
121
121 const Id cbuf{ctx.cbufs[binding.U32()].*member_ptr}; 122 const Id cbuf{ctx.cbufs[binding.U32()].*member_ptr};
122 const Id access_chain{ctx.OpAccessChain(uniform_type, cbuf, ctx.u32_zero_value, buffer_offset)}; 123 const Id access_chain{ctx.OpAccessChain(uniform_type, cbuf, ctx.u32_zero_value, buffer_offset)};
123 return ctx.OpLoad(result_type, access_chain); 124 const Id val = ctx.OpLoad(result_type, access_chain);
125
126 if (offset.IsImmediate() || !ctx.profile.has_broken_robust) {
127 return val;
128 }
129
130 const auto is_float = UniformDefinitions::IsFloat(member_ptr);
131 const auto num_elements = UniformDefinitions::NumElements(member_ptr);
132 const std::array zero_vec{
133 is_float ? ctx.Const(0.0f) : ctx.Const(0u),
134 is_float ? ctx.Const(0.0f) : ctx.Const(0u),
135 is_float ? ctx.Const(0.0f) : ctx.Const(0u),
136 is_float ? ctx.Const(0.0f) : ctx.Const(0u),
137 };
138 const Id cond = ctx.OpULessThanEqual(ctx.TypeBool(), buffer_offset, ctx.Const(0xFFFFu));
139 const Id zero = ctx.OpCompositeConstruct(result_type, std::span(zero_vec.data(), num_elements));
140 return ctx.OpSelect(result_type, cond, val, zero);
124} 141}
125 142
126Id GetCbufU32(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { 143Id GetCbufU32(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
@@ -138,7 +155,7 @@ Id GetCbufElement(EmitContext& ctx, Id vector, const IR::Value& offset, u32 inde
138 const u32 element{(offset.U32() / 4) % 4 + index_offset}; 155 const u32 element{(offset.U32() / 4) % 4 + index_offset};
139 return ctx.OpCompositeExtract(ctx.U32[1], vector, element); 156 return ctx.OpCompositeExtract(ctx.U32[1], vector, element);
140 } 157 }
141 const Id shift{ctx.OpShiftRightArithmetic(ctx.U32[1], ctx.Def(offset), ctx.Const(2u))}; 158 const Id shift{ctx.OpShiftRightLogical(ctx.U32[1], ctx.Def(offset), ctx.Const(2u))};
142 Id element{ctx.OpBitwiseAnd(ctx.U32[1], shift, ctx.Const(3u))}; 159 Id element{ctx.OpBitwiseAnd(ctx.U32[1], shift, ctx.Const(3u))};
143 if (index_offset > 0) { 160 if (index_offset > 0) {
144 element = ctx.OpIAdd(ctx.U32[1], element, ctx.Const(index_offset)); 161 element = ctx.OpIAdd(ctx.U32[1], element, ctx.Const(index_offset));
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index 7c49fd504..1aa79863d 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -64,6 +64,42 @@ struct UniformDefinitions {
64 Id F32{}; 64 Id F32{};
65 Id U32x2{}; 65 Id U32x2{};
66 Id U32x4{}; 66 Id U32x4{};
67
68 constexpr static size_t NumElements(Id UniformDefinitions::*member_ptr) {
69 if (member_ptr == &UniformDefinitions::U8) {
70 return 1;
71 }
72 if (member_ptr == &UniformDefinitions::S8) {
73 return 1;
74 }
75 if (member_ptr == &UniformDefinitions::U16) {
76 return 1;
77 }
78 if (member_ptr == &UniformDefinitions::S16) {
79 return 1;
80 }
81 if (member_ptr == &UniformDefinitions::U32) {
82 return 1;
83 }
84 if (member_ptr == &UniformDefinitions::F32) {
85 return 1;
86 }
87 if (member_ptr == &UniformDefinitions::U32x2) {
88 return 2;
89 }
90 if (member_ptr == &UniformDefinitions::U32x4) {
91 return 4;
92 }
93 ASSERT(false);
94 return 1;
95 }
96
97 constexpr static bool IsFloat(Id UniformDefinitions::*member_ptr) {
98 if (member_ptr == &UniformDefinitions::F32) {
99 return true;
100 }
101 return false;
102 }
67}; 103};
68 104
69struct StorageTypeDefinition { 105struct StorageTypeDefinition {
diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h
index 9ca97f6a4..38d820db2 100644
--- a/src/shader_recompiler/profile.h
+++ b/src/shader_recompiler/profile.h
@@ -9,7 +9,6 @@ namespace Shader {
9 9
10struct Profile { 10struct Profile {
11 u32 supported_spirv{0x00010000}; 11 u32 supported_spirv{0x00010000};
12
13 bool unified_descriptor_binding{}; 12 bool unified_descriptor_binding{};
14 bool support_descriptor_aliasing{}; 13 bool support_descriptor_aliasing{};
15 bool support_int8{}; 14 bool support_int8{};
@@ -82,6 +81,9 @@ struct Profile {
82 bool has_broken_spirv_subgroup_mask_vector_extract_dynamic{}; 81 bool has_broken_spirv_subgroup_mask_vector_extract_dynamic{};
83 82
84 u32 gl_max_compute_smem_size{}; 83 u32 gl_max_compute_smem_size{};
84
85 /// Maxwell and earlier nVidia architectures have broken robust support
86 bool has_broken_robust{};
85}; 87};
86 88
87} // namespace Shader 89} // namespace Shader
diff --git a/src/tests/common/unique_function.cpp b/src/tests/common/unique_function.cpp
index f7a23e876..42e6ade09 100644
--- a/src/tests/common/unique_function.cpp
+++ b/src/tests/common/unique_function.cpp
@@ -46,8 +46,8 @@ TEST_CASE("UniqueFunction", "[common]") {
46 Noisy noisy; 46 Noisy noisy;
47 REQUIRE(noisy.state == "Default constructed"); 47 REQUIRE(noisy.state == "Default constructed");
48 48
49 Common::UniqueFunction<void> func = [noisy = std::move(noisy)] { 49 Common::UniqueFunction<void> func = [noisy_inner = std::move(noisy)] {
50 REQUIRE(noisy.state == "Move constructed"); 50 REQUIRE(noisy_inner.state == "Move constructed");
51 }; 51 };
52 REQUIRE(noisy.state == "Moved away"); 52 REQUIRE(noisy.state == "Moved away");
53 func(); 53 func();
@@ -101,7 +101,7 @@ TEST_CASE("UniqueFunction", "[common]") {
101 }; 101 };
102 Foo object{&num_destroyed}; 102 Foo object{&num_destroyed};
103 { 103 {
104 Common::UniqueFunction<void> func = [object = std::move(object)] {}; 104 Common::UniqueFunction<void> func = [object_inner = std::move(object)] {};
105 REQUIRE(num_destroyed == 0); 105 REQUIRE(num_destroyed == 0);
106 } 106 }
107 REQUIRE(num_destroyed == 1); 107 REQUIRE(num_destroyed == 1);
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 9e90c587c..081a574e8 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) {
@@ -1044,8 +1067,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
1044 1067
1045template <class P> 1068template <class P>
1046void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) { 1069void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
1047 do { 1070 BufferOperations([&]() {
1048 channel_state->has_deleted_buffers = false;
1049 if (is_indexed) { 1071 if (is_indexed) {
1050 UpdateIndexBuffer(); 1072 UpdateIndexBuffer();
1051 } 1073 }
@@ -1059,14 +1081,16 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
1059 if (current_draw_indirect) { 1081 if (current_draw_indirect) {
1060 UpdateDrawIndirect(); 1082 UpdateDrawIndirect();
1061 } 1083 }
1062 } while (channel_state->has_deleted_buffers); 1084 });
1063} 1085}
1064 1086
1065template <class P> 1087template <class P>
1066void BufferCache<P>::DoUpdateComputeBuffers() { 1088void BufferCache<P>::DoUpdateComputeBuffers() {
1067 UpdateComputeUniformBuffers(); 1089 BufferOperations([&]() {
1068 UpdateComputeStorageBuffers(); 1090 UpdateComputeUniformBuffers();
1069 UpdateComputeTextureBuffers(); 1091 UpdateComputeStorageBuffers();
1092 UpdateComputeTextureBuffers();
1093 });
1070} 1094}
1071 1095
1072template <class P> 1096template <class P>
@@ -1201,16 +1225,11 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
1201 1225
1202template <class P> 1226template <class P>
1203void BufferCache<P>::UpdateStorageBuffers(size_t stage) { 1227void 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) { 1228 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
1206 // Resolve buffer 1229 // Resolve buffer
1207 Binding& binding = channel_state->storage_buffers[stage][index]; 1230 Binding& binding = channel_state->storage_buffers[stage][index];
1208 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1231 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1209 binding.buffer_id = buffer_id; 1232 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 }); 1233 });
1215} 1234}
1216 1235
@@ -1219,10 +1238,6 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
1219 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { 1238 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
1220 Binding& binding = channel_state->texture_buffers[stage][index]; 1239 Binding& binding = channel_state->texture_buffers[stage][index];
1221 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1240 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 }); 1241 });
1227} 1242}
1228 1243
@@ -1252,7 +1267,6 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
1252 .size = size, 1267 .size = size,
1253 .buffer_id = buffer_id, 1268 .buffer_id = buffer_id,
1254 }; 1269 };
1255 MarkWrittenBuffer(buffer_id, *cpu_addr, size);
1256} 1270}
1257 1271
1258template <class P> 1272template <class P>
@@ -1279,10 +1293,6 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
1279 // Resolve buffer 1293 // Resolve buffer
1280 Binding& binding = channel_state->compute_storage_buffers[index]; 1294 Binding& binding = channel_state->compute_storage_buffers[index];
1281 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1295 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 }); 1296 });
1287} 1297}
1288 1298
@@ -1291,18 +1301,11 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
1291 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { 1301 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
1292 Binding& binding = channel_state->compute_texture_buffers[index]; 1302 Binding& binding = channel_state->compute_texture_buffers[index];
1293 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1303 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 }); 1304 });
1299} 1305}
1300 1306
1301template <class P> 1307template <class P>
1302void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { 1308void 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); 1309 memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
1307 1310
1308 const IntervalType base_interval{cpu_addr, cpu_addr + size}; 1311 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/fence_manager.h b/src/video_core/fence_manager.h
index 805a89900..c0e6471fe 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -86,7 +86,10 @@ public:
86 uncommitted_operations.emplace_back(std::move(func)); 86 uncommitted_operations.emplace_back(std::move(func));
87 } 87 }
88 pending_operations.emplace_back(std::move(uncommitted_operations)); 88 pending_operations.emplace_back(std::move(uncommitted_operations));
89 QueueFence(new_fence); 89 {
90 std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
91 QueueFence(new_fence);
92 }
90 if (!delay_fence) { 93 if (!delay_fence) {
91 func(); 94 func();
92 } 95 }
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/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index 6b912027f..cd2549232 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -19,6 +19,8 @@ set(SHADER_FILES
19 block_linear_unswizzle_2d.comp 19 block_linear_unswizzle_2d.comp
20 block_linear_unswizzle_3d.comp 20 block_linear_unswizzle_3d.comp
21 convert_abgr8_to_d24s8.frag 21 convert_abgr8_to_d24s8.frag
22 convert_abgr8_to_d32f.frag
23 convert_d32f_to_abgr8.frag
22 convert_d24s8_to_abgr8.frag 24 convert_d24s8_to_abgr8.frag
23 convert_depth_to_float.frag 25 convert_depth_to_float.frag
24 convert_float_to_depth.frag 26 convert_float_to_depth.frag
diff --git a/src/video_core/host_shaders/convert_abgr8_to_d32f.frag b/src/video_core/host_shaders/convert_abgr8_to_d32f.frag
new file mode 100644
index 000000000..095b910c2
--- /dev/null
+++ b/src/video_core/host_shaders/convert_abgr8_to_d32f.frag
@@ -0,0 +1,15 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#version 450
5
6layout(binding = 0) uniform sampler2D color_texture;
7
8void main() {
9 ivec2 coord = ivec2(gl_FragCoord.xy);
10 vec4 color = texelFetch(color_texture, coord, 0).abgr;
11
12 float value = color.a * (color.r + color.g + color.b) / 3.0f;
13
14 gl_FragDepth = value;
15}
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_d32f_to_abgr8.frag b/src/video_core/host_shaders/convert_d32f_to_abgr8.frag
new file mode 100644
index 000000000..4e5a9f955
--- /dev/null
+++ b/src/video_core/host_shaders/convert_d32f_to_abgr8.frag
@@ -0,0 +1,14 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#version 450
5
6layout(binding = 0) uniform sampler2D depth_tex;
7
8layout(location = 0) out vec4 color;
9
10void main() {
11 ivec2 coord = ivec2(gl_FragCoord.xy);
12 float depth = texelFetch(depth_tex, coord, 0).r;
13 color = vec4(depth, depth, depth, 1.0);
14}
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 1032c9d12..c3db09424 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -8,7 +8,9 @@
8#include "common/settings.h" 8#include "common/settings.h"
9#include "video_core/host_shaders/blit_color_float_frag_spv.h" 9#include "video_core/host_shaders/blit_color_float_frag_spv.h"
10#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" 10#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
11#include "video_core/host_shaders/convert_abgr8_to_d32f_frag_spv.h"
11#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" 12#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
13#include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h"
12#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" 14#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
13#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h" 15#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
14#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h" 16#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
@@ -433,6 +435,8 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
433 convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), 435 convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
434 convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), 436 convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
435 convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), 437 convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
438 convert_abgr8_to_d32f_frag(BuildShader(device, CONVERT_ABGR8_TO_D32F_FRAG_SPV)),
439 convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
436 convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)), 440 convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
437 convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)), 441 convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
438 linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)), 442 linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
@@ -557,6 +561,20 @@ void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
557 Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view); 561 Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
558} 562}
559 563
564void BlitImageHelper::ConvertABGR8ToD32F(const Framebuffer* dst_framebuffer,
565 const ImageView& src_image_view) {
566 ConvertPipelineDepthTargetEx(convert_abgr8_to_d32f_pipeline, dst_framebuffer->RenderPass(),
567 convert_abgr8_to_d32f_frag);
568 Convert(*convert_abgr8_to_d32f_pipeline, dst_framebuffer, src_image_view);
569}
570
571void BlitImageHelper::ConvertD32FToABGR8(const Framebuffer* dst_framebuffer,
572 ImageView& src_image_view) {
573 ConvertPipelineColorTargetEx(convert_d32f_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
574 convert_d32f_to_abgr8_frag);
575 ConvertDepthStencil(*convert_d32f_to_abgr8_pipeline, dst_framebuffer, src_image_view);
576}
577
560void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, 578void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
561 ImageView& src_image_view) { 579 ImageView& src_image_view) {
562 ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(), 580 ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
@@ -609,6 +627,8 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
609 const VkPipelineLayout layout = *clear_color_pipeline_layout; 627 const VkPipelineLayout layout = *clear_color_pipeline_layout;
610 scheduler.RequestRenderpass(dst_framebuffer); 628 scheduler.RequestRenderpass(dst_framebuffer);
611 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());
612 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); 632 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
613 BindBlitState(cmdbuf, dst_region); 633 BindBlitState(cmdbuf, dst_region);
614 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth); 634 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
@@ -865,7 +885,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
865 .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, 885 .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
866 .pNext = nullptr, 886 .pNext = nullptr,
867 .flags = 0, 887 .flags = 0,
868 .depthTestEnable = VK_FALSE, 888 .depthTestEnable = key.depth_clear,
869 .depthWriteEnable = key.depth_clear, 889 .depthWriteEnable = key.depth_clear,
870 .depthCompareOp = VK_COMPARE_OP_ALWAYS, 890 .depthCompareOp = VK_COMPARE_OP_ALWAYS,
871 .depthBoundsTestEnable = VK_FALSE, 891 .depthBoundsTestEnable = VK_FALSE,
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h
index dcfe217aa..b2104a59e 100644
--- a/src/video_core/renderer_vulkan/blit_image.h
+++ b/src/video_core/renderer_vulkan/blit_image.h
@@ -67,6 +67,10 @@ public:
67 67
68 void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); 68 void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
69 69
70 void ConvertABGR8ToD32F(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
71
72 void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
73
70 void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); 74 void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
71 75
72 void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); 76 void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
@@ -128,6 +132,8 @@ private:
128 vk::ShaderModule convert_depth_to_float_frag; 132 vk::ShaderModule convert_depth_to_float_frag;
129 vk::ShaderModule convert_float_to_depth_frag; 133 vk::ShaderModule convert_float_to_depth_frag;
130 vk::ShaderModule convert_abgr8_to_d24s8_frag; 134 vk::ShaderModule convert_abgr8_to_d24s8_frag;
135 vk::ShaderModule convert_abgr8_to_d32f_frag;
136 vk::ShaderModule convert_d32f_to_abgr8_frag;
131 vk::ShaderModule convert_d24s8_to_abgr8_frag; 137 vk::ShaderModule convert_d24s8_to_abgr8_frag;
132 vk::ShaderModule convert_s8d24_to_abgr8_frag; 138 vk::ShaderModule convert_s8d24_to_abgr8_frag;
133 vk::Sampler linear_sampler; 139 vk::Sampler linear_sampler;
@@ -146,6 +152,8 @@ private:
146 vk::Pipeline convert_d16_to_r16_pipeline; 152 vk::Pipeline convert_d16_to_r16_pipeline;
147 vk::Pipeline convert_r16_to_d16_pipeline; 153 vk::Pipeline convert_r16_to_d16_pipeline;
148 vk::Pipeline convert_abgr8_to_d24s8_pipeline; 154 vk::Pipeline convert_abgr8_to_d24s8_pipeline;
155 vk::Pipeline convert_abgr8_to_d32f_pipeline;
156 vk::Pipeline convert_d32f_to_abgr8_pipeline;
149 vk::Pipeline convert_d24s8_to_abgr8_pipeline; 157 vk::Pipeline convert_d24s8_to_abgr8_pipeline;
150 vk::Pipeline convert_s8d24_to_abgr8_pipeline; 158 vk::Pipeline convert_s8d24_to_abgr8_pipeline;
151}; 159};
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.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index c4c30d807..7e7a80740 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -132,12 +132,16 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
132 const bool use_accelerated = 132 const bool use_accelerated =
133 rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); 133 rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
134 const bool is_srgb = use_accelerated && screen_info.is_srgb; 134 const bool is_srgb = use_accelerated && screen_info.is_srgb;
135 RenderScreenshot(*framebuffer, use_accelerated);
136 135
137 Frame* frame = present_manager.GetRenderFrame(); 136 {
138 blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb); 137 std::scoped_lock lock{rasterizer.LockCaches()};
139 scheduler.Flush(*frame->render_ready); 138 RenderScreenshot(*framebuffer, use_accelerated);
140 present_manager.Present(frame); 139
140 Frame* frame = present_manager.GetRenderFrame();
141 blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb);
142 scheduler.Flush(*frame->render_ready);
143 present_manager.Present(frame);
144 }
141 145
142 gpu.RendererFrameEndNotify(); 146 gpu.RendererFrameEndNotify();
143 rasterizer.TickFrame(); 147 rasterizer.TickFrame();
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_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index a1ec1a100..22bf8cc77 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -356,7 +356,11 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
356 .has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY, 356 .has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY,
357 .ignore_nan_fp_comparisons = false, 357 .ignore_nan_fp_comparisons = false,
358 .has_broken_spirv_subgroup_mask_vector_extract_dynamic = 358 .has_broken_spirv_subgroup_mask_vector_extract_dynamic =
359 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY}; 359 driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
360 .has_broken_robust =
361 device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal,
362 };
363
360 host_info = Shader::HostTranslateInfo{ 364 host_info = Shader::HostTranslateInfo{
361 .support_float64 = device.IsFloat64Supported(), 365 .support_float64 = device.IsFloat64Supported(),
362 .support_float16 = device.IsFloat16Supported(), 366 .support_float16 = device.IsFloat16Supported(),
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..059b7cb40 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -13,6 +13,7 @@
13#include "common/microprofile.h" 13#include "common/microprofile.h"
14#include "common/scope_exit.h" 14#include "common/scope_exit.h"
15#include "common/settings.h" 15#include "common/settings.h"
16#include "video_core/buffer_cache/buffer_cache.h"
16#include "video_core/control/channel_state.h" 17#include "video_core/control/channel_state.h"
17#include "video_core/engines/draw_manager.h" 18#include "video_core/engines/draw_manager.h"
18#include "video_core/engines/kepler_compute.h" 19#include "video_core/engines/kepler_compute.h"
@@ -198,7 +199,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
198 if (!pipeline) { 199 if (!pipeline) {
199 return; 200 return;
200 } 201 }
201 std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; 202 std::scoped_lock lock{LockCaches()};
202 // update engine as channel may be different. 203 // update engine as channel may be different.
203 pipeline->SetEngine(maxwell3d, gpu_memory); 204 pipeline->SetEngine(maxwell3d, gpu_memory);
204 pipeline->Configure(is_indexed); 205 pipeline->Configure(is_indexed);
@@ -285,6 +286,7 @@ void RasterizerVulkan::DrawTexture() {
285 286
286 query_cache.NotifySegment(true); 287 query_cache.NotifySegment(true);
287 288
289 std::scoped_lock l{texture_cache.mutex};
288 texture_cache.SynchronizeGraphicsDescriptors(); 290 texture_cache.SynchronizeGraphicsDescriptors();
289 texture_cache.UpdateRenderTargets(false); 291 texture_cache.UpdateRenderTargets(false);
290 292
@@ -422,7 +424,8 @@ void RasterizerVulkan::Clear(u32 layer_count) {
422 return; 424 return;
423 } 425 }
424 426
425 if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) { 427 if (use_stencil && framebuffer->HasAspectStencilBit() && regs.stencil_front_mask != 0xFF &&
428 regs.stencil_front_mask != 0) {
426 Region2D dst_region = { 429 Region2D dst_region = {
427 Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, 430 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), 431 Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
@@ -707,6 +710,7 @@ void RasterizerVulkan::TiledCacheBarrier() {
707} 710}
708 711
709void RasterizerVulkan::FlushCommands() { 712void RasterizerVulkan::FlushCommands() {
713 std::scoped_lock lock{LockCaches()};
710 if (draw_counter == 0) { 714 if (draw_counter == 0) {
711 return; 715 return;
712 } 716 }
@@ -804,6 +808,7 @@ void RasterizerVulkan::FlushWork() {
804 if ((++draw_counter & 7) != 7) { 808 if ((++draw_counter & 7) != 7) {
805 return; 809 return;
806 } 810 }
811 std::scoped_lock lock{LockCaches()};
807 if (draw_counter < DRAWS_TO_DISPATCH) { 812 if (draw_counter < DRAWS_TO_DISPATCH) {
808 // Send recorded tasks to the worker thread 813 // Send recorded tasks to the worker thread
809 scheduler.DispatchWork(); 814 scheduler.DispatchWork();
@@ -974,6 +979,19 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs
974 if (!state_tracker.TouchScissors()) { 979 if (!state_tracker.TouchScissors()) {
975 return; 980 return;
976 } 981 }
982 if (!regs.viewport_scale_offset_enabled) {
983 const auto x = static_cast<float>(regs.surface_clip.x);
984 const auto y = static_cast<float>(regs.surface_clip.y);
985 const auto width = static_cast<float>(regs.surface_clip.width);
986 const auto height = static_cast<float>(regs.surface_clip.height);
987 VkRect2D scissor;
988 scissor.offset.x = static_cast<u32>(x);
989 scissor.offset.y = static_cast<u32>(y);
990 scissor.extent.width = static_cast<u32>(width != 0.0f ? width : 1.0f);
991 scissor.extent.height = static_cast<u32>(height != 0.0f ? height : 1.0f);
992 scheduler.Record([scissor](vk::CommandBuffer cmdbuf) { cmdbuf.SetScissor(0, scissor); });
993 return;
994 }
977 u32 up_scale = 1; 995 u32 up_scale = 1;
978 u32 down_shift = 0; 996 u32 down_shift = 0;
979 if (texture_cache.IsRescaling()) { 997 if (texture_cache.IsRescaling()) {
@@ -1485,7 +1503,7 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
1485void RasterizerVulkan::InitializeChannel(Tegra::Control::ChannelState& channel) { 1503void RasterizerVulkan::InitializeChannel(Tegra::Control::ChannelState& channel) {
1486 CreateChannel(channel); 1504 CreateChannel(channel);
1487 { 1505 {
1488 std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; 1506 std::scoped_lock lock{LockCaches()};
1489 texture_cache.CreateChannel(channel); 1507 texture_cache.CreateChannel(channel);
1490 buffer_cache.CreateChannel(channel); 1508 buffer_cache.CreateChannel(channel);
1491 } 1509 }
@@ -1498,7 +1516,7 @@ void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) {
1498 const s32 channel_id = channel.bind_id; 1516 const s32 channel_id = channel.bind_id;
1499 BindToChannel(channel_id); 1517 BindToChannel(channel_id);
1500 { 1518 {
1501 std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; 1519 std::scoped_lock lock{LockCaches()};
1502 texture_cache.BindToChannel(channel_id); 1520 texture_cache.BindToChannel(channel_id);
1503 buffer_cache.BindToChannel(channel_id); 1521 buffer_cache.BindToChannel(channel_id);
1504 } 1522 }
@@ -1511,7 +1529,7 @@ void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) {
1511void RasterizerVulkan::ReleaseChannel(s32 channel_id) { 1529void RasterizerVulkan::ReleaseChannel(s32 channel_id) {
1512 EraseChannel(channel_id); 1530 EraseChannel(channel_id);
1513 { 1531 {
1514 std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; 1532 std::scoped_lock lock{LockCaches()};
1515 texture_cache.EraseChannel(channel_id); 1533 texture_cache.EraseChannel(channel_id);
1516 buffer_cache.EraseChannel(channel_id); 1534 buffer_cache.EraseChannel(channel_id);
1517 } 1535 }
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index ad069556c..ce3dfbaab 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -133,6 +133,10 @@ public:
133 133
134 void ReleaseChannel(s32 channel_id) override; 134 void ReleaseChannel(s32 channel_id) override;
135 135
136 std::scoped_lock<std::recursive_mutex, std::recursive_mutex> LockCaches() {
137 return std::scoped_lock{buffer_cache.mutex, texture_cache.mutex};
138 }
139
136private: 140private:
137 static constexpr size_t MAX_TEXTURES = 192; 141 static constexpr size_t MAX_TEXTURES = 192;
138 static constexpr size_t MAX_IMAGES = 48; 142 static constexpr size_t MAX_IMAGES = 48;
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_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index d56558a83..daaea2979 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -190,7 +190,7 @@ void SetupDirtySpecialOps(Tables& tables) {
190void SetupDirtyViewportSwizzles(Tables& tables) { 190void SetupDirtyViewportSwizzles(Tables& tables) {
191 static constexpr size_t swizzle_offset = 6; 191 static constexpr size_t swizzle_offset = 6;
192 for (size_t index = 0; index < Regs::NumViewports; ++index) { 192 for (size_t index = 0; index < Regs::NumViewports; ++index) {
193 tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] = 193 tables[1][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] =
194 ViewportSwizzles; 194 ViewportSwizzles;
195 } 195 }
196} 196}
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 71fdec809..de34f6d49 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;
@@ -1193,6 +1194,11 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
1193 return blit_image_helper.ConvertD16ToR16(dst, src_view); 1194 return blit_image_helper.ConvertD16ToR16(dst, src_view);
1194 } 1195 }
1195 break; 1196 break;
1197 case PixelFormat::A8B8G8R8_SRGB:
1198 if (src_view.format == PixelFormat::D32_FLOAT) {
1199 return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
1200 }
1201 break;
1196 case PixelFormat::A8B8G8R8_UNORM: 1202 case PixelFormat::A8B8G8R8_UNORM:
1197 if (src_view.format == PixelFormat::S8_UINT_D24_UNORM) { 1203 if (src_view.format == PixelFormat::S8_UINT_D24_UNORM) {
1198 return blit_image_helper.ConvertD24S8ToABGR8(dst, src_view); 1204 return blit_image_helper.ConvertD24S8ToABGR8(dst, src_view);
@@ -1200,6 +1206,19 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
1200 if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) { 1206 if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
1201 return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view); 1207 return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
1202 } 1208 }
1209 if (src_view.format == PixelFormat::D32_FLOAT) {
1210 return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
1211 }
1212 break;
1213 case PixelFormat::B8G8R8A8_SRGB:
1214 if (src_view.format == PixelFormat::D32_FLOAT) {
1215 return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
1216 }
1217 break;
1218 case PixelFormat::B8G8R8A8_UNORM:
1219 if (src_view.format == PixelFormat::D32_FLOAT) {
1220 return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
1221 }
1203 break; 1222 break;
1204 case PixelFormat::R32_FLOAT: 1223 case PixelFormat::R32_FLOAT:
1205 if (src_view.format == PixelFormat::D32_FLOAT) { 1224 if (src_view.format == PixelFormat::D32_FLOAT) {
@@ -1218,6 +1237,12 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
1218 } 1237 }
1219 break; 1238 break;
1220 case PixelFormat::D32_FLOAT: 1239 case PixelFormat::D32_FLOAT:
1240 if (src_view.format == PixelFormat::A8B8G8R8_UNORM ||
1241 src_view.format == PixelFormat::B8G8R8A8_UNORM ||
1242 src_view.format == PixelFormat::A8B8G8R8_SRGB ||
1243 src_view.format == PixelFormat::B8G8R8A8_SRGB) {
1244 return blit_image_helper.ConvertABGR8ToD32F(dst, src_view);
1245 }
1221 if (src_view.format == PixelFormat::R32_FLOAT) { 1246 if (src_view.format == PixelFormat::R32_FLOAT) {
1222 return blit_image_helper.ConvertR32ToD32(dst, src_view); 1247 return blit_image_helper.ConvertR32ToD32(dst, src_view);
1223 } 1248 }
@@ -1526,15 +1551,15 @@ bool Image::IsRescaled() const noexcept {
1526} 1551}
1527 1552
1528bool Image::ScaleUp(bool ignore) { 1553bool Image::ScaleUp(bool ignore) {
1554 const auto& resolution = runtime->resolution;
1555 if (!resolution.active) {
1556 return false;
1557 }
1529 if (True(flags & ImageFlagBits::Rescaled)) { 1558 if (True(flags & ImageFlagBits::Rescaled)) {
1530 return false; 1559 return false;
1531 } 1560 }
1532 ASSERT(info.type != ImageType::Linear); 1561 ASSERT(info.type != ImageType::Linear);
1533 flags |= ImageFlagBits::Rescaled; 1562 flags |= ImageFlagBits::Rescaled;
1534 const auto& resolution = runtime->resolution;
1535 if (!resolution.active) {
1536 return false;
1537 }
1538 has_scaled = true; 1563 has_scaled = true;
1539 if (!scaled_image) { 1564 if (!scaled_image) {
1540 const bool is_2d = info.type == ImageType::e2D; 1565 const bool is_2d = info.type == ImageType::e2D;
@@ -1563,15 +1588,15 @@ bool Image::ScaleUp(bool ignore) {
1563} 1588}
1564 1589
1565bool Image::ScaleDown(bool ignore) { 1590bool Image::ScaleDown(bool ignore) {
1591 const auto& resolution = runtime->resolution;
1592 if (!resolution.active) {
1593 return false;
1594 }
1566 if (False(flags & ImageFlagBits::Rescaled)) { 1595 if (False(flags & ImageFlagBits::Rescaled)) {
1567 return false; 1596 return false;
1568 } 1597 }
1569 ASSERT(info.type != ImageType::Linear); 1598 ASSERT(info.type != ImageType::Linear);
1570 flags &= ~ImageFlagBits::Rescaled; 1599 flags &= ~ImageFlagBits::Rescaled;
1571 const auto& resolution = runtime->resolution;
1572 if (!resolution.active) {
1573 return false;
1574 }
1575 current_image = *original_image; 1600 current_image = *original_image;
1576 if (ignore) { 1601 if (ignore) {
1577 return true; 1602 return true;
@@ -2009,4 +2034,32 @@ void TextureCacheRuntime::AccelerateImageUpload(
2009 ASSERT(false); 2034 ASSERT(false);
2010} 2035}
2011 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](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
2012} // 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..e518756d2 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -83,12 +83,6 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
83 83
84} // namespace Alternatives 84} // namespace Alternatives
85 85
86enum class NvidiaArchitecture {
87 AmpereOrNewer,
88 Turing,
89 VoltaOrOlder,
90};
91
92template <typename T> 86template <typename T>
93void SetNext(void**& next, T& data) { 87void SetNext(void**& next, T& data) {
94 *next = &data; 88 *next = &data;
@@ -200,6 +194,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
200 VK_FORMAT_BC7_UNORM_BLOCK, 194 VK_FORMAT_BC7_UNORM_BLOCK,
201 VK_FORMAT_D16_UNORM, 195 VK_FORMAT_D16_UNORM,
202 VK_FORMAT_D16_UNORM_S8_UINT, 196 VK_FORMAT_D16_UNORM_S8_UINT,
197 VK_FORMAT_X8_D24_UNORM_PACK32,
203 VK_FORMAT_D24_UNORM_S8_UINT, 198 VK_FORMAT_D24_UNORM_S8_UINT,
204 VK_FORMAT_D32_SFLOAT, 199 VK_FORMAT_D32_SFLOAT,
205 VK_FORMAT_D32_SFLOAT_S8_UINT, 200 VK_FORMAT_D32_SFLOAT_S8_UINT,
@@ -321,13 +316,38 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
321 physical.GetProperties2(physical_properties); 316 physical.GetProperties2(physical_properties);
322 if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) { 317 if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) {
323 // Only Ampere and newer support this feature 318 // Only Ampere and newer support this feature
324 return NvidiaArchitecture::AmpereOrNewer; 319 // TODO: Find a way to differentiate Ampere and Ada
320 return NvidiaArchitecture::Arch_AmpereOrNewer;
325 } 321 }
322 return NvidiaArchitecture::Arch_Turing;
326 } 323 }
327 if (exts.contains(VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) { 324
328 return NvidiaArchitecture::Turing; 325 if (exts.contains(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME)) {
326 VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT advanced_blending_props{};
327 advanced_blending_props.sType =
328 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT;
329 VkPhysicalDeviceProperties2 physical_properties{};
330 physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
331 physical_properties.pNext = &advanced_blending_props;
332 physical.GetProperties2(physical_properties);
333 if (advanced_blending_props.advancedBlendMaxColorAttachments == 1) {
334 return NvidiaArchitecture::Arch_Maxwell;
335 }
336
337 if (exts.contains(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) {
338 VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_props{};
339 conservative_raster_props.sType =
340 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT;
341 physical_properties.pNext = &conservative_raster_props;
342 physical.GetProperties2(physical_properties);
343 if (conservative_raster_props.degenerateLinesRasterized) {
344 return NvidiaArchitecture::Arch_Volta;
345 }
346 return NvidiaArchitecture::Arch_Pascal;
347 }
329 } 348 }
330 return NvidiaArchitecture::VoltaOrOlder; 349
350 return NvidiaArchitecture::Arch_KeplerOrOlder;
331} 351}
332 352
333std::vector<const char*> ExtensionListForVulkan( 353std::vector<const char*> ExtensionListForVulkan(
@@ -407,6 +427,10 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
407 throw vk::Exception(VK_ERROR_INCOMPATIBLE_DRIVER); 427 throw vk::Exception(VK_ERROR_INCOMPATIBLE_DRIVER);
408 } 428 }
409 429
430 if (is_nvidia) {
431 nvidia_arch = GetNvidiaArchitecture(physical, supported_extensions);
432 }
433
410 SetupFamilies(surface); 434 SetupFamilies(surface);
411 const auto queue_cis = GetDeviceQueueCreateInfos(); 435 const auto queue_cis = GetDeviceQueueCreateInfos();
412 436
@@ -503,20 +527,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
503 527
504 if (is_nvidia) { 528 if (is_nvidia) {
505 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; 529 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
506 const auto arch = GetNvidiaArchitecture(physical, supported_extensions); 530 const auto arch = GetNvidiaArch();
507 switch (arch) { 531 if (arch >= NvidiaArchitecture::Arch_AmpereOrNewer) {
508 case NvidiaArchitecture::AmpereOrNewer:
509 LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math"); 532 LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
510 features.shader_float16_int8.shaderFloat16 = false; 533 features.shader_float16_int8.shaderFloat16 = false;
511 break; 534 } else if (arch <= NvidiaArchitecture::Arch_Volta) {
512 case NvidiaArchitecture::Turing:
513 break;
514 case NvidiaArchitecture::VoltaOrOlder:
515 if (nv_major_version < 527) { 535 if (nv_major_version < 527) {
516 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); 536 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
517 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); 537 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
518 } 538 }
519 break;
520 } 539 }
521 if (nv_major_version >= 510) { 540 if (nv_major_version >= 510) {
522 LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits"); 541 LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
@@ -661,7 +680,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"); 680 "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); 681 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
663 } 682 }
683 } else if (extensions.push_descriptor && is_nvidia) {
684 const auto arch = GetNvidiaArch();
685 if (arch <= NvidiaArchitecture::Arch_Pascal) {
686 LOG_WARNING(Render_Vulkan,
687 "Pascal and older architectures have broken VK_KHR_push_descriptor");
688 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
689 }
664 } 690 }
691
665 if (is_mvk) { 692 if (is_mvk) {
666 LOG_WARNING(Render_Vulkan, 693 LOG_WARNING(Render_Vulkan,
667 "MVK driver breaks when using more than 16 vertex attributes/bindings"); 694 "MVK driver breaks when using more than 16 vertex attributes/bindings");
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 282a2925d..b213ed7dd 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -177,6 +177,15 @@ enum class FormatType { Linear, Optimal, Buffer };
177/// Subgroup size of the guest emulated hardware (Nvidia has 32 threads per subgroup). 177/// Subgroup size of the guest emulated hardware (Nvidia has 32 threads per subgroup).
178const u32 GuestWarpSize = 32; 178const u32 GuestWarpSize = 32;
179 179
180enum class NvidiaArchitecture {
181 Arch_KeplerOrOlder,
182 Arch_Maxwell,
183 Arch_Pascal,
184 Arch_Volta,
185 Arch_Turing,
186 Arch_AmpereOrNewer,
187};
188
180/// Handles data specific to a physical device. 189/// Handles data specific to a physical device.
181class Device { 190class Device {
182public: 191public:
@@ -670,6 +679,14 @@ public:
670 return false; 679 return false;
671 } 680 }
672 681
682 bool IsNvidia() const noexcept {
683 return properties.driver.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY;
684 }
685
686 NvidiaArchitecture GetNvidiaArch() const noexcept {
687 return nvidia_arch;
688 }
689
673private: 690private:
674 /// Checks if the physical device is suitable and configures the object state 691 /// Checks if the physical device is suitable and configures the object state
675 /// with all necessary info about its properties. 692 /// with all necessary info about its properties.
@@ -788,6 +805,7 @@ private:
788 bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow. 805 bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow.
789 u64 device_access_memory{}; ///< Total size of device local memory in bytes. 806 u64 device_access_memory{}; ///< Total size of device local memory in bytes.
790 u32 sets_per_pool{}; ///< Sets per Description Pool 807 u32 sets_per_pool{}; ///< Sets per Description Pool
808 NvidiaArchitecture nvidia_arch{NvidiaArchitecture::Arch_AmpereOrNewer};
791 809
792 // Telemetry parameters 810 // Telemetry parameters
793 std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions. 811 std::set<std::string, std::less<>> supported_extensions; ///< Reported Vulkan extensions.
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.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index 2f3254a97..70cf14afa 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -522,7 +522,7 @@ Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char
522 .applicationVersion = VK_MAKE_VERSION(0, 1, 0), 522 .applicationVersion = VK_MAKE_VERSION(0, 1, 0),
523 .pEngineName = "yuzu Emulator", 523 .pEngineName = "yuzu Emulator",
524 .engineVersion = VK_MAKE_VERSION(0, 1, 0), 524 .engineVersion = VK_MAKE_VERSION(0, 1, 0),
525 .apiVersion = version, 525 .apiVersion = VK_API_VERSION_1_3,
526 }; 526 };
527 const VkInstanceCreateInfo ci{ 527 const VkInstanceCreateInfo ci{
528 .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 528 .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
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 d23c1e9d0..33e1fb663 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..515cb7ce6 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);
@@ -150,16 +155,27 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
150 UpdateBorderColor(i); 155 UpdateBorderColor(i);
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 // Reconnect current controller if it was the last one checked
154 for (std::size_t index = 0; index <= i; ++index) { 159 // (player number was reduced by more than one)
155 connected_controller_checkboxes[index]->setChecked(checked); 160 const bool reconnect_first = !checked && i < player_groupboxes.size() - 1 &&
156 } 161 player_groupboxes[i + 1]->isChecked();
157 } else { 162
158 for (std::size_t index = i; index < NUM_PLAYERS; ++index) { 163 // Ensures that connecting a controller changes the number of players
159 connected_controller_checkboxes[index]->setChecked(checked); 164 if (connected_controller_checkboxes[i]->isChecked() != checked) {
160 } 165 // Ensures that the players are always connected in sequential order
166 PropagatePlayerNumberChanged(i, checked, reconnect_first);
161 } 167 }
162 }); 168 });
169 connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) {
170 // Reconnect current controller if it was the last one checked
171 // (player number was reduced by more than one)
172 const bool reconnect_first = !checked &&
173 i < connected_controller_checkboxes.size() - 1 &&
174 connected_controller_checkboxes[i + 1]->isChecked();
175
176 // Ensures that the players are always connected in sequential order
177 PropagatePlayerNumberChanged(i, checked, reconnect_first);
178 });
163 179
164 connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged), 180 connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
165 [this, i](int) { 181 [this, i](int) {
@@ -199,6 +215,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
199 connect(ui->buttonBox, &QDialogButtonBox::accepted, this, 215 connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
200 &QtControllerSelectorDialog::ApplyConfiguration); 216 &QtControllerSelectorDialog::ApplyConfiguration);
201 217
218 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
219 [this](Qt::Key key) {
220 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
221 QCoreApplication::postEvent(this, event);
222 });
223
202 // Enhancement: Check if the parameters have already been met before disconnecting controllers. 224 // 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, 225 // If all the parameters are met AND only allows a single player,
204 // stop the constructor here as we do not need to continue. 226 // stop the constructor here as we do not need to continue.
@@ -217,6 +239,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
217} 239}
218 240
219QtControllerSelectorDialog::~QtControllerSelectorDialog() { 241QtControllerSelectorDialog::~QtControllerSelectorDialog() {
242 controller_navigation->UnloadController();
220 system.HIDCore().DisableAllControllerConfiguration(); 243 system.HIDCore().DisableAllControllerConfiguration();
221} 244}
222 245
@@ -291,6 +314,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
291 dialog.exec(); 314 dialog.exec();
292} 315}
293 316
317void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
318 const auto num_connected_players = static_cast<int>(
319 std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
320 [](const QGroupBox* player) { return player->isChecked(); }));
321
322 const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
323 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
324
325 if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
326 // Display error message when trying to validate using "Enter" and "OK" button is disabled
327 ui->labelError->setVisible(true);
328 return;
329 } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
330 // Remove a player if possible
331 connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
332 return;
333 } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
334 // Add a player, if possible
335 ui->labelError->setVisible(false);
336 connected_controller_checkboxes[num_connected_players]->setChecked(true);
337 return;
338 }
339 QDialog::keyPressEvent(evt);
340}
341
294bool QtControllerSelectorDialog::CheckIfParametersMet() { 342bool QtControllerSelectorDialog::CheckIfParametersMet() {
295 // Here, we check and validate the current configuration against all applicable parameters. 343 // Here, we check and validate the current configuration against all applicable parameters.
296 const auto num_connected_players = static_cast<int>( 344 const auto num_connected_players = static_cast<int>(
@@ -629,6 +677,29 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
629 } 677 }
630} 678}
631 679
680void QtControllerSelectorDialog::PropagatePlayerNumberChanged(size_t player_index, bool checked,
681 bool reconnect_current) {
682 connected_controller_checkboxes[player_index]->setChecked(checked);
683 // Hide eventual error message about number of controllers
684 ui->labelError->setVisible(false);
685
686 if (checked) {
687 // Check all previous buttons when checked
688 if (player_index > 0) {
689 PropagatePlayerNumberChanged(player_index - 1, checked);
690 }
691 } else {
692 // Unchecked all following buttons when unchecked
693 if (player_index < connected_controller_checkboxes.size() - 1) {
694 PropagatePlayerNumberChanged(player_index + 1, checked);
695 }
696 }
697
698 if (reconnect_current) {
699 connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked);
700 }
701}
702
632void QtControllerSelectorDialog::DisableUnsupportedPlayers() { 703void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
633 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; 704 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
634 705
diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h
index 2fdc35857..e5372495d 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();
@@ -96,6 +100,10 @@ private:
96 // Updates the console mode. 100 // Updates the console mode.
97 void UpdateDockedState(bool is_handheld); 101 void UpdateDockedState(bool is_handheld);
98 102
103 // Enable preceding controllers or disable following ones
104 void PropagatePlayerNumberChanged(size_t player_index, bool checked,
105 bool reconnect_current = false);
106
99 // Disables and disconnects unsupported players based on the given parameters. 107 // Disables and disconnects unsupported players based on the given parameters.
100 void DisableUnsupportedPlayers(); 108 void DisableUnsupportedPlayers();
101 109
@@ -110,6 +118,8 @@ private:
110 118
111 Core::System& system; 119 Core::System& system;
112 120
121 ControllerNavigation* controller_navigation = nullptr;
122
113 // This is true if and only if all parameters are met. Otherwise, this is false. 123 // 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. 124 // This determines whether the "OK" button can be clicked to exit the applet.
115 bool parameters_met{false}; 125 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..baa3e55f3 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -114,7 +114,7 @@ const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_ma
114// This must be in alphabetical order according to action name as it must have the same order as 114// This must be in alphabetical order according to action name as it must have the same order as
115// UISetting::values.shortcuts, which is alphabetically ordered. 115// UISetting::values.shortcuts, which is alphabetically ordered.
116// clang-format off 116// clang-format off
117const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ 117const std::array<UISettings::Shortcut, 23> Config::default_hotkeys{{
118 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut, false}}, 118 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut, false}},
119 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut, true}}, 119 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
120 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut, true}}, 120 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
@@ -128,14 +128,15 @@ 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}},
136 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut, false}}, 136 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut, false}},
137 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut, false}}, 137 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut, false}},
138 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, 138 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
139 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Renderdoc Capture")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut, false}},
139 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut, false}}, 140 {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut, false}},
140}}; 141}};
141// clang-format on 142// clang-format on
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 727feebfb..74ec4f771 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -48,7 +48,7 @@ public:
48 default_mouse_buttons; 48 default_mouse_buttons;
49 static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; 49 static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
50 static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; 50 static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
51 static const std::array<UISettings::Shortcut, 22> default_hotkeys; 51 static const std::array<UISettings::Shortcut, 23> default_hotkeys;
52 52
53 static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map; 53 static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map;
54 static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map; 54 static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map;
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_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index 0b2a965f8..68e21cd84 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -319,6 +319,13 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
319void ConfigureHotkeys::RestoreDefaults() { 319void ConfigureHotkeys::RestoreDefaults() {
320 for (int r = 0; r < model->rowCount(); ++r) { 320 for (int r = 0; r < model->rowCount(); ++r) {
321 const QStandardItem* parent = model->item(r, 0); 321 const QStandardItem* parent = model->item(r, 0);
322 const int hotkey_size = static_cast<int>(Config::default_hotkeys.size());
323
324 if (hotkey_size != parent->rowCount()) {
325 QMessageBox::warning(this, tr("Invalid hotkey settings"),
326 tr("An error occurred. Please report this issue on github."));
327 return;
328 }
322 329
323 for (int r2 = 0; r2 < parent->rowCount(); ++r2) { 330 for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
324 model->item(r, 0) 331 model->item(r, 0)
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index e8f9ebfd8..3dcad2701 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -101,13 +101,13 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
101 ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, 101 ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8,
102 }; 102 };
103 103
104 player_connected = { 104 connected_controller_checkboxes = {
105 ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, 105 ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
106 ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, 106 ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
107 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, 107 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
108 }; 108 };
109 109
110 std::array<QLabel*, 8> player_connected_labels = { 110 std::array<QLabel*, 8> connected_controller_labels = {
111 ui->label, ui->label_3, ui->label_4, ui->label_5, 111 ui->label, ui->label_3, ui->label_4, ui->label_5,
112 ui->label_6, ui->label_7, ui->label_8, ui->label_9, 112 ui->label_6, ui->label_7, ui->label_8, ui->label_9,
113 }; 113 };
@@ -115,31 +115,37 @@ 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_controllers[i], &ConfigureInputPlayer::Connected, [this, i](bool checked) {
119 // Ensures that the controllers are always connected in sequential order 119 // Ensures that connecting a controller changes the number of players
120 if (is_connected) { 120 if (connected_controller_checkboxes[i]->isChecked() != checked) {
121 for (std::size_t index = 0; index <= i; ++index) { 121 // Ensures that the players are always connected in sequential order
122 player_connected[index]->setChecked(is_connected); 122 PropagatePlayerNumberChanged(i, checked);
123 }
124 } else {
125 for (std::size_t index = i; index < player_tabs.size(); ++index) {
126 player_connected[index]->setChecked(is_connected);
127 }
128 } 123 }
129 }); 124 });
125 connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) {
126 // Reconnect current controller if it was the last one checked
127 // (player number was reduced by more than one)
128 const bool reconnect_first = !checked &&
129 i < connected_controller_checkboxes.size() - 1 &&
130 connected_controller_checkboxes[i + 1]->isChecked();
131
132 // Ensures that the players are always connected in sequential order
133 PropagatePlayerNumberChanged(i, checked, reconnect_first);
134 });
130 connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, 135 connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
131 &ConfigureInput::UpdateAllInputDevices); 136 &ConfigureInput::UpdateAllInputDevices);
132 connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this, 137 connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
133 &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection); 138 &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
134 connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) { 139 connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
140 // Keep activated controllers synced with the "Connected Controllers" checkboxes
135 player_controllers[i]->ConnectPlayer(state == Qt::Checked); 141 player_controllers[i]->ConnectPlayer(state == Qt::Checked);
136 }); 142 });
137 143
138 // Remove/hide all the elements that exceed max_players, if applicable. 144 // Remove/hide all the elements that exceed max_players, if applicable.
139 if (i >= max_players) { 145 if (i >= max_players) {
140 ui->tabWidget->removeTab(static_cast<int>(max_players)); 146 ui->tabWidget->removeTab(static_cast<int>(max_players));
141 player_connected[i]->hide(); 147 connected_controller_checkboxes[i]->hide();
142 player_connected_labels[i]->hide(); 148 connected_controller_labels[i]->hide();
143 } 149 }
144 } 150 }
145 // Only the first player can choose handheld mode so connect the signal just to player 1 151 // Only the first player can choose handheld mode so connect the signal just to player 1
@@ -183,6 +189,27 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
183 LoadConfiguration(); 189 LoadConfiguration();
184} 190}
185 191
192void ConfigureInput::PropagatePlayerNumberChanged(size_t player_index, bool checked,
193 bool reconnect_current) {
194 connected_controller_checkboxes[player_index]->setChecked(checked);
195
196 if (checked) {
197 // Check all previous buttons when checked
198 if (player_index > 0) {
199 PropagatePlayerNumberChanged(player_index - 1, checked);
200 }
201 } else {
202 // Unchecked all following buttons when unchecked
203 if (player_index < connected_controller_checkboxes.size() - 1) {
204 PropagatePlayerNumberChanged(player_index + 1, checked);
205 }
206 }
207
208 if (reconnect_current) {
209 connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked);
210 }
211}
212
186QList<QWidget*> ConfigureInput::GetSubTabs() const { 213QList<QWidget*> ConfigureInput::GetSubTabs() const {
187 return { 214 return {
188 ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, 215 ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
@@ -233,17 +260,17 @@ void ConfigureInput::LoadConfiguration() {
233} 260}
234 261
235void ConfigureInput::LoadPlayerControllerIndices() { 262void ConfigureInput::LoadPlayerControllerIndices() {
236 for (std::size_t i = 0; i < player_connected.size(); ++i) { 263 for (std::size_t i = 0; i < connected_controller_checkboxes.size(); ++i) {
237 if (i == 0) { 264 if (i == 0) {
238 auto* handheld = 265 auto* handheld =
239 system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); 266 system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
240 if (handheld->IsConnected()) { 267 if (handheld->IsConnected()) {
241 player_connected[i]->setChecked(true); 268 connected_controller_checkboxes[i]->setChecked(true);
242 continue; 269 continue;
243 } 270 }
244 } 271 }
245 const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(i); 272 const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(i);
246 player_connected[i]->setChecked(controller->IsConnected()); 273 connected_controller_checkboxes[i]->setChecked(controller->IsConnected());
247 } 274 }
248} 275}
249 276
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index c89189c36..136cd3a0a 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -56,6 +56,9 @@ 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 // Enable preceding controllers or disable following ones
60 void PropagatePlayerNumberChanged(size_t player_index, bool checked,
61 bool reconnect_current = false);
59 62
60 /// Load configuration settings. 63 /// Load configuration settings.
61 void LoadConfiguration(); 64 void LoadConfiguration();
@@ -70,7 +73,8 @@ private:
70 73
71 std::array<ConfigureInputPlayer*, 8> player_controllers; 74 std::array<ConfigureInputPlayer*, 8> player_controllers;
72 std::array<QWidget*, 8> player_tabs; 75 std::array<QWidget*, 8> player_tabs;
73 std::array<QCheckBox*, 8> player_connected; 76 // Checkboxes representing the "Connected Controllers".
77 std::array<QCheckBox*, 8> connected_controller_checkboxes;
74 ConfigureInputAdvanced* advanced; 78 ConfigureInputAdvanced* advanced;
75 79
76 Core::System& system; 80 Core::System& system;
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index d4df43d73..d3255d2b4 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -75,7 +75,7 @@ public:
75 void ClearAll(); 75 void ClearAll();
76 76
77signals: 77signals:
78 /// Emitted when this controller is connected by the user. 78 /// Emitted when this controller is (dis)connected by the user.
79 void Connected(bool connected); 79 void Connected(bool connected);
80 /// Emitted when the Handheld mode is selected (undocked with dual joycons attached). 80 /// Emitted when the Handheld mode is selected (undocked with dual joycons attached).
81 void HandheldStateChanged(bool is_handheld); 81 void HandheldStateChanged(bool is_handheld);
@@ -183,9 +183,6 @@ private:
183 /// Stores a pair of "Connected Controllers" combobox index and Controller Type enum. 183 /// Stores a pair of "Connected Controllers" combobox index and Controller Type enum.
184 std::vector<std::pair<int, Core::HID::NpadStyleIndex>> index_controller_type_pairs; 184 std::vector<std::pair<int, Core::HID::NpadStyleIndex>> index_controller_type_pairs;
185 185
186 static constexpr int PLAYER_COUNT = 8;
187 std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox;
188
189 /// This will be the the setting function when an input is awaiting configuration. 186 /// This will be the the setting function when an input is awaiting configuration.
190 std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; 187 std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
191 188
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/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp
index d765e808a..68c28b320 100644
--- a/src/yuzu/configuration/configure_vibration.cpp
+++ b/src/yuzu/configuration/configure_vibration.cpp
@@ -89,7 +89,7 @@ void ConfigureVibration::VibrateController(Core::HID::ControllerTriggerType type
89 89
90 auto& player = Settings::values.players.GetValue()[player_index]; 90 auto& player = Settings::values.players.GetValue()[player_index];
91 auto controller = hid_core.GetEmulatedControllerByIndex(player_index); 91 auto controller = hid_core.GetEmulatedControllerByIndex(player_index);
92 const int vibration_strenght = vibration_spinboxes[player_index]->value(); 92 const int vibration_strength = vibration_spinboxes[player_index]->value();
93 const auto& buttons = controller->GetButtonsValues(); 93 const auto& buttons = controller->GetButtonsValues();
94 94
95 bool button_is_pressed = false; 95 bool button_is_pressed = false;
@@ -105,10 +105,10 @@ void ConfigureVibration::VibrateController(Core::HID::ControllerTriggerType type
105 return; 105 return;
106 } 106 }
107 107
108 const int old_vibration_enabled = player.vibration_enabled; 108 const bool old_vibration_enabled = player.vibration_enabled;
109 const bool old_vibration_strenght = player.vibration_strength; 109 const int old_vibration_strength = player.vibration_strength;
110 player.vibration_enabled = true; 110 player.vibration_enabled = true;
111 player.vibration_strength = vibration_strenght; 111 player.vibration_strength = vibration_strength;
112 112
113 const Core::HID::VibrationValue vibration{ 113 const Core::HID::VibrationValue vibration{
114 .low_amplitude = 1.0f, 114 .low_amplitude = 1.0f,
@@ -121,7 +121,7 @@ void ConfigureVibration::VibrateController(Core::HID::ControllerTriggerType type
121 121
122 // Restore previous values 122 // Restore previous values
123 player.vibration_enabled = old_vibration_enabled; 123 player.vibration_enabled = old_vibration_enabled;
124 player.vibration_strength = old_vibration_strenght; 124 player.vibration_strength = old_vibration_strength;
125} 125}
126 126
127void ConfigureVibration::StopVibrations() { 127void ConfigureVibration::StopVibrations() {
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/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 0783a2430..7049c57b6 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -127,7 +127,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
127 return list; 127 return list;
128 } 128 }
129 129
130 if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64BitProcess()) { 130 if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64Bit()) {
131 return list; 131 return list;
132 } 132 }
133 133
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index f254c1e1c..7e7d8e252 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);
@@ -377,7 +380,6 @@ void GameList::UnloadController() {
377 380
378GameList::~GameList() { 381GameList::~GameList() {
379 UnloadController(); 382 UnloadController();
380 emit ShouldCancelWorker();
381} 383}
382 384
383void GameList::SetFilterFocus() { 385void GameList::SetFilterFocus() {
@@ -394,6 +396,10 @@ void GameList::ClearFilter() {
394 search_field->clear(); 396 search_field->clear();
395} 397}
396 398
399void GameList::WorkerEvent() {
400 current_worker->ProcessEvents(this);
401}
402
397void GameList::AddDirEntry(GameListDir* entry_items) { 403void GameList::AddDirEntry(GameListDir* entry_items) {
398 item_model->invisibleRootItem()->appendRow(entry_items); 404 item_model->invisibleRootItem()->appendRow(entry_items);
399 tree_view->setExpanded( 405 tree_view->setExpanded(
@@ -548,6 +554,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
548 QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); 554 QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
549 QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); 555 QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
550 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); 556 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
557 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")); 558 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")); 559 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")); 560 QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache"));
@@ -560,9 +567,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
560 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); 567 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
561 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 568 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")); 569 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")); 570 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
565 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); 571 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
572#ifndef WIN32
566 QAction* create_applications_menu_shortcut = 573 QAction* create_applications_menu_shortcut =
567 shortcut_menu->addAction(tr("Add to Applications Menu")); 574 shortcut_menu->addAction(tr("Add to Applications Menu"));
568#endif 575#endif
@@ -622,6 +629,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
622 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { 629 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
623 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); 630 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
624 }); 631 });
632 connect(remove_play_time_data, &QAction::triggered,
633 [this, program_id]() { emit RemovePlayTimeRequested(program_id); });
625 connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { 634 connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
626 emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); 635 emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path);
627 }); 636 });
@@ -638,10 +647,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
638 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { 647 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
639 emit NavigateToGamedbEntryRequested(program_id, compatibility_list); 648 emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
640 }); 649 });
641#ifndef WIN32
642 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { 650 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
643 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); 651 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
644 }); 652 });
653#ifndef WIN32
645 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { 654 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
646 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); 655 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
647 }); 656 });
@@ -790,6 +799,7 @@ void GameList::RetranslateUI() {
790 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); 799 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
791 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); 800 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
792 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); 801 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
802 item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time"));
793} 803}
794 804
795void GameListSearchField::changeEvent(QEvent* event) { 805void GameListSearchField::changeEvent(QEvent* event) {
@@ -817,28 +827,23 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
817 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); 827 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
818 tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); 828 tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
819 tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); 829 tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
830 tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
831
832 // Cancel any existing worker.
833 current_worker.reset();
820 834
821 // Delete any rows that might already exist if we're repopulating 835 // Delete any rows that might already exist if we're repopulating
822 item_model->removeRows(0, item_model->rowCount()); 836 item_model->removeRows(0, item_model->rowCount());
823 search_field->clear(); 837 search_field->clear();
824 838
825 emit ShouldCancelWorker(); 839 current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, compatibility_list,
840 play_time_manager, system);
826 841
827 GameListWorker* worker = 842 // Get events from the worker as data becomes available
828 new GameListWorker(vfs, provider, game_dirs, compatibility_list, system); 843 connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent,
829
830 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
831 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
832 Qt::QueuedConnection);
833 connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
834 Qt::QueuedConnection); 844 Qt::QueuedConnection);
835 // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to
836 // cancel without delay.
837 connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel,
838 Qt::DirectConnection);
839 845
840 QThreadPool::globalInstance()->start(worker); 846 QThreadPool::globalInstance()->start(current_worker.get());
841 current_worker = std::move(worker);
842} 847}
843 848
844void GameList::SaveInterfaceLayout() { 849void GameList::SaveInterfaceLayout() {
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 1fcbbf0ba..563a3a35b 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
@@ -106,13 +109,13 @@ signals:
106 void BootGame(const QString& game_path, u64 program_id, std::size_t program_index, 109 void BootGame(const QString& game_path, u64 program_id, std::size_t program_index,
107 StartGameType type, AmLaunchType launch_type); 110 StartGameType type, AmLaunchType launch_type);
108 void GameChosen(const QString& game_path, const u64 title_id = 0); 111 void GameChosen(const QString& game_path, const u64 title_id = 0);
109 void ShouldCancelWorker();
110 void OpenFolderRequested(u64 program_id, GameListOpenTarget target, 112 void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
111 const std::string& game_path); 113 const std::string& game_path);
112 void OpenTransferableShaderCacheRequested(u64 program_id); 114 void OpenTransferableShaderCacheRequested(u64 program_id);
113 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); 115 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
114 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, 116 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
115 const std::string& game_path); 117 const std::string& game_path);
118 void RemovePlayTimeRequested(u64 program_id);
116 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 119 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
117 void VerifyIntegrityRequested(const std::string& game_path); 120 void VerifyIntegrityRequested(const std::string& game_path);
118 void CopyTIDRequested(u64 program_id); 121 void CopyTIDRequested(u64 program_id);
@@ -134,11 +137,16 @@ private slots:
134 void OnUpdateThemedIcons(); 137 void OnUpdateThemedIcons();
135 138
136private: 139private:
140 friend class GameListWorker;
141 void WorkerEvent();
142
137 void AddDirEntry(GameListDir* entry_items); 143 void AddDirEntry(GameListDir* entry_items);
138 void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent); 144 void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
139 void ValidateEntry(const QModelIndex& item);
140 void DonePopulating(const QStringList& watch_list); 145 void DonePopulating(const QStringList& watch_list);
141 146
147private:
148 void ValidateEntry(const QModelIndex& item);
149
142 void RefreshGameDirectory(); 150 void RefreshGameDirectory();
143 151
144 void ToggleFavorite(u64 program_id); 152 void ToggleFavorite(u64 program_id);
@@ -161,13 +169,14 @@ private:
161 QVBoxLayout* layout = nullptr; 169 QVBoxLayout* layout = nullptr;
162 QTreeView* tree_view = nullptr; 170 QTreeView* tree_view = nullptr;
163 QStandardItemModel* item_model = nullptr; 171 QStandardItemModel* item_model = nullptr;
164 GameListWorker* current_worker = nullptr; 172 std::unique_ptr<GameListWorker> current_worker;
165 QFileSystemWatcher* watcher = nullptr; 173 QFileSystemWatcher* watcher = nullptr;
166 ControllerNavigation* controller_navigation = nullptr; 174 ControllerNavigation* controller_navigation = nullptr;
167 CompatibilityList compatibility_list; 175 CompatibilityList compatibility_list;
168 176
169 friend class GameListSearchField; 177 friend class GameListSearchField;
170 178
179 const PlayTime::PlayTimeManager& play_time_manager;
171 Core::System& system; 180 Core::System& system;
172}; 181};
173 182
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..69be21027 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,11 +229,57 @@ 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_}, play_time_manager{play_time_manager_}, system{
237 system_} {
238 // We want the game list to manage our lifetime.
239 setAutoDelete(false);
240}
241
242GameListWorker::~GameListWorker() {
243 this->disconnect();
244 stop_requested.store(true);
245 processing_completed.Wait();
246}
233 247
234GameListWorker::~GameListWorker() = default; 248void GameListWorker::ProcessEvents(GameList* game_list) {
249 while (true) {
250 std::function<void(GameList*)> func;
251 {
252 // Lock queue to protect concurrent modification.
253 std::scoped_lock lk(lock);
254
255 // If we can't pop a function, return.
256 if (queued_events.empty()) {
257 return;
258 }
259
260 // Pop a function.
261 func = std::move(queued_events.back());
262 queued_events.pop_back();
263 }
264
265 // Run the function.
266 func(game_list);
267 }
268}
269
270template <typename F>
271void GameListWorker::RecordEvent(F&& func) {
272 {
273 // Lock queue to protect concurrent modification.
274 std::scoped_lock lk(lock);
275
276 // Add the function into the front of the queue.
277 queued_events.emplace_front(std::move(func));
278 }
279
280 // Data now available.
281 emit DataAvailable();
282}
235 283
236void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { 284void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
237 using namespace FileSys; 285 using namespace FileSys;
@@ -279,16 +327,16 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
279 GetMetadataFromControlNCA(patch, *control, icon, name); 327 GetMetadataFromControlNCA(patch, *control, icon, name);
280 } 328 }
281 329
282 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, 330 auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
283 program_id, compatibility_list, patch), 331 program_id, compatibility_list, play_time_manager, patch);
284 parent_dir); 332 RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
285 } 333 }
286} 334}
287 335
288void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, 336void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
289 GameListDir* parent_dir) { 337 GameListDir* parent_dir) {
290 const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { 338 const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool {
291 if (stop_processing) { 339 if (stop_requested) {
292 // Breaks the callback loop. 340 // Breaks the callback loop.
293 return false; 341 return false;
294 } 342 }
@@ -355,10 +403,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
355 const FileSys::PatchManager patch{id, system.GetFileSystemController(), 403 const FileSys::PatchManager patch{id, system.GetFileSystemController(),
356 system.GetContentProvider()}; 404 system.GetContentProvider()};
357 405
358 emit EntryReady(MakeGameListEntry(physical_name, name, 406 auto entry = MakeGameListEntry(
359 Common::FS::GetSize(physical_name), icon, 407 physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
360 *loader, id, compatibility_list, patch), 408 id, compatibility_list, play_time_manager, patch);
361 parent_dir); 409
410 RecordEvent(
411 [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
362 } 412 }
363 } else { 413 } else {
364 std::vector<u8> icon; 414 std::vector<u8> icon;
@@ -370,10 +420,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
370 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), 420 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
371 system.GetContentProvider()}; 421 system.GetContentProvider()};
372 422
373 emit EntryReady( 423 auto entry = MakeGameListEntry(
374 MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), 424 physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
375 icon, *loader, program_id, compatibility_list, patch), 425 program_id, compatibility_list, play_time_manager, patch);
376 parent_dir); 426
427 RecordEvent(
428 [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
377 } 429 }
378 } 430 }
379 } else if (is_dir) { 431 } else if (is_dir) {
@@ -392,26 +444,34 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
392} 444}
393 445
394void GameListWorker::run() { 446void GameListWorker::run() {
395 stop_processing = false; 447 watch_list.clear();
396 provider->ClearAllEntries(); 448 provider->ClearAllEntries();
397 449
450 const auto DirEntryReady = [&](GameListDir* game_list_dir) {
451 RecordEvent([=](GameList* game_list) { game_list->AddDirEntry(game_list_dir); });
452 };
453
398 for (UISettings::GameDir& game_dir : game_dirs) { 454 for (UISettings::GameDir& game_dir : game_dirs) {
455 if (stop_requested) {
456 break;
457 }
458
399 if (game_dir.path == QStringLiteral("SDMC")) { 459 if (game_dir.path == QStringLiteral("SDMC")) {
400 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); 460 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
401 emit DirEntryReady(game_list_dir); 461 DirEntryReady(game_list_dir);
402 AddTitlesToGameList(game_list_dir); 462 AddTitlesToGameList(game_list_dir);
403 } else if (game_dir.path == QStringLiteral("UserNAND")) { 463 } else if (game_dir.path == QStringLiteral("UserNAND")) {
404 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); 464 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
405 emit DirEntryReady(game_list_dir); 465 DirEntryReady(game_list_dir);
406 AddTitlesToGameList(game_list_dir); 466 AddTitlesToGameList(game_list_dir);
407 } else if (game_dir.path == QStringLiteral("SysNAND")) { 467 } else if (game_dir.path == QStringLiteral("SysNAND")) {
408 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); 468 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
409 emit DirEntryReady(game_list_dir); 469 DirEntryReady(game_list_dir);
410 AddTitlesToGameList(game_list_dir); 470 AddTitlesToGameList(game_list_dir);
411 } else { 471 } else {
412 watch_list.append(game_dir.path); 472 watch_list.append(game_dir.path);
413 auto* const game_list_dir = new GameListDir(game_dir); 473 auto* const game_list_dir = new GameListDir(game_dir);
414 emit DirEntryReady(game_list_dir); 474 DirEntryReady(game_list_dir);
415 ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 475 ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(),
416 game_dir.deep_scan, game_list_dir); 476 game_dir.deep_scan, game_list_dir);
417 ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), 477 ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
@@ -419,10 +479,6 @@ void GameListWorker::run() {
419 } 479 }
420 } 480 }
421 481
422 emit Finished(watch_list); 482 RecordEvent([=](GameList* game_list) { game_list->DonePopulating(watch_list); });
423} 483 processing_completed.Set();
424
425void GameListWorker::Cancel() {
426 this->disconnect();
427 stop_processing = true;
428} 484}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 24a4e92c3..d5990fcde 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -4,6 +4,7 @@
4#pragma once 4#pragma once
5 5
6#include <atomic> 6#include <atomic>
7#include <deque>
7#include <memory> 8#include <memory>
8#include <string> 9#include <string>
9 10
@@ -12,12 +13,15 @@
12#include <QRunnable> 13#include <QRunnable>
13#include <QString> 14#include <QString>
14 15
16#include "common/thread.h"
15#include "yuzu/compatibility_list.h" 17#include "yuzu/compatibility_list.h"
18#include "yuzu/play_time_manager.h"
16 19
17namespace Core { 20namespace Core {
18class System; 21class System;
19} 22}
20 23
24class GameList;
21class QStandardItem; 25class QStandardItem;
22 26
23namespace FileSys { 27namespace FileSys {
@@ -36,30 +40,30 @@ public:
36 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, 40 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
37 FileSys::ManualContentProvider* provider_, 41 FileSys::ManualContentProvider* provider_,
38 QVector<UISettings::GameDir>& game_dirs_, 42 QVector<UISettings::GameDir>& game_dirs_,
39 const CompatibilityList& compatibility_list_, Core::System& system_); 43 const CompatibilityList& compatibility_list_,
44 const PlayTime::PlayTimeManager& play_time_manager_,
45 Core::System& system_);
40 ~GameListWorker() override; 46 ~GameListWorker() override;
41 47
42 /// Starts the processing of directory tree information. 48 /// Starts the processing of directory tree information.
43 void run() override; 49 void run() override;
44 50
45 /// Tells the worker that it should no longer continue processing. Thread-safe. 51public:
46 void Cancel();
47
48signals:
49 /** 52 /**
50 * The `EntryReady` signal is emitted once an entry has been prepared and is ready 53 * Synchronously processes any events queued by the worker.
51 * to be added to the game list. 54 *
52 * @param entry_items a list with `QStandardItem`s that make up the columns of the new 55 * AddDirEntry is called on the game list for every discovered directory.
53 * entry. 56 * AddEntry is called on the game list for every discovered program.
57 * DonePopulating is called on the game list when processing completes.
54 */ 58 */
55 void DirEntryReady(GameListDir* entry_items); 59 void ProcessEvents(GameList* game_list);
56 void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir);
57 60
58 /** 61signals:
59 * After the worker has traversed the game directory looking for entries, this signal is 62 void DataAvailable();
60 * emitted with a list of folders that should be watched for changes as well. 63
61 */ 64private:
62 void Finished(QStringList watch_list); 65 template <typename F>
66 void RecordEvent(F&& func);
63 67
64private: 68private:
65 void AddTitlesToGameList(GameListDir* parent_dir); 69 void AddTitlesToGameList(GameListDir* parent_dir);
@@ -76,9 +80,15 @@ 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 std::mutex lock;
88 std::condition_variable cv;
89 std::deque<std::function<void(GameList*)>> queued_events;
90 std::atomic_bool stop_requested = false;
91 Common::Event processing_completed;
82 92
83 Core::System& system; 93 Core::System& system;
84}; 94};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index eb69da4ba..7cc11ae3b 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,
@@ -2008,7 +2019,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
2008 std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())} 2019 std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())}
2009 .filename()); 2020 .filename());
2010 } 2021 }
2011 const bool is_64bit = system->Kernel().ApplicationProcess()->Is64BitProcess(); 2022 const bool is_64bit = system->Kernel().ApplicationProcess()->Is64Bit();
2012 const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)"); 2023 const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)");
2013 title_name = tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit") 2024 title_name = tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit")
2014 .arg(QString::fromStdString(title_name), instruction_set_suffix) 2025 .arg(QString::fromStdString(title_name), instruction_set_suffix)
@@ -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);