From: franki Date: Tue, 22 Nov 2022 08:47:56 +0000 (+0800) Subject: 初始化提交 X-Git-Url: http://gitweb.100806.xyz/?a=commitdiff_plain;ds=inline;p=duckstation.git 初始化提交 --- dce268fa14da5f15e36f029657e507119d0e447b diff --git a/jni/Android.mk b/jni/Android.mk new file mode 100755 index 0000000..de9255f --- /dev/null +++ b/jni/Android.mk @@ -0,0 +1,454 @@ +LOCAL_PATH := $(call my-dir) + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := core +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/core \ + $(LOCAL_PATH)/dep/stb/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include \ + $(LOCAL_PATH)/dep/vixl/include + +LOCAL_SRC_FILES := \ + core/analog_controller.cpp \ + core/analog_joystick.cpp \ + core/bios.cpp \ + core/bus.cpp \ + core/cdrom.cpp \ + core/cdrom_async_reader.cpp \ + core/cheats.cpp \ + core/controller.cpp \ + core/cpu_code_cache.cpp \ + core/cpu_core.cpp \ + core/cpu_disasm.cpp \ + core/cpu_types.cpp \ + core/digital_controller.cpp \ + core/dma.cpp \ + core/gpu.cpp \ + core/gpu_backend.cpp \ + core/gpu_commands.cpp \ + core/gpu_hw.cpp \ + core/gpu_hw_opengl.cpp \ + core/gpu_hw_shadergen.cpp \ + core/gpu_hw_vulkan.cpp \ + core/gpu_sw.cpp \ + core/gpu_sw_backend.cpp \ + core/gte.cpp \ + core/host_display.cpp \ + core/host_interface.cpp \ + core/host_interface_progress_callback.cpp \ + core/interrupt_controller.cpp \ + core/libcrypt_game_codes.cpp \ + core/mdec.cpp \ + core/memory_card.cpp \ + core/memory_card_image.cpp \ + core/namco_guncon.cpp \ + core/negcon.cpp \ + core/pad.cpp \ + core/pgxp.cpp \ + core/playstation_mouse.cpp \ + core/psf_loader.cpp \ + core/resources.cpp \ + core/settings.cpp \ + core/shadergen.cpp \ + core/sio.cpp \ + core/spu.cpp \ + core/system.cpp \ + core/timers.cpp \ + core/timing_event.cpp \ + core/cpu_recompiler_code_generator.cpp \ + core/cpu_recompiler_code_generator_generic.cpp \ + core/cpu_recompiler_register_cache.cpp \ + core/cpu_recompiler_code_generator_aarch64.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -DWITH_RECOMPILER=1 -DWITH_MMAP_FASTMEM=1 +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := common +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libcue/include \ + $(LOCAL_PATH)/dep/libchdr/include \ + $(LOCAL_PATH)/dep/stb/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/minizip/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include \ + $(LOCAL_PATH)/dep/glslang + +LOCAL_SRC_FILES := \ + common/assert.cpp \ + common/audio_stream.cpp \ + common/byte_stream.cpp \ + common/cd_image.cpp \ + common/cd_image_bin.cpp \ + common/cd_image_cue.cpp \ + common/cd_image_chd.cpp \ + common/cd_image_hasher.cpp \ + common/cd_image_memory.cpp \ + common/cd_subchannel_replacement.cpp \ + common/cd_xa.cpp \ + common/event.cpp \ + common/file_system.cpp \ + common/image.cpp \ + common/gl/context.cpp \ + common/gl/program.cpp \ + common/gl/shader_cache.cpp \ + common/gl/stream_buffer.cpp \ + common/gl/texture.cpp \ + common/iso_reader.cpp \ + common/jit_code_buffer.cpp \ + common/log.cpp \ + common/md5_digest.cpp \ + common/minizip_helpers.cpp \ + common/null_audio_stream.cpp \ + common/memory_arena.cpp \ + common/page_fault_handler.cpp \ + common/progress_callback.cpp \ + common/shiftjis.cpp \ + common/state_wrapper.cpp \ + common/string.cpp \ + common/string_util.cpp \ + common/timer.cpp \ + common/timestamp.cpp \ + common/vulkan/builders.cpp \ + common/vulkan/context.cpp \ + common/vulkan/shader_cache.cpp \ + common/vulkan/shader_compiler.cpp \ + common/vulkan/staging_buffer.cpp \ + common/vulkan/staging_texture.cpp \ + common/vulkan/stream_buffer.cpp \ + common/vulkan/swap_chain.cpp \ + common/vulkan/texture.cpp \ + common/vulkan/util.cpp \ + common/wav_writer.cpp \ + common/gl/context_egl.cpp \ + common/gl/context_egl_android.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := glad +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/glad/include + +LOCAL_SRC_FILES := \ + dep/glad/src/glad.c \ + dep/glad/src/glad_egl.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := frontend-common +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/simpleini/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include + +LOCAL_SRC_FILES := \ + frontend-common/game_settings.cpp \ + frontend-common/opengl_host_display.cpp \ + frontend-common/vulkan_host_display.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := vulkan-loader +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/vulkan-loader/include + +LOCAL_SRC_FILES := \ + dep/vulkan-loader/src/vulkan_loader.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := stb +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/stb/include + +LOCAL_SRC_FILES := \ + dep/stb/src/stb_image.c \ + dep/stb/src/stb_image_resize.c \ + dep/stb/src/stb_image_write.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := libcue +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libcue/include/libcue + +LOCAL_SRC_FILES := \ + dep/libcue/src/cd.c \ + dep/libcue/src/cdtext.c \ + dep/libcue/src/cue_parser.c \ + dep/libcue/src/cue_parser.h \ + dep/libcue/src/cue_scanner.c \ + dep/libcue/src/rem.c \ + dep/libcue/src/time.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := libchdr +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libchdr/include \ + $(LOCAL_PATH)/dep/libchdr/include/libchdr \ + $(LOCAL_PATH)/dep/libFLAC/include \ + $(LOCAL_PATH)/dep/lzma/include + +LOCAL_SRC_FILES := \ + dep/libchdr/src/libchdr_bitstream.c \ + dep/libchdr/src/libchdr_cdrom.c \ + dep/libchdr/src/libchdr_chd.c \ + dep/libchdr/src/libchdr_flac.c \ + dep/libchdr/src/libchdr_huffman.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := glslang +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/glslang \ + $(LOCAL_PATH)/dep/glslang/glslang/Include + + +LOCAL_SRC_FILES := \ + dep/glslang/glslang/CInterface/glslang_c_interface.cpp \ + dep/glslang/glslang/GenericCodeGen/CodeGen.cpp \ + dep/glslang/glslang/GenericCodeGen/Link.cpp \ + dep/glslang/glslang/MachineIndependent/attribute.cpp \ + dep/glslang/glslang/MachineIndependent/Constant.cpp \ + dep/glslang/glslang/MachineIndependent/glslang_tab.cpp \ + dep/glslang/glslang/MachineIndependent/InfoSink.cpp \ + dep/glslang/glslang/MachineIndependent/Initialize.cpp \ + dep/glslang/glslang/MachineIndependent/Intermediate.cpp \ + dep/glslang/glslang/MachineIndependent/intermOut.cpp \ + dep/glslang/glslang/MachineIndependent/IntermTraverse.cpp \ + dep/glslang/glslang/MachineIndependent/iomapper.cpp \ + dep/glslang/glslang/MachineIndependent/limits.cpp \ + dep/glslang/glslang/MachineIndependent/linkValidate.cpp \ + dep/glslang/glslang/MachineIndependent/parseConst.cpp \ + dep/glslang/glslang/MachineIndependent/ParseContextBase.cpp \ + dep/glslang/glslang/MachineIndependent/ParseHelper.cpp \ + dep/glslang/glslang/MachineIndependent/PoolAlloc.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpAtom.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpContext.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp \ + dep/glslang/glslang/MachineIndependent/propagateNoContraction.cpp \ + dep/glslang/glslang/MachineIndependent/reflection.cpp \ + dep/glslang/glslang/MachineIndependent/RemoveTree.cpp \ + dep/glslang/glslang/MachineIndependent/Scan.cpp \ + dep/glslang/glslang/MachineIndependent/ShaderLang.cpp \ + dep/glslang/glslang/MachineIndependent/SymbolTable.cpp \ + dep/glslang/glslang/MachineIndependent/Versions.cpp \ + dep/glslang/OGLCompilersDLL/InitializeDll.cpp \ + dep/glslang/SPIRV/disassemble.cpp \ + dep/glslang/SPIRV/doc.cpp \ + dep/glslang/SPIRV/GlslangToSpv.cpp \ + dep/glslang/SPIRV/InReadableOrder.cpp \ + dep/glslang/SPIRV/Logger.cpp \ + dep/glslang/SPIRV/SpvBuilder.cpp \ + dep/glslang/SPIRV/SpvPostProcess.cpp \ + dep/glslang/SPIRV/SPVRemapper.cpp \ + dep/glslang/SPIRV/SpvTools.cpp \ + dep/glslang/StandAlone/ResourceLimits.cpp \ + dep/glslang/StandAlone/resource_limits_c.cpp \ + dep/glslang/glslang/OSDependent/Unix/ossource.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := lzma +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/lzma/include + +LOCAL_SRC_FILES := \ + dep/lzma/src/Alloc.c \ + dep/lzma/src/Bra86.c \ + dep/lzma/src/BraIA64.c \ + dep/lzma/src/CpuArch.c \ + dep/lzma/src/Delta.c \ + dep/lzma/src/LzFind.c \ + dep/lzma/src/Lzma86Dec.c \ + dep/lzma/src/Lzma86Enc.c \ + dep/lzma/src/LzmaDec.c \ + dep/lzma/src/LzmaEnc.c \ + dep/lzma/src/LzmaLib.c \ + dep/lzma/src/Sort.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -D_7ZIP_ST +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := libFLAC +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libFLAC/include \ + $(LOCAL_PATH)/dep/libFLAC/src/include + +LOCAL_SRC_FILES := \ + dep/libFLAC/src/bitmath.c \ + dep/libFLAC/src/bitreader.c \ + dep/libFLAC/src/cpu.c \ + dep/libFLAC/src/crc.c \ + dep/libFLAC/src/fixed.c \ + dep/libFLAC/src/fixed_intrin_sse2.c \ + dep/libFLAC/src/fixed_intrin_ssse3.c \ + dep/libFLAC/src/float.c \ + dep/libFLAC/src/format.c \ + dep/libFLAC/src/lpc.c \ + dep/libFLAC/src/lpc_intrin_avx2.c \ + dep/libFLAC/src/lpc_intrin_sse.c \ + dep/libFLAC/src/lpc_intrin_sse2.c \ + dep/libFLAC/src/lpc_intrin_sse41.c \ + dep/libFLAC/src/md5.c \ + dep/libFLAC/src/memory.c \ + dep/libFLAC/src/metadata_iterators.c \ + dep/libFLAC/src/metadata_object.c \ + dep/libFLAC/src/stream_decoder.c \ + dep/libFLAC/src/window.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -DPACKAGE_VERSION='"1.3.2"' -DFLAC__HAS_OGG=0 -DHAVE_LROUND -DHAVE_STDINT_H -DHAVE_STDLIB_H +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := vixl +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/vixl/include \ + $(LOCAL_PATH)/dep/vixl/include/vixl \ + $(LOCAL_PATH)/dep/vixl/include/vixl/aarch64 + +LOCAL_SRC_FILES := \ + dep/vixl/src/code-buffer-vixl.cc \ + dep/vixl/src/compiler-intrinsics-vixl.cc \ + dep/vixl/src/cpu-features.cc \ + dep/vixl/src/utils-vixl.cc \ + dep/vixl/src/aarch64/assembler-aarch64.cc \ + dep/vixl/src/aarch64/cpu-aarch64.cc \ + dep/vixl/src/aarch64/cpu-features-auditor-aarch64.cc \ + dep/vixl/src/aarch64/decoder-aarch64.cc \ + dep/vixl/src/aarch64/disasm-aarch64.cc \ + dep/vixl/src/aarch64/instructions-aarch64.cc \ + dep/vixl/src/aarch64/instrument-aarch64.cc \ + dep/vixl/src/aarch64/logic-aarch64.cc \ + dep/vixl/src/aarch64/macro-assembler-aarch64.cc \ + dep/vixl/src/aarch64/operands-aarch64.cc \ + dep/vixl/src/aarch64/pointer-auth-aarch64.cc \ + dep/vixl/src/aarch64/simulator-aarch64.cc + + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -DVIXL_CODE_BUFFER_MALLOC +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := duckstation +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/libretro \ + $(LOCAL_PATH)/dep/libretro-common/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include + +LOCAL_SRC_FILES := \ + libretro/libretro_audio_stream.cpp \ + libretro/libretro_game_settings.cpp \ + libretro/libretro_host_display.cpp \ + libretro/libretro_host_interface.cpp \ + libretro/libretro_opengl_host_display.cpp \ + libretro/libretro_settings_interface.cpp \ + libretro/libretro_vulkan_host_display.cpp \ + libretro/main.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W $(DEFINES) -Wno-unused-variable +LOCAL_CPPFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W $(DEFINES) -Wno-write-strings -Wno-missing-braces -Wno-error=format-security -Wno-unused-variable +LOCAL_LDFLAGS := -Wl,-O2 +LOCAL_STATIC_LIBRARIES := core common glad frontend-common vulkan-loader stb libcue libchdr glslang lzma libFLAC vixl +LOCAL_LDLIBS := -llog -lz + +include $(BUILD_SHARED_LIBRARY) + diff --git a/jni/Android.mk.all b/jni/Android.mk.all new file mode 100755 index 0000000..de9255f --- /dev/null +++ b/jni/Android.mk.all @@ -0,0 +1,454 @@ +LOCAL_PATH := $(call my-dir) + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := core +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/core \ + $(LOCAL_PATH)/dep/stb/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include \ + $(LOCAL_PATH)/dep/vixl/include + +LOCAL_SRC_FILES := \ + core/analog_controller.cpp \ + core/analog_joystick.cpp \ + core/bios.cpp \ + core/bus.cpp \ + core/cdrom.cpp \ + core/cdrom_async_reader.cpp \ + core/cheats.cpp \ + core/controller.cpp \ + core/cpu_code_cache.cpp \ + core/cpu_core.cpp \ + core/cpu_disasm.cpp \ + core/cpu_types.cpp \ + core/digital_controller.cpp \ + core/dma.cpp \ + core/gpu.cpp \ + core/gpu_backend.cpp \ + core/gpu_commands.cpp \ + core/gpu_hw.cpp \ + core/gpu_hw_opengl.cpp \ + core/gpu_hw_shadergen.cpp \ + core/gpu_hw_vulkan.cpp \ + core/gpu_sw.cpp \ + core/gpu_sw_backend.cpp \ + core/gte.cpp \ + core/host_display.cpp \ + core/host_interface.cpp \ + core/host_interface_progress_callback.cpp \ + core/interrupt_controller.cpp \ + core/libcrypt_game_codes.cpp \ + core/mdec.cpp \ + core/memory_card.cpp \ + core/memory_card_image.cpp \ + core/namco_guncon.cpp \ + core/negcon.cpp \ + core/pad.cpp \ + core/pgxp.cpp \ + core/playstation_mouse.cpp \ + core/psf_loader.cpp \ + core/resources.cpp \ + core/settings.cpp \ + core/shadergen.cpp \ + core/sio.cpp \ + core/spu.cpp \ + core/system.cpp \ + core/timers.cpp \ + core/timing_event.cpp \ + core/cpu_recompiler_code_generator.cpp \ + core/cpu_recompiler_code_generator_generic.cpp \ + core/cpu_recompiler_register_cache.cpp \ + core/cpu_recompiler_code_generator_aarch64.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -DWITH_RECOMPILER=1 -DWITH_MMAP_FASTMEM=1 +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := common +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libcue/include \ + $(LOCAL_PATH)/dep/libchdr/include \ + $(LOCAL_PATH)/dep/stb/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/minizip/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include \ + $(LOCAL_PATH)/dep/glslang + +LOCAL_SRC_FILES := \ + common/assert.cpp \ + common/audio_stream.cpp \ + common/byte_stream.cpp \ + common/cd_image.cpp \ + common/cd_image_bin.cpp \ + common/cd_image_cue.cpp \ + common/cd_image_chd.cpp \ + common/cd_image_hasher.cpp \ + common/cd_image_memory.cpp \ + common/cd_subchannel_replacement.cpp \ + common/cd_xa.cpp \ + common/event.cpp \ + common/file_system.cpp \ + common/image.cpp \ + common/gl/context.cpp \ + common/gl/program.cpp \ + common/gl/shader_cache.cpp \ + common/gl/stream_buffer.cpp \ + common/gl/texture.cpp \ + common/iso_reader.cpp \ + common/jit_code_buffer.cpp \ + common/log.cpp \ + common/md5_digest.cpp \ + common/minizip_helpers.cpp \ + common/null_audio_stream.cpp \ + common/memory_arena.cpp \ + common/page_fault_handler.cpp \ + common/progress_callback.cpp \ + common/shiftjis.cpp \ + common/state_wrapper.cpp \ + common/string.cpp \ + common/string_util.cpp \ + common/timer.cpp \ + common/timestamp.cpp \ + common/vulkan/builders.cpp \ + common/vulkan/context.cpp \ + common/vulkan/shader_cache.cpp \ + common/vulkan/shader_compiler.cpp \ + common/vulkan/staging_buffer.cpp \ + common/vulkan/staging_texture.cpp \ + common/vulkan/stream_buffer.cpp \ + common/vulkan/swap_chain.cpp \ + common/vulkan/texture.cpp \ + common/vulkan/util.cpp \ + common/wav_writer.cpp \ + common/gl/context_egl.cpp \ + common/gl/context_egl_android.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := glad +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/glad/include + +LOCAL_SRC_FILES := \ + dep/glad/src/glad.c \ + dep/glad/src/glad_egl.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := frontend-common +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/simpleini/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include + +LOCAL_SRC_FILES := \ + frontend-common/game_settings.cpp \ + frontend-common/opengl_host_display.cpp \ + frontend-common/vulkan_host_display.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := vulkan-loader +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/vulkan-loader/include + +LOCAL_SRC_FILES := \ + dep/vulkan-loader/src/vulkan_loader.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := stb +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/stb/include + +LOCAL_SRC_FILES := \ + dep/stb/src/stb_image.c \ + dep/stb/src/stb_image_resize.c \ + dep/stb/src/stb_image_write.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := libcue +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libcue/include/libcue + +LOCAL_SRC_FILES := \ + dep/libcue/src/cd.c \ + dep/libcue/src/cdtext.c \ + dep/libcue/src/cue_parser.c \ + dep/libcue/src/cue_parser.h \ + dep/libcue/src/cue_scanner.c \ + dep/libcue/src/rem.c \ + dep/libcue/src/time.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := libchdr +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libchdr/include \ + $(LOCAL_PATH)/dep/libchdr/include/libchdr \ + $(LOCAL_PATH)/dep/libFLAC/include \ + $(LOCAL_PATH)/dep/lzma/include + +LOCAL_SRC_FILES := \ + dep/libchdr/src/libchdr_bitstream.c \ + dep/libchdr/src/libchdr_cdrom.c \ + dep/libchdr/src/libchdr_chd.c \ + dep/libchdr/src/libchdr_flac.c \ + dep/libchdr/src/libchdr_huffman.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := glslang +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/glslang \ + $(LOCAL_PATH)/dep/glslang/glslang/Include + + +LOCAL_SRC_FILES := \ + dep/glslang/glslang/CInterface/glslang_c_interface.cpp \ + dep/glslang/glslang/GenericCodeGen/CodeGen.cpp \ + dep/glslang/glslang/GenericCodeGen/Link.cpp \ + dep/glslang/glslang/MachineIndependent/attribute.cpp \ + dep/glslang/glslang/MachineIndependent/Constant.cpp \ + dep/glslang/glslang/MachineIndependent/glslang_tab.cpp \ + dep/glslang/glslang/MachineIndependent/InfoSink.cpp \ + dep/glslang/glslang/MachineIndependent/Initialize.cpp \ + dep/glslang/glslang/MachineIndependent/Intermediate.cpp \ + dep/glslang/glslang/MachineIndependent/intermOut.cpp \ + dep/glslang/glslang/MachineIndependent/IntermTraverse.cpp \ + dep/glslang/glslang/MachineIndependent/iomapper.cpp \ + dep/glslang/glslang/MachineIndependent/limits.cpp \ + dep/glslang/glslang/MachineIndependent/linkValidate.cpp \ + dep/glslang/glslang/MachineIndependent/parseConst.cpp \ + dep/glslang/glslang/MachineIndependent/ParseContextBase.cpp \ + dep/glslang/glslang/MachineIndependent/ParseHelper.cpp \ + dep/glslang/glslang/MachineIndependent/PoolAlloc.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpAtom.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpContext.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp \ + dep/glslang/glslang/MachineIndependent/propagateNoContraction.cpp \ + dep/glslang/glslang/MachineIndependent/reflection.cpp \ + dep/glslang/glslang/MachineIndependent/RemoveTree.cpp \ + dep/glslang/glslang/MachineIndependent/Scan.cpp \ + dep/glslang/glslang/MachineIndependent/ShaderLang.cpp \ + dep/glslang/glslang/MachineIndependent/SymbolTable.cpp \ + dep/glslang/glslang/MachineIndependent/Versions.cpp \ + dep/glslang/OGLCompilersDLL/InitializeDll.cpp \ + dep/glslang/SPIRV/disassemble.cpp \ + dep/glslang/SPIRV/doc.cpp \ + dep/glslang/SPIRV/GlslangToSpv.cpp \ + dep/glslang/SPIRV/InReadableOrder.cpp \ + dep/glslang/SPIRV/Logger.cpp \ + dep/glslang/SPIRV/SpvBuilder.cpp \ + dep/glslang/SPIRV/SpvPostProcess.cpp \ + dep/glslang/SPIRV/SPVRemapper.cpp \ + dep/glslang/SPIRV/SpvTools.cpp \ + dep/glslang/StandAlone/ResourceLimits.cpp \ + dep/glslang/StandAlone/resource_limits_c.cpp \ + dep/glslang/glslang/OSDependent/Unix/ossource.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := lzma +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/lzma/include + +LOCAL_SRC_FILES := \ + dep/lzma/src/Alloc.c \ + dep/lzma/src/Bra86.c \ + dep/lzma/src/BraIA64.c \ + dep/lzma/src/CpuArch.c \ + dep/lzma/src/Delta.c \ + dep/lzma/src/LzFind.c \ + dep/lzma/src/Lzma86Dec.c \ + dep/lzma/src/Lzma86Enc.c \ + dep/lzma/src/LzmaDec.c \ + dep/lzma/src/LzmaEnc.c \ + dep/lzma/src/LzmaLib.c \ + dep/lzma/src/Sort.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -D_7ZIP_ST +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := libFLAC +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libFLAC/include \ + $(LOCAL_PATH)/dep/libFLAC/src/include + +LOCAL_SRC_FILES := \ + dep/libFLAC/src/bitmath.c \ + dep/libFLAC/src/bitreader.c \ + dep/libFLAC/src/cpu.c \ + dep/libFLAC/src/crc.c \ + dep/libFLAC/src/fixed.c \ + dep/libFLAC/src/fixed_intrin_sse2.c \ + dep/libFLAC/src/fixed_intrin_ssse3.c \ + dep/libFLAC/src/float.c \ + dep/libFLAC/src/format.c \ + dep/libFLAC/src/lpc.c \ + dep/libFLAC/src/lpc_intrin_avx2.c \ + dep/libFLAC/src/lpc_intrin_sse.c \ + dep/libFLAC/src/lpc_intrin_sse2.c \ + dep/libFLAC/src/lpc_intrin_sse41.c \ + dep/libFLAC/src/md5.c \ + dep/libFLAC/src/memory.c \ + dep/libFLAC/src/metadata_iterators.c \ + dep/libFLAC/src/metadata_object.c \ + dep/libFLAC/src/stream_decoder.c \ + dep/libFLAC/src/window.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -DPACKAGE_VERSION='"1.3.2"' -DFLAC__HAS_OGG=0 -DHAVE_LROUND -DHAVE_STDINT_H -DHAVE_STDLIB_H +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := vixl +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/vixl/include \ + $(LOCAL_PATH)/dep/vixl/include/vixl \ + $(LOCAL_PATH)/dep/vixl/include/vixl/aarch64 + +LOCAL_SRC_FILES := \ + dep/vixl/src/code-buffer-vixl.cc \ + dep/vixl/src/compiler-intrinsics-vixl.cc \ + dep/vixl/src/cpu-features.cc \ + dep/vixl/src/utils-vixl.cc \ + dep/vixl/src/aarch64/assembler-aarch64.cc \ + dep/vixl/src/aarch64/cpu-aarch64.cc \ + dep/vixl/src/aarch64/cpu-features-auditor-aarch64.cc \ + dep/vixl/src/aarch64/decoder-aarch64.cc \ + dep/vixl/src/aarch64/disasm-aarch64.cc \ + dep/vixl/src/aarch64/instructions-aarch64.cc \ + dep/vixl/src/aarch64/instrument-aarch64.cc \ + dep/vixl/src/aarch64/logic-aarch64.cc \ + dep/vixl/src/aarch64/macro-assembler-aarch64.cc \ + dep/vixl/src/aarch64/operands-aarch64.cc \ + dep/vixl/src/aarch64/pointer-auth-aarch64.cc \ + dep/vixl/src/aarch64/simulator-aarch64.cc + + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -DVIXL_CODE_BUFFER_MALLOC +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) + + +################################################################################ +include $(CLEAR_VARS) +LOCAL_MODULE := duckstation +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/libretro \ + $(LOCAL_PATH)/dep/libretro-common/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include + +LOCAL_SRC_FILES := \ + libretro/libretro_audio_stream.cpp \ + libretro/libretro_game_settings.cpp \ + libretro/libretro_host_display.cpp \ + libretro/libretro_host_interface.cpp \ + libretro/libretro_opengl_host_display.cpp \ + libretro/libretro_settings_interface.cpp \ + libretro/libretro_vulkan_host_display.cpp \ + libretro/main.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W $(DEFINES) -Wno-unused-variable +LOCAL_CPPFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W $(DEFINES) -Wno-write-strings -Wno-missing-braces -Wno-error=format-security -Wno-unused-variable +LOCAL_LDFLAGS := -Wl,-O2 +LOCAL_STATIC_LIBRARIES := core common glad frontend-common vulkan-loader stb libcue libchdr glslang lzma libFLAC vixl +LOCAL_LDLIBS := -llog -lz + +include $(BUILD_SHARED_LIBRARY) + diff --git a/jni/Android.mk.common b/jni/Android.mk.common new file mode 100644 index 0000000..2cf29cc --- /dev/null +++ b/jni/Android.mk.common @@ -0,0 +1,68 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := common +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libcue/include \ + $(LOCAL_PATH)/dep/libchdr/include \ + $(LOCAL_PATH)/dep/stb/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/minizip/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include \ + $(LOCAL_PATH)/dep/glslang + +LOCAL_SRC_FILES := \ + common/assert.cpp \ + common/audio_stream.cpp \ + common/byte_stream.cpp \ + common/cd_image.cpp \ + common/cd_image_bin.cpp \ + common/cd_image_cue.cpp \ + common/cd_image_chd.cpp \ + common/cd_image_hasher.cpp \ + common/cd_image_memory.cpp \ + common/cd_subchannel_replacement.cpp \ + common/cd_xa.cpp \ + common/event.cpp \ + common/file_system.cpp \ + common/image.cpp \ + common/gl/context.cpp \ + common/gl/program.cpp \ + common/gl/shader_cache.cpp \ + common/gl/stream_buffer.cpp \ + common/gl/texture.cpp \ + common/iso_reader.cpp \ + common/jit_code_buffer.cpp \ + common/log.cpp \ + common/md5_digest.cpp \ + common/minizip_helpers.cpp \ + common/null_audio_stream.cpp \ + common/memory_arena.cpp \ + common/page_fault_handler.cpp \ + common/progress_callback.cpp \ + common/shiftjis.cpp \ + common/state_wrapper.cpp \ + common/string.cpp \ + common/string_util.cpp \ + common/timer.cpp \ + common/timestamp.cpp \ + common/vulkan/builders.cpp \ + common/vulkan/context.cpp \ + common/vulkan/shader_cache.cpp \ + common/vulkan/shader_compiler.cpp \ + common/vulkan/staging_buffer.cpp \ + common/vulkan/staging_texture.cpp \ + common/vulkan/stream_buffer.cpp \ + common/vulkan/swap_chain.cpp \ + common/vulkan/texture.cpp \ + common/vulkan/util.cpp \ + common/wav_writer.cpp \ + common/gl/context_egl.cpp \ + common/gl/context_egl_android.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Android.mk.core b/jni/Android.mk.core new file mode 100644 index 0000000..fa16273 --- /dev/null +++ b/jni/Android.mk.core @@ -0,0 +1,69 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := core +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/core \ + $(LOCAL_PATH)/dep/stb/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include \ + $(LOCAL_PATH)/dep/vixl/include + +LOCAL_SRC_FILES := \ + core/analog_controller.cpp \ + core/analog_joystick.cpp \ + core/bios.cpp \ + core/bus.cpp \ + core/cdrom.cpp \ + core/cdrom_async_reader.cpp \ + core/cheats.cpp \ + core/controller.cpp \ + core/cpu_code_cache.cpp \ + core/cpu_core.cpp \ + core/cpu_disasm.cpp \ + core/cpu_types.cpp \ + core/digital_controller.cpp \ + core/dma.cpp \ + core/gpu.cpp \ + core/gpu_backend.cpp \ + core/gpu_commands.cpp \ + core/gpu_hw.cpp \ + core/gpu_hw_opengl.cpp \ + core/gpu_hw_shadergen.cpp \ + core/gpu_hw_vulkan.cpp \ + core/gpu_sw.cpp \ + core/gpu_sw_backend.cpp \ + core/gte.cpp \ + core/host_display.cpp \ + core/host_interface.cpp \ + core/host_interface_progress_callback.cpp \ + core/interrupt_controller.cpp \ + core/libcrypt_game_codes.cpp \ + core/mdec.cpp \ + core/memory_card.cpp \ + core/memory_card_image.cpp \ + core/namco_guncon.cpp \ + core/negcon.cpp \ + core/pad.cpp \ + core/pgxp.cpp \ + core/playstation_mouse.cpp \ + core/psf_loader.cpp \ + core/resources.cpp \ + core/settings.cpp \ + core/shadergen.cpp \ + core/sio.cpp \ + core/spu.cpp \ + core/system.cpp \ + core/timers.cpp \ + core/timing_event.cpp \ + core/cpu_recompiler_code_generator.cpp \ + core/cpu_recompiler_code_generator_generic.cpp \ + core/cpu_recompiler_register_cache.cpp \ + core/cpu_recompiler_code_generator_aarch64.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -DWITH_RECOMPILER=1 -DWITH_MMAP_FASTMEM=1 +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Android.mk.frontend-common b/jni/Android.mk.frontend-common new file mode 100644 index 0000000..2f8238e --- /dev/null +++ b/jni/Android.mk.frontend-common @@ -0,0 +1,20 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := frontend-common +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/simpleini/include \ + $(LOCAL_PATH)/dep/glad/include \ + $(LOCAL_PATH)/dep/vulkan-loader/include + +LOCAL_SRC_FILES := \ + frontend-common/game_settings.cpp \ + frontend-common/opengl_host_display.cpp \ + frontend-common/vulkan_host_display.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Android.mk.glad b/jni/Android.mk.glad new file mode 100644 index 0000000..bf8d431 --- /dev/null +++ b/jni/Android.mk.glad @@ -0,0 +1,17 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := glad +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/glad/include + +LOCAL_SRC_FILES := \ + dep/glad/src/glad.c \ + dep/glad/src/glad_egl.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Android.mk.glslang b/jni/Android.mk.glslang new file mode 100644 index 0000000..6160c2f --- /dev/null +++ b/jni/Android.mk.glslang @@ -0,0 +1,60 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := glslang +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/glslang \ + $(LOCAL_PATH)/dep/glslang/glslang/Include + + +LOCAL_SRC_FILES := \ + dep/glslang/glslang/CInterface/glslang_c_interface.cpp \ + dep/glslang/glslang/GenericCodeGen/CodeGen.cpp \ + dep/glslang/glslang/GenericCodeGen/Link.cpp \ + dep/glslang/glslang/MachineIndependent/attribute.cpp \ + dep/glslang/glslang/MachineIndependent/Constant.cpp \ + dep/glslang/glslang/MachineIndependent/glslang_tab.cpp \ + dep/glslang/glslang/MachineIndependent/InfoSink.cpp \ + dep/glslang/glslang/MachineIndependent/Initialize.cpp \ + dep/glslang/glslang/MachineIndependent/Intermediate.cpp \ + dep/glslang/glslang/MachineIndependent/intermOut.cpp \ + dep/glslang/glslang/MachineIndependent/IntermTraverse.cpp \ + dep/glslang/glslang/MachineIndependent/iomapper.cpp \ + dep/glslang/glslang/MachineIndependent/limits.cpp \ + dep/glslang/glslang/MachineIndependent/linkValidate.cpp \ + dep/glslang/glslang/MachineIndependent/parseConst.cpp \ + dep/glslang/glslang/MachineIndependent/ParseContextBase.cpp \ + dep/glslang/glslang/MachineIndependent/ParseHelper.cpp \ + dep/glslang/glslang/MachineIndependent/PoolAlloc.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpAtom.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpContext.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp \ + dep/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp \ + dep/glslang/glslang/MachineIndependent/propagateNoContraction.cpp \ + dep/glslang/glslang/MachineIndependent/reflection.cpp \ + dep/glslang/glslang/MachineIndependent/RemoveTree.cpp \ + dep/glslang/glslang/MachineIndependent/Scan.cpp \ + dep/glslang/glslang/MachineIndependent/ShaderLang.cpp \ + dep/glslang/glslang/MachineIndependent/SymbolTable.cpp \ + dep/glslang/glslang/MachineIndependent/Versions.cpp \ + dep/glslang/OGLCompilersDLL/InitializeDll.cpp \ + dep/glslang/SPIRV/disassemble.cpp \ + dep/glslang/SPIRV/doc.cpp \ + dep/glslang/SPIRV/GlslangToSpv.cpp \ + dep/glslang/SPIRV/InReadableOrder.cpp \ + dep/glslang/SPIRV/Logger.cpp \ + dep/glslang/SPIRV/SpvBuilder.cpp \ + dep/glslang/SPIRV/SpvPostProcess.cpp \ + dep/glslang/SPIRV/SPVRemapper.cpp \ + dep/glslang/SPIRV/SpvTools.cpp \ + dep/glslang/StandAlone/ResourceLimits.cpp \ + dep/glslang/StandAlone/resource_limits_c.cpp \ + dep/glslang/glslang/OSDependent/Unix/ossource.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Android.mk.libFLAC b/jni/Android.mk.libFLAC new file mode 100644 index 0000000..03b8b65 --- /dev/null +++ b/jni/Android.mk.libFLAC @@ -0,0 +1,36 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := libFLAC +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libFLAC/include \ + $(LOCAL_PATH)/dep/libFLAC/src/include + +LOCAL_SRC_FILES := \ + dep/libFLAC/src/bitmath.c \ + dep/libFLAC/src/bitreader.c \ + dep/libFLAC/src/cpu.c \ + dep/libFLAC/src/crc.c \ + dep/libFLAC/src/fixed.c \ + dep/libFLAC/src/fixed_intrin_sse2.c \ + dep/libFLAC/src/fixed_intrin_ssse3.c \ + dep/libFLAC/src/float.c \ + dep/libFLAC/src/format.c \ + dep/libFLAC/src/lpc.c \ + dep/libFLAC/src/lpc_intrin_avx2.c \ + dep/libFLAC/src/lpc_intrin_sse.c \ + dep/libFLAC/src/lpc_intrin_sse2.c \ + dep/libFLAC/src/lpc_intrin_sse41.c \ + dep/libFLAC/src/md5.c \ + dep/libFLAC/src/memory.c \ + dep/libFLAC/src/metadata_iterators.c \ + dep/libFLAC/src/metadata_object.c \ + dep/libFLAC/src/stream_decoder.c \ + dep/libFLAC/src/window.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -DPACKAGE_VERSION='"1.3.2"' -DFLAC__HAS_OGG=0 -DHAVE_LROUND -DHAVE_STDINT_H -DHAVE_STDLIB_H +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Android.mk.libchdr b/jni/Android.mk.libchdr new file mode 100644 index 0000000..caca865 --- /dev/null +++ b/jni/Android.mk.libchdr @@ -0,0 +1,23 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := libchdr +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libchdr/include \ + $(LOCAL_PATH)/dep/libchdr/include/libchdr \ + $(LOCAL_PATH)/dep/libFLAC/include \ + $(LOCAL_PATH)/dep/lzma/include + +LOCAL_SRC_FILES := \ + dep/libchdr/src/libchdr_bitstream.c \ + dep/libchdr/src/libchdr_cdrom.c \ + dep/libchdr/src/libchdr_chd.c \ + dep/libchdr/src/libchdr_flac.c \ + dep/libchdr/src/libchdr_huffman.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Android.mk.libcue b/jni/Android.mk.libcue new file mode 100644 index 0000000..2d1d69c --- /dev/null +++ b/jni/Android.mk.libcue @@ -0,0 +1,22 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := libcue +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/libcue/include/libcue + +LOCAL_SRC_FILES := \ + dep/libcue/src/cd.c \ + dep/libcue/src/cdtext.c \ + dep/libcue/src/cue_parser.c \ + dep/libcue/src/cue_parser.h \ + dep/libcue/src/cue_scanner.c \ + dep/libcue/src/rem.c \ + dep/libcue/src/time.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Android.mk.lzma.txt b/jni/Android.mk.lzma.txt new file mode 100644 index 0000000..f91f05e --- /dev/null +++ b/jni/Android.mk.lzma.txt @@ -0,0 +1,27 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := lzma +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/lzma/include + +LOCAL_SRC_FILES := \ + dep/lzma/src/Alloc.c \ + dep/lzma/src/Bra86.c \ + dep/lzma/src/BraIA64.c \ + dep/lzma/src/CpuArch.c \ + dep/lzma/src/Delta.c \ + dep/lzma/src/LzFind.c \ + dep/lzma/src/Lzma86Dec.c \ + dep/lzma/src/Lzma86Enc.c \ + dep/lzma/src/LzmaDec.c \ + dep/lzma/src/LzmaEnc.c \ + dep/lzma/src/LzmaLib.c \ + dep/lzma/src/Sort.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -D_7ZIP_ST +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Android.mk.stb b/jni/Android.mk.stb new file mode 100644 index 0000000..92910fd --- /dev/null +++ b/jni/Android.mk.stb @@ -0,0 +1,18 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := stb +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/stb/include + +LOCAL_SRC_FILES := \ + dep/stb/src/stb_image.c \ + dep/stb/src/stb_image_resize.c \ + dep/stb/src/stb_image_write.c + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Android.mk.vixl b/jni/Android.mk.vixl new file mode 100644 index 0000000..70b6d90 --- /dev/null +++ b/jni/Android.mk.vixl @@ -0,0 +1,34 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := vixl +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/vixl/include \ + $(LOCAL_PATH)/dep/vixl/include/vixl \ + $(LOCAL_PATH)/dep/vixl/include/vixl/aarch64 + +LOCAL_SRC_FILES := \ + dep/vixl/src/code-buffer-vixl.cc \ + dep/vixl/src/compiler-intrinsics-vixl.cc \ + dep/vixl/src/cpu-features.cc \ + dep/vixl/src/utils-vixl.cc \ + dep/vixl/src/aarch64/assembler-aarch64.cc \ + dep/vixl/src/aarch64/cpu-aarch64.cc \ + dep/vixl/src/aarch64/cpu-features-auditor-aarch64.cc \ + dep/vixl/src/aarch64/decoder-aarch64.cc \ + dep/vixl/src/aarch64/disasm-aarch64.cc \ + dep/vixl/src/aarch64/instructions-aarch64.cc \ + dep/vixl/src/aarch64/instrument-aarch64.cc \ + dep/vixl/src/aarch64/logic-aarch64.cc \ + dep/vixl/src/aarch64/macro-assembler-aarch64.cc \ + dep/vixl/src/aarch64/operands-aarch64.cc \ + dep/vixl/src/aarch64/pointer-auth-aarch64.cc \ + dep/vixl/src/aarch64/simulator-aarch64.cc + + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH -DVIXL_CODE_BUFFER_MALLOC +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) diff --git a/jni/Android.mk.vulkan-loader b/jni/Android.mk.vulkan-loader new file mode 100644 index 0000000..a053df3 --- /dev/null +++ b/jni/Android.mk.vulkan-loader @@ -0,0 +1,16 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := glad +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/dep/vulkan-loader/include + +LOCAL_SRC_FILES := \ + dep/vulkan-loader/src/vulkan_loader.cpp + +LOCAL_ARM_MODE := arm +LOCAL_ARM_NEON := true +DEFINES := -DZZF_PATCH +LOCAL_CFLAGS := -pipe -O2 -std=c++17 -fsigned-char -Wall -W -Wno-unused-parameter $(DEFINES) + +include $(BUILD_STATIC_LIBRARY) \ No newline at end of file diff --git a/jni/Application.mk b/jni/Application.mk new file mode 100755 index 0000000..6726d91 --- /dev/null +++ b/jni/Application.mk @@ -0,0 +1,5 @@ +APP_OPTIM := release +APP_ABI := arm64-v8a +APP_PLATFORM := android-17 +APP_STL := c++_static +NDK_TOOLCHAIN_VERSION = llvm \ No newline at end of file diff --git a/jni/common/CMakeLists.txt b/jni/common/CMakeLists.txt new file mode 100644 index 0000000..e71ecee --- /dev/null +++ b/jni/common/CMakeLists.txt @@ -0,0 +1,194 @@ +add_library(common + align.h + assert.cpp + assert.h + audio_stream.cpp + audio_stream.h + bitfield.h + bitutils.h + byte_stream.cpp + byte_stream.h + cd_image.cpp + cd_image.h + cd_image_bin.cpp + cd_image_cue.cpp + cd_image_chd.cpp + cd_image_hasher.cpp + cd_image_hasher.h + cd_image_memory.cpp + cd_subchannel_replacement.cpp + cd_subchannel_replacement.h + cd_xa.cpp + cd_xa.h + cpu_detect.h + dimensional_array.h + event.cpp + event.h + fifo_queue.h + file_system.cpp + file_system.h + image.cpp + image.h + gl/context.cpp + gl/context.h + gl/program.cpp + gl/program.h + gl/shader_cache.cpp + gl/shader_cache.h + gl/stream_buffer.cpp + gl/stream_buffer.h + gl/texture.cpp + gl/texture.h + hash_combine.h + heap_array.h + iso_reader.cpp + iso_reader.h + jit_code_buffer.cpp + jit_code_buffer.h + log.cpp + log.h + make_array.h + md5_digest.cpp + md5_digest.h + minizip_helpers.cpp + minizip_helpers.h + null_audio_stream.cpp + null_audio_stream.h + memory_arena.cpp + memory_arena.h + page_fault_handler.cpp + page_fault_handler.h + rectangle.h + progress_callback.cpp + progress_callback.h + scope_guard.h + shiftjis.cpp + shiftjis.h + state_wrapper.cpp + state_wrapper.h + string.cpp + string.h + string_util.cpp + string_util.h + timer.cpp + timer.h + timestamp.cpp + timestamp.h + types.h + vulkan/builders.cpp + vulkan/builders.h + vulkan/context.cpp + vulkan/context.h + vulkan/shader_cache.cpp + vulkan/shader_cache.h + vulkan/shader_compiler.cpp + vulkan/shader_compiler.h + vulkan/staging_buffer.cpp + vulkan/staging_buffer.h + vulkan/staging_texture.cpp + vulkan/staging_texture.h + vulkan/stream_buffer.cpp + vulkan/stream_buffer.h + vulkan/swap_chain.cpp + vulkan/swap_chain.h + vulkan/texture.cpp + vulkan/texture.h + vulkan/util.cpp + vulkan/util.h + wav_writer.cpp + wav_writer.h +) + +target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") +target_include_directories(common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") +target_link_libraries(common PRIVATE glad libcue stb Threads::Threads libchdr glslang vulkan-loader zlib minizip) + +if(WIN32) + target_sources(common PRIVATE + gl/context_wgl.cpp + gl/context_wgl.h + d3d11/shader_cache.cpp + d3d11/shader_cache.h + d3d11/shader_compiler.cpp + d3d11/shader_compiler.h + d3d11/staging_texture.cpp + d3d11/staging_texture.h + d3d11/stream_buffer.cpp + d3d11/stream_buffer.h + d3d11/texture.cpp + d3d11/texture.h + windows_headers.h + win32_progress_callback.cpp + win32_progress_callback.h + ) + target_link_libraries(common PRIVATE d3dcompiler.lib) +endif() + +if(ANDROID) + target_link_libraries(common PRIVATE log) +endif() + +if(USE_X11) + target_sources(common PRIVATE + gl/x11_window.cpp + gl/x11_window.h + ) + target_compile_definitions(common PRIVATE "-DUSE_X11=1") + target_include_directories(common PRIVATE "${X11_INCLUDE_DIR}") + target_link_libraries(common PRIVATE "${X11_LIBRARIES}") +endif() + +if(USE_EGL) + target_sources(common PRIVATE + gl/context_egl.cpp + gl/context_egl.h + ) + target_compile_definitions(common PRIVATE "-DUSE_EGL=1") + + if(USE_X11) + target_sources(common PRIVATE + gl/context_egl_x11.cpp + gl/context_egl_x11.h + ) + endif() + if(ANDROID AND USE_EGL) + target_sources(common PRIVATE + gl/context_egl_android.cpp + gl/context_egl_android.h + ) + endif() +endif() + +if(USE_X11) + target_sources(common PRIVATE + gl/context_glx.cpp + gl/context_glx.h + ) + target_compile_definitions(common PRIVATE "-DUSE_GLX=1") +endif() + +if(USE_WAYLAND) + target_sources(common PRIVATE + gl/context_egl_wayland.cpp + gl/context_egl_wayland.h + ) + target_compile_definitions(common PRIVATE "-DUSE_WAYLAND=1") + target_link_libraries(common PRIVATE Wayland::Egl) +endif() + +if(APPLE) + # Needed for Vulkan Swap Chain. + target_link_libraries(common PRIVATE "objc") +endif() + +if(APPLE AND NOT BUILD_LIBRETRO_CORE) + target_sources(common PRIVATE + gl/context_agl.mm + gl/context_agl.h + ) +endif() + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + # We need -lrt for shm_unlink + target_link_libraries(common PRIVATE rt) +endif() diff --git a/jni/common/align.h b/jni/common/align.h new file mode 100644 index 0000000..ed893e3 --- /dev/null +++ b/jni/common/align.h @@ -0,0 +1,39 @@ +#pragma once + +namespace Common { +template +constexpr bool IsAligned(T value, unsigned int alignment) +{ + return (value % static_cast(alignment)) == 0; +} +template +constexpr T AlignUp(T value, unsigned int alignment) +{ + return (value + static_cast(alignment - 1)) / static_cast(alignment) * static_cast(alignment); +} +template +constexpr T AlignDown(T value, unsigned int alignment) +{ + return value / static_cast(alignment) * static_cast(alignment); +} +template +constexpr bool IsAlignedPow2(T value, unsigned int alignment) +{ + return (value & static_cast(alignment - 1)) == 0; +} +template +constexpr T AlignUpPow2(T value, unsigned int alignment) +{ + return (value + static_cast(alignment - 1)) & static_cast(~static_cast(alignment - 1)); +} +template +constexpr T AlignDownPow2(T value, unsigned int alignment) +{ + return value & static_cast(~static_cast(alignment - 1)); +} +template +constexpr bool IsPow2(T value) +{ + return (value & (value - 1)) == 0; +} +} // namespace Common diff --git a/jni/common/assert.cpp b/jni/common/assert.cpp new file mode 100644 index 0000000..fe97974 --- /dev/null +++ b/jni/common/assert.cpp @@ -0,0 +1,140 @@ +#include "assert.h" +#include +#include +#include + +#ifdef WIN32 +#include "windows_headers.h" +#include +#include +#endif + +static std::mutex s_AssertFailedMutex; + +static inline void FreezeThreads(void** ppHandle) +{ +#ifdef WIN32 + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (hSnapshot != INVALID_HANDLE_VALUE) + { + THREADENTRY32 threadEntry; + if (Thread32First(hSnapshot, &threadEntry)) + { + do + { + if (threadEntry.th32ThreadID == GetCurrentThreadId()) + continue; + + HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, threadEntry.th32ThreadID); + if (hThread != NULL) + { + SuspendThread(hThread); + CloseHandle(hThread); + } + } while (Thread32Next(hSnapshot, &threadEntry)); + } + } + + *ppHandle = (void*)hSnapshot; +#else + *ppHandle = nullptr; +#endif +} + +static inline void ResumeThreads(void* pHandle) +{ +#ifdef WIN32 + HANDLE hSnapshot = (HANDLE)pHandle; + if (pHandle != INVALID_HANDLE_VALUE) + { + THREADENTRY32 threadEntry; + if (Thread32First(hSnapshot, &threadEntry)) + { + do + { + if (threadEntry.th32ThreadID == GetCurrentThreadId()) + continue; + + HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, threadEntry.th32ThreadID); + if (hThread != NULL) + { + ResumeThread(hThread); + CloseHandle(hThread); + } + } while (Thread32Next(hSnapshot, &threadEntry)); + } + CloseHandle(hSnapshot); + } +#else +#endif +} + +void Y_OnAssertFailed(const char* szMessage, const char* szFunction, const char* szFile, unsigned uLine) +{ + std::lock_guard guard(s_AssertFailedMutex); + + void* pHandle; + FreezeThreads(&pHandle); + + char szMsg[512]; + std::snprintf(szMsg, sizeof(szMsg), "%s in function %s (%s:%u)", szMessage, szFunction, szFile, uLine); + +#ifdef WIN32 + SetConsoleTextAttribute(GetStdHandle(STD_ERROR_HANDLE), FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY); + WriteConsoleA(GetStdHandle(STD_ERROR_HANDLE), szMsg, static_cast(std::strlen(szMsg)), NULL, NULL); + OutputDebugStringA(szMsg); + + std::snprintf( + szMsg, sizeof(szMsg), + "%s in function %s (%s:%u)\n\nPress Abort to exit, Retry to break to debugger, or Ignore to attempt to continue.", + szMessage, szFunction, szFile, uLine); + + int result = MessageBoxA(NULL, szMsg, NULL, MB_ABORTRETRYIGNORE | MB_ICONERROR); + if (result == IDRETRY) + __debugbreak(); + else if (result != IDIGNORE) + TerminateProcess(GetCurrentProcess(), 0xBAADC0DE); +#else + fputs(szMsg, stderr); + fputs("\nAborting application.\n", stderr); + fflush(stderr); + abort(); +#endif + + ResumeThreads(pHandle); +} + +void Y_OnPanicReached(const char* szMessage, const char* szFunction, const char* szFile, unsigned uLine) +{ + std::lock_guard guard(s_AssertFailedMutex); + + void* pHandle; + FreezeThreads(&pHandle); + + char szMsg[512]; + std::snprintf(szMsg, sizeof(szMsg), "%s in function %s (%s:%u)", szMessage, szFunction, szFile, uLine); + +#ifdef WIN32 + SetConsoleTextAttribute(GetStdHandle(STD_ERROR_HANDLE), FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY); + WriteConsoleA(GetStdHandle(STD_ERROR_HANDLE), szMsg, static_cast(std::strlen(szMsg)), NULL, NULL); + OutputDebugStringA(szMsg); + + std::snprintf(szMsg, sizeof(szMsg), + "%s in function %s (%s:%u)\n\nDo you want to attempt to break into a debugger? Pressing Cancel will " + "abort the application.", + szMessage, szFunction, szFile, uLine); + + int result = MessageBoxA(NULL, szMsg, NULL, MB_OKCANCEL | MB_ICONERROR); + if (result == IDOK) + __debugbreak(); + + TerminateProcess(GetCurrentProcess(), 0xBAADC0DE); +#else + fputs(szMsg, stderr); + fputs("\nAborting application.\n", stderr); + fflush(stderr); + abort(); +#endif + + ResumeThreads(pHandle); +} diff --git a/jni/common/assert.h b/jni/common/assert.h new file mode 100644 index 0000000..3608050 --- /dev/null +++ b/jni/common/assert.h @@ -0,0 +1,48 @@ +#pragma once + +void Y_OnAssertFailed(const char* szMessage, const char* szFunction, const char* szFile, unsigned uLine); +void Y_OnPanicReached(const char* szMessage, const char* szFunction, const char* szFile, unsigned uLine); + +#define Assert(expr) \ + if (!(expr)) \ + { \ + Y_OnAssertFailed("Assertion failed: '" #expr "'", __FUNCTION__, __FILE__, __LINE__); \ + } +#define AssertMsg(expr, msg) \ + if (!(expr)) \ + { \ + Y_OnAssertFailed("Assertion failed: '" msg "'", __FUNCTION__, __FILE__, __LINE__); \ + } + +#ifdef _DEBUG +#define DebugAssert(expr) \ + if (!(expr)) \ + { \ + Y_OnAssertFailed("Debug assertion failed: '" #expr "'", __FUNCTION__, __FILE__, __LINE__); \ + } +#define DebugAssertMsg(expr, msg) \ + if (!(expr)) \ + { \ + Y_OnAssertFailed("Debug assertion failed: '" msg "'", __FUNCTION__, __FILE__, __LINE__); \ + } +#define DebugUnreachableCode() Y_OnPanicReached("Unreachable code reached", __FUNCTION__, __FILE__, __LINE__) +#else +#define DebugAssert(expr) +#define DebugAssertMsg(expr, msg) +#define DebugUnreachableCode() +#endif + +// Panics the application, displaying an error message. +#define Panic(Message) Y_OnPanicReached("Panic triggered: '" Message "'", __FUNCTION__, __FILE__, __LINE__) + +// Kills the application, indicating a pure function call that should not have happened. +#define PureCall() Y_OnPanicReached("PureCall encountered", __FUNCTION__, __FILE__, __LINE__) + +// Kills the application, indicating that code that was never supposed to be reached has been executed. +#define UnreachableCode() Y_OnPanicReached("Unreachable code reached", __FUNCTION__, __FILE__, __LINE__) + +// Helper for switch cases. +#define DefaultCaseIsUnreachable() \ + default: \ + UnreachableCode(); \ + break; diff --git a/jni/common/audio_stream.cpp b/jni/common/audio_stream.cpp new file mode 100644 index 0000000..0b35f58 --- /dev/null +++ b/jni/common/audio_stream.cpp @@ -0,0 +1,224 @@ +#include "audio_stream.h" +#include "assert.h" +#include "log.h" +#include +#include +Log_SetChannel(AudioStream); + +AudioStream::AudioStream() = default; + +AudioStream::~AudioStream() = default; + +bool AudioStream::Reconfigure(u32 output_sample_rate /*= DefaultOutputSampleRate*/, u32 channels /*= 1*/, + u32 buffer_size /*= DefaultBufferSize*/) +{ + if (IsDeviceOpen()) + CloseDevice(); + + m_output_sample_rate = output_sample_rate; + m_channels = channels; + m_buffer_size = buffer_size; + m_output_paused = true; + + if (!SetBufferSize(buffer_size)) + return false; + + if (!OpenDevice()) + { + EmptyBuffers(); + m_buffer_size = 0; + m_output_sample_rate = 0; + m_channels = 0; + return false; + } + + return true; +} + +void AudioStream::SetOutputVolume(u32 volume) +{ + std::unique_lock lock(m_buffer_mutex); + m_output_volume = volume; +} + +void AudioStream::PauseOutput(bool paused) +{ + if (m_output_paused == paused) + return; + + PauseDevice(paused); + m_output_paused = paused; + + // Empty buffers on pause. + if (paused) + EmptyBuffers(); +} + +void AudioStream::Shutdown() +{ + if (!IsDeviceOpen()) + return; + + CloseDevice(); + EmptyBuffers(); + m_buffer_size = 0; + m_output_sample_rate = 0; + m_channels = 0; + m_output_paused = true; +} + +void AudioStream::BeginWrite(SampleType** buffer_ptr, u32* num_frames) +{ + m_buffer_mutex.lock(); + + EnsureBuffer(*num_frames * m_channels); + + *buffer_ptr = m_buffer.GetWritePointer(); + *num_frames = m_buffer.GetContiguousSpace() / m_channels; +} + +void AudioStream::WriteFrames(const SampleType* frames, u32 num_frames) +{ + const u32 num_samples = num_frames * m_channels; + { + std::unique_lock lock(m_buffer_mutex); + EnsureBuffer(num_samples); + m_buffer.PushRange(frames, num_samples); + } + + FramesAvailable(); +} + +void AudioStream::EndWrite(u32 num_frames) +{ + m_buffer.AdvanceTail(num_frames * m_channels); + m_buffer_mutex.unlock(); + FramesAvailable(); +} + +float AudioStream::GetMaxLatency(u32 sample_rate, u32 buffer_size) +{ + return (static_cast(buffer_size) / static_cast(sample_rate)); +} + +bool AudioStream::SetBufferSize(u32 buffer_size) +{ + const u32 buffer_size_in_samples = buffer_size * m_channels; + const u32 max_samples = buffer_size_in_samples * 2u; + if (max_samples > m_buffer.GetCapacity()) + return false; + + m_buffer_size = buffer_size; + m_max_samples = max_samples; + return true; +} + +u32 AudioStream::GetSamplesAvailable() const +{ + // TODO: Use atomic loads + u32 available_samples; + { + std::unique_lock lock(m_buffer_mutex); + available_samples = m_buffer.GetSize(); + } + + return available_samples / m_channels; +} + +u32 AudioStream::GetSamplesAvailableLocked() const +{ + return m_buffer.GetSize() / m_channels; +} + +void AudioStream::ReadFrames(SampleType* samples, u32 num_frames, bool apply_volume) +{ + const u32 total_samples = num_frames * m_channels; + u32 samples_copied = 0; + { + std::unique_lock lock(m_buffer_mutex); + samples_copied = std::min(m_buffer.GetSize(), total_samples); + if (samples_copied > 0) + m_buffer.PopRange(samples, samples_copied); + + m_buffer_draining_cv.notify_one(); + } + + if (samples_copied < total_samples) + { + if (samples_copied > 0) + { + m_resample_buffer.resize(samples_copied); + std::memcpy(m_resample_buffer.data(), samples, sizeof(SampleType) * samples_copied); + + // super basic resampler - spread the input samples evenly across the output samples. will sound like ass and have + // aliasing, but better than popping by inserting silence. + const u32 increment = + static_cast(65536.0f * (static_cast(samples_copied / m_channels) / static_cast(num_frames))); + + SampleType* out_ptr = samples; + const SampleType* resample_ptr = m_resample_buffer.data(); + const u32 copy_stride = sizeof(SampleType) * m_channels; + u32 resample_subpos = 0; + for (u32 i = 0; i < num_frames; i++) + { + std::memcpy(out_ptr, resample_ptr, copy_stride); + out_ptr += m_channels; + + resample_subpos += increment; + resample_ptr += (resample_subpos >> 16) * m_channels; + resample_subpos %= 65536u; + } + + Log_DevPrintf("Audio buffer underflow, resampled %u frames to %u", samples_copied / m_channels, num_frames); + m_underflow_flag.store(true); + } + else + { + // read nothing, so zero-fill + std::memset(samples, 0, sizeof(SampleType) * total_samples); + Log_DevPrintf("Audio buffer underflow with no samples, added %u frames silence", num_frames); + m_underflow_flag.store(true); + } + } + + if (apply_volume && m_output_volume != FullVolume) + { + SampleType* current_ptr = samples; + const SampleType* end_ptr = samples + (num_frames * m_channels); + while (current_ptr != end_ptr) + { + *current_ptr = ApplyVolume(*current_ptr, m_output_volume); + current_ptr++; + } + } +} + +void AudioStream::EnsureBuffer(u32 size) +{ + if (GetBufferSpace() >= size) + return; + + if (m_sync) + { + std::unique_lock lock(m_buffer_mutex, std::adopt_lock); + m_buffer_draining_cv.wait(lock, [this, size]() { return GetBufferSpace() >= size; }); + lock.release(); + } + else + { + m_buffer.Remove(size); + } +} + +void AudioStream::DropFrames(u32 count) +{ + std::unique_lock lock(m_buffer_mutex); + m_buffer.Remove(count); +} + +void AudioStream::EmptyBuffers() +{ + std::unique_lock lock(m_buffer_mutex); + m_buffer.Clear(); + m_underflow_flag.store(false); +} diff --git a/jni/common/audio_stream.h b/jni/common/audio_stream.h new file mode 100644 index 0000000..e788779 --- /dev/null +++ b/jni/common/audio_stream.h @@ -0,0 +1,99 @@ +#pragma once +#include "fifo_queue.h" +#include "types.h" +#include +#include +#include +#include +#include + +// Uses signed 16-bits samples. + +class AudioStream +{ +public: + using SampleType = s16; + + enum : u32 + { + DefaultOutputSampleRate = 44100, + DefaultBufferSize = 2048, + MaxSamples = 32768, + FullVolume = 100 + }; + + AudioStream(); + virtual ~AudioStream(); + + u32 GetOutputSampleRate() const { return m_output_sample_rate; } + u32 GetChannels() const { return m_channels; } + u32 GetBufferSize() const { return m_buffer_size; } + s32 GetOutputVolume() const { return m_output_volume; } + bool IsSyncing() const { return m_sync; } + + bool Reconfigure(u32 output_sample_rate = DefaultOutputSampleRate, u32 channels = 1, + u32 buffer_size = DefaultBufferSize); + void SetSync(bool enable) { m_sync = enable; } + + virtual void SetOutputVolume(u32 volume); + + void PauseOutput(bool paused); + void EmptyBuffers(); + + void Shutdown(); + + void BeginWrite(SampleType** buffer_ptr, u32* num_frames); + void WriteFrames(const SampleType* frames, u32 num_frames); + void EndWrite(u32 num_frames); + + bool DidUnderflow() + { + bool expected = true; + return m_underflow_flag.compare_exchange_strong(expected, false); + } + + static std::unique_ptr CreateNullAudioStream(); + + // Latency computation - returns values in seconds + static float GetMaxLatency(u32 sample_rate, u32 buffer_size); + +protected: + virtual bool OpenDevice() = 0; + virtual void PauseDevice(bool paused) = 0; + virtual void CloseDevice() = 0; + virtual void FramesAvailable() = 0; + + ALWAYS_INLINE static SampleType ApplyVolume(SampleType sample, u32 volume) + { + return s16((s32(sample) * s32(volume)) / 100); + } + + bool SetBufferSize(u32 buffer_size); + bool IsDeviceOpen() const { return (m_output_sample_rate > 0); } + + u32 GetSamplesAvailable() const; + u32 GetSamplesAvailableLocked() const; + void ReadFrames(SampleType* samples, u32 num_frames, bool apply_volume); + void DropFrames(u32 count); + + u32 m_output_sample_rate = 0; + u32 m_channels = 0; + u32 m_buffer_size = 0; + + // volume, 0-100 + u32 m_output_volume = FullVolume; + +private: + ALWAYS_INLINE u32 GetBufferSpace() const { return (m_max_samples - m_buffer.GetSize()); } + void EnsureBuffer(u32 size); + + HeapFIFOQueue m_buffer; + mutable std::mutex m_buffer_mutex; + std::condition_variable m_buffer_draining_cv; + std::vector m_resample_buffer; + std::atomic_bool m_underflow_flag{false}; + u32 m_max_samples = 0; + + bool m_output_paused = true; + bool m_sync = true; +}; \ No newline at end of file diff --git a/jni/common/bitfield.h b/jni/common/bitfield.h new file mode 100644 index 0000000..fe1449f --- /dev/null +++ b/jni/common/bitfield.h @@ -0,0 +1,141 @@ +#pragma once +#include "types.h" +#include + +// Disable MSVC warnings that we actually handle +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) // warning C4800: 'int': forcing value to bool 'true' or 'false' (performance warning) +#endif + +template +struct BitField +{ + static_assert(!std::is_same_v || BitCount == 1, "Boolean bitfields should only be 1 bit"); + + // We have to delete the copy assignment operator otherwise we can't use this class in anonymous structs/unions. + BitField& operator=(const BitField& rhs) = delete; + + ALWAYS_INLINE constexpr BackingDataType GetMask() const + { + return ((static_cast(~0)) >> (8 * sizeof(BackingDataType) - BitCount)) << BitIndex; + } + + ALWAYS_INLINE constexpr operator DataType() const { return GetValue(); } + + ALWAYS_INLINE constexpr BitField& operator=(DataType value) + { + SetValue(value); + return *this; + } + + ALWAYS_INLINE constexpr DataType operator++() + { + DataType value = GetValue() + 1; + SetValue(value); + return GetValue(); + } + + ALWAYS_INLINE constexpr DataType operator++(int) + { + DataType value = GetValue(); + SetValue(value + 1); + return value; + } + + ALWAYS_INLINE constexpr DataType operator--() + { + DataType value = GetValue() - 1; + SetValue(value); + return GetValue(); + } + + ALWAYS_INLINE constexpr DataType operator--(int) + { + DataType value = GetValue(); + SetValue(value - 1); + return value; + } + + ALWAYS_INLINE constexpr BitField& operator+=(DataType rhs) + { + SetValue(GetValue() + rhs); + return *this; + } + + ALWAYS_INLINE constexpr BitField& operator-=(DataType rhs) + { + SetValue(GetValue() - rhs); + return *this; + } + + ALWAYS_INLINE constexpr BitField& operator*=(DataType rhs) + { + SetValue(GetValue() * rhs); + return *this; + } + + ALWAYS_INLINE constexpr BitField& operator/=(DataType rhs) + { + SetValue(GetValue() / rhs); + return *this; + } + + ALWAYS_INLINE constexpr BitField& operator&=(DataType rhs) + { + SetValue(GetValue() & rhs); + return *this; + } + + ALWAYS_INLINE constexpr BitField& operator|=(DataType rhs) + { + SetValue(GetValue() | rhs); + return *this; + } + + ALWAYS_INLINE constexpr BitField& operator^=(DataType rhs) + { + SetValue(GetValue() ^ rhs); + return *this; + } + + ALWAYS_INLINE constexpr BitField& operator<<=(DataType rhs) + { + SetValue(GetValue() << rhs); + return *this; + } + + ALWAYS_INLINE constexpr BitField& operator>>=(DataType rhs) + { + SetValue(GetValue() >> rhs); + return *this; + } + + ALWAYS_INLINE constexpr DataType GetValue() const + { + if constexpr (std::is_same_v) + { + return static_cast(!!((data & GetMask()) >> BitIndex)); + } + else if constexpr (std::is_signed_v) + { + constexpr int shift = 8 * sizeof(DataType) - BitCount; + return (static_cast(data >> BitIndex) << shift) >> shift; + } + else + { + return static_cast((data & GetMask()) >> BitIndex); + } + } + + ALWAYS_INLINE constexpr void SetValue(DataType value) + { + data = (data & ~GetMask()) | ((static_cast(value) << BitIndex) & GetMask()); + } + + BackingDataType data; +}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/jni/common/bitfield.natvis b/jni/common/bitfield.natvis new file mode 100644 index 0000000..51b9d77 --- /dev/null +++ b/jni/common/bitfield.natvis @@ -0,0 +1,19 @@ + + + + > $T2) & 1) != 0}]]> + + $T2 + + + + > $T3) & ((1 << $T4) - 1))}]]> + + $T3 + $T4 + + > $T3) & (((1 << $T4) - 1) << $T3)]]> + data + + + \ No newline at end of file diff --git a/jni/common/bitutils.h b/jni/common/bitutils.h new file mode 100644 index 0000000..12c1d21 --- /dev/null +++ b/jni/common/bitutils.h @@ -0,0 +1,58 @@ +#pragma once +#include "types.h" + +#ifdef _MSC_VER +#include +#endif + +/// Returns the number of zero bits before the first set bit, going MSB->LSB. +template +ALWAYS_INLINE unsigned CountLeadingZeros(T value) +{ +#ifdef _MSC_VER + if constexpr (sizeof(value) >= sizeof(u64)) + { + unsigned long index; + _BitScanReverse64(&index, ZeroExtend64(value)); + return static_cast(index) ^ static_cast((sizeof(value) * 8u) - 1u); + } + else + { + unsigned long index; + _BitScanReverse(&index, ZeroExtend32(value)); + return static_cast(index) ^ static_cast((sizeof(value) * 8u) - 1u); + } +#else + if constexpr (sizeof(value) >= sizeof(u64)) + return static_cast(__builtin_clzl(ZeroExtend64(value))); + else if constexpr (sizeof(value) == sizeof(u32)) + return static_cast(__builtin_clz(ZeroExtend32(value))); + else + return static_cast(__builtin_clz(ZeroExtend32(value))) & static_cast((sizeof(value) * 8u) - 1u); +#endif +} + +/// Returns the number of zero bits before the first set bit, going LSB->MSB. +template +ALWAYS_INLINE unsigned CountTrailingZeros(T value) +{ +#ifdef _MSC_VER + if constexpr (sizeof(value) >= sizeof(u64)) + { + unsigned long index; + _BitScanForward64(&index, ZeroExtend64(value)); + return index; + } + else + { + unsigned long index; + _BitScanForward(&index, ZeroExtend32(value)); + return index; + } +#else + if constexpr (sizeof(value) >= sizeof(u64)) + return static_cast(__builtin_ctzl(ZeroExtend64(value))); + else + return static_cast(__builtin_ctz(ZeroExtend32(value))); +#endif +} diff --git a/jni/common/byte_stream.cpp b/jni/common/byte_stream.cpp new file mode 100644 index 0000000..20d00ba --- /dev/null +++ b/jni/common/byte_stream.cpp @@ -0,0 +1,1363 @@ +#include "byte_stream.h" +#include "assert.h" +#include "file_system.h" +#include "log.h" +#include "string_util.h" +#include +#include +#include +#include +#include +#include +#if defined(WIN32) +#include "windows_headers.h" +#include +#include +#include +#else +#include +#include +#endif + +Log_SetChannel(ByteStream); + +class FileByteStream : public ByteStream +{ +public: + FileByteStream(FILE* pFile) : m_pFile(pFile) { DebugAssert(m_pFile != nullptr); } + + virtual ~FileByteStream() { fclose(m_pFile); } + + virtual bool ReadByte(u8* pDestByte) override + { + if (m_errorState) + return false; + + if (fread(pDestByte, 1, 1, m_pFile) != 1) + { + m_errorState = true; + return false; + } + + return true; + } + + virtual u32 Read(void* pDestination, u32 ByteCount) override + { + if (m_errorState) + return 0; + + u32 readCount = (u32)fread(pDestination, 1, ByteCount, m_pFile); + if (readCount != ByteCount && ferror(m_pFile) != 0) + m_errorState = true; + + return readCount; + } + + virtual bool Read2(void* pDestination, u32 ByteCount, u32* pNumberOfBytesRead /* = nullptr */) override + { + if (m_errorState) + return false; + + u32 bytesRead = Read(pDestination, ByteCount); + + if (pNumberOfBytesRead != nullptr) + *pNumberOfBytesRead = bytesRead; + + if (bytesRead != ByteCount) + { + m_errorState = true; + return false; + } + + return true; + } + + virtual bool WriteByte(u8 SourceByte) override + { + if (m_errorState) + return false; + + if (fwrite(&SourceByte, 1, 1, m_pFile) != 1) + { + m_errorState = true; + return false; + } + + return true; + } + + virtual u32 Write(const void* pSource, u32 ByteCount) override + { + if (m_errorState) + return 0; + + u32 writeCount = (u32)fwrite(pSource, 1, ByteCount, m_pFile); + if (writeCount != ByteCount) + m_errorState = true; + + return writeCount; + } + + virtual bool Write2(const void* pSource, u32 ByteCount, u32* pNumberOfBytesWritten /* = nullptr */) override + { + if (m_errorState) + return false; + + u32 bytesWritten = Write(pSource, ByteCount); + + if (pNumberOfBytesWritten != nullptr) + *pNumberOfBytesWritten = bytesWritten; + + if (bytesWritten != ByteCount) + { + m_errorState = true; + return false; + } + + return true; + } + +#if defined(WIN32) + + virtual bool SeekAbsolute(u64 Offset) override + { + if (m_errorState) + return false; + + if (_fseeki64(m_pFile, Offset, SEEK_SET) != 0) + { + m_errorState = true; + return false; + } + + return true; + } + + virtual bool SeekRelative(s64 Offset) override + { + if (m_errorState) + return false; + + if (_fseeki64(m_pFile, Offset, SEEK_CUR) != 0) + { + m_errorState = true; + return true; + } + + return true; + } + + virtual bool SeekToEnd() override + { + if (m_errorState) + return false; + + if (_fseeki64(m_pFile, 0, SEEK_END) != 0) + { + m_errorState = true; + return false; + } + + return true; + } + + virtual u64 GetPosition() const override { return _ftelli64(m_pFile); } + + virtual u64 GetSize() const override + { + s64 OldPos = _ftelli64(m_pFile); + _fseeki64(m_pFile, 0, SEEK_END); + s64 Size = _ftelli64(m_pFile); + _fseeki64(m_pFile, OldPos, SEEK_SET); + return (u64)Size; + } + +#else + + virtual bool SeekAbsolute(u64 Offset) override + { + if (m_errorState) + return false; + + if (fseeko(m_pFile, static_cast(Offset), SEEK_SET) != 0) + { + m_errorState = true; + return false; + } + + return true; + } + + virtual bool SeekRelative(s64 Offset) override + { + if (m_errorState) + return false; + + if (fseeko(m_pFile, static_cast(Offset), SEEK_CUR) != 0) + { + m_errorState = true; + return false; + } + + return true; + } + + virtual bool SeekToEnd() override + { + if (m_errorState) + return false; + + if (fseeko(m_pFile, 0, SEEK_END) != 0) + { + m_errorState = true; + return false; + } + + return true; + } + + virtual u64 GetPosition() const override { return static_cast(ftello(m_pFile)); } + + virtual u64 GetSize() const override + { + off_t OldPos = ftello(m_pFile); + fseeko(m_pFile, 0, SEEK_END); + off_t Size = ftello(m_pFile); + fseeko(m_pFile, OldPos, SEEK_SET); + return (u64)Size; + } + +#endif + + virtual bool Flush() override + { + if (m_errorState) + return false; + + if (fflush(m_pFile) != 0) + { + m_errorState = true; + return false; + } + + return true; + } + + virtual bool Commit() override { return true; } + + virtual bool Discard() override { return false; } + +protected: + FILE* m_pFile; +}; + +class AtomicUpdatedFileByteStream : public FileByteStream +{ +public: + AtomicUpdatedFileByteStream(FILE* pFile, const char* originalFileName, const char* temporaryFileName) + : FileByteStream(pFile), m_committed(false), m_discarded(false), m_originalFileName(originalFileName), + m_temporaryFileName(temporaryFileName) + { + } + + virtual ~AtomicUpdatedFileByteStream() + { + if (m_discarded) + { +#if WIN32 + // delete the temporary file + if (!DeleteFileW(StringUtil::UTF8StringToWideString(m_temporaryFileName).c_str())) + { + Log_WarningPrintf( + "AtomicUpdatedFileByteStream::~AtomicUpdatedFileByteStream(): Failed to delete temporary file '%s'", + m_temporaryFileName.c_str()); + } +#else + // delete the temporary file + if (remove(m_temporaryFileName.c_str()) < 0) + Log_WarningPrintf( + "AtomicUpdatedFileByteStream::~AtomicUpdatedFileByteStream(): Failed to delete temporary file '%s'", + m_temporaryFileName.c_str()); +#endif + } + else if (!m_committed) + { + Commit(); + } + + // fclose called by FileByteStream destructor + } + + virtual bool Flush() override + { + if (fflush(m_pFile) != 0) + { + m_errorState = true; + return false; + } + + return true; + } + + virtual bool Commit() override + { + Assert(!m_discarded); + if (m_committed) + return Flush(); + + fflush(m_pFile); + +#ifdef WIN32 + // move the atomic file name to the original file name + if (!MoveFileExW(StringUtil::UTF8StringToWideString(m_temporaryFileName).c_str(), + StringUtil::UTF8StringToWideString(m_originalFileName).c_str(), MOVEFILE_REPLACE_EXISTING)) + { + Log_WarningPrintf("AtomicUpdatedFileByteStream::Commit(): Failed to rename temporary file '%s' to '%s'", + m_temporaryFileName.c_str(), m_originalFileName.c_str()); + m_discarded = true; + } + else + { + m_committed = true; + } +#else + // move the atomic file name to the original file name + if (rename(m_temporaryFileName.c_str(), m_originalFileName.c_str()) < 0) + { + Log_WarningPrintf("AtomicUpdatedFileByteStream::Commit(): Failed to rename temporary file '%s' to '%s'", + m_temporaryFileName.c_str(), m_originalFileName.c_str()); + m_discarded = true; + } + else + { + m_committed = true; + } +#endif + + return (!m_discarded); + } + + virtual bool Discard() override + { + Assert(!m_committed); + m_discarded = true; + return true; + } + +private: + bool m_committed; + bool m_discarded; + std::string m_originalFileName; + std::string m_temporaryFileName; +}; + +NullByteStream::NullByteStream() {} + +NullByteStream::~NullByteStream() {} + +bool NullByteStream::ReadByte(u8* pDestByte) +{ + *pDestByte = 0; + return true; +} + +u32 NullByteStream::Read(void* pDestination, u32 ByteCount) +{ + if (ByteCount > 0) + std::memset(pDestination, 0, ByteCount); + + return ByteCount; +} + +bool NullByteStream::Read2(void* pDestination, u32 ByteCount, u32* pNumberOfBytesRead /* = nullptr */) +{ + if (ByteCount > 0) + std::memset(pDestination, 0, ByteCount); + + if (pNumberOfBytesRead) + *pNumberOfBytesRead = ByteCount; + + return true; +} + +bool NullByteStream::WriteByte(u8 SourceByte) +{ + return true; +} + +u32 NullByteStream::Write(const void* pSource, u32 ByteCount) +{ + return ByteCount; +} + +bool NullByteStream::Write2(const void* pSource, u32 ByteCount, u32* pNumberOfBytesWritten /* = nullptr */) +{ + return true; +} + +bool NullByteStream::SeekAbsolute(u64 Offset) +{ + return true; +} + +bool NullByteStream::SeekRelative(s64 Offset) +{ + return true; +} + +bool NullByteStream::SeekToEnd() +{ + return true; +} + +u64 NullByteStream::GetSize() const +{ + return 0; +} + +u64 NullByteStream::GetPosition() const +{ + return 0; +} + +bool NullByteStream::Flush() +{ + return true; +} + +bool NullByteStream::Commit() +{ + return true; +} + +bool NullByteStream::Discard() +{ + return true; +} + +MemoryByteStream::MemoryByteStream(void* pMemory, u32 MemSize) +{ + m_iPosition = 0; + m_iSize = MemSize; + m_pMemory = (u8*)pMemory; +} + +MemoryByteStream::~MemoryByteStream() {} + +bool MemoryByteStream::ReadByte(u8* pDestByte) +{ + if (m_iPosition < m_iSize) + { + *pDestByte = m_pMemory[m_iPosition++]; + return true; + } + + return false; +} + +u32 MemoryByteStream::Read(void* pDestination, u32 ByteCount) +{ + u32 sz = ByteCount; + if ((m_iPosition + ByteCount) > m_iSize) + sz = m_iSize - m_iPosition; + + if (sz > 0) + { + std::memcpy(pDestination, m_pMemory + m_iPosition, sz); + m_iPosition += sz; + } + + return sz; +} + +bool MemoryByteStream::Read2(void* pDestination, u32 ByteCount, u32* pNumberOfBytesRead /* = nullptr */) +{ + u32 r = Read(pDestination, ByteCount); + if (pNumberOfBytesRead != NULL) + *pNumberOfBytesRead = r; + + return (r == ByteCount); +} + +bool MemoryByteStream::WriteByte(u8 SourceByte) +{ + if (m_iPosition < m_iSize) + { + m_pMemory[m_iPosition++] = SourceByte; + return true; + } + + return false; +} + +u32 MemoryByteStream::Write(const void* pSource, u32 ByteCount) +{ + u32 sz = ByteCount; + if ((m_iPosition + ByteCount) > m_iSize) + sz = m_iSize - m_iPosition; + + if (sz > 0) + { + std::memcpy(m_pMemory + m_iPosition, pSource, sz); + m_iPosition += sz; + } + + return sz; +} + +bool MemoryByteStream::Write2(const void* pSource, u32 ByteCount, u32* pNumberOfBytesWritten /* = nullptr */) +{ + u32 r = Write(pSource, ByteCount); + if (pNumberOfBytesWritten != nullptr) + *pNumberOfBytesWritten = r; + + return (r == ByteCount); +} + +bool MemoryByteStream::SeekAbsolute(u64 Offset) +{ + u32 Offset32 = (u32)Offset; + if (Offset32 > m_iSize) + return false; + + m_iPosition = Offset32; + return true; +} + +bool MemoryByteStream::SeekRelative(s64 Offset) +{ + s32 Offset32 = (s32)Offset; + if ((Offset32 < 0 && -Offset32 > (s32)m_iPosition) || (u32)((s32)m_iPosition + Offset32) > m_iSize) + return false; + + m_iPosition += Offset32; + return true; +} + +bool MemoryByteStream::SeekToEnd() +{ + m_iPosition = m_iSize; + return true; +} + +u64 MemoryByteStream::GetSize() const +{ + return (u64)m_iSize; +} + +u64 MemoryByteStream::GetPosition() const +{ + return (u64)m_iPosition; +} + +bool MemoryByteStream::Flush() +{ + return true; +} + +bool MemoryByteStream::Commit() +{ + return true; +} + +bool MemoryByteStream::Discard() +{ + return false; +} + +ReadOnlyMemoryByteStream::ReadOnlyMemoryByteStream(const void* pMemory, u32 MemSize) +{ + m_iPosition = 0; + m_iSize = MemSize; + m_pMemory = reinterpret_cast(pMemory); +} + +ReadOnlyMemoryByteStream::~ReadOnlyMemoryByteStream() {} + +bool ReadOnlyMemoryByteStream::ReadByte(u8* pDestByte) +{ + if (m_iPosition < m_iSize) + { + *pDestByte = m_pMemory[m_iPosition++]; + return true; + } + + return false; +} + +u32 ReadOnlyMemoryByteStream::Read(void* pDestination, u32 ByteCount) +{ + u32 sz = ByteCount; + if ((m_iPosition + ByteCount) > m_iSize) + sz = m_iSize - m_iPosition; + + if (sz > 0) + { + std::memcpy(pDestination, m_pMemory + m_iPosition, sz); + m_iPosition += sz; + } + + return sz; +} + +bool ReadOnlyMemoryByteStream::Read2(void* pDestination, u32 ByteCount, u32* pNumberOfBytesRead /* = nullptr */) +{ + u32 r = Read(pDestination, ByteCount); + if (pNumberOfBytesRead != nullptr) + *pNumberOfBytesRead = r; + + return (r == ByteCount); +} + +bool ReadOnlyMemoryByteStream::WriteByte(u8 SourceByte) +{ + return false; +} + +u32 ReadOnlyMemoryByteStream::Write(const void* pSource, u32 ByteCount) +{ + return 0; +} + +bool ReadOnlyMemoryByteStream::Write2(const void* pSource, u32 ByteCount, u32* pNumberOfBytesWritten /* = nullptr */) +{ + return false; +} + +bool ReadOnlyMemoryByteStream::SeekAbsolute(u64 Offset) +{ + u32 Offset32 = (u32)Offset; + if (Offset32 > m_iSize) + return false; + + m_iPosition = Offset32; + return true; +} + +bool ReadOnlyMemoryByteStream::SeekRelative(s64 Offset) +{ + s32 Offset32 = (s32)Offset; + if ((Offset32 < 0 && -Offset32 > (s32)m_iPosition) || (u32)((s32)m_iPosition + Offset32) > m_iSize) + return false; + + m_iPosition += Offset32; + return true; +} + +bool ReadOnlyMemoryByteStream::SeekToEnd() +{ + m_iPosition = m_iSize; + return true; +} + +u64 ReadOnlyMemoryByteStream::GetSize() const +{ + return (u64)m_iSize; +} + +u64 ReadOnlyMemoryByteStream::GetPosition() const +{ + return (u64)m_iPosition; +} + +bool ReadOnlyMemoryByteStream::Flush() +{ + return false; +} + +bool ReadOnlyMemoryByteStream::Commit() +{ + return false; +} + +bool ReadOnlyMemoryByteStream::Discard() +{ + return false; +} + +GrowableMemoryByteStream::GrowableMemoryByteStream(void* pInitialMem, u32 InitialMemSize) +{ + m_iPosition = 0; + m_iSize = 0; + + if (pInitialMem != nullptr) + { + m_iMemorySize = InitialMemSize; + m_pPrivateMemory = nullptr; + m_pMemory = (u8*)pInitialMem; + } + else + { + m_iMemorySize = std::max(InitialMemSize, (u32)64); + m_pPrivateMemory = m_pMemory = (u8*)std::malloc(m_iMemorySize); + } +} + +GrowableMemoryByteStream::~GrowableMemoryByteStream() +{ + if (m_pPrivateMemory != nullptr) + std::free(m_pPrivateMemory); +} + +void GrowableMemoryByteStream::Resize(u32 new_size) +{ + if (new_size > m_iMemorySize) + ResizeMemory(new_size); + + m_iSize = new_size; +} + +void GrowableMemoryByteStream::ResizeMemory(u32 new_size) +{ + if (new_size == m_iMemorySize) + return; + + if (m_pPrivateMemory == nullptr) + { + m_pPrivateMemory = (u8*)std::malloc(new_size); + std::memcpy(m_pPrivateMemory, m_pMemory, m_iSize); + m_pMemory = m_pPrivateMemory; + m_iMemorySize = new_size; + } + else + { + m_pPrivateMemory = m_pMemory = (u8*)std::realloc(m_pPrivateMemory, new_size); + m_iMemorySize = new_size; + } +} + +void GrowableMemoryByteStream::EnsureSpace(u32 space) +{ + if ((m_iSize + space) >= m_iMemorySize) + return; + + Grow((m_iSize + space) - m_iMemorySize); +} + +void GrowableMemoryByteStream::ShrinkToFit() +{ + if (!m_pPrivateMemory || m_iSize == m_iMemorySize) + return; + + u8* new_ptr = static_cast(std::realloc(m_pPrivateMemory, m_iSize)); + if (new_ptr) + { + m_pPrivateMemory = new_ptr; + m_iMemorySize = m_iSize; + } +} + +bool GrowableMemoryByteStream::ReadByte(u8* pDestByte) +{ + if (m_iPosition < m_iSize) + { + *pDestByte = m_pMemory[m_iPosition++]; + return true; + } + + return false; +} + +u32 GrowableMemoryByteStream::Read(void* pDestination, u32 ByteCount) +{ + u32 sz = ByteCount; + if ((m_iPosition + ByteCount) > m_iSize) + sz = m_iSize - m_iPosition; + + if (sz > 0) + { + std::memcpy(pDestination, m_pMemory + m_iPosition, sz); + m_iPosition += sz; + } + + return sz; +} + +bool GrowableMemoryByteStream::Read2(void* pDestination, u32 ByteCount, u32* pNumberOfBytesRead /* = nullptr */) +{ + u32 r = Read(pDestination, ByteCount); + if (pNumberOfBytesRead != NULL) + *pNumberOfBytesRead = r; + + return (r == ByteCount); +} + +bool GrowableMemoryByteStream::WriteByte(u8 SourceByte) +{ + if (m_iPosition == m_iMemorySize) + Grow(1); + + m_pMemory[m_iPosition++] = SourceByte; + m_iSize = std::max(m_iSize, m_iPosition); + return true; +} + +u32 GrowableMemoryByteStream::Write(const void* pSource, u32 ByteCount) +{ + if ((m_iPosition + ByteCount) > m_iMemorySize) + Grow(ByteCount); + + std::memcpy(m_pMemory + m_iPosition, pSource, ByteCount); + m_iPosition += ByteCount; + m_iSize = std::max(m_iSize, m_iPosition); + return ByteCount; +} + +bool GrowableMemoryByteStream::Write2(const void* pSource, u32 ByteCount, u32* pNumberOfBytesWritten /* = nullptr */) +{ + u32 r = Write(pSource, ByteCount); + if (pNumberOfBytesWritten != nullptr) + *pNumberOfBytesWritten = r; + + return (r == ByteCount); +} + +bool GrowableMemoryByteStream::SeekAbsolute(u64 Offset) +{ + u32 Offset32 = (u32)Offset; + if (Offset32 > m_iSize) + return false; + + m_iPosition = Offset32; + return true; +} + +bool GrowableMemoryByteStream::SeekRelative(s64 Offset) +{ + s32 Offset32 = (s32)Offset; + if ((Offset32 < 0 && -Offset32 > (s32)m_iPosition) || (u32)((s32)m_iPosition + Offset32) > m_iSize) + return false; + + m_iPosition += Offset32; + return true; +} + +bool GrowableMemoryByteStream::SeekToEnd() +{ + m_iPosition = m_iSize; + return true; +} + +u64 GrowableMemoryByteStream::GetSize() const +{ + return (u64)m_iSize; +} + +u64 GrowableMemoryByteStream::GetPosition() const +{ + return (u64)m_iPosition; +} + +bool GrowableMemoryByteStream::Flush() +{ + return true; +} + +bool GrowableMemoryByteStream::Commit() +{ + return true; +} + +bool GrowableMemoryByteStream::Discard() +{ + return false; +} + +void GrowableMemoryByteStream::Grow(u32 MinimumGrowth) +{ + u32 NewSize = std::max(m_iMemorySize + MinimumGrowth, m_iMemorySize * 2); + ResizeMemory(NewSize); +} + +#if defined(_MSC_VER) + +std::unique_ptr ByteStream_OpenFileStream(const char* fileName, u32 openMode) +{ + if ((openMode & (BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE)) == BYTESTREAM_OPEN_WRITE) + { + // if opening with write but not create, the path must exist. + if (GetFileAttributes(fileName) == INVALID_FILE_ATTRIBUTES) + return nullptr; + } + + char modeString[16]; + u32 modeStringLength = 0; + + if (openMode & BYTESTREAM_OPEN_WRITE) + { + // if the file exists, use r+, otherwise w+ + // HACK: if we're not truncating, and the file exists (we want to only update it), we still have to use r+ + if ((openMode & BYTESTREAM_OPEN_TRUNCATE) || GetFileAttributes(fileName) == INVALID_FILE_ATTRIBUTES) + { + modeString[modeStringLength++] = 'w'; + if (openMode & BYTESTREAM_OPEN_READ) + modeString[modeStringLength++] = '+'; + } + else + { + modeString[modeStringLength++] = 'r'; + modeString[modeStringLength++] = '+'; + } + + modeString[modeStringLength++] = 'b'; + } + else if (openMode & BYTESTREAM_OPEN_READ) + { + modeString[modeStringLength++] = 'r'; + modeString[modeStringLength++] = 'b'; + } + + // doesn't work with _fdopen + if (!(openMode & BYTESTREAM_OPEN_ATOMIC_UPDATE)) + { + if (openMode & BYTESTREAM_OPEN_STREAMED) + modeString[modeStringLength++] = 'S'; + else if (openMode & BYTESTREAM_OPEN_SEEKABLE) + modeString[modeStringLength++] = 'R'; + } + + modeString[modeStringLength] = 0; + + if (openMode & BYTESTREAM_OPEN_CREATE_PATH) + { + u32 i; + u32 fileNameLength = static_cast(std::strlen(fileName)); + char* tempStr = (char*)alloca(fileNameLength + 1); + + // check if it starts with a drive letter. if so, skip ahead + if (fileNameLength >= 2 && fileName[1] == ':') + { + if (fileNameLength <= 3) + { + // create a file called driveletter: or driveletter:\ ? you must be crazy + i = fileNameLength; + } + else + { + std::memcpy(tempStr, fileName, 3); + i = 3; + } + } + else + { + // start at beginning + i = 0; + } + + // step through each path component, create folders as necessary + for (; i < fileNameLength; i++) + { + if (i > 0 && (fileName[i] == '\\' || fileName[i] == '/')) + { + // terminate the string + tempStr[i] = '\0'; + + // check if it exists + struct stat s; + if (stat(tempStr, &s) < 0) + { + if (errno == ENOENT) + { + // try creating it + if (_mkdir(tempStr) < 0) + { + // no point trying any further down the chain + break; + } + } + else // if (errno == ENOTDIR) + { + // well.. someone's trying to open a fucking weird path that is comprised of both directories and files... + // I aint sticking around here to find out what disaster awaits... let fopen deal with it + break; + } + } + +// append platform path seperator +#if defined(WIN32) + tempStr[i] = '\\'; +#else + tempStr[i] = '/'; +#endif + } + else + { + // append character to temp string + tempStr[i] = fileName[i]; + } + } + } + + if (openMode & BYTESTREAM_OPEN_ATOMIC_UPDATE) + { + DebugAssert(openMode & (BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE)); + + // generate the temporary file name + u32 fileNameLength = static_cast(std::strlen(fileName)); + char* temporaryFileName = (char*)alloca(fileNameLength + 8); + std::snprintf(temporaryFileName, fileNameLength + 8, "%s.XXXXXX", fileName); + + // fill in random characters + _mktemp_s(temporaryFileName, fileNameLength + 8); + const std::wstring wideTemporaryFileName(StringUtil::UTF8StringToWideString(temporaryFileName)); + + // massive hack here + DWORD desiredAccess = GENERIC_WRITE; + if (openMode & BYTESTREAM_OPEN_READ) + desiredAccess |= GENERIC_READ; + HANDLE hFile = + CreateFileW(wideTemporaryFileName.c_str(), desiredAccess, FILE_SHARE_DELETE, NULL, CREATE_NEW, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) + return nullptr; + + // get fd from this + int fd = _open_osfhandle(reinterpret_cast(hFile), 0); + if (fd < 0) + { + CloseHandle(hFile); + DeleteFileW(wideTemporaryFileName.c_str()); + return nullptr; + } + + // convert to a stream + FILE* pTemporaryFile = _fdopen(fd, modeString); + if (!pTemporaryFile) + { + _close(fd); + DeleteFileW(wideTemporaryFileName.c_str()); + return nullptr; + } + + // create the stream pointer + std::unique_ptr pStream = + std::make_unique(pTemporaryFile, fileName, temporaryFileName); + + // do we need to copy the existing file into this one? + if (!(openMode & BYTESTREAM_OPEN_TRUNCATE)) + { + FILE* pOriginalFile = FileSystem::OpenCFile(fileName, "rb"); + if (!pOriginalFile) + { + // this will delete the temporary file + pStream->Discard(); + return nullptr; + } + + static const size_t BUFFERSIZE = 4096; + u8 buffer[BUFFERSIZE]; + while (!feof(pOriginalFile)) + { + size_t nBytes = fread(buffer, BUFFERSIZE, sizeof(u8), pOriginalFile); + if (nBytes == 0) + break; + + if (pStream->Write(buffer, (u32)nBytes) != (u32)nBytes) + { + pStream->Discard(); + fclose(pOriginalFile); + return nullptr; + } + } + + // close original file + fclose(pOriginalFile); + } + + // return pointer + return pStream; + } + else + { + // forward through + FILE* pFile = FileSystem::OpenCFile(fileName, modeString); + if (!pFile) + return nullptr; + + return std::make_unique(pFile); + } +} + +#else + +std::unique_ptr ByteStream_OpenFileStream(const char* fileName, u32 openMode) +{ + if ((openMode & (BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE)) == BYTESTREAM_OPEN_WRITE) + { + // if opening with write but not create, the path must exist. + struct stat s; + if (stat(fileName, &s) < 0) + return nullptr; + } + + char modeString[16]; + u32 modeStringLength = 0; + + if (openMode & BYTESTREAM_OPEN_WRITE) + { + if (openMode & BYTESTREAM_OPEN_TRUNCATE) + modeString[modeStringLength++] = 'w'; + else + modeString[modeStringLength++] = 'a'; + + modeString[modeStringLength++] = 'b'; + + if (openMode & BYTESTREAM_OPEN_READ) + modeString[modeStringLength++] = '+'; + } + else if (openMode & BYTESTREAM_OPEN_READ) + { + modeString[modeStringLength++] = 'r'; + modeString[modeStringLength++] = 'b'; + } + + modeString[modeStringLength] = 0; + + if (openMode & BYTESTREAM_OPEN_CREATE_PATH) + { + u32 i; + const u32 fileNameLength = static_cast(std::strlen(fileName)); + char* tempStr = (char*)alloca(fileNameLength + 1); + +#if defined(WIN32) + // check if it starts with a drive letter. if so, skip ahead + if (fileNameLength >= 2 && fileName[1] == ':') + { + if (fileNameLength <= 3) + { + // create a file called driveletter: or driveletter:\ ? you must be crazy + i = fileNameLength; + } + else + { + std::memcpy(tempStr, fileName, 3); + i = 3; + } + } + else + { + // start at beginning + i = 0; + } +#endif + + // step through each path component, create folders as necessary + for (i = 0; i < fileNameLength; i++) + { + if (i > 0 && (fileName[i] == '\\' || fileName[i] == '/') && fileName[i] != ':') + { + // terminate the string + tempStr[i] = '\0'; + + // check if it exists + struct stat s; + if (stat(tempStr, &s) < 0) + { + if (errno == ENOENT) + { + // try creating it +#if defined(WIN32) + if (mkdir(tempStr) < 0) +#else + if (mkdir(tempStr, 0777) < 0) +#endif + { + // no point trying any further down the chain + break; + } + } + else // if (errno == ENOTDIR) + { + // well.. someone's trying to open a fucking weird path that is comprised of both directories and files... + // I aint sticking around here to find out what disaster awaits... let fopen deal with it + break; + } + } + +// append platform path seperator +#if defined(WIN32) + tempStr[i] = '\\'; +#else + tempStr[i] = '/'; +#endif + } + else + { + // append character to temp string + tempStr[i] = fileName[i]; + } + } + } + + if (openMode & BYTESTREAM_OPEN_ATOMIC_UPDATE) + { + DebugAssert(openMode & (BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE)); + + // generate the temporary file name + const u32 fileNameLength = static_cast(std::strlen(fileName)); + char* temporaryFileName = (char*)alloca(fileNameLength + 8); + std::snprintf(temporaryFileName, fileNameLength + 8, "%s.XXXXXX", fileName); + + // fill in random characters +#if defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) + mkstemp(temporaryFileName); +#else + mktemp(temporaryFileName); +#endif + + // open the file + std::FILE* pTemporaryFile = std::fopen(temporaryFileName, modeString); + if (pTemporaryFile == nullptr) + return nullptr; + + // create the stream pointer + std::unique_ptr pStream = + std::make_unique(pTemporaryFile, fileName, temporaryFileName); + + // do we need to copy the existing file into this one? + if (!(openMode & BYTESTREAM_OPEN_TRUNCATE)) + { + std::FILE* pOriginalFile = std::fopen(fileName, "rb"); + if (!pOriginalFile) + { + // this will delete the temporary file + pStream->SetErrorState(); + return nullptr; + } + + static const size_t BUFFERSIZE = 4096; + u8 buffer[BUFFERSIZE]; + while (!std::feof(pOriginalFile)) + { + size_t nBytes = std::fread(buffer, BUFFERSIZE, sizeof(u8), pOriginalFile); + if (nBytes == 0) + break; + + if (pStream->Write(buffer, (u32)nBytes) != (u32)nBytes) + { + pStream->SetErrorState(); + std::fclose(pOriginalFile); + return nullptr; + } + } + + // close original file + std::fclose(pOriginalFile); + } + + // return pointer + return pStream; + } + else + { + std::FILE* pFile = std::fopen(fileName, modeString); + if (!pFile) + return nullptr; + + return std::make_unique(pFile); + } +} + +#endif + +std::unique_ptr ByteStream_CreateMemoryStream(void* pMemory, u32 Size) +{ + DebugAssert(pMemory != nullptr && Size > 0); + return std::make_unique(pMemory, Size); +} + +std::unique_ptr ByteStream_CreateReadOnlyMemoryStream(const void* pMemory, u32 Size) +{ + DebugAssert(pMemory != nullptr && Size > 0); + return std::make_unique(pMemory, Size); +} + +std::unique_ptr ByteStream_CreateNullStream() +{ + return std::make_unique(); +} + +std::unique_ptr ByteStream_CreateGrowableMemoryStream(void* pInitialMemory, u32 InitialSize) +{ + return std::make_unique(pInitialMemory, InitialSize); +} + +std::unique_ptr ByteStream_CreateGrowableMemoryStream() +{ + return std::make_unique(nullptr, 0); +} + +bool ByteStream_CopyStream(ByteStream* pDestinationStream, ByteStream* pSourceStream) +{ + const u32 chunkSize = 4096; + u8 chunkData[chunkSize]; + + u64 oldSourcePosition = pSourceStream->GetPosition(); + if (!pSourceStream->SeekAbsolute(0) || !pDestinationStream->SeekAbsolute(0)) + return false; + + bool success = false; + for (;;) + { + u32 nBytes = pSourceStream->Read(chunkData, chunkSize); + if (nBytes == 0) + { + success = true; + break; + } + + if (pDestinationStream->Write(chunkData, nBytes) != nBytes) + break; + } + + return (pSourceStream->SeekAbsolute(oldSourcePosition) && success); +} + +bool ByteStream_AppendStream(ByteStream* pSourceStream, ByteStream* pDestinationStream) +{ + const u32 chunkSize = 4096; + u8 chunkData[chunkSize]; + + u64 oldSourcePosition = pSourceStream->GetPosition(); + if (!pSourceStream->SeekAbsolute(0)) + return false; + + bool success = false; + for (;;) + { + u32 nBytes = pSourceStream->Read(chunkData, chunkSize); + if (nBytes == 0) + { + success = true; + break; + } + + if (pDestinationStream->Write(chunkData, nBytes) != nBytes) + break; + } + + return (pSourceStream->SeekAbsolute(oldSourcePosition) && success); +} + +u32 ByteStream_CopyBytes(ByteStream* pSourceStream, u32 byteCount, ByteStream* pDestinationStream) +{ + const u32 chunkSize = 4096; + u8 chunkData[chunkSize]; + + u32 remaining = byteCount; + while (remaining > 0) + { + u32 toCopy = std::min(remaining, chunkSize); + u32 bytesRead = pSourceStream->Read(chunkData, toCopy); + if (bytesRead == 0) + break; + + u32 bytesWritten = pDestinationStream->Write(chunkData, bytesRead); + if (bytesWritten == 0) + break; + + remaining -= bytesWritten; + } + + return byteCount - remaining; +} diff --git a/jni/common/byte_stream.h b/jni/common/byte_stream.h new file mode 100644 index 0000000..a9096a1 --- /dev/null +++ b/jni/common/byte_stream.h @@ -0,0 +1,231 @@ +#pragma once +#include "types.h" +#include + +// base byte stream creation functions +enum BYTESTREAM_OPEN_MODE +{ + BYTESTREAM_OPEN_READ = 1, // open stream for writing + BYTESTREAM_OPEN_WRITE = 2, // open stream for writing + BYTESTREAM_OPEN_APPEND = 4, // seek to the end + BYTESTREAM_OPEN_TRUNCATE = 8, // truncate the file, seek to start + BYTESTREAM_OPEN_CREATE = 16, // if the file does not exist, create it + BYTESTREAM_OPEN_CREATE_PATH = 32, // if the file parent directories don't exist, create them + BYTESTREAM_OPEN_ATOMIC_UPDATE = 64, // + BYTESTREAM_OPEN_SEEKABLE = 128, + BYTESTREAM_OPEN_STREAMED = 256, +}; + +// interface class used by readers, writers, etc. +class ByteStream +{ +public: + virtual ~ByteStream() {} + + // reads a single byte from the stream. + virtual bool ReadByte(u8* pDestByte) = 0; + + // read bytes from this stream. returns the number of bytes read, if this isn't equal to the requested size, an error + // or EOF occurred. + virtual u32 Read(void* pDestination, u32 ByteCount) = 0; + + // read bytes from this stream, optionally returning the number of bytes read. + virtual bool Read2(void* pDestination, u32 ByteCount, u32* pNumberOfBytesRead = nullptr) = 0; + + // writes a single byte to the stream. + virtual bool WriteByte(u8 SourceByte) = 0; + + // write bytes to this stream, returns the number of bytes written. if this isn't equal to the requested size, a + // buffer overflow, or write error occurred. + virtual u32 Write(const void* pSource, u32 ByteCount) = 0; + + // write bytes to this stream, optionally returning the number of bytes written. + virtual bool Write2(const void* pSource, u32 ByteCount, u32* pNumberOfBytesWritten = nullptr) = 0; + + // seeks to the specified position in the stream + // if seek failed, returns false. + virtual bool SeekAbsolute(u64 Offset) = 0; + virtual bool SeekRelative(s64 Offset) = 0; + virtual bool SeekToEnd() = 0; + + // gets the current offset in the stream + virtual u64 GetPosition() const = 0; + + // gets the size of the stream + virtual u64 GetSize() const = 0; + + // flush any changes to the stream to disk + virtual bool Flush() = 0; + + // if the file was opened in atomic update mode, discards any changes made to the file + virtual bool Discard() = 0; + + // if the file was opened in atomic update mode, commits the file and replaces the temporary file + virtual bool Commit() = 0; + + // state accessors + inline bool InErrorState() const { return m_errorState; } + inline void SetErrorState() { m_errorState = true; } + inline void ClearErrorState() { m_errorState = false; } + +protected: + ByteStream() : m_errorState(false) {} + + // state bits + bool m_errorState; + + // make it noncopyable + ByteStream(const ByteStream&) = delete; + ByteStream& operator=(const ByteStream&) = delete; +}; + +class NullByteStream : public ByteStream +{ +public: + NullByteStream(); + ~NullByteStream(); + + virtual bool ReadByte(u8* pDestByte) override final; + virtual u32 Read(void* pDestination, u32 ByteCount) override final; + virtual bool Read2(void* pDestination, u32 ByteCount, u32* pNumberOfBytesRead /* = nullptr */) override final; + virtual bool WriteByte(u8 SourceByte) override final; + virtual u32 Write(const void* pSource, u32 ByteCount) override final; + virtual bool Write2(const void* pSource, u32 ByteCount, u32* pNumberOfBytesWritten /* = nullptr */) override final; + virtual bool SeekAbsolute(u64 Offset) override final; + virtual bool SeekRelative(s64 Offset) override final; + virtual bool SeekToEnd() override final; + virtual u64 GetSize() const override final; + virtual u64 GetPosition() const override final; + virtual bool Flush() override final; + virtual bool Commit() override final; + virtual bool Discard() override final; +}; + +class MemoryByteStream : public ByteStream +{ +public: + MemoryByteStream(void* pMemory, u32 MemSize); + virtual ~MemoryByteStream(); + + u8* GetMemoryPointer() const { return m_pMemory; } + u32 GetMemorySize() const { return m_iSize; } + + virtual bool ReadByte(u8* pDestByte) override; + virtual u32 Read(void* pDestination, u32 ByteCount) override; + virtual bool Read2(void* pDestination, u32 ByteCount, u32* pNumberOfBytesRead /* = nullptr */) override; + virtual bool WriteByte(u8 SourceByte) override; + virtual u32 Write(const void* pSource, u32 ByteCount) override; + virtual bool Write2(const void* pSource, u32 ByteCount, u32* pNumberOfBytesWritten /* = nullptr */) override; + virtual bool SeekAbsolute(u64 Offset) override; + virtual bool SeekRelative(s64 Offset) override; + virtual bool SeekToEnd() override; + virtual u64 GetSize() const override; + virtual u64 GetPosition() const override; + virtual bool Flush() override; + virtual bool Commit() override; + virtual bool Discard() override; + +private: + u8* m_pMemory; + u32 m_iPosition; + u32 m_iSize; +}; + +class ReadOnlyMemoryByteStream : public ByteStream +{ +public: + ReadOnlyMemoryByteStream(const void* pMemory, u32 MemSize); + virtual ~ReadOnlyMemoryByteStream(); + + const u8* GetMemoryPointer() const { return m_pMemory; } + u32 GetMemorySize() const { return m_iSize; } + + virtual bool ReadByte(u8* pDestByte) override; + virtual u32 Read(void* pDestination, u32 ByteCount) override; + virtual bool Read2(void* pDestination, u32 ByteCount, u32* pNumberOfBytesRead /* = nullptr */) override; + virtual bool WriteByte(u8 SourceByte) override; + virtual u32 Write(const void* pSource, u32 ByteCount) override; + virtual bool Write2(const void* pSource, u32 ByteCount, u32* pNumberOfBytesWritten /* = nullptr */) override; + virtual bool SeekAbsolute(u64 Offset) override; + virtual bool SeekRelative(s64 Offset) override; + virtual bool SeekToEnd() override; + virtual u64 GetSize() const override; + virtual u64 GetPosition() const override; + virtual bool Flush() override; + virtual bool Commit() override; + virtual bool Discard() override; + +private: + const u8* m_pMemory; + u32 m_iPosition; + u32 m_iSize; +}; + +class GrowableMemoryByteStream : public ByteStream +{ +public: + GrowableMemoryByteStream(void* pInitialMem, u32 InitialMemSize); + virtual ~GrowableMemoryByteStream(); + + u8* GetMemoryPointer() const { return m_pMemory; } + u32 GetMemorySize() const { return m_iMemorySize; } + + void Resize(u32 new_size); + void ResizeMemory(u32 new_size); + void EnsureSpace(u32 space); + void ShrinkToFit(); + + virtual bool ReadByte(u8* pDestByte) override; + virtual u32 Read(void* pDestination, u32 ByteCount) override; + virtual bool Read2(void* pDestination, u32 ByteCount, u32* pNumberOfBytesRead /* = nullptr */) override; + virtual bool WriteByte(u8 SourceByte) override; + virtual u32 Write(const void* pSource, u32 ByteCount) override; + virtual bool Write2(const void* pSource, u32 ByteCount, u32* pNumberOfBytesWritten /* = nullptr */) override; + virtual bool SeekAbsolute(u64 Offset) override; + virtual bool SeekRelative(s64 Offset) override; + virtual bool SeekToEnd() override; + virtual u64 GetSize() const override; + virtual u64 GetPosition() const override; + virtual bool Flush() override; + virtual bool Commit() override; + virtual bool Discard() override; + +private: + void Grow(u32 MinimumGrowth); + + u8* m_pPrivateMemory; + u8* m_pMemory; + u32 m_iPosition; + u32 m_iSize; + u32 m_iMemorySize; +}; + +// base byte stream creation functions +// opens a local file-based stream. fills in error if passed, and returns false if the file cannot be opened. +std::unique_ptr ByteStream_OpenFileStream(const char* FileName, u32 OpenMode); + +// memory byte stream, caller is responsible for management, therefore it can be located on either the stack or on the +// heap. +std::unique_ptr ByteStream_CreateMemoryStream(void* pMemory, u32 Size); + +// a growable memory byte stream will automatically allocate its own memory if the provided memory is overflowed. +// a "pure heap" buffer, i.e. a buffer completely managed by this implementation, can be created by supplying a NULL +// pointer and initialSize of zero. +std::unique_ptr ByteStream_CreateGrowableMemoryStream(void* pInitialMemory, u32 InitialSize); +std::unique_ptr ByteStream_CreateGrowableMemoryStream(); + +// readable memory stream +std::unique_ptr ByteStream_CreateReadOnlyMemoryStream(const void* pMemory, u32 Size); + +// null memory stream +std::unique_ptr ByteStream_CreateNullStream(); + +// copies one stream's contents to another. rewinds source streams automatically, and returns it back to its old +// position. +bool ByteStream_CopyStream(ByteStream* pDestinationStream, ByteStream* pSourceStream); + +// appends one stream's contents to another. +bool ByteStream_AppendStream(ByteStream* pSourceStream, ByteStream* pDestinationStream); + +// copies a number of bytes from one to another +u32 ByteStream_CopyBytes(ByteStream* pSourceStream, u32 byteCount, ByteStream* pDestinationStream); diff --git a/jni/common/cd_image.cpp b/jni/common/cd_image.cpp new file mode 100644 index 0000000..a235df5 --- /dev/null +++ b/jni/common/cd_image.cpp @@ -0,0 +1,367 @@ +#include "cd_image.h" +#include "assert.h" +#include "log.h" +#include +Log_SetChannel(CDImage); + +CDImage::CDImage() = default; + +CDImage::~CDImage() = default; + +u32 CDImage::GetBytesPerSector(TrackMode mode) +{ + static constexpr std::array sizes = {{2352, 2048, 2352, 2336, 2048, 2324, 2332, 2352}}; + return sizes[static_cast(mode)]; +} + +std::unique_ptr CDImage::Open(const char* filename) +{ + const char* extension = std::strrchr(filename, '.'); + if (!extension) + { + Log_ErrorPrintf("Invalid filename: '%s'", filename); + return nullptr; + } + +#ifdef _MSC_VER +#define CASE_COMPARE _stricmp +#else +#define CASE_COMPARE strcasecmp +#endif + + if (CASE_COMPARE(extension, ".cue") == 0) + { + return OpenCueSheetImage(filename); + } + else if (CASE_COMPARE(extension, ".bin") == 0 || CASE_COMPARE(extension, ".img") == 0 || + CASE_COMPARE(extension, ".iso") == 0) + { + return OpenBinImage(filename); + } + else if (CASE_COMPARE(extension, ".chd") == 0) + { + return OpenCHDImage(filename); + } + +#undef CASE_COMPARE + + Log_ErrorPrintf("Unknown extension '%s' from filename '%s'", extension, filename); + return nullptr; +} + +CDImage::LBA CDImage::GetTrackStartPosition(u8 track) const +{ + Assert(track > 0 && track <= m_tracks.size()); + return m_tracks[track - 1].start_lba; +} + +CDImage::Position CDImage::GetTrackStartMSFPosition(u8 track) const +{ + Assert(track > 0 && track <= m_tracks.size()); + return Position::FromLBA(m_tracks[track - 1].start_lba); +} + +CDImage::LBA CDImage::GetTrackLength(u8 track) const +{ + Assert(track > 0 && track <= m_tracks.size()); + return m_tracks[track - 1].length; +} + +CDImage::Position CDImage::GetTrackMSFLength(u8 track) const +{ + Assert(track > 0 && track <= m_tracks.size()); + return Position::FromLBA(m_tracks[track - 1].length); +} + +CDImage::TrackMode CDImage::GetTrackMode(u8 track) const +{ + Assert(track > 0 && track <= m_tracks.size()); + return m_tracks[track - 1].mode; +} + +CDImage::LBA CDImage::GetTrackIndexPosition(u8 track, u8 index) const +{ + for (const Index& current_index : m_indices) + { + if (current_index.track_number == track && current_index.index_number == index) + return current_index.start_lba_on_disc; + } + + return m_lba_count; +} + +CDImage::LBA CDImage::GetTrackIndexLength(u8 track, u8 index) const +{ + for (const Index& current_index : m_indices) + { + if (current_index.track_number == track && current_index.index_number == index) + return current_index.length; + } + + return 0; +} + +const CDImage::CDImage::Track& CDImage::GetTrack(u32 track) const +{ + Assert(track > 0 && track <= m_tracks.size()); + return m_tracks[track - 1]; +} + +const CDImage::CDImage::Index& CDImage::GetIndex(u32 i) const +{ + return m_indices[i]; +} + +bool CDImage::Seek(LBA lba) +{ + const Index* new_index; + if (m_current_index && lba >= m_current_index->start_lba_on_disc && + (lba - m_current_index->start_lba_on_disc) < m_current_index->length) + { + new_index = m_current_index; + } + else + { + new_index = GetIndexForDiscPosition(lba); + if (!new_index) + return false; + } + + const LBA new_index_offset = lba - new_index->start_lba_on_disc; + if (new_index_offset >= new_index->length) + return false; + + m_current_index = new_index; + m_position_on_disc = lba; + m_position_in_index = new_index_offset; + m_position_in_track = new_index->start_lba_in_track + new_index_offset; + return true; +} + +bool CDImage::Seek(u32 track_number, const Position& pos_in_track) +{ + if (track_number < 1 || track_number > m_tracks.size()) + return false; + + const Track& track = m_tracks[track_number - 1]; + const LBA pos_lba = pos_in_track.ToLBA(); + if (pos_lba >= track.length) + return false; + + return Seek(track.start_lba + pos_lba); +} + +bool CDImage::Seek(const Position& pos) +{ + return Seek(pos.ToLBA()); +} + +bool CDImage::Seek(u32 track_number, LBA lba) +{ + if (track_number < 1 || track_number > m_tracks.size()) + return false; + + const Track& track = m_tracks[track_number - 1]; + return Seek(track.start_lba + lba); +} + +u32 CDImage::Read(ReadMode read_mode, u32 sector_count, void* buffer) +{ + u8* buffer_ptr = static_cast(buffer); + u32 sectors_read = 0; + for (; sectors_read < sector_count; sectors_read++) + { + // get raw sector + u8 raw_sector[RAW_SECTOR_SIZE]; + if (!ReadRawSector(raw_sector)) + break; + + switch (read_mode) + { + case ReadMode::DataOnly: + std::memcpy(buffer_ptr, raw_sector + 24, DATA_SECTOR_SIZE); + buffer_ptr += DATA_SECTOR_SIZE; + break; + + case ReadMode::RawNoSync: + std::memcpy(buffer_ptr, raw_sector + SECTOR_SYNC_SIZE, RAW_SECTOR_SIZE - SECTOR_SYNC_SIZE); + buffer_ptr += RAW_SECTOR_SIZE - SECTOR_SYNC_SIZE; + break; + + case ReadMode::RawSector: + std::memcpy(buffer_ptr, raw_sector, RAW_SECTOR_SIZE); + buffer_ptr += RAW_SECTOR_SIZE; + break; + + default: + UnreachableCode(); + break; + } + + m_position_on_disc++; + m_position_in_index++; + m_position_in_track++; + } + + return sectors_read; +} + +bool CDImage::ReadRawSector(void* buffer) +{ + if (m_position_in_index == m_current_index->length) + { + if (!Seek(m_position_on_disc)) + return false; + } + + if (m_current_index->file_sector_size > 0) + { + // TODO: This is where we'd reconstruct the header for other mode tracks. + if (!ReadSectorFromIndex(buffer, *m_current_index, m_position_in_index)) + { + Log_ErrorPrintf("Read of LBA %u failed", m_position_on_disc); + Seek(m_position_on_disc); + return false; + } + } + else + { + if (m_current_index->track_number == LEAD_OUT_TRACK_NUMBER) + { + // Lead-out area. + std::fill(static_cast(buffer), static_cast(buffer) + RAW_SECTOR_SIZE, u8(0xAA)); + } + else + { + // This in an implicit pregap. Return silence. + std::fill(static_cast(buffer), static_cast(buffer) + RAW_SECTOR_SIZE, u8(0)); + } + } + + m_position_on_disc++; + m_position_in_index++; + m_position_in_track++; + return true; +} + +bool CDImage::ReadSubChannelQ(SubChannelQ* subq) +{ + // handle case where we're at the end of the track/index + if (!m_current_index || m_position_in_index == m_current_index->length) + return GenerateSubChannelQ(subq, m_position_on_disc); + + // otherwise save the index lookup + GenerateSubChannelQ(subq, m_current_index, m_position_in_index); + return true; +} + +bool CDImage::HasNonStandardSubchannel() const +{ + return false; +} + +const CDImage::Index* CDImage::GetIndexForDiscPosition(LBA pos) +{ + for (const Index& index : m_indices) + { + if (pos < index.start_lba_on_disc) + continue; + + const LBA index_offset = pos - index.start_lba_on_disc; + if (index_offset >= index.length) + continue; + + return &index; + } + + return nullptr; +} + +const CDImage::Index* CDImage::GetIndexForTrackPosition(u32 track_number, LBA track_pos) +{ + if (track_number < 1 || track_number > m_tracks.size()) + return nullptr; + + const Track& track = m_tracks[track_number - 1]; + if (track_pos >= track.length) + return nullptr; + + return GetIndexForDiscPosition(track.start_lba + track_pos); +} + +bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba) +{ + const Index* index = GetIndexForDiscPosition(lba); + if (!index) + return false; + + const u32 index_offset = index->start_lba_on_disc - lba; + GenerateSubChannelQ(subq, index, index_offset); + return true; +} + +void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index* index, u32 index_offset) +{ + subq->control.bits = index->control.bits; + subq->track_number_bcd = + (index->track_number <= m_tracks.size() ? BinaryToBCD(index->track_number) : index->track_number); + subq->index_number_bcd = BinaryToBCD(index->index_number); + + const Position relative_position = + Position::FromLBA(std::abs(static_cast(index->start_lba_in_track + index_offset))); + std::tie(subq->relative_minute_bcd, subq->relative_second_bcd, subq->relative_frame_bcd) = relative_position.ToBCD(); + subq->reserved = 0; + + const Position absolute_position = Position::FromLBA(index->start_lba_on_disc + index_offset); + std::tie(subq->absolute_minute_bcd, subq->absolute_second_bcd, subq->absolute_frame_bcd) = absolute_position.ToBCD(); + subq->crc = SubChannelQ::ComputeCRC(subq->data); +} + +void CDImage::AddLeadOutIndex() +{ + Assert(!m_indices.empty()); + const Index& last_index = m_indices.back(); + + Index index = {}; + index.start_lba_on_disc = last_index.start_lba_on_disc + last_index.length; + index.length = LEAD_OUT_SECTOR_COUNT; + index.track_number = LEAD_OUT_TRACK_NUMBER; + index.index_number = 0; + index.control.bits = last_index.control.bits; + m_indices.push_back(index); +} + +u16 CDImage::SubChannelQ::ComputeCRC(const Data& data) +{ + static constexpr std::array crc16_table = { + {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, + 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, + 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, + 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, + 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, + 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, + 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, + 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, + 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, + 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, + 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, + 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, + 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, + 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, + 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}}; + + u16 value = 0; + for (u32 i = 0; i < 10; i++) + value = crc16_table[(value >> 8) ^ data[i]] ^ (value << 8); + + return ~(value >> 8) | (~(value) << 8); +} + +bool CDImage::SubChannelQ::IsCRCValid() const +{ + return crc == ComputeCRC(data); +} \ No newline at end of file diff --git a/jni/common/cd_image.h b/jni/common/cd_image.h new file mode 100644 index 0000000..088e74f --- /dev/null +++ b/jni/common/cd_image.h @@ -0,0 +1,277 @@ +#pragma once +#include "bitfield.h" +#include "progress_callback.h" +#include "types.h" +#include +#include +#include +#include +#include + +class CDImage +{ +public: + CDImage(); + virtual ~CDImage(); + + using LBA = u32; + + enum : u32 + { + RAW_SECTOR_SIZE = 2352, + DATA_SECTOR_SIZE = 2048, + SECTOR_SYNC_SIZE = 12, + SECTOR_HEADER_SIZE = 4, + FRAMES_PER_SECOND = 75, // "sectors", or "timecode frames" (not "channel frames") + SECONDS_PER_MINUTE = 60, + FRAMES_PER_MINUTE = FRAMES_PER_SECOND * SECONDS_PER_MINUTE, + SUBCHANNEL_BYTES_PER_FRAME = 12, + LEAD_OUT_SECTOR_COUNT = 6750 + }; + + enum : u8 + { + LEAD_OUT_TRACK_NUMBER = 0xAA + }; + + enum class ReadMode : u32 + { + DataOnly, // 2048 bytes per sector. + RawSector, // 2352 bytes per sector. + RawNoSync, // 2340 bytes per sector. + }; + + enum class TrackMode : u32 + { + Audio, // 2352 bytes per sector + Mode1, // 2048 bytes per sector + Mode1Raw, // 2352 bytes per sector + Mode2, // 2336 bytes per sector + Mode2Form1, // 2048 bytes per sector + Mode2Form2, // 2324 bytes per sector + Mode2FormMix, // 2332 bytes per sector + Mode2Raw // 2352 bytes per sector + }; + + struct SectorHeader + { + u8 minute; + u8 second; + u8 frame; + u8 sector_mode; + }; + + struct Position + { + u8 minute; + u8 second; + u8 frame; + + static constexpr Position FromBCD(u8 minute, u8 second, u8 frame) + { + return Position{PackedBCDToBinary(minute), PackedBCDToBinary(second), PackedBCDToBinary(frame)}; + } + + static constexpr Position FromLBA(LBA lba) + { + const u8 frame = Truncate8(lba % FRAMES_PER_SECOND); + lba /= FRAMES_PER_SECOND; + + const u8 second = Truncate8(lba % SECONDS_PER_MINUTE); + lba /= SECONDS_PER_MINUTE; + + const u8 minute = Truncate8(lba); + + return Position{minute, second, frame}; + } + + LBA ToLBA() const + { + return ZeroExtend32(minute) * FRAMES_PER_MINUTE + ZeroExtend32(second) * FRAMES_PER_SECOND + ZeroExtend32(frame); + } + + constexpr std::tuple ToBCD() const + { + return std::make_tuple(BinaryToBCD(minute), BinaryToBCD(second), BinaryToBCD(frame)); + } + + Position operator+(const Position& rhs) { return FromLBA(ToLBA() + rhs.ToLBA()); } + Position& operator+=(const Position& pos) + { + *this = *this + pos; + return *this; + } + +#define RELATIONAL_OPERATOR(op) \ + bool operator op(const Position& rhs) const \ + { \ + return std::tie(minute, second, frame) op std::tie(rhs.minute, rhs.second, rhs.frame); \ + } + + RELATIONAL_OPERATOR(==); + RELATIONAL_OPERATOR(!=); + RELATIONAL_OPERATOR(<); + RELATIONAL_OPERATOR(<=); + RELATIONAL_OPERATOR(>); + RELATIONAL_OPERATOR(>=); + +#undef RELATIONAL_OPERATOR + }; + + union SubChannelQ + { + using Data = std::array; + + union Control + { + u8 bits; + + BitField adr; + BitField audio_preemphasis; + BitField digital_copy_permitted; + BitField data; + BitField four_channel_audio; + }; + + struct + { + Control control; + u8 track_number_bcd; + u8 index_number_bcd; + u8 relative_minute_bcd; + u8 relative_second_bcd; + u8 relative_frame_bcd; + u8 reserved; + u8 absolute_minute_bcd; + u8 absolute_second_bcd; + u8 absolute_frame_bcd; + u16 crc; + }; + + Data data; + + static u16 ComputeCRC(const Data& data); + + bool IsCRCValid() const; + + SubChannelQ& operator=(const SubChannelQ& q) + { + data = q.data; + return *this; + } + }; + static_assert(sizeof(SubChannelQ) == SUBCHANNEL_BYTES_PER_FRAME, "SubChannelQ is correct size"); + + struct Track + { + u32 track_number; + LBA start_lba; + u32 first_index; + u32 length; + TrackMode mode; + SubChannelQ::Control control; + }; + + struct Index + { + u64 file_offset; + u32 file_index; + u32 file_sector_size; + LBA start_lba_on_disc; + u32 track_number; + u32 index_number; + LBA start_lba_in_track; + u32 length; + TrackMode mode; + SubChannelQ::Control control; + bool is_pregap; + }; + + // Helper functions. + static u32 GetBytesPerSector(TrackMode mode); + + // Opening disc image. + static std::unique_ptr Open(const char* filename); + static std::unique_ptr OpenBinImage(const char* filename); + static std::unique_ptr OpenCueSheetImage(const char* filename); + static std::unique_ptr OpenCHDImage(const char* filename); + static std::unique_ptr + CreateMemoryImage(CDImage* image, ProgressCallback* progress = ProgressCallback::NullProgressCallback); + + // Accessors. + const std::string& GetFileName() const { return m_filename; } + LBA GetPositionOnDisc() const { return m_position_on_disc; } + Position GetMSFPositionOnDisc() const { return Position::FromLBA(m_position_on_disc); } + LBA GetPositionInTrack() const { return m_position_in_track; } + Position GetMSFPositionInTrack() const { return Position::FromLBA(m_position_in_track); } + LBA GetLBACount() const { return m_lba_count; } + u32 GetIndexNumber() const { return m_current_index->index_number; } + u32 GetTrackNumber() const { return m_current_index->track_number; } + u32 GetTrackCount() const { return static_cast(m_tracks.size()); } + LBA GetTrackStartPosition(u8 track) const; + Position GetTrackStartMSFPosition(u8 track) const; + LBA GetTrackLength(u8 track) const; + Position GetTrackMSFLength(u8 track) const; + TrackMode GetTrackMode(u8 track) const; + LBA GetTrackIndexPosition(u8 track, u8 index) const; + LBA GetTrackIndexLength(u8 track, u8 index) const; + u32 GetFirstTrackNumber() const { return m_tracks.front().track_number; } + u32 GetLastTrackNumber() const { return m_tracks.back().track_number; } + u32 GetIndexCount() const { return static_cast(m_indices.size()); } + const Track& GetTrack(u32 track) const; + const Index& GetIndex(u32 i) const; + + // Seek to data LBA. + bool Seek(LBA lba); + + // Seek to disc position (MSF). + bool Seek(const Position& pos); + + // Seek to track and position. + bool Seek(u32 track_number, const Position& pos_in_track); + + // Seek to track and LBA. + bool Seek(u32 track_number, LBA lba); + + // Read from the current LBA. Returns the number of sectors read. + u32 Read(ReadMode read_mode, u32 sector_count, void* buffer); + + // Read a single raw sector from the current LBA. + bool ReadRawSector(void* buffer); + + // Reads sub-channel Q for the current LBA. + virtual bool ReadSubChannelQ(SubChannelQ* subq); + + // Returns true if the image has replacement subchannel data. + virtual bool HasNonStandardSubchannel() const; + + // Reads a single sector from an index. + virtual bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) = 0; + +protected: + const Index* GetIndexForDiscPosition(LBA pos); + const Index* GetIndexForTrackPosition(u32 track_number, LBA track_pos); + + /// Generates sub-channel Q given the specified position. + bool GenerateSubChannelQ(SubChannelQ* subq, LBA lba); + + /// Generates sub-channel Q from the given index and index-offset. + void GenerateSubChannelQ(SubChannelQ* subq, const Index* index, u32 index_offset); + + /// Synthesis of lead-out data. + void AddLeadOutIndex(); + + std::string m_filename; + u32 m_lba_count = 0; + + std::vector m_tracks; + std::vector m_indices; + + // Position on disc. + LBA m_position_on_disc = 0; + + // Position in track/index. + const Index* m_current_index = nullptr; + LBA m_position_in_index = 0; + LBA m_position_in_track = 0; +}; diff --git a/jni/common/cd_image_bin.cpp b/jni/common/cd_image_bin.cpp new file mode 100644 index 0000000..6cc9cef --- /dev/null +++ b/jni/common/cd_image_bin.cpp @@ -0,0 +1,140 @@ +#include "cd_image.h" +#include "cd_subchannel_replacement.h" +#include "file_system.h" +#include "log.h" +#include +Log_SetChannel(CDImageBin); + +class CDImageBin : public CDImage +{ +public: + CDImageBin(); + ~CDImageBin() override; + + bool Open(const char* filename); + + bool ReadSubChannelQ(SubChannelQ* subq) override; + bool HasNonStandardSubchannel() const override; + +protected: + bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; + +private: + std::FILE* m_fp = nullptr; + u64 m_file_position = 0; + + CDSubChannelReplacement m_sbi; +}; + +CDImageBin::CDImageBin() = default; + +CDImageBin::~CDImageBin() +{ + if (m_fp) + std::fclose(m_fp); +} + +bool CDImageBin::Open(const char* filename) +{ + m_filename = filename; + m_fp = FileSystem::OpenCFile(filename, "rb"); + if (!m_fp) + { + Log_ErrorPrintf("Failed to open binfile '%s': errno %d", filename, errno); + return false; + } + + const u32 track_sector_size = RAW_SECTOR_SIZE; + + // determine the length from the file + std::fseek(m_fp, 0, SEEK_END); + const u32 file_size = static_cast(std::ftell(m_fp)); + std::fseek(m_fp, 0, SEEK_SET); + + m_lba_count = file_size / track_sector_size; + + SubChannelQ::Control control = {}; + TrackMode mode = TrackMode::Mode2Raw; + control.data = mode != TrackMode::Audio; + + // Two seconds default pregap. + const u32 pregap_frames = 2 * FRAMES_PER_SECOND; + Index pregap_index = {}; + pregap_index.file_sector_size = track_sector_size; + pregap_index.start_lba_on_disc = 0; + pregap_index.start_lba_in_track = static_cast(-static_cast(pregap_frames)); + pregap_index.length = pregap_frames; + pregap_index.track_number = 1; + pregap_index.index_number = 0; + pregap_index.mode = mode; + pregap_index.control.bits = control.bits; + pregap_index.is_pregap = true; + m_indices.push_back(pregap_index); + + // Data index. + Index data_index = {}; + data_index.file_index = 0; + data_index.file_offset = 0; + data_index.file_sector_size = track_sector_size; + data_index.start_lba_on_disc = pregap_index.length; + data_index.track_number = 1; + data_index.index_number = 1; + data_index.start_lba_in_track = 0; + data_index.length = m_lba_count; + data_index.mode = mode; + data_index.control.bits = control.bits; + m_indices.push_back(data_index); + + // Assume a single track. + m_tracks.push_back( + Track{static_cast(1), data_index.start_lba_on_disc, static_cast(0), m_lba_count, mode, control}); + + AddLeadOutIndex(); + + m_sbi.LoadSBI(FileSystem::ReplaceExtension(filename, "sbi").c_str()); + + return Seek(1, Position{0, 0, 0}); +} + +bool CDImageBin::ReadSubChannelQ(SubChannelQ* subq) +{ + if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + return true; + + return CDImage::ReadSubChannelQ(subq); +} + +bool CDImageBin::HasNonStandardSubchannel() const +{ + return (m_sbi.GetReplacementSectorCount() > 0); +} + +bool CDImageBin::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) +{ + const u64 file_position = index.file_offset + (static_cast(lba_in_index) * index.file_sector_size); + if (m_file_position != file_position) + { + if (std::fseek(m_fp, static_cast(file_position), SEEK_SET) != 0) + return false; + + m_file_position = file_position; + } + + if (std::fread(buffer, index.file_sector_size, 1, m_fp) != 1) + { + std::fseek(m_fp, static_cast(m_file_position), SEEK_SET); + return false; + } + + m_file_position += index.file_sector_size; + return true; +} + +std::unique_ptr CDImage::OpenBinImage(const char* filename) +{ + std::unique_ptr image = std::make_unique(); + if (!image->Open(filename)) + return {}; + + return image; +} diff --git a/jni/common/cd_image_chd.cpp b/jni/common/cd_image_chd.cpp new file mode 100644 index 0000000..eae1555 --- /dev/null +++ b/jni/common/cd_image_chd.cpp @@ -0,0 +1,353 @@ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "align.h" +#include "assert.h" +#include "cd_image.h" +#include "cd_subchannel_replacement.h" +#include "cpu_detect.h" +#include "file_system.h" +#include "libchdr/chd.h" +#include "log.h" +#include +#include +#include +#include +#include +#include +Log_SetChannel(CDImageCHD); + +static std::optional ParseTrackModeString(const char* str) +{ + if (std::strncmp(str, "MODE2_FORM_MIX", 14) == 0) + return CDImage::TrackMode::Mode2FormMix; + else if (std::strncmp(str, "MODE2_FORM1", 10) == 0) + return CDImage::TrackMode::Mode2Form1; + else if (std::strncmp(str, "MODE2_FORM2", 10) == 0) + return CDImage::TrackMode::Mode2Form2; + else if (std::strncmp(str, "MODE2_RAW", 9) == 0) + return CDImage::TrackMode::Mode2Raw; + else if (std::strncmp(str, "MODE1_RAW", 9) == 0) + return CDImage::TrackMode::Mode1Raw; + else if (std::strncmp(str, "MODE1", 5) == 0) + return CDImage::TrackMode::Mode1; + else if (std::strncmp(str, "MODE2", 5) == 0) + return CDImage::TrackMode::Mode2; + else if (std::strncmp(str, "AUDIO", 5) == 0) + return CDImage::TrackMode::Audio; + else + return std::nullopt; +} + +class CDImageCHD : public CDImage +{ +public: + CDImageCHD(); + ~CDImageCHD() override; + + bool Open(const char* filename); + + bool ReadSubChannelQ(SubChannelQ* subq) override; + bool HasNonStandardSubchannel() const override; + +protected: + bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; + +private: + enum : u32 + { + CHD_CD_SECTOR_DATA_SIZE = 2352 + 96, + CHD_CD_TRACK_ALIGNMENT = 4 + }; + + bool ReadHunk(u32 hunk_index); + + std::FILE* m_fp = nullptr; + chd_file* m_chd = nullptr; + u32 m_hunk_size = 0; + u32 m_sectors_per_hunk = 0; + + std::vector m_hunk_buffer; + u32 m_current_hunk_index = static_cast(-1); + + CDSubChannelReplacement m_sbi; +}; + +CDImageCHD::CDImageCHD() = default; + +CDImageCHD::~CDImageCHD() +{ + if (m_chd) + chd_close(m_chd); + if (m_fp) + std::fclose(m_fp); +} + +bool CDImageCHD::Open(const char* filename) +{ + Assert(!m_fp); + m_fp = FileSystem::OpenCFile(filename, "rb"); + if (!m_fp) + { + Log_ErrorPrintf("Failed to open CHD '%s': errno %d", filename, errno); + return false; + } + + chd_error err = chd_open_file(m_fp, CHD_OPEN_READ, nullptr, &m_chd); + if (err != CHDERR_NONE) + { + Log_ErrorPrintf("Failed to open CHD '%s': %s", filename, chd_error_string(err)); + return false; + } + + const chd_header* header = chd_get_header(m_chd); + m_hunk_size = header->hunkbytes; + if ((m_hunk_size % CHD_CD_SECTOR_DATA_SIZE) != 0) + { + Log_ErrorPrintf("Hunk size (%u) is not a multiple of %u", m_hunk_size, CHD_CD_SECTOR_DATA_SIZE); + return false; + } + + m_sectors_per_hunk = m_hunk_size / CHD_CD_SECTOR_DATA_SIZE; + m_hunk_buffer.resize(m_hunk_size); + m_filename = filename; + + u32 disc_lba = 0; + u64 file_lba = 0; + + // for each track.. + int num_tracks = 0; + for (;;) + { + char metadata_str[256]; + char type_str[256]; + char subtype_str[256]; + char pgtype_str[256]; + char pgsub_str[256]; + u32 metadata_length; + + int track_num = 0, frames = 0, pregap_frames = 0, postgap_frames = 0; + err = chd_get_metadata(m_chd, CDROM_TRACK_METADATA2_TAG, num_tracks, metadata_str, sizeof(metadata_str), + &metadata_length, nullptr, nullptr); + if (err == CHDERR_NONE) + { + if (std::sscanf(metadata_str, CDROM_TRACK_METADATA2_FORMAT, &track_num, type_str, subtype_str, &frames, + &pregap_frames, pgtype_str, pgsub_str, &postgap_frames) != 8) + { + Log_ErrorPrintf("Invalid track v2 metadata: '%s'", metadata_str); + return false; + } + } + else + { + // try old version + err = chd_get_metadata(m_chd, CDROM_TRACK_METADATA_TAG, num_tracks, metadata_str, sizeof(metadata_str), + &metadata_length, nullptr, nullptr); + if (err != CHDERR_NONE) + { + // not found, so no more tracks + break; + } + + if (std::sscanf(metadata_str, CDROM_TRACK_METADATA_FORMAT, &track_num, type_str, subtype_str, &frames) != 4) + { + Log_ErrorPrintf("Invalid track metadata: '%s'", metadata_str); + return false; + } + } + + if (track_num != (num_tracks + 1)) + { + Log_ErrorPrintf("Incorrect track number at index %d, expected %d got %d", num_tracks, (num_tracks + 1), + track_num); + return false; + } + + std::optional mode = ParseTrackModeString(type_str); + if (!mode.has_value()) + { + Log_ErrorPrintf("Invalid track mode: '%s'", type_str); + return false; + } + + // precompute subchannel q flags for the whole track + SubChannelQ::Control control{}; + control.data = mode.value() != TrackMode::Audio; + + // two seconds pregap for track 1 is assumed if not specified + const bool pregap_in_file = (pregap_frames > 0 && pgtype_str[0] == 'V'); + if (pregap_frames <= 0 && mode != TrackMode::Audio) + pregap_frames = 2 * FRAMES_PER_SECOND; + + // create the index for the pregap + if (pregap_frames > 0) + { + Index pregap_index = {}; + pregap_index.start_lba_on_disc = disc_lba; + pregap_index.start_lba_in_track = static_cast(static_cast(-pregap_frames)); + pregap_index.length = pregap_frames; + pregap_index.track_number = track_num; + pregap_index.index_number = 0; + pregap_index.mode = mode.value(); + pregap_index.control.bits = control.bits; + pregap_index.is_pregap = true; + + if (pregap_in_file) + { + if (pregap_frames > frames) + { + Log_ErrorPrintf("Pregap length %u exceeds track length %u", pregap_frames, frames); + return false; + } + + pregap_index.file_index = 0; + pregap_index.file_offset = file_lba; + pregap_index.file_sector_size = CHD_CD_SECTOR_DATA_SIZE; + file_lba += pregap_frames; + frames -= pregap_frames; + } + + m_indices.push_back(pregap_index); + disc_lba += pregap_frames; + } + + // add the track itself + m_tracks.push_back(Track{static_cast(track_num), disc_lba, static_cast(m_indices.size()), + static_cast(frames + pregap_frames), mode.value(), control}); + + // how many indices in this track? + Index index = {}; + index.start_lba_on_disc = disc_lba; + index.start_lba_in_track = 0; + index.track_number = track_num; + index.index_number = 1; + index.file_index = 0; + index.file_sector_size = CHD_CD_SECTOR_DATA_SIZE; + index.file_offset = file_lba; + index.mode = mode.value(); + index.control.bits = control.bits; + index.is_pregap = false; + index.length = static_cast(frames); + m_indices.push_back(index); + + disc_lba += index.length; + file_lba += index.length; + num_tracks++; + + // each track is padded to a multiple of 4 frames, see chdman source. + file_lba = Common::AlignUp(file_lba, CHD_CD_TRACK_ALIGNMENT); + } + + if (m_tracks.empty()) + { + Log_ErrorPrintf("File '%s' contains no tracks", filename); + return false; + } + + m_lba_count = disc_lba; + AddLeadOutIndex(); + + m_sbi.LoadSBI(FileSystem::ReplaceExtension(filename, "sbi").c_str()); + + return Seek(1, Position{0, 0, 0}); +} + +bool CDImageCHD::ReadSubChannelQ(SubChannelQ* subq) +{ + if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + return true; + + // TODO: Read subchannel data from CHD + + return CDImage::ReadSubChannelQ(subq); +} + +bool CDImageCHD::HasNonStandardSubchannel() const +{ + return (m_sbi.GetReplacementSectorCount() > 0); +} + +// There's probably a more efficient way of doing this with vectorization... +ALWAYS_INLINE static void CopyAndSwap(void* dst_ptr, const u8* src_ptr, u32 data_size) +{ + u8* dst_ptr_byte = static_cast(dst_ptr); +#if defined(CPU_X64) || defined(CPU_AARCH64) + const u32 num_values = data_size / 8; + for (u32 i = 0; i < num_values; i++) + { + u64 value; + std::memcpy(&value, src_ptr, sizeof(value)); + value = ((value >> 8) & UINT64_C(0x00FF00FF00FF00FF)) | ((value << 8) & UINT64_C(0xFF00FF00FF00FF00)); + std::memcpy(dst_ptr_byte, &value, sizeof(value)); + src_ptr += sizeof(value); + dst_ptr_byte += sizeof(value); + } +#elif defined(CPU_X86) || defined(CPU_ARM) + const u32 num_values = data_size / 4; + for (u32 i = 0; i < num_values; i++) + { + u32 value; + std::memcpy(&value, src_ptr, sizeof(value)); + value = ((value >> 8) & UINT32_C(0x00FF00FF)) | ((value << 8) & UINT32_C(0xFF00FF00)); + std::memcpy(dst_ptr_byte, &value, sizeof(value)); + src_ptr += sizeof(value); + dst_ptr_byte += sizeof(value); + } +#else + const u32 num_values = data_size / sizeof(u16); + for (u32 i = 0; i < num_values; i++) + { + u16 value; + std::memcpy(&value, src_ptr, sizeof(value)); + value = (value << 8) | (value >> 8); + std::memcpy(dst_ptr_byte, &value, sizeof(value)); + src_ptr += sizeof(value); + dst_ptr_byte += sizeof(value); + } +#endif +} + +bool CDImageCHD::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) +{ + const u32 disc_frame = static_cast(index.file_offset) + lba_in_index; + const u32 hunk_index = static_cast(disc_frame / m_sectors_per_hunk); + const u32 hunk_offset = static_cast((disc_frame % m_sectors_per_hunk) * CHD_CD_SECTOR_DATA_SIZE); + DebugAssert((m_hunk_size - hunk_offset) >= CHD_CD_SECTOR_DATA_SIZE); + + if (m_current_hunk_index != hunk_index && !ReadHunk(hunk_index)) + return false; + + // Audio data is in big-endian, so we have to swap it for little endian hosts... + if (index.mode == TrackMode::Audio) + CopyAndSwap(buffer, &m_hunk_buffer[hunk_offset], RAW_SECTOR_SIZE); + else + std::memcpy(buffer, &m_hunk_buffer[hunk_offset], RAW_SECTOR_SIZE); + + return true; +} + +bool CDImageCHD::ReadHunk(u32 hunk_index) +{ + const chd_error err = chd_read(m_chd, hunk_index, m_hunk_buffer.data()); + if (err != CHDERR_NONE) + { + Log_ErrorPrintf("chd_read(%u) failed: %s", hunk_index, chd_error_string(err)); + + // data might have been partially written + m_current_hunk_index = static_cast(-1); + return false; + } + + m_current_hunk_index = hunk_index; + return true; +} + +std::unique_ptr CDImage::OpenCHDImage(const char* filename) +{ + std::unique_ptr image = std::make_unique(); + if (!image->Open(filename)) + return {}; + + return image; +} diff --git a/jni/common/cd_image_cue.cpp b/jni/common/cd_image_cue.cpp new file mode 100644 index 0000000..a893bc4 --- /dev/null +++ b/jni/common/cd_image_cue.cpp @@ -0,0 +1,298 @@ +#include "assert.h" +#include "cd_image.h" +#include "cd_subchannel_replacement.h" +#include "file_system.h" +#include "log.h" +#include +#include +#include +#include +Log_SetChannel(CDImageCueSheet); + +class CDImageCueSheet : public CDImage +{ +public: + CDImageCueSheet(); + ~CDImageCueSheet() override; + + bool OpenAndParse(const char* filename); + + bool ReadSubChannelQ(SubChannelQ* subq) override; + bool HasNonStandardSubchannel() const override; + +protected: + bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; + +private: + Cd* m_cd = nullptr; + + struct TrackFile + { + std::string filename; + std::FILE* file; + u64 file_position; + }; + + std::vector m_files; + CDSubChannelReplacement m_sbi; +}; + +CDImageCueSheet::CDImageCueSheet() = default; + +CDImageCueSheet::~CDImageCueSheet() +{ + std::for_each(m_files.begin(), m_files.end(), [](TrackFile& t) { std::fclose(t.file); }); + cd_delete(m_cd); +} + +bool CDImageCueSheet::OpenAndParse(const char* filename) +{ + std::optional cuesheet_string = FileSystem::ReadFileToString(filename); + if (!cuesheet_string.has_value()) + { + Log_ErrorPrintf("Failed to open cuesheet '%s': errno %d", filename, errno); + return false; + } + + // work around cuesheet parsing issue - ensure the last character is a newline. + if (!cuesheet_string->empty() && cuesheet_string->at(cuesheet_string->size() - 1) != '\n') + *cuesheet_string += '\n'; + + m_cd = cue_parse_string(cuesheet_string->c_str()); + if (!m_cd) + { + Log_ErrorPrintf("Failed to parse cuesheet '%s'", filename); + return false; + } + + // get the directory of the filename + std::string basepath = FileSystem::GetPathDirectory(filename) + "/"; + m_filename = filename; + + u32 disc_lba = 0; + + // for each track.. + const int num_tracks = cd_get_ntrack(m_cd); + for (int track_num = 1; track_num <= num_tracks; track_num++) + { + const ::Track* track = cd_get_track(m_cd, track_num); + if (!track || !track_get_filename(track)) + { + Log_ErrorPrintf("Track/filename missing for track %d", track_num); + return false; + } + + const std::string track_filename = track_get_filename(track); + long track_start = track_get_start(track); + long track_length = track_get_length(track); + + u32 track_file_index = 0; + for (; track_file_index < m_files.size(); track_file_index++) + { + const TrackFile& t = m_files[track_file_index]; + if (t.filename == track_filename) + break; + } + if (track_file_index == m_files.size()) + { + const std::string track_full_filename(basepath + track_filename); + std::FILE* track_fp = FileSystem::OpenCFile(track_full_filename.c_str(), "rb"); + if (!track_fp && track_file_index == 0) + { + // many users have bad cuesheets, or they're renamed the files without updating the cuesheet. + // so, try searching for a bin with the same name as the cue, but only for the first referenced file. + const std::string alternative_filename(FileSystem::ReplaceExtension(filename, "bin")); + track_fp = FileSystem::OpenCFile(alternative_filename.c_str(), "rb"); + if (track_fp) + { + Log_WarningPrintf("Your cue sheet references an invalid file '%s', but this was found at '%s' instead.", + track_filename.c_str(), alternative_filename.c_str()); + } + } + + if (!track_fp) + { + Log_ErrorPrintf("Failed to open track filename '%s' (from '%s' and '%s'): errno %d", + track_full_filename.c_str(), track_filename.c_str(), filename, errno); + return false; + } + + m_files.push_back(TrackFile{std::move(track_filename), track_fp, 0}); + } + + // data type determines the sector size + const TrackMode mode = static_cast(track_get_mode(track)); + const u32 track_sector_size = GetBytesPerSector(mode); + + // precompute subchannel q flags for the whole track + SubChannelQ::Control control{}; + control.data = mode != TrackMode::Audio; + control.audio_preemphasis = track_is_set_flag(track, FLAG_PRE_EMPHASIS); + control.digital_copy_permitted = track_is_set_flag(track, FLAG_COPY_PERMITTED); + control.four_channel_audio = track_is_set_flag(track, FLAG_FOUR_CHANNEL); + + // determine the length from the file + if (track_length < 0) + { + std::fseek(m_files[track_file_index].file, 0, SEEK_END); + long file_size = std::ftell(m_files[track_file_index].file); + std::fseek(m_files[track_file_index].file, 0, SEEK_SET); + + file_size /= track_sector_size; + if (track_start >= file_size) + { + Log_ErrorPrintf("Failed to open track %u in '%s': track start is out of range (%u vs %u)", track_num, filename, + track_start, file_size); + return false; + } + + track_length = file_size - track_start; + } + + // Two seconds pregap for track 1 is assumed if not specified. + // Some people have broken (older) dumps where a two second pregap was implicit but not specified in the cuesheet. + // The problem is we can't tell between a missing implicit two second pregap and a zero second pregap. Most of these + // seem to be a single bin file for all tracks. So if this is the case, we add the two seconds in if it's not + // specified. + long pregap_frames = track_get_zero_pre(track); + const bool pregap_in_file = pregap_frames > 0 && track_start >= pregap_frames; + const bool is_multi_track_bin = (track_num > 1 && track_file_index == m_indices[0].file_index); + if ((track_num == 1 || is_multi_track_bin) && pregap_frames < 0) + pregap_frames = 2 * FRAMES_PER_SECOND; + + // create the index for the pregap + if (pregap_frames > 0) + { + Index pregap_index = {}; + pregap_index.start_lba_on_disc = disc_lba; + pregap_index.start_lba_in_track = static_cast(static_cast(-pregap_frames)); + pregap_index.length = pregap_frames; + pregap_index.track_number = track_num; + pregap_index.index_number = 0; + pregap_index.mode = mode; + pregap_index.control.bits = control.bits; + pregap_index.is_pregap = true; + if (pregap_in_file) + { + pregap_index.file_index = track_file_index; + pregap_index.file_offset = static_cast(static_cast(track_start - pregap_frames)) * track_sector_size; + pregap_index.file_sector_size = track_sector_size; + } + + m_indices.push_back(pregap_index); + + disc_lba += pregap_index.length; + } + + // add the track itself + m_tracks.push_back(Track{static_cast(track_num), disc_lba, static_cast(m_indices.size()), + static_cast(track_length + pregap_frames), mode, control}); + + // how many indices in this track? + Index last_index; + last_index.start_lba_on_disc = disc_lba; + last_index.start_lba_in_track = 0; + last_index.track_number = track_num; + last_index.index_number = 1; + last_index.file_index = track_file_index; + last_index.file_sector_size = track_sector_size; + last_index.file_offset = static_cast(static_cast(track_start)) * track_sector_size; + last_index.mode = mode; + last_index.control.bits = control.bits; + last_index.is_pregap = false; + + long last_index_offset = track_start; + for (int index_num = 1;; index_num++) + { + long index_offset = track_get_index(track, index_num); + if (index_offset < 0) + break; + + // add an index between the track indices + if (index_offset > last_index_offset) + { + last_index.length = index_offset - last_index_offset; + m_indices.push_back(last_index); + + disc_lba += last_index.length; + last_index.start_lba_in_track += last_index.length; + last_index.start_lba_on_disc = disc_lba; + last_index.length = 0; + } + + last_index.file_offset = index_offset * last_index.file_sector_size; + last_index.index_number = static_cast(index_num); + last_index_offset = index_offset; + } + + // and the last index is added here + const long track_end_index = track_start + track_length; + DebugAssert(track_end_index >= last_index_offset); + if (track_end_index > last_index_offset) + { + last_index.length = track_end_index - last_index_offset; + m_indices.push_back(last_index); + + disc_lba += last_index.length; + } + } + + if (m_tracks.empty()) + { + Log_ErrorPrintf("File '%s' contains no tracks", filename); + return false; + } + + m_lba_count = disc_lba; + AddLeadOutIndex(); + + m_sbi.LoadSBI(FileSystem::ReplaceExtension(filename, "sbi").c_str()); + + return Seek(1, Position{0, 0, 0}); +} + +bool CDImageCueSheet::ReadSubChannelQ(SubChannelQ* subq) +{ + if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + return true; + + return CDImage::ReadSubChannelQ(subq); +} + +bool CDImageCueSheet::HasNonStandardSubchannel() const +{ + return (m_sbi.GetReplacementSectorCount() > 0); +} + +bool CDImageCueSheet::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) +{ + DebugAssert(index.file_index < m_files.size()); + + TrackFile& tf = m_files[index.file_index]; + const u64 file_position = index.file_offset + (static_cast(lba_in_index) * index.file_sector_size); + if (tf.file_position != file_position) + { + if (std::fseek(tf.file, static_cast(file_position), SEEK_SET) != 0) + return false; + + tf.file_position = file_position; + } + + if (std::fread(buffer, index.file_sector_size, 1, tf.file) != 1) + { + std::fseek(tf.file, static_cast(tf.file_position), SEEK_SET); + return false; + } + + tf.file_position += index.file_sector_size; + return true; +} + +std::unique_ptr CDImage::OpenCueSheetImage(const char* filename) +{ + std::unique_ptr image = std::make_unique(); + if (!image->OpenAndParse(filename)) + return {}; + + return image; +} diff --git a/jni/common/cd_image_hasher.cpp b/jni/common/cd_image_hasher.cpp new file mode 100644 index 0000000..cf3f829 --- /dev/null +++ b/jni/common/cd_image_hasher.cpp @@ -0,0 +1,116 @@ +#include "cd_image_hasher.h" +#include "cd_image.h" +#include "md5_digest.h" +#include "string_util.h" + +namespace CDImageHasher { + +static bool ReadIndex(CDImage* image, u8 track, u8 index, MD5Digest* digest, ProgressCallback* progress_callback) +{ + const CDImage::LBA index_start = image->GetTrackIndexPosition(track, index); + const u32 index_length = image->GetTrackIndexLength(track, index); + const u32 update_interval = std::max(index_length / 100u, 1u); + + progress_callback->SetFormattedStatusText("Computing hash for track %u/index %u...", track, index); + progress_callback->SetProgressRange(index_length); + + if (!image->Seek(index_start)) + { + progress_callback->DisplayFormattedModalError("Failed to seek to sector %u for track %u index %u", index_start, + track, index); + return false; + } + + std::array sector; + for (u32 lba = 0; lba < index_length; lba++) + { + if ((lba % update_interval) == 0) + progress_callback->SetProgressValue(lba); + + if (!image->ReadRawSector(sector.data())) + { + progress_callback->DisplayFormattedModalError("Failed to read sector %u from image", image->GetPositionOnDisc()); + return false; + } + + digest->Update(sector.data(), static_cast(sector.size())); + } + + progress_callback->SetProgressValue(index_length); + return true; +} + +static bool ReadTrack(CDImage* image, u8 track, MD5Digest* digest, ProgressCallback* progress_callback) +{ + static constexpr u8 INDICES_TO_READ = 2; + + progress_callback->PushState(); + + progress_callback->SetProgressRange(2); + for (u8 index = 0; index < INDICES_TO_READ; index++) + { + progress_callback->SetProgressValue(index); + + // skip index 0 if data track + if (track == 1 && index == 0) + continue; + + progress_callback->PushState(); + if (!ReadIndex(image, track, index, digest, progress_callback)) + { + progress_callback->PopState(); + progress_callback->PopState(); + return false; + } + + progress_callback->PopState(); + } + + progress_callback->SetProgressValue(INDICES_TO_READ); + progress_callback->PopState(); + return true; +} + +std::string HashToString(const Hash& hash) +{ + return StringUtil::StdStringFromFormat("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", hash[0], + hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], + hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]); +} + +bool GetImageHash(CDImage* image, Hash* out_hash, + ProgressCallback* progress_callback /*= ProgressCallback::NullProgressCallback*/) +{ + MD5Digest digest; + + progress_callback->SetProgressRange(image->GetTrackCount()); + progress_callback->SetProgressValue(0); + progress_callback->PushState(); + + for (u32 i = 1; i <= image->GetTrackCount(); i++) + { + progress_callback->SetProgressValue(i - 1); + if (!ReadTrack(image, i, &digest, progress_callback)) + { + progress_callback->PopState(); + return false; + } + } + + progress_callback->SetProgressValue(image->GetTrackCount()); + digest.Final(out_hash->data()); + return true; +} + +bool GetTrackHash(CDImage* image, u8 track, Hash* out_hash, + ProgressCallback* progress_callback /*= ProgressCallback::NullProgressCallback*/) +{ + MD5Digest digest; + if (!ReadTrack(image, track, &digest, progress_callback)) + return false; + + digest.Final(out_hash->data()); + return true; +} + +} // namespace CDImageHasher \ No newline at end of file diff --git a/jni/common/cd_image_hasher.h b/jni/common/cd_image_hasher.h new file mode 100644 index 0000000..38d5f32 --- /dev/null +++ b/jni/common/cd_image_hasher.h @@ -0,0 +1,19 @@ +#pragma once +#include "progress_callback.h" +#include "types.h" +#include +#include + +class CDImage; + +namespace CDImageHasher { + +using Hash = std::array; +std::string HashToString(const Hash& hash); + +bool GetImageHash(CDImage* image, Hash* out_hash, + ProgressCallback* progress_callback = ProgressCallback::NullProgressCallback); +bool GetTrackHash(CDImage* image, u8 track, Hash* out_hash, + ProgressCallback* progress_callback = ProgressCallback::NullProgressCallback); + +} // namespace CDImageHasher \ No newline at end of file diff --git a/jni/common/cd_image_memory.cpp b/jni/common/cd_image_memory.cpp new file mode 100644 index 0000000..57711f2 --- /dev/null +++ b/jni/common/cd_image_memory.cpp @@ -0,0 +1,153 @@ +#include "assert.h" +#include "cd_image.h" +#include "cd_subchannel_replacement.h" +#include "file_system.h" +#include "log.h" +#include +#include +#include +#include +Log_SetChannel(CDImageMemory); + +class CDImageMemory : public CDImage +{ +public: + CDImageMemory(); + ~CDImageMemory() override; + + bool CopyImage(CDImage* image, ProgressCallback* progress); + + bool ReadSubChannelQ(SubChannelQ* subq) override; + bool HasNonStandardSubchannel() const override; + +protected: + bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; + +private: + u8* m_memory = nullptr; + u32 m_memory_sectors = 0; + CDSubChannelReplacement m_sbi; +}; + +CDImageMemory::CDImageMemory() = default; + +CDImageMemory::~CDImageMemory() +{ + if (m_memory) + std::free(m_memory); +} + +bool CDImageMemory::CopyImage(CDImage* image, ProgressCallback* progress) +{ + // figure out the total number of sectors (not including blank pregaps) + m_memory_sectors = 0; + for (u32 i = 0; i < image->GetIndexCount(); i++) + { + const Index& index = image->GetIndex(i); + if (index.file_sector_size > 0) + m_memory_sectors += image->GetIndex(i).length; + } + + if ((static_cast(RAW_SECTOR_SIZE) * static_cast(m_memory_sectors)) >= + static_cast(std::numeric_limits::max())) + { + progress->DisplayFormattedModalError("Insufficient address space"); + return false; + } + + progress->SetFormattedStatusText("Allocating memory for %u sectors...", m_memory_sectors); + + m_memory = + static_cast(std::malloc(static_cast(RAW_SECTOR_SIZE) * static_cast(m_memory_sectors))); + if (!m_memory) + { + progress->DisplayFormattedModalError("Failed to allocate memory for %llu sectors", m_memory_sectors); + return false; + } + + progress->SetStatusText("Preloading CD image to RAM..."); + progress->SetProgressRange(m_memory_sectors); + progress->SetProgressValue(0); + + u8* memory_ptr = m_memory; + u32 sectors_read = 0; + for (u32 i = 0; i < image->GetIndexCount(); i++) + { + const Index& index = image->GetIndex(i); + if (index.file_sector_size == 0) + continue; + + for (u32 lba = 0; lba < index.length; lba++) + { + if (!image->ReadSectorFromIndex(memory_ptr, index, lba)) + { + Log_ErrorPrintf("Failed to read LBA %u in index %u", lba, index); + return false; + } + + progress->SetProgressValue(sectors_read); + memory_ptr += RAW_SECTOR_SIZE; + sectors_read++; + } + } + + for (u32 i = 1; i <= image->GetTrackCount(); i++) + m_tracks.push_back(image->GetTrack(i)); + + u32 current_offset = 0; + for (u32 i = 0; i < image->GetIndexCount(); i++) + { + Index new_index = image->GetIndex(i); + new_index.file_index = 0; + if (new_index.file_sector_size > 0) + { + new_index.file_offset = current_offset; + current_offset += new_index.length; + } + m_indices.push_back(new_index); + } + + Assert(current_offset == m_memory_sectors); + m_filename = image->GetFileName(); + m_lba_count = image->GetLBACount(); + + m_sbi.LoadSBI(FileSystem::ReplaceExtension(m_filename, "sbi").c_str()); + + return Seek(1, Position{0, 0, 0}); +} + +bool CDImageMemory::ReadSubChannelQ(SubChannelQ* subq) +{ + if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq)) + return true; + + return CDImage::ReadSubChannelQ(subq); +} + +bool CDImageMemory::HasNonStandardSubchannel() const +{ + return (m_sbi.GetReplacementSectorCount() > 0); +} + +bool CDImageMemory::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) +{ + DebugAssert(index.file_index == 0); + + const u64 sector_number = index.file_offset + lba_in_index; + if (sector_number >= m_memory_sectors) + return false; + + const size_t file_offset = static_cast(sector_number) * static_cast(RAW_SECTOR_SIZE); + std::memcpy(buffer, &m_memory[file_offset], RAW_SECTOR_SIZE); + return true; +} + +std::unique_ptr +CDImage::CreateMemoryImage(CDImage* image, ProgressCallback* progress /* = ProgressCallback::NullProgressCallback */) +{ + std::unique_ptr memory_image = std::make_unique(); + if (!memory_image->CopyImage(image, progress)) + return {}; + + return memory_image; +} diff --git a/jni/common/cd_subchannel_replacement.cpp b/jni/common/cd_subchannel_replacement.cpp new file mode 100644 index 0000000..76b4ffa --- /dev/null +++ b/jni/common/cd_subchannel_replacement.cpp @@ -0,0 +1,109 @@ +#include "cd_subchannel_replacement.h" +#include "file_system.h" +#include "log.h" +#include +#include +Log_SetChannel(CDSubChannelReplacement); + +#pragma pack(push, 1) +struct SBIFileEntry +{ + u8 minute_bcd; + u8 second_bcd; + u8 frame_bcd; + u8 type; + u8 data[10]; +}; +#pragma pack(pop) + +CDSubChannelReplacement::CDSubChannelReplacement() = default; + +CDSubChannelReplacement::~CDSubChannelReplacement() = default; + +static constexpr u32 MSFToLBA(u8 minute_bcd, u8 second_bcd, u8 frame_bcd) +{ + const u8 minute = PackedBCDToBinary(minute_bcd); + const u8 second = PackedBCDToBinary(second_bcd); + const u8 frame = PackedBCDToBinary(frame_bcd); + + return (ZeroExtend32(minute) * 60 * 75) + (ZeroExtend32(second) * 75) + ZeroExtend32(frame); +} + +bool CDSubChannelReplacement::LoadSBI(const char* path) +{ + auto fp = FileSystem::OpenManagedCFile(path, "rb"); + if (!fp) + return false; + + char header[4]; + if (std::fread(header, sizeof(header), 1, fp.get()) != 1) + { + Log_ErrorPrintf("Failed to read header for '%s'", path); + return true; + } + + static constexpr char expected_header[] = {'S', 'B', 'I', '\0'}; + if (std::memcmp(header, expected_header, sizeof(header)) != 0) + { + Log_ErrorPrintf("Invalid header in '%s'", path); + return true; + } + + SBIFileEntry entry; + while (std::fread(&entry, sizeof(entry), 1, fp.get()) == 1) + { + if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) || + !IsValidPackedBCD(entry.frame_bcd)) + { + Log_ErrorPrintf("Invalid position [%02x:%02x:%02x] in '%s'", entry.minute_bcd, entry.second_bcd, entry.frame_bcd, + path); + return false; + } + + if (entry.type != 1) + { + Log_ErrorPrintf("Invalid type 0x%02X in '%s'", entry.type, path); + return false; + } + + const u32 lba = MSFToLBA(entry.minute_bcd, entry.second_bcd, entry.frame_bcd); + + CDImage::SubChannelQ subq; + std::copy_n(entry.data, countof(entry.data), subq.data.data()); + + // generate an invalid crc by flipping all bits from the valid crc (will never collide) + const u16 crc = subq.ComputeCRC(subq.data) ^ 0xFFFF; + subq.data[10] = Truncate8(crc); + subq.data[11] = Truncate8(crc >> 8); + + m_replacement_subq.emplace(lba, subq); + } + + Log_InfoPrintf("Loaded %zu replacement sectors from '%s'", m_replacement_subq.size(), path); + return true; +} + +void CDSubChannelReplacement::AddReplacementSubChannelQ(u32 lba, const CDImage::SubChannelQ& subq) +{ + auto iter = m_replacement_subq.find(lba); + if (iter != m_replacement_subq.end()) + iter->second.data = subq.data; + else + m_replacement_subq.emplace(lba, subq); +} + +bool CDSubChannelReplacement::GetReplacementSubChannelQ(u8 minute_bcd, u8 second_bcd, u8 frame_bcd, + CDImage::SubChannelQ* subq) const +{ + return GetReplacementSubChannelQ(MSFToLBA(minute_bcd, second_bcd, frame_bcd), subq); +} + +bool CDSubChannelReplacement::GetReplacementSubChannelQ(u32 lba, CDImage::SubChannelQ* subq) const +{ + const auto iter = m_replacement_subq.find(lba); + if (iter == m_replacement_subq.cend()) + return false; + + *subq = iter->second; + return true; +} diff --git a/jni/common/cd_subchannel_replacement.h b/jni/common/cd_subchannel_replacement.h new file mode 100644 index 0000000..4c622df --- /dev/null +++ b/jni/common/cd_subchannel_replacement.h @@ -0,0 +1,31 @@ +#pragma once +#include "cd_image.h" +#include "types.h" +#include +#include +#include + +class CDSubChannelReplacement +{ +public: + CDSubChannelReplacement(); + ~CDSubChannelReplacement(); + + u32 GetReplacementSectorCount() const { return static_cast(m_replacement_subq.size()); } + + bool LoadSBI(const char* path); + + /// Adds a sector to the replacement map. + void AddReplacementSubChannelQ(u32 lba, const CDImage::SubChannelQ& subq); + + /// Returns the replacement subchannel data for the specified position (in BCD). + bool GetReplacementSubChannelQ(u8 minute_bcd, u8 second_bcd, u8 frame_bcd, CDImage::SubChannelQ* subq) const; + + /// Returns the replacement subchannel data for the specified sector. + bool GetReplacementSubChannelQ(u32 lba, CDImage::SubChannelQ* subq) const; + +private: + using ReplacementMap = std::unordered_map; + + ReplacementMap m_replacement_subq; +}; diff --git a/jni/common/cd_xa.cpp b/jni/common/cd_xa.cpp new file mode 100644 index 0000000..fa4b1e0 --- /dev/null +++ b/jni/common/cd_xa.cpp @@ -0,0 +1,98 @@ +#include "cd_xa.h" +#include "cd_image.h" +#include +#include + +namespace CDXA { +static constexpr std::array s_xa_adpcm_filter_table_pos = {{0, 60, 115, 98}}; +static constexpr std::array s_xa_adpcm_filter_table_neg = {{0, 0, -52, -55}}; + +template +static void DecodeXA_ADPCMChunk(const u8* chunk_ptr, s16* samples, s32* last_samples) +{ + // The data layout is annoying here. Each word of data is interleaved with the other blocks, requiring multiple + // passes to decode the whole chunk. + constexpr u32 NUM_BLOCKS = IS_8BIT ? 4 : 8; + constexpr u32 WORDS_PER_BLOCK = 28; + + const u8* headers_ptr = chunk_ptr + 4; + const u8* words_ptr = chunk_ptr + 16; + + for (u32 block = 0; block < NUM_BLOCKS; block++) + { + const XA_ADPCMBlockHeader block_header{headers_ptr[block]}; + const u8 shift = block_header.GetShift(); + const u8 filter = block_header.GetFilter(); + const s32 filter_pos = s_xa_adpcm_filter_table_pos[filter]; + const s32 filter_neg = s_xa_adpcm_filter_table_neg[filter]; + + s16* out_samples_ptr = + IS_STEREO ? &samples[(block / 2) * (WORDS_PER_BLOCK * 2) + (block % 2)] : &samples[block * WORDS_PER_BLOCK]; + constexpr u32 out_samples_increment = IS_STEREO ? 2 : 1; + + for (u32 word = 0; word < 28; word++) + { + // NOTE: assumes LE + u32 word_data; + std::memcpy(&word_data, &words_ptr[word * sizeof(u32)], sizeof(word_data)); + + // extract nibble from block + const u32 nibble = IS_8BIT ? ((word_data >> (block * 8)) & 0xFF) : ((word_data >> (block * 4)) & 0x0F); + const s16 sample = static_cast(Truncate16(nibble << 12)) >> shift; + + // mix in previous values + s32* prev = IS_STEREO ? &last_samples[(block & 1) * 2] : last_samples; + const s32 interp_sample = s32(sample) + ((prev[0] * filter_pos) + (prev[1] * filter_neg) + 32) / 64; + + // update previous values + prev[1] = prev[0]; + prev[0] = interp_sample; + + *out_samples_ptr = static_cast(std::clamp(interp_sample, -0x8000, 0x7FFF)); + out_samples_ptr += out_samples_increment; + } + } +} + +template +static void DecodeXA_ADPCMChunks(const u8* chunk_ptr, s16* samples, s32* last_samples) +{ + constexpr u32 NUM_CHUNKS = 18; + constexpr u32 CHUNK_SIZE_IN_BYTES = 128; + constexpr u32 WORDS_PER_CHUNK = 28; + constexpr u32 SAMPLES_PER_CHUNK = WORDS_PER_CHUNK * (IS_8BIT ? 4 : 8); + + for (u32 i = 0; i < NUM_CHUNKS; i++) + { + DecodeXA_ADPCMChunk(chunk_ptr, samples, last_samples); + samples += SAMPLES_PER_CHUNK; + chunk_ptr += CHUNK_SIZE_IN_BYTES; + } +} + +void DecodeADPCMSector(const void* data, s16* samples, s32* last_samples) +{ + const XASubHeader* subheader = reinterpret_cast( + reinterpret_cast(data) + CDImage::SECTOR_SYNC_SIZE + sizeof(CDImage::SectorHeader)); + + // The XA subheader is repeated? + const u8* chunk_ptr = reinterpret_cast(data) + CDImage::SECTOR_SYNC_SIZE + sizeof(CDImage::SectorHeader) + + sizeof(XASubHeader) + 4; + + if (subheader->codinginfo.bits_per_sample != 1) + { + if (subheader->codinginfo.mono_stereo != 1) + DecodeXA_ADPCMChunks(chunk_ptr, samples, last_samples); + else + DecodeXA_ADPCMChunks(chunk_ptr, samples, last_samples); + } + else + { + if (subheader->codinginfo.mono_stereo != 1) + DecodeXA_ADPCMChunks(chunk_ptr, samples, last_samples); + else + DecodeXA_ADPCMChunks(chunk_ptr, samples, last_samples); + } +} + +} // namespace CDXA diff --git a/jni/common/cd_xa.h b/jni/common/cd_xa.h new file mode 100644 index 0000000..3e996d1 --- /dev/null +++ b/jni/common/cd_xa.h @@ -0,0 +1,70 @@ +#pragma once +#include "bitfield.h" +#include "types.h" + +namespace CDXA { +enum +{ + XA_SUBHEADER_SIZE = 4, + XA_ADPCM_SAMPLES_PER_SECTOR_4BIT = 4032, // 28 words * 8 nibbles per word * 18 chunks + XA_ADPCM_SAMPLES_PER_SECTOR_8BIT = 2016 // 28 words * 4 bytes per word * 18 chunks +}; + +struct XASubHeader +{ + u8 file_number; + u8 channel_number; + union Submode + { + u8 bits; + BitField eor; + BitField video; + BitField audio; + BitField data; + BitField trigger; + BitField form2; + BitField realtime; + BitField eof; + } submode; + union Codinginfo + { + u8 bits; + + BitField mono_stereo; + BitField sample_rate; + BitField bits_per_sample; + BitField emphasis; + + bool IsStereo() const { return mono_stereo == 1; } + bool IsHalfSampleRate() const { return sample_rate == 1; } + u32 GetSampleRate() const { return sample_rate == 1 ? 18900 : 37800; } + u32 GetBitsPerSample() const { return bits_per_sample == 1 ? 8 : 4; } + u32 GetSamplesPerSector() const + { + return bits_per_sample == 1 ? XA_ADPCM_SAMPLES_PER_SECTOR_8BIT : XA_ADPCM_SAMPLES_PER_SECTOR_4BIT; + } + } codinginfo; +}; + +union XA_ADPCMBlockHeader +{ + u8 bits; + + BitField shift; + BitField filter; + + // For both 4bit and 8bit ADPCM, reserved shift values 13..15 will act same as shift=9). + u8 GetShift() const + { + const u8 shift_value = shift; + return (shift_value > 12) ? 9 : shift_value; + } + + u8 GetFilter() const { return filter; } +}; +static_assert(sizeof(XA_ADPCMBlockHeader) == 1, "XA-ADPCM block header is one byte"); + +// Decodes XA-ADPCM samples in an audio sector. Stereo samples are interleaved with left first. +void DecodeADPCMSector(const void* data, s16* samples, s32* last_samples); + +} // namespace CDXA \ No newline at end of file diff --git a/jni/common/common.vcxproj b/jni/common/common.vcxproj new file mode 100644 index 0000000..78ce6ed --- /dev/null +++ b/jni/common/common.vcxproj @@ -0,0 +1,750 @@ + + + + + DebugFast + ARM64 + + + DebugFast + Win32 + + + DebugFast + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseLTCG + ARM64 + + + ReleaseLTCG + Win32 + + + ReleaseLTCG + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {43540154-9e1e-409c-834f-b84be5621388} + + + {7f909e29-4808-4bd9-a60c-56c51a3aaec2} + + + {425d6c99-d1c8-43c2-b8ac-4d7b1d941017} + + + {6a4208ed-e3dc-41e1-81cd-f61025fc285a} + + + + {EE054E08-3799-4A59-A422-18259C105FFD} + Win32Proj + common + 10.0 + + + + StaticLibrary + true + NotSet + v142 + + + StaticLibrary + true + NotSet + v142 + + + StaticLibrary + true + NotSet + v142 + + + StaticLibrary + true + NotSet + v142 + + + StaticLibrary + true + NotSet + v142 + + + StaticLibrary + true + NotSet + v142 + + + StaticLibrary + false + true + NotSet + v142 + false + + + StaticLibrary + false + true + NotSet + v142 + false + + + StaticLibrary + false + true + NotSet + v142 + false + + + StaticLibrary + false + true + NotSet + v142 + false + + + StaticLibrary + false + true + NotSet + v142 + false + + + StaticLibrary + false + true + NotSet + v142 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + true + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + true + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + true + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + true + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + true + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + false + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + false + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + false + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + false + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + false + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + false + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + stdcpp17 + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + _ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + Default + false + true + stdcpp17 + OnlyExplicitInline + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + stdcpp17 + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + stdcpp17 + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + _ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + Default + false + true + stdcpp17 + OnlyExplicitInline + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + _ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + Default + false + true + stdcpp17 + OnlyExplicitInline + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + stdcpp17 + false + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + true + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + true + stdcpp17 + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + true + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + stdcpp17 + false + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + true + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + stdcpp17 + false + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + true + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + true + stdcpp17 + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + true + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + $(SolutionDir)dep\glad\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + true + stdcpp17 + true + $(IntDir)/%(RelativeDir)/ + /Zo /utf-8 %(AdditionalOptions) + + + Console + true + true + true + SDL2.lib;%(AdditionalDependencies) + $(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories) + + + + d3dcompiler.lib;%(AdditionalDependencies) + + + + + + + diff --git a/jni/common/common.vcxproj.filters b/jni/common/common.vcxproj.filters new file mode 100644 index 0000000..0480112 --- /dev/null +++ b/jni/common/common.vcxproj.filters @@ -0,0 +1,218 @@ + + + + + + + + + + + + + gl + + + gl + + + gl + + + d3d11 + + + d3d11 + + + d3d11 + + + d3d11 + + + + + + + + + + + + + + + + + + + d3d11 + + + + + + gl + + + + + gl + + + gl + + + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + + + vulkan + + + vulkan + + + vulkan + + + + + + + + + + + + + + + + + + + gl + + + gl + + + gl + + + d3d11 + + + d3d11 + + + d3d11 + + + d3d11 + + + + + + + + + + + + + + + d3d11 + + + + + + gl + + + + gl + + + gl + + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + vulkan + + + + + + + + + + + + + + + {52487c57-753d-4888-ba26-ed63ab51a234} + + + {30251086-81f3-44f5-add4-7ff9a24098ab} + + + {642ff5eb-af39-4aab-a42f-6eb8188a11d7} + + + diff --git a/jni/common/cpu_detect.h b/jni/common/cpu_detect.h new file mode 100644 index 0000000..5c3c278 --- /dev/null +++ b/jni/common/cpu_detect.h @@ -0,0 +1,35 @@ +#pragma once + +#if defined(_MSC_VER) + +#if defined(_M_X64) +#define CPU_X64 1 +#elif defined(_M_IX86) +#define CPU_X86 1 +#elif defined(_M_ARM64) +#define CPU_AARCH64 1 +#elif defined(_M_ARM) +#define CPU_AARCH32 1 +#else +#error Unknown architecture. +#endif + +#elif defined(__GNUC__) || defined(__clang__) + +#if defined(__x86_64__) +#define CPU_X64 1 +#elif defined(__i386__) +#define CPU_X86 1 +#elif defined(__aarch64__) +#define CPU_AARCH64 1 +#elif defined(__arm__) +#define CPU_AARCH32 1 +#else +#error Unknown architecture. +#endif + +#else + +#error Unknown compiler. + +#endif diff --git a/jni/common/d3d11/shader_cache.cpp b/jni/common/d3d11/shader_cache.cpp new file mode 100644 index 0000000..1c293a0 --- /dev/null +++ b/jni/common/d3d11/shader_cache.cpp @@ -0,0 +1,300 @@ +#include "shader_cache.h" +#include "../file_system.h" +#include "../log.h" +#include "../md5_digest.h" +#include "shader_compiler.h" +#include +Log_SetChannel(D3D11::ShaderCache); + +namespace D3D11 { + +#pragma pack(push, 1) +struct CacheIndexEntry +{ + u64 source_hash_low; + u64 source_hash_high; + u32 source_length; + u32 shader_type; + u32 file_offset; + u32 blob_size; +}; +#pragma pack(pop) + +ShaderCache::ShaderCache() = default; + +ShaderCache::~ShaderCache() +{ + if (m_index_file) + std::fclose(m_index_file); + if (m_blob_file) + std::fclose(m_blob_file); +} + +bool ShaderCache::CacheIndexKey::operator==(const CacheIndexKey& key) const +{ + return (source_hash_low == key.source_hash_low && source_hash_high == key.source_hash_high && + source_length == key.source_length && shader_type == key.shader_type); +} + +bool ShaderCache::CacheIndexKey::operator!=(const CacheIndexKey& key) const +{ + return (source_hash_low != key.source_hash_low || source_hash_high != key.source_hash_high || + source_length != key.source_length || shader_type != key.shader_type); +} + +void ShaderCache::Open(std::string_view base_path, D3D_FEATURE_LEVEL feature_level, bool debug) +{ + m_feature_level = feature_level; + m_debug = debug; + + if (!base_path.empty()) + { + const std::string base_filename = GetCacheBaseFileName(base_path, feature_level, debug); + const std::string index_filename = base_filename + ".idx"; + const std::string blob_filename = base_filename + ".bin"; + + if (!ReadExisting(index_filename, blob_filename)) + CreateNew(index_filename, blob_filename); + } +} + +bool ShaderCache::CreateNew(const std::string& index_filename, const std::string& blob_filename) +{ + if (FileSystem::FileExists(index_filename.c_str())) + { + Log_WarningPrintf("Removing existing index file '%s'", index_filename.c_str()); + FileSystem::DeleteFile(index_filename.c_str()); + } + if (FileSystem::FileExists(blob_filename.c_str())) + { + Log_WarningPrintf("Removing existing blob file '%s'", blob_filename.c_str()); + FileSystem::DeleteFile(blob_filename.c_str()); + } + + m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "wb"); + if (!m_index_file) + { + Log_ErrorPrintf("Failed to open index file '%s' for writing", index_filename.c_str()); + return false; + } + + const u32 index_version = FILE_VERSION; + if (std::fwrite(&index_version, sizeof(index_version), 1, m_index_file) != 1) + { + Log_ErrorPrintf("Failed to write version to index file '%s'", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + FileSystem::DeleteFile(index_filename.c_str()); + return false; + } + + m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "w+b"); + if (!m_blob_file) + { + Log_ErrorPrintf("Failed to open blob file '%s' for writing", blob_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + FileSystem::DeleteFile(index_filename.c_str()); + return false; + } + + return true; +} + +bool ShaderCache::ReadExisting(const std::string& index_filename, const std::string& blob_filename) +{ + m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "r+b"); + if (!m_index_file) + return false; + + u32 file_version; + if (std::fread(&file_version, sizeof(file_version), 1, m_index_file) != 1 || file_version != FILE_VERSION) + { + Log_ErrorPrintf("Bad file version in '%s'", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "a+b"); + if (!m_blob_file) + { + Log_ErrorPrintf("Blob file '%s' is missing", blob_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + std::fseek(m_blob_file, 0, SEEK_END); + const u32 blob_file_size = static_cast(std::ftell(m_blob_file)); + + for (;;) + { + CacheIndexEntry entry; + if (std::fread(&entry, sizeof(entry), 1, m_index_file) != 1 || + (entry.file_offset + entry.blob_size) > blob_file_size) + { + if (std::feof(m_index_file)) + break; + + Log_ErrorPrintf("Failed to read entry from '%s', corrupt file?", index_filename.c_str()); + m_index.clear(); + std::fclose(m_blob_file); + m_blob_file = nullptr; + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + const CacheIndexKey key{entry.source_hash_low, entry.source_hash_high, entry.source_length, + static_cast(entry.shader_type)}; + const CacheIndexData data{entry.file_offset, entry.blob_size}; + m_index.emplace(key, data); + } + + // ensure we don't write before seeking + std::fseek(m_index_file, 0, SEEK_END); + + Log_InfoPrintf("Read %zu entries from '%s'", m_index.size(), index_filename.c_str()); + return true; +} + +std::string ShaderCache::GetCacheBaseFileName(const std::string_view& base_path, D3D_FEATURE_LEVEL feature_level, + bool debug) +{ + std::string base_filename(base_path); + base_filename += "d3d_shaders_"; + + switch (feature_level) + { + case D3D_FEATURE_LEVEL_10_0: + base_filename += "sm40"; + break; + case D3D_FEATURE_LEVEL_10_1: + base_filename += "sm41"; + break; + case D3D_FEATURE_LEVEL_11_0: + base_filename += "sm50"; + break; + default: + base_filename += "unk"; + break; + } + + if (debug) + base_filename += "_debug"; + + return base_filename; +} + +ShaderCache::CacheIndexKey ShaderCache::GetCacheKey(ShaderCompiler::Type type, const std::string_view& shader_code) +{ + union + { + struct + { + u64 hash_low; + u64 hash_high; + }; + u8 hash[16]; + }; + + MD5Digest digest; + digest.Update(shader_code.data(), static_cast(shader_code.length())); + digest.Final(hash); + + return CacheIndexKey{hash_low, hash_high, static_cast(shader_code.length()), type}; +} + +ShaderCache::ComPtr ShaderCache::GetShaderBlob(ShaderCompiler::Type type, std::string_view shader_code) +{ + const auto key = GetCacheKey(type, shader_code); + auto iter = m_index.find(key); + if (iter == m_index.end()) + return CompileAndAddShaderBlob(key, shader_code); + + ComPtr blob; + HRESULT hr = D3DCreateBlob(iter->second.blob_size, blob.GetAddressOf()); + if (FAILED(hr) || std::fseek(m_blob_file, iter->second.file_offset, SEEK_SET) != 0 || + std::fread(blob->GetBufferPointer(), 1, iter->second.blob_size, m_blob_file) != iter->second.blob_size) + { + Log_ErrorPrintf("Read blob from file failed"); + return {}; + } + + return blob; +} + +ShaderCache::ComPtr ShaderCache::GetVertexShader(ID3D11Device* device, std::string_view shader_code) +{ + ComPtr blob = GetShaderBlob(ShaderCompiler::Type::Vertex, std::move(shader_code)); + if (!blob) + return {}; + + return D3D11::ShaderCompiler::CreateVertexShader(device, blob.Get()); +} + +ShaderCache::ComPtr ShaderCache::GetGeometryShader(ID3D11Device* device, + std::string_view shader_code) +{ + ComPtr blob = GetShaderBlob(ShaderCompiler::Type::Geometry, std::move(shader_code)); + if (!blob) + return {}; + + return D3D11::ShaderCompiler::CreateGeometryShader(device, blob.Get()); +} + +ShaderCache::ComPtr ShaderCache::GetPixelShader(ID3D11Device* device, std::string_view shader_code) +{ + ComPtr blob = GetShaderBlob(ShaderCompiler::Type::Pixel, std::move(shader_code)); + if (!blob) + return {}; + + return D3D11::ShaderCompiler::CreatePixelShader(device, blob.Get()); +} + +ShaderCache::ComPtr ShaderCache::GetComputeShader(ID3D11Device* device, + std::string_view shader_code) +{ + ComPtr blob = GetShaderBlob(ShaderCompiler::Type::Compute, std::move(shader_code)); + if (!blob) + return {}; + + return D3D11::ShaderCompiler::CreateComputeShader(device, blob.Get()); +} + +ShaderCache::ComPtr ShaderCache::CompileAndAddShaderBlob(const CacheIndexKey& key, + std::string_view shader_code) +{ + ComPtr blob = ShaderCompiler::CompileShader(key.shader_type, m_feature_level, shader_code, m_debug); + if (!blob) + return {}; + + if (!m_blob_file || std::fseek(m_blob_file, 0, SEEK_END) != 0) + return blob; + + CacheIndexData data; + data.file_offset = static_cast(std::ftell(m_blob_file)); + data.blob_size = static_cast(blob->GetBufferSize()); + + CacheIndexEntry entry = {}; + entry.source_hash_low = key.source_hash_low; + entry.source_hash_high = key.source_hash_high; + entry.source_length = key.source_length; + entry.shader_type = static_cast(key.shader_type); + entry.blob_size = data.blob_size; + entry.file_offset = data.file_offset; + + if (std::fwrite(blob->GetBufferPointer(), 1, entry.blob_size, m_blob_file) != entry.blob_size || + std::fflush(m_blob_file) != 0 || std::fwrite(&entry, sizeof(entry), 1, m_index_file) != 1 || + std::fflush(m_index_file) != 0) + { + Log_ErrorPrintf("Failed to write shader blob to file"); + return blob; + } + + m_index.emplace(key, data); + return blob; +} + +} // namespace D3D11 diff --git a/jni/common/d3d11/shader_cache.h b/jni/common/d3d11/shader_cache.h new file mode 100644 index 0000000..d658fc9 --- /dev/null +++ b/jni/common/d3d11/shader_cache.h @@ -0,0 +1,83 @@ +#pragma once +#include "../hash_combine.h" +#include "../types.h" +#include "../windows_headers.h" +#include "shader_compiler.h" +#include +#include +#include +#include +#include +#include + +namespace D3D11 { + +class ShaderCache +{ +public: + template + using ComPtr = Microsoft::WRL::ComPtr; + + ShaderCache(); + ~ShaderCache(); + + void Open(std::string_view base_path, D3D_FEATURE_LEVEL feature_level, bool debug); + + ComPtr GetShaderBlob(ShaderCompiler::Type type, std::string_view shader_code); + + ComPtr GetVertexShader(ID3D11Device* device, std::string_view shader_code); + ComPtr GetGeometryShader(ID3D11Device* device, std::string_view shader_code); + ComPtr GetPixelShader(ID3D11Device* device, std::string_view shader_code); + ComPtr GetComputeShader(ID3D11Device* device, std::string_view shader_code); + +private: + static constexpr u32 FILE_VERSION = 1; + + struct CacheIndexKey + { + u64 source_hash_low; + u64 source_hash_high; + u32 source_length; + ShaderCompiler::Type shader_type; + + bool operator==(const CacheIndexKey& key) const; + bool operator!=(const CacheIndexKey& key) const; + }; + + struct CacheIndexEntryHasher + { + std::size_t operator()(const CacheIndexKey& e) const noexcept + { + std::size_t h = 0; + hash_combine(h, e.source_hash_low, e.source_hash_high, e.source_length, e.shader_type); + return h; + } + }; + + struct CacheIndexData + { + u32 file_offset; + u32 blob_size; + }; + + using CacheIndex = std::unordered_map; + + static std::string GetCacheBaseFileName(const std::string_view& base_path, D3D_FEATURE_LEVEL feature_level, bool debug); + static CacheIndexKey GetCacheKey(ShaderCompiler::Type type, const std::string_view& shader_code); + + bool CreateNew(const std::string& index_filename, const std::string& blob_filename); + bool ReadExisting(const std::string& index_filename, const std::string& blob_filename); + void Close(); + + ComPtr CompileAndAddShaderBlob(const CacheIndexKey& key, std::string_view shader_code); + + std::FILE* m_index_file = nullptr; + std::FILE* m_blob_file = nullptr; + + CacheIndex m_index; + + D3D_FEATURE_LEVEL m_feature_level = D3D_FEATURE_LEVEL_11_0; + bool m_debug = false; +}; + +} // namespace D3D11 diff --git a/jni/common/d3d11/shader_compiler.cpp b/jni/common/d3d11/shader_compiler.cpp new file mode 100644 index 0000000..3f225b3 --- /dev/null +++ b/jni/common/d3d11/shader_compiler.cpp @@ -0,0 +1,199 @@ +#include "shader_compiler.h" +#include "../log.h" +#include "../string_util.h" +#include +#include +#include +Log_SetChannel(D3D11); + +namespace D3D11::ShaderCompiler { + +static unsigned s_next_bad_shader_id = 1; + +ComPtr CompileShader(Type type, D3D_FEATURE_LEVEL feature_level, std::string_view code, bool debug) +{ + const char* target; + switch (feature_level) + { + case D3D_FEATURE_LEVEL_10_0: + { + static constexpr std::array targets = {{"vs_4_0", "gs_4_0", "ps_4_0", "cs_4_0"}}; + target = targets[static_cast(type)]; + } + break; + + case D3D_FEATURE_LEVEL_10_1: + { + static constexpr std::array targets = {{"vs_4_1", "gs_4_1", "ps_4_1", "cs_4_1"}}; + target = targets[static_cast(type)]; + } + break; + + case D3D_FEATURE_LEVEL_11_0: + { + static constexpr std::array targets = {{"vs_5_0", "gs_5_0", "ps_5_0", "cs_5_0"}}; + target = targets[static_cast(type)]; + } + break; + + case D3D_FEATURE_LEVEL_11_1: + default: + { + static constexpr std::array targets = {{"vs_5_1", "gs_5_1", "ps_5_1", "cs_5_1"}}; + target = targets[static_cast(type)]; + } + break; + } + + static constexpr UINT flags_non_debug = D3DCOMPILE_OPTIMIZATION_LEVEL3; + static constexpr UINT flags_debug = D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_DEBUG; + + ComPtr blob; + ComPtr error_blob; + const HRESULT hr = + D3DCompile(code.data(), code.size(), "0", nullptr, nullptr, "main", target, debug ? flags_debug : flags_non_debug, + 0, blob.GetAddressOf(), error_blob.GetAddressOf()); + + std::string error_string; + if (error_blob) + { + error_string.append(static_cast(error_blob->GetBufferPointer()), error_blob->GetBufferSize()); + error_blob.Reset(); + } + + if (FAILED(hr)) + { + Log_ErrorPrintf("Failed to compile '%s':\n%s", target, error_string.c_str()); + + std::ofstream ofs(StringUtil::StdStringFromFormat("bad_shader_%u.txt", s_next_bad_shader_id++).c_str(), + std::ofstream::out | std::ofstream::binary); + if (ofs.is_open()) + { + ofs << code; + ofs << "\n\nCompile as " << target << " failed: " << hr << "\n"; + ofs.write(error_string.c_str(), error_string.size()); + ofs.close(); + } + + return {}; + } + + if (!error_string.empty()) + Log_WarningPrintf("'%s' compiled with warnings:\n%s", target, error_string.c_str()); + + return blob; +} + +ComPtr CompileAndCreateVertexShader(ID3D11Device* device, std::string_view code, bool debug) +{ + ComPtr blob = CompileShader(Type::Vertex, device->GetFeatureLevel(), std::move(code), debug); + if (!blob) + return {}; + + return CreateVertexShader(device, blob.Get()); +} + +ComPtr CompileAndCreateGeometryShader(ID3D11Device* device, std::string_view code, bool debug) +{ + ComPtr blob = CompileShader(Type::Geometry, device->GetFeatureLevel(), std::move(code), debug); + if (!blob) + return {}; + + return CreateGeometryShader(device, blob.Get()); +} + +ComPtr CompileAndCreatePixelShader(ID3D11Device* device, std::string_view code, bool debug) +{ + ComPtr blob = CompileShader(Type::Pixel, device->GetFeatureLevel(), std::move(code), debug); + if (!blob) + return {}; + + return CreatePixelShader(device, blob.Get()); +} + +ComPtr CompileAndCreateComputeShader(ID3D11Device* device, std::string_view code, bool debug) +{ + ComPtr blob = CompileShader(Type::Compute, device->GetFeatureLevel(), std::move(code), debug); + if (!blob) + return {}; + + return CreateComputeShader(device, blob.Get()); +} + +ComPtr CreateVertexShader(ID3D11Device* device, const void* bytecode, size_t bytecode_length) +{ + ComPtr shader; + const HRESULT hr = device->CreateVertexShader(bytecode, bytecode_length, nullptr, shader.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Failed to create vertex shader: 0x%08X", hr); + return {}; + } + + return shader; +} + +ComPtr CreateVertexShader(ID3D11Device* device, const ID3DBlob* blob) +{ + return CreateVertexShader(device, const_cast(blob)->GetBufferPointer(), + const_cast(blob)->GetBufferSize()); +} + +ComPtr CreateGeometryShader(ID3D11Device* device, const void* bytecode, size_t bytecode_length) +{ + ComPtr shader; + const HRESULT hr = device->CreateGeometryShader(bytecode, bytecode_length, nullptr, shader.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Failed to create geometry shader: 0x%08X", hr); + return {}; + } + + return shader; +} + +ComPtr CreateGeometryShader(ID3D11Device* device, const ID3DBlob* blob) +{ + return CreateGeometryShader(device, const_cast(blob)->GetBufferPointer(), + const_cast(blob)->GetBufferSize()); +} + +ComPtr CreatePixelShader(ID3D11Device* device, const void* bytecode, size_t bytecode_length) +{ + ComPtr shader; + const HRESULT hr = device->CreatePixelShader(bytecode, bytecode_length, nullptr, shader.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Failed to create pixel shader: 0x%08X", hr); + return {}; + } + + return shader; +} + +ComPtr CreatePixelShader(ID3D11Device* device, const ID3DBlob* blob) +{ + return CreatePixelShader(device, const_cast(blob)->GetBufferPointer(), + const_cast(blob)->GetBufferSize()); +} + +ComPtr CreateComputeShader(ID3D11Device* device, const void* bytecode, size_t bytecode_length) +{ + ComPtr shader; + const HRESULT hr = device->CreateComputeShader(bytecode, bytecode_length, nullptr, shader.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Failed to create compute shader: 0x%08X", hr); + return {}; + } + + return shader; +} + +ComPtr CreateComputeShader(ID3D11Device* device, const ID3DBlob* blob) +{ + return CreateComputeShader(device, const_cast(blob)->GetBufferPointer(), + const_cast(blob)->GetBufferSize()); +} + +} // namespace D3D11::ShaderCompiler \ No newline at end of file diff --git a/jni/common/d3d11/shader_compiler.h b/jni/common/d3d11/shader_compiler.h new file mode 100644 index 0000000..f5e23da --- /dev/null +++ b/jni/common/d3d11/shader_compiler.h @@ -0,0 +1,36 @@ +#pragma once +#include "../windows_headers.h" +#include +#include +#include +#include + +namespace D3D11::ShaderCompiler { +template +using ComPtr = Microsoft::WRL::ComPtr; + +enum class Type +{ + Vertex, + Geometry, + Pixel, + Compute +}; + +ComPtr CompileShader(Type type, D3D_FEATURE_LEVEL feature_level, std::string_view code, bool debug); + +ComPtr CompileAndCreateVertexShader(ID3D11Device* device, std::string_view code, bool debug); +ComPtr CompileAndCreateGeometryShader(ID3D11Device* device, std::string_view code, bool debug); +ComPtr CompileAndCreatePixelShader(ID3D11Device* device, std::string_view code, bool debug); +ComPtr CompileAndCreateComputeShader(ID3D11Device* device, std::string_view code, bool debug); + +ComPtr CreateVertexShader(ID3D11Device* device, const void* bytecode, size_t bytecode_length); +ComPtr CreateVertexShader(ID3D11Device* device, const ID3DBlob* blob); +ComPtr CreateGeometryShader(ID3D11Device* device, const void* bytecode, size_t bytecode_length); +ComPtr CreateGeometryShader(ID3D11Device* device, const ID3DBlob* blob); +ComPtr CreatePixelShader(ID3D11Device* device, const void* bytecode, size_t bytecode_length); +ComPtr CreatePixelShader(ID3D11Device* device, const ID3DBlob* blob); +ComPtr CreateComputeShader(ID3D11Device* device, const void* bytecode, size_t bytecode_length); +ComPtr CreateComputeShader(ID3D11Device* device, const ID3DBlob* blob); + +}; // namespace D3D11::ShaderCompiler diff --git a/jni/common/d3d11/staging_texture.cpp b/jni/common/d3d11/staging_texture.cpp new file mode 100644 index 0000000..a90a3f3 --- /dev/null +++ b/jni/common/d3d11/staging_texture.cpp @@ -0,0 +1,117 @@ +#include "staging_texture.h" +#include "../assert.h" +#include "../log.h" +Log_SetChannel(D3D11); + +namespace D3D11 { + +StagingTexture::StagingTexture() : m_width(0), m_height(0) {} + +StagingTexture::~StagingTexture() +{ + Destroy(); +} + +bool StagingTexture::Create(ID3D11Device* device, u32 width, u32 height, DXGI_FORMAT format, bool for_uploading) +{ + CD3D11_TEXTURE2D_DESC desc(format, width, height, 1, 1, 0, for_uploading ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_STAGING, + for_uploading ? D3D11_CPU_ACCESS_WRITE : D3D11_CPU_ACCESS_READ, 1, 0, 0); + + ComPtr texture; + const HRESULT tex_hr = device->CreateTexture2D(&desc, nullptr, texture.GetAddressOf()); + if (FAILED(tex_hr)) + { + Log_ErrorPrintf("Create texture failed: 0x%08X", tex_hr); + return false; + } + + m_texture = std::move(texture); + m_width = desc.Width; + m_height = desc.Height; + m_format = desc.Format; + return true; +} + +void StagingTexture::Destroy() +{ + Assert(!IsMapped()); + m_texture.Reset(); +} + +bool StagingTexture::Map(ID3D11DeviceContext* context, bool writing) +{ + Assert(!IsMapped()); + const HRESULT hr = context->Map(m_texture.Get(), 0, writing ? D3D11_MAP_WRITE : D3D11_MAP_READ, 0, &m_map); + if (FAILED(hr)) + { + Log_ErrorPrintf("Map staging texture failed: 0x%08X", hr); + return false; + } + + return true; +} + +void StagingTexture::Unmap(ID3D11DeviceContext* context) +{ + Assert(IsMapped()); + context->Unmap(m_texture.Get(), 0); + m_map = {}; +} + +void StagingTexture::CopyToTexture(ID3D11DeviceContext* context, u32 src_x, u32 src_y, ID3D11Resource* dst_texture, + u32 dst_subresource, u32 dst_x, u32 dst_y, u32 width, u32 height) +{ + DebugAssert((src_x + width) <= m_width && (src_y + height) <= m_height); + + const CD3D11_BOX box(static_cast(src_x), static_cast(src_y), 0, static_cast(src_x + width), + static_cast(src_y + height), 1); + context->CopySubresourceRegion(dst_texture, dst_subresource, dst_x, dst_y, 0, m_texture.Get(), 0, &box); +} + +void StagingTexture::CopyFromTexture(ID3D11DeviceContext* context, ID3D11Resource* src_texture, u32 src_subresource, + u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) +{ + DebugAssert((dst_x + width) <= m_width && (dst_y + height) <= m_height); + + const CD3D11_BOX box(static_cast(src_x), static_cast(src_y), 0, static_cast(src_x + width), + static_cast(src_y + height), 1); + context->CopySubresourceRegion(m_texture.Get(), 0, dst_x, dst_y, 0, src_texture, src_subresource, &box); +} + +bool AutoStagingTexture::EnsureSize(ID3D11DeviceContext* context, u32 width, u32 height, DXGI_FORMAT format, + bool for_uploading) +{ + if (m_texture && m_width >= width && m_height >= height && m_format == format) + return true; + + ComPtr device; + context->GetDevice(device.GetAddressOf()); + + CD3D11_TEXTURE2D_DESC new_desc(format, width, height, 1, 1, 0, + for_uploading ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_STAGING, + for_uploading ? D3D11_CPU_ACCESS_WRITE : D3D11_CPU_ACCESS_READ, 1, 0, 0); + ComPtr new_texture; + HRESULT hr = device->CreateTexture2D(&new_desc, nullptr, new_texture.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Create texture failed: 0x%08X", hr); + return false; + } + + m_texture = std::move(new_texture); + m_width = width; + m_height = height; + m_format = format; + return true; +} + +void AutoStagingTexture::CopyFromTexture(ID3D11DeviceContext* context, ID3D11Resource* src_texture, u32 src_subresource, + u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) +{ + if (!EnsureSize(context, width, height, m_format, false)) + return; + + StagingTexture::CopyFromTexture(context, src_texture, src_subresource, src_x, src_y, dst_x, dst_y, width, height); +} + +} // namespace D3D11 \ No newline at end of file diff --git a/jni/common/d3d11/staging_texture.h b/jni/common/d3d11/staging_texture.h new file mode 100644 index 0000000..04a77c1 --- /dev/null +++ b/jni/common/d3d11/staging_texture.h @@ -0,0 +1,156 @@ +#pragma once +#include "../types.h" +#include "../windows_headers.h" +#include +#include +#include + +namespace D3D11 { +class StagingTexture +{ +public: + template + using ComPtr = Microsoft::WRL::ComPtr; + + StagingTexture(); + ~StagingTexture(); + + ALWAYS_INLINE ID3D11Texture2D* GetD3DTexture() const { return m_texture.Get(); } + + ALWAYS_INLINE u32 GetWidth() const { return m_width; } + ALWAYS_INLINE u32 GetHeight() const { return m_height; } + ALWAYS_INLINE DXGI_FORMAT GetFormat() const { return m_format; } + ALWAYS_INLINE bool IsMapped() const { return m_map.pData != nullptr; } + ALWAYS_INLINE const D3D11_MAPPED_SUBRESOURCE& GetMappedSubresource() const { return m_map; } + + ALWAYS_INLINE operator bool() const { return static_cast(m_texture); } + + bool Create(ID3D11Device* device, u32 width, u32 height, DXGI_FORMAT format, bool for_uploading); + void Destroy(); + + bool Map(ID3D11DeviceContext* context, bool writing); + void Unmap(ID3D11DeviceContext* context); + + void CopyToTexture(ID3D11DeviceContext* context, u32 src_x, u32 src_y, ID3D11Resource* dst_texture, + u32 dst_subresource, u32 dst_x, u32 dst_y, u32 width, u32 height); + void CopyFromTexture(ID3D11DeviceContext* context, ID3D11Resource* src_texture, u32 src_subresource, u32 src_x, + u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height); + + template + T ReadPixel(u32 x, u32 y) + { + T pixel; + std::memcpy(&pixel, static_cast(m_map.pData) + (y * m_map.RowPitch) + x, sizeof(T)); + return pixel; + } + + template + void WritePixel(u32 x, u32 y, T pixel) + { + std::memcpy(static_cast(m_map.pData) + (y * m_map.RowPitch) + x, &pixel, sizeof(T)); + } + + template + void ReadPixels(u32 x, u32 y, u32 width, u32 height, u32 stride, T* data) + { + const u8* src_ptr = static_cast(m_map.pData) + (y * m_map.RowPitch) + (x * sizeof(T)); + u8* dst_ptr = reinterpret_cast(data); + if (m_map.RowPitch != (sizeof(T) * stride)) + { + for (u32 row = 0; row < height; row++) + { + std::memcpy(dst_ptr, src_ptr, sizeof(T) * width); + src_ptr += m_map.RowPitch; + dst_ptr += sizeof(T) * stride; + } + } + else + { + std::memcpy(dst_ptr, src_ptr, (sizeof(T) * stride) * height); + } + } + + template + bool ReadPixels(ID3D11DeviceContext* context, u32 x, u32 y, u32 width, u32 height, u32 stride, T* data) + { + const bool was_mapped = IsMapped(); + if (!was_mapped && !Map(context, false)) + return false; + + ReadPixels(x, y, width, height, stride, data); + if (!was_mapped) + Unmap(context); + + return true; + } + + template + void WritePixels(u32 x, u32 y, u32 width, u32 height, u32 stride, const T* data) + { + const u8* src_ptr = reinterpret_cast(data); + u8* dst_ptr = static_cast(m_map.pData) + (y * m_map.RowPitch) + (x * sizeof(T)); + if (m_map.RowPitch != (sizeof(T) * stride)) + { + for (u32 row = 0; row < height; row++) + { + std::memcpy(dst_ptr, src_ptr, sizeof(T) * width); + src_ptr += sizeof(T) * stride; + dst_ptr += m_map.RowPitch; + } + } + else + { + std::memcpy(dst_ptr, src_ptr, (sizeof(T) * stride) * height); + } + } + + template + bool WritePixels(ID3D11DeviceContext* context, u32 x, u32 y, u32 width, u32 height, u32 stride, const T* data) + { + const bool was_mapped = IsMapped(); + if (!was_mapped && !Map(context, true)) + return false; + + WritePixels(context, x, y, width, height, stride, data); + if (!was_mapped) + Unmap(context); + + return true; + } + +protected: + ComPtr m_texture; + u32 m_width; + u32 m_height; + DXGI_FORMAT m_format; + + D3D11_MAPPED_SUBRESOURCE m_map = {}; +}; + +class AutoStagingTexture : public StagingTexture +{ +public: + bool EnsureSize(ID3D11DeviceContext* context, u32 width, u32 height, DXGI_FORMAT format, bool for_uploading); + + void CopyFromTexture(ID3D11DeviceContext* context, ID3D11Resource* src_texture, u32 src_subresource, u32 src_x, + u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height); + + template + bool WritePixels(ID3D11DeviceContext* context, u32 x, u32 y, u32 width, u32 height, u32 stride, const T* data) + { + if (!EnsureSize(context, width, height, m_format, true)) + return false; + + const bool was_mapped = IsMapped(); + if (!was_mapped && !Map(context, true)) + return false; + + WritePixels(context, x, y, width, height, stride, data); + + if (!was_mapped) + Unmap(context); + + return true; + } +}; +} // namespace D3D11 \ No newline at end of file diff --git a/jni/common/d3d11/stream_buffer.cpp b/jni/common/d3d11/stream_buffer.cpp new file mode 100644 index 0000000..38fbc42 --- /dev/null +++ b/jni/common/d3d11/stream_buffer.cpp @@ -0,0 +1,110 @@ +#include "stream_buffer.h" +#include "../align.h" +#include "../assert.h" +#include "../log.h" +Log_SetChannel(D3D11); + +namespace D3D11 { + +StreamBuffer::StreamBuffer() : m_size(0), m_position(0) {} + +StreamBuffer::StreamBuffer(ComPtr buffer) : m_buffer(std::move(buffer)), m_position(0) +{ + D3D11_BUFFER_DESC desc; + m_buffer->GetDesc(&desc); + m_size = desc.ByteWidth; +} + +StreamBuffer::~StreamBuffer() +{ + Release(); +} + +bool StreamBuffer::Create(ID3D11Device* device, D3D11_BIND_FLAG bind_flags, u32 size) +{ + CD3D11_BUFFER_DESC desc(size, bind_flags, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE, 0, 0); + ComPtr buffer; + HRESULT hr = device->CreateBuffer(&desc, nullptr, &buffer); + if (FAILED(hr)) + { + Log_ErrorPrintf("Creating buffer failed: 0x%08X", hr); + return false; + } + + m_buffer = std::move(buffer); + m_size = size; + m_position = 0; + + D3D11_FEATURE_DATA_D3D11_OPTIONS options = {}; + hr = device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, &options, sizeof(options)); + if (SUCCEEDED(hr)) + { + if (bind_flags & D3D11_BIND_CONSTANT_BUFFER) + m_use_map_no_overwrite = options.MapNoOverwriteOnDynamicConstantBuffer; + else if (bind_flags & D3D11_BIND_SHADER_RESOURCE) + m_use_map_no_overwrite = options.MapNoOverwriteOnDynamicBufferSRV; + else + m_use_map_no_overwrite = true; + + if (!m_use_map_no_overwrite) + { + Log_WarningPrintf("Unable to use MAP_NO_OVERWRITE on buffer with bind flag %u, this may affect performance. " + "Update your driver/operating system.", + static_cast(bind_flags)); + } + } + else + { + Log_WarningPrintf("ID3D11Device::CheckFeatureSupport() failed: 0x%08X", hr); + m_use_map_no_overwrite = false; + } + + return true; +} + +void StreamBuffer::Adopt(ComPtr buffer) +{ + m_buffer = std::move(buffer); + + D3D11_BUFFER_DESC desc; + m_buffer->GetDesc(&desc); + m_size = desc.ByteWidth; + m_position = 0; +} + +void StreamBuffer::Release() +{ + m_buffer.Reset(); +} + +StreamBuffer::MappingResult StreamBuffer::Map(ID3D11DeviceContext* context, u32 alignment, u32 min_size) +{ + m_position = Common::AlignUp(m_position, alignment); + if ((m_position + min_size) >= m_size || !m_use_map_no_overwrite) + { + // wrap around + m_position = 0; + } + + D3D11_MAPPED_SUBRESOURCE sr; + const D3D11_MAP map_type = (m_position == 0) ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_WRITE_NO_OVERWRITE; + const HRESULT hr = context->Map(m_buffer.Get(), 0, map_type, 0, &sr); + if (FAILED(hr)) + { + Log_ErrorPrintf("Map failed: 0x%08X (alignment %u, minsize %u, size %u, position %u, map type %u)", hr, alignment, + min_size, m_size, m_position, static_cast(map_type)); + Panic("Map failed"); + return {}; + } + + return MappingResult{static_cast(sr.pData) + m_position, m_position, m_position / alignment, + (m_size - m_position) / alignment}; +} + +void StreamBuffer::Unmap(ID3D11DeviceContext* context, u32 used_size) +{ + context->Unmap(m_buffer.Get(), 0); + m_position += used_size; +} + +} // namespace D3D11 \ No newline at end of file diff --git a/jni/common/d3d11/stream_buffer.h b/jni/common/d3d11/stream_buffer.h new file mode 100644 index 0000000..2e46f18 --- /dev/null +++ b/jni/common/d3d11/stream_buffer.h @@ -0,0 +1,44 @@ +#pragma once +#include "../types.h" +#include "../windows_headers.h" +#include +#include + +namespace D3D11 { +class StreamBuffer +{ +public: + template + using ComPtr = Microsoft::WRL::ComPtr; + + StreamBuffer(); + StreamBuffer(ComPtr buffer); + ~StreamBuffer(); + + ALWAYS_INLINE ID3D11Buffer* GetD3DBuffer() const { return m_buffer.Get(); } + ALWAYS_INLINE ID3D11Buffer* const* GetD3DBufferArray() const { return m_buffer.GetAddressOf(); } + ALWAYS_INLINE u32 GetSize() const { return m_size; } + ALWAYS_INLINE u32 GetPosition() const { return m_position; } + + bool Create(ID3D11Device* device, D3D11_BIND_FLAG bind_flags, u32 size); + void Adopt(ComPtr buffer); + void Release(); + + struct MappingResult + { + void* pointer; + u32 buffer_offset; + u32 index_aligned; // offset / alignment, suitable for base vertex + u32 space_aligned; // remaining space / alignment + }; + + MappingResult Map(ID3D11DeviceContext* context, u32 alignment, u32 min_size); + void Unmap(ID3D11DeviceContext* context, u32 used_size); + +private: + ComPtr m_buffer; + u32 m_size; + u32 m_position; + bool m_use_map_no_overwrite = false; +}; +} // namespace GL \ No newline at end of file diff --git a/jni/common/d3d11/texture.cpp b/jni/common/d3d11/texture.cpp new file mode 100644 index 0000000..eaddb08 --- /dev/null +++ b/jni/common/d3d11/texture.cpp @@ -0,0 +1,140 @@ +#include "texture.h" +#include "../log.h" +Log_SetChannel(D3D11); + +namespace D3D11 { + +Texture::Texture() : m_width(0), m_height(0), m_samples(0) {} + +Texture::Texture(ComPtr texture, ComPtr srv, + ComPtr rtv) + : m_texture(std::move(texture)), m_srv(std::move(srv)), m_rtv(std::move(rtv)) +{ + const D3D11_TEXTURE2D_DESC desc = GetDesc(); + m_width = desc.Width; + m_height = desc.Height; + m_samples = desc.SampleDesc.Count; +} + +Texture::~Texture() +{ + Destroy(); +} + +D3D11_TEXTURE2D_DESC Texture::GetDesc() const +{ + D3D11_TEXTURE2D_DESC desc; + m_texture->GetDesc(&desc); + return desc; +} + +bool Texture::Create(ID3D11Device* device, u32 width, u32 height, u32 samples, DXGI_FORMAT format, u32 bind_flags, + const void* initial_data /* = nullptr */, u32 initial_data_stride /* = 0 */, bool dynamic) +{ + CD3D11_TEXTURE2D_DESC desc(format, width, height, 1, 1, bind_flags, + dynamic ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT, dynamic ? D3D11_CPU_ACCESS_WRITE : 0, + samples, 0, 0); + + D3D11_SUBRESOURCE_DATA srd; + srd.pSysMem = initial_data; + srd.SysMemPitch = initial_data_stride; + srd.SysMemSlicePitch = initial_data_stride * height; + + ComPtr texture; + const HRESULT tex_hr = device->CreateTexture2D(&desc, initial_data ? &srd : nullptr, texture.GetAddressOf()); + if (FAILED(tex_hr)) + { + Log_ErrorPrintf("Create texture failed: 0x%08X", tex_hr); + return false; + } + + ComPtr srv; + if (bind_flags & D3D11_BIND_SHADER_RESOURCE) + { + const D3D11_SRV_DIMENSION srv_dimension = + (desc.SampleDesc.Count > 1) ? D3D11_SRV_DIMENSION_TEXTURE2DMS : D3D11_SRV_DIMENSION_TEXTURE2D; + const CD3D11_SHADER_RESOURCE_VIEW_DESC srv_desc(srv_dimension, desc.Format, 0, desc.MipLevels, 0, desc.ArraySize); + const HRESULT hr = device->CreateShaderResourceView(texture.Get(), &srv_desc, srv.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Create SRV for adopted texture failed: 0x%08X", hr); + return false; + } + } + + ComPtr rtv; + if (bind_flags & D3D11_BIND_RENDER_TARGET) + { + const D3D11_RTV_DIMENSION rtv_dimension = + (desc.SampleDesc.Count > 1) ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D; + const CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(rtv_dimension, desc.Format, 0, 0, desc.ArraySize); + const HRESULT hr = device->CreateRenderTargetView(texture.Get(), &rtv_desc, rtv.GetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Create RTV for adopted texture failed: 0x%08X", hr); + return false; + } + } + + m_texture = std::move(texture); + m_srv = std::move(srv); + m_rtv = std::move(rtv); + m_width = desc.Width; + m_height = desc.Height; + m_samples = desc.SampleDesc.Count; + return true; +} + +bool Texture::Adopt(ID3D11Device* device, ComPtr texture) +{ + D3D11_TEXTURE2D_DESC desc; + texture->GetDesc(&desc); + + ComPtr srv; + if (desc.BindFlags & D3D11_BIND_SHADER_RESOURCE) + { + const D3D11_SRV_DIMENSION srv_dimension = + (desc.SampleDesc.Count > 1) ? D3D11_SRV_DIMENSION_TEXTURE2DMS : D3D11_SRV_DIMENSION_TEXTURE2D; + const CD3D11_SHADER_RESOURCE_VIEW_DESC srv_desc(srv_dimension, desc.Format, 0, desc.MipLevels, 0, desc.ArraySize); + const HRESULT hr = device->CreateShaderResourceView(texture.Get(), &srv_desc, srv.ReleaseAndGetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Create SRV for adopted texture failed: 0x%08X", hr); + return false; + } + } + + ComPtr rtv; + if (desc.BindFlags & D3D11_BIND_RENDER_TARGET) + { + const D3D11_RTV_DIMENSION rtv_dimension = + (desc.SampleDesc.Count > 1) ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D; + const CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(rtv_dimension, desc.Format, 0, 0, desc.ArraySize); + const HRESULT hr = device->CreateRenderTargetView(texture.Get(), &rtv_desc, rtv.ReleaseAndGetAddressOf()); + if (FAILED(hr)) + { + Log_ErrorPrintf("Create RTV for adopted texture failed: 0x%08X", hr); + return false; + } + } + + m_texture = std::move(texture); + m_srv = std::move(srv); + m_rtv = std::move(rtv); + m_width = desc.Width; + m_height = desc.Height; + m_samples = desc.SampleDesc.Count; + return true; +} + +void Texture::Destroy() +{ + m_rtv.Reset(); + m_srv.Reset(); + m_texture.Reset(); + m_width = 0; + m_height = 0; + m_samples = 0; +} + +} // namespace D3D11 \ No newline at end of file diff --git a/jni/common/d3d11/texture.h b/jni/common/d3d11/texture.h new file mode 100644 index 0000000..0fc6f01 --- /dev/null +++ b/jni/common/d3d11/texture.h @@ -0,0 +1,50 @@ +#pragma once +#include "../types.h" +#include "../windows_headers.h" +#include +#include + +namespace D3D11 { +class Texture +{ +public: + template + using ComPtr = Microsoft::WRL::ComPtr; + + Texture(); + Texture(ComPtr texture, ComPtr srv, ComPtr rtv); + ~Texture(); + + ALWAYS_INLINE ID3D11Texture2D* GetD3DTexture() const { return m_texture.Get(); } + ALWAYS_INLINE ID3D11ShaderResourceView* GetD3DSRV() const { return m_srv.Get(); } + ALWAYS_INLINE ID3D11RenderTargetView* GetD3DRTV() const { return m_rtv.Get(); } + ALWAYS_INLINE ID3D11ShaderResourceView* const* GetD3DSRVArray() const { return m_srv.GetAddressOf(); } + ALWAYS_INLINE ID3D11RenderTargetView* const* GetD3DRTVArray() const { return m_rtv.GetAddressOf(); } + + ALWAYS_INLINE u32 GetWidth() const { return m_width; } + ALWAYS_INLINE u32 GetHeight() const { return m_height; } + ALWAYS_INLINE u32 GetSamples() const { return m_samples; } + ALWAYS_INLINE bool IsMultisampled() const { return m_samples > 1; } + ALWAYS_INLINE DXGI_FORMAT GetFormat() const { return GetDesc().Format; } + D3D11_TEXTURE2D_DESC GetDesc() const; + + ALWAYS_INLINE operator ID3D11Texture2D*() const { return m_texture.Get(); } + ALWAYS_INLINE operator ID3D11ShaderResourceView*() const { return m_srv.Get(); } + ALWAYS_INLINE operator ID3D11RenderTargetView*() const { return m_rtv.Get(); } + ALWAYS_INLINE operator bool() const { return static_cast(m_texture); } + + bool Create(ID3D11Device* device, u32 width, u32 height, u32 samples, DXGI_FORMAT format, u32 bind_flags, + const void* initial_data = nullptr, u32 initial_data_stride = 0, bool dynamic = false); + bool Adopt(ID3D11Device* device, ComPtr texture); + + void Destroy(); + +private: + ComPtr m_texture; + ComPtr m_srv; + ComPtr m_rtv; + u32 m_width; + u32 m_height; + u32 m_samples; +}; +} // namespace D3D11 \ No newline at end of file diff --git a/jni/common/dimensional_array.h b/jni/common/dimensional_array.h new file mode 100644 index 0000000..ea3de5e --- /dev/null +++ b/jni/common/dimensional_array.h @@ -0,0 +1,98 @@ +// Sourced from https://github.com/BreadFish64/ScaleFish/blob/master/common/dimensional_array.hpp +// Copyright (c) 2020 BreadFish64 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include + +namespace detail { +template +struct DimensionalArrayExplicitRank; + +// Workaround for VC2017 +#if defined(_MSC_VER) && _MSC_VER < 1920 + +template +struct GetRankSize +{ + static constexpr std::size_t size_array[] = {sizes...}; + static constexpr std::size_t value = size_array[rank - 1]; +}; + +template +using GetArrayImplType = + std::array>, + GetRankSize::value>; + +#else + +template +constexpr std::size_t GetRankSize() +{ + constexpr std::size_t size_array[] = {sizes...}; + return size_array[rank - 1]; +} + +template +using GetArrayImplType = + std::array>, + GetRankSize()>; + +#endif + +template +struct DimensionalArrayExplicitRank : public GetArrayImplType +{ + static constexpr std::size_t rank = rank_param; + static_assert(rank > 0, "Attempted to create dimensional array with rank less than 1"); + using ArrayImplType = GetArrayImplType; + using ArrayImplType::ArrayImplType; + + template + void enumerate(const Callable& f) + { + for (auto& it : *this) + { + if constexpr (rank == 1) + f(it); + else + it.enumerate(f); + } + } + + template + void enumerate(const Callable& f) const + { + for (const auto& it : *this) + { + if constexpr (rank == 1) + f(it); + else + it.enumerate(f); + } + } +}; +} // namespace detail + +template +using DimensionalArray = detail::DimensionalArrayExplicitRank; diff --git a/jni/common/event.cpp b/jni/common/event.cpp new file mode 100644 index 0000000..07a5db3 --- /dev/null +++ b/jni/common/event.cpp @@ -0,0 +1,253 @@ +#include "event.h" +#include "assert.h" + +#if defined(WIN32) +#include "windows_headers.h" +#include +#elif defined(__linux__) || defined(__APPLE__) || defined(__HAIKU__) +#include +#include +#endif + +namespace Common { + +#if defined(WIN32) && defined(USE_WIN32_EVENT_OBJECTS) + +Event::Event(bool auto_reset /* = false */) +{ + m_event_handle = reinterpret_cast(CreateEvent(nullptr, auto_reset ? FALSE : TRUE, FALSE, nullptr)); + Assert(m_event_handle != nullptr); +} + +Event::~Event() +{ + CloseHandle(reinterpret_cast(m_event_handle)); +} + +void Event::Signal() +{ + SetEvent(reinterpret_cast(m_event_handle)); +} + +void Event::Wait() +{ + WaitForSingleObject(reinterpret_cast(m_event_handle), INFINITE); +} + +bool Event::TryWait(u32 timeout_in_ms) +{ + return (WaitForSingleObject(reinterpret_cast(m_event_handle), timeout_in_ms) == WAIT_OBJECT_0); +} + +void Event::Reset() +{ + ResetEvent(reinterpret_cast(m_event_handle)); +} + +void Event::WaitForMultiple(Event** events, u32 num_events) +{ + DebugAssert(num_events > 0); + + HANDLE* event_handles = (HANDLE*)alloca(sizeof(HANDLE) * num_events); + for (u32 i = 0; i < num_events; i++) + event_handles[i] = reinterpret_cast(events[i]->m_event_handle); + + WaitForMultipleObjects(num_events, event_handles, TRUE, INFINITE); +} + +#elif defined(WIN32) + +Event::Event(bool auto_reset /* = false */) : m_auto_reset(auto_reset) +{ + InitializeCriticalSection(&m_cs); + InitializeConditionVariable(&m_cv); +} + +Event::~Event() +{ + DeleteCriticalSection(&m_cs); +} + +void Event::Signal() +{ + EnterCriticalSection(&m_cs); + m_signaled.store(true); + WakeAllConditionVariable(&m_cv); + LeaveCriticalSection(&m_cs); +} + +void Event::Wait() +{ + m_waiters.fetch_add(1); + + EnterCriticalSection(&m_cs); + while (!m_signaled.load()) + SleepConditionVariableCS(&m_cv, &m_cs, INFINITE); + + if (m_waiters.fetch_sub(1) == 1 && m_auto_reset) + m_signaled.store(false); + + LeaveCriticalSection(&m_cs); +} + +bool Event::TryWait(u32 timeout_in_ms) +{ + m_waiters.fetch_add(1); + + const u32 start = GetTickCount(); + + EnterCriticalSection(&m_cs); + while (!m_signaled.load() && (GetTickCount() - start) < timeout_in_ms) + SleepConditionVariableCS(&m_cv, &m_cs, INFINITE); + + const bool result = m_signaled.load(); + + if (m_waiters.fetch_sub(1) == 1 && result && m_auto_reset) + m_signaled.store(false); + + LeaveCriticalSection(&m_cs); + + return result; +} + +void Event::Reset() +{ + EnterCriticalSection(&m_cs); + m_signaled.store(false); + LeaveCriticalSection(&m_cs); +} + +void Event::WaitForMultiple(Event** events, u32 num_events) +{ + for (u32 i = 0; i < num_events; i++) + events[i]->Wait(); +} + +#elif defined(__linux__) || defined(__APPLE__) || defined(__HAIKU__) + +Event::Event(bool auto_reset /* = false */) : m_auto_reset(auto_reset) +{ + pthread_mutex_init(&m_mutex, nullptr); + pthread_cond_init(&m_cv, nullptr); +} + +Event::~Event() +{ + pthread_cond_destroy(&m_cv); + pthread_mutex_destroy(&m_mutex); +} + +void Event::Signal() +{ + pthread_mutex_lock(&m_mutex); + m_signaled.store(true); + pthread_cond_broadcast(&m_cv); + pthread_mutex_unlock(&m_mutex); +} + +void Event::Wait() +{ + m_waiters.fetch_add(1); + + pthread_mutex_lock(&m_mutex); + while (!m_signaled.load()) + pthread_cond_wait(&m_cv, &m_mutex); + + if (m_waiters.fetch_sub(1) == 1 && m_auto_reset) + m_signaled.store(false); + + pthread_mutex_unlock(&m_mutex); +} + +bool Event::TryWait(u32 timeout_in_ms) +{ + m_waiters.fetch_add(1); + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout_in_ms / 1000; + ts.tv_nsec += (timeout_in_ms % 1000) * 1000000; + + pthread_mutex_lock(&m_mutex); + while (!m_signaled.load()) + { + if (pthread_cond_timedwait(&m_cv, &m_mutex, &ts) != 0) + break; + } + + const bool result = m_signaled.load(); + + if (m_waiters.fetch_sub(1) == 1 && result && m_auto_reset) + m_signaled.store(false); + + pthread_mutex_unlock(&m_mutex); + + return result; +} + +void Event::Reset() +{ + pthread_mutex_lock(&m_mutex); + m_signaled.store(false); + pthread_mutex_unlock(&m_mutex); +} + +void Event::WaitForMultiple(Event** events, u32 num_events) +{ + for (u32 i = 0; i < num_events; i++) + events[i]->Wait(); +} + +#else + +Event::Event(bool auto_reset /* = false */) : m_auto_reset(auto_reset) {} + +Event::~Event() = default; + +void Event::Signal() +{ + std::unique_lock lock(m_mutex); + m_signaled.store(true); + m_cv.notify_all(); +} + +void Event::Wait() +{ + m_waiters.fetch_add(1); + + std::unique_lock lock(m_mutex); + m_cv.wait(lock, [this]() { return m_signaled.load(); }); + + if (m_waiters.fetch_sub(1) == 1 && m_auto_reset) + m_signaled.store(false); +} + +bool Event::TryWait(u32 timeout_in_ms) +{ + m_waiters.fetch_add(1); + + std::unique_lock lock(m_mutex); + const bool result = + m_cv.wait_for(lock, std::chrono::milliseconds(timeout_in_ms), [this]() { return m_signaled.load(); }); + + if (m_waiters.fetch_sub(1) == 1 && result && m_auto_reset) + m_signaled.store(false); + + return result; +} + +void Event::Reset() +{ + std::unique_lock lock(m_mutex); + m_signaled.store(false); +} + +void Event::WaitForMultiple(Event** events, u32 num_events) +{ + for (u32 i = 0; i < num_events; i++) + events[i]->Wait(); +} + +#endif + +} // namespace Common diff --git a/jni/common/event.h b/jni/common/event.h new file mode 100644 index 0000000..ad31061 --- /dev/null +++ b/jni/common/event.h @@ -0,0 +1,57 @@ +#pragma once +#include "types.h" + +// #define USE_WIN32_EVENT_OBJECTS 1 + +#if defined(WIN32) && !defined(USE_WIN32_EVENT_OBJECTS) +#include "windows_headers.h" +#include +#elif defined(__linux__) || defined(__APPLE__) || defined(__HAIKU__) +#include +#include +#else +#include +#include +#include +#endif + +namespace Common { + +class Event +{ +public: + Event(bool auto_reset = false); + ~Event(); + + void Reset(); + void Signal(); + void Wait(); + bool TryWait(u32 timeout_in_ms); + + static void WaitForMultiple(Event** events, u32 num_events); + +private: +#if defined(WIN32) && defined(USE_WIN32_EVENT_OBJECTS) + void* m_event_handle; +#elif defined(WIN32) + CRITICAL_SECTION m_cs; + CONDITION_VARIABLE m_cv; + std::atomic_uint32_t m_waiters{0}; + std::atomic_bool m_signaled{false}; + bool m_auto_reset = false; +#elif defined(__linux__) || defined(__APPLE__) || defined(__HAIKU__) + pthread_mutex_t m_mutex; + pthread_cond_t m_cv; + std::atomic_uint32_t m_waiters{0}; + std::atomic_bool m_signaled{false}; + bool m_auto_reset = false; +#else + std::mutex m_mutex; + std::condition_variable m_cv; + std::atomic_uint32_t m_waiters{0}; + std::atomic_bool m_signaled{false}; + bool m_auto_reset = false; +#endif +}; + +} // namespace Common diff --git a/jni/common/fifo_queue.h b/jni/common/fifo_queue.h new file mode 100644 index 0000000..c4a67fa --- /dev/null +++ b/jni/common/fifo_queue.h @@ -0,0 +1,229 @@ +#pragma once +#include "assert.h" +#include "types.h" +#include +#include +#include + +#ifdef _MSC_VER +#include // _aligned_malloc +#else +#include // posix_memalign +#endif + +template +class FIFOQueue +{ +public: + const T* GetDataPointer() const { return m_ptr; } + T* GetDataPointer() { return m_ptr; } + const T* GetReadPointer() const { return &m_ptr[m_head]; } + T* GetReadPointer() { return &m_ptr[m_head]; } + constexpr u32 GetCapacity() const { return CAPACITY; } + T* GetWritePointer() { return &m_ptr[m_tail]; } + u32 GetSize() const { return m_size; } + u32 GetSpace() const { return CAPACITY - m_size; } + u32 GetContiguousSpace() const { return (m_tail >= m_head) ? (CAPACITY - m_tail) : (m_head - m_tail); } + u32 GetContiguousSize() const { return std::min(CAPACITY - m_head, m_size); } + bool IsEmpty() const { return m_size == 0; } + bool IsFull() const { return m_size == CAPACITY; } + + void Clear() + { + m_head = 0; + m_tail = 0; + m_size = 0; + } + + template + T& Emplace(Args&&... args) + { + T& ref = PushAndGetReference(); + new (&ref) T(std::forward(args...)); + return ref; + } + + template, int> = 0> + T& Push(const T& value) + { + T& ref = PushAndGetReference(); + std::memcpy(&ref, &value, sizeof(T)); + return ref; + } + + template, int> = 0> + T& Push(const T& value) + { + T& ref = PushAndGetReference(); + new (&ref) T(value); + return ref; + } + + // faster version of push_back_range for POD types which can be memcpy()ed + template, int> = 0> + void PushRange(const T* data, u32 size) + { + DebugAssert((m_size + size) <= CAPACITY); + const u32 space_before_end = CAPACITY - m_tail; + const u32 size_before_end = (size > space_before_end) ? space_before_end : size; + const u32 size_after_end = size - size_before_end; + + std::memcpy(&m_ptr[m_tail], data, sizeof(T) * size_before_end); + m_tail = (m_tail + size_before_end) % CAPACITY; + + if (size_after_end > 0) + { + std::memcpy(&m_ptr[m_tail], data + size_before_end, sizeof(T) * size_after_end); + m_tail = (m_tail + size_after_end) % CAPACITY; + } + + m_size += size; + } + + template, int> = 0> + void PushRange(const T* data, u32 size) + { + DebugAssert((m_size + size) <= CAPACITY); + while (size > 0) + { + T& ref = PushAndGetReference(); + new (&ref) T(*data); + data++; + size--; + } + } + + const T& Peek() const { return m_ptr[m_head]; } + const T& Peek(u32 offset) { return m_ptr[(m_head + offset) % CAPACITY]; } + + void Remove(u32 count) + { + DebugAssert(m_size >= count); + for (u32 i = 0; i < count; i++) + { + m_ptr[m_head].~T(); + m_head = (m_head + 1) % CAPACITY; + m_size--; + } + } + + void RemoveOne() + { + DebugAssert(m_size > 0); + m_ptr[m_head].~T(); + m_head = (m_head + 1) % CAPACITY; + m_size--; + } + + // removes and returns moved value + T Pop() + { + DebugAssert(m_size > 0); + T val = std::move(m_ptr[m_head]); + m_ptr[m_head].~T(); + m_head = (m_head + 1) % CAPACITY; + m_size--; + return val; + } + + void PopRange(T* out_data, u32 count) + { + DebugAssert(m_size >= count); + + for (u32 i = 0; i < count; i++) + { + out_data[i] = std::move(m_ptr[m_head]); + m_ptr[m_head].~T(); + m_head = (m_head + 1) % CAPACITY; + m_size--; + } + } + + template + void PushFromQueue(FIFOQueue* other_queue) + { + while (!other_queue->IsEmpty() && !IsFull()) + { + T& dest = PushAndGetReference(); + dest = std::move(other_queue->Pop()); + } + } + + void AdvanceTail(u32 count) + { + DebugAssert((m_size + count) < CAPACITY); + DebugAssert((m_tail + count) <= CAPACITY); + m_tail = (m_tail + count) % CAPACITY; + m_size += count; + } + +protected: + FIFOQueue() = default; + + T& PushAndGetReference() + { + DebugAssert(m_size < CAPACITY); + T& ref = m_ptr[m_tail]; + m_tail = (m_tail + 1) % CAPACITY; + m_size++; + return ref; + } + + T* m_ptr = nullptr; + u32 m_head = 0; + u32 m_tail = 0; + u32 m_size = 0; +}; + +template +class InlineFIFOQueue : public FIFOQueue +{ +public: + InlineFIFOQueue() : FIFOQueue() { this->m_ptr = m_inline_data; } + +private: + T m_inline_data[CAPACITY] = {}; +}; + +template +class HeapFIFOQueue : public FIFOQueue +{ +public: + HeapFIFOQueue() : FIFOQueue() + { + if constexpr (ALIGNMENT > 0) + { +#ifdef _MSC_VER + this->m_ptr = static_cast(_aligned_malloc(sizeof(T) * CAPACITY, ALIGNMENT)); +#else + if (posix_memalign(reinterpret_cast(&this->m_ptr), ALIGNMENT, sizeof(T) * CAPACITY) != 0) + this->m_ptr = nullptr; +#endif + } + else + { + this->m_ptr = static_cast(std::malloc(sizeof(T) * CAPACITY)); + } + + if (!this->m_ptr) + Panic("Heap allocation failed"); + + std::memset(this->m_ptr, 0, sizeof(T) * CAPACITY); + } + + ~HeapFIFOQueue() + { + if constexpr (ALIGNMENT > 0) + { +#ifdef _MSC_VER + _aligned_free(this->m_ptr); +#else + free(this->m_ptr); +#endif + } + else + { + free(this->m_ptr); + } + } +}; diff --git a/jni/common/file_system.cpp b/jni/common/file_system.cpp new file mode 100644 index 0000000..e3da145 --- /dev/null +++ b/jni/common/file_system.cpp @@ -0,0 +1,1624 @@ +#include "file_system.h" +#include "assert.h" +#include "byte_stream.h" +#include "log.h" +#include "string_util.h" +#include +#include + +#ifdef __APPLE__ +#include +#include +#include +#else +#include +#endif + +#if defined(WIN32) +#include +#else +#include +#include +#include +#include +#include +#endif + +Log_SetChannel(FileSystem); + +namespace FileSystem { + +ChangeNotifier::ChangeNotifier(const String& directoryPath, bool recursiveWatch) + : m_directoryPath(directoryPath), m_recursiveWatch(recursiveWatch) +{ +} + +ChangeNotifier::~ChangeNotifier() {} + +void CanonicalizePath(char* Destination, u32 cbDestination, const char* Path, bool OSPath /*= true*/) +{ + u32 i, j; + DebugAssert(Destination && cbDestination > 0 && Path); + + // get length + u32 pathLength = static_cast(std::strlen(Path)); + + // clone to a local buffer if the same pointer + if (Destination == Path) + { + char* pathClone = (char*)alloca(pathLength + 1); + StringUtil::Strlcpy(pathClone, Path, pathLength + 1); + Path = pathClone; + } + + // zero destination + std::memset(Destination, 0, cbDestination); + + // iterate path + u32 destinationLength = 0; + for (i = 0; i < pathLength;) + { + char prevCh = (i > 0) ? Path[i - 1] : '\0'; + char currentCh = Path[i]; + char nextCh = (i < pathLength) ? Path[i + 1] : '\0'; + + if (currentCh == '.') + { + if (prevCh == '\\' || prevCh == '/' || prevCh == '\0') + { + // handle '.' + if (nextCh == '\\' || nextCh == '/' || nextCh == '\0') + { + // skip '.\' + i++; + + // remove the previous \, if we have one trailing the dot it'll append it anyway + if (destinationLength > 0) + Destination[--destinationLength] = '\0'; + + continue; + } + // handle '..' + else if (nextCh == '.') + { + char afterNext = ((i + 1) < pathLength) ? Path[i + 2] : '\0'; + if (afterNext == '\\' || afterNext == '/' || afterNext == '\0') + { + // remove one directory of the path, including the /. + if (destinationLength > 1) + { + for (j = destinationLength - 2; j > 0; j--) + { + if (Destination[j] == '\\' || Destination[j] == '/') + break; + } + + destinationLength = j; +#ifdef _DEBUG + Destination[destinationLength] = '\0'; +#endif + } + + // skip the dot segment + i += 2; + continue; + } + } + } + } + + // fix ospath + if (OSPath && (currentCh == '\\' || currentCh == '/')) + currentCh = FS_OSPATH_SEPERATOR_CHARACTER; + + // copy character + if (destinationLength < cbDestination) + { + Destination[destinationLength++] = currentCh; +#ifdef _DEBUG + Destination[destinationLength] = '\0'; +#endif + } + else + break; + + // increment position by one + i++; + } + + // ensure nullptr termination + if (destinationLength < cbDestination) + Destination[destinationLength] = '\0'; + else + Destination[destinationLength - 1] = '\0'; +} + +void CanonicalizePath(String& Destination, const char* Path, bool OSPath /* = true */) +{ + // the function won't actually write any more characters than are present to the buffer, + // so we can get away with simply passing both pointers if they are the same. + if (Destination.GetWriteableCharArray() != Path) + { + // otherwise, resize the destination to at least the source's size, and then pass as-is + Destination.Reserve(static_cast(std::strlen(Path)) + 1); + } + + CanonicalizePath(Destination.GetWriteableCharArray(), Destination.GetBufferSize(), Path, OSPath); + Destination.UpdateSize(); +} + +void CanonicalizePath(String& Destination, bool OSPath /* = true */) +{ + CanonicalizePath(Destination, Destination); +} + +void CanonicalizePath(std::string& path, bool OSPath /*= true*/) +{ + CanonicalizePath(path.data(), static_cast(path.size() + 1), path.c_str(), OSPath); +} + +static inline bool FileSystemCharacterIsSane(char c, bool StripSlashes) +{ + if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9') && c != ' ' && c != ' ' && + c != '_' && c != '-') + { + if (!StripSlashes && (c == '/' || c == '\\')) + return true; + + return false; + } + + return true; +} + +void SanitizeFileName(char* Destination, u32 cbDestination, const char* FileName, bool StripSlashes /* = true */) +{ + u32 i; + u32 fileNameLength = static_cast(std::strlen(FileName)); + + if (FileName == Destination) + { + for (i = 0; i < fileNameLength; i++) + { + if (!FileSystemCharacterIsSane(FileName[i], StripSlashes)) + Destination[i] = '_'; + } + } + else + { + for (i = 0; i < fileNameLength && i < cbDestination; i++) + { + if (FileSystemCharacterIsSane(FileName[i], StripSlashes)) + Destination[i] = FileName[i]; + else + Destination[i] = '_'; + } + } +} + +void SanitizeFileName(String& Destination, const char* FileName, bool StripSlashes /* = true */) +{ + u32 i; + u32 fileNameLength; + + // if same buffer, use fastpath + if (Destination.GetWriteableCharArray() == FileName) + { + fileNameLength = Destination.GetLength(); + for (i = 0; i < fileNameLength; i++) + { + if (!FileSystemCharacterIsSane(FileName[i], StripSlashes)) + Destination[i] = '_'; + } + } + else + { + fileNameLength = static_cast(std::strlen(FileName)); + Destination.Resize(fileNameLength); + for (i = 0; i < fileNameLength; i++) + { + if (FileSystemCharacterIsSane(FileName[i], StripSlashes)) + Destination[i] = FileName[i]; + else + Destination[i] = '_'; + } + } +} + +void SanitizeFileName(String& Destination, bool StripSlashes /* = true */) +{ + return SanitizeFileName(Destination, Destination, StripSlashes); +} + +bool IsAbsolutePath(const std::string_view& path) +{ +#ifdef WIN32 + return (path.length() >= 3 && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && + path[1] == ':' && (path[2] == '/' || path[2] == '\\')); +#else + return (path.length() >= 1 && path[0] == '/'); +#endif +} + +std::string ReplaceExtension(std::string_view path, std::string_view new_extension) +{ + std::string_view::size_type pos = path.rfind('.'); + if (pos == std::string::npos) + return std::string(path); + + std::string ret(path, 0, pos + 1); + ret.append(new_extension); + return ret; +} + +std::string GetPathDirectory(const char* path) +{ +#ifdef WIN32 + const char* forwardslash_ptr = std::strrchr(path, '/'); + const char* backslash_ptr = std::strrchr(path, '\\'); + const char* slash_ptr; + if (forwardslash_ptr && backslash_ptr) + slash_ptr = std::max(forwardslash_ptr, backslash_ptr); + else if (backslash_ptr) + slash_ptr = backslash_ptr; + else if (forwardslash_ptr) + slash_ptr = forwardslash_ptr; + else + return {}; +#else + const char* slash_ptr = std::strrchr(path, '/'); + if (!slash_ptr) + return {}; +#endif + + if (slash_ptr == path) + return {}; + + std::string str; + str.append(path, slash_ptr - path); + return str; +} + +void BuildPathRelativeToFile(char* Destination, u32 cbDestination, const char* CurrentFileName, const char* NewFileName, + bool OSPath /* = true */, bool Canonicalize /* = true */) +{ + s32 i; + u32 currentPos = 0; + DebugAssert(Destination != nullptr && cbDestination > 0 && CurrentFileName != nullptr && NewFileName != nullptr); + + // clone to a local buffer if the same pointer + std::string pathClone; + if (Destination == CurrentFileName) + { + pathClone = CurrentFileName; + CurrentFileName = pathClone.c_str(); + } + + // search for a / or \, copy everything up to and including it to the destination + i = (s32)std::strlen(CurrentFileName); + for (; i >= 0; i--) + { + if (CurrentFileName[i] == '/' || CurrentFileName[i] == '\\') + { + // cap to destination length + u32 copyLen; + if (NewFileName[0] != '\0') + copyLen = std::min((u32)(i + 1), cbDestination); + else + copyLen = std::min((u32)i, cbDestination); + + if (copyLen > 0) + { + std::memcpy(Destination, CurrentFileName, copyLen); + if (copyLen == cbDestination) + Destination[cbDestination - 1] = '\0'; + + currentPos = copyLen; + } + + break; + } + } + + // copy the new parts in + if (currentPos < cbDestination && NewFileName[0] != '\0') + StringUtil::Strlcpy(Destination + currentPos, NewFileName, cbDestination - currentPos); + + // canonicalize it + if (Canonicalize) + CanonicalizePath(Destination, cbDestination, Destination, OSPath); + else if (OSPath) + BuildOSPath(Destination, cbDestination, Destination); +} + +void BuildPathRelativeToFile(String& Destination, const char* CurrentFileName, const char* NewFileName, + bool OSPath /* = true */, bool Canonicalize /* = true */) +{ + s32 i; + DebugAssert(CurrentFileName != nullptr && NewFileName != nullptr); + + // get curfile length + u32 curFileLength = static_cast(std::strlen(CurrentFileName)); + + // clone to a local buffer if the same pointer + if (Destination.GetWriteableCharArray() == CurrentFileName) + { + char* pathClone = (char*)alloca(curFileLength + 1); + StringUtil::Strlcpy(pathClone, CurrentFileName, curFileLength + 1); + CurrentFileName = pathClone; + } + + // search for a / or \\, copy everything up to and including it to the destination + Destination.Clear(); + i = (s32)curFileLength; + for (; i >= 0; i--) + { + if (CurrentFileName[i] == '/' || CurrentFileName[i] == '\\') + { + if (NewFileName[0] != '\0') + Destination.AppendSubString(CurrentFileName, 0, i + 1); + else + Destination.AppendSubString(CurrentFileName, 0, i); + + break; + } + } + + // copy the new parts in + if (NewFileName[0] != '\0') + Destination.AppendString(NewFileName); + + // canonicalize it + if (Canonicalize) + CanonicalizePath(Destination, Destination.GetCharArray(), OSPath); + else if (OSPath) + BuildOSPath(Destination, Destination.GetCharArray()); +} + +String BuildPathRelativeToFile(const char* CurrentFileName, const char* NewFileName, bool OSPath /*= true*/, + bool Canonicalize /*= true*/) +{ + String ret; + BuildPathRelativeToFile(ret, CurrentFileName, NewFileName, OSPath, Canonicalize); + return ret; +} + +std::unique_ptr OpenFile(const char* FileName, u32 Flags) +{ + // has a path + if (FileName[0] == '\0') + return nullptr; + + // forward to local file wrapper + return ByteStream_OpenFileStream(FileName, Flags); +} + +FileSystem::ManagedCFilePtr OpenManagedCFile(const char* filename, const char* mode) +{ + return ManagedCFilePtr(OpenCFile(filename, mode), [](std::FILE* fp) { std::fclose(fp); }); +} + +std::FILE* OpenCFile(const char* filename, const char* mode) +{ +#ifdef WIN32 + int filename_len = static_cast(std::strlen(filename)); + int mode_len = static_cast(std::strlen(mode)); + int wlen = MultiByteToWideChar(CP_UTF8, 0, filename, filename_len, nullptr, 0); + int wmodelen = MultiByteToWideChar(CP_UTF8, 0, mode, mode_len, nullptr, 0); + if (wlen > 0 && wmodelen > 0) + { + wchar_t* wfilename = static_cast(alloca(sizeof(wchar_t) * (wlen + 1))); + wchar_t* wmode = static_cast(alloca(sizeof(wchar_t) * (wmodelen + 1))); + wlen = MultiByteToWideChar(CP_UTF8, 0, filename, filename_len, wfilename, wlen); + wmodelen = MultiByteToWideChar(CP_UTF8, 0, mode, mode_len, wmode, wmodelen); + if (wlen > 0 && wmodelen > 0) + { + wfilename[wlen] = 0; + wmode[wmodelen] = 0; + std::FILE* fp; + if (_wfopen_s(&fp, wfilename, wmode) != 0) + return nullptr; + + return fp; + } + } + + std::FILE* fp; + if (fopen_s(&fp, filename, mode) != 0) + return nullptr; + + return fp; +#else + return std::fopen(filename, mode); +#endif +} + +std::optional> ReadBinaryFile(const char* filename) +{ + ManagedCFilePtr fp = OpenManagedCFile(filename, "rb"); + if (!fp) + return std::nullopt; + + std::fseek(fp.get(), 0, SEEK_END); + long size = std::ftell(fp.get()); + std::fseek(fp.get(), 0, SEEK_SET); + if (size < 0) + return std::nullopt; + + std::vector res(static_cast(size)); + if (size > 0 && std::fread(res.data(), 1u, static_cast(size), fp.get()) != static_cast(size)) + return std::nullopt; + + return res; +} + +std::optional ReadFileToString(const char* filename) +{ + ManagedCFilePtr fp = OpenManagedCFile(filename, "rb"); + if (!fp) + return std::nullopt; + + std::fseek(fp.get(), 0, SEEK_END); + long size = std::ftell(fp.get()); + std::fseek(fp.get(), 0, SEEK_SET); + if (size < 0) + return std::nullopt; + + std::string res; + res.resize(static_cast(size)); + if (size > 0 && std::fread(res.data(), 1u, static_cast(size), fp.get()) != static_cast(size)) + return std::nullopt; + + return res; +} + +bool WriteBinaryFile(const char* filename, const void* data, size_t data_length) +{ + ManagedCFilePtr fp = OpenManagedCFile(filename, "wb"); + if (!fp) + return false; + + if (data_length > 0 && std::fwrite(data, 1u, data_length, fp.get()) != data_length) + return false; + + return true; +} + +bool WriteFileToString(const char* filename, const std::string_view& sv) +{ + ManagedCFilePtr fp = OpenManagedCFile(filename, "wb"); + if (!fp) + return false; + + if (sv.length() > 0 && std::fwrite(sv.data(), 1u, sv.length(), fp.get()) != sv.length()) + return false; + + return true; +} + +std::string ReadStreamToString(ByteStream* stream, bool seek_to_start /* = true */) +{ + u64 pos = stream->GetPosition(); + u64 size = stream->GetSize(); + if (pos > 0 && seek_to_start) + { + if (!stream->SeekAbsolute(0)) + return {}; + + pos = 0; + } + + Assert(size >= pos); + size -= pos; + if (size == 0 || size > std::numeric_limits::max()) + return {}; + + std::string ret; + ret.resize(static_cast(size)); + if (!stream->Read2(ret.data(), static_cast(size))) + return {}; + + return ret; +} + +bool WriteStreamToString(const std::string_view& sv, ByteStream* stream) +{ + if (sv.size() > std::numeric_limits::max()) + return false; + + return stream->Write2(sv.data(), static_cast(sv.size())); +} + +void BuildOSPath(char* Destination, u32 cbDestination, const char* Path) +{ + u32 i; + u32 pathLength = static_cast(std::strlen(Path)); + + if (Destination == Path) + { + // fast path + for (i = 0; i < pathLength; i++) + { + if (Destination[i] == '/') + Destination[i] = FS_OSPATH_SEPERATOR_CHARACTER; + } + } + else + { + // slow path + pathLength = std::max(pathLength, cbDestination - 1); + for (i = 0; i < pathLength; i++) + { + Destination[i] = (Path[i] == '/') ? FS_OSPATH_SEPERATOR_CHARACTER : Path[i]; + } + + Destination[pathLength] = '\0'; + } +} + +void BuildOSPath(String& Destination, const char* Path) +{ + u32 i; + u32 pathLength; + + if (Destination.GetWriteableCharArray() == Path) + { + // fast path + pathLength = Destination.GetLength(); + ; + for (i = 0; i < pathLength; i++) + { + if (Destination[i] == '/') + Destination[i] = FS_OSPATH_SEPERATOR_CHARACTER; + } + } + else + { + // slow path + pathLength = static_cast(std::strlen(Path)); + Destination.Resize(pathLength); + for (i = 0; i < pathLength; i++) + { + Destination[i] = (Path[i] == '/') ? FS_OSPATH_SEPERATOR_CHARACTER : Path[i]; + } + } +} + +void BuildOSPath(String& Destination) +{ + BuildOSPath(Destination, Destination); +} + +#ifdef _WIN32 + +static u32 TranslateWin32Attributes(u32 Win32Attributes) +{ + u32 r = 0; + + if (Win32Attributes & FILE_ATTRIBUTE_DIRECTORY) + r |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; + if (Win32Attributes & FILE_ATTRIBUTE_READONLY) + r |= FILESYSTEM_FILE_ATTRIBUTE_READ_ONLY; + if (Win32Attributes & FILE_ATTRIBUTE_COMPRESSED) + r |= FILESYSTEM_FILE_ATTRIBUTE_COMPRESSED; + + return r; +} + +static const u32 READ_DIRECTORY_CHANGES_NOTIFY_FILTER = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION; + +class ChangeNotifierWin32 : public FileSystem::ChangeNotifier +{ +public: + ChangeNotifierWin32(HANDLE hDirectory, const String& directoryPath, bool recursiveWatch) + : FileSystem::ChangeNotifier(directoryPath, recursiveWatch), m_hDirectory(hDirectory), + m_directoryChangeQueued(false) + { + m_bufferSize = 16384; + m_pBuffer = new byte[m_bufferSize]; + } + + virtual ~ChangeNotifierWin32() + { + // if there is outstanding io, cancel it + if (m_directoryChangeQueued) + { + CancelIo(m_hDirectory); + + DWORD bytesTransferred; + GetOverlappedResult(m_hDirectory, &m_overlapped, &bytesTransferred, TRUE); + } + + CloseHandle(m_hDirectory); + delete[] m_pBuffer; + } + + virtual void EnumerateChanges(EnumerateChangesCallback callback, void* pUserData) override + { + DWORD bytesRead; + if (!GetOverlappedResult(m_hDirectory, &m_overlapped, &bytesRead, FALSE)) + { + if (GetLastError() == ERROR_IO_INCOMPLETE) + return; + + CancelIo(m_hDirectory); + m_directoryChangeQueued = false; + + QueueReadDirectoryChanges(); + return; + } + + // not queued any more + m_directoryChangeQueued = false; + + // has any bytes? + if (bytesRead > 0) + { + const byte* pCurrentPointer = m_pBuffer; + PathString fileName; + for (;;) + { + const FILE_NOTIFY_INFORMATION* pFileNotifyInformation = + reinterpret_cast(pCurrentPointer); + + // translate the event + u32 changeEvent = 0; + if (pFileNotifyInformation->Action == FILE_ACTION_ADDED) + changeEvent = ChangeEvent_FileAdded; + else if (pFileNotifyInformation->Action == FILE_ACTION_REMOVED) + changeEvent = ChangeEvent_FileRemoved; + else if (pFileNotifyInformation->Action == FILE_ACTION_MODIFIED) + changeEvent = ChangeEvent_FileModified; + else if (pFileNotifyInformation->Action == FILE_ACTION_RENAMED_OLD_NAME) + changeEvent = ChangeEvent_RenamedOldName; + else if (pFileNotifyInformation->Action == FILE_ACTION_RENAMED_NEW_NAME) + changeEvent = ChangeEvent_RenamedNewName; + + // translate the filename + int fileNameLength = + WideCharToMultiByte(CP_UTF8, 0, pFileNotifyInformation->FileName, + pFileNotifyInformation->FileNameLength / sizeof(WCHAR), nullptr, 0, nullptr, nullptr); + DebugAssert(fileNameLength >= 0); + fileName.Resize(fileNameLength); + fileNameLength = WideCharToMultiByte(CP_UTF8, 0, pFileNotifyInformation->FileName, + pFileNotifyInformation->FileNameLength / sizeof(WCHAR), + fileName.GetWriteableCharArray(), fileName.GetLength(), nullptr, nullptr); + if (fileNameLength != (int)fileName.GetLength()) + fileName.Resize(fileNameLength); + + // prepend the base path + fileName.PrependFormattedString("%s\\", m_directoryPath.GetCharArray()); + + // construct change info + ChangeInfo changeInfo; + changeInfo.Path = fileName; + changeInfo.Event = changeEvent; + + // invoke callback + callback(&changeInfo, pUserData); + + // has a next entry? + if (pFileNotifyInformation->NextEntryOffset == 0) + break; + + pCurrentPointer += pFileNotifyInformation->NextEntryOffset; + DebugAssert(pCurrentPointer < (m_pBuffer + m_bufferSize)); + } + } + + // re-queue the operation + QueueReadDirectoryChanges(); + } + + bool QueueReadDirectoryChanges() + { + DebugAssert(!m_directoryChangeQueued); + + std::memset(&m_overlapped, 0, sizeof(m_overlapped)); + if (ReadDirectoryChangesW(m_hDirectory, m_pBuffer, m_bufferSize, m_recursiveWatch, + READ_DIRECTORY_CHANGES_NOTIFY_FILTER, nullptr, &m_overlapped, nullptr) == FALSE) + return false; + + m_directoryChangeQueued = true; + return true; + } + +private: + HANDLE m_hDirectory; + OVERLAPPED m_overlapped; + bool m_directoryChangeQueued; + byte* m_pBuffer; + u32 m_bufferSize; +}; + +std::unique_ptr CreateChangeNotifier(const char* path, bool recursiveWatch) +{ + // open the directory up + HANDLE hDirectory = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr); + if (hDirectory == nullptr) + return nullptr; + + // queue up the overlapped io + auto pChangeNotifier = std::make_unique(hDirectory, path, recursiveWatch); + if (!pChangeNotifier->QueueReadDirectoryChanges()) + return nullptr; + + return pChangeNotifier; +} + +static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, const char* Path, const char* Pattern, + u32 Flags, FileSystem::FindResultsArray* pResults) +{ + std::string tempStr; + if (Path) + { + if (ParentPath) + tempStr = StringUtil::StdStringFromFormat("%s\\%s\\%s\\*", OriginPath, ParentPath, Path); + else + tempStr = StringUtil::StdStringFromFormat("%s\\%s\\*", OriginPath, Path); + } + else + { + tempStr = StringUtil::StdStringFromFormat("%s\\*", OriginPath); + } + + WIN32_FIND_DATAW wfd; + HANDLE hFind = FindFirstFileW(StringUtil::UTF8StringToWideString(tempStr).c_str(), &wfd); + if (hFind == INVALID_HANDLE_VALUE) + return 0; + + // small speed optimization for '*' case + bool hasWildCards = false; + bool wildCardMatchAll = false; + u32 nFiles = 0; + if (std::strpbrk(Pattern, "*?") != nullptr) + { + hasWildCards = true; + wildCardMatchAll = !(std::strcmp(Pattern, "*")); + } + + // holder for utf-8 conversion + std::string utf8_filename; + utf8_filename.reserve(countof(wfd.cFileName) * 2); + + // iterate results + do + { + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(Flags & FILESYSTEM_FIND_HIDDEN_FILES)) + continue; + + if (wfd.cFileName[0] == L'.') + { + if (wfd.cFileName[1] == L'\0' || (wfd.cFileName[1] == L'.' && wfd.cFileName[2] == L'\0')) + continue; + + if (!(Flags & FILESYSTEM_FIND_HIDDEN_FILES)) + continue; + } + + if (!StringUtil::WideStringToUTF8String(utf8_filename, wfd.cFileName)) + continue; + + FILESYSTEM_FIND_DATA outData; + outData.Attributes = 0; + + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (Flags & FILESYSTEM_FIND_RECURSIVE) + { + // recurse into this directory + if (ParentPath != nullptr) + { + const std::string recurseDir = StringUtil::StdStringFromFormat("%s\\%s", ParentPath, Path); + nFiles += RecursiveFindFiles(OriginPath, recurseDir.c_str(), utf8_filename.c_str(), Pattern, Flags, pResults); + } + else + { + nFiles += RecursiveFindFiles(OriginPath, Path, utf8_filename.c_str(), Pattern, Flags, pResults); + } + } + + if (!(Flags & FILESYSTEM_FIND_FOLDERS)) + continue; + + outData.Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; + } + else + { + if (!(Flags & FILESYSTEM_FIND_FILES)) + continue; + } + + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + outData.Attributes |= FILESYSTEM_FILE_ATTRIBUTE_READ_ONLY; + + // match the filename + if (hasWildCards) + { + if (!wildCardMatchAll && !StringUtil::WildcardMatch(utf8_filename.c_str(), Pattern)) + continue; + } + else + { + if (std::strcmp(utf8_filename.c_str(), Pattern) != 0) + continue; + } + + // add file to list + // TODO string formatter, clean this mess.. + if (!(Flags & FILESYSTEM_FIND_RELATIVE_PATHS)) + { + if (ParentPath != nullptr) + outData.FileName = + StringUtil::StdStringFromFormat("%s\\%s\\%s\\%s", OriginPath, ParentPath, Path, utf8_filename.c_str()); + else if (Path != nullptr) + outData.FileName = StringUtil::StdStringFromFormat("%s\\%s\\%s", OriginPath, Path, utf8_filename.c_str()); + else + outData.FileName = StringUtil::StdStringFromFormat("%s\\%s", OriginPath, utf8_filename.c_str()); + } + else + { + if (ParentPath != nullptr) + outData.FileName = StringUtil::StdStringFromFormat("%s\\%s\\%s", ParentPath, Path, utf8_filename.c_str()); + else if (Path != nullptr) + outData.FileName = StringUtil::StdStringFromFormat("%s\\%s", Path, utf8_filename.c_str()); + else + outData.FileName = utf8_filename; + } + + outData.ModificationTime.SetWindowsFileTime(&wfd.ftLastWriteTime); + outData.Size = (u64)wfd.nFileSizeHigh << 32 | (u64)wfd.nFileSizeLow; + + nFiles++; + pResults->push_back(std::move(outData)); + } while (FindNextFileW(hFind, &wfd) == TRUE); + FindClose(hFind); + + return nFiles; +} + +bool FileSystem::FindFiles(const char* Path, const char* Pattern, u32 Flags, FindResultsArray* pResults) +{ + // has a path + if (Path[0] == '\0') + return false; + + // clear result array + if (!(Flags & FILESYSTEM_FIND_KEEP_ARRAY)) + pResults->clear(); + + // enter the recursive function + return (RecursiveFindFiles(Path, nullptr, nullptr, Pattern, Flags, pResults) > 0); +} + +bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* pStatData) +{ + // has a path + if (path[0] == '\0') + return false; + + // convert to wide string + int len = static_cast(std::strlen(path)); + int wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, nullptr, 0); + if (wlen <= 0) + return false; + + wchar_t* wpath = static_cast(alloca(sizeof(wchar_t) * (wlen + 1))); + wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, wpath, wlen); + if (wlen <= 0) + return false; + + wpath[wlen] = 0; + + // determine attributes for the path. if it's a directory, things have to be handled differently.. + DWORD fileAttributes = GetFileAttributesW(wpath); + if (fileAttributes == INVALID_FILE_ATTRIBUTES) + return false; + + // test if it is a directory + HANDLE hFile; + if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + hFile = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + } + else + { + hFile = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + OPEN_EXISTING, 0, nullptr); + } + + // createfile succeded? + if (hFile == INVALID_HANDLE_VALUE) + return false; + + // use GetFileInformationByHandle + BY_HANDLE_FILE_INFORMATION bhfi; + if (GetFileInformationByHandle(hFile, &bhfi) == FALSE) + { + CloseHandle(hFile); + return false; + } + + // close handle + CloseHandle(hFile); + + // fill in the stat data + pStatData->Attributes = TranslateWin32Attributes(bhfi.dwFileAttributes); + pStatData->ModificationTime.SetWindowsFileTime(&bhfi.ftLastWriteTime); + pStatData->Size = ((u64)bhfi.nFileSizeHigh) << 32 | (u64)bhfi.nFileSizeLow; + return true; +} + +bool FileSystem::FileExists(const char* path) +{ + // has a path + if (path[0] == '\0') + return false; + + // convert to wide string + int len = static_cast(std::strlen(path)); + int wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, nullptr, 0); + if (wlen <= 0) + return false; + + wchar_t* wpath = static_cast(alloca(sizeof(wchar_t) * (wlen + 1))); + wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, wpath, wlen); + if (wlen <= 0) + return false; + + wpath[wlen] = 0; + + // determine attributes for the path. if it's a directory, things have to be handled differently.. + DWORD fileAttributes = GetFileAttributesW(wpath); + if (fileAttributes == INVALID_FILE_ATTRIBUTES) + return false; + + if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) + return false; + else + return true; +} + +bool FileSystem::DirectoryExists(const char* path) +{ + // has a path + if (path[0] == '\0') + return false; + + // convert to wide string + int len = static_cast(std::strlen(path)); + int wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, nullptr, 0); + if (wlen <= 0) + return false; + + wchar_t* wpath = static_cast(alloca(sizeof(wchar_t) * (wlen + 1))); + wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, wpath, wlen); + if (wlen <= 0) + return false; + + wpath[wlen] = 0; + + // determine attributes for the path. if it's a directory, things have to be handled differently.. + DWORD fileAttributes = GetFileAttributesW(wpath); + if (fileAttributes == INVALID_FILE_ATTRIBUTES) + return false; + + if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) + return true; + else + return false; +} + +bool FileSystem::CreateDirectory(const char* Path, bool Recursive) +{ + std::wstring wpath(StringUtil::UTF8StringToWideString(Path)); + + // has a path + if (wpath[0] == L'\0') + return false; + + // try just flat-out, might work if there's no other segments that have to be made + if (CreateDirectoryW(wpath.c_str(), nullptr)) + return true; + + // check error + DWORD lastError = GetLastError(); + if (lastError == ERROR_ALREADY_EXISTS) + { + // check the attributes + u32 Attributes = GetFileAttributesW(wpath.c_str()); + if (Attributes != INVALID_FILE_ATTRIBUTES && Attributes & FILE_ATTRIBUTE_DIRECTORY) + return true; + else + return false; + } + else if (lastError == ERROR_PATH_NOT_FOUND) + { + // part of the path does not exist, so we'll create the parent folders, then + // the full path again. allocate another buffer with the same length + u32 pathLength = static_cast(wpath.size()); + wchar_t* tempStr = (wchar_t*)alloca(sizeof(wchar_t) * (pathLength + 1)); + + // create directories along the path + for (u32 i = 0; i < pathLength; i++) + { + if (wpath[i] == L'\\' || wpath[i] == L'/') + { + tempStr[i] = L'\0'; + if (!CreateDirectoryW(tempStr, nullptr)) + { + lastError = GetLastError(); + if (lastError != ERROR_ALREADY_EXISTS) // fine, continue to next path segment + return false; + } + } + + tempStr[i] = wpath[i]; + } + + // re-create the end if it's not a separator, check / as well because windows can interpret them + if (wpath[pathLength - 1] != L'\\' && wpath[pathLength - 1] != L'/') + { + if (!CreateDirectoryW(wpath.c_str(), nullptr)) + { + lastError = GetLastError(); + if (lastError != ERROR_ALREADY_EXISTS) + return false; + } + } + + // ok + return true; + } + else + { + // unhandled error + return false; + } +} + +bool FileSystem::DeleteFile(const char* Path) +{ + if (Path[0] == '\0') + return false; + + const std::wstring wpath(StringUtil::UTF8StringToWideString(Path)); + DWORD fileAttributes = GetFileAttributesW(wpath.c_str()); + if (fileAttributes == INVALID_FILE_ATTRIBUTES) + return false; + + if (!(fileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + return (DeleteFileW(wpath.c_str()) == TRUE); + else + return false; +} + +static bool RecursiveDeleteDirectory(const std::wstring& wpath, bool Recursive) +{ + // ensure it exists + DWORD fileAttributes = GetFileAttributesW(wpath.c_str()); + if (fileAttributes == INVALID_FILE_ATTRIBUTES || !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + return false; + + // non-recursive case just try removing the directory + if (!Recursive) + return (RemoveDirectoryW(wpath.c_str()) == TRUE); + + // doing a recursive delete + std::wstring fileName = wpath; + fileName += L"\\*"; + + // is there any files? + WIN32_FIND_DATAW findData; + HANDLE hFind = FindFirstFileW(fileName.c_str(), &findData); + if (hFind == INVALID_HANDLE_VALUE) + return false; + + // search through files + do + { + // skip . and .. + if (findData.cFileName[0] == L'.') + { + if ((findData.cFileName[1] == L'\0') || (findData.cFileName[1] == L'.' && findData.cFileName[2] == L'\0')) + { + continue; + } + } + + // found a directory? + fileName = wpath; + fileName += L"\\"; + fileName += findData.cFileName; + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + // recurse into that + if (!RecursiveDeleteDirectory(fileName, true)) + { + FindClose(hFind); + return false; + } + } + else + { + // found a file, so delete it + if (!DeleteFileW(fileName.c_str())) + { + FindClose(hFind); + return false; + } + } + } while (FindNextFileW(hFind, &findData)); + FindClose(hFind); + + // nuke the directory itself + if (!RemoveDirectoryW(wpath.c_str())) + return false; + + // done + return true; +} + +bool FileSystem::DeleteDirectory(const char* Path, bool Recursive) +{ + const std::wstring wpath(StringUtil::UTF8StringToWideString(Path)); + return RecursiveDeleteDirectory(wpath, Recursive); +} + +std::string GetProgramPath() +{ + std::wstring buffer; + buffer.resize(MAX_PATH); + + // Fall back to the main module if this fails. + HMODULE module = nullptr; + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(&GetProgramPath), &module); + + for (;;) + { + DWORD nChars = GetModuleFileNameW(module, buffer.data(), static_cast(buffer.size())); + if (nChars == static_cast(buffer.size()) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + buffer.resize(buffer.size() * 2); + continue; + } + + buffer.resize(nChars); + break; + } + + std::string utf8_path(StringUtil::WideStringToUTF8String(buffer)); + CanonicalizePath(utf8_path); + return utf8_path; +} + +std::string GetWorkingDirectory() +{ + DWORD required_size = GetCurrentDirectoryW(0, nullptr); + if (!required_size) + return {}; + + std::wstring buffer; + buffer.resize(required_size - 1); + + if (!GetCurrentDirectoryW(static_cast(buffer.size() + 1), buffer.data())) + return {}; + + return StringUtil::WideStringToUTF8String(buffer); +} + +bool SetWorkingDirectory(const char* path) +{ + const std::wstring wpath(StringUtil::UTF8StringToWideString(path)); + return (SetCurrentDirectoryW(wpath.c_str()) == TRUE); +} + +#else + +std::unique_ptr CreateChangeNotifier(const char* path, bool recursiveWatch) +{ + Log_ErrorPrintf("FileSystem::CreateChangeNotifier(%s) not implemented", path); + return nullptr; +} + +static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, const char* Path, const char* Pattern, + u32 Flags, FindResultsArray* pResults) +{ + std::string tempStr; + if (Path) + { + if (ParentPath) + tempStr = StringUtil::StdStringFromFormat("%s/%s/%s", OriginPath, ParentPath, Path); + else + tempStr = StringUtil::StdStringFromFormat("%s/%s", OriginPath, Path); + } + else + { + tempStr = StringUtil::StdStringFromFormat("%s", OriginPath); + } + + DIR* pDir = opendir(tempStr.c_str()); + if (pDir == nullptr) + return 0; + + // small speed optimization for '*' case + bool hasWildCards = false; + bool wildCardMatchAll = false; + u32 nFiles = 0; + if (std::strpbrk(Pattern, "*?")) + { + hasWildCards = true; + wildCardMatchAll = (std::strcmp(Pattern, "*") == 0); + } + + // iterate results + PathString full_path; + struct dirent* pDirEnt; + while ((pDirEnt = readdir(pDir)) != nullptr) + { + // if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(Flags & FILESYSTEM_FIND_HIDDEN_FILES)) + // continue; + // + if (pDirEnt->d_name[0] == '.') + { + if (pDirEnt->d_name[1] == '\0' || (pDirEnt->d_name[1] == '.' && pDirEnt->d_name[2] == '\0')) + continue; + + if (!(Flags & FILESYSTEM_FIND_HIDDEN_FILES)) + continue; + } + + if (ParentPath != nullptr) + full_path.Format("%s/%s/%s/%s", OriginPath, ParentPath, Path, pDirEnt->d_name); + else if (Path != nullptr) + full_path.Format("%s/%s/%s", OriginPath, Path, pDirEnt->d_name); + else + full_path.Format("%s/%s", OriginPath, pDirEnt->d_name); + + FILESYSTEM_FIND_DATA outData; + outData.Attributes = 0; + +#if defined(__HAIKU__) || defined(__APPLE__) + struct stat sDir; + if (stat(full_path, &sDir) < 0) + continue; + +#else + struct stat64 sDir; + if (stat64(full_path, &sDir) < 0) + continue; +#endif + + if (S_ISDIR(sDir.st_mode)) + { + if (Flags & FILESYSTEM_FIND_RECURSIVE) + { + // recurse into this directory + if (ParentPath != nullptr) + { + std::string recursiveDir = StringUtil::StdStringFromFormat("%s/%s", ParentPath, Path); + nFiles += RecursiveFindFiles(OriginPath, recursiveDir.c_str(), pDirEnt->d_name, Pattern, Flags, pResults); + } + else + { + nFiles += RecursiveFindFiles(OriginPath, Path, pDirEnt->d_name, Pattern, Flags, pResults); + } + } + + if (!(Flags & FILESYSTEM_FIND_FOLDERS)) + continue; + + outData.Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; + } + else + { + if (!(Flags & FILESYSTEM_FIND_FILES)) + continue; + } + + outData.Size = static_cast(sDir.st_size); + outData.ModificationTime.SetUnixTimestamp(static_cast(sDir.st_mtime)); + + // match the filename + if (hasWildCards) + { + if (!wildCardMatchAll && !StringUtil::WildcardMatch(pDirEnt->d_name, Pattern)) + continue; + } + else + { + if (std::strcmp(pDirEnt->d_name, Pattern) != 0) + continue; + } + + // add file to list + // TODO string formatter, clean this mess.. + if (!(Flags & FILESYSTEM_FIND_RELATIVE_PATHS)) + { + outData.FileName = std::string(full_path.GetCharArray()); + } + else + { + if (ParentPath != nullptr) + outData.FileName = StringUtil::StdStringFromFormat("%s\\%s\\%s", ParentPath, Path, pDirEnt->d_name); + else if (Path != nullptr) + outData.FileName = StringUtil::StdStringFromFormat("%s\\%s", Path, pDirEnt->d_name); + else + outData.FileName = pDirEnt->d_name; + } + + nFiles++; + pResults->push_back(std::move(outData)); + } + + closedir(pDir); + return nFiles; +} + +bool FindFiles(const char* Path, const char* Pattern, u32 Flags, FindResultsArray* pResults) +{ + // has a path + if (Path[0] == '\0') + return false; + + // clear result array + if (!(Flags & FILESYSTEM_FIND_KEEP_ARRAY)) + pResults->clear(); + + // enter the recursive function + return (RecursiveFindFiles(Path, nullptr, nullptr, Pattern, Flags, pResults) > 0); +} + +bool StatFile(const char* Path, FILESYSTEM_STAT_DATA* pStatData) +{ + // has a path + if (Path[0] == '\0') + return false; + + // stat file +#if defined(__HAIKU__) || defined(__APPLE__) + struct stat sysStatData; + if (stat(Path, &sysStatData) < 0) +#else + struct stat64 sysStatData; + if (stat64(Path, &sysStatData) < 0) +#endif + return false; + + // parse attributes + pStatData->Attributes = 0; + if (S_ISDIR(sysStatData.st_mode)) + pStatData->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; + + // parse times + pStatData->ModificationTime.SetUnixTimestamp((Timestamp::UnixTimestampValue)sysStatData.st_mtime); + + // parse size + if (S_ISREG(sysStatData.st_mode)) + pStatData->Size = static_cast(sysStatData.st_size); + else + pStatData->Size = 0; + + // ok + return true; +} + +bool FileExists(const char* Path) +{ + // has a path + if (Path[0] == '\0') + return false; + + // stat file +#if defined(__HAIKU__) || defined(__APPLE__) + struct stat sysStatData; + if (stat(Path, &sysStatData) < 0) +#else + struct stat64 sysStatData; + if (stat64(Path, &sysStatData) < 0) +#endif + return false; + + if (S_ISDIR(sysStatData.st_mode)) + return false; + else + return true; +} + +bool DirectoryExists(const char* Path) +{ + // has a path + if (Path[0] == '\0') + return false; + + // stat file +#if defined(__HAIKU__) || defined(__APPLE__) + struct stat sysStatData; + if (stat(Path, &sysStatData) < 0) +#else + struct stat64 sysStatData; + if (stat64(Path, &sysStatData) < 0) +#endif + return false; + + if (S_ISDIR(sysStatData.st_mode)) + return true; + else + return false; +} + +bool CreateDirectory(const char* Path, bool Recursive) +{ + u32 i; + int lastError; + + // has a path + if (Path[0] == '\0') + return false; + + // try just flat-out, might work if there's no other segments that have to be made + if (mkdir(Path, 0777) == 0) + return true; + + // check error + lastError = errno; + if (lastError == EEXIST) + { + // check the attributes + struct stat sysStatData; + if (stat(Path, &sysStatData) == 0 && S_ISDIR(sysStatData.st_mode)) + return true; + else + return false; + } + else if (lastError == ENOENT) + { + // part of the path does not exist, so we'll create the parent folders, then + // the full path again. allocate another buffer with the same length + u32 pathLength = static_cast(std::strlen(Path)); + char* tempStr = (char*)alloca(pathLength + 1); + + // create directories along the path + for (i = 0; i < pathLength; i++) + { + if (Path[i] == '/') + { + tempStr[i] = '\0'; + if (mkdir(tempStr, 0777) < 0) + { + lastError = errno; + if (lastError != EEXIST) // fine, continue to next path segment + return false; + } + } + + tempStr[i] = Path[i]; + } + + // re-create the end if it's not a separator, check / as well because windows can interpret them + if (Path[pathLength - 1] != '/') + { + if (mkdir(Path, 0777) < 0) + { + lastError = errno; + if (lastError != EEXIST) + return false; + } + } + + // ok + return true; + } + else + { + // unhandled error + return false; + } +} + +bool DeleteFile(const char* Path) +{ + if (Path[0] == '\0') + return false; + + struct stat sysStatData; + if (stat(Path, &sysStatData) != 0 || S_ISDIR(sysStatData.st_mode)) + return false; + + return (unlink(Path) == 0); +} + +bool DeleteDirectory(const char* Path, bool Recursive) +{ + Log_ErrorPrintf("FileSystem::DeleteDirectory(%s) not implemented", Path); + return false; +} + +std::string GetProgramPath() +{ +#if defined(__linux__) + static const char* exeFileName = "/proc/self/exe"; + + int curSize = PATH_MAX; + char* buffer = static_cast(std::realloc(nullptr, curSize)); + for (;;) + { + int len = readlink(exeFileName, buffer, curSize); + if (len < 0) + { + std::free(buffer); + return {}; + } + else if (len < curSize) + { + buffer[len] = '\0'; + std::string ret(buffer, len); + std::free(buffer); + return ret; + } + + curSize *= 2; + buffer = static_cast(std::realloc(buffer, curSize)); + } + +#elif defined(__APPLE__) + + int curSize = PATH_MAX; + char* buffer = static_cast(std::realloc(nullptr, curSize)); + for (;;) + { + u32 nChars = curSize - 1; + int res = _NSGetExecutablePath(buffer, &nChars); + if (res == 0) + { + buffer[nChars] = 0; + + char* resolvedBuffer = realpath(buffer, nullptr); + if (resolvedBuffer == nullptr) + { + std::free(buffer); + return {}; + } + + std::string ret(buffer); + std::free(buffer); + return ret; + } + + curSize *= 2; + buffer = static_cast(std::realloc(buffer, curSize + 1)); + } + +#else + return {}; +#endif +} + +std::string GetWorkingDirectory() +{ + std::string buffer; + buffer.resize(PATH_MAX); + while (!getcwd(buffer.data(), buffer.size())) + { + if (errno != ERANGE) + return {}; + + buffer.resize(buffer.size() * 2); + } + + return buffer; +} + +bool SetWorkingDirectory(const char* path) +{ + return (chdir(path) == 0); +} + +#endif + +} // namespace FileSystem diff --git a/jni/common/file_system.h b/jni/common/file_system.h new file mode 100644 index 0000000..e7a804b --- /dev/null +++ b/jni/common/file_system.h @@ -0,0 +1,200 @@ +#pragma once +#include "timestamp.h" +#include "types.h" +#include +#include +#include +#include +#include + +class ByteStream; + +#ifdef WIN32 +#define FS_OSPATH_SEPERATOR_CHARACTER '\\' +#define FS_OSPATH_SEPARATOR_STR "\\" +#else +#define FS_OSPATH_SEPERATOR_CHARACTER '/' +#define FS_OSPATH_SEPARATOR_STR "/" +#endif + +enum FILESYSTEM_FILE_ATTRIBUTES +{ + FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY = 1, + FILESYSTEM_FILE_ATTRIBUTE_READ_ONLY = 2, + FILESYSTEM_FILE_ATTRIBUTE_COMPRESSED = 4, +}; + +enum FILESYSTEM_FIND_FLAGS +{ + FILESYSTEM_FIND_RECURSIVE = (1 << 0), + FILESYSTEM_FIND_RELATIVE_PATHS = (1 << 1), + FILESYSTEM_FIND_HIDDEN_FILES = (1 << 2), + FILESYSTEM_FIND_FOLDERS = (1 << 3), + FILESYSTEM_FIND_FILES = (1 << 4), + FILESYSTEM_FIND_KEEP_ARRAY = (1 << 5), +}; + +struct FILESYSTEM_STAT_DATA +{ + u32 Attributes; + Timestamp ModificationTime; + u64 Size; +}; + +struct FILESYSTEM_FIND_DATA +{ + std::string FileName; + Timestamp ModificationTime; + u32 Attributes; + u64 Size; +}; + +struct FILESYSTEM_CHANGE_NOTIFY_DATA +{ + String DirectoryPath; + bool RecursiveWatch; + + void* pSystemData; +}; + +namespace FileSystem { + +using FindResultsArray = std::vector; + +class ChangeNotifier +{ +public: + enum ChangeEvent + { + ChangeEvent_FileAdded = (1 << 0), + ChangeEvent_FileRemoved = (1 << 1), + ChangeEvent_FileModified = (1 << 2), + ChangeEvent_RenamedOldName = (1 << 3), + ChangeEvent_RenamedNewName = (1 << 4), + }; + + struct ChangeInfo + { + const char* Path; + u32 Event; + }; + +public: + virtual ~ChangeNotifier(); + + const String& GetDirectoryPath() const { return m_directoryPath; } + const bool GetRecursiveWatch() const { return m_recursiveWatch; } + + typedef void (*EnumerateChangesCallback)(const ChangeInfo* pChangeInfo, void* pUserData); + virtual void EnumerateChanges(EnumerateChangesCallback callback, void* pUserData) = 0; + +private: + template + static void EnumerateChangesTrampoline(const ChangeInfo* pChangeInfo, void* pUserData) + { + CALLBACK_TYPE* pRealCallback = reinterpret_cast(pUserData); + (*pRealCallback)(pChangeInfo); + } + +public: + template + void EnumerateChanges(CALLBACK_TYPE callback) + { + CALLBACK_TYPE* pCallback = &callback; + EnumerateChanges(&ChangeNotifier::EnumerateChangesTrampoline, reinterpret_cast(pCallback)); + } + +protected: + ChangeNotifier(const String& directoryPath, bool recursiveWatch); + + String m_directoryPath; + bool m_recursiveWatch; +}; + +// create a change notifier +std::unique_ptr CreateChangeNotifier(const char* path, bool recursiveWatch); + +// canonicalize a path string (i.e. replace .. with actual folder name, etc), if OS path is used, on windows, the +// separators will be \, otherwise / +void CanonicalizePath(char* Destination, u32 cbDestination, const char* Path, bool OSPath = true); +void CanonicalizePath(String& Destination, const char* Path, bool OSPath = true); +void CanonicalizePath(String& Destination, bool OSPath = true); +void CanonicalizePath(std::string& path, bool OSPath = true); + +// translates the specified path into a string compatible with the hosting OS +void BuildOSPath(char* Destination, u32 cbDestination, const char* Path); +void BuildOSPath(String& Destination, const char* Path); +void BuildOSPath(String& Destination); + +// builds a path relative to the specified file, optionally canonicalizing it +void BuildPathRelativeToFile(char* Destination, u32 cbDestination, const char* CurrentFileName, const char* NewFileName, + bool OSPath = true, bool Canonicalize = true); +void BuildPathRelativeToFile(String& Destination, const char* CurrentFileName, const char* NewFileName, + bool OSPath = true, bool Canonicalize = true); +String BuildPathRelativeToFile(const char* CurrentFileName, const char* NewFileName, bool OSPath = true, + bool Canonicalize = true); + +// sanitizes a filename for use in a filesystem. +void SanitizeFileName(char* Destination, u32 cbDestination, const char* FileName, bool StripSlashes = true); +void SanitizeFileName(String& Destination, const char* FileName, bool StripSlashes = true); +void SanitizeFileName(String& Destination, bool StripSlashes = true); + +/// Returns true if the specified path is an absolute path (C:\Path on Windows or /path on Unix). +bool IsAbsolutePath(const std::string_view& path); + +/// Replaces the extension of a filename with another. +std::string ReplaceExtension(std::string_view path, std::string_view new_extension); + +/// Returns the directory component of a filename. +std::string GetPathDirectory(const char* path); + +// search for files +bool FindFiles(const char* Path, const char* Pattern, u32 Flags, FindResultsArray* pResults); + +// stat file +bool StatFile(const char* Path, FILESYSTEM_STAT_DATA* pStatData); + +// file exists? +bool FileExists(const char* Path); + +// directory exists? +bool DirectoryExists(const char* Path); + +// delete file +bool DeleteFile(const char* Path); + +// open files +std::unique_ptr OpenFile(const char* FileName, u32 Flags); + +using ManagedCFilePtr = std::unique_ptr; +ManagedCFilePtr OpenManagedCFile(const char* filename, const char* mode); +std::FILE* OpenCFile(const char* filename, const char* mode); + +std::optional> ReadBinaryFile(const char* filename); +std::optional ReadFileToString(const char* filename); +bool WriteBinaryFile(const char* filename, const void* data, size_t data_length); +bool WriteFileToString(const char* filename, const std::string_view& sv); + +std::string ReadStreamToString(ByteStream* stream, bool seek_to_start = true); +bool WriteStreamToString(const std::string_view& sv, ByteStream* stream); + +// creates a directory in the local filesystem +// if the directory already exists, the return value will be true. +// if Recursive is specified, all parent directories will be created +// if they do not exist. +bool CreateDirectory(const char* Path, bool Recursive); + +// deletes a directory in the local filesystem +// if the directory has files, unless the recursive flag is set, it will fail +bool DeleteDirectory(const char* Path, bool Recursive); + +/// Returns the path to the current executable. +std::string GetProgramPath(); + +/// Retrieves the current working directory. +std::string GetWorkingDirectory(); + +/// Sets the current working directory. Returns true if successful. +bool SetWorkingDirectory(const char* path); + +}; // namespace FileSystem diff --git a/jni/common/gl/context.cpp b/jni/common/gl/context.cpp new file mode 100644 index 0000000..861cb21 --- /dev/null +++ b/jni/common/gl/context.cpp @@ -0,0 +1,208 @@ +#include "context.h" +#include "../log.h" +#include "glad.h" +#include +#ifdef __APPLE__ +#include +#else +#include +#endif +Log_SetChannel(GL::Context); + +#if defined(WIN32) && !defined(_M_ARM64) +#include "context_wgl.h" +#elif defined(__APPLE__) && !defined(LIBERTRO) +#include "context_agl.h" +#endif + +#ifdef USE_EGL +#if defined(USE_X11) || defined(USE_WAYLAND) +#if defined(USE_X11) +#include "context_egl_x11.h" +#endif +#if defined(USE_WAYLAND) +#include "context_egl_wayland.h" +#endif +#elif defined(ANDROID) +#include "context_egl_android.h" +#else +#error Unknown EGL platform +#endif +#endif + +#ifdef USE_GLX +#include "context_glx.h" +#endif + +namespace GL { + +static bool ShouldPreferESContext() +{ +#ifndef _MSC_VER + const char* value = std::getenv("PREFER_GLES_CONTEXT"); + return (value && std::strcmp(value, "1") == 0); +#else + char buffer[2] = {}; + size_t buffer_size = sizeof(buffer); + getenv_s(&buffer_size, buffer, "PREFER_GLES_CONTEXT"); + return (std::strcmp(buffer, "1") == 0); +#endif +} + +Context::Context(const WindowInfo& wi) : m_wi(wi) {} + +Context::~Context() = default; + +std::unique_ptr Context::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + if (ShouldPreferESContext()) + { + // move ES versions to the front + Version* new_versions_to_try = static_cast(alloca(sizeof(Version) * num_versions_to_try)); + size_t count = 0; + for (size_t i = 0; i < num_versions_to_try; i++) + { + if (versions_to_try[i].profile == Profile::ES) + new_versions_to_try[count++] = versions_to_try[i]; + } + for (size_t i = 0; i < num_versions_to_try; i++) + { + if (versions_to_try[i].profile != Profile::ES) + new_versions_to_try[count++] = versions_to_try[i]; + } + versions_to_try = new_versions_to_try; + } + + std::unique_ptr context; +#if defined(WIN32) && !defined(_M_ARM64) + context = ContextWGL::Create(wi, versions_to_try, num_versions_to_try); +#elif defined(__APPLE__) && !defined(LIBRETRO) + context = ContextAGL::Create(wi, versions_to_try, num_versions_to_try); +#elif defined(ANDROID) +#ifdef USE_EGL + context = ContextEGLAndroid::Create(wi, versions_to_try, num_versions_to_try); +#endif +#endif + +#if defined(USE_X11) + if (wi.type == WindowInfo::Type::X11) + { +#ifdef USE_EGL + const char* use_egl_x11 = std::getenv("USE_EGL_X11"); + if (use_egl_x11 && std::strcmp(use_egl_x11, "1") == 0) + context = ContextEGLX11::Create(wi, versions_to_try, num_versions_to_try); + else + context = ContextGLX::Create(wi, versions_to_try, num_versions_to_try); +#else + context = ContextGLX::Create(wi, versions_to_try, num_versions_to_try); +#endif + } +#endif + +#if defined(USE_WAYLAND) + if (wi.type == WindowInfo::Type::Wayland) + context = ContextEGLWayland::Create(wi, versions_to_try, num_versions_to_try); +#endif + + if (!context) + return nullptr; + + Log_InfoPrintf("Created a %s context", context->IsGLES() ? "OpenGL ES" : "OpenGL"); + + // TODO: Not thread-safe. + static Context* context_being_created; + context_being_created = context.get(); + + // load up glad + if (!context->IsGLES()) + { + if (!gladLoadGLLoader([](const char* name) { return context_being_created->GetProcAddress(name); })) + { + Log_ErrorPrintf("Failed to load GL functions for GLAD"); + return nullptr; + } + } + else + { + if (!gladLoadGLES2Loader([](const char* name) { return context_being_created->GetProcAddress(name); })) + { + Log_ErrorPrintf("Failed to load GLES functions for GLAD"); + return nullptr; + } + } + + const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR)); + const char* gl_renderer = reinterpret_cast(glGetString(GL_RENDERER)); + const char* gl_version = reinterpret_cast(glGetString(GL_VERSION)); + const char* gl_shading_language_version = reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)); + Log_InfoPrintf("GL_VENDOR: %s", gl_vendor); + Log_InfoPrintf("GL_RENDERER: %s", gl_renderer); + Log_InfoPrintf("GL_VERSION: %s", gl_version); + Log_InfoPrintf("GL_SHADING_LANGUAGE_VERSION: %s", gl_shading_language_version); + + return context; +} + +const std::array& Context::GetAllDesktopVersionsList() +{ + static constexpr std::array vlist = {{{Profile::Core, 4, 6}, + {Profile::Core, 4, 5}, + {Profile::Core, 4, 4}, + {Profile::Core, 4, 3}, + {Profile::Core, 4, 2}, + {Profile::Core, 4, 1}, + {Profile::Core, 4, 0}, + {Profile::Core, 3, 3}, + {Profile::Core, 3, 2}, + {Profile::Core, 3, 1}, + {Profile::Core, 3, 0}}}; + return vlist; +} + +const std::array& Context::GetAllDesktopVersionsListWithFallback() +{ + static constexpr std::array vlist = {{{Profile::Core, 4, 6}, + {Profile::Core, 4, 5}, + {Profile::Core, 4, 4}, + {Profile::Core, 4, 3}, + {Profile::Core, 4, 2}, + {Profile::Core, 4, 1}, + {Profile::Core, 4, 0}, + {Profile::Core, 3, 3}, + {Profile::Core, 3, 2}, + {Profile::Core, 3, 1}, + {Profile::Core, 3, 0}, + {Profile::NoProfile, 0, 0}}}; + return vlist; +} + +const std::array& Context::GetAllESVersionsList() +{ + static constexpr std::array vlist = { + {{Profile::ES, 3, 2}, {Profile::ES, 3, 1}, {Profile::ES, 3, 0}, {Profile::ES, 2, 0}}}; + return vlist; +} + +const std::array& Context::GetAllVersionsList() +{ + static constexpr std::array vlist = {{{Profile::Core, 4, 6}, + {Profile::Core, 4, 5}, + {Profile::Core, 4, 4}, + {Profile::Core, 4, 3}, + {Profile::Core, 4, 2}, + {Profile::Core, 4, 1}, + {Profile::Core, 4, 0}, + {Profile::Core, 3, 3}, + {Profile::Core, 3, 2}, + {Profile::Core, 3, 1}, + {Profile::Core, 3, 0}, + {Profile::ES, 3, 2}, + {Profile::ES, 3, 1}, + {Profile::ES, 3, 0}, + {Profile::ES, 2, 0}, + {Profile::NoProfile, 0, 0}}}; + return vlist; +} + +} // namespace GL diff --git a/jni/common/gl/context.h b/jni/common/gl/context.h new file mode 100644 index 0000000..695ca5d --- /dev/null +++ b/jni/common/gl/context.h @@ -0,0 +1,66 @@ +#pragma once +#include "../types.h" +#include "../window_info.h" +#include +#include + +namespace GL { +class Context +{ +public: + Context(const WindowInfo& wi); + virtual ~Context(); + + enum class Profile + { + NoProfile, + Core, + ES + }; + + struct Version + { + Profile profile; + int major_version; + int minor_version; + }; + + ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_wi; } + ALWAYS_INLINE bool IsGLES() const { return (m_version.profile == Profile::ES); } + ALWAYS_INLINE u32 GetSurfaceWidth() const { return m_wi.surface_width; } + ALWAYS_INLINE u32 GetSurfaceHeight() const { return m_wi.surface_height; } + ALWAYS_INLINE WindowInfo::SurfaceFormat GetSurfaceFormat() const { return m_wi.surface_format; } + + virtual void* GetProcAddress(const char* name) = 0; + virtual bool ChangeSurface(const WindowInfo& new_wi) = 0; + virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) = 0; + virtual bool SwapBuffers() = 0; + virtual bool MakeCurrent() = 0; + virtual bool DoneCurrent() = 0; + virtual bool SetSwapInterval(s32 interval) = 0; + virtual std::unique_ptr CreateSharedContext(const WindowInfo& wi) = 0; + + static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + template + static std::unique_ptr Create(const WindowInfo& wi, const std::array& versions_to_try) + { + return Create(wi, versions_to_try.data(), versions_to_try.size()); + } + + static std::unique_ptr Create(const WindowInfo& wi) { return Create(wi, GetAllVersionsList()); } + + static const std::array& GetAllDesktopVersionsList(); + static const std::array& GetAllDesktopVersionsListWithFallback(); + static const std::array& GetAllESVersionsList(); + static const std::array& GetAllVersionsList(); + +protected: +#ifdef WIN32 +#endif + + WindowInfo m_wi; + Version m_version = {}; +}; +} // namespace GL diff --git a/jni/common/gl/context_agl.h b/jni/common/gl/context_agl.h new file mode 100644 index 0000000..25d69fe --- /dev/null +++ b/jni/common/gl/context_agl.h @@ -0,0 +1,49 @@ +#pragma once +#include "context.h" +#include + +#if defined(__APPLE__) && defined(__OBJC__) +#import +#else +struct NSOpenGLContext; +struct NSOpenGLPixelFormat; +struct NSView; +#define __bridge +#endif + +namespace GL { + +class ContextAGL final : public Context +{ +public: + ContextAGL(const WindowInfo& wi); + ~ContextAGL() override; + + static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + void* GetProcAddress(const char* name) override; + bool ChangeSurface(const WindowInfo& new_wi) override; + void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + bool SwapBuffers() override; + bool MakeCurrent() override; + bool DoneCurrent() override; + bool SetSwapInterval(s32 interval) override; + std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + +private: + ALWAYS_INLINE NSView* GetView() const { return static_cast((__bridge NSView*)m_wi.window_handle); } + + bool Initialize(const Version* versions_to_try, size_t num_versions_to_try); + bool CreateContext(NSOpenGLContext* share_context, int profile, bool make_current); + void BindContextToView(); + + // returns true if dimensions have changed + bool UpdateDimensions(); + + NSOpenGLContext* m_context = nullptr; + NSOpenGLPixelFormat* m_pixel_format = nullptr; + void* m_opengl_module_handle = nullptr; +}; + +} // namespace GL diff --git a/jni/common/gl/context_agl.mm b/jni/common/gl/context_agl.mm new file mode 100644 index 0000000..1af1a08 --- /dev/null +++ b/jni/common/gl/context_agl.mm @@ -0,0 +1,214 @@ +#include "context_agl.h" +#include "../assert.h" +#include "../log.h" +#include "glad.h" +#include +Log_SetChannel(GL::ContextAGL); + +namespace GL { +ContextAGL::ContextAGL(const WindowInfo& wi) : Context(wi) +{ + m_opengl_module_handle = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_NOW); + if (!m_opengl_module_handle) + Log_ErrorPrint("Could not open OpenGL.framework, function lookups will probably fail"); +} + +ContextAGL::~ContextAGL() +{ + if ([NSOpenGLContext currentContext] == m_context) + [NSOpenGLContext clearCurrentContext]; + + if (m_context) + [m_context release]; + + if (m_pixel_format) + [m_pixel_format release]; + + if (m_opengl_module_handle) + dlclose(m_opengl_module_handle); +} + +std::unique_ptr ContextAGL::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr context = std::make_unique(wi); + if (!context->Initialize(versions_to_try, num_versions_to_try)) + return nullptr; + + return context; +} + +bool ContextAGL::Initialize(const Version* versions_to_try, size_t num_versions_to_try) +{ + for (size_t i = 0; i < num_versions_to_try; i++) + { + const Version& cv = versions_to_try[i]; + if (cv.profile == Profile::NoProfile && CreateContext(nullptr, NSOpenGLProfileVersionLegacy, true)) + { + // we already have the dummy context, so just use that + m_version = cv; + return true; + } + else if (cv.profile == Profile::Core) + { + if (cv.major_version > 4 || cv.minor_version > 1) + continue; + + const NSOpenGLPixelFormatAttribute profile = (cv.major_version > 3 || cv.minor_version > 2) ? NSOpenGLProfileVersion4_1Core : NSOpenGLProfileVersion3_2Core; + if (CreateContext(nullptr, static_cast(profile), true)) + { + m_version = cv; + return true; + } + } + } + + return false; +} + +void* ContextAGL::GetProcAddress(const char* name) +{ + void* addr = m_opengl_module_handle ? dlsym(m_opengl_module_handle, name) : nullptr; + if (addr) + return addr; + + return dlsym(RTLD_NEXT, name); +} + +bool ContextAGL::ChangeSurface(const WindowInfo& new_wi) +{ + m_wi = new_wi; + BindContextToView(); + return true; +} + +void ContextAGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) +{ + UpdateDimensions(); +} + +bool ContextAGL::UpdateDimensions() +{ + const NSSize window_size = [GetView() frame].size; + const CGFloat window_scale = [[GetView() window] backingScaleFactor]; + const u32 new_width = static_cast(static_cast(window_size.width) * window_scale); + const u32 new_height = static_cast(static_cast(window_size.height) * window_scale); + + if (m_wi.surface_width == new_width && m_wi.surface_height == new_height) + return false; + + m_wi.surface_width = new_width; + m_wi.surface_height = new_height; + + dispatch_block_t block = ^{ + [m_context update]; + }; + + if ([NSThread isMainThread]) + block(); + else + dispatch_sync(dispatch_get_main_queue(), block); + + return true; +} + +bool ContextAGL::SwapBuffers() +{ + [m_context flushBuffer]; + return true; +} + +bool ContextAGL::MakeCurrent() +{ + [m_context makeCurrentContext]; + return true; +} + +bool ContextAGL::DoneCurrent() +{ + [NSOpenGLContext clearCurrentContext]; + return true; +} + +bool ContextAGL::SetSwapInterval(s32 interval) +{ + GLint gl_interval = static_cast(interval); + [m_context setValues:&gl_interval forParameter:NSOpenGLCPSwapInterval]; + return true; +} + +std::unique_ptr ContextAGL::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + + context->m_context = [[NSOpenGLContext alloc] initWithFormat:m_pixel_format shareContext:m_context]; + if (context->m_context == nil) + return nullptr; + + context->m_version = m_version; + context->m_pixel_format = m_pixel_format; + [context->m_pixel_format retain]; + + if (wi.type == WindowInfo::Type::MacOS) + context->BindContextToView(); + + return context; +} + +bool ContextAGL::CreateContext(NSOpenGLContext* share_context, int profile, bool make_current) +{ + if (m_context) + { + [m_context release]; + m_context = nullptr; + } + + if (m_pixel_format) + [m_pixel_format release]; + + const std::array attribs = {{ + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAOpenGLProfile, + static_cast(profile), + NSOpenGLPFAAccelerated, + 0}}; + m_pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs.data()]; + if (m_pixel_format == nil) + { + Log_ErrorPrintf("Failed to initialize pixel format"); + return false; + } + + m_context = [[NSOpenGLContext alloc] initWithFormat:m_pixel_format shareContext:nil]; + if (m_context == nil) + return false; + + if (m_wi.type == WindowInfo::Type::MacOS) + BindContextToView(); + + if (make_current) + [m_context makeCurrentContext]; + + return true; +} + +void ContextAGL::BindContextToView() +{ + NSView* const view = GetView(); + NSWindow* const window = [view window]; + [view setWantsBestResolutionOpenGLSurface:YES]; + + UpdateDimensions(); + + dispatch_block_t block = ^{ + [window makeFirstResponder:view]; + [m_context setView:view]; + [window makeKeyAndOrderFront:nil]; + }; + + if ([NSThread isMainThread]) + block(); + else + dispatch_sync(dispatch_get_main_queue(), block); +} +} // namespace GL diff --git a/jni/common/gl/context_egl.cpp b/jni/common/gl/context_egl.cpp new file mode 100644 index 0000000..2ee2129 --- /dev/null +++ b/jni/common/gl/context_egl.cpp @@ -0,0 +1,344 @@ +#include "context_egl.h" +#include "../assert.h" +#include "../log.h" +Log_SetChannel(GL::ContextEGL); + +namespace GL { +ContextEGL::ContextEGL(const WindowInfo& wi) : Context(wi) {} + +ContextEGL::~ContextEGL() +{ + if (eglGetCurrentContext() == m_context) + eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (m_context) + eglDestroyContext(m_display, m_context); +} + +std::unique_ptr ContextEGL::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr context = std::make_unique(wi); + if (!context->Initialize(versions_to_try, num_versions_to_try)) + return nullptr; + + return context; +} + +bool ContextEGL::Initialize(const Version* versions_to_try, size_t num_versions_to_try) +{ + if (!gladLoadEGL()) + { + Log_ErrorPrintf("Loading GLAD EGL functions failed"); + return false; + } + + m_display = eglGetDisplay(static_cast(m_wi.display_connection)); + if (!m_display) + { + Log_ErrorPrintf("eglGetDisplay() failed: %d", eglGetError()); + return false; + } + + int egl_major, egl_minor; + if (!eglInitialize(m_display, &egl_major, &egl_minor)) + { + Log_ErrorPrintf("eglInitialize() failed: %d", eglGetError()); + return false; + } + Log_InfoPrintf("EGL Version: %d.%d", egl_major, egl_minor); + + const char* extensions = eglQueryString(m_display, EGL_EXTENSIONS); + if (extensions) + { + Log_InfoPrintf("EGL Extensions: %s", extensions); + m_supports_surfaceless = std::strstr(extensions, "EGL_KHR_surfaceless_context") != nullptr; + } + if (!m_supports_surfaceless) + Log_WarningPrint("EGL implementation does not support surfaceless contexts, emulating with pbuffers"); + + for (size_t i = 0; i < num_versions_to_try; i++) + { + if (CreateContextAndSurface(versions_to_try[i], nullptr, true)) + return true; + } + + return false; +} + +void* ContextEGL::GetProcAddress(const char* name) +{ + return reinterpret_cast(eglGetProcAddress(name)); +} + +bool ContextEGL::ChangeSurface(const WindowInfo& new_wi) +{ + const bool was_current = (eglGetCurrentContext() == m_context); + if (was_current) + eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (m_surface != EGL_NO_SURFACE) + { + eglDestroySurface(m_display, m_surface); + m_surface = EGL_NO_SURFACE; + } + + m_wi = new_wi; + if (!CreateSurface()) + return false; + + if (was_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context)) + { + Log_ErrorPrintf("Failed to make context current again after surface change"); + return false; + } + + return true; +} + +void ContextEGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) +{ + // This seems to race on Android... +#ifndef ANDROID + EGLint surface_width, surface_height; + if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) && + eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height)) + { + m_wi.surface_width = static_cast(surface_width); + m_wi.surface_height = static_cast(surface_height); + return; + } + else + { + Log_ErrorPrintf("eglQuerySurface() failed: %d", eglGetError()); + } +#endif + + m_wi.surface_width = new_surface_width; + m_wi.surface_height = new_surface_height; +} + +bool ContextEGL::SwapBuffers() +{ + return eglSwapBuffers(m_display, m_surface); +} + +bool ContextEGL::MakeCurrent() +{ + if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context)) + { + Log_ErrorPrintf("eglMakeCurrent() failed: %d", eglGetError()); + return false; + } + + return true; +} + +bool ContextEGL::DoneCurrent() +{ + return eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +bool ContextEGL::SetSwapInterval(s32 interval) +{ + return eglSwapInterval(m_display, interval); +} + +std::unique_ptr ContextEGL::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + context->m_display = m_display; + context->m_supports_surfaceless = m_supports_surfaceless; + + if (!context->CreateContextAndSurface(m_version, m_context, false)) + return nullptr; + + return context; +} + +EGLNativeWindowType ContextEGL::GetNativeWindow(EGLConfig config) +{ + return {}; +} + +bool ContextEGL::CreateSurface() +{ + if (m_wi.type == WindowInfo::Type::Surfaceless) + { + if (m_supports_surfaceless) + return true; + else + return CreatePBufferSurface(); + } + + EGLNativeWindowType native_window = GetNativeWindow(m_config); + m_surface = eglCreateWindowSurface(m_display, m_config, native_window, nullptr); + if (!m_surface) + { + Log_ErrorPrintf("eglCreateWindowSurface() failed: %d", eglGetError()); + return false; + } + + // Some implementations may require the size to be queried at runtime. + EGLint surface_width, surface_height; + if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) && + eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height)) + { + m_wi.surface_width = static_cast(surface_width); + m_wi.surface_height = static_cast(surface_height); + } + else + { + Log_ErrorPrintf("eglQuerySurface() failed: %d", eglGetError()); + } + + return true; +} + +bool ContextEGL::CreatePBufferSurface() +{ + const u32 width = std::max(m_wi.surface_width, 1); + const u32 height = std::max(m_wi.surface_height, 1); + + // TODO: Format + EGLint attrib_list[] = { + EGL_WIDTH, static_cast(width), EGL_HEIGHT, static_cast(height), EGL_NONE, + }; + + m_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list); + if (!m_surface) + { + Log_ErrorPrintf("eglCreatePbufferSurface() failed: %d", eglGetError()); + return false; + } + + Log_DevPrintf("Created %ux%u pbuffer surface", width, height); + return true; +} + +bool ContextEGL::CreateContext(const Version& version, EGLContext share_context) +{ + Log_DevPrintf( + "Trying version %u.%u (%s)", version.major_version, version.minor_version, + version.profile == Context::Profile::ES ? "ES" : (version.profile == Context::Profile::Core ? "Core" : "None")); + int surface_attribs[16] = { + EGL_RENDERABLE_TYPE, + (version.profile == Profile::ES) ? + ((version.major_version >= 3) ? EGL_OPENGL_ES3_BIT : + ((version.major_version == 2) ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT)) : + EGL_OPENGL_BIT, + EGL_SURFACE_TYPE, + (m_wi.type != WindowInfo::Type::Surfaceless) ? EGL_WINDOW_BIT : 0, + }; + int nsurface_attribs = 4; + + switch (m_wi.surface_format) + { + case WindowInfo::SurfaceFormat::RGB8: + surface_attribs[nsurface_attribs++] = EGL_RED_SIZE; + surface_attribs[nsurface_attribs++] = 8; + surface_attribs[nsurface_attribs++] = EGL_GREEN_SIZE; + surface_attribs[nsurface_attribs++] = 8; + surface_attribs[nsurface_attribs++] = EGL_BLUE_SIZE; + surface_attribs[nsurface_attribs++] = 8; + break; + + case WindowInfo::SurfaceFormat::RGBA8: + surface_attribs[nsurface_attribs++] = EGL_RED_SIZE; + surface_attribs[nsurface_attribs++] = 8; + surface_attribs[nsurface_attribs++] = EGL_GREEN_SIZE; + surface_attribs[nsurface_attribs++] = 8; + surface_attribs[nsurface_attribs++] = EGL_BLUE_SIZE; + surface_attribs[nsurface_attribs++] = 8; + surface_attribs[nsurface_attribs++] = EGL_ALPHA_SIZE; + surface_attribs[nsurface_attribs++] = 8; + break; + + case WindowInfo::SurfaceFormat::RGB565: + surface_attribs[nsurface_attribs++] = EGL_RED_SIZE; + surface_attribs[nsurface_attribs++] = 5; + surface_attribs[nsurface_attribs++] = EGL_GREEN_SIZE; + surface_attribs[nsurface_attribs++] = 6; + surface_attribs[nsurface_attribs++] = EGL_BLUE_SIZE; + surface_attribs[nsurface_attribs++] = 5; + break; + + default: + UnreachableCode(); + break; + } + + surface_attribs[nsurface_attribs++] = EGL_NONE; + surface_attribs[nsurface_attribs++] = 0; + + EGLint num_configs; + EGLConfig config; + if (!eglChooseConfig(m_display, surface_attribs, &config, 1, &num_configs) || num_configs == 0) + { + Log_ErrorPrintf("eglChooseConfig() failed: %d", eglGetError()); + return false; + } + + int attribs[8]; + int nattribs = 0; + if (version.profile != Profile::NoProfile) + { + attribs[nattribs++] = EGL_CONTEXT_MAJOR_VERSION; + attribs[nattribs++] = version.major_version; + attribs[nattribs++] = EGL_CONTEXT_MINOR_VERSION; + attribs[nattribs++] = version.minor_version; + } + attribs[nattribs++] = EGL_NONE; + attribs[nattribs++] = 0; + + if (!eglBindAPI((version.profile == Profile::ES) ? EGL_OPENGL_ES_API : EGL_OPENGL_API)) + { + Log_ErrorPrintf("eglBindAPI(%s) failed", (version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API"); + return false; + } + + m_context = eglCreateContext(m_display, config, share_context, attribs); + if (!m_context) + { + Log_ErrorPrintf("eglCreateContext() failed: %d", eglGetError()); + return false; + } + + Log_InfoPrintf( + "Got version %u.%u (%s)", version.major_version, version.minor_version, + version.profile == Context::Profile::ES ? "ES" : (version.profile == Context::Profile::Core ? "Core" : "None")); + + m_config = config; + m_version = version; + return true; +} + +bool ContextEGL::CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current) +{ + if (!CreateContext(version, share_context)) + return false; + + if (!CreateSurface()) + { + Log_ErrorPrintf("Failed to create surface for context"); + eglDestroyContext(m_display, m_context); + m_context = EGL_NO_CONTEXT; + return false; + } + + if (make_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context)) + { + Log_ErrorPrintf("eglMakeCurrent() failed: %d", eglGetError()); + if (m_surface != EGL_NO_SURFACE) + { + eglDestroySurface(m_display, m_surface); + m_surface = EGL_NO_SURFACE; + } + eglDestroyContext(m_display, m_context); + m_context = EGL_NO_CONTEXT; + return false; + } + + return true; +} +} // namespace GL diff --git a/jni/common/gl/context_egl.h b/jni/common/gl/context_egl.h new file mode 100644 index 0000000..ce22898 --- /dev/null +++ b/jni/common/gl/context_egl.h @@ -0,0 +1,44 @@ +#pragma once +#include "context.h" +#include "glad_egl.h" + +namespace GL { + +class ContextEGL : public Context +{ +public: + ContextEGL(const WindowInfo& wi); + ~ContextEGL() override; + + static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + void* GetProcAddress(const char* name) override; + virtual bool ChangeSurface(const WindowInfo& new_wi) override; + virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + bool SwapBuffers() override; + bool MakeCurrent() override; + bool DoneCurrent() override; + bool SetSwapInterval(s32 interval) override; + virtual std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + +protected: + virtual EGLNativeWindowType GetNativeWindow(EGLConfig config); + + bool Initialize(const Version* versions_to_try, size_t num_versions_to_try); + bool CreateDisplay(); + bool CreateContext(const Version& version, EGLContext share_context); + bool CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current); + bool CreateSurface(); + bool CreatePBufferSurface(); + + EGLDisplay m_display = EGL_NO_DISPLAY; + EGLSurface m_surface = EGL_NO_SURFACE; + EGLContext m_context = EGL_NO_CONTEXT; + + EGLConfig m_config = {}; + + bool m_supports_surfaceless = false; +}; + +} // namespace GL diff --git a/jni/common/gl/context_egl_android.cpp b/jni/common/gl/context_egl_android.cpp new file mode 100644 index 0000000..daf7b23 --- /dev/null +++ b/jni/common/gl/context_egl_android.cpp @@ -0,0 +1,45 @@ +#include "context_egl_android.h" +#include "../log.h" +#include +Log_SetChannel(GL::ContextEGLAndroid); + +namespace GL { +ContextEGLAndroid::ContextEGLAndroid(const WindowInfo& wi) : ContextEGL(wi) {} +ContextEGLAndroid::~ContextEGLAndroid() = default; + +std::unique_ptr ContextEGLAndroid::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr context = std::make_unique(wi); + if (!context->Initialize(versions_to_try, num_versions_to_try)) + return nullptr; + + return context; +} + +std::unique_ptr ContextEGLAndroid::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + context->m_display = m_display; + + if (!context->CreateContextAndSurface(m_version, m_context, false)) + return nullptr; + + return context; +} + +EGLNativeWindowType ContextEGLAndroid::GetNativeWindow(EGLConfig config) +{ + EGLint native_visual_id = 0; + if (!eglGetConfigAttrib(m_display, m_config, EGL_NATIVE_VISUAL_ID, &native_visual_id)) + { + Log_ErrorPrintf("Failed to get native visual ID"); + return 0; + } + + ANativeWindow_setBuffersGeometry(static_cast(m_wi.window_handle), 0, 0, static_cast(native_visual_id)); + m_wi.surface_width = ANativeWindow_getWidth(static_cast(m_wi.window_handle)); + m_wi.surface_height = ANativeWindow_getHeight(static_cast(m_wi.window_handle)); + return static_cast(m_wi.window_handle); +} +} // namespace GL diff --git a/jni/common/gl/context_egl_android.h b/jni/common/gl/context_egl_android.h new file mode 100644 index 0000000..1b2de02 --- /dev/null +++ b/jni/common/gl/context_egl_android.h @@ -0,0 +1,21 @@ +#pragma once +#include "context_egl.h" + +namespace GL { + +class ContextEGLAndroid final : public ContextEGL +{ +public: + ContextEGLAndroid(const WindowInfo& wi); + ~ContextEGLAndroid() override; + + static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + +protected: + EGLNativeWindowType GetNativeWindow(EGLConfig config) override; +}; + +} // namespace GL diff --git a/jni/common/gl/context_egl_wayland.cpp b/jni/common/gl/context_egl_wayland.cpp new file mode 100644 index 0000000..49596e1 --- /dev/null +++ b/jni/common/gl/context_egl_wayland.cpp @@ -0,0 +1,58 @@ +#include "context_egl_wayland.h" +#include "../log.h" +#include +Log_SetChannel(GL::ContextEGLWayland); + +namespace GL { +ContextEGLWayland::ContextEGLWayland(const WindowInfo& wi) : ContextEGL(wi) {} +ContextEGLWayland::~ContextEGLWayland() +{ + if (m_wl_window) + wl_egl_window_destroy(m_wl_window); +} + +std::unique_ptr ContextEGLWayland::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr context = std::make_unique(wi); + if (!context->Initialize(versions_to_try, num_versions_to_try)) + return nullptr; + + return context; +} + +std::unique_ptr ContextEGLWayland::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + context->m_display = m_display; + + if (!context->CreateContextAndSurface(m_version, m_context, false)) + return nullptr; + + return context; +} + +void ContextEGLWayland::ResizeSurface(u32 new_surface_width, u32 new_surface_height) +{ + if (m_wl_window) + wl_egl_window_resize(m_wl_window, new_surface_width, new_surface_height, 0, 0); + + ContextEGL::ResizeSurface(new_surface_width, new_surface_height); +} + +EGLNativeWindowType ContextEGLWayland::GetNativeWindow(EGLConfig config) +{ + if (m_wl_window) + { + wl_egl_window_destroy(m_wl_window); + m_wl_window = nullptr; + } + + m_wl_window = + wl_egl_window_create(static_cast(m_wi.window_handle), m_wi.surface_width, m_wi.surface_height); + if (!m_wl_window) + return {}; + + return reinterpret_cast(m_wl_window); +} +} // namespace GL diff --git a/jni/common/gl/context_egl_wayland.h b/jni/common/gl/context_egl_wayland.h new file mode 100644 index 0000000..d57dd82 --- /dev/null +++ b/jni/common/gl/context_egl_wayland.h @@ -0,0 +1,26 @@ +#pragma once +#include "context_egl.h" +#include + +namespace GL { + +class ContextEGLWayland final : public ContextEGL +{ +public: + ContextEGLWayland(const WindowInfo& wi); + ~ContextEGLWayland() override; + + static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + +protected: + EGLNativeWindowType GetNativeWindow(EGLConfig config) override; + +private: + wl_egl_window* m_wl_window = nullptr; +}; + +} // namespace GL diff --git a/jni/common/gl/context_egl_x11.cpp b/jni/common/gl/context_egl_x11.cpp new file mode 100644 index 0000000..c7310e3 --- /dev/null +++ b/jni/common/gl/context_egl_x11.cpp @@ -0,0 +1,69 @@ +#include "context_egl_x11.h" +#include "../log.h" +Log_SetChannel(GL::ContextEGLX11); + +namespace GL { +ContextEGLX11::ContextEGLX11(const WindowInfo& wi) : ContextEGL(wi) {} +ContextEGLX11::~ContextEGLX11() = default; + +std::unique_ptr ContextEGLX11::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr context = std::make_unique(wi); + if (!context->Initialize(versions_to_try, num_versions_to_try)) + return nullptr; + + return context; +} + +std::unique_ptr ContextEGLX11::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + context->m_display = m_display; + + if (!context->CreateContextAndSurface(m_version, m_context, false)) + return nullptr; + + return context; +} + +void ContextEGLX11::ResizeSurface(u32 new_surface_width, u32 new_surface_height) +{ + m_window.Resize(); + ContextEGL::ResizeSurface(new_surface_width, new_surface_height); +} + +EGLNativeWindowType ContextEGLX11::GetNativeWindow(EGLConfig config) +{ + X11InhibitErrors ei; + + EGLint native_visual_id = 0; + if (!eglGetConfigAttrib(m_display, m_config, EGL_NATIVE_VISUAL_ID, &native_visual_id)) + { + Log_ErrorPrintf("Failed to get X11 visual ID"); + return false; + } + + XVisualInfo vi_query = {}; + vi_query.visualid = native_visual_id; + + int num_vis; + XVisualInfo* vi = XGetVisualInfo(static_cast(m_wi.display_connection), VisualIDMask, &vi_query, &num_vis); + if (num_vis <= 0 || !vi) + { + Log_ErrorPrintf("Failed to query visual from X11"); + return false; + } + + m_window.Destroy(); + if (!m_window.Create(GetDisplay(), static_cast(reinterpret_cast(m_wi.window_handle)), vi)) + { + Log_ErrorPrintf("Faild to create X11 child window"); + XFree(vi); + return false; + } + + XFree(vi); + return static_cast(m_window.GetWindow()); +} +} // namespace GL diff --git a/jni/common/gl/context_egl_x11.h b/jni/common/gl/context_egl_x11.h new file mode 100644 index 0000000..7def8bf --- /dev/null +++ b/jni/common/gl/context_egl_x11.h @@ -0,0 +1,28 @@ +#pragma once +#include "context_egl.h" +#include "x11_window.h" + +namespace GL { + +class ContextEGLX11 final : public ContextEGL +{ +public: + ContextEGLX11(const WindowInfo& wi); + ~ContextEGLX11() override; + + static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + +protected: + EGLNativeWindowType GetNativeWindow(EGLConfig config) override; + +private: + ALWAYS_INLINE Display* GetDisplay() const { return static_cast(m_wi.display_connection); } + + X11Window m_window; +}; + +} // namespace GL diff --git a/jni/common/gl/context_glx.cpp b/jni/common/gl/context_glx.cpp new file mode 100644 index 0000000..a46037b --- /dev/null +++ b/jni/common/gl/context_glx.cpp @@ -0,0 +1,325 @@ +#include "context_glx.h" +#include "../assert.h" +#include "../log.h" +#include +Log_SetChannel(GL::ContextGLX); + +namespace GL { +ContextGLX::ContextGLX(const WindowInfo& wi) : Context(wi) {} + +ContextGLX::~ContextGLX() +{ + if (glXGetCurrentContext() == m_context) + glXMakeCurrent(GetDisplay(), None, nullptr); + + if (m_context) + glXDestroyContext(GetDisplay(), m_context); + + if (m_vi) + XFree(m_vi); + + if (m_libGL_handle) + dlclose(m_libGL_handle); +} + +std::unique_ptr ContextGLX::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr context = std::make_unique(wi); + if (!context->Initialize(versions_to_try, num_versions_to_try)) + return nullptr; + + return context; +} + +bool ContextGLX::Initialize(const Version* versions_to_try, size_t num_versions_to_try) +{ + // We need libGL loaded, because GLAD loads its own, then releases it. + m_libGL_handle = dlopen("libGL.so.1", RTLD_NOW | RTLD_GLOBAL); + if (!m_libGL_handle) + { + m_libGL_handle = dlopen("libGL.so", RTLD_NOW | RTLD_GLOBAL); + if (!m_libGL_handle) + { + Log_ErrorPrintf("Failed to load libGL.so: %s", dlerror()); + return false; + } + } + + const int screen = DefaultScreen(GetDisplay()); + if (!gladLoadGLX(GetDisplay(), screen)) + { + Log_ErrorPrintf("Loading GLAD GLX functions failed"); + return false; + } + + if (m_wi.type == WindowInfo::Type::X11) + { + if (!CreateWindow(screen)) + return false; + } + else + { + Panic("Create pbuffer"); + } + + for (size_t i = 0; i < num_versions_to_try; i++) + { + const Version& cv = versions_to_try[i]; + if (cv.profile == Profile::NoProfile && CreateAnyContext(nullptr, true)) + { + m_version = cv; + return true; + } + else if (cv.profile != Profile::NoProfile && CreateVersionContext(cv, nullptr, true)) + { + m_version = cv; + return true; + } + } + + return false; +} + +void* ContextGLX::GetProcAddress(const char* name) +{ + return reinterpret_cast(glXGetProcAddress(reinterpret_cast(name))); +} + +bool ContextGLX::ChangeSurface(const WindowInfo& new_wi) +{ + const bool was_current = (glXGetCurrentContext() == m_context); + if (was_current) + glXMakeCurrent(GetDisplay(), None, nullptr); + + m_window.Destroy(); + m_wi = new_wi; + + if (new_wi.type == WindowInfo::Type::X11) + { + const int screen = DefaultScreen(GetDisplay()); + if (!CreateWindow(screen)) + return false; + } + + if (was_current && !glXMakeCurrent(GetDisplay(), GetDrawable(), m_context)) + { + Log_ErrorPrintf("Failed to make context current again after surface change"); + return false; + } + + return true; +} + +void ContextGLX::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) +{ + m_window.Resize(); + m_wi.surface_width = m_window.GetWidth(); + m_wi.surface_height = m_window.GetHeight(); +} + +bool ContextGLX::SwapBuffers() +{ + glXSwapBuffers(GetDisplay(), GetDrawable()); + return true; +} + +bool ContextGLX::MakeCurrent() +{ + return (glXMakeCurrent(GetDisplay(), GetDrawable(), m_context) == True); +} + +bool ContextGLX::DoneCurrent() +{ + return (glXMakeCurrent(GetDisplay(), None, nullptr) == True); +} + +bool ContextGLX::SetSwapInterval(s32 interval) +{ + if (GLAD_GLX_EXT_swap_control) + { + glXSwapIntervalEXT(GetDisplay(), GetDrawable(), interval); + return true; + } + else if (GLAD_GLX_MESA_swap_control) + { + return (glXSwapIntervalMESA(static_cast(std::max(interval, 0))) != 0); + } + else if (GLAD_GLX_SGI_swap_control) + { + return (glXSwapIntervalSGI(interval) != 0); + } + else + { + return false; + } +} + +std::unique_ptr ContextGLX::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + if (wi.type == WindowInfo::Type::X11) + { + const int screen = DefaultScreen(context->GetDisplay()); + if (!context->CreateWindow(screen)) + return nullptr; + } + else + { + Panic("Create pbuffer"); + } + + if (m_version.profile == Profile::NoProfile) + { + if (!context->CreateAnyContext(m_context, false)) + return nullptr; + } + else + { + if (!context->CreateVersionContext(m_version, m_context, false)) + return nullptr; + } + + context->m_version = m_version; + return context; +} + +bool ContextGLX::CreateWindow(int screen) +{ + int attribs[32] = {GLX_X_RENDERABLE, True, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_DOUBLEBUFFER, True}; + int nattribs = 8; + + switch (m_wi.surface_format) + { + case WindowInfo::SurfaceFormat::RGB8: + attribs[nattribs++] = GLX_RED_SIZE; + attribs[nattribs++] = 8; + attribs[nattribs++] = GLX_GREEN_SIZE; + attribs[nattribs++] = 8; + attribs[nattribs++] = GLX_BLUE_SIZE; + attribs[nattribs++] = 8; + break; + + case WindowInfo::SurfaceFormat::RGBA8: + attribs[nattribs++] = GLX_RED_SIZE; + attribs[nattribs++] = 8; + attribs[nattribs++] = GLX_GREEN_SIZE; + attribs[nattribs++] = 8; + attribs[nattribs++] = GLX_BLUE_SIZE; + attribs[nattribs++] = 8; + attribs[nattribs++] = GLX_ALPHA_SIZE; + attribs[nattribs++] = 8; + break; + + case WindowInfo::SurfaceFormat::RGB565: + attribs[nattribs++] = GLX_RED_SIZE; + attribs[nattribs++] = 5; + attribs[nattribs++] = GLX_GREEN_SIZE; + attribs[nattribs++] = 6; + attribs[nattribs++] = GLX_BLUE_SIZE; + attribs[nattribs++] = 5; + break; + + default: + UnreachableCode(); + break; + } + + attribs[nattribs++] = None; + attribs[nattribs++] = 0; + + int fbcount = 0; + GLXFBConfig* fbc = glXChooseFBConfig(GetDisplay(), screen, attribs, &fbcount); + if (!fbc || !fbcount) + { + Log_ErrorPrintf("glXChooseFBConfig() failed"); + return false; + } + m_fb_config = *fbc; + XFree(fbc); + + if (!GLAD_GLX_VERSION_1_3) + { + Log_ErrorPrintf("GLX Version 1.3 is required"); + return false; + } + + m_vi = glXGetVisualFromFBConfig(GetDisplay(), m_fb_config); + if (!m_vi) + { + Log_ErrorPrintf("glXGetVisualFromFBConfig() failed"); + return false; + } + + return m_window.Create(GetDisplay(), static_cast(reinterpret_cast(m_wi.window_handle)), m_vi); +} + +bool ContextGLX::CreateAnyContext(GLXContext share_context, bool make_current) +{ + X11InhibitErrors ie; + + m_context = glXCreateContext(GetDisplay(), m_vi, share_context, True); + if (!m_context || ie.HadError()) + { + Log_ErrorPrintf("glxCreateContext() failed"); + return false; + } + + if (make_current) + { + if (!glXMakeCurrent(GetDisplay(), GetDrawable(), m_context)) + { + Log_ErrorPrintf("glXMakeCurrent() failed"); + return false; + } + } + + return true; +} + +bool ContextGLX::CreateVersionContext(const Version& version, GLXContext share_context, bool make_current) +{ + // we need create context attribs + if (!GLAD_GLX_VERSION_1_3) + { + Log_ErrorPrint("Missing GLX version 1.3."); + return false; + } + + int attribs[32]; + int nattribs = 0; + attribs[nattribs++] = GLX_CONTEXT_PROFILE_MASK_ARB; + attribs[nattribs++] = + ((version.profile == Profile::ES) ? + ((version.major_version >= 2) ? GLX_CONTEXT_ES2_PROFILE_BIT_EXT : GLX_CONTEXT_ES_PROFILE_BIT_EXT) : + GLX_CONTEXT_CORE_PROFILE_BIT_ARB); + attribs[nattribs++] = GLX_CONTEXT_MAJOR_VERSION_ARB; + attribs[nattribs++] = version.major_version; + attribs[nattribs++] = GLX_CONTEXT_MINOR_VERSION_ARB; + attribs[nattribs++] = version.minor_version; + attribs[nattribs++] = None; + attribs[nattribs++] = 0; + + X11InhibitErrors ie; + m_context = glXCreateContextAttribsARB(GetDisplay(), m_fb_config, share_context, True, attribs); + XSync(GetDisplay(), False); + if (ie.HadError()) + m_context = nullptr; + if (!m_context) + return false; + + if (make_current) + { + if (!glXMakeCurrent(GetDisplay(), GetDrawable(), m_context)) + { + Log_ErrorPrint("glXMakeCurrent() failed"); + glXDestroyContext(GetDisplay(), m_context); + m_context = nullptr; + return false; + } + } + + return true; +} +} // namespace GL diff --git a/jni/common/gl/context_glx.h b/jni/common/gl/context_glx.h new file mode 100644 index 0000000..35b89b0 --- /dev/null +++ b/jni/common/gl/context_glx.h @@ -0,0 +1,44 @@ +#pragma once +#include "context.h" +#include "glad_glx.h" +#include "x11_window.h" + +namespace GL { + +class ContextGLX final : public Context +{ +public: + ContextGLX(const WindowInfo& wi); + ~ContextGLX() override; + + static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + void* GetProcAddress(const char* name) override; + bool ChangeSurface(const WindowInfo& new_wi) override; + void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + bool SwapBuffers() override; + bool MakeCurrent() override; + bool DoneCurrent() override; + bool SetSwapInterval(s32 interval) override; + std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + +private: + ALWAYS_INLINE Display* GetDisplay() const { return static_cast(m_wi.display_connection); } + ALWAYS_INLINE GLXDrawable GetDrawable() const { return static_cast(m_window.GetWindow()); } + + bool Initialize(const Version* versions_to_try, size_t num_versions_to_try); + bool CreateWindow(int screen); + bool CreateAnyContext(GLXContext share_context, bool make_current); + bool CreateVersionContext(const Version& version, GLXContext share_context, bool make_current); + + GLXContext m_context = nullptr; + GLXFBConfig m_fb_config = {}; + XVisualInfo* m_vi = nullptr; + X11Window m_window; + + // GLAD releases its reference to libGL.so, so we need to maintain our own. + void* m_libGL_handle = nullptr; +}; + +} // namespace GL diff --git a/jni/common/gl/context_wgl.cpp b/jni/common/gl/context_wgl.cpp new file mode 100644 index 0000000..857084e --- /dev/null +++ b/jni/common/gl/context_wgl.cpp @@ -0,0 +1,352 @@ +#include "context_wgl.h" +#include "../assert.h" +#include "../log.h" +#include "glad.h" +#include "glad_wgl.h" +Log_SetChannel(GL::ContextWGL); + +// TODO: get rid of this +#pragma comment(lib, "opengl32.lib") + +static void* GetProcAddressCallback(const char* name) +{ + void* addr = wglGetProcAddress(name); + if (addr) + return addr; + + // try opengl32.dll + return ::GetProcAddress(GetModuleHandleA("opengl32.dll"), name); +} + +namespace GL { +ContextWGL::ContextWGL(const WindowInfo& wi) : Context(wi) {} + +ContextWGL::~ContextWGL() +{ + if (wglGetCurrentContext() == m_rc) + wglMakeCurrent(m_dc, nullptr); + + if (m_rc) + wglDeleteContext(m_rc); + + if (m_dc) + ReleaseDC(GetHWND(), m_dc); +} + +std::unique_ptr ContextWGL::Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try) +{ + std::unique_ptr context = std::make_unique(wi); + if (!context->Initialize(versions_to_try, num_versions_to_try)) + return nullptr; + + return context; +} + +bool ContextWGL::Initialize(const Version* versions_to_try, size_t num_versions_to_try) +{ + if (m_wi.type == WindowInfo::Type::Win32) + { + if (!InitializeDC()) + return false; + } + else + { + Panic("Create pbuffer"); + } + + // Everything including core/ES requires a dummy profile to load the WGL extensions. + if (!CreateAnyContext(nullptr, true)) + return false; + + for (size_t i = 0; i < num_versions_to_try; i++) + { + const Version& cv = versions_to_try[i]; + if (cv.profile == Profile::NoProfile) + { + // we already have the dummy context, so just use that + m_version = cv; + return true; + } + else if (CreateVersionContext(cv, nullptr, true)) + { + m_version = cv; + return true; + } + } + + return false; +} + +void* ContextWGL::GetProcAddress(const char* name) +{ + return GetProcAddressCallback(name); +} + +bool ContextWGL::ChangeSurface(const WindowInfo& new_wi) +{ + const bool was_current = (wglGetCurrentContext() == m_rc); + + if (m_dc) + { + ReleaseDC(GetHWND(), m_dc); + m_dc = {}; + } + + m_wi = new_wi; + if (!InitializeDC()) + return false; + + if (was_current && !wglMakeCurrent(m_dc, m_rc)) + { + Log_ErrorPrintf("Failed to make context current again after surface change: 0x%08X", GetLastError()); + return false; + } + + return true; +} + +void ContextWGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) +{ + RECT client_rc = {}; + GetClientRect(GetHWND(), &client_rc); + m_wi.surface_width = static_cast(client_rc.right - client_rc.left); + m_wi.surface_height = static_cast(client_rc.bottom - client_rc.top); +} + +bool ContextWGL::SwapBuffers() +{ + return ::SwapBuffers(m_dc); +} + +bool ContextWGL::MakeCurrent() +{ + if (!wglMakeCurrent(m_dc, m_rc)) + { + Log_ErrorPrintf("wglMakeCurrent() failed: 0x%08X", GetLastError()); + return false; + } + + return true; +} + +bool ContextWGL::DoneCurrent() +{ + return wglMakeCurrent(m_dc, nullptr); +} + +bool ContextWGL::SetSwapInterval(s32 interval) +{ + if (!GLAD_WGL_EXT_swap_control) + return false; + + return wglSwapIntervalEXT(interval); +} + +std::unique_ptr ContextWGL::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + if (wi.type == WindowInfo::Type::Win32) + { + if (!context->InitializeDC()) + return nullptr; + } + else + { + Panic("Create pbuffer"); + } + + if (m_version.profile == Profile::NoProfile) + { + if (!context->CreateAnyContext(m_rc, false)) + return nullptr; + } + else + { + if (!context->CreateVersionContext(m_version, m_rc, false)) + return nullptr; + } + + context->m_version = m_version; + return context; +} + +bool ContextWGL::InitializeDC() +{ + PIXELFORMATDESCRIPTOR pfd = {}; + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.dwLayerMask = PFD_MAIN_PLANE; + + switch (m_wi.surface_format) + { + case WindowInfo::SurfaceFormat::RGB8: + pfd.cColorBits = 32; + pfd.cRedBits = 8; + pfd.cGreenBits = 8; + pfd.cBlueBits = 8; + break; + + case WindowInfo::SurfaceFormat::RGBA8: + pfd.cColorBits = 32; + pfd.cRedBits = 8; + pfd.cGreenBits = 8; + pfd.cBlueBits = 8; + pfd.cAlphaBits = 8; + break; + + case WindowInfo::SurfaceFormat::RGB565: + pfd.cColorBits = 16; + pfd.cRedBits = 5; + pfd.cGreenBits = 6; + pfd.cBlueBits = 5; + break; + + default: + UnreachableCode(); + break; + } + + m_dc = GetDC(GetHWND()); + if (!m_dc) + { + Log_ErrorPrintf("GetDC() failed: 0x%08X", GetLastError()); + return false; + } + + const int pf = ChoosePixelFormat(m_dc, &pfd); + if (pf == 0) + { + Log_ErrorPrintf("ChoosePixelFormat() failed: 0x%08X", GetLastError()); + return false; + } + + if (!SetPixelFormat(m_dc, pf, &pfd)) + { + Log_ErrorPrintf("SetPixelFormat() failed: 0x%08X", GetLastError()); + return false; + } + + return true; +} + +bool ContextWGL::CreateAnyContext(HGLRC share_context, bool make_current) +{ + m_rc = wglCreateContext(m_dc); + if (!m_rc) + { + Log_ErrorPrintf("wglCreateContext() failed: 0x%08X", GetLastError()); + return false; + } + + if (make_current) + { + if (!wglMakeCurrent(m_dc, m_rc)) + { + Log_ErrorPrintf("wglMakeCurrent() failed: 0x%08X", GetLastError()); + return false; + } + + // re-init glad-wgl + if (!gladLoadWGLLoader([](const char* name) -> void* { return wglGetProcAddress(name); }, m_dc)) + { + Log_ErrorPrintf("Loading GLAD WGL functions failed"); + return false; + } + } + + if (share_context && !wglShareLists(share_context, m_rc)) + { + Log_ErrorPrintf("wglShareLists() failed: 0x%08X", GetLastError()); + return false; + } + + return true; +} + +bool ContextWGL::CreateVersionContext(const Version& version, HGLRC share_context, bool make_current) +{ + // we need create context attribs + if (!GLAD_WGL_ARB_create_context) + { + Log_ErrorPrint("Missing GLAD_WGL_ARB_create_context."); + return false; + } + + HGLRC new_rc; + if (version.profile == Profile::Core) + { + const int attribs[] = {WGL_CONTEXT_PROFILE_MASK_ARB, + WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + WGL_CONTEXT_MAJOR_VERSION_ARB, + version.major_version, + WGL_CONTEXT_MINOR_VERSION_ARB, + version.minor_version, +#ifdef _DEBUG + WGL_CONTEXT_FLAGS_ARB, + WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB, +#else + WGL_CONTEXT_FLAGS_ARB, + WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, +#endif + 0, + 0}; + + new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs); + } + else if (version.profile == Profile::ES) + { + if ((version.major_version >= 2 && !GLAD_WGL_EXT_create_context_es2_profile) || + (version.major_version < 2 && !GLAD_WGL_EXT_create_context_es_profile)) + { + Log_ErrorPrint("WGL_EXT_create_context_es_profile not supported"); + return false; + } + + const int attribs[] = { + WGL_CONTEXT_PROFILE_MASK_ARB, + ((version.major_version >= 2) ? WGL_CONTEXT_ES2_PROFILE_BIT_EXT : WGL_CONTEXT_ES_PROFILE_BIT_EXT), + WGL_CONTEXT_MAJOR_VERSION_ARB, + version.major_version, + WGL_CONTEXT_MINOR_VERSION_ARB, + version.minor_version, + 0, + 0}; + + new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs); + } + else + { + Log_ErrorPrintf("Unknown profile"); + return false; + } + + if (!new_rc) + return false; + + // destroy and swap contexts + if (m_rc) + { + if (!wglMakeCurrent(m_dc, make_current ? new_rc : nullptr)) + { + Log_ErrorPrintf("wglMakeCurrent() failed: 0x%08X", GetLastError()); + wglDeleteContext(new_rc); + return false; + } + + // re-init glad-wgl + if (make_current && !gladLoadWGLLoader([](const char* name) -> void* { return wglGetProcAddress(name); }, m_dc)) + { + Log_ErrorPrintf("Loading GLAD WGL functions failed"); + return false; + } + + wglDeleteContext(m_rc); + } + + m_rc = new_rc; + return true; +} +} // namespace GL diff --git a/jni/common/gl/context_wgl.h b/jni/common/gl/context_wgl.h new file mode 100644 index 0000000..6303235 --- /dev/null +++ b/jni/common/gl/context_wgl.h @@ -0,0 +1,38 @@ +#pragma once +#include "../windows_headers.h" +#include "context.h" +#include + +namespace GL { + +class ContextWGL final : public Context +{ +public: + ContextWGL(const WindowInfo& wi); + ~ContextWGL() override; + + static std::unique_ptr Create(const WindowInfo& wi, const Version* versions_to_try, + size_t num_versions_to_try); + + void* GetProcAddress(const char* name) override; + bool ChangeSurface(const WindowInfo& new_wi) override; + void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + bool SwapBuffers() override; + bool MakeCurrent() override; + bool DoneCurrent() override; + bool SetSwapInterval(s32 interval) override; + std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + +private: + ALWAYS_INLINE HWND GetHWND() const { return static_cast(m_wi.window_handle); } + + bool Initialize(const Version* versions_to_try, size_t num_versions_to_try); + bool InitializeDC(); + bool CreateAnyContext(HGLRC share_context, bool make_current); + bool CreateVersionContext(const Version& version, HGLRC share_context, bool make_current); + + HDC m_dc = {}; + HGLRC m_rc = {}; +}; + +} // namespace GL \ No newline at end of file diff --git a/jni/common/gl/program.cpp b/jni/common/gl/program.cpp new file mode 100644 index 0000000..c6e99c7 --- /dev/null +++ b/jni/common/gl/program.cpp @@ -0,0 +1,609 @@ +#include "program.h" +#include "../assert.h" +#include "../log.h" +#include "../string_util.h" +#include +#include +Log_SetChannel(GL); + +namespace GL { + +GLuint Program::s_last_program_id = 0; +static GLuint s_next_bad_shader_id = 1; + +Program::Program() = default; + +Program::Program(Program&& prog) +{ + m_program_id = prog.m_program_id; + prog.m_program_id = 0; + m_vertex_shader_id = prog.m_vertex_shader_id; + prog.m_vertex_shader_id = 0; + m_fragment_shader_id = prog.m_fragment_shader_id; + prog.m_fragment_shader_id = 0; + m_uniform_locations = std::move(prog.m_uniform_locations); +} + +Program::~Program() +{ + Destroy(); +} + +GLuint Program::CompileShader(GLenum type, const std::string_view source) +{ + GLuint id = glCreateShader(type); + + std::array sources = {{source.data()}}; + std::array source_lengths = {{static_cast(source.size())}}; + glShaderSource(id, static_cast(sources.size()), sources.data(), source_lengths.data()); + glCompileShader(id); + + GLint status = GL_FALSE; + glGetShaderiv(id, GL_COMPILE_STATUS, &status); + + GLint info_log_length = 0; + glGetShaderiv(id, GL_INFO_LOG_LENGTH, &info_log_length); + + if (status == GL_FALSE || info_log_length > 0) + { + std::string info_log; + info_log.resize(info_log_length + 1); + glGetShaderInfoLog(id, info_log_length, &info_log_length, &info_log[0]); + + if (status == GL_TRUE) + { + Log_ErrorPrintf("Shader compiled with warnings:\n%s", info_log.c_str()); + } + else + { + Log_ErrorPrintf("Shader failed to compile:\n%s", info_log.c_str()); + + std::ofstream ofs(StringUtil::StdStringFromFormat("bad_shader_%u.txt", s_next_bad_shader_id++).c_str(), + std::ofstream::out | std::ofstream::binary); + if (ofs.is_open()) + { + ofs.write(sources[0], source_lengths[0]); + ofs << "\n\nCompile failed, info log:\n"; + ofs << info_log; + ofs.close(); + } + + glDeleteShader(id); + return 0; + } + } + + return id; +} + +void Program::ResetLastProgram() +{ + s_last_program_id = 0; +} + +bool Program::Compile(const std::string_view vertex_shader, const std::string_view geometry_shader, + const std::string_view fragment_shader) +{ + GLuint vertex_shader_id = 0; + if (!vertex_shader.empty()) + { + vertex_shader_id = CompileShader(GL_VERTEX_SHADER, vertex_shader); + if (vertex_shader_id == 0) + return false; + } + + GLuint geometry_shader_id = 0; + if (!geometry_shader.empty()) + { + geometry_shader_id = CompileShader(GL_GEOMETRY_SHADER, geometry_shader); + if (geometry_shader_id == 0) + return false; + } + + GLuint fragment_shader_id = 0; + if (!fragment_shader.empty()) + { + fragment_shader_id = CompileShader(GL_FRAGMENT_SHADER, fragment_shader); + if (fragment_shader_id == 0) + { + glDeleteShader(vertex_shader_id); + return false; + } + } + + m_program_id = glCreateProgram(); + if (vertex_shader_id != 0) + glAttachShader(m_program_id, vertex_shader_id); + if (geometry_shader_id != 0) + glAttachShader(m_program_id, geometry_shader_id); + if (fragment_shader_id != 0) + glAttachShader(m_program_id, fragment_shader_id); + return true; +} + +bool Program::CreateFromBinary(const void* data, u32 data_length, u32 data_format) +{ + GLuint prog = glCreateProgram(); + glProgramBinary(prog, static_cast(data_format), data, data_length); + + GLint link_status; + glGetProgramiv(prog, GL_LINK_STATUS, &link_status); + if (link_status != GL_TRUE) + { + Log_ErrorPrintf("Failed to create GL program from binary: status %d", link_status); + glDeleteProgram(prog); + return false; + } + + m_program_id = prog; + return true; +} + +bool Program::GetBinary(std::vector* out_data, u32* out_data_format) +{ + GLint binary_size = 0; + glGetProgramiv(m_program_id, GL_PROGRAM_BINARY_LENGTH, &binary_size); + if (binary_size == 0) + { + Log_WarningPrint("glGetProgramiv(GL_PROGRAM_BINARY_LENGTH) returned 0"); + return false; + } + + GLenum format = 0; + out_data->resize(static_cast(binary_size)); + glGetProgramBinary(m_program_id, binary_size, &binary_size, &format, out_data->data()); + if (binary_size == 0) + { + Log_WarningPrint("glGetProgramBinary() failed"); + return false; + } + else if (static_cast(binary_size) != out_data->size()) + { + Log_WarningPrintf("Size changed from %zu to %d after glGetProgramBinary()", out_data->data(), binary_size); + out_data->resize(static_cast(binary_size)); + } + + *out_data_format = static_cast(format); + Log_InfoPrintf("Program binary retrieved, %zu bytes, format %u", out_data->size(), *out_data_format); + return true; +} + +void Program::SetBinaryRetrievableHint() +{ + glProgramParameteri(m_program_id, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); +} + +void Program::BindAttribute(GLuint index, const char* name) +{ + glBindAttribLocation(m_program_id, index, name); +} + +void Program::BindDefaultAttributes() +{ + BindAttribute(0, "a_position"); + BindAttribute(1, "a_texcoord"); + BindAttribute(2, "a_color"); +} + +void Program::BindFragData(GLuint index /*= 0*/, const char* name /*= "o_col0"*/) +{ + glBindFragDataLocation(m_program_id, index, name); +} + +void Program::BindFragDataIndexed(GLuint color_number /*= 0*/, const char* name /*= "o_col0"*/) +{ + glBindFragDataLocationIndexed(m_program_id, color_number, 0, name); +} + +bool Program::Link() +{ + glLinkProgram(m_program_id); + + if (m_vertex_shader_id != 0) + glDeleteShader(m_vertex_shader_id); + m_vertex_shader_id = 0; + if (m_fragment_shader_id != 0) + glDeleteShader(m_fragment_shader_id); + m_fragment_shader_id = 0; + + GLint status = GL_FALSE; + glGetProgramiv(m_program_id, GL_LINK_STATUS, &status); + + GLint info_log_length = 0; + glGetProgramiv(m_program_id, GL_INFO_LOG_LENGTH, &info_log_length); + + if (status == GL_FALSE || info_log_length > 0) + { + std::string info_log; + info_log.resize(info_log_length + 1); + glGetProgramInfoLog(m_program_id, info_log_length, &info_log_length, &info_log[0]); + + if (status == GL_TRUE) + { + Log_ErrorPrintf("Program linked with warnings:\n%s", info_log.c_str()); + } + else + { + Log_ErrorPrintf("Program failed to link:\n%s", info_log.c_str()); + glDeleteProgram(m_program_id); + m_program_id = 0; + return false; + } + } + + return true; +} + +void Program::Bind() const +{ + if (s_last_program_id == m_program_id) + return; + + glUseProgram(m_program_id); + s_last_program_id = m_program_id; +} + +void Program::Destroy() +{ + if (m_vertex_shader_id != 0) + { + glDeleteShader(m_vertex_shader_id); + m_vertex_shader_id = 0; + } + if (m_fragment_shader_id != 0) + { + glDeleteShader(m_fragment_shader_id); + m_fragment_shader_id = 0; + } + if (m_program_id != 0) + { + glDeleteProgram(m_program_id); + m_program_id = 0; + } + + m_uniform_locations.clear(); +} + +int Program::RegisterUniform(const char* name) +{ + int id = static_cast(m_uniform_locations.size()); + m_uniform_locations.push_back(glGetUniformLocation(m_program_id, name)); + return id; +} + +void Program::Uniform1ui(int index, u32 x) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform1ui(location, x); +} + +void Program::Uniform2ui(int index, u32 x, u32 y) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2ui(location, x, y); +} + +void Program::Uniform3ui(int index, u32 x, u32 y, u32 z) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3ui(location, x, y, z); +} + +void Program::Uniform4ui(int index, u32 x, u32 y, u32 z, u32 w) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4ui(location, x, y, z, w); +} + +void Program::Uniform1i(int index, s32 x) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform1i(location, x); +} + +void Program::Uniform2i(int index, s32 x, s32 y) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2i(location, x, y); +} + +void Program::Uniform3i(int index, s32 x, s32 y, s32 z) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3i(location, x, y, z); +} + +void Program::Uniform4i(int index, s32 x, s32 y, s32 z, s32 w) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4i(location, x, y, z, w); +} + +void Program::Uniform1f(int index, float x) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform1f(location, x); +} + +void Program::Uniform2f(int index, float x, float y) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2f(location, x, y); +} + +void Program::Uniform3f(int index, float x, float y, float z) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3f(location, x, y, z); +} + +void Program::Uniform4f(int index, float x, float y, float z, float w) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4f(location, x, y, z, w); +} + +void Program::Uniform2uiv(int index, const u32* v) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2uiv(location, 1, v); +} + +void Program::Uniform3uiv(int index, const u32* v) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3uiv(location, 1, v); +} + +void Program::Uniform4uiv(int index, const u32* v) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4uiv(location, 1, v); +} + +void Program::Uniform2iv(int index, const s32* v) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2iv(location, 1, v); +} + +void Program::Uniform3iv(int index, const s32* v) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3iv(location, 1, v); +} + +void Program::Uniform4iv(int index, const s32* v) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4iv(location, 1, v); +} + +void Program::Uniform2fv(int index, const float* v) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2fv(location, 1, v); +} + +void Program::Uniform3fv(int index, const float* v) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3fv(location, 1, v); +} + +void Program::Uniform4fv(int index, const float* v) const +{ + Assert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4fv(location, 1, v); +} + +void Program::Uniform1ui(const char* name, u32 x) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform1ui(location, x); +} + +void Program::Uniform2ui(const char* name, u32 x, u32 y) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform2ui(location, x, y); +} + +void Program::Uniform3ui(const char* name, u32 x, u32 y, u32 z) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform3ui(location, x, y, z); +} + +void Program::Uniform4ui(const char* name, u32 x, u32 y, u32 z, u32 w) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform4ui(location, x, y, z, w); +} + +void Program::Uniform1i(const char* name, s32 x) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform1i(location, x); +} + +void Program::Uniform2i(const char* name, s32 x, s32 y) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform2i(location, x, y); +} + +void Program::Uniform3i(const char* name, s32 x, s32 y, s32 z) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform3i(location, x, y, z); +} + +void Program::Uniform4i(const char* name, s32 x, s32 y, s32 z, s32 w) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform4i(location, x, y, z, w); +} + +void Program::Uniform1f(const char* name, float x) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform1f(location, x); +} + +void Program::Uniform2f(const char* name, float x, float y) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform2f(location, x, y); +} + +void Program::Uniform3f(const char* name, float x, float y, float z) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform3f(location, x, y, z); +} + +void Program::Uniform4f(const char* name, float x, float y, float z, float w) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform4f(location, x, y, z, w); +} + +void Program::Uniform2uiv(const char* name, const u32* v) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform2uiv(location, 1, v); +} + +void Program::Uniform3uiv(const char* name, const u32* v) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform3uiv(location, 1, v); +} + +void Program::Uniform4uiv(const char* name, const u32* v) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform4uiv(location, 1, v); +} + +void Program::Uniform2iv(const char* name, const s32* v) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform2iv(location, 1, v); +} + +void Program::Uniform3iv(const char* name, const s32* v) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform3iv(location, 1, v); +} + +void Program::Uniform4iv(const char* name, const s32* v) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform4iv(location, 1, v); +} + +void Program::Uniform2fv(const char* name, const float* v) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform2fv(location, 1, v); +} + +void Program::Uniform3fv(const char* name, const float* v) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform3fv(location, 1, v); +} + +void Program::Uniform4fv(const char* name, const float* v) const +{ + const GLint location = glGetUniformLocation(m_program_id, name); + if (location >= 0) + glUniform4fv(location, 1, v); +} + +void Program::BindUniformBlock(const char* name, u32 index) +{ + const GLint location = glGetUniformBlockIndex(m_program_id, name); + if (location >= 0) + glUniformBlockBinding(m_program_id, location, index); +} + +Program& Program::operator=(Program&& prog) +{ + Destroy(); + m_program_id = prog.m_program_id; + prog.m_program_id = 0; + m_vertex_shader_id = prog.m_vertex_shader_id; + prog.m_vertex_shader_id = 0; + m_fragment_shader_id = prog.m_fragment_shader_id; + prog.m_fragment_shader_id = 0; + m_uniform_locations = std::move(prog.m_uniform_locations); + return *this; +} + +} // namespace GL \ No newline at end of file diff --git a/jni/common/gl/program.h b/jni/common/gl/program.h new file mode 100644 index 0000000..d877a46 --- /dev/null +++ b/jni/common/gl/program.h @@ -0,0 +1,102 @@ +#pragma once +#include "../types.h" +#include "glad.h" +#include +#include + +namespace GL { +class Program +{ +public: + Program(); + Program(const Program&) = delete; + Program(Program&& prog); + ~Program(); + + static GLuint CompileShader(GLenum type, const std::string_view source); + static void ResetLastProgram(); + + bool IsVaild() const { return m_program_id != 0; } + bool IsBound() const { return s_last_program_id == m_program_id; } + + bool Compile(const std::string_view vertex_shader, const std::string_view geometry_shader, + const std::string_view fragment_shader); + + bool CreateFromBinary(const void* data, u32 data_length, u32 data_format); + + bool GetBinary(std::vector* out_data, u32* out_data_format); + void SetBinaryRetrievableHint(); + + void BindAttribute(GLuint index, const char* name); + void BindDefaultAttributes(); + + void BindFragData(GLuint index = 0, const char* name = "o_col0"); + void BindFragDataIndexed(GLuint color_number = 0, const char* name = "o_col0"); + + bool Link(); + + void Bind() const; + + void Destroy(); + + int RegisterUniform(const char* name); + void Uniform1ui(int index, u32 x) const; + void Uniform2ui(int index, u32 x, u32 y) const; + void Uniform3ui(int index, u32 x, u32 y, u32 z) const; + void Uniform4ui(int index, u32 x, u32 y, u32 z, u32 w) const; + void Uniform1i(int index, s32 x) const; + void Uniform2i(int index, s32 x, s32 y) const; + void Uniform3i(int index, s32 x, s32 y, s32 z) const; + void Uniform4i(int index, s32 x, s32 y, s32 z, s32 w) const; + void Uniform1f(int index, float x) const; + void Uniform2f(int index, float x, float y) const; + void Uniform3f(int index, float x, float y, float z) const; + void Uniform4f(int index, float x, float y, float z, float w) const; + void Uniform2uiv(int index, const u32* v) const; + void Uniform3uiv(int index, const u32* v) const; + void Uniform4uiv(int index, const u32* v) const; + void Uniform2iv(int index, const s32* v) const; + void Uniform3iv(int index, const s32* v) const; + void Uniform4iv(int index, const s32* v) const; + void Uniform2fv(int index, const float* v) const; + void Uniform3fv(int index, const float* v) const; + void Uniform4fv(int index, const float* v) const; + + void Uniform1ui(const char* name, u32 x) const; + void Uniform2ui(const char* name, u32 x, u32 y) const; + void Uniform3ui(const char* name, u32 x, u32 y, u32 z) const; + void Uniform4ui(const char* name, u32 x, u32 y, u32 z, u32 w) const; + void Uniform1i(const char* name, s32 x) const; + void Uniform2i(const char* name, s32 x, s32 y) const; + void Uniform3i(const char* name, s32 x, s32 y, s32 z) const; + void Uniform4i(const char* name, s32 x, s32 y, s32 z, s32 w) const; + void Uniform1f(const char* name, float x) const; + void Uniform2f(const char* name, float x, float y) const; + void Uniform3f(const char* name, float x, float y, float z) const; + void Uniform4f(const char* name, float x, float y, float z, float w) const; + void Uniform2uiv(const char* name, const u32* v) const; + void Uniform3uiv(const char* name, const u32* v) const; + void Uniform4uiv(const char* name, const u32* v) const; + void Uniform2iv(const char* name, const s32* v) const; + void Uniform3iv(const char* name, const s32* v) const; + void Uniform4iv(const char* name, const s32* v) const; + void Uniform2fv(const char* name, const float* v) const; + void Uniform3fv(const char* name, const float* v) const; + void Uniform4fv(const char* name, const float* v) const; + + void BindUniformBlock(const char* name, u32 index); + + Program& operator=(const Program&) = delete; + Program& operator=(Program&& prog); + +private: + static u32 s_last_program_id; + + GLuint m_program_id = 0; + GLuint m_vertex_shader_id = 0; + GLuint m_fragment_shader_id = 0; + + std::vector m_uniform_locations; +}; + +} // namespace GL \ No newline at end of file diff --git a/jni/common/gl/shader_cache.cpp b/jni/common/gl/shader_cache.cpp new file mode 100644 index 0000000..90166bd --- /dev/null +++ b/jni/common/gl/shader_cache.cpp @@ -0,0 +1,360 @@ +#include "shader_cache.h" +#include "../file_system.h" +#include "../log.h" +#include "../md5_digest.h" +#include "../string_util.h" +Log_SetChannel(GL::ShaderCache); + +namespace GL { + +#pragma pack(push, 1) +struct CacheIndexEntry +{ + u64 vertex_source_hash_low; + u64 vertex_source_hash_high; + u32 vertex_source_length; + u64 geometry_source_hash_low; + u64 geometry_source_hash_high; + u32 geometry_source_length; + u64 fragment_source_hash_low; + u64 fragment_source_hash_high; + u32 fragment_source_length; + u32 file_offset; + u32 blob_size; + u32 blob_format; +}; +#pragma pack(pop) + +ShaderCache::ShaderCache() = default; + +ShaderCache::~ShaderCache() +{ + Close(); +} + +bool ShaderCache::CacheIndexKey::operator==(const CacheIndexKey& key) const +{ + return ( + vertex_source_hash_low == key.vertex_source_hash_low && vertex_source_hash_high == key.vertex_source_hash_high && + vertex_source_length == key.vertex_source_length && geometry_source_hash_low == key.geometry_source_hash_low && + geometry_source_hash_high == key.geometry_source_hash_high && + geometry_source_length == key.geometry_source_length && fragment_source_hash_low == key.fragment_source_hash_low && + fragment_source_hash_high == key.fragment_source_hash_high && fragment_source_length == key.fragment_source_length); +} + +bool ShaderCache::CacheIndexKey::operator!=(const CacheIndexKey& key) const +{ + return ( + vertex_source_hash_low != key.vertex_source_hash_low || vertex_source_hash_high != key.vertex_source_hash_high || + vertex_source_length != key.vertex_source_length || geometry_source_hash_low != key.geometry_source_hash_low || + geometry_source_hash_high != key.geometry_source_hash_high || + geometry_source_length != key.geometry_source_length || fragment_source_hash_low != key.fragment_source_hash_low || + fragment_source_hash_high != key.fragment_source_hash_high || fragment_source_length != key.fragment_source_length); +} + +void ShaderCache::Open(bool is_gles, std::string_view base_path) +{ + m_base_path = base_path; + m_program_binary_supported = is_gles || GLAD_GL_ARB_get_program_binary; + if (m_program_binary_supported) + { + // check that there's at least one format and the extension isn't being "faked" + GLint num_formats = 0; + glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats); + Log_InfoPrintf("%u program binary formats supported by driver", num_formats); + m_program_binary_supported = (num_formats > 0); + } + + if (!m_program_binary_supported) + { + Log_WarningPrintf("Your GL driver does not support program binaries. Hopefully it has a built-in cache, otherwise " + "startup will be slow due to compiling shaders."); + return; + } + + if (!base_path.empty()) + { + const std::string index_filename = GetIndexFileName(); + const std::string blob_filename = GetBlobFileName(); + + if (!ReadExisting(index_filename, blob_filename)) + CreateNew(index_filename, blob_filename); + } +} + +bool ShaderCache::CreateNew(const std::string& index_filename, const std::string& blob_filename) +{ + if (FileSystem::FileExists(index_filename.c_str())) + { + Log_WarningPrintf("Removing existing index file '%s'", index_filename.c_str()); + FileSystem::DeleteFile(index_filename.c_str()); + } + if (FileSystem::FileExists(blob_filename.c_str())) + { + Log_WarningPrintf("Removing existing blob file '%s'", blob_filename.c_str()); + FileSystem::DeleteFile(blob_filename.c_str()); + } + + m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "wb"); + if (!m_index_file) + { + Log_ErrorPrintf("Failed to open index file '%s' for writing", index_filename.c_str()); + return false; + } + + const u32 index_version = FILE_VERSION; + if (std::fwrite(&index_version, sizeof(index_version), 1, m_index_file) != 1) + { + Log_ErrorPrintf("Failed to write version to index file '%s'", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + FileSystem::DeleteFile(index_filename.c_str()); + return false; + } + + m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "w+b"); + if (!m_blob_file) + { + Log_ErrorPrintf("Failed to open blob file '%s' for writing", blob_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + FileSystem::DeleteFile(index_filename.c_str()); + return false; + } + + return true; +} + +bool ShaderCache::ReadExisting(const std::string& index_filename, const std::string& blob_filename) +{ + m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "r+b"); + if (!m_index_file) + return false; + + u32 file_version; + if (std::fread(&file_version, sizeof(file_version), 1, m_index_file) != 1 || file_version != FILE_VERSION) + { + Log_ErrorPrintf("Bad file version in '%s'", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "a+b"); + if (!m_blob_file) + { + Log_ErrorPrintf("Blob file '%s' is missing", blob_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + std::fseek(m_blob_file, 0, SEEK_END); + const u32 blob_file_size = static_cast(std::ftell(m_blob_file)); + + for (;;) + { + CacheIndexEntry entry; + if (std::fread(&entry, sizeof(entry), 1, m_index_file) != 1 || + (entry.file_offset + entry.blob_size) > blob_file_size) + { + if (std::feof(m_index_file)) + break; + + Log_ErrorPrintf("Failed to read entry from '%s', corrupt file?", index_filename.c_str()); + m_index.clear(); + std::fclose(m_blob_file); + m_blob_file = nullptr; + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + const CacheIndexKey key{ + entry.vertex_source_hash_low, entry.vertex_source_hash_high, entry.vertex_source_length, + entry.geometry_source_hash_low, entry.geometry_source_hash_high, entry.geometry_source_length, + entry.fragment_source_hash_low, entry.fragment_source_hash_high, entry.fragment_source_length}; + const CacheIndexData data{entry.file_offset, entry.blob_size, entry.blob_format}; + m_index.emplace(key, data); + } + + Log_InfoPrintf("Read %zu entries from '%s'", m_index.size(), index_filename.c_str()); + return true; +} + +void ShaderCache::Close() +{ + m_index.clear(); + if (m_index_file) + std::fclose(m_index_file); + if (m_blob_file) + std::fclose(m_blob_file); +} + +bool ShaderCache::Recreate() +{ + Close(); + + const std::string index_filename = GetIndexFileName(); + const std::string blob_filename = GetBlobFileName(); + + return CreateNew(index_filename, blob_filename); +} + +ShaderCache::CacheIndexKey ShaderCache::GetCacheKey(const std::string_view& vertex_shader, + const std::string_view& geometry_shader, + const std::string_view& fragment_shader) +{ + union ShaderHash + { + struct + { + u64 low; + u64 high; + }; + u8 bytes[16]; + }; + + ShaderHash vertex_hash = {}; + ShaderHash geometry_hash = {}; + ShaderHash fragment_hash = {}; + + MD5Digest digest; + if (!vertex_shader.empty()) + { + digest.Update(vertex_shader.data(), static_cast(vertex_shader.length())); + digest.Final(vertex_hash.bytes); + } + + if (!geometry_shader.empty()) + { + digest.Reset(); + digest.Update(geometry_shader.data(), static_cast(geometry_shader.length())); + digest.Final(geometry_hash.bytes); + } + + if (!fragment_shader.empty()) + { + digest.Reset(); + digest.Update(fragment_shader.data(), static_cast(fragment_shader.length())); + digest.Final(fragment_hash.bytes); + } + + return CacheIndexKey{vertex_hash.low, vertex_hash.high, static_cast(vertex_shader.length()), + geometry_hash.low, geometry_hash.high, static_cast(geometry_shader.length()), + fragment_hash.low, fragment_hash.high, static_cast(fragment_shader.length())}; +} + +std::string ShaderCache::GetIndexFileName() const +{ + return StringUtil::StdStringFromFormat("%sgl_programs.idx", m_base_path.c_str()); +} + +std::string ShaderCache::GetBlobFileName() const +{ + return StringUtil::StdStringFromFormat("%sgl_programs.bin", m_base_path.c_str()); +} + +std::optional ShaderCache::GetProgram(const std::string_view vertex_shader, + const std::string_view geometry_shader, + const std::string_view fragment_shader, const PreLinkCallback& callback) +{ + if (!m_program_binary_supported || !m_blob_file) + return CompileProgram(vertex_shader, geometry_shader, fragment_shader, callback, false); + + const auto key = GetCacheKey(vertex_shader, geometry_shader, fragment_shader); + auto iter = m_index.find(key); + if (iter == m_index.end()) + return CompileAndAddProgram(key, vertex_shader, geometry_shader, fragment_shader, callback); + + std::vector data(iter->second.blob_size); + if (std::fseek(m_blob_file, iter->second.file_offset, SEEK_SET) != 0 || + std::fread(data.data(), 1, iter->second.blob_size, m_blob_file) != iter->second.blob_size) + { + Log_ErrorPrintf("Read blob from file failed"); + return {}; + } + + Program prog; + if (prog.CreateFromBinary(data.data(), static_cast(data.size()), iter->second.blob_format)) + return std::optional(std::move(prog)); + + Log_WarningPrintf( + "Failed to create program from binary, this may be due to a driver or GPU Change. Recreating cache."); + if (!Recreate()) + return CompileProgram(vertex_shader, geometry_shader, fragment_shader, callback, false); + else + return CompileAndAddProgram(key, vertex_shader, geometry_shader, fragment_shader, callback); +} + +std::optional ShaderCache::CompileProgram(const std::string_view& vertex_shader, + const std::string_view& geometry_shader, + const std::string_view& fragment_shader, + const PreLinkCallback& callback, bool set_retrievable) +{ + Program prog; + if (!prog.Compile(vertex_shader, geometry_shader, fragment_shader)) + return std::nullopt; + + if (callback) + callback(prog); + + if (set_retrievable) + prog.SetBinaryRetrievableHint(); + + if (!prog.Link()) + return std::nullopt; + + return std::optional(std::move(prog)); +} + +std::optional ShaderCache::CompileAndAddProgram(const CacheIndexKey& key, + const std::string_view& vertex_shader, + const std::string_view& geometry_shader, + const std::string_view& fragment_shader, + const PreLinkCallback& callback) +{ + std::optional prog = CompileProgram(vertex_shader, geometry_shader, fragment_shader, callback, true); + if (!prog) + return std::nullopt; + + std::vector prog_data; + u32 prog_format = 0; + if (!prog->GetBinary(&prog_data, &prog_format)) + return std::nullopt; + + if (!m_blob_file || std::fseek(m_blob_file, 0, SEEK_END) != 0) + return prog; + + CacheIndexData data; + data.file_offset = static_cast(std::ftell(m_blob_file)); + data.blob_size = static_cast(prog_data.size()); + data.blob_format = prog_format; + + CacheIndexEntry entry = {}; + entry.vertex_source_hash_low = key.vertex_source_hash_low; + entry.vertex_source_hash_high = key.vertex_source_hash_high; + entry.vertex_source_length = key.vertex_source_length; + entry.geometry_source_hash_low = key.geometry_source_hash_low; + entry.geometry_source_hash_high = key.geometry_source_hash_high; + entry.geometry_source_length = key.geometry_source_length; + entry.fragment_source_hash_low = key.fragment_source_hash_low; + entry.fragment_source_hash_high = key.fragment_source_hash_high; + entry.fragment_source_length = key.fragment_source_length; + entry.file_offset = data.file_offset; + entry.blob_size = data.blob_size; + entry.blob_format = data.blob_format; + + if (std::fwrite(prog_data.data(), 1, entry.blob_size, m_blob_file) != entry.blob_size || + std::fflush(m_blob_file) != 0 || std::fwrite(&entry, sizeof(entry), 1, m_index_file) != 1 || + std::fflush(m_index_file) != 0) + { + Log_ErrorPrintf("Failed to write shader blob to file"); + return prog; + } + + m_index.emplace(key, data); + return prog; +} + +} // namespace GL diff --git a/jni/common/gl/shader_cache.h b/jni/common/gl/shader_cache.h new file mode 100644 index 0000000..304c6a5 --- /dev/null +++ b/jni/common/gl/shader_cache.h @@ -0,0 +1,94 @@ +#pragma once +#include "../hash_combine.h" +#include "../types.h" +#include "program.h" +#include +#include +#include +#include +#include +#include +#include + +namespace GL { + +class ShaderCache +{ +public: + using PreLinkCallback = std::function; + + ShaderCache(); + ~ShaderCache(); + + void Open(bool is_gles, std::string_view base_path); + + std::optional GetProgram(const std::string_view vertex_shader, const std::string_view geometry_shader, + const std::string_view fragment_shader, const PreLinkCallback& callback = {}); + +private: + static constexpr u32 FILE_VERSION = 2; + + struct CacheIndexKey + { + u64 vertex_source_hash_low; + u64 vertex_source_hash_high; + u32 vertex_source_length; + u64 geometry_source_hash_low; + u64 geometry_source_hash_high; + u32 geometry_source_length; + u64 fragment_source_hash_low; + u64 fragment_source_hash_high; + u32 fragment_source_length; + + bool operator==(const CacheIndexKey& key) const; + bool operator!=(const CacheIndexKey& key) const; + }; + + struct CacheIndexEntryHasher + { + std::size_t operator()(const CacheIndexKey& e) const noexcept + { + std::size_t h = 0; + hash_combine(h, e.vertex_source_hash_low, e.vertex_source_hash_high, e.vertex_source_length, + e.geometry_source_hash_low, e.geometry_source_hash_high, e.geometry_source_length, + e.fragment_source_hash_low, e.fragment_source_hash_high, e.fragment_source_length); + return h; + } + }; + + struct CacheIndexData + { + u32 file_offset; + u32 blob_size; + u32 blob_format; + }; + + using CacheIndex = std::unordered_map; + + static CacheIndexKey GetCacheKey(const std::string_view& vertex_shader, const std::string_view& geometry_shader, + const std::string_view& fragment_shader); + + std::string GetIndexFileName() const; + std::string GetBlobFileName() const; + + bool CreateNew(const std::string& index_filename, const std::string& blob_filename); + bool ReadExisting(const std::string& index_filename, const std::string& blob_filename); + void Close(); + bool Recreate(); + + std::optional CompileProgram(const std::string_view& vertex_shader, const std::string_view& geometry_shader, + const std::string_view& fragment_shader, const PreLinkCallback& callback, + bool set_retrievable); + std::optional CompileAndAddProgram(const CacheIndexKey& key, const std::string_view& vertex_shader, + const std::string_view& geometry_shader, + const std::string_view& fragment_shader, const PreLinkCallback& callback); + + std::string m_base_path; + std::FILE* m_index_file = nullptr; + std::FILE* m_blob_file = nullptr; + + CacheIndex m_index; + bool m_program_binary_supported = false; +}; + +} // namespace GL diff --git a/jni/common/gl/stream_buffer.cpp b/jni/common/gl/stream_buffer.cpp new file mode 100644 index 0000000..6de7702 --- /dev/null +++ b/jni/common/gl/stream_buffer.cpp @@ -0,0 +1,304 @@ +#include "stream_buffer.h" +#include "../assert.h" +#include "../align.h" +#include +#include + +namespace GL { + +StreamBuffer::StreamBuffer(GLenum target, GLuint buffer_id, u32 size) + : m_target(target), m_buffer_id(buffer_id), m_size(size) +{ +} + +StreamBuffer::~StreamBuffer() +{ + glDeleteBuffers(1, &m_buffer_id); +} + +void StreamBuffer::Bind() +{ + glBindBuffer(m_target, m_buffer_id); +} + +void StreamBuffer::Unbind() +{ + glBindBuffer(m_target, 0); +} + +namespace detail { + +// Uses glBufferSubData() to update. Preferred for drivers which don't support {ARB,EXT}_buffer_storage. +class BufferSubDataStreamBuffer final : public StreamBuffer +{ +public: + ~BufferSubDataStreamBuffer() override = default; + + MappingResult Map(u32 alignment, u32 min_size) override + { + return MappingResult{static_cast(m_cpu_buffer.data()), 0, 0, m_size / alignment}; + } + + void Unmap(u32 used_size) override + { + if (used_size == 0) + return; + + glBindBuffer(m_target, m_buffer_id); + glBufferSubData(m_target, 0, used_size, m_cpu_buffer.data()); + } + + static std::unique_ptr Create(GLenum target, u32 size) + { + glGetError(); + + GLuint buffer_id; + glGenBuffers(1, &buffer_id); + glBindBuffer(target, buffer_id); + glBufferData(target, size, nullptr, GL_STREAM_DRAW); + + GLenum err = glGetError(); + if (err != GL_NO_ERROR) + { + glDeleteBuffers(1, &buffer_id); + return {}; + } + + return std::unique_ptr(new BufferSubDataStreamBuffer(target, buffer_id, size)); + } + +private: + BufferSubDataStreamBuffer(GLenum target, GLuint buffer_id, u32 size) + : StreamBuffer(target, buffer_id, size), m_cpu_buffer(size) + { + } + + std::vector m_cpu_buffer; +}; + +// Uses BufferData() to orphan the buffer after every update. Used on Mali where BufferSubData forces a sync. +class BufferDataStreamBuffer final : public StreamBuffer +{ +public: + ~BufferDataStreamBuffer() override = default; + + MappingResult Map(u32 alignment, u32 min_size) override + { + return MappingResult{static_cast(m_cpu_buffer.data()), 0, 0, m_size / alignment}; + } + + void Unmap(u32 used_size) override + { + if (used_size == 0) + return; + + glBindBuffer(m_target, m_buffer_id); + glBufferData(m_target, used_size, m_cpu_buffer.data(), GL_STREAM_DRAW); + } + + static std::unique_ptr Create(GLenum target, u32 size) + { + glGetError(); + + GLuint buffer_id; + glGenBuffers(1, &buffer_id); + glBindBuffer(target, buffer_id); + glBufferData(target, size, nullptr, GL_STREAM_DRAW); + + GLenum err = glGetError(); + if (err != GL_NO_ERROR) + { + glDeleteBuffers(1, &buffer_id); + return {}; + } + + return std::unique_ptr(new BufferDataStreamBuffer(target, buffer_id, size)); + } + +private: + BufferDataStreamBuffer(GLenum target, GLuint buffer_id, u32 size) + : StreamBuffer(target, buffer_id, size), m_cpu_buffer(size) + { + } + + std::vector m_cpu_buffer; +}; + +// Base class for implementations which require syncing. +class SyncingStreamBuffer : public StreamBuffer +{ +public: + enum : u32 + { + NUM_SYNC_POINTS = 16 + }; + + virtual ~SyncingStreamBuffer() override + { + for (u32 i = m_available_block_index; i <= m_used_block_index; i++) + { + DebugAssert(m_sync_objects[i]); + glDeleteSync(m_sync_objects[i]); + } + } + +protected: + SyncingStreamBuffer(GLenum target, GLuint buffer_id, u32 size) + : StreamBuffer(target, buffer_id, size), m_bytes_per_block((size + (NUM_SYNC_POINTS)-1) / NUM_SYNC_POINTS) + { + } + + u32 GetSyncIndexForOffset(u32 offset) { return offset / m_bytes_per_block; } + + void AddSyncsForOffset(u32 offset) + { + const u32 end = GetSyncIndexForOffset(offset); + for (; m_used_block_index < end; m_used_block_index++) + { + DebugAssert(!m_sync_objects[m_used_block_index]); + m_sync_objects[m_used_block_index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + } + + void WaitForSync(GLsync& sync) + { + glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + glDeleteSync(sync); + sync = nullptr; + } + + void EnsureSyncsWaitedForOffset(u32 offset) + { + const u32 end = std::min(GetSyncIndexForOffset(offset) + 1, NUM_SYNC_POINTS); + for (; m_available_block_index < end; m_available_block_index++) + { + DebugAssert(m_sync_objects[m_available_block_index]); + WaitForSync(m_sync_objects[m_available_block_index]); + } + } + + void AllocateSpace(u32 size) + { + // add sync objects for writes since the last allocation + AddSyncsForOffset(m_position); + + // wait for sync objects for the space we want to use + EnsureSyncsWaitedForOffset(m_position + size); + + // wrap-around? + if ((m_position + size) > m_size) + { + // current position ... buffer end + AddSyncsForOffset(m_size); + + // rewind, and try again + m_position = 0; + + // wait for the sync at the start of the buffer + WaitForSync(m_sync_objects[0]); + m_available_block_index = 1; + + // and however much more we need to satisfy the allocation + EnsureSyncsWaitedForOffset(size); + m_used_block_index = 0; + } + } + + u32 m_position = 0; + u32 m_used_block_index = 0; + u32 m_available_block_index = NUM_SYNC_POINTS; + u32 m_bytes_per_block; + std::array m_sync_objects{}; +}; + +class BufferStorageStreamBuffer : public SyncingStreamBuffer +{ +public: + ~BufferStorageStreamBuffer() override + { + glBindBuffer(m_target, m_buffer_id); + glUnmapBuffer(m_target); + } + + MappingResult Map(u32 alignment, u32 min_size) override + { + if (m_position > 0) + m_position = Common::AlignUp(m_position, alignment); + + AllocateSpace(min_size); + DebugAssert((m_position + min_size) <= (m_available_block_index * m_bytes_per_block)); + + const u32 free_space_in_block = ((m_available_block_index * m_bytes_per_block) - m_position); + return MappingResult{static_cast(m_mapped_ptr + m_position), m_position, m_position / alignment, + free_space_in_block / alignment}; + } + + void Unmap(u32 used_size) override + { + DebugAssert((m_position + used_size) <= m_size); + m_position += used_size; + } + + static std::unique_ptr Create(GLenum target, u32 size) + { + glGetError(); + + GLuint buffer_id; + glGenBuffers(1, &buffer_id); + glBindBuffer(target, buffer_id); + if (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage) + glBufferStorage(target, size, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); + else if (GLAD_GL_EXT_buffer_storage) + glBufferStorageEXT(target, size, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); + + GLenum err = glGetError(); + if (err != GL_NO_ERROR) + { + glDeleteBuffers(1, &buffer_id); + return {}; + } + + u8* mapped_ptr = static_cast( + glMapBufferRange(target, 0, size, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT)); + Assert(mapped_ptr); + + return std::unique_ptr(new BufferStorageStreamBuffer(target, buffer_id, size, mapped_ptr)); + } + +private: + BufferStorageStreamBuffer(GLenum target, GLuint buffer_id, u32 size, u8* mapped_ptr) + : SyncingStreamBuffer(target, buffer_id, size), m_mapped_ptr(mapped_ptr) + { + } + + u8* m_mapped_ptr; +}; + +} // namespace detail + +std::unique_ptr StreamBuffer::Create(GLenum target, u32 size) +{ + std::unique_ptr buf; + if (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage) + { + buf = detail::BufferStorageStreamBuffer::Create(target, size); + if (buf) + return buf; + } + + // BufferSubData is slower on all drivers except NVIDIA... +#if 0 + const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); + if (std::strcmp(vendor, "ARM") == 0 || std::strcmp(vendor, "Qualcomm") == 0) + { + // Mali and Adreno drivers can't do sub-buffer tracking... + return detail::BufferDataStreamBuffer::Create(target, size); + } + + return detail::BufferSubDataStreamBuffer::Create(target, size); +#else + return detail::BufferDataStreamBuffer::Create(target, size); +#endif +} + +} // namespace GL \ No newline at end of file diff --git a/jni/common/gl/stream_buffer.h b/jni/common/gl/stream_buffer.h new file mode 100644 index 0000000..78958dc --- /dev/null +++ b/jni/common/gl/stream_buffer.h @@ -0,0 +1,41 @@ +#pragma once +#include "../types.h" +#include +#include +#include +#include + +namespace GL { +class StreamBuffer +{ +public: + virtual ~StreamBuffer(); + + ALWAYS_INLINE GLuint GetGLBufferId() const { return m_buffer_id; } + ALWAYS_INLINE GLenum GetGLTarget() const { return m_target; } + ALWAYS_INLINE u32 GetSize() const { return m_size; } + + void Bind(); + void Unbind(); + + struct MappingResult + { + void* pointer; + u32 buffer_offset; + u32 index_aligned; // offset / alignment, suitable for base vertex + u32 space_aligned; // remaining space / alignment + }; + + virtual MappingResult Map(u32 alignment, u32 min_size) = 0; + virtual void Unmap(u32 used_size) = 0; + + static std::unique_ptr Create(GLenum target, u32 size); + +protected: + StreamBuffer(GLenum target, GLuint buffer_id, u32 size); + + GLenum m_target; + GLuint m_buffer_id; + u32 m_size; +}; +} // namespace GL \ No newline at end of file diff --git a/jni/common/gl/texture.cpp b/jni/common/gl/texture.cpp new file mode 100644 index 0000000..416c40d --- /dev/null +++ b/jni/common/gl/texture.cpp @@ -0,0 +1,195 @@ +#include "texture.h" +#include "../assert.h" +#include "../log.h" +Log_SetChannel(GL); + +namespace GL { + +Texture::Texture() = default; + +Texture::Texture(Texture&& moved) + : m_id(moved.m_id), m_width(moved.m_width), m_height(moved.m_height), m_samples(moved.m_samples), + m_fbo_id(moved.m_fbo_id) +{ + moved.m_id = 0; + moved.m_width = 0; + moved.m_height = 0; + moved.m_samples = 0; + moved.m_fbo_id = 0; +} + +Texture::~Texture() +{ + Destroy(); +} + +bool Texture::Create(u32 width, u32 height, u32 samples, GLenum internal_format, GLenum format, GLenum type, + const void* data, bool linear_filter, bool wrap) +{ + glGetError(); + + const GLenum target = (samples > 1) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; + + GLuint id; + glGenTextures(1, &id); + glBindTexture(target, id); + + if (samples > 1) + { + if (GLAD_GL_ARB_texture_storage || GLAD_GL_ES_VERSION_3_1) + glTexStorage2DMultisample(target, samples, internal_format, width, height, GL_FALSE); + else + glTexImage2DMultisample(target, samples, internal_format, width, height, GL_FALSE); + } + else + { + if (GLAD_GL_ARB_texture_storage || GLAD_GL_ES_VERSION_3_0) + glTexStorage2D(target, 1, internal_format, width, height); + else + glTexImage2D(target, 0, internal_format, width, height, 0, format, type, data); + + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST); + glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE); + glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE); + } + + glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, 1); + + GLenum error = glGetError(); + if (error != GL_NO_ERROR) + { + Log_ErrorPrintf("Failed to create texture: 0x%X", error); + glDeleteTextures(1, &id); + return false; + } + + if (IsValid()) + Destroy(); + + m_id = id; + m_width = width; + m_height = height; + m_samples = samples; + return true; +} + +void Texture::SetLinearFilter(bool enabled) +{ + Assert(!IsMultisampled()); + + Bind(); + + const GLenum target = GetGLTarget(); + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, enabled ? GL_LINEAR : GL_NEAREST); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, enabled ? GL_LINEAR : GL_NEAREST); +} + +bool Texture::CreateFramebuffer() +{ + if (!IsValid()) + return false; + + glGetError(); + + GLuint fbo_id; + glGenFramebuffers(1, &fbo_id); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_id); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_id, 0); + if (glGetError() != GL_NO_ERROR || glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + glDeleteFramebuffers(1, &fbo_id); + return false; + } + + if (m_fbo_id != 0) + glDeleteFramebuffers(1, &m_fbo_id); + + m_fbo_id = fbo_id; + return true; +} + +void Texture::Destroy() +{ + if (m_fbo_id != 0) + { + glDeleteFramebuffers(1, &m_fbo_id); + m_fbo_id = 0; + } + if (m_id != 0) + { + glDeleteTextures(1, &m_id); + m_id = 0; + } + + m_width = 0; + m_height = 0; + m_samples = 0; +} + +void Texture::Bind() +{ + glBindTexture(GetGLTarget(), m_id); +} + +void Texture::BindFramebuffer(GLenum target /*= GL_DRAW_FRAMEBUFFER*/) +{ + DebugAssert(m_fbo_id != 0); + glBindFramebuffer(target, m_fbo_id); +} + +void Texture::Unbind() +{ + glBindTexture(GL_TEXTURE_2D, 0); +} + +Texture& Texture::operator=(Texture&& moved) +{ + Destroy(); + + m_id = moved.m_id; + m_width = moved.m_width; + m_height = moved.m_height; + m_samples = moved.m_samples; + m_fbo_id = moved.m_fbo_id; + + moved.m_id = 0; + moved.m_width = 0; + moved.m_height = 0; + moved.m_samples = 0; + moved.m_fbo_id = 0; + return *this; +} + +void Texture::GetTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, + GLsizei bufSize, void* pixels) +{ + if (GLAD_GL_VERSION_4_5 || GLAD_GL_ARB_get_texture_sub_image) + { + glGetTextureSubImage(texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, bufSize, + pixels); + return; + } + + Assert(depth == 1); + + GLuint old_read_fbo; + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, reinterpret_cast(&old_read_fbo)); + + GLuint temp_fbo; + glGenFramebuffers(1, &temp_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, temp_fbo); + if (zoffset > 0 && (GLAD_GL_VERSION_3_0 || GLAD_GL_ES_VERSION_3_0)) + glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, level, zoffset); + else + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level); + + DebugAssert(glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + glReadPixels(xoffset, yoffset, width, height, format, type, pixels); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fbo); + glDeleteFramebuffers(1, &temp_fbo); +} + +} // namespace GL \ No newline at end of file diff --git a/jni/common/gl/texture.h b/jni/common/gl/texture.h new file mode 100644 index 0000000..3a8102f --- /dev/null +++ b/jni/common/gl/texture.h @@ -0,0 +1,53 @@ +#pragma once +#include "../types.h" +#include + +namespace GL { +class Texture +{ +public: + Texture(); + Texture(Texture&& moved); + ~Texture(); + + bool Create(u32 width, u32 height, u32 samples, GLenum internal_format, GLenum format, GLenum type, + const void* data = nullptr, bool linear_filter = false, bool wrap = false); + bool CreateFramebuffer(); + + void Destroy(); + + void SetLinearFilter(bool enabled); + + bool IsValid() const { return m_id != 0; } + bool IsMultisampled() const { return m_samples > 1; } + GLuint GetGLId() const { return m_id; } + u32 GetWidth() const { return m_width; } + u32 GetHeight() const { return m_height; } + u32 GetSamples() const { return m_samples; } + + GLuint GetGLFramebufferID() const { return m_fbo_id; } + GLenum GetGLTarget() const { return IsMultisampled() ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; } + + void Bind(); + void BindFramebuffer(GLenum target = GL_DRAW_FRAMEBUFFER); + + static void Unbind(); + + Texture& operator=(const Texture& copy) = delete; + Texture& operator=(Texture&& moved); + + // Helper which uses glGetTextureSubImage where available, otherwise a temporary FBO. + static void GetTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, + GLsizei bufSize, void* pixels); + +private: + GLuint m_id = 0; + u32 m_width = 0; + u32 m_height = 0; + u32 m_samples = 0; + + GLuint m_fbo_id = 0; +}; + +} // namespace GL \ No newline at end of file diff --git a/jni/common/gl/x11_window.cpp b/jni/common/gl/x11_window.cpp new file mode 100644 index 0000000..d62273a --- /dev/null +++ b/jni/common/gl/x11_window.cpp @@ -0,0 +1,101 @@ +#include "x11_window.h" +#include "../assert.h" +#include "../log.h" +#include +Log_SetChannel(X11Window); + +namespace GL { +X11Window::X11Window() = default; + +X11Window::~X11Window() +{ + Destroy(); +} + +bool X11Window::Create(Display* display, Window parent_window, const XVisualInfo* vi) +{ + m_display = display; + m_parent_window = parent_window; + XSync(m_display, True); + + XWindowAttributes parent_wa = {}; + XGetWindowAttributes(m_display, m_parent_window, &parent_wa); + m_width = static_cast(parent_wa.width); + m_height = static_cast(parent_wa.height); + + // Failed X calls terminate the process so no need to check for errors. + // We could swap the error handler out here as well. + m_colormap = XCreateColormap(m_display, m_parent_window, vi->visual, AllocNone); + + XSetWindowAttributes wa = {}; + wa.colormap = m_colormap; + + m_window = XCreateWindow(m_display, m_parent_window, 0, 0, m_width, m_height, 0, vi->depth, InputOutput, vi->visual, + CWColormap, &wa); + XMapWindow(m_display, m_window); + XSync(m_display, True); + + return true; +} + +void X11Window::Destroy() +{ + if (m_window) + { + XUnmapWindow(m_display, m_window); + XDestroyWindow(m_display, m_window); + m_window = {}; + } + + if (m_colormap) + { + XFreeColormap(m_display, m_colormap); + m_colormap = {}; + } +} + +void X11Window::Resize(u32 width, u32 height) +{ + if (width != 0 && height != 0) + { + m_width = width; + m_height = height; + } + else + { + XWindowAttributes parent_wa = {}; + XGetWindowAttributes(m_display, m_parent_window, &parent_wa); + m_width = static_cast(parent_wa.width); + m_height = static_cast(parent_wa.height); + } + + XResizeWindow(m_display, m_window, m_width, m_height); +} + +static X11InhibitErrors* s_current_error_inhibiter; + +X11InhibitErrors::X11InhibitErrors() +{ + Assert(!s_current_error_inhibiter); + m_old_handler = XSetErrorHandler(ErrorHandler); + s_current_error_inhibiter = this; +} + +X11InhibitErrors::~X11InhibitErrors() +{ + Assert(s_current_error_inhibiter == this); + s_current_error_inhibiter = nullptr; + XSetErrorHandler(m_old_handler); +} + +int X11InhibitErrors::ErrorHandler(Display* display, XErrorEvent* ee) +{ + char error_string[256] = {}; + XGetErrorText(display, ee->error_code, error_string, sizeof(error_string)); + Log_WarningPrintf("X11 Error: %s (Error %u Minor %u Request %u)", error_string, ee->error_code, ee->minor_code, + ee->request_code); + + s_current_error_inhibiter->m_had_error = true; + return 0; +} +} // namespace GL diff --git a/jni/common/gl/x11_window.h b/jni/common/gl/x11_window.h new file mode 100644 index 0000000..4752980 --- /dev/null +++ b/jni/common/gl/x11_window.h @@ -0,0 +1,48 @@ +#pragma once +#include "../types.h" +#include +#include + +namespace GL { +class X11Window +{ +public: + X11Window(); + ~X11Window(); + + ALWAYS_INLINE Window GetWindow() const { return m_window; } + ALWAYS_INLINE u32 GetWidth() const { return m_width; } + ALWAYS_INLINE u32 GetHeight() const { return m_height; } + + bool Create(Display* display, Window parent_window, const XVisualInfo* vi); + void Destroy(); + + // Setting a width/height of 0 will use parent dimensions. + void Resize(u32 width = 0, u32 height = 0); + +private: + Display* m_display = nullptr; + Window m_parent_window = {}; + Window m_window = {}; + Colormap m_colormap = {}; + u32 m_width = 0; + u32 m_height = 0; +}; + +// Helper class for managing X errors +class X11InhibitErrors +{ +public: + X11InhibitErrors(); + ~X11InhibitErrors(); + + ALWAYS_INLINE bool HadError() const { return m_had_error; } + +private: + static int ErrorHandler(Display* display, XErrorEvent* ee); + + XErrorHandler m_old_handler = {}; + bool m_had_error = false; +}; + +} // namespace GL diff --git a/jni/common/hash_combine.h b/jni/common/hash_combine.h new file mode 100644 index 0000000..52188ba --- /dev/null +++ b/jni/common/hash_combine.h @@ -0,0 +1,10 @@ +// https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x +#pragma once +#include + +template +void hash_combine(std::size_t& seed, const T& v, const Rest&... rest) +{ + seed ^= std::hash{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + (hash_combine(seed, rest), ...); +} diff --git a/jni/common/heap_array.h b/jni/common/heap_array.h new file mode 100644 index 0000000..bf4a9a2 --- /dev/null +++ b/jni/common/heap_array.h @@ -0,0 +1,103 @@ +#pragma once +#include +#include +#include + +template +class HeapArray +{ +public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using this_type = HeapArray; + + HeapArray() { m_data = new T[SIZE]; } + + HeapArray(const this_type& copy) + { + m_data = new T[SIZE]; + std::copy(copy.cbegin(), copy.cend(), begin()); + } + + HeapArray(this_type&& move) + { + m_data = move.m_data; + move.m_data = nullptr; + } + + ~HeapArray() { delete[] m_data; } + + size_type size() const { return SIZE; } + size_type capacity() const { return SIZE; } + bool empty() const { return false; } + + pointer begin() { return m_data; } + pointer end() { return m_data + SIZE; } + + const_pointer data() const { return m_data; } + pointer data() { return m_data; } + + const_pointer cbegin() const { return m_data; } + const_pointer cend() const { return m_data + SIZE; } + + const_reference operator[](size_type index) const + { + assert(index < SIZE); + return m_data[index]; + } + reference operator[](size_type index) + { + assert(index < SIZE); + return m_data[index]; + } + + const_reference front() const { return m_data[0]; } + const_reference back() const { return m_data[SIZE - 1]; } + reference front() { return m_data[0]; } + reference back() { return m_data[SIZE - 1]; } + + void fill(const_reference value) { std::fill(begin(), end(), value); } + + void swap(this_type& move) { std::swap(m_data, move.m_data); } + + this_type& operator=(const this_type& rhs) + { + std::copy(begin(), end(), rhs.cbegin()); + return *this; + } + + this_type& operator=(this_type&& move) + { + delete[] m_data; + m_data = move.m_data; + move.m_data = nullptr; + return *this; + } + +#define RELATIONAL_OPERATOR(op) \ + bool operator op(const this_type& rhs) const \ + { \ + for (size_type i = 0; i < SIZE; i++) \ + { \ + if (!(m_data[i] op rhs.m_data[i])) \ + return false; \ + } \ + } + + RELATIONAL_OPERATOR(==); + RELATIONAL_OPERATOR(!=); + RELATIONAL_OPERATOR(<); + RELATIONAL_OPERATOR(<=); + RELATIONAL_OPERATOR(>); + RELATIONAL_OPERATOR(>=); + +#undef RELATIONAL_OPERATOR + +private: + T* m_data; +}; diff --git a/jni/common/image.cpp b/jni/common/image.cpp new file mode 100644 index 0000000..1fe92fe --- /dev/null +++ b/jni/common/image.cpp @@ -0,0 +1,46 @@ +#include "image.h" +#include "file_system.h" +#include "log.h" +#include "stb_image.h" +Log_SetChannel(Common::Image); + +namespace Common { +bool LoadImageFromFile(Common::RGBA8Image* image, const char* filename) +{ + auto fp = FileSystem::OpenManagedCFile(filename, "rb"); + if (!fp) + { + return false; + } + + int width, height, file_channels; + u8* pixel_data = stbi_load_from_file(fp.get(), &width, &height, &file_channels, 4); + if (!pixel_data) + { + const char* error_reason = stbi_failure_reason(); + Log_ErrorPrintf("Failed to load image from '%s': %s", filename, error_reason ? error_reason : "unknown error"); + return false; + } + + image->SetPixels(static_cast(width), static_cast(height), reinterpret_cast(pixel_data)); + stbi_image_free(pixel_data); + return true; +} + +bool LoadImageFromBuffer(Common::RGBA8Image* image, const void* buffer, std::size_t buffer_size) +{ + int width, height, file_channels; + u8* pixel_data = stbi_load_from_memory(static_cast(buffer), static_cast(buffer_size), &width, + &height, &file_channels, 4); + if (!pixel_data) + { + const char* error_reason = stbi_failure_reason(); + Log_ErrorPrintf("Failed to load image from memory: %s", error_reason ? error_reason : "unknown error"); + return false; + } + + image->SetPixels(static_cast(width), static_cast(height), reinterpret_cast(pixel_data)); + stbi_image_free(pixel_data); + return true; +} +} // namespace Common \ No newline at end of file diff --git a/jni/common/image.h b/jni/common/image.h new file mode 100644 index 0000000..6054aa0 --- /dev/null +++ b/jni/common/image.h @@ -0,0 +1,98 @@ +#pragma once +#include "assert.h" +#include "types.h" +#include +#include +#include +#include + +namespace Common { +template +class Image +{ +public: + Image() = default; + Image(u32 width, u32 height, const PixelType* pixels) { SetPixels(width, height, pixels); } + Image(const Image& copy) + { + m_width = copy.m_width; + m_height = copy.m_height; + m_pixels = copy.m_pixels; + } + Image(Image&& move) + { + m_width = move.m_width; + m_height = move.m_height; + m_pixels = std::move(move.m_width); + move.m_width = 0; + move.m_height = 0; + } + + Image& operator=(const Image& copy) + { + m_width = copy.m_width; + m_height = copy.m_height; + m_pixels = copy.m_pixels; + return *this; + } + Image& operator=(Image&& move) + { + m_width = move.m_width; + m_height = move.m_height; + m_pixels = std::move(move.m_width); + move.m_width = 0; + move.m_height = 0; + return *this; + } + + ALWAYS_INLINE bool IsValid() const { return (m_width > 0 && m_height > 0); } + ALWAYS_INLINE u32 GetWidth() const { return m_width; } + ALWAYS_INLINE u32 GetHeight() const { return m_height; } + ALWAYS_INLINE u32 GetByteStride() const { return (sizeof(PixelType) * m_width); } + ALWAYS_INLINE const PixelType* GetPixels() const { return m_pixels.data(); } + ALWAYS_INLINE PixelType* GetPixels() { return m_pixels.data(); } + ALWAYS_INLINE const PixelType* GetRowPixels(u32 y) const { return &m_pixels[y * m_width]; } + ALWAYS_INLINE PixelType* GetRowPixels(u32 y) { return &m_pixels[y * m_width]; } + ALWAYS_INLINE void SetPixel(u32 x, u32 y, PixelType pixel) { m_pixels[y * m_width + x] = pixel; } + ALWAYS_INLINE PixelType GetPixel(u32 x, u32 y) const { return m_pixels[y * m_width + x]; } + + void Clear(PixelType fill_value = static_cast(0)) + { + std::fill(m_pixels.begin(), m_pixels.end(), fill_value); + } + + void Invalidate() + { + m_width = 0; + m_height = 0; + m_pixels.clear(); + } + + void SetSize(u32 new_width, u32 new_height, PixelType fill_value = static_cast(0)) + { + m_width = new_width; + m_height = new_height; + m_pixels.resize(new_width * new_height); + Clear(fill_value); + } + + void SetPixels(u32 width, u32 height, const PixelType* pixels) + { + m_width = width; + m_height = height; + m_pixels.resize(width * height); + std::memcpy(m_pixels.data(), pixels, width * height * sizeof(PixelType)); + } + +private: + u32 m_width = 0; + u32 m_height = 0; + std::vector m_pixels; +}; + +using RGBA8Image = Image; + +bool LoadImageFromFile(Common::RGBA8Image* image, const char* filename); +bool LoadImageFromBuffer(Common::RGBA8Image* image, const void* buffer, std::size_t buffer_size); + +} // namespace Common \ No newline at end of file diff --git a/jni/common/iso_reader.cpp b/jni/common/iso_reader.cpp new file mode 100644 index 0000000..8192ec1 --- /dev/null +++ b/jni/common/iso_reader.cpp @@ -0,0 +1,280 @@ +#include "iso_reader.h" +#include "log.h" +#include "cd_image.h" +#include +Log_SetChannel(ISOReader); + +static bool FilenamesEqual(const char* a, const char* b, u32 length) +{ + u32 pos = 0; + for (; pos < length && *a != '\0' && *b != '\0'; pos++) + { + if (std::tolower(*(a++)) != std::tolower(*(b++))) + return false; + } + + return true; +} + +ISOReader::ISOReader() = default; + +ISOReader::~ISOReader() = default; + +bool ISOReader::Open(CDImage* image, u32 track_number) +{ + m_image = image; + m_track_number = track_number; + if (!ReadPVD()) + return false; + + return true; +} + +bool ISOReader::ReadPVD() +{ + // volume descriptor start at sector 16 + if (!m_image->Seek(m_track_number, 16)) + return false; + + // try only a maximum of 256 volume descriptors + for (u32 i = 0; i < 256; i++) + { + u8 buffer[SECTOR_SIZE]; + if (m_image->Read(CDImage::ReadMode::DataOnly, 1, buffer) != 1) + return false; + + const ISOVolumeDescriptorHeader* header = reinterpret_cast(buffer); + if (header->type_code != 1) + continue; + else if (header->type_code == 255) + break; + + std::memcpy(&m_pvd, buffer, sizeof(ISOPrimaryVolumeDescriptor)); + Log_DebugPrintf("PVD found at index %u", i); + return true; + } + + Log_ErrorPrint("PVD not found"); + return false; +} + +std::optional ISOReader::LocateFile(const char* path) +{ + u8 sector_buffer[SECTOR_SIZE]; + + const ISODirectoryEntry* root_de = reinterpret_cast(m_pvd.root_directory_entry); + if (*path == '\0' || std::strcmp(path, "/") == 0) + { + // locating the root directory + return *root_de; + } + + // start at the root directory + return LocateFile(path, sector_buffer, root_de->location_le, root_de->length_le); +} + +std::optional ISOReader::LocateFile(const char* path, u8* sector_buffer, + u32 directory_record_lba, u32 directory_record_size) +{ + if (directory_record_size == 0) + { + Log_ErrorPrintf("Directory entry record size 0 while looking for '%s'", path); + return std::nullopt; + } + + // strip any leading slashes + const char* path_component_start = path; + while (*path_component_start == '/') + path_component_start++; + + u32 path_component_length = 0; + const char* path_component_end = path_component_start; + while (*path_component_end != '\0' && *path_component_end != '/') + { + path_component_length++; + path_component_end++; + } + + // start reading directory entries + const u32 num_sectors = (directory_record_size + (SECTOR_SIZE - 1)) / SECTOR_SIZE; + if (!m_image->Seek(m_track_number, directory_record_lba)) + { + Log_ErrorPrintf("Seek to LBA %u failed", directory_record_lba); + return std::nullopt; + } + + for (u32 i = 0; i < num_sectors; i++) + { + if (m_image->Read(CDImage::ReadMode::DataOnly, 1, sector_buffer) != 1) + { + Log_ErrorPrintf("Failed to read LBA %u", directory_record_lba + i); + return std::nullopt; + } + + u32 sector_offset = 0; + while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE) + { + const ISODirectoryEntry* de = reinterpret_cast(§or_buffer[sector_offset]); + const char* de_filename = + reinterpret_cast(§or_buffer[sector_offset + sizeof(ISODirectoryEntry)]); + if ((sector_offset + de->entry_length) > SECTOR_SIZE || de->filename_length > de->entry_length || + de->entry_length < sizeof(ISODirectoryEntry)) + { + break; + } + + sector_offset += de->entry_length; + + // skip current/parent directory + if (de->filename_length == 1 && (*de_filename == '\x0' || *de_filename == '\x1')) + continue; + + // check filename length + if (de->filename_length < path_component_length) + continue; + + // compare filename + if (!FilenamesEqual(de_filename, path_component_start, path_component_length) || + de_filename[path_component_length] != ';') + { + continue; + } + + // found it. is this the file we're looking for? + if (*path_component_end == '\0') + return *de; + + // if it is a directory, recurse into it + if (de->flags & ISODirectoryEntryFlag_Directory) + return LocateFile(path_component_end, sector_buffer, de->location_le, de->length_le); + + // we're looking for a directory but got a file + Log_ErrorPrintf("Looking for directory but got file"); + return std::nullopt; + } + } + + std::string temp(path_component_start, path_component_length); + Log_ErrorPrintf("Path component '%s' not found", temp.c_str()); + return std::nullopt; +} + +std::vector ISOReader::GetFilesInDirectory(const char* path) +{ + std::string base_path = path; + u32 directory_record_lba; + u32 directory_record_length; + if (base_path.empty()) + { + // root directory + const ISODirectoryEntry* root_de = reinterpret_cast(m_pvd.root_directory_entry); + directory_record_lba = root_de->location_le; + directory_record_length = root_de->length_le; + } + else + { + auto directory_de = LocateFile(base_path.c_str()); + if (!directory_de) + { + Log_ErrorPrintf("Directory entry not found for '%s'", path); + return {}; + } + + if ((directory_de->flags & ISODirectoryEntryFlag_Directory) == 0) + { + Log_ErrorPrintf("Path '%s' is not a directory, can't list", path); + return {}; + } + + directory_record_lba = directory_de->location_le; + directory_record_length = directory_de->length_le; + + if (base_path[base_path.size() - 1] != '/') + base_path += '/'; + } + + // start reading directory entries + const u32 num_sectors = (directory_record_length + (SECTOR_SIZE - 1)) / SECTOR_SIZE; + if (!m_image->Seek(m_track_number, directory_record_lba)) + { + Log_ErrorPrintf("Seek to LBA %u failed", directory_record_lba); + return {}; + } + + std::vector files; + u8 sector_buffer[SECTOR_SIZE]; + for (u32 i = 0; i < num_sectors; i++) + { + if (m_image->Read(CDImage::ReadMode::DataOnly, 1, sector_buffer) != 1) + { + Log_ErrorPrintf("Failed to read LBA %u", directory_record_lba + i); + break; + } + + u32 sector_offset = 0; + while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE) + { + const ISODirectoryEntry* de = reinterpret_cast(§or_buffer[sector_offset]); + const char* de_filename = + reinterpret_cast(§or_buffer[sector_offset + sizeof(ISODirectoryEntry)]); + if ((sector_offset + de->entry_length) > SECTOR_SIZE || de->filename_length > de->entry_length || + de->entry_length < sizeof(ISODirectoryEntry)) + { + break; + } + + sector_offset += de->entry_length; + + // skip current/parent directory + if (de->filename_length == 1 && (*de_filename == '\x0' || *de_filename == '\x1')) + continue; + + // strip off terminator/file version + std::string filename(de_filename, de->filename_length); + std::string::size_type pos = filename.rfind(';'); + if (pos == std::string::npos) + { + Log_ErrorPrintf("Invalid filename '%s'", filename.c_str()); + continue; + } + filename.erase(pos); + + if (!filename.empty()) + files.push_back(base_path + filename); + } + } + + return files; +} + +bool ISOReader::ReadFile(const char* path, std::vector* data) +{ + auto de = LocateFile(path); + if (!de) + { + Log_ErrorPrintf("File not found: '%s'", path); + return false; + } + if (de->flags & ISODirectoryEntryFlag_Directory) + { + Log_ErrorPrintf("File is a directory: '%s'", path); + return false; + } + + if (!m_image->Seek(m_track_number, de->location_le)) + return false; + + if (de->length_le == 0) + { + data->clear(); + return true; + } + + const u32 num_sectors = (de->length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE; + data->resize(num_sectors * u64(SECTOR_SIZE)); + if (m_image->Read(CDImage::ReadMode::DataOnly, num_sectors, data->data()) != num_sectors) + return false; + + data->resize(de->length_le); + return true; +} diff --git a/jni/common/iso_reader.h b/jni/common/iso_reader.h new file mode 100644 index 0000000..0098e18 --- /dev/null +++ b/jni/common/iso_reader.h @@ -0,0 +1,151 @@ +#pragma once +#include "types.h" +#include +#include +#include +#include + +class CDImage; + +class ISOReader +{ +public: + enum : u32 + { + SECTOR_SIZE = 2048 + }; + + ISOReader(); + ~ISOReader(); + + bool Open(CDImage* image, u32 track_number); + + std::vector GetFilesInDirectory(const char* path); + + bool ReadFile(const char* path, std::vector* data); + +private: +#pragma pack(push, 1) + + struct ISOVolumeDescriptorHeader + { + u8 type_code; + char standard_identifier[5]; + u8 version; + }; + static_assert(sizeof(ISOVolumeDescriptorHeader) == 7); + + struct ISOBootRecord + { + ISOVolumeDescriptorHeader header; + char boot_system_identifier[32]; + char boot_identifier[32]; + u8 data[1977]; + }; + static_assert(sizeof(ISOBootRecord) == 2048); + + struct ISOPVDDateTime + { + char year[4]; + char month[2]; + char day[2]; + char hour[2]; + char minute[2]; + char second[2]; + char milliseconds[2]; + s8 gmt_offset; + }; + static_assert(sizeof(ISOPVDDateTime) == 17); + + struct ISOPrimaryVolumeDescriptor + { + ISOVolumeDescriptorHeader header; + u8 unused; + char system_identifier[32]; + char volume_identifier[32]; + char unused2[8]; + u32 total_sectors_le; + u32 total_sectors_be; + char unused3[32]; + u16 volume_set_size_le; + u16 volume_set_size_be; + u16 volume_sequence_number_le; + u16 volume_sequence_number_be; + u16 block_size_le; + u16 block_size_be; + u32 path_table_size_le; + u32 path_table_size_be; + u32 path_table_location_le; + u32 optional_path_table_location_le; + u32 path_table_location_be; + u32 optional_path_table_location_be; + u8 root_directory_entry[34]; + char volume_set_identifier[128]; + char publisher_identifier[128]; + char data_preparer_identifier[128]; + char application_identifier[128]; + char copyright_file_identifier[38]; + char abstract_file_identifier[36]; + char bibliographic_file_identifier[37]; + ISOPVDDateTime volume_creation_time; + ISOPVDDateTime volume_modification_time; + ISOPVDDateTime volume_expiration_time; + ISOPVDDateTime volume_effective_time; + u8 structure_version; + u8 unused4; + u8 application_used[512]; + u8 reserved[653]; + }; + static_assert(sizeof(ISOPrimaryVolumeDescriptor) == 2048); + + struct ISODirectoryEntryDateTime + { + u8 years_since_1900; + u8 month; + u8 day; + u8 hour; + u8 minute; + u8 second; + s8 gmt_offset; + }; + + enum ISODirectoryEntryFlags : u8 + { + ISODirectoryEntryFlag_Hidden = (1 << 0), + ISODirectoryEntryFlag_Directory = (1 << 1), + ISODirectoryEntryFlag_AssociatedFile = (1 << 2), + ISODirectoryEntryFlag_ExtendedAttributePresent = (1 << 3), + ISODirectoryEntryFlag_OwnerGroupPermissions = (1 << 4), + ISODirectoryEntryFlag_MoreExtents = (1 << 7), + }; + + struct ISODirectoryEntry + { + u8 entry_length; + u8 extended_attribute_length; + u32 location_le; + u32 location_be; + u32 length_le; + u32 length_be; + ISODirectoryEntryDateTime recoding_time; + ISODirectoryEntryFlags flags; + u8 interleaved_unit_size; + u8 interleaved_gap_size; + u16 sequence_le; + u16 sequence_be; + u8 filename_length; + }; + +#pragma pack(pop) + + bool ReadPVD(); + + std::optional LocateFile(const char* path); + std::optional LocateFile(const char* path, u8* sector_buffer, u32 directory_record_lba, + u32 directory_record_size); + + CDImage* m_image; + u32 m_track_number; + + ISOPrimaryVolumeDescriptor m_pvd = {}; +}; \ No newline at end of file diff --git a/jni/common/jit_code_buffer.cpp b/jni/common/jit_code_buffer.cpp new file mode 100644 index 0000000..62baf02 --- /dev/null +++ b/jni/common/jit_code_buffer.cpp @@ -0,0 +1,233 @@ +#include "jit_code_buffer.h" +#include "align.h" +#include "assert.h" +#include "common/log.h" +#include "cpu_detect.h" +#include +Log_SetChannel(JitCodeBuffer); + +#if defined(WIN32) +#include "windows_headers.h" +#else +#include +#include +#endif + +JitCodeBuffer::JitCodeBuffer() = default; + +JitCodeBuffer::JitCodeBuffer(u32 size, u32 far_code_size) +{ + if (!Allocate(size, far_code_size)) + Panic("Failed to allocate code space"); +} + +JitCodeBuffer::JitCodeBuffer(void* buffer, u32 size, u32 far_code_size, u32 guard_pages) +{ + if (!Initialize(buffer, size, far_code_size)) + Panic("Failed to initialize code space"); +} + +JitCodeBuffer::~JitCodeBuffer() +{ + Destroy(); +} + +bool JitCodeBuffer::Allocate(u32 size /* = 64 * 1024 * 1024 */, u32 far_code_size /* = 0 */) +{ + Destroy(); + + m_total_size = size + far_code_size; + +#if defined(WIN32) + m_code_ptr = static_cast(VirtualAlloc(nullptr, m_total_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE)); + if (!m_code_ptr) + { + Log_ErrorPrintf("VirtualAlloc(RWX, %u) for internal buffer failed: %u", m_total_size, GetLastError()); + return false; + } +#elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) || defined(__HAIKU__) + m_code_ptr = static_cast( + mmap(nullptr, m_total_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + if (!m_code_ptr) + { + Log_ErrorPrintf("mmap(RWX, %u) for internal buffer failed: %d", m_total_size, errno); + return false; + } +#else + return false; +#endif + + m_free_code_ptr = m_code_ptr; + m_code_size = size; + m_code_used = 0; + + m_far_code_ptr = static_cast(m_code_ptr) + size; + m_free_far_code_ptr = m_far_code_ptr; + m_far_code_size = far_code_size; + m_far_code_used = 0; + + m_old_protection = 0; + m_owns_buffer = true; + return true; +} + +bool JitCodeBuffer::Initialize(void* buffer, u32 size, u32 far_code_size /* = 0 */, u32 guard_size /* = 0 */) +{ + Destroy(); + + if ((far_code_size > 0 && guard_size >= far_code_size) || (far_code_size + (guard_size * 2)) > size) + return false; + +#if defined(WIN32) + DWORD old_protect = 0; + if (!VirtualProtect(buffer, size, PAGE_EXECUTE_READWRITE, &old_protect)) + { + Log_ErrorPrintf("VirtualProtect(RWX) for external buffer failed: %u", GetLastError()); + return false; + } + + if (guard_size > 0) + { + DWORD old_guard_protect = 0; + u8* guard_at_end = (static_cast(buffer) + size) - guard_size; + if (!VirtualProtect(buffer, guard_size, PAGE_NOACCESS, &old_guard_protect) || + !VirtualProtect(guard_at_end, guard_size, PAGE_NOACCESS, &old_guard_protect)) + { + Log_ErrorPrintf("VirtualProtect(NOACCESS) for guard page failed: %u", GetLastError()); + return false; + } + } + + m_code_ptr = static_cast(buffer); + m_old_protection = static_cast(old_protect); +#elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) || defined(__HAIKU__) + if (mprotect(buffer, size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) + { + Log_ErrorPrintf("mprotect(RWX) for external buffer failed: %d", errno); + return false; + } + + if (guard_size > 0) + { + u8* guard_at_end = (static_cast(buffer) + size) - guard_size; + if (mprotect(buffer, guard_size, PROT_NONE) != 0 || mprotect(guard_at_end, guard_size, PROT_NONE) != 0) + { + Log_ErrorPrintf("mprotect(NONE) for guard page failed: %d", errno); + return false; + } + } + + // reasonable default? + m_code_ptr = static_cast(buffer); + m_old_protection = PROT_READ | PROT_WRITE; +#else + m_code_ptr = nullptr; +#endif + + if (!m_code_ptr) + return false; + + m_total_size = size; + m_free_code_ptr = m_code_ptr + guard_size; + m_code_size = size - far_code_size - (guard_size * 2); + m_code_used = 0; + + m_far_code_ptr = static_cast(m_code_ptr) + m_code_size; + m_free_far_code_ptr = m_far_code_ptr; + m_far_code_size = far_code_size - guard_size; + m_far_code_used = 0; + + m_guard_size = guard_size; + m_owns_buffer = false; + return true; +} + +void JitCodeBuffer::Destroy() +{ + if (m_owns_buffer) + { +#if defined(WIN32) + VirtualFree(m_code_ptr, 0, MEM_RELEASE); +#elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) || defined(__HAIKU__) + munmap(m_code_ptr, m_total_size); +#endif + } + else if (m_code_ptr) + { +#if defined(WIN32) + DWORD old_protect = 0; + VirtualProtect(m_code_ptr, m_total_size, m_old_protection, &old_protect); +#else + mprotect(m_code_ptr, m_total_size, m_old_protection); +#endif + } +} + +void JitCodeBuffer::CommitCode(u32 length) +{ + if (length == 0) + return; + +#if defined(CPU_AARCH32) || defined(CPU_AARCH64) + // ARM instruction and data caches are not coherent, we need to flush after every block. + FlushInstructionCache(m_free_code_ptr, length); +#endif + + Assert(length <= (m_code_size - m_code_used)); + m_free_code_ptr += length; + m_code_used += length; +} + +void JitCodeBuffer::CommitFarCode(u32 length) +{ + if (length == 0) + return; + +#if defined(CPU_AARCH32) || defined(CPU_AARCH64) + // ARM instruction and data caches are not coherent, we need to flush after every block. + FlushInstructionCache(m_free_far_code_ptr, length); +#endif + + Assert(length <= (m_far_code_size - m_far_code_used)); + m_free_far_code_ptr += length; + m_far_code_used += length; +} + +void JitCodeBuffer::Reset() +{ + m_free_code_ptr = m_code_ptr + m_guard_size; + m_code_used = 0; + std::memset(m_free_code_ptr, 0, m_code_size); + FlushInstructionCache(m_free_code_ptr, m_code_size); + + if (m_far_code_size > 0) + { + m_free_far_code_ptr = m_far_code_ptr; + m_far_code_used = 0; + std::memset(m_free_far_code_ptr, 0, m_far_code_size); + FlushInstructionCache(m_free_far_code_ptr, m_far_code_size); + } +} + +void JitCodeBuffer::Align(u32 alignment, u8 padding_value) +{ + DebugAssert(Common::IsPow2(alignment)); + const u32 num_padding_bytes = + std::min(static_cast(Common::AlignUpPow2(reinterpret_cast(m_free_code_ptr), alignment) - + reinterpret_cast(m_free_code_ptr)), + GetFreeCodeSpace()); + std::memset(m_free_code_ptr, padding_value, num_padding_bytes); + m_free_code_ptr += num_padding_bytes; + m_code_used += num_padding_bytes; +} + +void JitCodeBuffer::FlushInstructionCache(void* address, u32 size) +{ +#if defined(WIN32) + ::FlushInstructionCache(GetCurrentProcess(), address, size); +#elif defined(__GNUC__) || defined(__clang__) + __builtin___clear_cache(reinterpret_cast(address), reinterpret_cast(address) + size); +#else +#error Unknown platform. +#endif +} diff --git a/jni/common/jit_code_buffer.h b/jni/common/jit_code_buffer.h new file mode 100644 index 0000000..64957d6 --- /dev/null +++ b/jni/common/jit_code_buffer.h @@ -0,0 +1,48 @@ +#pragma once +#include "types.h" + +class JitCodeBuffer +{ +public: + JitCodeBuffer(); + JitCodeBuffer(u32 size, u32 far_code_size); + JitCodeBuffer(void* buffer, u32 size, u32 far_code_size, u32 guard_size); + ~JitCodeBuffer(); + + bool Allocate(u32 size = 64 * 1024 * 1024, u32 far_code_size = 0); + bool Initialize(void* buffer, u32 size, u32 far_code_size = 0, u32 guard_size = 0); + void Destroy(); + void Reset(); + + u8* GetFreeCodePointer() const { return m_free_code_ptr; } + u32 GetFreeCodeSpace() const { return static_cast(m_code_size - m_code_used); } + void CommitCode(u32 length); + + u8* GetFreeFarCodePointer() const { return m_free_far_code_ptr; } + u32 GetFreeFarCodeSpace() const { return static_cast(m_far_code_size - m_far_code_used); } + void CommitFarCode(u32 length); + + /// Adjusts the free code pointer to the specified alignment, padding with bytes. + /// Assumes alignment is a power-of-two. + void Align(u32 alignment, u8 padding_value); + + /// Flushes the instruction cache on the host for the specified range. + static void FlushInstructionCache(void* address, u32 size); + +private: + u8* m_code_ptr = nullptr; + u8* m_free_code_ptr = nullptr; + u32 m_code_size = 0; + u32 m_code_used = 0; + + u8* m_far_code_ptr = nullptr; + u8* m_free_far_code_ptr = nullptr; + u32 m_far_code_size = 0; + u32 m_far_code_used = 0; + + u32 m_total_size = 0; + u32 m_guard_size = 0; + u32 m_old_protection = 0; + bool m_owns_buffer = false; +}; + diff --git a/jni/common/log.cpp b/jni/common/log.cpp new file mode 100644 index 0000000..734a8e5 --- /dev/null +++ b/jni/common/log.cpp @@ -0,0 +1,460 @@ +#include "log.h" +#include "assert.h" +#include "file_system.h" +#include "string.h" +#include "timer.h" +#include +#include +#include + +#if defined(WIN32) +#include "windows_headers.h" +#elif defined(__ANDROID__) +#include +#else +#include +#endif + +namespace Log { + +static const char s_log_level_characters[LOGLEVEL_COUNT] = {'X', 'E', 'W', 'P', 'I', 'V', 'D', 'R', 'B', 'T'}; + +struct RegisteredCallback +{ + CallbackFunctionType Function; + void* Parameter; +}; + +std::vector s_callbacks; +static std::mutex s_callback_mutex; + +static LOGLEVEL s_filter_level = LOGLEVEL_TRACE; + +static Common::Timer::Value s_startTimeStamp = Common::Timer::GetValue(); + +static bool s_consoleOutputEnabled = false; +static String s_consoleOutputChannelFilter; +static LOGLEVEL s_consoleOutputLevelFilter = LOGLEVEL_TRACE; + +static bool s_debugOutputEnabled = false; +static String s_debugOutputChannelFilter; +static LOGLEVEL s_debugOutputLevelFilter = LOGLEVEL_TRACE; + +static bool s_fileOutputEnabled = false; +static bool s_fileOutputTimestamp = false; +static String s_fileOutputChannelFilter; +static LOGLEVEL s_fileOutputLevelFilter = LOGLEVEL_TRACE; +std::unique_ptr s_fileOutputHandle(nullptr, [](std::FILE* fp) { + if (fp) + { + std::fclose(fp); + } +}); + +void RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam) +{ + RegisteredCallback Callback; + Callback.Function = callbackFunction; + Callback.Parameter = pUserParam; + + std::lock_guard guard(s_callback_mutex); + s_callbacks.push_back(std::move(Callback)); +} + +void UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam) +{ + std::lock_guard guard(s_callback_mutex); + + for (auto iter = s_callbacks.begin(); iter != s_callbacks.end(); ++iter) + { + if (iter->Function == callbackFunction && iter->Parameter == pUserParam) + { + s_callbacks.erase(iter); + break; + } + } +} + +bool IsConsoleOutputEnabled() +{ + return s_consoleOutputEnabled; +} + +bool IsDebugOutputEnabled() +{ + return s_debugOutputEnabled; +} + +static void ExecuteCallbacks(const char* channelName, const char* functionName, LOGLEVEL level, const char* message) +{ + std::lock_guard guard(s_callback_mutex); + for (RegisteredCallback& callback : s_callbacks) + callback.Function(callback.Parameter, channelName, functionName, level, message); +} + +static void FormatLogMessageForDisplay(const char* channelName, const char* functionName, LOGLEVEL level, + const char* message, void (*printCallback)(const char*, void*), + void* pCallbackUserData, bool timestamp = true) +{ + if (timestamp) + { + // find time since start of process + float messageTime = + static_cast(Common::Timer::ConvertValueToSeconds(Common::Timer::GetValue() - s_startTimeStamp)); + + // write prefix + char prefix[256]; + if (level <= LOGLEVEL_PERF) + std::snprintf(prefix, countof(prefix), "[%10.4f] %c(%s): ", messageTime, s_log_level_characters[level], + functionName); + else + std::snprintf(prefix, countof(prefix), "[%10.4f] %c/%s: ", messageTime, s_log_level_characters[level], + channelName); + + printCallback(prefix, pCallbackUserData); + } + else + { + // write prefix + char prefix[256]; + if (level <= LOGLEVEL_PERF) + std::snprintf(prefix, countof(prefix), "%c(%s): ", s_log_level_characters[level], functionName); + else + std::snprintf(prefix, countof(prefix), "%c/%s: ", s_log_level_characters[level], channelName); + + printCallback(prefix, pCallbackUserData); + } + + // write message + printCallback(message, pCallbackUserData); +} + +#if defined(WIN32) + +static void ConsoleOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, + LOGLEVEL level, const char* message) +{ + if (!s_consoleOutputEnabled || level > s_consoleOutputLevelFilter || + s_consoleOutputChannelFilter.Find(channelName) >= 0) + return; + + if (level > LOGLEVEL_COUNT) + level = LOGLEVEL_TRACE; + + HANDLE hConsole = GetStdHandle((level <= LOGLEVEL_WARNING) ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE); + if (hConsole != INVALID_HANDLE_VALUE) + { + static const WORD levelColors[LOGLEVEL_COUNT] = { + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, // NONE + FOREGROUND_RED | FOREGROUND_INTENSITY, // ERROR + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // WARNING + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY, // PERF + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // INFO + FOREGROUND_GREEN | FOREGROUND_INTENSITY, // VERBOSE + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, // DEV + FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // PROFILE + FOREGROUND_GREEN, // DEBUG + FOREGROUND_BLUE, // TRACE + }; + + CONSOLE_SCREEN_BUFFER_INFO oldConsoleScreenBufferInfo; + GetConsoleScreenBufferInfo(hConsole, &oldConsoleScreenBufferInfo); + SetConsoleTextAttribute(hConsole, levelColors[level]); + + // write message in the formatted way + FormatLogMessageForDisplay( + channelName, functionName, level, message, + [](const char* text, void* hConsole) { + DWORD written; + WriteConsoleA(static_cast(hConsole), text, static_cast(std::strlen(text)), &written, nullptr); + }, + (void*)hConsole); + + // write newline + DWORD written; + WriteConsoleA(hConsole, "\r\n", 2, &written, nullptr); + + // restore color + SetConsoleTextAttribute(hConsole, oldConsoleScreenBufferInfo.wAttributes); + } +} + +static void DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, + const char* message) +{ + if (!s_debugOutputEnabled || level > s_debugOutputLevelFilter || s_debugOutputChannelFilter.Find(channelName) >= 0) + return; + + FormatLogMessageForDisplay( + channelName, functionName, level, message, [](const char* text, void*) { OutputDebugStringA(text); }, nullptr); + + OutputDebugStringA("\n"); +} + +#elif defined(__ANDROID__) + +static void ConsoleOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, + LOGLEVEL level, const char* message) +{ +} + +static void DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, + const char* message) +{ + if (!s_debugOutputEnabled || level > s_debugOutputLevelFilter || s_debugOutputChannelFilter.Find(functionName) >= 0) + return; + + static const int logPriority[LOGLEVEL_COUNT] = { + ANDROID_LOG_INFO, // NONE + ANDROID_LOG_ERROR, // ERROR + ANDROID_LOG_WARN, // WARNING + ANDROID_LOG_INFO, // PERF + ANDROID_LOG_INFO, // INFO + ANDROID_LOG_INFO, // VERBOSE + ANDROID_LOG_DEBUG, // DEV + ANDROID_LOG_DEBUG, // PROFILE + ANDROID_LOG_DEBUG, // DEBUG + ANDROID_LOG_DEBUG, // TRACE + }; + + __android_log_write(logPriority[level], channelName, message); +} + +#else + +static void ConsoleOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, + LOGLEVEL level, const char* message) +{ + if (!s_consoleOutputEnabled || level > s_consoleOutputLevelFilter || + s_consoleOutputChannelFilter.Find(channelName) >= 0) + return; + + static const char* colorCodes[LOGLEVEL_COUNT] = { + "\033[0m", // NONE + "\033[1;31m", // ERROR + "\033[1;33m", // WARNING + "\033[1;35m", // PERF + "\033[1;37m", // INFO + "\033[1;32m", // VERBOSE + "\033[0;37m", // DEV + "\033[1;36m", // PROFILE + "\033[0;32m", // DEBUG + "\033[0;34m", // TRACE + }; + + int outputFd = (level <= LOGLEVEL_WARNING) ? STDERR_FILENO : STDOUT_FILENO; + + write(outputFd, colorCodes[level], std::strlen(colorCodes[level])); + + Log::FormatLogMessageForDisplay( + channelName, functionName, level, message, + [](const char* text, void* outputFd) { write((int)(intptr_t)outputFd, text, std::strlen(text)); }, + (void*)(intptr_t)outputFd); + + write(outputFd, colorCodes[0], std::strlen(colorCodes[0])); + write(outputFd, "\n", 1); +} + +static void DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, + const char* message) +{ +} + +#endif + +void SetConsoleOutputParams(bool Enabled, const char* ChannelFilter, LOGLEVEL LevelFilter) +{ + if (s_consoleOutputEnabled != Enabled) + { + s_consoleOutputEnabled = Enabled; + if (Enabled) + RegisterCallback(ConsoleOutputLogCallback, NULL); + else + UnregisterCallback(ConsoleOutputLogCallback, NULL); + +#if defined(WIN32) + // On windows, no console is allocated by default on a windows based application + static bool console_was_allocated = false; + static std::FILE* stdin_fp = nullptr; + static std::FILE* stdout_fp = nullptr; + static std::FILE* stderr_fp = nullptr; + if (Enabled) + { + if (GetConsoleWindow() == NULL) + { + DebugAssert(!console_was_allocated); + + // Attach to the parent console if we're running from a command window + if (!AttachConsole(ATTACH_PARENT_PROCESS)) + { + if (!AllocConsole()) + return; + } + + console_was_allocated = true; + + std::FILE* fp; + freopen_s(&fp, "CONIN$", "r", stdin); + freopen_s(&fp, "CONOUT$", "w", stdout); + freopen_s(&fp, "CONOUT$", "w", stderr); + } + } + else + { + if (console_was_allocated) + { + std::FILE* fp; + freopen_s(&fp, "NUL:", "w", stderr); + freopen_s(&fp, "NUL:", "w", stdout); + freopen_s(&fp, "NUL:", "w", stdin); + FreeConsole(); + console_was_allocated = false; + } + } +#endif + } + + s_consoleOutputChannelFilter = (ChannelFilter != NULL) ? ChannelFilter : ""; + s_consoleOutputLevelFilter = LevelFilter; +} + +void SetDebugOutputParams(bool enabled, const char* channelFilter /* = nullptr */, + LOGLEVEL levelFilter /* = LOGLEVEL_TRACE */) +{ + if (s_debugOutputEnabled != enabled) + { + s_debugOutputEnabled = enabled; + if (enabled) + RegisterCallback(DebugOutputLogCallback, nullptr); + else + UnregisterCallback(DebugOutputLogCallback, nullptr); + } + + s_debugOutputChannelFilter = (channelFilter != nullptr) ? channelFilter : ""; + s_debugOutputLevelFilter = levelFilter; +} + +static void FileOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, + const char* message) +{ + if (level > s_fileOutputLevelFilter || s_fileOutputChannelFilter.Find(channelName) >= 0) + return; + + if (s_fileOutputTimestamp) + { + // find time since start of process + float messageTime = + static_cast(Common::Timer::ConvertValueToSeconds(Common::Timer::GetValue() - s_startTimeStamp)); + + // write prefix + if (level <= LOGLEVEL_PERF) + { + std::fprintf(s_fileOutputHandle.get(), "[%10.4f] %c(%s): %s\n", messageTime, s_log_level_characters[level], + functionName, message); + } + else + { + std::fprintf(s_fileOutputHandle.get(), "[%10.4f] %c/%s: %s\n", messageTime, s_log_level_characters[level], + channelName, message); + } + } + else + { + if (level <= LOGLEVEL_PERF) + { + std::fprintf(s_fileOutputHandle.get(), "%c(%s): %s\n", s_log_level_characters[level], functionName, message); + } + else + { + std::fprintf(s_fileOutputHandle.get(), "%c/%s: %s\n", s_log_level_characters[level], channelName, message); + } + } +} + +void SetFileOutputParams(bool enabled, const char* filename, bool timestamps /* = true */, + const char* channelFilter /* = nullptr */, LOGLEVEL levelFilter /* = LOGLEVEL_TRACE */) +{ + if (s_fileOutputEnabled != enabled) + { + if (enabled) + { + s_fileOutputHandle.reset(FileSystem::OpenCFile(filename, "wb")); + if (!s_fileOutputHandle) + { + Log::Writef("Log", __FUNCTION__, LOGLEVEL_ERROR, "Failed to open log file '%s'", filename); + return; + } + + RegisterCallback(FileOutputLogCallback, nullptr); + } + else + { + UnregisterCallback(FileOutputLogCallback, nullptr); + s_fileOutputHandle.reset(); + } + + s_fileOutputEnabled = enabled; + } + + std::lock_guard guard(s_callback_mutex); + s_fileOutputChannelFilter = (channelFilter != nullptr) ? channelFilter : ""; + ; + s_fileOutputLevelFilter = levelFilter; +} + +void SetFilterLevel(LOGLEVEL level) +{ + DebugAssert(level < LOGLEVEL_COUNT); + s_filter_level = level; +} + +void Write(const char* channelName, const char* functionName, LOGLEVEL level, const char* message) +{ + if (level > s_filter_level) + return; + + ExecuteCallbacks(channelName, functionName, level, message); +} + +void Writef(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, ...) +{ + if (level > s_filter_level) + return; + + va_list ap; + va_start(ap, format); + Writev(channelName, functionName, level, format, ap); + va_end(ap); +} + +void Writev(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, va_list ap) +{ + if (level > s_filter_level) + return; + + va_list apCopy; + va_copy(apCopy, ap); + +#ifdef WIN32 + u32 requiredSize = static_cast(_vscprintf(format, apCopy)); +#else + u32 requiredSize = std::vsnprintf(nullptr, 0, format, apCopy); +#endif + va_end(apCopy); + + if (requiredSize < 256) + { + char buffer[256]; + std::vsnprintf(buffer, countof(buffer), format, ap); + ExecuteCallbacks(channelName, functionName, level, buffer); + } + else + { + char* buffer = new char[requiredSize + 1]; + std::vsnprintf(buffer, requiredSize + 1, format, ap); + ExecuteCallbacks(channelName, functionName, level, buffer); + delete[] buffer; + } +} + +} // namespace Log diff --git a/jni/common/log.h b/jni/common/log.h new file mode 100644 index 0000000..1223ce0 --- /dev/null +++ b/jni/common/log.h @@ -0,0 +1,92 @@ +#pragma once +#include "types.h" +#include +#include + +enum LOGLEVEL +{ + LOGLEVEL_NONE = 0, // Silences all log traffic + LOGLEVEL_ERROR = 1, // "ErrorPrint" + LOGLEVEL_WARNING = 2, // "WarningPrint" + LOGLEVEL_PERF = 3, // "PerfPrint" + LOGLEVEL_INFO = 4, // "InfoPrint" + LOGLEVEL_VERBOSE = 5, // "VerbosePrint" + LOGLEVEL_DEV = 6, // "DevPrint" + LOGLEVEL_PROFILE = 7, // "ProfilePrint" + LOGLEVEL_DEBUG = 8, // "DebugPrint" + LOGLEVEL_TRACE = 9, // "TracePrint" + LOGLEVEL_COUNT = 10 +}; + +namespace Log { +// log message callback type +using CallbackFunctionType = void (*)(void* pUserParam, const char* channelName, const char* functionName, + LOGLEVEL level, const char* message); + +// registers a log callback +void RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam); + +// unregisters a log callback +void UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam); + +// adds a standard console output +bool IsConsoleOutputEnabled(); +void SetConsoleOutputParams(bool enabled, const char* channelFilter = nullptr, LOGLEVEL levelFilter = LOGLEVEL_TRACE); + +// adds a debug console output [win32/android only] +bool IsDebugOutputEnabled(); +void SetDebugOutputParams(bool enabled, const char* channelFilter = nullptr, LOGLEVEL levelFilter = LOGLEVEL_TRACE); + +// adds a file output +void SetFileOutputParams(bool enabled, const char* filename, bool timestamps = true, + const char* channelFilter = nullptr, LOGLEVEL levelFilter = LOGLEVEL_TRACE); + +// Sets global filtering level, messages below this level won't be sent to any of the logging sinks. +void SetFilterLevel(LOGLEVEL level); + +// writes a message to the log +void Write(const char* channelName, const char* functionName, LOGLEVEL level, const char* message); +void Writef(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, ...); +void Writev(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, va_list ap); +} // namespace Log + +// log wrappers +#define Log_SetChannel(ChannelName) static const char* ___LogChannel___ = #ChannelName; +#define Log_ErrorPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_ERROR, msg) +#define Log_ErrorPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_ERROR, __VA_ARGS__) +#define Log_WarningPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_WARNING, msg) +#define Log_WarningPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_WARNING, __VA_ARGS__) +#define Log_PerfPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_PERF, msg) +#define Log_PerfPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_PERF, __VA_ARGS__) +#define Log_InfoPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_INFO, msg) +#define Log_InfoPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_INFO, __VA_ARGS__) +#define Log_VerbosePrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_VERBOSE, msg) +#define Log_VerbosePrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_VERBOSE, __VA_ARGS__) +#define Log_DevPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_DEV, msg) +#define Log_DevPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_DEV, __VA_ARGS__) +#define Log_ProfilePrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_PROFILE, msg) +#define Log_ProfilePrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_PROFILE, __VA_ARGS__) + +#ifdef _DEBUG +#define Log_DebugPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_DEBUG, msg) +#define Log_DebugPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_DEBUG, __VA_ARGS__) +#define Log_TracePrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_TRACE, msg) +#define Log_TracePrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_TRACE, __VA_ARGS__) +#else +#define Log_DebugPrint(msg) \ + do \ + { \ + } while (0) +#define Log_DebugPrintf(...) \ + do \ + { \ + } while (0) +#define Log_TracePrint(msg) \ + do \ + { \ + } while (0) +#define Log_TracePrintf(...) \ + do \ + { \ + } while (0) +#endif diff --git a/jni/common/make_array.h b/jni/common/make_array.h new file mode 100644 index 0000000..989c5eb --- /dev/null +++ b/jni/common/make_array.h @@ -0,0 +1,12 @@ +#pragma once +#include +#include + +// Source: https://gist.github.com/klmr/2775736#file-make_array-hpp + +template +constexpr auto make_array(T&&... values) + -> std::array::type>::type, sizeof...(T)> +{ + return {std::forward(values)...}; +} diff --git a/jni/common/md5_digest.cpp b/jni/common/md5_digest.cpp new file mode 100644 index 0000000..4a2aea5 --- /dev/null +++ b/jni/common/md5_digest.cpp @@ -0,0 +1,226 @@ +#include "md5_digest.h" + +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse(unsigned char* buf, unsigned longs) +{ + u32 t; + do + { + t = (u32)((unsigned)buf[3] << 8 | buf[2]) << 16 | ((unsigned)buf[1] << 8 | buf[0]); + *(u32*)buf = t; + buf += 4; + } while (--longs); +} +#endif + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(u32 buf[4], u32 in[16]) +{ + // register u32 a, b, c, d; + u32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +MD5Digest::MD5Digest() +{ + Reset(); +} + +void MD5Digest::Reset() +{ + this->buf[0] = 0x67452301; + this->buf[1] = 0xefcdab89; + this->buf[2] = 0x98badcfe; + this->buf[3] = 0x10325476; + + this->bits[0] = 0; + this->bits[1] = 0; +} + +void MD5Digest::Update(const void* pData, u32 cbData) +{ + u32 t; + const u8* pByteData = reinterpret_cast(pData); + + /* Update bitcount */ + + t = this->bits[0]; + if ((this->bits[0] = t + ((u32)cbData << 3)) < t) + this->bits[1]++; /* Carry from low to high */ + this->bits[1] += cbData >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) + { + u8* p = (u8*)this->in + t; + + t = 64 - t; + if (cbData < t) + { + std::memcpy(p, pByteData, cbData); + return; + } + std::memcpy(p, pByteData, t); + byteReverse(this->in, 16); + MD5Transform(this->buf, (u32*)this->in); + pByteData += t; + cbData -= t; + } + /* Process data in 64-byte chunks */ + + while (cbData >= 64) + { + std::memcpy(this->in, pByteData, 64); + byteReverse(this->in, 16); + MD5Transform(this->buf, (u32*)this->in); + pByteData += 64; + cbData -= 64; + } + + /* Handle any remaining bytes of data. */ + + std::memcpy(this->in, pByteData, cbData); +} + +void MD5Digest::Final(u8 Digest[16]) +{ + u32 count; + u8* p; + + /* Compute number of bytes mod 64 */ + count = (this->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = this->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) + { + /* Two lots of padding: Pad the first block to 64 bytes */ + std::memset(p, 0, count); + byteReverse(this->in, 16); + MD5Transform(this->buf, (u32*)this->in); + + /* Now fill the next block with 56 bytes */ + std::memset(this->in, 0, 56); + } + else + { + /* Pad block to 56 bytes */ + std::memset(p, 0, count - 8); + } + byteReverse(this->in, 14); + + /* Append length in bits and transform */ + ((u32*)this->in)[14] = this->bits[0]; + ((u32*)this->in)[15] = this->bits[1]; + + MD5Transform(this->buf, (u32*)this->in); + byteReverse((unsigned char*)this->buf, 4); + std::memcpy(Digest, this->buf, 16); +} diff --git a/jni/common/md5_digest.h b/jni/common/md5_digest.h new file mode 100644 index 0000000..3ff5a47 --- /dev/null +++ b/jni/common/md5_digest.h @@ -0,0 +1,20 @@ +#pragma once +#include "types.h" + +// based heavily on this implementation: +// http://www.fourmilab.ch/md5/ + +class MD5Digest +{ +public: + MD5Digest(); + + void Update(const void* pData, u32 cbData); + void Final(u8 Digest[16]); + void Reset(); + +private: + u32 buf[4]; + u32 bits[2]; + u8 in[64]; +}; diff --git a/jni/common/memory_arena.cpp b/jni/common/memory_arena.cpp new file mode 100644 index 0000000..96cbc0e --- /dev/null +++ b/jni/common/memory_arena.cpp @@ -0,0 +1,294 @@ +#include "memory_arena.h" +#include "common/assert.h" +#include "common/log.h" +#include "common/string_util.h" +Log_SetChannel(Common::MemoryArena); + +#if defined(WIN32) +#include "common/windows_headers.h" +#elif defined(ANDROID) +#include +#include +#include +#include +#include +#include +#elif defined(__linux__) || defined(__APPLE__) +#include +#include +#include +#include +#endif + +namespace Common { + +// Borrowed from Dolphin +#ifdef ANDROID +#define ASHMEM_DEVICE "/dev/ashmem" + +static int AshmemCreateFileMapping(const char* name, size_t size) +{ + // ASharedMemory path - works on API >= 26 and falls through on API < 26: + + // We can't call ASharedMemory_create the normal way without increasing the + // minimum version requirement to API 26, so we use dlopen/dlsym instead + static void* libandroid = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL); + static auto shared_memory_create = + reinterpret_cast(dlsym(libandroid, "ASharedMemory_create")); + if (shared_memory_create) + return shared_memory_create(name, size); + + // /dev/ashmem path - works on API < 29: + + int fd, ret; + fd = open(ASHMEM_DEVICE, O_RDWR); + if (fd < 0) + return fd; + + // We don't really care if we can't set the name, it is optional + ioctl(fd, ASHMEM_SET_NAME, name); + + ret = ioctl(fd, ASHMEM_SET_SIZE, size); + if (ret < 0) + { + close(fd); + Log_ErrorPrintf("Ashmem returned error: 0x%08x", ret); + return ret; + } + return fd; +} +#endif + +MemoryArena::MemoryArena() = default; + +MemoryArena::~MemoryArena() +{ +#if defined(WIN32) + if (m_file_handle) + CloseHandle(m_file_handle); +#elif defined(__linux__) + if (m_shmem_fd > 0) + close(m_shmem_fd); +#endif +} + +void* MemoryArena::FindBaseAddressForMapping(size_t size) +{ + void* base_address; +#if defined(WIN32) + base_address = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); + if (base_address) + VirtualFree(base_address, 0, MEM_RELEASE); +#elif defined(__linux__) || defined(__APPLE__) + base_address = mmap(nullptr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); + if (base_address) + munmap(base_address, size); +#elif defined(__ANDROID__) + base_address = mmap(nullptr, size, PROT_NONE, MAP_ANON | MAP_SHARED, -1, 0); + if (base_address) + munmap(base_address, size); +#else + base_address = nullptr; +#endif + + if (!base_address) + { + Log_ErrorPrintf("Failed to get base address for memory mapping of size %zu", size); + return nullptr; + } + + return base_address; +} + +bool MemoryArena::Create(size_t size, bool writable, bool executable) +{ +#if defined(WIN32) + const std::string file_mapping_name = StringUtil::StdStringFromFormat("duckstation_%u", GetCurrentProcessId()); + + const DWORD protect = (writable ? (executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE) : PAGE_READONLY); + m_file_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, protect, Truncate32(size >> 32), Truncate32(size), + file_mapping_name.c_str()); + if (!m_file_handle) + { + Log_ErrorPrintf("CreateFileMapping failed: %u", GetLastError()); + return false; + } + + return true; +#elif defined(ANDROID) + const std::string file_mapping_name = + StringUtil::StdStringFromFormat("duckstation_%u", static_cast(getpid())); + m_shmem_fd = AshmemCreateFileMapping(file_mapping_name.c_str(), size); + if (m_shmem_fd < 0) + { + Log_ErrorPrintf("AshmemCreateFileMapping failed: %d %d", m_shmem_fd, errno); + return false; + } + + return true; +#elif defined(__linux__) + const std::string file_mapping_name = + StringUtil::StdStringFromFormat("duckstation_%u", static_cast(getpid())); + m_shmem_fd = shm_open(file_mapping_name.c_str(), O_CREAT | O_EXCL | (writable ? O_RDWR : O_RDONLY), 0600); + if (m_shmem_fd < 0) + { + Log_ErrorPrintf("shm_open failed: %d", errno); + return false; + } + + // we're not going to be opening this mapping in other processes, so remove the file + shm_unlink(file_mapping_name.c_str()); + + // ensure it's the correct size + if (ftruncate64(m_shmem_fd, static_cast(size)) < 0) + { + Log_ErrorPrintf("ftruncate64(%zu) failed: %d", size, errno); + return false; + } + + return true; +#elif defined(__APPLE__) + const std::string file_mapping_name = + StringUtil::StdStringFromFormat("duckstation_%u", static_cast(getpid())); + m_shmem_fd = shm_open(file_mapping_name.c_str(), O_CREAT | O_EXCL | (writable ? O_RDWR : O_RDONLY), 0600); + if (m_shmem_fd < 0) + { + Log_ErrorPrintf("shm_open failed: %d", errno); + return false; + } + + // we're not going to be opening this mapping in other processes, so remove the file + shm_unlink(file_mapping_name.c_str()); + + // ensure it's the correct size + if (ftruncate(m_shmem_fd, static_cast(size)) < 0) + { + Log_ErrorPrintf("ftruncate(%zu) failed: %d", size, errno); + return false; + } + + return true; +#else + return false; +#endif +} + +std::optional MemoryArena::CreateView(size_t offset, size_t size, bool writable, bool executable, + void* fixed_address) +{ + void* base_pointer = CreateViewPtr(offset, size, writable, executable, fixed_address); + if (!base_pointer) + return std::nullopt; + + return View(this, base_pointer, offset, size, writable); +} + +void* MemoryArena::CreateViewPtr(size_t offset, size_t size, bool writable, bool executable, + void* fixed_address /*= nullptr*/) +{ + void* base_pointer; +#if defined(WIN32) + const DWORD desired_access = FILE_MAP_READ | (writable ? FILE_MAP_WRITE : 0) | (executable ? FILE_MAP_EXECUTE : 0); + base_pointer = + MapViewOfFileEx(m_file_handle, desired_access, Truncate32(offset >> 32), Truncate32(offset), size, fixed_address); + if (!base_pointer) + return nullptr; +#elif defined(__linux__) + const int flags = (fixed_address != nullptr) ? (MAP_SHARED | MAP_FIXED) : MAP_SHARED; + const int prot = PROT_READ | (writable ? PROT_WRITE : 0) | (executable ? PROT_EXEC : 0); + base_pointer = mmap64(fixed_address, size, prot, flags, m_shmem_fd, static_cast(offset)); + if (base_pointer == reinterpret_cast(-1)) + return nullptr; +#elif defined(__APPLE__) + const int flags = (fixed_address != nullptr) ? (MAP_SHARED | MAP_FIXED) : MAP_SHARED; + const int prot = PROT_READ | (writable ? PROT_WRITE : 0) | (executable ? PROT_EXEC : 0); + base_pointer = mmap(fixed_address, size, prot, flags, m_shmem_fd, static_cast(offset)); + if (base_pointer == reinterpret_cast(-1)) + return nullptr; +#else + return nullptr; +#endif + + m_num_views.fetch_add(1); + return base_pointer; +} + +bool MemoryArena::FlushViewPtr(void* address, size_t size) +{ +#if defined(WIN32) + return FlushViewOfFile(address, size); +#elif defined(__linux__) || defined(__APPLE__) + return (msync(address, size, 0) >= 0); +#else + return false; +#endif +} + +bool MemoryArena::ReleaseViewPtr(void* address, size_t size) +{ + bool result; +#if defined(WIN32) + result = static_cast(UnmapViewOfFile(address)); +#elif defined(__linux__) || defined(__APPLE__) + result = (munmap(address, size) >= 0); +#else + result = false; +#endif + + if (!result) + { + Log_ErrorPrintf("Failed to unmap previously-created view at %p", address); + return false; + } + + const size_t prev_count = m_num_views.fetch_sub(1); + Assert(prev_count > 0); + return true; +} + +bool MemoryArena::SetPageProtection(void* address, size_t length, bool readable, bool writable, bool executable) +{ +#if defined(WIN32) + static constexpr DWORD protection_table[2][2][2] = { + {{PAGE_NOACCESS, PAGE_EXECUTE}, {PAGE_WRITECOPY, PAGE_EXECUTE_WRITECOPY}}, + {{PAGE_READONLY, PAGE_EXECUTE_READ}, {PAGE_READWRITE, PAGE_EXECUTE_READWRITE}}}; + + DWORD old_protect; + return static_cast( + VirtualProtect(address, length, protection_table[readable][writable][executable], &old_protect)); +#elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) + const int prot = (readable ? PROT_READ : 0) | (writable ? PROT_WRITE : 0) | (executable ? PROT_EXEC : 0); + return (mprotect(address, length, prot) >= 0); +#else + return false; +#endif +} + +MemoryArena::View::View(MemoryArena* parent, void* base_pointer, size_t arena_offset, size_t mapping_size, + bool writable) + : m_parent(parent), m_base_pointer(base_pointer), m_arena_offset(arena_offset), m_mapping_size(mapping_size), + m_writable(writable) +{ +} + +MemoryArena::View::View(View&& view) + : m_parent(view.m_parent), m_base_pointer(view.m_base_pointer), m_arena_offset(view.m_arena_offset), + m_mapping_size(view.m_mapping_size) +{ + view.m_parent = nullptr; + view.m_base_pointer = nullptr; + view.m_arena_offset = 0; + view.m_mapping_size = 0; +} + +MemoryArena::View::~View() +{ + if (m_parent) + { + if (m_writable && !m_parent->FlushViewPtr(m_base_pointer, m_mapping_size)) + Panic("Failed to flush previously-created view"); + if (!m_parent->ReleaseViewPtr(m_base_pointer, m_mapping_size)) + Panic("Failed to unmap previously-created view"); + } +} +} // namespace Common diff --git a/jni/common/memory_arena.h b/jni/common/memory_arena.h new file mode 100644 index 0000000..7e2d51b --- /dev/null +++ b/jni/common/memory_arena.h @@ -0,0 +1,58 @@ +#pragma once +#include "types.h" +#include +#include + +namespace Common { +class MemoryArena +{ +public: + class View + { + public: + View(MemoryArena* parent, void* base_pointer, size_t arena_offset, size_t mapping_size, bool writable); + View(View&& view); + ~View(); + + void* GetBasePointer() const { return m_base_pointer; } + size_t GetArenaOffset() const { return m_arena_offset; } + size_t GetMappingSize() const { return m_mapping_size; } + bool IsWritable() const { return m_writable; } + + private: + MemoryArena* m_parent; + void* m_base_pointer; + size_t m_arena_offset; + size_t m_mapping_size; + bool m_writable; + }; + + MemoryArena(); + ~MemoryArena(); + + static void* FindBaseAddressForMapping(size_t size); + + bool Create(size_t size, bool writable, bool executable); + + std::optional CreateView(size_t offset, size_t size, bool writable, bool executable, + void* fixed_address = nullptr); + + void* CreateViewPtr(size_t offset, size_t size, bool writable, bool executable, void* fixed_address = nullptr); + bool FlushViewPtr(void* address, size_t size); + bool ReleaseViewPtr(void* address, size_t size); + + static bool SetPageProtection(void* address, size_t length, bool readable, bool writable, bool executable); + +private: +#if defined(WIN32) + void* m_file_handle = nullptr; +#elif defined(__linux__) || defined(ANDROID) || defined(__APPLE__) + int m_shmem_fd = -1; +#endif + + std::atomic_size_t m_num_views{0}; + size_t m_size = 0; + bool m_writable = false; + bool m_executable = false; +}; +} // namespace Common diff --git a/jni/common/minizip_helpers.cpp b/jni/common/minizip_helpers.cpp new file mode 100644 index 0000000..ffa885d --- /dev/null +++ b/jni/common/minizip_helpers.cpp @@ -0,0 +1,90 @@ +#include "minizip_helpers.h" +#include "file_system.h" +#include "ioapi.h" +#include "types.h" +#include + +namespace MinizipHelpers { + +unzFile OpenUnzMemoryFile(const void* memory, size_t memory_size) +{ + struct MemoryFileInfo + { + const u8* data; + ZPOS64_T data_size; + ZPOS64_T position; + }; + + MemoryFileInfo* fi = new MemoryFileInfo; + fi->data = static_cast(memory); + fi->data_size = static_cast(memory_size); + fi->position = 0; + +#define FI static_cast(stream) + + zlib_filefunc64_def funcs = { + [](voidpf opaque, const void* filename, int mode) -> voidpf { return opaque; }, // open + [](voidpf opaque, voidpf stream, void* buf, uLong size) -> uLong { // read + const ZPOS64_T remaining = FI->data_size - FI->position; + const ZPOS64_T to_read = std::min(remaining, static_cast(size)); + if (to_read > 0) + { + std::memcpy(buf, FI->data + FI->position, to_read); + FI->position += to_read; + } + + return static_cast(to_read); + }, + [](voidpf opaque, voidpf stream, const void* buf, uLong size) -> uLong { return 0; }, // write + [](voidpf opaque, voidpf stream) -> ZPOS64_T { return static_cast(FI->position); }, // tell + [](voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) -> long { // seek + ZPOS64_T new_position = FI->position; + if (origin == SEEK_SET) + new_position = static_cast(offset); + else if (origin == SEEK_CUR) + new_position += static_cast(offset); + else + new_position = FI->data_size; + if (new_position < 0 || new_position > FI->data_size) + return -1; + + FI->position = new_position; + return 0; + }, + [](voidpf opaque, voidpf stream) -> int { + delete FI; + return 0; + }, // close + [](voidpf opaque, voidpf stream) -> int { return 0; }, // testerror + static_cast(fi)}; + +#undef FI + + unzFile zf = unzOpen2_64("", &funcs); + if (!zf) + delete fi; + + return zf; +} + +unzFile OpenUnzFile(const char* filename) +{ + zlib_filefunc64_def funcs; + fill_fopen64_filefunc(&funcs); + + funcs.zopen64_file = [](voidpf opaque, const void* filename, int mode) -> voidpf { + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + return FileSystem::OpenCFile(static_cast(filename), mode_fopen); + }; + + return unzOpen2_64(filename, &funcs); +} + +} // namespace MinizipHelpers \ No newline at end of file diff --git a/jni/common/minizip_helpers.h b/jni/common/minizip_helpers.h new file mode 100644 index 0000000..15a78b0 --- /dev/null +++ b/jni/common/minizip_helpers.h @@ -0,0 +1,9 @@ +#pragma once +#include "unzip.h" + +namespace MinizipHelpers { + +unzFile OpenUnzMemoryFile(const void* memory, size_t memory_size); +unzFile OpenUnzFile(const char* filename); + +} // namespace MinizipHelpers \ No newline at end of file diff --git a/jni/common/null_audio_stream.cpp b/jni/common/null_audio_stream.cpp new file mode 100644 index 0000000..3c9c81a --- /dev/null +++ b/jni/common/null_audio_stream.cpp @@ -0,0 +1,25 @@ +#include "null_audio_stream.h" + +NullAudioStream::NullAudioStream() = default; + +NullAudioStream::~NullAudioStream() = default; + +bool NullAudioStream::OpenDevice() +{ + return true; +} + +void NullAudioStream::PauseDevice(bool paused) {} + +void NullAudioStream::CloseDevice() {} + +void NullAudioStream::FramesAvailable() +{ + // drop any buffer as soon as they're available + DropFrames(GetSamplesAvailable()); +} + +std::unique_ptr AudioStream::CreateNullAudioStream() +{ + return std::make_unique(); +} diff --git a/jni/common/null_audio_stream.h b/jni/common/null_audio_stream.h new file mode 100644 index 0000000..55ce9a3 --- /dev/null +++ b/jni/common/null_audio_stream.h @@ -0,0 +1,15 @@ +#pragma once +#include "audio_stream.h" + +class NullAudioStream final : public AudioStream +{ +public: + NullAudioStream(); + ~NullAudioStream(); + +protected: + bool OpenDevice() override; + void PauseDevice(bool paused) override; + void CloseDevice() override; + void FramesAvailable() override; +}; diff --git a/jni/common/page_fault_handler.cpp b/jni/common/page_fault_handler.cpp new file mode 100644 index 0000000..1b4f1bf --- /dev/null +++ b/jni/common/page_fault_handler.cpp @@ -0,0 +1,292 @@ +#include "page_fault_handler.h" +#include "common/cpu_detect.h" +#include "common/log.h" +#include +#include +#include +#include +Log_SetChannel(Common::PageFaultHandler); + +#if defined(WIN32) +#include "common/windows_headers.h" +#elif defined(__linux__) || defined(__ANDROID__) +#include +#include +#include +#define USE_SIGSEGV 1 +#elif defined(__APPLE__) +#include +#include +#define USE_SIGSEGV 1 +#endif + +namespace Common::PageFaultHandler { + +struct RegisteredHandler +{ + void* owner; + Callback callback; +}; +static std::vector m_handlers; +static std::mutex m_handler_lock; +static thread_local bool s_in_handler; + +#if defined(CPU_AARCH32) +static bool IsStoreInstruction(const void* ptr) +{ + u32 bits; + std::memcpy(&bits, ptr, sizeof(bits)); + + // TODO + return false; +} + +#elif defined(CPU_AARCH64) +static bool IsStoreInstruction(const void* ptr) +{ + u32 bits; + std::memcpy(&bits, ptr, sizeof(bits)); + + // Based on vixl's disassembler Instruction::IsStore(). + // if (Mask(LoadStoreAnyFMask) != LoadStoreAnyFixed) + if ((bits & 0x0a000000) != 0x08000000) + return false; + + // if (Mask(LoadStorePairAnyFMask) == LoadStorePairAnyFixed) + if ((bits & 0x3a000000) == 0x28000000) + { + // return Mask(LoadStorePairLBit) == 0 + return (bits & (1 << 22)) == 0; + } + + switch (bits & 0xC4C00000) + { + case 0x00000000: // STRB_w + case 0x40000000: // STRH_w + case 0x80000000: // STR_w + case 0xC0000000: // STR_x + case 0x04000000: // STR_b + case 0x44000000: // STR_h + case 0x84000000: // STR_s + case 0xC4000000: // STR_d + case 0x04800000: // STR_q + return true; + + default: + return false; + } +} +#endif + +#if defined(WIN32) && (defined(CPU_X64) || defined(CPU_AARCH64)) +static PVOID s_veh_handle; + +static LONG ExceptionHandler(PEXCEPTION_POINTERS exi) +{ + if (exi->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION || s_in_handler) + return EXCEPTION_CONTINUE_SEARCH; + + s_in_handler = true; + +#if defined(_M_AMD64) + void* const exception_pc = reinterpret_cast(exi->ContextRecord->Rip); +#elif defined(_M_ARM64) + void* const exception_pc = reinterpret_cast(exi->ContextRecord->Pc); +#else + void* const exception_pc = nullptr; +#endif + + void* const exception_address = reinterpret_cast(exi->ExceptionRecord->ExceptionInformation[1]); + bool const is_write = exi->ExceptionRecord->ExceptionInformation[0] == 1; + + std::lock_guard guard(m_handler_lock); + for (const RegisteredHandler& rh : m_handlers) + { + if (rh.callback(exception_pc, exception_address, is_write) == HandlerResult::ContinueExecution) + { + s_in_handler = false; + return EXCEPTION_CONTINUE_EXECUTION; + } + } + + s_in_handler = false; + return EXCEPTION_CONTINUE_SEARCH; +} + +#elif defined(USE_SIGSEGV) + +static struct sigaction s_old_sigsegv_action; +#if defined(__APPLE__) || defined(__aarch64__) +static struct sigaction s_old_sigbus_action; +#endif + +static void SIGSEGVHandler(int sig, siginfo_t* info, void* ctx) +{ + if ((info->si_code != SEGV_MAPERR && info->si_code != SEGV_ACCERR) || s_in_handler) + return; + +#ifndef __APPLE__ + void* const exception_address = reinterpret_cast(info->si_addr); + +#if defined(CPU_X64) + void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext.gregs[REG_RIP]); + const bool is_write = (static_cast(ctx)->uc_mcontext.gregs[REG_ERR] & 2) != 0; +#elif defined(CPU_AARCH32) + void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext.arm_pc); + const bool is_write = IsStoreInstruction(exception_pc); +#elif defined(CPU_AARCH64) + void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext.pc); + const bool is_write = IsStoreInstruction(exception_pc); +#else + void* const exception_pc = nullptr; + const bool is_write = false; +#endif +#else // __APPLE__ +#if defined(CPU_X64) + void* const exception_address = + reinterpret_cast(static_cast(ctx)->uc_mcontext->__es.__faultvaddr); + void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext->__ss.__rip); + const bool is_write = (static_cast(ctx)->uc_mcontext->__es.__err & 2) != 0; +#elif defined(CPU_AARCH64) + void* const exception_address = reinterpret_cast(static_cast(ctx)->uc_mcontext->__es.__far); + void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext->__ss.__pc); + const bool is_write = IsStoreInstruction(exception_pc); +#else + void* const exception_address = reinterpret_cast(info->si_addr); + void* const exception_pc = nullptr; + const bool is_write = false; +#endif +#endif // __APPLE__ + + std::lock_guard guard(m_handler_lock); + for (const RegisteredHandler& rh : m_handlers) + { + if (rh.callback(exception_pc, exception_address, is_write) == HandlerResult::ContinueExecution) + { + s_in_handler = false; + return; + } + } + + // call old signal handler +#if !defined(__APPLE__) && !defined(__aarch64__) + const struct sigaction& sa = s_old_sigsegv_action; +#else + const struct sigaction& sa = (sig == SIGBUS) ? s_old_sigbus_action : s_old_sigsegv_action; +#endif + if (sa.sa_flags & SA_SIGINFO) + sa.sa_sigaction(sig, info, ctx); + else if (sa.sa_handler == SIG_DFL) + signal(sig, SIG_DFL); + else if (sa.sa_handler == SIG_IGN) + return; + else + sa.sa_handler(sig); +} + +#endif + +bool InstallHandler(void* owner, Callback callback) +{ + bool was_empty; + { + std::lock_guard guard(m_handler_lock); + if (std::find_if(m_handlers.begin(), m_handlers.end(), + [owner](const RegisteredHandler& rh) { return rh.owner == owner; }) != m_handlers.end()) + { + return false; + } + + was_empty = m_handlers.empty(); + m_handlers.push_back(RegisteredHandler{owner, std::move(callback)}); + } + + if (was_empty) + { +#if defined(WIN32) && (defined(CPU_X64) || defined(CPU_AARCH64)) + s_veh_handle = AddVectoredExceptionHandler(1, ExceptionHandler); + if (!s_veh_handle) + { + Log_ErrorPrint("Failed to add vectored exception handler"); + return false; + } +#elif defined(USE_SIGSEGV) +#if 0 + // Alternative stack - we'll need this is we ever use the host stack for branches. + stack_t signal_stack = {}; + signal_stack.ss_sp = malloc(SIGSTKSZ); + signal_stack.ss_size = SIGSTKSZ; + if (sigaltstack(&signal_stack, nullptr)) + { + Log_ErrorPrintf("signaltstack() failed: %d", errno); + return false; + } +#endif + + struct sigaction sa = {}; + sa.sa_sigaction = SIGSEGVHandler; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGSEGV, &sa, &s_old_sigsegv_action) < 0) + { + Log_ErrorPrintf("sigaction(SIGSEGV) failed: %d", errno); + return false; + } +#if defined(__APPLE__) || defined(__aarch64__) + if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) < 0) + { + Log_ErrorPrintf("sigaction(SIGBUS) failed: %d", errno); + return false; + } +#endif + +#else + return false; +#endif + } + + return true; +} + +bool RemoveHandler(void* owner) +{ + std::lock_guard guard(m_handler_lock); + auto it = std::find_if(m_handlers.begin(), m_handlers.end(), + [owner](const RegisteredHandler& rh) { return rh.owner == owner; }); + if (it == m_handlers.end()) + return false; + + m_handlers.erase(it); + + if (m_handlers.empty()) + { +#if defined(WIN32) && (defined(CPU_X64) || defined(CPU_AARCH64)) + RemoveVectoredExceptionHandler(s_veh_handle); + s_veh_handle = nullptr; +#elif defined(USE_SIGSEGV) + // restore old signal handler +#if defined(__APPLE__) || defined(__aarch64__) + if (sigaction(SIGBUS, &s_old_sigbus_action, nullptr) < 0) + { + Log_ErrorPrintf("sigaction(SIGBUS) failed: %d", errno); + return false; + } + s_old_sigbus_action = {}; +#endif + + if (sigaction(SIGSEGV, &s_old_sigsegv_action, nullptr) < 0) + { + Log_ErrorPrintf("sigaction(SIGSEGV) failed: %d", errno); + return false; + } + + s_old_sigsegv_action = {}; +#else + return false; +#endif + } + + return true; +} + +} // namespace Common::PageFaultHandler diff --git a/jni/common/page_fault_handler.h b/jni/common/page_fault_handler.h new file mode 100644 index 0000000..67ef38c --- /dev/null +++ b/jni/common/page_fault_handler.h @@ -0,0 +1,17 @@ +#pragma once +#include "types.h" + +namespace Common::PageFaultHandler { +enum class HandlerResult +{ + ContinueExecution, + ExecuteNextHandler, +}; + +using Callback = HandlerResult(*)(void* exception_pc, void* fault_address, bool is_write); +using Handle = void*; + +bool InstallHandler(void* owner, Callback callback); +bool RemoveHandler(void* owner); + +} // namespace Common::PageFaultHandler diff --git a/jni/common/progress_callback.cpp b/jni/common/progress_callback.cpp new file mode 100644 index 0000000..0e8a43d --- /dev/null +++ b/jni/common/progress_callback.cpp @@ -0,0 +1,410 @@ +#include "progress_callback.h" +#include "assert.h" +#include "byte_stream.h" +#include "log.h" +#include +#include +#include +Log_SetChannel(ProgressCallback); + +ProgressCallback::~ProgressCallback() {} + +void ProgressCallback::SetFormattedStatusText(const char* Format, ...) +{ + SmallString str; + va_list ap; + + va_start(ap, Format); + str.FormatVA(Format, ap); + va_end(ap); + + SetStatusText(str); +} + +void ProgressCallback::DisplayFormattedError(const char* format, ...) +{ + SmallString str; + va_list ap; + + va_start(ap, format); + str.FormatVA(format, ap); + va_end(ap); + + DisplayError(str); +} + +void ProgressCallback::DisplayFormattedWarning(const char* format, ...) +{ + SmallString str; + va_list ap; + + va_start(ap, format); + str.FormatVA(format, ap); + va_end(ap); + + DisplayWarning(str); +} + +void ProgressCallback::DisplayFormattedInformation(const char* format, ...) +{ + SmallString str; + va_list ap; + + va_start(ap, format); + str.FormatVA(format, ap); + va_end(ap); + + DisplayInformation(str); +} + +void ProgressCallback::DisplayFormattedDebugMessage(const char* format, ...) +{ + SmallString str; + va_list ap; + + va_start(ap, format); + str.FormatVA(format, ap); + va_end(ap); + + DisplayDebugMessage(str); +} + +void ProgressCallback::DisplayFormattedModalError(const char* format, ...) +{ + SmallString str; + va_list ap; + + va_start(ap, format); + str.FormatVA(format, ap); + va_end(ap); + + ModalError(str); +} + +bool ProgressCallback::DisplayFormattedModalConfirmation(const char* format, ...) +{ + SmallString str; + va_list ap; + + va_start(ap, format); + str.FormatVA(format, ap); + va_end(ap); + + return ModalConfirmation(str); +} + +void ProgressCallback::DisplayFormattedModalInformation(const char* format, ...) +{ + SmallString str; + va_list ap; + + va_start(ap, format); + str.FormatVA(format, ap); + va_end(ap); + + ModalInformation(str); +} + +void ProgressCallback::UpdateProgressFromStream(ByteStream* pStream) +{ + u32 streamSize = (u32)pStream->GetSize(); + u32 streamPosition = (u32)pStream->GetPosition(); + + SetProgressRange(streamSize); + SetProgressValue(streamPosition); +} + +class NullProgressCallbacks final : public ProgressCallback +{ +public: + void PushState() override {} + void PopState() override {} + + bool IsCancelled() const override { return false; } + bool IsCancellable() const override { return false; } + + void SetCancellable(bool cancellable) override {} + void SetTitle(const char* title) override {} + void SetStatusText(const char* statusText) override {} + void SetProgressRange(u32 range) override {} + void SetProgressValue(u32 value) override {} + void IncrementProgressValue() override {} + + void DisplayError(const char* message) override { Log_ErrorPrint(message); } + void DisplayWarning(const char* message) override { Log_WarningPrint(message); } + void DisplayInformation(const char* message) override { Log_InfoPrint(message); } + void DisplayDebugMessage(const char* message) override { Log_DevPrint(message); } + + void ModalError(const char* message) override { Log_ErrorPrint(message); } + bool ModalConfirmation(const char* message) override + { + Log_InfoPrint(message); + return false; + } + void ModalInformation(const char* message) override { Log_InfoPrint(message); } +}; + +static NullProgressCallbacks s_nullProgressCallbacks; +ProgressCallback* ProgressCallback::NullProgressCallback = &s_nullProgressCallbacks; + +BaseProgressCallback::BaseProgressCallback() + : m_cancellable(false), m_cancelled(false), m_progress_range(1), m_progress_value(0), m_base_progress_value(0), + m_saved_state(NULL) +{ +} + +BaseProgressCallback::~BaseProgressCallback() +{ + State* pNextState = m_saved_state; + while (pNextState != NULL) + { + State* pCurrentState = pNextState; + pNextState = pCurrentState->next_saved_state; + delete pCurrentState; + } +} + +void BaseProgressCallback::PushState() +{ + State* pNewState = new State; + pNewState->cancellable = m_cancellable; + pNewState->status_text = m_status_text; + pNewState->progress_range = m_progress_range; + pNewState->progress_value = m_progress_value; + pNewState->base_progress_value = m_base_progress_value; + pNewState->next_saved_state = m_saved_state; + m_saved_state = pNewState; +} + +void BaseProgressCallback::PopState() +{ + DebugAssert(m_saved_state); + State* state = m_saved_state; + m_saved_state = nullptr; + + // impose the current position into the previous range + const u32 new_progress_value = + (m_progress_range != 0) ? + static_cast(((float)m_progress_value / (float)m_progress_range) * (float)state->progress_range) : + state->progress_value; + + SetCancellable(state->cancellable); + SetStatusText(state->status_text); + SetProgressRange(state->progress_range); + SetProgressValue(new_progress_value); + + m_base_progress_value = state->base_progress_value; + m_saved_state = state->next_saved_state; + delete state; +} + +bool BaseProgressCallback::IsCancelled() const +{ + return m_cancelled; +} + +bool BaseProgressCallback::IsCancellable() const +{ + return m_cancellable; +} + +void BaseProgressCallback::SetCancellable(bool cancellable) +{ + m_cancellable = cancellable; +} + +void BaseProgressCallback::SetStatusText(const char* text) +{ + m_status_text = text; +} + +void BaseProgressCallback::SetProgressRange(u32 range) +{ + if (m_saved_state) + { + // impose the previous range on this range + m_progress_range = m_saved_state->progress_range * range; + m_base_progress_value = m_progress_value = m_saved_state->progress_value * range; + } + else + { + m_progress_range = range; + m_progress_value = 0; + m_base_progress_value = 0; + } +} + +void BaseProgressCallback::SetProgressValue(u32 value) +{ + m_progress_value = m_base_progress_value + value; +} + +void BaseProgressCallback::IncrementProgressValue() +{ + SetProgressValue((m_progress_value - m_base_progress_value) + 1); +} + +ConsoleProgressCallback::ConsoleProgressCallback() + : BaseProgressCallback(), m_last_percent_complete(std::numeric_limits::infinity()), + m_last_bar_length(0xFFFFFFFF) +{ +} + +ConsoleProgressCallback::~ConsoleProgressCallback() +{ + Clear(); +} + +void ConsoleProgressCallback::PushState() +{ + BaseProgressCallback::PushState(); +} + +void ConsoleProgressCallback::PopState() +{ + BaseProgressCallback::PopState(); + Redraw(false); +} + +void ConsoleProgressCallback::SetCancellable(bool cancellable) +{ + BaseProgressCallback::SetCancellable(cancellable); + Redraw(false); +} + +void ConsoleProgressCallback::SetTitle(const char* title) +{ + Clear(); + std::fprintf(stdout, "== %s ==\n", title); + Redraw(false); +} + +void ConsoleProgressCallback::SetStatusText(const char* text) +{ + BaseProgressCallback::SetStatusText(text); + Redraw(false); +} + +void ConsoleProgressCallback::SetProgressRange(u32 range) +{ + u32 last_range = m_progress_range; + + BaseProgressCallback::SetProgressRange(range); + + if (m_progress_range != last_range) + Redraw(false); +} + +void ConsoleProgressCallback::SetProgressValue(u32 value) +{ + u32 lastValue = m_progress_value; + + BaseProgressCallback::SetProgressValue(value); + + if (m_progress_value != lastValue) + Redraw(true); +} + +void ConsoleProgressCallback::Clear() +{ + SmallString message; + for (u32 i = 0; i < COLUMNS; i++) + message.AppendCharacter(' '); + message.AppendCharacter('\r'); + + std::fwrite(message.GetCharArray(), message.GetLength(), 1, stderr); + std::fflush(stderr); +} + +void ConsoleProgressCallback::Redraw(bool update_value_only) +{ + float percent_complete = (m_progress_range > 0) ? ((float)m_progress_value / (float)m_progress_range) * 100.0f : 0.0f; + if (percent_complete > 100.0f) + percent_complete = 100.0f; + + const u32 current_length = m_status_text.GetLength() + 14; + const u32 max_bar_length = (current_length < COLUMNS) ? COLUMNS - current_length : 0; + const u32 current_bar_length = + (max_bar_length > 0) ? (static_cast(percent_complete / 100.0f * (float)max_bar_length)) : 0; + + if (update_value_only && (current_bar_length == m_last_bar_length) && + std::abs(percent_complete - m_last_percent_complete) < 0.01f) + { + return; + } + + m_last_bar_length = current_bar_length; + m_last_percent_complete = percent_complete; + + SmallString message; + message.AppendString(m_status_text); + message.AppendFormattedString(" [%.2f%%]", percent_complete); + + if (max_bar_length > 0) + { + message.AppendString(" |"); + + u32 i; + for (i = 0; i < current_bar_length; i++) + message.AppendCharacter('='); + for (; i < max_bar_length; i++) + message.AppendCharacter(' '); + + message.AppendString("|"); + } + + message.AppendCharacter('\r'); + + std::fwrite(message.GetCharArray(), message.GetLength(), 1, stderr); + std::fflush(stderr); +} + +void ConsoleProgressCallback::DisplayError(const char* message) +{ + Clear(); + Log_ErrorPrint(message); + Redraw(false); +} + +void ConsoleProgressCallback::DisplayWarning(const char* message) +{ + Clear(); + Log_WarningPrint(message); + Redraw(false); +} + +void ConsoleProgressCallback::DisplayInformation(const char* message) +{ + Clear(); + Log_InfoPrint(message); + Redraw(false); +} + +void ConsoleProgressCallback::DisplayDebugMessage(const char* message) +{ + Clear(); + Log_DevPrint(message); + Redraw(false); +} + +void ConsoleProgressCallback::ModalError(const char* message) +{ + Clear(); + Log_ErrorPrint(message); + Redraw(false); +} + +bool ConsoleProgressCallback::ModalConfirmation(const char* message) +{ + Clear(); + Log_InfoPrint(message); + Redraw(false); + return false; +} + +void ConsoleProgressCallback::ModalInformation(const char* message) +{ + Clear(); + Log_InfoPrint(message); + Redraw(false); +} diff --git a/jni/common/progress_callback.h b/jni/common/progress_callback.h new file mode 100644 index 0000000..9ea431c --- /dev/null +++ b/jni/common/progress_callback.h @@ -0,0 +1,124 @@ +#pragma once +#include "string.h" +#include "types.h" + +class ByteStream; + +class ProgressCallback +{ +public: + virtual ~ProgressCallback(); + + virtual void PushState() = 0; + virtual void PopState() = 0; + + virtual bool IsCancelled() const = 0; + virtual bool IsCancellable() const = 0; + + virtual void SetCancellable(bool cancellable) = 0; + + virtual void SetTitle(const char* title) = 0; + virtual void SetStatusText(const char* text) = 0; + virtual void SetProgressRange(u32 range) = 0; + virtual void SetProgressValue(u32 value) = 0; + virtual void IncrementProgressValue() = 0; + + void SetFormattedStatusText(const char* Format, ...); + + virtual void DisplayError(const char* message) = 0; + virtual void DisplayWarning(const char* message) = 0; + virtual void DisplayInformation(const char* message) = 0; + virtual void DisplayDebugMessage(const char* message) = 0; + + virtual void ModalError(const char* message) = 0; + virtual bool ModalConfirmation(const char* message) = 0; + virtual void ModalInformation(const char* message) = 0; + + void DisplayFormattedError(const char* format, ...); + void DisplayFormattedWarning(const char* format, ...); + void DisplayFormattedInformation(const char* format, ...); + void DisplayFormattedDebugMessage(const char* format, ...); + void DisplayFormattedModalError(const char* format, ...); + bool DisplayFormattedModalConfirmation(const char* format, ...); + void DisplayFormattedModalInformation(const char* format, ...); + + void UpdateProgressFromStream(ByteStream* stream); + +public: + static ProgressCallback* NullProgressCallback; +}; + +class BaseProgressCallback : public ProgressCallback +{ +public: + BaseProgressCallback(); + virtual ~BaseProgressCallback(); + + virtual void PushState() override; + virtual void PopState() override; + + virtual bool IsCancelled() const override; + virtual bool IsCancellable() const override; + + virtual void SetCancellable(bool cancellable) override; + virtual void SetStatusText(const char* text) override; + virtual void SetProgressRange(u32 range) override; + virtual void SetProgressValue(u32 value) override; + virtual void IncrementProgressValue() override; + +protected: + struct State + { + State* next_saved_state; + String status_text; + u32 progress_range; + u32 progress_value; + u32 base_progress_value; + bool cancellable; + }; + + bool m_cancellable; + bool m_cancelled; + String m_status_text; + u32 m_progress_range; + u32 m_progress_value; + + u32 m_base_progress_value; + + State* m_saved_state; +}; + +class ConsoleProgressCallback final : public BaseProgressCallback +{ +public: + static const u32 COLUMNS = 78; + +public: + ConsoleProgressCallback(); + ~ConsoleProgressCallback(); + + void PushState() override; + void PopState() override; + + void SetCancellable(bool cancellable) override; + void SetTitle(const char* title) override; + void SetStatusText(const char* text) override; + void SetProgressRange(u32 range) override; + void SetProgressValue(u32 value) override; + + void DisplayError(const char* message) override; + void DisplayWarning(const char* message) override; + void DisplayInformation(const char* message) override; + void DisplayDebugMessage(const char* message) override; + + void ModalError(const char* message) override; + bool ModalConfirmation(const char* message) override; + void ModalInformation(const char* message) override; + +private: + void Clear(); + void Redraw(bool update_value_only); + + float m_last_percent_complete; + u32 m_last_bar_length; +}; diff --git a/jni/common/rectangle.h b/jni/common/rectangle.h new file mode 100644 index 0000000..bd26895 --- /dev/null +++ b/jni/common/rectangle.h @@ -0,0 +1,194 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace Common { + +/// Templated rectangle class. Assumes an upper-left origin, that is (0,0) is the top-left corner. +template +struct Rectangle +{ + static constexpr T InvalidMinCoord = std::numeric_limits::max(); + static constexpr T InvalidMaxCoord = std::numeric_limits::min(); + + /// Default constructor - initializes to an invalid coordinate range suitable for including points. + constexpr Rectangle() : left(InvalidMinCoord), top(InvalidMinCoord), right(InvalidMaxCoord), bottom(InvalidMaxCoord) + { + } + + /// Construct with values. + constexpr Rectangle(T left_, T top_, T right_, T bottom_) : left(left_), top(top_), right(right_), bottom(bottom_) {} + + /// Copy constructor. + constexpr Rectangle(const Rectangle& copy) : left(copy.left), top(copy.top), right(copy.right), bottom(copy.bottom) {} + + /// Sets the rectangle using the specified values. + constexpr void Set(T left_, T top_, T right_, T bottom_) + { + left = left_; + top = top_; + right = right_; + bottom = bottom_; + } + + /// Sets the rectangle using the specified top-left position and extents. + constexpr void SetExtents(T x, T y, T width, T height) + { + left = x; + top = y; + right = x + width; + bottom = y + height; + } + + /// Returns a new rectangle from the specified position and size. + static Rectangle FromExtents(T x, T y, T width, T height) { return Rectangle(x, y, x + width, y + height); } + + /// Sets the rectangle to invalid coordinates (right < left, top < bottom). + constexpr void SetInvalid() { Set(InvalidMinCoord, InvalidMinCoord, InvalidMaxCoord, InvalidMaxCoord); } + + /// Returns the width of the rectangle. + constexpr T GetWidth() const { return right - left; } + + /// Returns the height of the rectangle. + constexpr T GetHeight() const { return bottom - top; } + + /// Returns true if the rectangles's width/height can be considered valid. + constexpr bool Valid() const { return left <= right && top <= bottom; } + + /// Returns false if the rectangle does not have any extents (zero size). + constexpr bool HasExtents() const { return left < right && top < bottom; } + + /// Assignment operator. + constexpr Rectangle& operator=(const Rectangle& rhs) + { + std::memcpy(this, &rhs, sizeof(Rectangle)); + return *this; + } + + // Relational operators. +#define RELATIONAL_OPERATOR(op) \ + constexpr bool operator op(const Rectangle& rhs) const \ + { \ + return std::tie(left, top, right, bottom) op std::tie(rhs.left, rhs.top, rhs.right, rhs.bottom); \ + } + + RELATIONAL_OPERATOR(==); + RELATIONAL_OPERATOR(!=); + RELATIONAL_OPERATOR(<); + RELATIONAL_OPERATOR(<=); + RELATIONAL_OPERATOR(>); + RELATIONAL_OPERATOR(>=); + +#undef RELATIONAL_OPERATOR + + // Arithmetic operators. +#define ARITHMETIC_OPERATOR(op) \ + constexpr Rectangle& operator op##=(const T amount) \ + { \ + left op## = amount; \ + top op## = amount; \ + right op## = amount; \ + bottom op## = amount; \ + } \ + constexpr Rectangle operator op(const T amount) const \ + { \ + return Rectangle(left op amount, top op amount, right op amount, bottom op amount); \ + } + + ARITHMETIC_OPERATOR(+); + ARITHMETIC_OPERATOR(-); + ARITHMETIC_OPERATOR(*); + ARITHMETIC_OPERATOR(/); + ARITHMETIC_OPERATOR(%); + ARITHMETIC_OPERATOR(>>); + ARITHMETIC_OPERATOR(<<); + ARITHMETIC_OPERATOR(|); + ARITHMETIC_OPERATOR(&); + ARITHMETIC_OPERATOR(^); + +#undef ARITHMETIC_OPERATOR + +#ifdef _WINDEF_ + /// Casts this rectangle to a Win32 RECT structure if compatible. + template && _>> + const RECT* AsRECT() const + { + return reinterpret_cast(this); + } +#endif + + /// Tests for intersection between two rectangles. + constexpr bool Intersects(const Rectangle& rhs) const + { + return !(left >= rhs.right || rhs.left >= right || top >= rhs.bottom || rhs.top >= bottom); + } + + /// Tests whether the specified point is contained in the rectangle. + constexpr bool Contains(T x, T y) const { return (x >= left && x < right && y >= top && y < bottom); } + + /// Expands the bounds of the rectangle to contain the specified point. + constexpr void Include(T x, T y) + { + left = std::min(left, x); + right = std::max(right, x + static_cast(1)); + top = std::min(top, y); + bottom = std::max(bottom, y + static_cast(1)); + } + + /// Expands the bounds of the rectangle to contain another rectangle. + constexpr void Include(const Rectangle& rhs) + { + left = std::min(left, rhs.left); + right = std::max(right, rhs.right); + top = std::min(top, rhs.top); + bottom = std::max(bottom, rhs.bottom); + } + + /// Expands the bounds of the rectangle to contain another rectangle. + constexpr void Include(T other_left, T other_right, T other_top, T other_bottom) + { + left = std::min(left, other_left); + right = std::max(right, other_right); + top = std::min(top, other_top); + bottom = std::max(bottom, other_bottom); + } + + /// Clamps the rectangle to the specified coordinates. + constexpr void Clamp(T x1, T y1, T x2, T y2) + { + left = std::clamp(left, x1, x2); + right = std::clamp(right, x1, x2); + top = std::clamp(top, y1, y2); + bottom = std::clamp(bottom, y1, y2); + } + + /// Clamps the rectangle to the specified size. + constexpr void ClampSize(T width, T height) + { + right = std::min(right, left + width); + bottom = std::min(bottom, top + height); + } + + /// Returns a new rectangle with clamped coordinates. + constexpr Rectangle Clamped(T x1, T y1, T x2, T y2) const + { + return Rectangle(std::clamp(left, x1, x2), std::clamp(top, y1, y2), std::clamp(right, x1, x2), + std::clamp(bottom, y1, y2)); + } + + /// Returns a new rectangle with clamped size. + constexpr Rectangle ClampedSize(T width, T height) const + { + return Rectangle(left, top, std::min(right, left + width), std::min(bottom, top + height)); + } + + T left; + T top; + T right; + T bottom; +}; + +} // namespace Common diff --git a/jni/common/scope_guard.h b/jni/common/scope_guard.h new file mode 100644 index 0000000..c1cad06 --- /dev/null +++ b/jni/common/scope_guard.h @@ -0,0 +1,41 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +namespace Common +{ +template +class ScopeGuard final +{ +public: + ScopeGuard(Callable&& finalizer) : m_finalizer(std::forward(finalizer)) {} + + ScopeGuard(ScopeGuard&& other) : m_finalizer(std::move(other.m_finalizer)) + { + other.m_finalizer = nullptr; + } + + ~ScopeGuard() { Exit(); } + void Dismiss() { m_finalizer.reset(); } + void Exit() + { + if (m_finalizer) + { + (*m_finalizer)(); // must not throw + m_finalizer.reset(); + } + } + + ScopeGuard(const ScopeGuard&) = delete; + + void operator=(const ScopeGuard&) = delete; + +private: + std::optional m_finalizer; +}; + +} // Namespace Common diff --git a/jni/common/shiftjis.cpp b/jni/common/shiftjis.cpp new file mode 100644 index 0000000..6dead78 --- /dev/null +++ b/jni/common/shiftjis.cpp @@ -0,0 +1,1596 @@ +#include "shiftjis.h" +#include +#include +#include + +// https://github.com/bucanero/apollo-ps3/commit/b8e52b021239d40f2ba6945d7352345f4457b7b7 +extern const unsigned char shiftJIS_convTable[25088]; + +void sjis2ascii(char* bData) +{ + std::uint16_t ch; + int i, j = 0; + int len = static_cast(std::strlen(bData)); + + for (i = 0; i < len; i += 2) + { + ch = (bData[i] << 8) | bData[i + 1]; + + // 'A' .. 'Z' + // '0' .. '9' + if ((ch >= 0x8260 && ch <= 0x8279) || (ch >= 0x824F && ch <= 0x8258)) + { + bData[j++] = (ch & 0xFF) - 0x1F; + continue; + } + + // 'a' .. 'z' + if (ch >= 0x8281 && ch <= 0x829A) + { + bData[j++] = (ch & 0xFF) - 0x20; + continue; + } + + switch (ch) + { + case 0x0000: // End of the string + bData[j] = 0; + return; + + case 0x8140: + bData[j++] = ' '; + break; + + case 0x8143: + bData[j++] = ','; + break; + + case 0x8144: + bData[j++] = '.'; + break; + + case 0x8145: + bData[j++] = '\xFA'; + break; + + case 0x8146: + bData[j++] = ':'; + break; + + case 0x8147: + bData[j++] = ';'; + break; + + case 0x8148: + bData[j++] = '?'; + break; + + case 0x8149: + bData[j++] = '!'; + break; + + case 0x814F: + bData[j++] = '^'; + break; + + case 0x8151: + bData[j++] = '_'; + break; + + case 0x815B: + case 0x815C: + case 0x815D: + bData[j++] = '-'; + break; + + case 0x815E: + bData[j++] = '/'; + break; + + case 0x815F: + bData[j++] = '\\'; + break; + + case 0x8160: + bData[j++] = '~'; + break; + + case 0x8161: + bData[j++] = '|'; + break; + + case 0x8168: + bData[j++] = '"'; + break; + + case 0x8169: + bData[j++] = '('; + break; + + case 0x816A: + bData[j++] = ')'; + break; + + case 0x816D: + bData[j++] = '['; + break; + + case 0x816E: + bData[j++] = ']'; + break; + + case 0x816F: + bData[j++] = '{'; + break; + + case 0x8170: + bData[j++] = '}'; + break; + + case 0x817B: + bData[j++] = '+'; + break; + + case 0x817C: + bData[j++] = '-'; + break; + + case 0x817D: + bData[j++] = '\xF1'; + break; + + case 0x817E: + bData[j++] = '*'; + break; + + case 0x8180: + bData[j++] = '\xF6'; + break; + + case 0x8181: + bData[j++] = '='; + break; + + case 0x8183: + bData[j++] = '<'; + break; + + case 0x8184: + bData[j++] = '>'; + break; + + case 0x818A: + bData[j++] = '\xF8'; + break; + + case 0x818B: + bData[j++] = '\''; + break; + + case 0x818C: + bData[j++] = '"'; + break; + + case 0x8190: + bData[j++] = '$'; + break; + + case 0x8193: + bData[j++] = '%'; + break; + + case 0x8194: + bData[j++] = '#'; + break; + + case 0x8195: + bData[j++] = '&'; + break; + + case 0x8196: + bData[j++] = '*'; + break; + + case 0x8197: + bData[j++] = '@'; + break; + + // Character not found + default: + bData[j++] = bData[i]; + bData[j++] = bData[i + 1]; + break; + } + } + + bData[j] = 0; + return; +} + +char* sjis2utf8(char* input) +{ + // Simplify the input and decode standard ASCII characters + sjis2ascii(input); + + int len = static_cast(std::strlen(input)); + char* output = reinterpret_cast( + std::malloc(3 * len)); // ShiftJis won't give 4byte UTF8, so max. 3 byte per input char are needed + size_t indexInput = 0, indexOutput = 0; + + while (indexInput < len) + { + char arraySection = ((uint8_t)input[indexInput]) >> 4; + + size_t arrayOffset; + if (arraySection == 0x8) + arrayOffset = 0x100; // these are two-byte shiftjis + else if (arraySection == 0x9) + arrayOffset = 0x1100; + else if (arraySection == 0xE) + arrayOffset = 0x2100; + else + arrayOffset = 0; // this is one byte shiftjis + + // determining real array offset + if (arrayOffset) + { + arrayOffset += (((uint8_t)input[indexInput]) & 0xf) << 8; + indexInput++; + if (indexInput >= len) + break; + } + arrayOffset += (uint8_t)input[indexInput++]; + arrayOffset <<= 1; + + // unicode number is... + uint16_t unicodeValue = (shiftJIS_convTable[arrayOffset] << 8) | shiftJIS_convTable[arrayOffset + 1]; + + // converting to UTF8 + if (unicodeValue < 0x80) + { + output[indexOutput++] = static_cast(unicodeValue); + } + else if (unicodeValue < 0x800) + { + output[indexOutput++] = 0xC0 | static_cast((unicodeValue >> 6)); + output[indexOutput++] = 0x80 | static_cast((unicodeValue & 0x3f)); + } + else + { + output[indexOutput++] = 0xE0 | static_cast((unicodeValue >> 12)); + output[indexOutput++] = 0x80 | static_cast(((unicodeValue & 0xfff) >> 6)); + output[indexOutput++] = 0x80 | static_cast((unicodeValue & 0x3f)); + } + } + + // remove the unnecessary bytes + output[indexOutput] = 0; + return output; +} + +// https://stackoverflow.com/questions/33165171/c-shiftjis-to-utf8-conversion + +const unsigned char shiftJIS_convTable[25088] = { + 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x0f, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, + 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x1a, 0x00, 0x1b, 0x00, + 0x1c, 0x00, 0x1d, 0x00, 0x1e, 0x00, 0x1f, 0x00, 0x20, 0x00, 0x21, 0x00, 0x22, 0x00, 0x23, 0x00, 0x24, 0x00, 0x25, + 0x00, 0x26, 0x00, 0x27, 0x00, 0x28, 0x00, 0x29, 0x00, 0x2a, 0x00, 0x2b, 0x00, 0x2c, 0x00, 0x2d, 0x00, 0x2e, 0x00, + 0x2f, 0x00, 0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34, 0x00, 0x35, 0x00, 0x36, 0x00, 0x37, 0x00, 0x38, + 0x00, 0x39, 0x00, 0x3a, 0x00, 0x3b, 0x00, 0x3c, 0x00, 0x3d, 0x00, 0x3e, 0x00, 0x3f, 0x00, 0x40, 0x00, 0x41, 0x00, + 0x42, 0x00, 0x43, 0x00, 0x44, 0x00, 0x45, 0x00, 0x46, 0x00, 0x47, 0x00, 0x48, 0x00, 0x49, 0x00, 0x4a, 0x00, 0x4b, + 0x00, 0x4c, 0x00, 0x4d, 0x00, 0x4e, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x51, 0x00, 0x52, 0x00, 0x53, 0x00, 0x54, 0x00, + 0x55, 0x00, 0x56, 0x00, 0x57, 0x00, 0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, 0x5b, 0x00, 0xa5, 0x00, 0x5d, 0x00, 0x5e, + 0x00, 0x5f, 0x00, 0x60, 0x00, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00, 0x64, 0x00, 0x65, 0x00, 0x66, 0x00, 0x67, 0x00, + 0x68, 0x00, 0x69, 0x00, 0x6a, 0x00, 0x6b, 0x00, 0x6c, 0x00, 0x6d, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x71, + 0x00, 0x72, 0x00, 0x73, 0x00, 0x74, 0x00, 0x75, 0x00, 0x76, 0x00, 0x77, 0x00, 0x78, 0x00, 0x79, 0x00, 0x7a, 0x00, + 0x7b, 0x00, 0x7c, 0x00, 0x7d, 0x20, 0x3e, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0xff, + 0x61, 0xff, 0x62, 0xff, 0x63, 0xff, 0x64, 0xff, 0x65, 0xff, 0x66, 0xff, 0x67, 0xff, 0x68, 0xff, 0x69, 0xff, 0x6a, + 0xff, 0x6b, 0xff, 0x6c, 0xff, 0x6d, 0xff, 0x6e, 0xff, 0x6f, 0xff, 0x70, 0xff, 0x71, 0xff, 0x72, 0xff, 0x73, 0xff, + 0x74, 0xff, 0x75, 0xff, 0x76, 0xff, 0x77, 0xff, 0x78, 0xff, 0x79, 0xff, 0x7a, 0xff, 0x7b, 0xff, 0x7c, 0xff, 0x7d, + 0xff, 0x7e, 0xff, 0x7f, 0xff, 0x80, 0xff, 0x81, 0xff, 0x82, 0xff, 0x83, 0xff, 0x84, 0xff, 0x85, 0xff, 0x86, 0xff, + 0x87, 0xff, 0x88, 0xff, 0x89, 0xff, 0x8a, 0xff, 0x8b, 0xff, 0x8c, 0xff, 0x8d, 0xff, 0x8e, 0xff, 0x8f, 0xff, 0x90, + 0xff, 0x91, 0xff, 0x92, 0xff, 0x93, 0xff, 0x94, 0xff, 0x95, 0xff, 0x96, 0xff, 0x97, 0xff, 0x98, 0xff, 0x99, 0xff, + 0x9a, 0xff, 0x9b, 0xff, 0x9c, 0xff, 0x9d, 0xff, 0x9e, 0xff, 0x9f, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x30, 0x00, 0x30, 0x01, 0x30, 0x02, 0xff, + 0x0c, 0xff, 0x0e, 0x30, 0xfb, 0xff, 0x1a, 0xff, 0x1b, 0xff, 0x1f, 0xff, 0x01, 0x30, 0x9b, 0x30, 0x9c, 0x00, 0xb4, + 0xff, 0x40, 0x00, 0xa8, 0xff, 0x3e, 0xff, 0xe3, 0xff, 0x3f, 0x30, 0xfd, 0x30, 0xfe, 0x30, 0x9d, 0x30, 0x9e, 0x30, + 0x03, 0x4e, 0xdd, 0x30, 0x05, 0x30, 0x06, 0x30, 0x07, 0x30, 0xfc, 0x20, 0x15, 0x20, 0x10, 0xff, 0x0f, 0x00, 0x5c, + 0x30, 0x1c, 0x20, 0x16, 0xff, 0x5c, 0x20, 0x26, 0x20, 0x25, 0x20, 0x18, 0x20, 0x19, 0x20, 0x1c, 0x20, 0x1d, 0xff, + 0x08, 0xff, 0x09, 0x30, 0x14, 0x30, 0x15, 0xff, 0x3b, 0xff, 0x3d, 0xff, 0x5b, 0xff, 0x5d, 0x30, 0x08, 0x30, 0x09, + 0x30, 0x0a, 0x30, 0x0b, 0x30, 0x0c, 0x30, 0x0d, 0x30, 0x0e, 0x30, 0x0f, 0x30, 0x10, 0x30, 0x11, 0xff, 0x0b, 0x22, + 0x12, 0x00, 0xb1, 0x00, 0xd7, 0x00, 0x20, 0x00, 0xf7, 0xff, 0x1d, 0x22, 0x60, 0xff, 0x1c, 0xff, 0x1e, 0x22, 0x66, + 0x22, 0x67, 0x22, 0x1e, 0x22, 0x34, 0x26, 0x42, 0x26, 0x40, 0x00, 0xb0, 0x20, 0x32, 0x20, 0x33, 0x21, 0x03, 0xff, + 0xe5, 0xff, 0x04, 0x00, 0xa2, 0x00, 0xa3, 0xff, 0x05, 0xff, 0x03, 0xff, 0x06, 0xff, 0x0a, 0xff, 0x20, 0x00, 0xa7, + 0x26, 0x06, 0x26, 0x05, 0x25, 0xcb, 0x25, 0xcf, 0x25, 0xce, 0x25, 0xc7, 0x25, 0xc6, 0x25, 0xa1, 0x25, 0xa0, 0x25, + 0xb3, 0x25, 0xb2, 0x25, 0xbd, 0x25, 0xbc, 0x20, 0x3b, 0x30, 0x12, 0x21, 0x92, 0x21, 0x90, 0x21, 0x91, 0x21, 0x93, + 0x30, 0x13, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x22, 0x08, 0x22, 0x0b, 0x22, 0x86, 0x22, 0x87, 0x22, 0x82, 0x22, 0x83, 0x22, 0x2a, + 0x22, 0x29, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x22, + 0x27, 0x22, 0x28, 0x00, 0xac, 0x21, 0xd2, 0x21, 0xd4, 0x22, 0x00, 0x22, 0x03, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x22, 0x20, 0x22, + 0xa5, 0x23, 0x12, 0x22, 0x02, 0x22, 0x07, 0x22, 0x61, 0x22, 0x52, 0x22, 0x6a, 0x22, 0x6b, 0x22, 0x1a, 0x22, 0x3d, + 0x22, 0x1d, 0x22, 0x35, 0x22, 0x2b, 0x22, 0x2c, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x21, 0x2b, 0x20, 0x30, 0x26, 0x6f, 0x26, 0x6d, 0x26, 0x6a, 0x20, 0x20, 0x20, 0x21, 0x00, 0xb6, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x25, 0xef, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0xff, 0x10, 0xff, 0x11, 0xff, 0x12, 0xff, 0x13, 0xff, 0x14, 0xff, 0x15, 0xff, 0x16, 0xff, 0x17, + 0xff, 0x18, 0xff, 0x19, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0xff, + 0x21, 0xff, 0x22, 0xff, 0x23, 0xff, 0x24, 0xff, 0x25, 0xff, 0x26, 0xff, 0x27, 0xff, 0x28, 0xff, 0x29, 0xff, 0x2a, + 0xff, 0x2b, 0xff, 0x2c, 0xff, 0x2d, 0xff, 0x2e, 0xff, 0x2f, 0xff, 0x30, 0xff, 0x31, 0xff, 0x32, 0xff, 0x33, 0xff, + 0x34, 0xff, 0x35, 0xff, 0x36, 0xff, 0x37, 0xff, 0x38, 0xff, 0x39, 0xff, 0x3a, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0xff, 0x41, 0xff, 0x42, 0xff, 0x43, 0xff, 0x44, 0xff, 0x45, 0xff, + 0x46, 0xff, 0x47, 0xff, 0x48, 0xff, 0x49, 0xff, 0x4a, 0xff, 0x4b, 0xff, 0x4c, 0xff, 0x4d, 0xff, 0x4e, 0xff, 0x4f, + 0xff, 0x50, 0xff, 0x51, 0xff, 0x52, 0xff, 0x53, 0xff, 0x54, 0xff, 0x55, 0xff, 0x56, 0xff, 0x57, 0xff, 0x58, 0xff, + 0x59, 0xff, 0x5a, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x30, 0x41, 0x30, 0x42, 0x30, 0x43, 0x30, 0x44, + 0x30, 0x45, 0x30, 0x46, 0x30, 0x47, 0x30, 0x48, 0x30, 0x49, 0x30, 0x4a, 0x30, 0x4b, 0x30, 0x4c, 0x30, 0x4d, 0x30, + 0x4e, 0x30, 0x4f, 0x30, 0x50, 0x30, 0x51, 0x30, 0x52, 0x30, 0x53, 0x30, 0x54, 0x30, 0x55, 0x30, 0x56, 0x30, 0x57, + 0x30, 0x58, 0x30, 0x59, 0x30, 0x5a, 0x30, 0x5b, 0x30, 0x5c, 0x30, 0x5d, 0x30, 0x5e, 0x30, 0x5f, 0x30, 0x60, 0x30, + 0x61, 0x30, 0x62, 0x30, 0x63, 0x30, 0x64, 0x30, 0x65, 0x30, 0x66, 0x30, 0x67, 0x30, 0x68, 0x30, 0x69, 0x30, 0x6a, + 0x30, 0x6b, 0x30, 0x6c, 0x30, 0x6d, 0x30, 0x6e, 0x30, 0x6f, 0x30, 0x70, 0x30, 0x71, 0x30, 0x72, 0x30, 0x73, 0x30, + 0x74, 0x30, 0x75, 0x30, 0x76, 0x30, 0x77, 0x30, 0x78, 0x30, 0x79, 0x30, 0x7a, 0x30, 0x7b, 0x30, 0x7c, 0x30, 0x7d, + 0x30, 0x7e, 0x30, 0x7f, 0x30, 0x80, 0x30, 0x81, 0x30, 0x82, 0x30, 0x83, 0x30, 0x84, 0x30, 0x85, 0x30, 0x86, 0x30, + 0x87, 0x30, 0x88, 0x30, 0x89, 0x30, 0x8a, 0x30, 0x8b, 0x30, 0x8c, 0x30, 0x8d, 0x30, 0x8e, 0x30, 0x8f, 0x30, 0x90, + 0x30, 0x91, 0x30, 0x92, 0x30, 0x93, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x30, 0xa1, 0x30, 0xa2, 0x30, 0xa3, 0x30, 0xa4, 0x30, + 0xa5, 0x30, 0xa6, 0x30, 0xa7, 0x30, 0xa8, 0x30, 0xa9, 0x30, 0xaa, 0x30, 0xab, 0x30, 0xac, 0x30, 0xad, 0x30, 0xae, + 0x30, 0xaf, 0x30, 0xb0, 0x30, 0xb1, 0x30, 0xb2, 0x30, 0xb3, 0x30, 0xb4, 0x30, 0xb5, 0x30, 0xb6, 0x30, 0xb7, 0x30, + 0xb8, 0x30, 0xb9, 0x30, 0xba, 0x30, 0xbb, 0x30, 0xbc, 0x30, 0xbd, 0x30, 0xbe, 0x30, 0xbf, 0x30, 0xc0, 0x30, 0xc1, + 0x30, 0xc2, 0x30, 0xc3, 0x30, 0xc4, 0x30, 0xc5, 0x30, 0xc6, 0x30, 0xc7, 0x30, 0xc8, 0x30, 0xc9, 0x30, 0xca, 0x30, + 0xcb, 0x30, 0xcc, 0x30, 0xcd, 0x30, 0xce, 0x30, 0xcf, 0x30, 0xd0, 0x30, 0xd1, 0x30, 0xd2, 0x30, 0xd3, 0x30, 0xd4, + 0x30, 0xd5, 0x30, 0xd6, 0x30, 0xd7, 0x30, 0xd8, 0x30, 0xd9, 0x30, 0xda, 0x30, 0xdb, 0x30, 0xdc, 0x30, 0xdd, 0x30, + 0xde, 0x30, 0xdf, 0x00, 0x20, 0x30, 0xe0, 0x30, 0xe1, 0x30, 0xe2, 0x30, 0xe3, 0x30, 0xe4, 0x30, 0xe5, 0x30, 0xe6, + 0x30, 0xe7, 0x30, 0xe8, 0x30, 0xe9, 0x30, 0xea, 0x30, 0xeb, 0x30, 0xec, 0x30, 0xed, 0x30, 0xee, 0x30, 0xef, 0x30, + 0xf0, 0x30, 0xf1, 0x30, 0xf2, 0x30, 0xf3, 0x30, 0xf4, 0x30, 0xf5, 0x30, 0xf6, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x03, 0x91, 0x03, 0x92, 0x03, 0x93, 0x03, 0x94, 0x03, + 0x95, 0x03, 0x96, 0x03, 0x97, 0x03, 0x98, 0x03, 0x99, 0x03, 0x9a, 0x03, 0x9b, 0x03, 0x9c, 0x03, 0x9d, 0x03, 0x9e, + 0x03, 0x9f, 0x03, 0xa0, 0x03, 0xa1, 0x03, 0xa3, 0x03, 0xa4, 0x03, 0xa5, 0x03, 0xa6, 0x03, 0xa7, 0x03, 0xa8, 0x03, + 0xa9, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x03, 0xb1, + 0x03, 0xb2, 0x03, 0xb3, 0x03, 0xb4, 0x03, 0xb5, 0x03, 0xb6, 0x03, 0xb7, 0x03, 0xb8, 0x03, 0xb9, 0x03, 0xba, 0x03, + 0xbb, 0x03, 0xbc, 0x03, 0xbd, 0x03, 0xbe, 0x03, 0xbf, 0x03, 0xc0, 0x03, 0xc1, 0x03, 0xc3, 0x03, 0xc4, 0x03, 0xc5, + 0x03, 0xc6, 0x03, 0xc7, 0x03, 0xc8, 0x03, 0xc9, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x04, 0x10, 0x04, 0x11, 0x04, 0x12, 0x04, 0x13, 0x04, 0x14, + 0x04, 0x15, 0x04, 0x01, 0x04, 0x16, 0x04, 0x17, 0x04, 0x18, 0x04, 0x19, 0x04, 0x1a, 0x04, 0x1b, 0x04, 0x1c, 0x04, + 0x1d, 0x04, 0x1e, 0x04, 0x1f, 0x04, 0x20, 0x04, 0x21, 0x04, 0x22, 0x04, 0x23, 0x04, 0x24, 0x04, 0x25, 0x04, 0x26, + 0x04, 0x27, 0x04, 0x28, 0x04, 0x29, 0x04, 0x2a, 0x04, 0x2b, 0x04, 0x2c, 0x04, 0x2d, 0x04, 0x2e, 0x04, 0x2f, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x04, 0x30, 0x04, 0x31, 0x04, 0x32, 0x04, 0x33, 0x04, + 0x34, 0x04, 0x35, 0x04, 0x51, 0x04, 0x36, 0x04, 0x37, 0x04, 0x38, 0x04, 0x39, 0x04, 0x3a, 0x04, 0x3b, 0x04, 0x3c, + 0x04, 0x3d, 0x00, 0x20, 0x04, 0x3e, 0x04, 0x3f, 0x04, 0x40, 0x04, 0x41, 0x04, 0x42, 0x04, 0x43, 0x04, 0x44, 0x04, + 0x45, 0x04, 0x46, 0x04, 0x47, 0x04, 0x48, 0x04, 0x49, 0x04, 0x4a, 0x04, 0x4b, 0x04, 0x4c, 0x04, 0x4d, 0x04, 0x4e, + 0x04, 0x4f, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x25, 0x00, 0x25, 0x02, 0x25, 0x0c, 0x25, 0x10, 0x25, 0x18, + 0x25, 0x14, 0x25, 0x1c, 0x25, 0x2c, 0x25, 0x24, 0x25, 0x34, 0x25, 0x3c, 0x25, 0x01, 0x25, 0x03, 0x25, 0x0f, 0x25, + 0x13, 0x25, 0x1b, 0x25, 0x17, 0x25, 0x23, 0x25, 0x33, 0x25, 0x2b, 0x25, 0x3b, 0x25, 0x4b, 0x25, 0x20, 0x25, 0x2f, + 0x25, 0x28, 0x25, 0x37, 0x25, 0x3f, 0x25, 0x1d, 0x25, 0x30, 0x25, 0x25, 0x25, 0x38, 0x25, 0x42, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x4e, 0x9c, 0x55, 0x16, 0x5a, 0x03, 0x96, 0x3f, 0x54, 0xc0, 0x61, 0x1b, 0x63, 0x28, + 0x59, 0xf6, 0x90, 0x22, 0x84, 0x75, 0x83, 0x1c, 0x7a, 0x50, 0x60, 0xaa, 0x63, 0xe1, 0x6e, 0x25, 0x65, 0xed, 0x84, + 0x66, 0x82, 0xa6, 0x9b, 0xf5, 0x68, 0x93, 0x57, 0x27, 0x65, 0xa1, 0x62, 0x71, 0x5b, 0x9b, 0x59, 0xd0, 0x86, 0x7b, + 0x98, 0xf4, 0x7d, 0x62, 0x7d, 0xbe, 0x9b, 0x8e, 0x62, 0x16, 0x7c, 0x9f, 0x88, 0xb7, 0x5b, 0x89, 0x5e, 0xb5, 0x63, + 0x09, 0x66, 0x97, 0x68, 0x48, 0x95, 0xc7, 0x97, 0x8d, 0x67, 0x4f, 0x4e, 0xe5, 0x4f, 0x0a, 0x4f, 0x4d, 0x4f, 0x9d, + 0x50, 0x49, 0x56, 0xf2, 0x59, 0x37, 0x59, 0xd4, 0x5a, 0x01, 0x5c, 0x09, 0x60, 0xdf, 0x61, 0x0f, 0x61, 0x70, 0x66, + 0x13, 0x69, 0x05, 0x70, 0xba, 0x75, 0x4f, 0x75, 0x70, 0x79, 0xfb, 0x7d, 0xad, 0x7d, 0xef, 0x80, 0xc3, 0x84, 0x0e, + 0x88, 0x63, 0x8b, 0x02, 0x90, 0x55, 0x90, 0x7a, 0x53, 0x3b, 0x4e, 0x95, 0x4e, 0xa5, 0x57, 0xdf, 0x80, 0xb2, 0x90, + 0xc1, 0x78, 0xef, 0x4e, 0x00, 0x58, 0xf1, 0x6e, 0xa2, 0x90, 0x38, 0x7a, 0x32, 0x83, 0x28, 0x82, 0x8b, 0x9c, 0x2f, + 0x51, 0x41, 0x53, 0x70, 0x54, 0xbd, 0x54, 0xe1, 0x56, 0xe0, 0x59, 0xfb, 0x5f, 0x15, 0x98, 0xf2, 0x6d, 0xeb, 0x80, + 0xe4, 0x85, 0x2d, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x96, 0x62, 0x96, 0x70, 0x96, 0xa0, 0x97, 0xfb, 0x54, 0x0b, 0x53, 0xf3, 0x5b, 0x87, 0x70, + 0xcf, 0x7f, 0xbd, 0x8f, 0xc2, 0x96, 0xe8, 0x53, 0x6f, 0x9d, 0x5c, 0x7a, 0xba, 0x4e, 0x11, 0x78, 0x93, 0x81, 0xfc, + 0x6e, 0x26, 0x56, 0x18, 0x55, 0x04, 0x6b, 0x1d, 0x85, 0x1a, 0x9c, 0x3b, 0x59, 0xe5, 0x53, 0xa9, 0x6d, 0x66, 0x74, + 0xdc, 0x95, 0x8f, 0x56, 0x42, 0x4e, 0x91, 0x90, 0x4b, 0x96, 0xf2, 0x83, 0x4f, 0x99, 0x0c, 0x53, 0xe1, 0x55, 0xb6, + 0x5b, 0x30, 0x5f, 0x71, 0x66, 0x20, 0x66, 0xf3, 0x68, 0x04, 0x6c, 0x38, 0x6c, 0xf3, 0x6d, 0x29, 0x74, 0x5b, 0x76, + 0xc8, 0x7a, 0x4e, 0x98, 0x34, 0x82, 0xf1, 0x88, 0x5b, 0x8a, 0x60, 0x92, 0xed, 0x6d, 0xb2, 0x75, 0xab, 0x76, 0xca, + 0x99, 0xc5, 0x60, 0xa6, 0x8b, 0x01, 0x8d, 0x8a, 0x95, 0xb2, 0x69, 0x8e, 0x53, 0xad, 0x51, 0x86, 0x00, 0x20, 0x57, + 0x12, 0x58, 0x30, 0x59, 0x44, 0x5b, 0xb4, 0x5e, 0xf6, 0x60, 0x28, 0x63, 0xa9, 0x63, 0xf4, 0x6c, 0xbf, 0x6f, 0x14, + 0x70, 0x8e, 0x71, 0x14, 0x71, 0x59, 0x71, 0xd5, 0x73, 0x3f, 0x7e, 0x01, 0x82, 0x76, 0x82, 0xd1, 0x85, 0x97, 0x90, + 0x60, 0x92, 0x5b, 0x9d, 0x1b, 0x58, 0x69, 0x65, 0xbc, 0x6c, 0x5a, 0x75, 0x25, 0x51, 0xf9, 0x59, 0x2e, 0x59, 0x65, + 0x5f, 0x80, 0x5f, 0xdc, 0x62, 0xbc, 0x65, 0xfa, 0x6a, 0x2a, 0x6b, 0x27, 0x6b, 0xb4, 0x73, 0x8b, 0x7f, 0xc1, 0x89, + 0x56, 0x9d, 0x2c, 0x9d, 0x0e, 0x9e, 0xc4, 0x5c, 0xa1, 0x6c, 0x96, 0x83, 0x7b, 0x51, 0x04, 0x5c, 0x4b, 0x61, 0xb6, + 0x81, 0xc6, 0x68, 0x76, 0x72, 0x61, 0x4e, 0x59, 0x4f, 0xfa, 0x53, 0x78, 0x60, 0x69, 0x6e, 0x29, 0x7a, 0x4f, 0x97, + 0xf3, 0x4e, 0x0b, 0x53, 0x16, 0x4e, 0xee, 0x4f, 0x55, 0x4f, 0x3d, 0x4f, 0xa1, 0x4f, 0x73, 0x52, 0xa0, 0x53, 0xef, + 0x56, 0x09, 0x59, 0x0f, 0x5a, 0xc1, 0x5b, 0xb6, 0x5b, 0xe1, 0x79, 0xd1, 0x66, 0x87, 0x67, 0x9c, 0x67, 0xb6, 0x6b, + 0x4c, 0x6c, 0xb3, 0x70, 0x6b, 0x73, 0xc2, 0x79, 0x8d, 0x79, 0xbe, 0x7a, 0x3c, 0x7b, 0x87, 0x82, 0xb1, 0x82, 0xdb, + 0x83, 0x04, 0x83, 0x77, 0x83, 0xef, 0x83, 0xd3, 0x87, 0x66, 0x8a, 0xb2, 0x56, 0x29, 0x8c, 0xa8, 0x8f, 0xe6, 0x90, + 0x4e, 0x97, 0x1e, 0x86, 0x8a, 0x4f, 0xc4, 0x5c, 0xe8, 0x62, 0x11, 0x72, 0x59, 0x75, 0x3b, 0x81, 0xe5, 0x82, 0xbd, + 0x86, 0xfe, 0x8c, 0xc0, 0x96, 0xc5, 0x99, 0x13, 0x99, 0xd5, 0x4e, 0xcb, 0x4f, 0x1a, 0x89, 0xe3, 0x56, 0xde, 0x58, + 0x4a, 0x58, 0xca, 0x5e, 0xfb, 0x5f, 0xeb, 0x60, 0x2a, 0x60, 0x94, 0x60, 0x62, 0x61, 0xd0, 0x62, 0x12, 0x62, 0xd0, + 0x65, 0x39, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x9b, 0x41, 0x66, 0x66, 0x68, 0xb0, 0x6d, 0x77, 0x70, 0x70, 0x75, 0x4c, 0x76, 0x86, 0x7d, 0x75, + 0x82, 0xa5, 0x87, 0xf9, 0x95, 0x8b, 0x96, 0x8e, 0x8c, 0x9d, 0x51, 0xf1, 0x52, 0xbe, 0x59, 0x16, 0x54, 0xb3, 0x5b, + 0xb3, 0x5d, 0x16, 0x61, 0x68, 0x69, 0x82, 0x6d, 0xaf, 0x78, 0x8d, 0x84, 0xcb, 0x88, 0x57, 0x8a, 0x72, 0x93, 0xa7, + 0x9a, 0xb8, 0x6d, 0x6c, 0x99, 0xa8, 0x86, 0xd9, 0x57, 0xa3, 0x67, 0xff, 0x86, 0xce, 0x92, 0x0e, 0x52, 0x83, 0x56, + 0x87, 0x54, 0x04, 0x5e, 0xd3, 0x62, 0xe1, 0x64, 0xb9, 0x68, 0x3c, 0x68, 0x38, 0x6b, 0xbb, 0x73, 0x72, 0x78, 0xba, + 0x7a, 0x6b, 0x89, 0x9a, 0x89, 0xd2, 0x8d, 0x6b, 0x8f, 0x03, 0x90, 0xed, 0x95, 0xa3, 0x96, 0x94, 0x97, 0x69, 0x5b, + 0x66, 0x5c, 0xb3, 0x69, 0x7d, 0x98, 0x4d, 0x98, 0x4e, 0x63, 0x9b, 0x7b, 0x20, 0x6a, 0x2b, 0x00, 0x20, 0x6a, 0x7f, + 0x68, 0xb6, 0x9c, 0x0d, 0x6f, 0x5f, 0x52, 0x72, 0x55, 0x9d, 0x60, 0x70, 0x62, 0xec, 0x6d, 0x3b, 0x6e, 0x07, 0x6e, + 0xd1, 0x84, 0x5b, 0x89, 0x10, 0x8f, 0x44, 0x4e, 0x14, 0x9c, 0x39, 0x53, 0xf6, 0x69, 0x1b, 0x6a, 0x3a, 0x97, 0x84, + 0x68, 0x2a, 0x51, 0x5c, 0x7a, 0xc3, 0x84, 0xb2, 0x91, 0xdc, 0x93, 0x8c, 0x56, 0x5b, 0x9d, 0x28, 0x68, 0x22, 0x83, + 0x05, 0x84, 0x31, 0x7c, 0xa5, 0x52, 0x08, 0x82, 0xc5, 0x74, 0xe6, 0x4e, 0x7e, 0x4f, 0x83, 0x51, 0xa0, 0x5b, 0xd2, + 0x52, 0x0a, 0x52, 0xd8, 0x52, 0xe7, 0x5d, 0xfb, 0x55, 0x9a, 0x58, 0x2a, 0x59, 0xe6, 0x5b, 0x8c, 0x5b, 0x98, 0x5b, + 0xdb, 0x5e, 0x72, 0x5e, 0x79, 0x60, 0xa3, 0x61, 0x1f, 0x61, 0x63, 0x61, 0xbe, 0x63, 0xdb, 0x65, 0x62, 0x67, 0xd1, + 0x68, 0x53, 0x68, 0xfa, 0x6b, 0x3e, 0x6b, 0x53, 0x6c, 0x57, 0x6f, 0x22, 0x6f, 0x97, 0x6f, 0x45, 0x74, 0xb0, 0x75, + 0x18, 0x76, 0xe3, 0x77, 0x0b, 0x7a, 0xff, 0x7b, 0xa1, 0x7c, 0x21, 0x7d, 0xe9, 0x7f, 0x36, 0x7f, 0xf0, 0x80, 0x9d, + 0x82, 0x66, 0x83, 0x9e, 0x89, 0xb3, 0x8a, 0xcc, 0x8c, 0xab, 0x90, 0x84, 0x94, 0x51, 0x95, 0x93, 0x95, 0x91, 0x95, + 0xa2, 0x96, 0x65, 0x97, 0xd3, 0x99, 0x28, 0x82, 0x18, 0x4e, 0x38, 0x54, 0x2b, 0x5c, 0xb8, 0x5d, 0xcc, 0x73, 0xa9, + 0x76, 0x4c, 0x77, 0x3c, 0x5c, 0xa9, 0x7f, 0xeb, 0x8d, 0x0b, 0x96, 0xc1, 0x98, 0x11, 0x98, 0x54, 0x98, 0x58, 0x4f, + 0x01, 0x4f, 0x0e, 0x53, 0x71, 0x55, 0x9c, 0x56, 0x68, 0x57, 0xfa, 0x59, 0x47, 0x5b, 0x09, 0x5b, 0xc4, 0x5c, 0x90, + 0x5e, 0x0c, 0x5e, 0x7e, 0x5f, 0xcc, 0x63, 0xee, 0x67, 0x3a, 0x65, 0xd7, 0x65, 0xe2, 0x67, 0x1f, 0x68, 0xcb, 0x68, + 0xc4, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x6a, 0x5f, 0x5e, 0x30, 0x6b, 0xc5, 0x6c, 0x17, 0x6c, 0x7d, 0x75, 0x7f, 0x79, 0x48, 0x5b, 0x63, 0x7a, + 0x00, 0x7d, 0x00, 0x5f, 0xbd, 0x89, 0x8f, 0x8a, 0x18, 0x8c, 0xb4, 0x8d, 0x77, 0x8e, 0xcc, 0x8f, 0x1d, 0x98, 0xe2, + 0x9a, 0x0e, 0x9b, 0x3c, 0x4e, 0x80, 0x50, 0x7d, 0x51, 0x00, 0x59, 0x93, 0x5b, 0x9c, 0x62, 0x2f, 0x62, 0x80, 0x64, + 0xec, 0x6b, 0x3a, 0x72, 0xa0, 0x75, 0x91, 0x79, 0x47, 0x7f, 0xa9, 0x87, 0xfb, 0x8a, 0xbc, 0x8b, 0x70, 0x63, 0xac, + 0x83, 0xca, 0x97, 0xa0, 0x54, 0x09, 0x54, 0x03, 0x55, 0xab, 0x68, 0x54, 0x6a, 0x58, 0x8a, 0x70, 0x78, 0x27, 0x67, + 0x75, 0x9e, 0xcd, 0x53, 0x74, 0x5b, 0xa2, 0x81, 0x1a, 0x86, 0x50, 0x90, 0x06, 0x4e, 0x18, 0x4e, 0x45, 0x4e, 0xc7, + 0x4f, 0x11, 0x53, 0xca, 0x54, 0x38, 0x5b, 0xae, 0x5f, 0x13, 0x60, 0x25, 0x65, 0x51, 0x00, 0x20, 0x67, 0x3d, 0x6c, + 0x42, 0x6c, 0x72, 0x6c, 0xe3, 0x70, 0x78, 0x74, 0x03, 0x7a, 0x76, 0x7a, 0xae, 0x7b, 0x08, 0x7d, 0x1a, 0x7c, 0xfe, + 0x7d, 0x66, 0x65, 0xe7, 0x72, 0x5b, 0x53, 0xbb, 0x5c, 0x45, 0x5d, 0xe8, 0x62, 0xd2, 0x62, 0xe0, 0x63, 0x19, 0x6e, + 0x20, 0x86, 0x5a, 0x8a, 0x31, 0x8d, 0xdd, 0x92, 0xf8, 0x6f, 0x01, 0x79, 0xa6, 0x9b, 0x5a, 0x4e, 0xa8, 0x4e, 0xab, + 0x4e, 0xac, 0x4f, 0x9b, 0x4f, 0xa0, 0x50, 0xd1, 0x51, 0x47, 0x7a, 0xf6, 0x51, 0x71, 0x51, 0xf6, 0x53, 0x54, 0x53, + 0x21, 0x53, 0x7f, 0x53, 0xeb, 0x55, 0xac, 0x58, 0x83, 0x5c, 0xe1, 0x5f, 0x37, 0x5f, 0x4a, 0x60, 0x2f, 0x60, 0x50, + 0x60, 0x6d, 0x63, 0x1f, 0x65, 0x59, 0x6a, 0x4b, 0x6c, 0xc1, 0x72, 0xc2, 0x72, 0xed, 0x77, 0xef, 0x80, 0xf8, 0x81, + 0x05, 0x82, 0x08, 0x85, 0x4e, 0x90, 0xf7, 0x93, 0xe1, 0x97, 0xff, 0x99, 0x57, 0x9a, 0x5a, 0x4e, 0xf0, 0x51, 0xdd, + 0x5c, 0x2d, 0x66, 0x81, 0x69, 0x6d, 0x5c, 0x40, 0x66, 0xf2, 0x69, 0x75, 0x73, 0x89, 0x68, 0x50, 0x7c, 0x81, 0x50, + 0xc5, 0x52, 0xe4, 0x57, 0x47, 0x5d, 0xfe, 0x93, 0x26, 0x65, 0xa4, 0x6b, 0x23, 0x6b, 0x3d, 0x74, 0x34, 0x79, 0x81, + 0x79, 0xbd, 0x7b, 0x4b, 0x7d, 0xca, 0x82, 0xb9, 0x83, 0xcc, 0x88, 0x7f, 0x89, 0x5f, 0x8b, 0x39, 0x8f, 0xd1, 0x91, + 0xd1, 0x54, 0x1f, 0x92, 0x80, 0x4e, 0x5d, 0x50, 0x36, 0x53, 0xe5, 0x53, 0x3a, 0x72, 0xd7, 0x73, 0x96, 0x77, 0xe9, + 0x82, 0xe6, 0x8e, 0xaf, 0x99, 0xc6, 0x99, 0xc8, 0x99, 0xd2, 0x51, 0x77, 0x61, 0x1a, 0x86, 0x5e, 0x55, 0xb0, 0x7a, + 0x7a, 0x50, 0x76, 0x5b, 0xd3, 0x90, 0x47, 0x96, 0x85, 0x4e, 0x32, 0x6a, 0xdb, 0x91, 0xe7, 0x5c, 0x51, 0x5c, 0x48, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x63, 0x98, 0x7a, 0x9f, 0x6c, 0x93, 0x97, 0x74, 0x8f, 0x61, 0x7a, 0xaa, 0x71, 0x8a, 0x96, 0x88, 0x7c, 0x82, + 0x68, 0x17, 0x7e, 0x70, 0x68, 0x51, 0x93, 0x6c, 0x52, 0xf2, 0x54, 0x1b, 0x85, 0xab, 0x8a, 0x13, 0x7f, 0xa4, 0x8e, + 0xcd, 0x90, 0xe1, 0x53, 0x66, 0x88, 0x88, 0x79, 0x41, 0x4f, 0xc2, 0x50, 0xbe, 0x52, 0x11, 0x51, 0x44, 0x55, 0x53, + 0x57, 0x2d, 0x73, 0xea, 0x57, 0x8b, 0x59, 0x51, 0x5f, 0x62, 0x5f, 0x84, 0x60, 0x75, 0x61, 0x76, 0x61, 0x67, 0x61, + 0xa9, 0x63, 0xb2, 0x64, 0x3a, 0x65, 0x6c, 0x66, 0x6f, 0x68, 0x42, 0x6e, 0x13, 0x75, 0x66, 0x7a, 0x3d, 0x7c, 0xfb, + 0x7d, 0x4c, 0x7d, 0x99, 0x7e, 0x4b, 0x7f, 0x6b, 0x83, 0x0e, 0x83, 0x4a, 0x86, 0xcd, 0x8a, 0x08, 0x8a, 0x63, 0x8b, + 0x66, 0x8e, 0xfd, 0x98, 0x1a, 0x9d, 0x8f, 0x82, 0xb8, 0x8f, 0xce, 0x9b, 0xe8, 0x00, 0x20, 0x52, 0x87, 0x62, 0x1f, + 0x64, 0x83, 0x6f, 0xc0, 0x96, 0x99, 0x68, 0x41, 0x50, 0x91, 0x6b, 0x20, 0x6c, 0x7a, 0x6f, 0x54, 0x7a, 0x74, 0x7d, + 0x50, 0x88, 0x40, 0x8a, 0x23, 0x67, 0x08, 0x4e, 0xf6, 0x50, 0x39, 0x50, 0x26, 0x50, 0x65, 0x51, 0x7c, 0x52, 0x38, + 0x52, 0x63, 0x55, 0xa7, 0x57, 0x0f, 0x58, 0x05, 0x5a, 0xcc, 0x5e, 0xfa, 0x61, 0xb2, 0x61, 0xf8, 0x62, 0xf3, 0x63, + 0x72, 0x69, 0x1c, 0x6a, 0x29, 0x72, 0x7d, 0x72, 0xac, 0x73, 0x2e, 0x78, 0x14, 0x78, 0x6f, 0x7d, 0x79, 0x77, 0x0c, + 0x80, 0xa9, 0x89, 0x8b, 0x8b, 0x19, 0x8c, 0xe2, 0x8e, 0xd2, 0x90, 0x63, 0x93, 0x75, 0x96, 0x7a, 0x98, 0x55, 0x9a, + 0x13, 0x9e, 0x78, 0x51, 0x43, 0x53, 0x9f, 0x53, 0xb3, 0x5e, 0x7b, 0x5f, 0x26, 0x6e, 0x1b, 0x6e, 0x90, 0x73, 0x84, + 0x73, 0xfe, 0x7d, 0x43, 0x82, 0x37, 0x8a, 0x00, 0x8a, 0xfa, 0x96, 0x50, 0x4e, 0x4e, 0x50, 0x0b, 0x53, 0xe4, 0x54, + 0x7c, 0x56, 0xfa, 0x59, 0xd1, 0x5b, 0x64, 0x5d, 0xf1, 0x5e, 0xab, 0x5f, 0x27, 0x62, 0x38, 0x65, 0x45, 0x67, 0xaf, + 0x6e, 0x56, 0x72, 0xd0, 0x7c, 0xca, 0x88, 0xb4, 0x80, 0xa1, 0x80, 0xe1, 0x83, 0xf0, 0x86, 0x4e, 0x8a, 0x87, 0x8d, + 0xe8, 0x92, 0x37, 0x96, 0xc7, 0x98, 0x67, 0x9f, 0x13, 0x4e, 0x94, 0x4e, 0x92, 0x4f, 0x0d, 0x53, 0x48, 0x54, 0x49, + 0x54, 0x3e, 0x5a, 0x2f, 0x5f, 0x8c, 0x5f, 0xa1, 0x60, 0x9f, 0x68, 0xa7, 0x6a, 0x8e, 0x74, 0x5a, 0x78, 0x81, 0x8a, + 0x9e, 0x8a, 0xa4, 0x8b, 0x77, 0x91, 0x90, 0x4e, 0x5e, 0x9b, 0xc9, 0x4e, 0xa4, 0x4f, 0x7c, 0x4f, 0xaf, 0x50, 0x19, + 0x50, 0x16, 0x51, 0x49, 0x51, 0x6c, 0x52, 0x9f, 0x52, 0xb9, 0x52, 0xfe, 0x53, 0x9a, 0x53, 0xe3, 0x54, 0x11, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x54, 0x0e, 0x55, 0x89, 0x57, 0x51, 0x57, 0xa2, 0x59, 0x7d, 0x5b, 0x54, 0x5b, 0x5d, 0x5b, 0x8f, 0x5d, 0xe5, 0x5d, + 0xe7, 0x5d, 0xf7, 0x5e, 0x78, 0x5e, 0x83, 0x5e, 0x9a, 0x5e, 0xb7, 0x5f, 0x18, 0x60, 0x52, 0x61, 0x4c, 0x62, 0x97, + 0x62, 0xd8, 0x63, 0xa7, 0x65, 0x3b, 0x66, 0x02, 0x66, 0x43, 0x66, 0xf4, 0x67, 0x6d, 0x68, 0x21, 0x68, 0x97, 0x69, + 0xcb, 0x6c, 0x5f, 0x6d, 0x2a, 0x6d, 0x69, 0x6e, 0x2f, 0x6e, 0x9d, 0x75, 0x32, 0x76, 0x87, 0x78, 0x6c, 0x7a, 0x3f, + 0x7c, 0xe0, 0x7d, 0x05, 0x7d, 0x18, 0x7d, 0x5e, 0x7d, 0xb1, 0x80, 0x15, 0x80, 0x03, 0x80, 0xaf, 0x80, 0xb1, 0x81, + 0x54, 0x81, 0x8f, 0x82, 0x2a, 0x83, 0x52, 0x88, 0x4c, 0x88, 0x61, 0x8b, 0x1b, 0x8c, 0xa2, 0x8c, 0xfc, 0x90, 0xca, + 0x91, 0x75, 0x92, 0x71, 0x78, 0x3f, 0x92, 0xfc, 0x95, 0xa4, 0x96, 0x4d, 0x00, 0x20, 0x98, 0x05, 0x99, 0x99, 0x9a, + 0xd8, 0x9d, 0x3b, 0x52, 0x5b, 0x52, 0xab, 0x53, 0xf7, 0x54, 0x08, 0x58, 0xd5, 0x62, 0xf7, 0x6f, 0xe0, 0x8c, 0x6a, + 0x8f, 0x5f, 0x9e, 0xb9, 0x51, 0x4b, 0x52, 0x3b, 0x54, 0x4a, 0x56, 0xfd, 0x7a, 0x40, 0x91, 0x77, 0x9d, 0x60, 0x9e, + 0xd2, 0x73, 0x44, 0x6f, 0x09, 0x81, 0x70, 0x75, 0x11, 0x5f, 0xfd, 0x60, 0xda, 0x9a, 0xa8, 0x72, 0xdb, 0x8f, 0xbc, + 0x6b, 0x64, 0x98, 0x03, 0x4e, 0xca, 0x56, 0xf0, 0x57, 0x64, 0x58, 0xbe, 0x5a, 0x5a, 0x60, 0x68, 0x61, 0xc7, 0x66, + 0x0f, 0x66, 0x06, 0x68, 0x39, 0x68, 0xb1, 0x6d, 0xf7, 0x75, 0xd5, 0x7d, 0x3a, 0x82, 0x6e, 0x9b, 0x42, 0x4e, 0x9b, + 0x4f, 0x50, 0x53, 0xc9, 0x55, 0x06, 0x5d, 0x6f, 0x5d, 0xe6, 0x5d, 0xee, 0x67, 0xfb, 0x6c, 0x99, 0x74, 0x73, 0x78, + 0x02, 0x8a, 0x50, 0x93, 0x96, 0x88, 0xdf, 0x57, 0x50, 0x5e, 0xa7, 0x63, 0x2b, 0x50, 0xb5, 0x50, 0xac, 0x51, 0x8d, + 0x67, 0x00, 0x54, 0xc9, 0x58, 0x5e, 0x59, 0xbb, 0x5b, 0xb0, 0x5f, 0x69, 0x62, 0x4d, 0x63, 0xa1, 0x68, 0x3d, 0x6b, + 0x73, 0x6e, 0x08, 0x70, 0x7d, 0x91, 0xc7, 0x72, 0x80, 0x78, 0x15, 0x78, 0x26, 0x79, 0x6d, 0x65, 0x8e, 0x7d, 0x30, + 0x83, 0xdc, 0x88, 0xc1, 0x8f, 0x09, 0x96, 0x9b, 0x52, 0x64, 0x57, 0x28, 0x67, 0x50, 0x7f, 0x6a, 0x8c, 0xa1, 0x51, + 0xb4, 0x57, 0x42, 0x96, 0x2a, 0x58, 0x3a, 0x69, 0x8a, 0x80, 0xb4, 0x54, 0xb2, 0x5d, 0x0e, 0x57, 0xfc, 0x78, 0x95, + 0x9d, 0xfa, 0x4f, 0x5c, 0x52, 0x4a, 0x54, 0x8b, 0x64, 0x3e, 0x66, 0x28, 0x67, 0x14, 0x67, 0xf5, 0x7a, 0x84, 0x7b, + 0x56, 0x7d, 0x22, 0x93, 0x2f, 0x68, 0x5c, 0x9b, 0xad, 0x7b, 0x39, 0x53, 0x19, 0x51, 0x8a, 0x52, 0x37, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x5b, + 0xdf, 0x62, 0xf6, 0x64, 0xae, 0x64, 0xe6, 0x67, 0x2d, 0x6b, 0xba, 0x85, 0xa9, 0x96, 0xd1, 0x76, 0x90, 0x9b, 0xd6, + 0x63, 0x4c, 0x93, 0x06, 0x9b, 0xab, 0x76, 0xbf, 0x66, 0x52, 0x4e, 0x09, 0x50, 0x98, 0x53, 0xc2, 0x5c, 0x71, 0x60, + 0xe8, 0x64, 0x92, 0x65, 0x63, 0x68, 0x5f, 0x71, 0xe6, 0x73, 0xca, 0x75, 0x23, 0x7b, 0x97, 0x7e, 0x82, 0x86, 0x95, + 0x8b, 0x83, 0x8c, 0xdb, 0x91, 0x78, 0x99, 0x10, 0x65, 0xac, 0x66, 0xab, 0x6b, 0x8b, 0x4e, 0xd5, 0x4e, 0xd4, 0x4f, + 0x3a, 0x4f, 0x7f, 0x52, 0x3a, 0x53, 0xf8, 0x53, 0xf2, 0x55, 0xe3, 0x56, 0xdb, 0x58, 0xeb, 0x59, 0xcb, 0x59, 0xc9, + 0x59, 0xff, 0x5b, 0x50, 0x5c, 0x4d, 0x5e, 0x02, 0x5e, 0x2b, 0x5f, 0xd7, 0x60, 0x1d, 0x63, 0x07, 0x65, 0x2f, 0x5b, + 0x5c, 0x65, 0xaf, 0x65, 0xbd, 0x65, 0xe8, 0x67, 0x9d, 0x6b, 0x62, 0x00, 0x20, 0x6b, 0x7b, 0x6c, 0x0f, 0x73, 0x45, + 0x79, 0x49, 0x79, 0xc1, 0x7c, 0xf8, 0x7d, 0x19, 0x7d, 0x2b, 0x80, 0xa2, 0x81, 0x02, 0x81, 0xf3, 0x89, 0x96, 0x8a, + 0x5e, 0x8a, 0x69, 0x8a, 0x66, 0x8a, 0x8c, 0x8a, 0xee, 0x8c, 0xc7, 0x8c, 0xdc, 0x96, 0xcc, 0x98, 0xfc, 0x6b, 0x6f, + 0x4e, 0x8b, 0x4f, 0x3c, 0x4f, 0x8d, 0x51, 0x50, 0x5b, 0x57, 0x5b, 0xfa, 0x61, 0x48, 0x63, 0x01, 0x66, 0x42, 0x6b, + 0x21, 0x6e, 0xcb, 0x6c, 0xbb, 0x72, 0x3e, 0x74, 0xbd, 0x75, 0xd4, 0x78, 0xc1, 0x79, 0x3a, 0x80, 0x0c, 0x80, 0x33, + 0x81, 0xea, 0x84, 0x94, 0x8f, 0x9e, 0x6c, 0x50, 0x9e, 0x7f, 0x5f, 0x0f, 0x8b, 0x58, 0x9d, 0x2b, 0x7a, 0xfa, 0x8e, + 0xf8, 0x5b, 0x8d, 0x96, 0xeb, 0x4e, 0x03, 0x53, 0xf1, 0x57, 0xf7, 0x59, 0x31, 0x5a, 0xc9, 0x5b, 0xa4, 0x60, 0x89, + 0x6e, 0x7f, 0x6f, 0x06, 0x75, 0xbe, 0x8c, 0xea, 0x5b, 0x9f, 0x85, 0x00, 0x7b, 0xe0, 0x50, 0x72, 0x67, 0xf4, 0x82, + 0x9d, 0x5c, 0x61, 0x85, 0x4a, 0x7e, 0x1e, 0x82, 0x0e, 0x51, 0x99, 0x5c, 0x04, 0x63, 0x68, 0x8d, 0x66, 0x65, 0x9c, + 0x71, 0x6e, 0x79, 0x3e, 0x7d, 0x17, 0x80, 0x05, 0x8b, 0x1d, 0x8e, 0xca, 0x90, 0x6e, 0x86, 0xc7, 0x90, 0xaa, 0x50, + 0x1f, 0x52, 0xfa, 0x5c, 0x3a, 0x67, 0x53, 0x70, 0x7c, 0x72, 0x35, 0x91, 0x4c, 0x91, 0xc8, 0x93, 0x2b, 0x82, 0xe5, + 0x5b, 0xc2, 0x5f, 0x31, 0x60, 0xf9, 0x4e, 0x3b, 0x53, 0xd6, 0x5b, 0x88, 0x62, 0x4b, 0x67, 0x31, 0x6b, 0x8a, 0x72, + 0xe9, 0x73, 0xe0, 0x7a, 0x2e, 0x81, 0x6b, 0x8d, 0xa3, 0x91, 0x52, 0x99, 0x96, 0x51, 0x12, 0x53, 0xd7, 0x54, 0x6a, + 0x5b, 0xff, 0x63, 0x88, 0x6a, 0x39, 0x7d, 0xac, 0x97, 0x00, 0x56, 0xda, 0x53, 0xce, 0x54, 0x68, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x5b, 0x97, + 0x5c, 0x31, 0x5d, 0xde, 0x4f, 0xee, 0x61, 0x01, 0x62, 0xfe, 0x6d, 0x32, 0x79, 0xc0, 0x79, 0xcb, 0x7d, 0x42, 0x7e, + 0x4d, 0x7f, 0xd2, 0x81, 0xed, 0x82, 0x1f, 0x84, 0x90, 0x88, 0x46, 0x89, 0x72, 0x8b, 0x90, 0x8e, 0x74, 0x8f, 0x2f, + 0x90, 0x31, 0x91, 0x4b, 0x91, 0x6c, 0x96, 0xc6, 0x91, 0x9c, 0x4e, 0xc0, 0x4f, 0x4f, 0x51, 0x45, 0x53, 0x41, 0x5f, + 0x93, 0x62, 0x0e, 0x67, 0xd4, 0x6c, 0x41, 0x6e, 0x0b, 0x73, 0x63, 0x7e, 0x26, 0x91, 0xcd, 0x92, 0x83, 0x53, 0xd4, + 0x59, 0x19, 0x5b, 0xbf, 0x6d, 0xd1, 0x79, 0x5d, 0x7e, 0x2e, 0x7c, 0x9b, 0x58, 0x7e, 0x71, 0x9f, 0x51, 0xfa, 0x88, + 0x53, 0x8f, 0xf0, 0x4f, 0xca, 0x5c, 0xfb, 0x66, 0x25, 0x77, 0xac, 0x7a, 0xe3, 0x82, 0x1c, 0x99, 0xff, 0x51, 0xc6, + 0x5f, 0xaa, 0x65, 0xec, 0x69, 0x6f, 0x6b, 0x89, 0x6d, 0xf3, 0x00, 0x20, 0x6e, 0x96, 0x6f, 0x64, 0x76, 0xfe, 0x7d, + 0x14, 0x5d, 0xe1, 0x90, 0x75, 0x91, 0x87, 0x98, 0x06, 0x51, 0xe6, 0x52, 0x1d, 0x62, 0x40, 0x66, 0x91, 0x66, 0xd9, + 0x6e, 0x1a, 0x5e, 0xb6, 0x7d, 0xd2, 0x7f, 0x72, 0x66, 0xf8, 0x85, 0xaf, 0x85, 0xf7, 0x8a, 0xf8, 0x52, 0xa9, 0x53, + 0xd9, 0x59, 0x73, 0x5e, 0x8f, 0x5f, 0x90, 0x60, 0x55, 0x92, 0xe4, 0x96, 0x64, 0x50, 0xb7, 0x51, 0x1f, 0x52, 0xdd, + 0x53, 0x20, 0x53, 0x47, 0x53, 0xec, 0x54, 0xe8, 0x55, 0x46, 0x55, 0x31, 0x56, 0x17, 0x59, 0x68, 0x59, 0xbe, 0x5a, + 0x3c, 0x5b, 0xb5, 0x5c, 0x06, 0x5c, 0x0f, 0x5c, 0x11, 0x5c, 0x1a, 0x5e, 0x84, 0x5e, 0x8a, 0x5e, 0xe0, 0x5f, 0x70, + 0x62, 0x7f, 0x62, 0x84, 0x62, 0xdb, 0x63, 0x8c, 0x63, 0x77, 0x66, 0x07, 0x66, 0x0c, 0x66, 0x2d, 0x66, 0x76, 0x67, + 0x7e, 0x68, 0xa2, 0x6a, 0x1f, 0x6a, 0x35, 0x6c, 0xbc, 0x6d, 0x88, 0x6e, 0x09, 0x6e, 0x58, 0x71, 0x3c, 0x71, 0x26, + 0x71, 0x67, 0x75, 0xc7, 0x77, 0x01, 0x78, 0x5d, 0x79, 0x01, 0x79, 0x65, 0x79, 0xf0, 0x7a, 0xe0, 0x7b, 0x11, 0x7c, + 0xa7, 0x7d, 0x39, 0x80, 0x96, 0x83, 0xd6, 0x84, 0x8b, 0x85, 0x49, 0x88, 0x5d, 0x88, 0xf3, 0x8a, 0x1f, 0x8a, 0x3c, + 0x8a, 0x54, 0x8a, 0x73, 0x8c, 0x61, 0x8c, 0xde, 0x91, 0xa4, 0x92, 0x66, 0x93, 0x7e, 0x94, 0x18, 0x96, 0x9c, 0x97, + 0x98, 0x4e, 0x0a, 0x4e, 0x08, 0x4e, 0x1e, 0x4e, 0x57, 0x51, 0x97, 0x52, 0x70, 0x57, 0xce, 0x58, 0x34, 0x58, 0xcc, + 0x5b, 0x22, 0x5e, 0x38, 0x60, 0xc5, 0x64, 0xfe, 0x67, 0x61, 0x67, 0x56, 0x6d, 0x44, 0x72, 0xb6, 0x75, 0x73, 0x7a, + 0x63, 0x84, 0xb8, 0x8b, 0x72, 0x91, 0xb8, 0x93, 0x20, 0x56, 0x31, 0x57, 0xf4, 0x98, 0xfe, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x62, 0xed, 0x69, + 0x0d, 0x6b, 0x96, 0x71, 0xed, 0x7e, 0x54, 0x80, 0x77, 0x82, 0x72, 0x89, 0xe6, 0x98, 0xdf, 0x87, 0x55, 0x8f, 0xb1, + 0x5c, 0x3b, 0x4f, 0x38, 0x4f, 0xe1, 0x4f, 0xb5, 0x55, 0x07, 0x5a, 0x20, 0x5b, 0xdd, 0x5b, 0xe9, 0x5f, 0xc3, 0x61, + 0x4e, 0x63, 0x2f, 0x65, 0xb0, 0x66, 0x4b, 0x68, 0xee, 0x69, 0x9b, 0x6d, 0x78, 0x6d, 0xf1, 0x75, 0x33, 0x75, 0xb9, + 0x77, 0x1f, 0x79, 0x5e, 0x79, 0xe6, 0x7d, 0x33, 0x81, 0xe3, 0x82, 0xaf, 0x85, 0xaa, 0x89, 0xaa, 0x8a, 0x3a, 0x8e, + 0xab, 0x8f, 0x9b, 0x90, 0x32, 0x91, 0xdd, 0x97, 0x07, 0x4e, 0xba, 0x4e, 0xc1, 0x52, 0x03, 0x58, 0x75, 0x58, 0xec, + 0x5c, 0x0b, 0x75, 0x1a, 0x5c, 0x3d, 0x81, 0x4e, 0x8a, 0x0a, 0x8f, 0xc5, 0x96, 0x63, 0x97, 0x6d, 0x7b, 0x25, 0x8a, + 0xcf, 0x98, 0x08, 0x91, 0x62, 0x56, 0xf3, 0x53, 0xa8, 0x00, 0x20, 0x90, 0x17, 0x54, 0x39, 0x57, 0x82, 0x5e, 0x25, + 0x63, 0xa8, 0x6c, 0x34, 0x70, 0x8a, 0x77, 0x61, 0x7c, 0x8b, 0x7f, 0xe0, 0x88, 0x70, 0x90, 0x42, 0x91, 0x54, 0x93, + 0x10, 0x93, 0x18, 0x96, 0x8f, 0x74, 0x5e, 0x9a, 0xc4, 0x5d, 0x07, 0x5d, 0x69, 0x65, 0x70, 0x67, 0xa2, 0x8d, 0xa8, + 0x96, 0xdb, 0x63, 0x6e, 0x67, 0x49, 0x69, 0x19, 0x83, 0xc5, 0x98, 0x17, 0x96, 0xc0, 0x88, 0xfe, 0x6f, 0x84, 0x64, + 0x7a, 0x5b, 0xf8, 0x4e, 0x16, 0x70, 0x2c, 0x75, 0x5d, 0x66, 0x2f, 0x51, 0xc4, 0x52, 0x36, 0x52, 0xe2, 0x59, 0xd3, + 0x5f, 0x81, 0x60, 0x27, 0x62, 0x10, 0x65, 0x3f, 0x65, 0x74, 0x66, 0x1f, 0x66, 0x74, 0x68, 0xf2, 0x68, 0x16, 0x6b, + 0x63, 0x6e, 0x05, 0x72, 0x72, 0x75, 0x1f, 0x76, 0xdb, 0x7c, 0xbe, 0x80, 0x56, 0x58, 0xf0, 0x88, 0xfd, 0x89, 0x7f, + 0x8a, 0xa0, 0x8a, 0x93, 0x8a, 0xcb, 0x90, 0x1d, 0x91, 0x92, 0x97, 0x52, 0x97, 0x59, 0x65, 0x89, 0x7a, 0x0e, 0x81, + 0x06, 0x96, 0xbb, 0x5e, 0x2d, 0x60, 0xdc, 0x62, 0x1a, 0x65, 0xa5, 0x66, 0x14, 0x67, 0x90, 0x77, 0xf3, 0x7a, 0x4d, + 0x7c, 0x4d, 0x7e, 0x3e, 0x81, 0x0a, 0x8c, 0xac, 0x8d, 0x64, 0x8d, 0xe1, 0x8e, 0x5f, 0x78, 0xa9, 0x52, 0x07, 0x62, + 0xd9, 0x63, 0xa5, 0x64, 0x42, 0x62, 0x98, 0x8a, 0x2d, 0x7a, 0x83, 0x7b, 0xc0, 0x8a, 0xac, 0x96, 0xea, 0x7d, 0x76, + 0x82, 0x0c, 0x87, 0x49, 0x4e, 0xd9, 0x51, 0x48, 0x53, 0x43, 0x53, 0x60, 0x5b, 0xa3, 0x5c, 0x02, 0x5c, 0x16, 0x5d, + 0xdd, 0x62, 0x26, 0x62, 0x47, 0x64, 0xb0, 0x68, 0x13, 0x68, 0x34, 0x6c, 0xc9, 0x6d, 0x45, 0x6d, 0x17, 0x67, 0xd3, + 0x6f, 0x5c, 0x71, 0x4e, 0x71, 0x7d, 0x65, 0xcb, 0x7a, 0x7f, 0x7b, 0xad, 0x7d, 0xda, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x7e, 0x4a, 0x7f, 0xa8, + 0x81, 0x7a, 0x82, 0x1b, 0x82, 0x39, 0x85, 0xa6, 0x8a, 0x6e, 0x8c, 0xce, 0x8d, 0xf5, 0x90, 0x78, 0x90, 0x77, 0x92, + 0xad, 0x92, 0x91, 0x95, 0x83, 0x9b, 0xae, 0x52, 0x4d, 0x55, 0x84, 0x6f, 0x38, 0x71, 0x36, 0x51, 0x68, 0x79, 0x85, + 0x7e, 0x55, 0x81, 0xb3, 0x7c, 0xce, 0x56, 0x4c, 0x58, 0x51, 0x5c, 0xa8, 0x63, 0xaa, 0x66, 0xfe, 0x66, 0xfd, 0x69, + 0x5a, 0x72, 0xd9, 0x75, 0x8f, 0x75, 0x8e, 0x79, 0x0e, 0x79, 0x56, 0x79, 0xdf, 0x7c, 0x97, 0x7d, 0x20, 0x7d, 0x44, + 0x86, 0x07, 0x8a, 0x34, 0x96, 0x3b, 0x90, 0x61, 0x9f, 0x20, 0x50, 0xe7, 0x52, 0x75, 0x53, 0xcc, 0x53, 0xe2, 0x50, + 0x09, 0x55, 0xaa, 0x58, 0xee, 0x59, 0x4f, 0x72, 0x3d, 0x5b, 0x8b, 0x5c, 0x64, 0x53, 0x1d, 0x60, 0xe3, 0x60, 0xf3, + 0x63, 0x5c, 0x63, 0x83, 0x63, 0x3f, 0x63, 0xbb, 0x00, 0x20, 0x64, 0xcd, 0x65, 0xe9, 0x66, 0xf9, 0x5d, 0xe3, 0x69, + 0xcd, 0x69, 0xfd, 0x6f, 0x15, 0x71, 0xe5, 0x4e, 0x89, 0x75, 0xe9, 0x76, 0xf8, 0x7a, 0x93, 0x7c, 0xdf, 0x7d, 0xcf, + 0x7d, 0x9c, 0x80, 0x61, 0x83, 0x49, 0x83, 0x58, 0x84, 0x6c, 0x84, 0xbc, 0x85, 0xfb, 0x88, 0xc5, 0x8d, 0x70, 0x90, + 0x01, 0x90, 0x6d, 0x93, 0x97, 0x97, 0x1c, 0x9a, 0x12, 0x50, 0xcf, 0x58, 0x97, 0x61, 0x8e, 0x81, 0xd3, 0x85, 0x35, + 0x8d, 0x08, 0x90, 0x20, 0x4f, 0xc3, 0x50, 0x74, 0x52, 0x47, 0x53, 0x73, 0x60, 0x6f, 0x63, 0x49, 0x67, 0x5f, 0x6e, + 0x2c, 0x8d, 0xb3, 0x90, 0x1f, 0x4f, 0xd7, 0x5c, 0x5e, 0x8c, 0xca, 0x65, 0xcf, 0x7d, 0x9a, 0x53, 0x52, 0x88, 0x96, + 0x51, 0x76, 0x63, 0xc3, 0x5b, 0x58, 0x5b, 0x6b, 0x5c, 0x0a, 0x64, 0x0d, 0x67, 0x51, 0x90, 0x5c, 0x4e, 0xd6, 0x59, + 0x1a, 0x59, 0x2a, 0x6c, 0x70, 0x8a, 0x51, 0x55, 0x3e, 0x58, 0x15, 0x59, 0xa5, 0x60, 0xf0, 0x62, 0x53, 0x67, 0xc1, + 0x82, 0x35, 0x69, 0x55, 0x96, 0x40, 0x99, 0xc4, 0x9a, 0x28, 0x4f, 0x53, 0x58, 0x06, 0x5b, 0xfe, 0x80, 0x10, 0x5c, + 0xb1, 0x5e, 0x2f, 0x5f, 0x85, 0x60, 0x20, 0x61, 0x4b, 0x62, 0x34, 0x66, 0xff, 0x6c, 0xf0, 0x6e, 0xde, 0x80, 0xce, + 0x81, 0x7f, 0x82, 0xd4, 0x88, 0x8b, 0x8c, 0xb8, 0x90, 0x00, 0x90, 0x2e, 0x96, 0x8a, 0x9e, 0xdb, 0x9b, 0xdb, 0x4e, + 0xe3, 0x53, 0xf0, 0x59, 0x27, 0x7b, 0x2c, 0x91, 0x8d, 0x98, 0x4c, 0x9d, 0xf9, 0x6e, 0xdd, 0x70, 0x27, 0x53, 0x53, + 0x55, 0x44, 0x5b, 0x85, 0x62, 0x58, 0x62, 0x9e, 0x62, 0xd3, 0x6c, 0xa2, 0x6f, 0xef, 0x74, 0x22, 0x8a, 0x17, 0x94, + 0x38, 0x6f, 0xc1, 0x8a, 0xfe, 0x83, 0x38, 0x51, 0xe7, 0x86, 0xf8, 0x53, 0xea, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x53, 0xe9, 0x4f, 0x46, 0x90, + 0x54, 0x8f, 0xb0, 0x59, 0x6a, 0x81, 0x31, 0x5d, 0xfd, 0x7a, 0xea, 0x8f, 0xbf, 0x68, 0xda, 0x8c, 0x37, 0x72, 0xf8, + 0x9c, 0x48, 0x6a, 0x3d, 0x8a, 0xb0, 0x4e, 0x39, 0x53, 0x58, 0x56, 0x06, 0x57, 0x66, 0x62, 0xc5, 0x63, 0xa2, 0x65, + 0xe6, 0x6b, 0x4e, 0x6d, 0xe1, 0x6e, 0x5b, 0x70, 0xad, 0x77, 0xed, 0x7a, 0xef, 0x7b, 0xaa, 0x7d, 0xbb, 0x80, 0x3d, + 0x80, 0xc6, 0x86, 0xcb, 0x8a, 0x95, 0x93, 0x5b, 0x56, 0xe3, 0x58, 0xc7, 0x5f, 0x3e, 0x65, 0xad, 0x66, 0x96, 0x6a, + 0x80, 0x6b, 0xb5, 0x75, 0x37, 0x8a, 0xc7, 0x50, 0x24, 0x77, 0xe5, 0x57, 0x30, 0x5f, 0x1b, 0x60, 0x65, 0x66, 0x7a, + 0x6c, 0x60, 0x75, 0xf4, 0x7a, 0x1a, 0x7f, 0x6e, 0x81, 0xf4, 0x87, 0x18, 0x90, 0x45, 0x99, 0xb3, 0x7b, 0xc9, 0x75, + 0x5c, 0x7a, 0xf9, 0x7b, 0x51, 0x84, 0xc4, 0x00, 0x20, 0x90, 0x10, 0x79, 0xe9, 0x7a, 0x92, 0x83, 0x36, 0x5a, 0xe1, + 0x77, 0x40, 0x4e, 0x2d, 0x4e, 0xf2, 0x5b, 0x99, 0x5f, 0xe0, 0x62, 0xbd, 0x66, 0x3c, 0x67, 0xf1, 0x6c, 0xe8, 0x86, + 0x6b, 0x88, 0x77, 0x8a, 0x3b, 0x91, 0x4e, 0x92, 0xf3, 0x99, 0xd0, 0x6a, 0x17, 0x70, 0x26, 0x73, 0x2a, 0x82, 0xe7, + 0x84, 0x57, 0x8c, 0xaf, 0x4e, 0x01, 0x51, 0x46, 0x51, 0xcb, 0x55, 0x8b, 0x5b, 0xf5, 0x5e, 0x16, 0x5e, 0x33, 0x5e, + 0x81, 0x5f, 0x14, 0x5f, 0x35, 0x5f, 0x6b, 0x5f, 0xb4, 0x61, 0xf2, 0x63, 0x11, 0x66, 0xa2, 0x67, 0x1d, 0x6f, 0x6e, + 0x72, 0x52, 0x75, 0x3a, 0x77, 0x3a, 0x80, 0x74, 0x81, 0x39, 0x81, 0x78, 0x87, 0x76, 0x8a, 0xbf, 0x8a, 0xdc, 0x8d, + 0x85, 0x8d, 0xf3, 0x92, 0x9a, 0x95, 0x77, 0x98, 0x02, 0x9c, 0xe5, 0x52, 0xc5, 0x63, 0x57, 0x76, 0xf4, 0x67, 0x15, + 0x6c, 0x88, 0x73, 0xcd, 0x8c, 0xc3, 0x93, 0xae, 0x96, 0x73, 0x6d, 0x25, 0x58, 0x9c, 0x69, 0x0e, 0x69, 0xcc, 0x8f, + 0xfd, 0x93, 0x9a, 0x75, 0xdb, 0x90, 0x1a, 0x58, 0x5a, 0x68, 0x02, 0x63, 0xb4, 0x69, 0xfb, 0x4f, 0x43, 0x6f, 0x2c, + 0x67, 0xd8, 0x8f, 0xbb, 0x85, 0x26, 0x7d, 0xb4, 0x93, 0x54, 0x69, 0x3f, 0x6f, 0x70, 0x57, 0x6a, 0x58, 0xf7, 0x5b, + 0x2c, 0x7d, 0x2c, 0x72, 0x2a, 0x54, 0x0a, 0x91, 0xe3, 0x9d, 0xb4, 0x4e, 0xad, 0x4f, 0x4e, 0x50, 0x5c, 0x50, 0x75, + 0x52, 0x43, 0x8c, 0x9e, 0x54, 0x48, 0x58, 0x24, 0x5b, 0x9a, 0x5e, 0x1d, 0x5e, 0x95, 0x5e, 0xad, 0x5e, 0xf7, 0x5f, + 0x1f, 0x60, 0x8c, 0x62, 0xb5, 0x63, 0x3a, 0x63, 0xd0, 0x68, 0xaf, 0x6c, 0x40, 0x78, 0x87, 0x79, 0x8e, 0x7a, 0x0b, + 0x7d, 0xe0, 0x82, 0x47, 0x8a, 0x02, 0x8a, 0xe6, 0x8e, 0x44, 0x90, 0x13, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x90, 0xb8, 0x91, 0x2d, 0x91, 0xd8, + 0x9f, 0x0e, 0x6c, 0xe5, 0x64, 0x58, 0x64, 0xe2, 0x65, 0x75, 0x6e, 0xf4, 0x76, 0x84, 0x7b, 0x1b, 0x90, 0x69, 0x93, + 0xd1, 0x6e, 0xba, 0x54, 0xf2, 0x5f, 0xb9, 0x64, 0xa4, 0x8f, 0x4d, 0x8f, 0xed, 0x92, 0x44, 0x51, 0x78, 0x58, 0x6b, + 0x59, 0x29, 0x5c, 0x55, 0x5e, 0x97, 0x6d, 0xfb, 0x7e, 0x8f, 0x75, 0x1c, 0x8c, 0xbc, 0x8e, 0xe2, 0x98, 0x5b, 0x70, + 0xb9, 0x4f, 0x1d, 0x6b, 0xbf, 0x6f, 0xb1, 0x75, 0x30, 0x96, 0xfb, 0x51, 0x4e, 0x54, 0x10, 0x58, 0x35, 0x58, 0x57, + 0x59, 0xac, 0x5c, 0x60, 0x5f, 0x92, 0x65, 0x97, 0x67, 0x5c, 0x6e, 0x21, 0x76, 0x7b, 0x83, 0xdf, 0x8c, 0xed, 0x90, + 0x14, 0x90, 0xfd, 0x93, 0x4d, 0x78, 0x25, 0x78, 0x3a, 0x52, 0xaa, 0x5e, 0xa6, 0x57, 0x1f, 0x59, 0x74, 0x60, 0x12, + 0x50, 0x12, 0x51, 0x5a, 0x51, 0xac, 0x00, 0x20, 0x51, 0xcd, 0x52, 0x00, 0x55, 0x10, 0x58, 0x54, 0x58, 0x58, 0x59, + 0x57, 0x5b, 0x95, 0x5c, 0xf6, 0x5d, 0x8b, 0x60, 0xbc, 0x62, 0x95, 0x64, 0x2d, 0x67, 0x71, 0x68, 0x43, 0x68, 0xbc, + 0x68, 0xdf, 0x76, 0xd7, 0x6d, 0xd8, 0x6e, 0x6f, 0x6d, 0x9b, 0x70, 0x6f, 0x71, 0xc8, 0x5f, 0x53, 0x75, 0xd8, 0x79, + 0x77, 0x7b, 0x49, 0x7b, 0x54, 0x7b, 0x52, 0x7c, 0xd6, 0x7d, 0x71, 0x52, 0x30, 0x84, 0x63, 0x85, 0x69, 0x85, 0xe4, + 0x8a, 0x0e, 0x8b, 0x04, 0x8c, 0x46, 0x8e, 0x0f, 0x90, 0x03, 0x90, 0x0f, 0x94, 0x19, 0x96, 0x76, 0x98, 0x2d, 0x9a, + 0x30, 0x95, 0xd8, 0x50, 0xcd, 0x52, 0xd5, 0x54, 0x0c, 0x58, 0x02, 0x5c, 0x0e, 0x61, 0xa7, 0x64, 0x9e, 0x6d, 0x1e, + 0x77, 0xb3, 0x7a, 0xe5, 0x80, 0xf4, 0x84, 0x04, 0x90, 0x53, 0x92, 0x85, 0x5c, 0xe0, 0x9d, 0x07, 0x53, 0x3f, 0x5f, + 0x97, 0x5f, 0xb3, 0x6d, 0x9c, 0x72, 0x79, 0x77, 0x63, 0x79, 0xbf, 0x7b, 0xe4, 0x6b, 0xd2, 0x72, 0xec, 0x8a, 0xad, + 0x68, 0x03, 0x6a, 0x61, 0x51, 0xf8, 0x7a, 0x81, 0x69, 0x34, 0x5c, 0x4a, 0x9c, 0xf6, 0x82, 0xeb, 0x5b, 0xc5, 0x91, + 0x49, 0x70, 0x1e, 0x56, 0x78, 0x5c, 0x6f, 0x60, 0xc7, 0x65, 0x66, 0x6c, 0x8c, 0x8c, 0x5a, 0x90, 0x41, 0x98, 0x13, + 0x54, 0x51, 0x66, 0xc7, 0x92, 0x0d, 0x59, 0x48, 0x90, 0xa3, 0x51, 0x85, 0x4e, 0x4d, 0x51, 0xea, 0x85, 0x99, 0x8b, + 0x0e, 0x70, 0x58, 0x63, 0x7a, 0x93, 0x4b, 0x69, 0x62, 0x99, 0xb4, 0x7e, 0x04, 0x75, 0x77, 0x53, 0x57, 0x69, 0x60, + 0x8e, 0xdf, 0x96, 0xe3, 0x6c, 0x5d, 0x4e, 0x8c, 0x5c, 0x3c, 0x5f, 0x10, 0x8f, 0xe9, 0x53, 0x02, 0x8c, 0xd1, 0x80, + 0x89, 0x86, 0x79, 0x5e, 0xff, 0x65, 0xe5, 0x4e, 0x73, 0x51, 0x65, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x59, 0x82, 0x5c, 0x3f, 0x97, 0xee, 0x4e, + 0xfb, 0x59, 0x8a, 0x5f, 0xcd, 0x8a, 0x8d, 0x6f, 0xe1, 0x79, 0xb0, 0x79, 0x62, 0x5b, 0xe7, 0x84, 0x71, 0x73, 0x2b, + 0x71, 0xb1, 0x5e, 0x74, 0x5f, 0xf5, 0x63, 0x7b, 0x64, 0x9a, 0x71, 0xc3, 0x7c, 0x98, 0x4e, 0x43, 0x5e, 0xfc, 0x4e, + 0x4b, 0x57, 0xdc, 0x56, 0xa2, 0x60, 0xa9, 0x6f, 0xc3, 0x7d, 0x0d, 0x80, 0xfd, 0x81, 0x33, 0x81, 0xbf, 0x8f, 0xb2, + 0x89, 0x97, 0x86, 0xa4, 0x5d, 0xf4, 0x62, 0x8a, 0x64, 0xad, 0x89, 0x87, 0x67, 0x77, 0x6c, 0xe2, 0x6d, 0x3e, 0x74, + 0x36, 0x78, 0x34, 0x5a, 0x46, 0x7f, 0x75, 0x82, 0xad, 0x99, 0xac, 0x4f, 0xf3, 0x5e, 0xc3, 0x62, 0xdd, 0x63, 0x92, + 0x65, 0x57, 0x67, 0x6f, 0x76, 0xc3, 0x72, 0x4c, 0x80, 0xcc, 0x80, 0xba, 0x8f, 0x29, 0x91, 0x4d, 0x50, 0x0d, 0x57, + 0xf9, 0x5a, 0x92, 0x68, 0x85, 0x00, 0x20, 0x69, 0x73, 0x71, 0x64, 0x72, 0xfd, 0x8c, 0xb7, 0x58, 0xf2, 0x8c, 0xe0, + 0x96, 0x6a, 0x90, 0x19, 0x87, 0x7f, 0x79, 0xe4, 0x77, 0xe7, 0x84, 0x29, 0x4f, 0x2f, 0x52, 0x65, 0x53, 0x5a, 0x62, + 0xcd, 0x67, 0xcf, 0x6c, 0xca, 0x76, 0x7d, 0x7b, 0x94, 0x7c, 0x95, 0x82, 0x36, 0x85, 0x84, 0x8f, 0xeb, 0x66, 0xdd, + 0x6f, 0x20, 0x72, 0x06, 0x7e, 0x1b, 0x83, 0xab, 0x99, 0xc1, 0x9e, 0xa6, 0x51, 0xfd, 0x7b, 0xb1, 0x78, 0x72, 0x7b, + 0xb8, 0x80, 0x87, 0x7b, 0x48, 0x6a, 0xe8, 0x5e, 0x61, 0x80, 0x8c, 0x75, 0x51, 0x75, 0x60, 0x51, 0x6b, 0x92, 0x62, + 0x6e, 0x8c, 0x76, 0x7a, 0x91, 0x97, 0x9a, 0xea, 0x4f, 0x10, 0x7f, 0x70, 0x62, 0x9c, 0x7b, 0x4f, 0x95, 0xa5, 0x9c, + 0xe9, 0x56, 0x7a, 0x58, 0x59, 0x86, 0xe4, 0x96, 0xbc, 0x4f, 0x34, 0x52, 0x24, 0x53, 0x4a, 0x53, 0xcd, 0x53, 0xdb, + 0x5e, 0x06, 0x64, 0x2c, 0x65, 0x91, 0x67, 0x7f, 0x6c, 0x3e, 0x6c, 0x4e, 0x72, 0x48, 0x72, 0xaf, 0x73, 0xed, 0x75, + 0x54, 0x7e, 0x41, 0x82, 0x2c, 0x85, 0xe9, 0x8c, 0xa9, 0x7b, 0xc4, 0x91, 0xc6, 0x71, 0x69, 0x98, 0x12, 0x98, 0xef, + 0x63, 0x3d, 0x66, 0x69, 0x75, 0x6a, 0x76, 0xe4, 0x78, 0xd0, 0x85, 0x43, 0x86, 0xee, 0x53, 0x2a, 0x53, 0x51, 0x54, + 0x26, 0x59, 0x83, 0x5e, 0x87, 0x5f, 0x7c, 0x60, 0xb2, 0x62, 0x49, 0x62, 0x79, 0x62, 0xab, 0x65, 0x90, 0x6b, 0xd4, + 0x6c, 0xcc, 0x75, 0xb2, 0x76, 0xae, 0x78, 0x91, 0x79, 0xd8, 0x7d, 0xcb, 0x7f, 0x77, 0x80, 0xa5, 0x88, 0xab, 0x8a, + 0xb9, 0x8c, 0xbb, 0x90, 0x7f, 0x97, 0x5e, 0x98, 0xdb, 0x6a, 0x0b, 0x7c, 0x38, 0x50, 0x99, 0x5c, 0x3e, 0x5f, 0xae, + 0x67, 0x87, 0x6b, 0xd8, 0x74, 0x35, 0x77, 0x09, 0x7f, 0x8e, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x9f, 0x3b, 0x67, 0xca, 0x7a, 0x17, 0x53, 0x39, + 0x75, 0x8b, 0x9a, 0xed, 0x5f, 0x66, 0x81, 0x9d, 0x83, 0xf1, 0x80, 0x98, 0x5f, 0x3c, 0x5f, 0xc5, 0x75, 0x62, 0x7b, + 0x46, 0x90, 0x3c, 0x68, 0x67, 0x59, 0xeb, 0x5a, 0x9b, 0x7d, 0x10, 0x76, 0x7e, 0x8b, 0x2c, 0x4f, 0xf5, 0x5f, 0x6a, + 0x6a, 0x19, 0x6c, 0x37, 0x6f, 0x02, 0x74, 0xe2, 0x79, 0x68, 0x88, 0x68, 0x8a, 0x55, 0x8c, 0x79, 0x5e, 0xdf, 0x63, + 0xcf, 0x75, 0xc5, 0x79, 0xd2, 0x82, 0xd7, 0x93, 0x28, 0x92, 0xf2, 0x84, 0x9c, 0x86, 0xed, 0x9c, 0x2d, 0x54, 0xc1, + 0x5f, 0x6c, 0x65, 0x8c, 0x6d, 0x5c, 0x70, 0x15, 0x8c, 0xa7, 0x8c, 0xd3, 0x98, 0x3b, 0x65, 0x4f, 0x74, 0xf6, 0x4e, + 0x0d, 0x4e, 0xd8, 0x57, 0xe0, 0x59, 0x2b, 0x5a, 0x66, 0x5b, 0xcc, 0x51, 0xa8, 0x5e, 0x03, 0x5e, 0x9c, 0x60, 0x16, + 0x62, 0x76, 0x65, 0x77, 0x00, 0x20, 0x65, 0xa7, 0x66, 0x6e, 0x6d, 0x6e, 0x72, 0x36, 0x7b, 0x26, 0x81, 0x50, 0x81, + 0x9a, 0x82, 0x99, 0x8b, 0x5c, 0x8c, 0xa0, 0x8c, 0xe6, 0x8d, 0x74, 0x96, 0x1c, 0x96, 0x44, 0x4f, 0xae, 0x64, 0xab, + 0x6b, 0x66, 0x82, 0x1e, 0x84, 0x61, 0x85, 0x6a, 0x90, 0xe8, 0x5c, 0x01, 0x69, 0x53, 0x98, 0xa8, 0x84, 0x7a, 0x85, + 0x57, 0x4f, 0x0f, 0x52, 0x6f, 0x5f, 0xa9, 0x5e, 0x45, 0x67, 0x0d, 0x79, 0x8f, 0x81, 0x79, 0x89, 0x07, 0x89, 0x86, + 0x6d, 0xf5, 0x5f, 0x17, 0x62, 0x55, 0x6c, 0xb8, 0x4e, 0xcf, 0x72, 0x69, 0x9b, 0x92, 0x52, 0x06, 0x54, 0x3b, 0x56, + 0x74, 0x58, 0xb3, 0x61, 0xa4, 0x62, 0x6e, 0x71, 0x1a, 0x59, 0x6e, 0x7c, 0x89, 0x7c, 0xde, 0x7d, 0x1b, 0x96, 0xf0, + 0x65, 0x87, 0x80, 0x5e, 0x4e, 0x19, 0x4f, 0x75, 0x51, 0x75, 0x58, 0x40, 0x5e, 0x63, 0x5e, 0x73, 0x5f, 0x0a, 0x67, + 0xc4, 0x4e, 0x26, 0x85, 0x3d, 0x95, 0x89, 0x96, 0x5b, 0x7c, 0x73, 0x98, 0x01, 0x50, 0xfb, 0x58, 0xc1, 0x76, 0x56, + 0x78, 0xa7, 0x52, 0x25, 0x77, 0xa5, 0x85, 0x11, 0x7b, 0x86, 0x50, 0x4f, 0x59, 0x09, 0x72, 0x47, 0x7b, 0xc7, 0x7d, + 0xe8, 0x8f, 0xba, 0x8f, 0xd4, 0x90, 0x4d, 0x4f, 0xbf, 0x52, 0xc9, 0x5a, 0x29, 0x5f, 0x01, 0x97, 0xad, 0x4f, 0xdd, + 0x82, 0x17, 0x92, 0xea, 0x57, 0x03, 0x63, 0x55, 0x6b, 0x69, 0x75, 0x2b, 0x88, 0xdc, 0x8f, 0x14, 0x7a, 0x42, 0x52, + 0xdf, 0x58, 0x93, 0x61, 0x55, 0x62, 0x0a, 0x66, 0xae, 0x6b, 0xcd, 0x7c, 0x3f, 0x83, 0xe9, 0x50, 0x23, 0x4f, 0xf8, + 0x53, 0x05, 0x54, 0x46, 0x58, 0x31, 0x59, 0x49, 0x5b, 0x9d, 0x5c, 0xf0, 0x5c, 0xef, 0x5d, 0x29, 0x5e, 0x96, 0x62, + 0xb1, 0x63, 0x67, 0x65, 0x3e, 0x65, 0xb9, 0x67, 0x0b, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x6c, 0xd5, 0x6c, 0xe1, 0x70, 0xf9, 0x78, 0x32, 0x7e, + 0x2b, 0x80, 0xde, 0x82, 0xb3, 0x84, 0x0c, 0x84, 0xec, 0x87, 0x02, 0x89, 0x12, 0x8a, 0x2a, 0x8c, 0x4a, 0x90, 0xa6, + 0x92, 0xd2, 0x98, 0xfd, 0x9c, 0xf3, 0x9d, 0x6c, 0x4e, 0x4f, 0x4e, 0xa1, 0x50, 0x8d, 0x52, 0x56, 0x57, 0x4a, 0x59, + 0xa8, 0x5e, 0x3d, 0x5f, 0xd8, 0x5f, 0xd9, 0x62, 0x3f, 0x66, 0xb4, 0x67, 0x1b, 0x67, 0xd0, 0x68, 0xd2, 0x51, 0x92, + 0x7d, 0x21, 0x80, 0xaa, 0x81, 0xa8, 0x8b, 0x00, 0x8c, 0x8c, 0x8c, 0xbf, 0x92, 0x7e, 0x96, 0x32, 0x54, 0x20, 0x98, + 0x2c, 0x53, 0x17, 0x50, 0xd5, 0x53, 0x5c, 0x58, 0xa8, 0x64, 0xb2, 0x67, 0x34, 0x72, 0x67, 0x77, 0x66, 0x7a, 0x46, + 0x91, 0xe6, 0x52, 0xc3, 0x6c, 0xa1, 0x6b, 0x86, 0x58, 0x00, 0x5e, 0x4c, 0x59, 0x54, 0x67, 0x2c, 0x7f, 0xfb, 0x51, + 0xe1, 0x76, 0xc6, 0x00, 0x20, 0x64, 0x69, 0x78, 0xe8, 0x9b, 0x54, 0x9e, 0xbb, 0x57, 0xcb, 0x59, 0xb9, 0x66, 0x27, + 0x67, 0x9a, 0x6b, 0xce, 0x54, 0xe9, 0x69, 0xd9, 0x5e, 0x55, 0x81, 0x9c, 0x67, 0x95, 0x9b, 0xaa, 0x67, 0xfe, 0x9c, + 0x52, 0x68, 0x5d, 0x4e, 0xa6, 0x4f, 0xe3, 0x53, 0xc8, 0x62, 0xb9, 0x67, 0x2b, 0x6c, 0xab, 0x8f, 0xc4, 0x4f, 0xad, + 0x7e, 0x6d, 0x9e, 0xbf, 0x4e, 0x07, 0x61, 0x62, 0x6e, 0x80, 0x6f, 0x2b, 0x85, 0x13, 0x54, 0x73, 0x67, 0x2a, 0x9b, + 0x45, 0x5d, 0xf3, 0x7b, 0x95, 0x5c, 0xac, 0x5b, 0xc6, 0x87, 0x1c, 0x6e, 0x4a, 0x84, 0xd1, 0x7a, 0x14, 0x81, 0x08, + 0x59, 0x99, 0x7c, 0x8d, 0x6c, 0x11, 0x77, 0x20, 0x52, 0xd9, 0x59, 0x22, 0x71, 0x21, 0x72, 0x5f, 0x77, 0xdb, 0x97, + 0x27, 0x9d, 0x61, 0x69, 0x0b, 0x5a, 0x7f, 0x5a, 0x18, 0x51, 0xa5, 0x54, 0x0d, 0x54, 0x7d, 0x66, 0x0e, 0x76, 0xdf, + 0x8f, 0xf7, 0x92, 0x98, 0x9c, 0xf4, 0x59, 0xea, 0x72, 0x5d, 0x6e, 0xc5, 0x51, 0x4d, 0x68, 0xc9, 0x7d, 0xbf, 0x7d, + 0xec, 0x97, 0x62, 0x9e, 0xba, 0x64, 0x78, 0x6a, 0x21, 0x83, 0x02, 0x59, 0x84, 0x5b, 0x5f, 0x6b, 0xdb, 0x73, 0x1b, + 0x76, 0xf2, 0x7d, 0xb2, 0x80, 0x17, 0x84, 0x99, 0x51, 0x32, 0x67, 0x28, 0x9e, 0xd9, 0x76, 0xee, 0x67, 0x62, 0x52, + 0xff, 0x99, 0x05, 0x5c, 0x24, 0x62, 0x3b, 0x7c, 0x7e, 0x8c, 0xb0, 0x55, 0x4f, 0x60, 0xb6, 0x7d, 0x0b, 0x95, 0x80, + 0x53, 0x01, 0x4e, 0x5f, 0x51, 0xb6, 0x59, 0x1c, 0x72, 0x3a, 0x80, 0x36, 0x91, 0xce, 0x5f, 0x25, 0x77, 0xe2, 0x53, + 0x84, 0x5f, 0x79, 0x7d, 0x04, 0x85, 0xac, 0x8a, 0x33, 0x8e, 0x8d, 0x97, 0x56, 0x67, 0xf3, 0x85, 0xae, 0x94, 0x53, + 0x61, 0x09, 0x61, 0x08, 0x6c, 0xb9, 0x76, 0x52, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x8a, 0xed, 0x8f, 0x38, 0x55, 0x2f, 0x4f, 0x51, 0x51, 0x2a, + 0x52, 0xc7, 0x53, 0xcb, 0x5b, 0xa5, 0x5e, 0x7d, 0x60, 0xa0, 0x61, 0x82, 0x63, 0xd6, 0x67, 0x09, 0x67, 0xda, 0x6e, + 0x67, 0x6d, 0x8c, 0x73, 0x36, 0x73, 0x37, 0x75, 0x31, 0x79, 0x50, 0x88, 0xd5, 0x8a, 0x98, 0x90, 0x4a, 0x90, 0x91, + 0x90, 0xf5, 0x96, 0xc4, 0x87, 0x8d, 0x59, 0x15, 0x4e, 0x88, 0x4f, 0x59, 0x4e, 0x0e, 0x8a, 0x89, 0x8f, 0x3f, 0x98, + 0x10, 0x50, 0xad, 0x5e, 0x7c, 0x59, 0x96, 0x5b, 0xb9, 0x5e, 0xb8, 0x63, 0xda, 0x63, 0xfa, 0x64, 0xc1, 0x66, 0xdc, + 0x69, 0x4a, 0x69, 0xd8, 0x6d, 0x0b, 0x6e, 0xb6, 0x71, 0x94, 0x75, 0x28, 0x7a, 0xaf, 0x7f, 0x8a, 0x80, 0x00, 0x84, + 0x49, 0x84, 0xc9, 0x89, 0x81, 0x8b, 0x21, 0x8e, 0x0a, 0x90, 0x65, 0x96, 0x7d, 0x99, 0x0a, 0x61, 0x7e, 0x62, 0x91, + 0x6b, 0x32, 0x00, 0x20, 0x6c, 0x83, 0x6d, 0x74, 0x7f, 0xcc, 0x7f, 0xfc, 0x6d, 0xc0, 0x7f, 0x85, 0x87, 0xba, 0x88, + 0xf8, 0x67, 0x65, 0x83, 0xb1, 0x98, 0x3c, 0x96, 0xf7, 0x6d, 0x1b, 0x7d, 0x61, 0x84, 0x3d, 0x91, 0x6a, 0x4e, 0x71, + 0x53, 0x75, 0x5d, 0x50, 0x6b, 0x04, 0x6f, 0xeb, 0x85, 0xcd, 0x86, 0x2d, 0x89, 0xa7, 0x52, 0x29, 0x54, 0x0f, 0x5c, + 0x65, 0x67, 0x4e, 0x68, 0xa8, 0x74, 0x06, 0x74, 0x83, 0x75, 0xe2, 0x88, 0xcf, 0x88, 0xe1, 0x91, 0xcc, 0x96, 0xe2, + 0x96, 0x78, 0x5f, 0x8b, 0x73, 0x87, 0x7a, 0xcb, 0x84, 0x4e, 0x63, 0xa0, 0x75, 0x65, 0x52, 0x89, 0x6d, 0x41, 0x6e, + 0x9c, 0x74, 0x09, 0x75, 0x59, 0x78, 0x6b, 0x7c, 0x92, 0x96, 0x86, 0x7a, 0xdc, 0x9f, 0x8d, 0x4f, 0xb6, 0x61, 0x6e, + 0x65, 0xc5, 0x86, 0x5c, 0x4e, 0x86, 0x4e, 0xae, 0x50, 0xda, 0x4e, 0x21, 0x51, 0xcc, 0x5b, 0xee, 0x65, 0x99, 0x68, + 0x81, 0x6d, 0xbc, 0x73, 0x1f, 0x76, 0x42, 0x77, 0xad, 0x7a, 0x1c, 0x7c, 0xe7, 0x82, 0x6f, 0x8a, 0xd2, 0x90, 0x7c, + 0x91, 0xcf, 0x96, 0x75, 0x98, 0x18, 0x52, 0x9b, 0x7d, 0xd1, 0x50, 0x2b, 0x53, 0x98, 0x67, 0x97, 0x6d, 0xcb, 0x71, + 0xd0, 0x74, 0x33, 0x81, 0xe8, 0x8f, 0x2a, 0x96, 0xa3, 0x9c, 0x57, 0x9e, 0x9f, 0x74, 0x60, 0x58, 0x41, 0x6d, 0x99, + 0x7d, 0x2f, 0x98, 0x5e, 0x4e, 0xe4, 0x4f, 0x36, 0x4f, 0x8b, 0x51, 0xb7, 0x52, 0xb1, 0x5d, 0xba, 0x60, 0x1c, 0x73, + 0xb2, 0x79, 0x3c, 0x82, 0xd3, 0x92, 0x34, 0x96, 0xb7, 0x96, 0xf6, 0x97, 0x0a, 0x9e, 0x97, 0x9f, 0x62, 0x66, 0xa6, + 0x6b, 0x74, 0x52, 0x17, 0x52, 0xa3, 0x70, 0xc8, 0x88, 0xc2, 0x5e, 0xc9, 0x60, 0x4b, 0x61, 0x90, 0x6f, 0x23, 0x71, + 0x49, 0x7c, 0x3e, 0x7d, 0xf4, 0x80, 0x6f, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x84, 0xee, 0x90, 0x23, 0x93, 0x2c, 0x54, 0x42, 0x9b, 0x6f, 0x6a, + 0xd3, 0x70, 0x89, 0x8c, 0xc2, 0x8d, 0xef, 0x97, 0x32, 0x52, 0xb4, 0x5a, 0x41, 0x5e, 0xca, 0x5f, 0x04, 0x67, 0x17, + 0x69, 0x7c, 0x69, 0x94, 0x6d, 0x6a, 0x6f, 0x0f, 0x72, 0x62, 0x72, 0xfc, 0x7b, 0xed, 0x80, 0x01, 0x80, 0x7e, 0x87, + 0x4b, 0x90, 0xce, 0x51, 0x6d, 0x9e, 0x93, 0x79, 0x84, 0x80, 0x8b, 0x93, 0x32, 0x8a, 0xd6, 0x50, 0x2d, 0x54, 0x8c, + 0x8a, 0x71, 0x6b, 0x6a, 0x8c, 0xc4, 0x81, 0x07, 0x60, 0xd1, 0x67, 0xa0, 0x9d, 0xf2, 0x4e, 0x99, 0x4e, 0x98, 0x9c, + 0x10, 0x8a, 0x6b, 0x85, 0xc1, 0x85, 0x68, 0x69, 0x00, 0x6e, 0x7e, 0x78, 0x97, 0x81, 0x55, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x5f, 0x0c, 0x4e, 0x10, 0x4e, 0x15, 0x4e, 0x2a, 0x4e, 0x31, 0x4e, + 0x36, 0x4e, 0x3c, 0x4e, 0x3f, 0x4e, 0x42, 0x4e, 0x56, 0x4e, 0x58, 0x4e, 0x82, 0x4e, 0x85, 0x8c, 0x6b, 0x4e, 0x8a, + 0x82, 0x12, 0x5f, 0x0d, 0x4e, 0x8e, 0x4e, 0x9e, 0x4e, 0x9f, 0x4e, 0xa0, 0x4e, 0xa2, 0x4e, 0xb0, 0x4e, 0xb3, 0x4e, + 0xb6, 0x4e, 0xce, 0x4e, 0xcd, 0x4e, 0xc4, 0x4e, 0xc6, 0x4e, 0xc2, 0x4e, 0xd7, 0x4e, 0xde, 0x4e, 0xed, 0x4e, 0xdf, + 0x4e, 0xf7, 0x4f, 0x09, 0x4f, 0x5a, 0x4f, 0x30, 0x4f, 0x5b, 0x4f, 0x5d, 0x4f, 0x57, 0x4f, 0x47, 0x4f, 0x76, 0x4f, + 0x88, 0x4f, 0x8f, 0x4f, 0x98, 0x4f, 0x7b, 0x4f, 0x69, 0x4f, 0x70, 0x4f, 0x91, 0x4f, 0x6f, 0x4f, 0x86, 0x4f, 0x96, + 0x51, 0x18, 0x4f, 0xd4, 0x4f, 0xdf, 0x4f, 0xce, 0x4f, 0xd8, 0x4f, 0xdb, 0x4f, 0xd1, 0x4f, 0xda, 0x4f, 0xd0, 0x4f, + 0xe4, 0x4f, 0xe5, 0x50, 0x1a, 0x50, 0x28, 0x50, 0x14, 0x50, 0x2a, 0x50, 0x25, 0x50, 0x05, 0x4f, 0x1c, 0x4f, 0xf6, + 0x50, 0x21, 0x50, 0x29, 0x50, 0x2c, 0x4f, 0xfe, 0x4f, 0xef, 0x50, 0x11, 0x50, 0x06, 0x50, 0x43, 0x50, 0x47, 0x67, + 0x03, 0x50, 0x55, 0x50, 0x50, 0x50, 0x48, 0x50, 0x5a, 0x50, 0x56, 0x50, 0x6c, 0x50, 0x78, 0x50, 0x80, 0x50, 0x9a, + 0x50, 0x85, 0x50, 0xb4, 0x50, 0xb2, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x50, 0xc9, 0x50, 0xca, 0x50, 0xb3, 0x50, 0xc2, 0x50, 0xd6, 0x50, 0xde, + 0x50, 0xe5, 0x50, 0xed, 0x50, 0xe3, 0x50, 0xee, 0x50, 0xf9, 0x50, 0xf5, 0x51, 0x09, 0x51, 0x01, 0x51, 0x02, 0x51, + 0x16, 0x51, 0x15, 0x51, 0x14, 0x51, 0x1a, 0x51, 0x21, 0x51, 0x3a, 0x51, 0x37, 0x51, 0x3c, 0x51, 0x3b, 0x51, 0x3f, + 0x51, 0x40, 0x51, 0x52, 0x51, 0x4c, 0x51, 0x54, 0x51, 0x62, 0x7a, 0xf8, 0x51, 0x69, 0x51, 0x6a, 0x51, 0x6e, 0x51, + 0x80, 0x51, 0x82, 0x56, 0xd8, 0x51, 0x8c, 0x51, 0x89, 0x51, 0x8f, 0x51, 0x91, 0x51, 0x93, 0x51, 0x95, 0x51, 0x96, + 0x51, 0xa4, 0x51, 0xa6, 0x51, 0xa2, 0x51, 0xa9, 0x51, 0xaa, 0x51, 0xab, 0x51, 0xb3, 0x51, 0xb1, 0x51, 0xb2, 0x51, + 0xb0, 0x51, 0xb5, 0x51, 0xbd, 0x51, 0xc5, 0x51, 0xc9, 0x51, 0xdb, 0x51, 0xe0, 0x86, 0x55, 0x51, 0xe9, 0x51, 0xed, + 0x00, 0x20, 0x51, 0xf0, 0x51, 0xf5, 0x51, 0xfe, 0x52, 0x04, 0x52, 0x0b, 0x52, 0x14, 0x52, 0x0e, 0x52, 0x27, 0x52, + 0x2a, 0x52, 0x2e, 0x52, 0x33, 0x52, 0x39, 0x52, 0x4f, 0x52, 0x44, 0x52, 0x4b, 0x52, 0x4c, 0x52, 0x5e, 0x52, 0x54, + 0x52, 0x6a, 0x52, 0x74, 0x52, 0x69, 0x52, 0x73, 0x52, 0x7f, 0x52, 0x7d, 0x52, 0x8d, 0x52, 0x94, 0x52, 0x92, 0x52, + 0x71, 0x52, 0x88, 0x52, 0x91, 0x8f, 0xa8, 0x8f, 0xa7, 0x52, 0xac, 0x52, 0xad, 0x52, 0xbc, 0x52, 0xb5, 0x52, 0xc1, + 0x52, 0xcd, 0x52, 0xd7, 0x52, 0xde, 0x52, 0xe3, 0x52, 0xe6, 0x98, 0xed, 0x52, 0xe0, 0x52, 0xf3, 0x52, 0xf5, 0x52, + 0xf8, 0x52, 0xf9, 0x53, 0x06, 0x53, 0x08, 0x75, 0x38, 0x53, 0x0d, 0x53, 0x10, 0x53, 0x0f, 0x53, 0x15, 0x53, 0x1a, + 0x53, 0x23, 0x53, 0x2f, 0x53, 0x31, 0x53, 0x33, 0x53, 0x38, 0x53, 0x40, 0x53, 0x46, 0x53, 0x45, 0x4e, 0x17, 0x53, + 0x49, 0x53, 0x4d, 0x51, 0xd6, 0x53, 0x5e, 0x53, 0x69, 0x53, 0x6e, 0x59, 0x18, 0x53, 0x7b, 0x53, 0x77, 0x53, 0x82, + 0x53, 0x96, 0x53, 0xa0, 0x53, 0xa6, 0x53, 0xa5, 0x53, 0xae, 0x53, 0xb0, 0x53, 0xb6, 0x53, 0xc3, 0x7c, 0x12, 0x96, + 0xd9, 0x53, 0xdf, 0x66, 0xfc, 0x71, 0xee, 0x53, 0xee, 0x53, 0xe8, 0x53, 0xed, 0x53, 0xfa, 0x54, 0x01, 0x54, 0x3d, + 0x54, 0x40, 0x54, 0x2c, 0x54, 0x2d, 0x54, 0x3c, 0x54, 0x2e, 0x54, 0x36, 0x54, 0x29, 0x54, 0x1d, 0x54, 0x4e, 0x54, + 0x8f, 0x54, 0x75, 0x54, 0x8e, 0x54, 0x5f, 0x54, 0x71, 0x54, 0x77, 0x54, 0x70, 0x54, 0x92, 0x54, 0x7b, 0x54, 0x80, + 0x54, 0x76, 0x54, 0x84, 0x54, 0x90, 0x54, 0x86, 0x54, 0xc7, 0x54, 0xa2, 0x54, 0xb8, 0x54, 0xa5, 0x54, 0xac, 0x54, + 0xc4, 0x54, 0xc8, 0x54, 0xa8, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x54, 0xab, 0x54, 0xc2, 0x54, 0xa4, 0x54, 0xbe, 0x54, 0xbc, 0x54, 0xd8, 0x54, + 0xe5, 0x54, 0xe6, 0x55, 0x0f, 0x55, 0x14, 0x54, 0xfd, 0x54, 0xee, 0x54, 0xed, 0x54, 0xfa, 0x54, 0xe2, 0x55, 0x39, + 0x55, 0x40, 0x55, 0x63, 0x55, 0x4c, 0x55, 0x2e, 0x55, 0x5c, 0x55, 0x45, 0x55, 0x56, 0x55, 0x57, 0x55, 0x38, 0x55, + 0x33, 0x55, 0x5d, 0x55, 0x99, 0x55, 0x80, 0x54, 0xaf, 0x55, 0x8a, 0x55, 0x9f, 0x55, 0x7b, 0x55, 0x7e, 0x55, 0x98, + 0x55, 0x9e, 0x55, 0xae, 0x55, 0x7c, 0x55, 0x83, 0x55, 0xa9, 0x55, 0x87, 0x55, 0xa8, 0x55, 0xda, 0x55, 0xc5, 0x55, + 0xdf, 0x55, 0xc4, 0x55, 0xdc, 0x55, 0xe4, 0x55, 0xd4, 0x56, 0x14, 0x55, 0xf7, 0x56, 0x16, 0x55, 0xfe, 0x55, 0xfd, + 0x56, 0x1b, 0x55, 0xf9, 0x56, 0x4e, 0x56, 0x50, 0x71, 0xdf, 0x56, 0x34, 0x56, 0x36, 0x56, 0x32, 0x56, 0x38, 0x00, + 0x20, 0x56, 0x6b, 0x56, 0x64, 0x56, 0x2f, 0x56, 0x6c, 0x56, 0x6a, 0x56, 0x86, 0x56, 0x80, 0x56, 0x8a, 0x56, 0xa0, + 0x56, 0x94, 0x56, 0x8f, 0x56, 0xa5, 0x56, 0xae, 0x56, 0xb6, 0x56, 0xb4, 0x56, 0xc2, 0x56, 0xbc, 0x56, 0xc1, 0x56, + 0xc3, 0x56, 0xc0, 0x56, 0xc8, 0x56, 0xce, 0x56, 0xd1, 0x56, 0xd3, 0x56, 0xd7, 0x56, 0xee, 0x56, 0xf9, 0x57, 0x00, + 0x56, 0xff, 0x57, 0x04, 0x57, 0x09, 0x57, 0x08, 0x57, 0x0b, 0x57, 0x0d, 0x57, 0x13, 0x57, 0x18, 0x57, 0x16, 0x55, + 0xc7, 0x57, 0x1c, 0x57, 0x26, 0x57, 0x37, 0x57, 0x38, 0x57, 0x4e, 0x57, 0x3b, 0x57, 0x40, 0x57, 0x4f, 0x57, 0x69, + 0x57, 0xc0, 0x57, 0x88, 0x57, 0x61, 0x57, 0x7f, 0x57, 0x89, 0x57, 0x93, 0x57, 0xa0, 0x57, 0xb3, 0x57, 0xa4, 0x57, + 0xaa, 0x57, 0xb0, 0x57, 0xc3, 0x57, 0xc6, 0x57, 0xd4, 0x57, 0xd2, 0x57, 0xd3, 0x58, 0x0a, 0x57, 0xd6, 0x57, 0xe3, + 0x58, 0x0b, 0x58, 0x19, 0x58, 0x1d, 0x58, 0x72, 0x58, 0x21, 0x58, 0x62, 0x58, 0x4b, 0x58, 0x70, 0x6b, 0xc0, 0x58, + 0x52, 0x58, 0x3d, 0x58, 0x79, 0x58, 0x85, 0x58, 0xb9, 0x58, 0x9f, 0x58, 0xab, 0x58, 0xba, 0x58, 0xde, 0x58, 0xbb, + 0x58, 0xb8, 0x58, 0xae, 0x58, 0xc5, 0x58, 0xd3, 0x58, 0xd1, 0x58, 0xd7, 0x58, 0xd9, 0x58, 0xd8, 0x58, 0xe5, 0x58, + 0xdc, 0x58, 0xe4, 0x58, 0xdf, 0x58, 0xef, 0x58, 0xfa, 0x58, 0xf9, 0x58, 0xfb, 0x58, 0xfc, 0x58, 0xfd, 0x59, 0x02, + 0x59, 0x0a, 0x59, 0x10, 0x59, 0x1b, 0x68, 0xa6, 0x59, 0x25, 0x59, 0x2c, 0x59, 0x2d, 0x59, 0x32, 0x59, 0x38, 0x59, + 0x3e, 0x7a, 0xd2, 0x59, 0x55, 0x59, 0x50, 0x59, 0x4e, 0x59, 0x5a, 0x59, 0x58, 0x59, 0x62, 0x59, 0x60, 0x59, 0x67, + 0x59, 0x6c, 0x59, 0x69, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x59, 0x78, 0x59, 0x81, 0x59, 0x9d, 0x4f, 0x5e, 0x4f, 0xab, 0x59, 0xa3, 0x59, 0xb2, + 0x59, 0xc6, 0x59, 0xe8, 0x59, 0xdc, 0x59, 0x8d, 0x59, 0xd9, 0x59, 0xda, 0x5a, 0x25, 0x5a, 0x1f, 0x5a, 0x11, 0x5a, + 0x1c, 0x5a, 0x09, 0x5a, 0x1a, 0x5a, 0x40, 0x5a, 0x6c, 0x5a, 0x49, 0x5a, 0x35, 0x5a, 0x36, 0x5a, 0x62, 0x5a, 0x6a, + 0x5a, 0x9a, 0x5a, 0xbc, 0x5a, 0xbe, 0x5a, 0xcb, 0x5a, 0xc2, 0x5a, 0xbd, 0x5a, 0xe3, 0x5a, 0xd7, 0x5a, 0xe6, 0x5a, + 0xe9, 0x5a, 0xd6, 0x5a, 0xfa, 0x5a, 0xfb, 0x5b, 0x0c, 0x5b, 0x0b, 0x5b, 0x16, 0x5b, 0x32, 0x5a, 0xd0, 0x5b, 0x2a, + 0x5b, 0x36, 0x5b, 0x3e, 0x5b, 0x43, 0x5b, 0x45, 0x5b, 0x40, 0x5b, 0x51, 0x5b, 0x55, 0x5b, 0x5a, 0x5b, 0x5b, 0x5b, + 0x65, 0x5b, 0x69, 0x5b, 0x70, 0x5b, 0x73, 0x5b, 0x75, 0x5b, 0x78, 0x65, 0x88, 0x5b, 0x7a, 0x5b, 0x80, 0x00, 0x20, + 0x5b, 0x83, 0x5b, 0xa6, 0x5b, 0xb8, 0x5b, 0xc3, 0x5b, 0xc7, 0x5b, 0xc9, 0x5b, 0xd4, 0x5b, 0xd0, 0x5b, 0xe4, 0x5b, + 0xe6, 0x5b, 0xe2, 0x5b, 0xde, 0x5b, 0xe5, 0x5b, 0xeb, 0x5b, 0xf0, 0x5b, 0xf6, 0x5b, 0xf3, 0x5c, 0x05, 0x5c, 0x07, + 0x5c, 0x08, 0x5c, 0x0d, 0x5c, 0x13, 0x5c, 0x20, 0x5c, 0x22, 0x5c, 0x28, 0x5c, 0x38, 0x5c, 0x39, 0x5c, 0x41, 0x5c, + 0x46, 0x5c, 0x4e, 0x5c, 0x53, 0x5c, 0x50, 0x5c, 0x4f, 0x5b, 0x71, 0x5c, 0x6c, 0x5c, 0x6e, 0x4e, 0x62, 0x5c, 0x76, + 0x5c, 0x79, 0x5c, 0x8c, 0x5c, 0x91, 0x5c, 0x94, 0x59, 0x9b, 0x5c, 0xab, 0x5c, 0xbb, 0x5c, 0xb6, 0x5c, 0xbc, 0x5c, + 0xb7, 0x5c, 0xc5, 0x5c, 0xbe, 0x5c, 0xc7, 0x5c, 0xd9, 0x5c, 0xe9, 0x5c, 0xfd, 0x5c, 0xfa, 0x5c, 0xed, 0x5d, 0x8c, + 0x5c, 0xea, 0x5d, 0x0b, 0x5d, 0x15, 0x5d, 0x17, 0x5d, 0x5c, 0x5d, 0x1f, 0x5d, 0x1b, 0x5d, 0x11, 0x5d, 0x14, 0x5d, + 0x22, 0x5d, 0x1a, 0x5d, 0x19, 0x5d, 0x18, 0x5d, 0x4c, 0x5d, 0x52, 0x5d, 0x4e, 0x5d, 0x4b, 0x5d, 0x6c, 0x5d, 0x73, + 0x5d, 0x76, 0x5d, 0x87, 0x5d, 0x84, 0x5d, 0x82, 0x5d, 0xa2, 0x5d, 0x9d, 0x5d, 0xac, 0x5d, 0xae, 0x5d, 0xbd, 0x5d, + 0x90, 0x5d, 0xb7, 0x5d, 0xbc, 0x5d, 0xc9, 0x5d, 0xcd, 0x5d, 0xd3, 0x5d, 0xd2, 0x5d, 0xd6, 0x5d, 0xdb, 0x5d, 0xeb, + 0x5d, 0xf2, 0x5d, 0xf5, 0x5e, 0x0b, 0x5e, 0x1a, 0x5e, 0x19, 0x5e, 0x11, 0x5e, 0x1b, 0x5e, 0x36, 0x5e, 0x37, 0x5e, + 0x44, 0x5e, 0x43, 0x5e, 0x40, 0x5e, 0x4e, 0x5e, 0x57, 0x5e, 0x54, 0x5e, 0x5f, 0x5e, 0x62, 0x5e, 0x64, 0x5e, 0x47, + 0x5e, 0x75, 0x5e, 0x76, 0x5e, 0x7a, 0x9e, 0xbc, 0x5e, 0x7f, 0x5e, 0xa0, 0x5e, 0xc1, 0x5e, 0xc2, 0x5e, 0xc8, 0x5e, + 0xd0, 0x5e, 0xcf, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x5e, 0xd6, 0x5e, 0xe3, 0x5e, 0xdd, 0x5e, 0xda, 0x5e, 0xdb, 0x5e, 0xe2, 0x5e, 0xe1, 0x5e, + 0xe8, 0x5e, 0xe9, 0x5e, 0xec, 0x5e, 0xf1, 0x5e, 0xf3, 0x5e, 0xf0, 0x5e, 0xf4, 0x5e, 0xf8, 0x5e, 0xfe, 0x5f, 0x03, + 0x5f, 0x09, 0x5f, 0x5d, 0x5f, 0x5c, 0x5f, 0x0b, 0x5f, 0x11, 0x5f, 0x16, 0x5f, 0x29, 0x5f, 0x2d, 0x5f, 0x38, 0x5f, + 0x41, 0x5f, 0x48, 0x5f, 0x4c, 0x5f, 0x4e, 0x5f, 0x2f, 0x5f, 0x51, 0x5f, 0x56, 0x5f, 0x57, 0x5f, 0x59, 0x5f, 0x61, + 0x5f, 0x6d, 0x5f, 0x73, 0x5f, 0x77, 0x5f, 0x83, 0x5f, 0x82, 0x5f, 0x7f, 0x5f, 0x8a, 0x5f, 0x88, 0x5f, 0x91, 0x5f, + 0x87, 0x5f, 0x9e, 0x5f, 0x99, 0x5f, 0x98, 0x5f, 0xa0, 0x5f, 0xa8, 0x5f, 0xad, 0x5f, 0xbc, 0x5f, 0xd6, 0x5f, 0xfb, + 0x5f, 0xe4, 0x5f, 0xf8, 0x5f, 0xf1, 0x5f, 0xdd, 0x60, 0xb3, 0x5f, 0xff, 0x60, 0x21, 0x60, 0x60, 0x00, 0x20, 0x60, + 0x19, 0x60, 0x10, 0x60, 0x29, 0x60, 0x0e, 0x60, 0x31, 0x60, 0x1b, 0x60, 0x15, 0x60, 0x2b, 0x60, 0x26, 0x60, 0x0f, + 0x60, 0x3a, 0x60, 0x5a, 0x60, 0x41, 0x60, 0x6a, 0x60, 0x77, 0x60, 0x5f, 0x60, 0x4a, 0x60, 0x46, 0x60, 0x4d, 0x60, + 0x63, 0x60, 0x43, 0x60, 0x64, 0x60, 0x42, 0x60, 0x6c, 0x60, 0x6b, 0x60, 0x59, 0x60, 0x81, 0x60, 0x8d, 0x60, 0xe7, + 0x60, 0x83, 0x60, 0x9a, 0x60, 0x84, 0x60, 0x9b, 0x60, 0x96, 0x60, 0x97, 0x60, 0x92, 0x60, 0xa7, 0x60, 0x8b, 0x60, + 0xe1, 0x60, 0xb8, 0x60, 0xe0, 0x60, 0xd3, 0x60, 0xb4, 0x5f, 0xf0, 0x60, 0xbd, 0x60, 0xc6, 0x60, 0xb5, 0x60, 0xd8, + 0x61, 0x4d, 0x61, 0x15, 0x61, 0x06, 0x60, 0xf6, 0x60, 0xf7, 0x61, 0x00, 0x60, 0xf4, 0x60, 0xfa, 0x61, 0x03, 0x61, + 0x21, 0x60, 0xfb, 0x60, 0xf1, 0x61, 0x0d, 0x61, 0x0e, 0x61, 0x47, 0x61, 0x3e, 0x61, 0x28, 0x61, 0x27, 0x61, 0x4a, + 0x61, 0x3f, 0x61, 0x3c, 0x61, 0x2c, 0x61, 0x34, 0x61, 0x3d, 0x61, 0x42, 0x61, 0x44, 0x61, 0x73, 0x61, 0x77, 0x61, + 0x58, 0x61, 0x59, 0x61, 0x5a, 0x61, 0x6b, 0x61, 0x74, 0x61, 0x6f, 0x61, 0x65, 0x61, 0x71, 0x61, 0x5f, 0x61, 0x5d, + 0x61, 0x53, 0x61, 0x75, 0x61, 0x99, 0x61, 0x96, 0x61, 0x87, 0x61, 0xac, 0x61, 0x94, 0x61, 0x9a, 0x61, 0x8a, 0x61, + 0x91, 0x61, 0xab, 0x61, 0xae, 0x61, 0xcc, 0x61, 0xca, 0x61, 0xc9, 0x61, 0xf7, 0x61, 0xc8, 0x61, 0xc3, 0x61, 0xc6, + 0x61, 0xba, 0x61, 0xcb, 0x7f, 0x79, 0x61, 0xcd, 0x61, 0xe6, 0x61, 0xe3, 0x61, 0xf6, 0x61, 0xfa, 0x61, 0xf4, 0x61, + 0xff, 0x61, 0xfd, 0x61, 0xfc, 0x61, 0xfe, 0x62, 0x00, 0x62, 0x08, 0x62, 0x09, 0x62, 0x0d, 0x62, 0x0c, 0x62, 0x14, + 0x62, 0x1b, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x62, 0x1e, 0x62, 0x21, 0x62, 0x2a, 0x62, 0x2e, 0x62, 0x30, 0x62, 0x32, 0x62, 0x33, 0x62, 0x41, + 0x62, 0x4e, 0x62, 0x5e, 0x62, 0x63, 0x62, 0x5b, 0x62, 0x60, 0x62, 0x68, 0x62, 0x7c, 0x62, 0x82, 0x62, 0x89, 0x62, + 0x7e, 0x62, 0x92, 0x62, 0x93, 0x62, 0x96, 0x62, 0xd4, 0x62, 0x83, 0x62, 0x94, 0x62, 0xd7, 0x62, 0xd1, 0x62, 0xbb, + 0x62, 0xcf, 0x62, 0xff, 0x62, 0xc6, 0x64, 0xd4, 0x62, 0xc8, 0x62, 0xdc, 0x62, 0xcc, 0x62, 0xca, 0x62, 0xc2, 0x62, + 0xc7, 0x62, 0x9b, 0x62, 0xc9, 0x63, 0x0c, 0x62, 0xee, 0x62, 0xf1, 0x63, 0x27, 0x63, 0x02, 0x63, 0x08, 0x62, 0xef, + 0x62, 0xf5, 0x63, 0x50, 0x63, 0x3e, 0x63, 0x4d, 0x64, 0x1c, 0x63, 0x4f, 0x63, 0x96, 0x63, 0x8e, 0x63, 0x80, 0x63, + 0xab, 0x63, 0x76, 0x63, 0xa3, 0x63, 0x8f, 0x63, 0x89, 0x63, 0x9f, 0x63, 0xb5, 0x63, 0x6b, 0x00, 0x20, 0x63, 0x69, + 0x63, 0xbe, 0x63, 0xe9, 0x63, 0xc0, 0x63, 0xc6, 0x63, 0xe3, 0x63, 0xc9, 0x63, 0xd2, 0x63, 0xf6, 0x63, 0xc4, 0x64, + 0x16, 0x64, 0x34, 0x64, 0x06, 0x64, 0x13, 0x64, 0x26, 0x64, 0x36, 0x65, 0x1d, 0x64, 0x17, 0x64, 0x28, 0x64, 0x0f, + 0x64, 0x67, 0x64, 0x6f, 0x64, 0x76, 0x64, 0x4e, 0x65, 0x2a, 0x64, 0x95, 0x64, 0x93, 0x64, 0xa5, 0x64, 0xa9, 0x64, + 0x88, 0x64, 0xbc, 0x64, 0xda, 0x64, 0xd2, 0x64, 0xc5, 0x64, 0xc7, 0x64, 0xbb, 0x64, 0xd8, 0x64, 0xc2, 0x64, 0xf1, + 0x64, 0xe7, 0x82, 0x09, 0x64, 0xe0, 0x64, 0xe1, 0x62, 0xac, 0x64, 0xe3, 0x64, 0xef, 0x65, 0x2c, 0x64, 0xf6, 0x64, + 0xf4, 0x64, 0xf2, 0x64, 0xfa, 0x65, 0x00, 0x64, 0xfd, 0x65, 0x18, 0x65, 0x1c, 0x65, 0x05, 0x65, 0x24, 0x65, 0x23, + 0x65, 0x2b, 0x65, 0x34, 0x65, 0x35, 0x65, 0x37, 0x65, 0x36, 0x65, 0x38, 0x75, 0x4b, 0x65, 0x48, 0x65, 0x56, 0x65, + 0x55, 0x65, 0x4d, 0x65, 0x58, 0x65, 0x5e, 0x65, 0x5d, 0x65, 0x72, 0x65, 0x78, 0x65, 0x82, 0x65, 0x83, 0x8b, 0x8a, + 0x65, 0x9b, 0x65, 0x9f, 0x65, 0xab, 0x65, 0xb7, 0x65, 0xc3, 0x65, 0xc6, 0x65, 0xc1, 0x65, 0xc4, 0x65, 0xcc, 0x65, + 0xd2, 0x65, 0xdb, 0x65, 0xd9, 0x65, 0xe0, 0x65, 0xe1, 0x65, 0xf1, 0x67, 0x72, 0x66, 0x0a, 0x66, 0x03, 0x65, 0xfb, + 0x67, 0x73, 0x66, 0x35, 0x66, 0x36, 0x66, 0x34, 0x66, 0x1c, 0x66, 0x4f, 0x66, 0x44, 0x66, 0x49, 0x66, 0x41, 0x66, + 0x5e, 0x66, 0x5d, 0x66, 0x64, 0x66, 0x67, 0x66, 0x68, 0x66, 0x5f, 0x66, 0x62, 0x66, 0x70, 0x66, 0x83, 0x66, 0x88, + 0x66, 0x8e, 0x66, 0x89, 0x66, 0x84, 0x66, 0x98, 0x66, 0x9d, 0x66, 0xc1, 0x66, 0xb9, 0x66, 0xc9, 0x66, 0xbe, 0x66, + 0xbc, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x66, 0xc4, 0x66, 0xb8, 0x66, 0xd6, 0x66, 0xda, 0x66, 0xe0, 0x66, 0x3f, 0x66, 0xe6, 0x66, 0xe9, 0x66, + 0xf0, 0x66, 0xf5, 0x66, 0xf7, 0x67, 0x0f, 0x67, 0x16, 0x67, 0x1e, 0x67, 0x26, 0x67, 0x27, 0x97, 0x38, 0x67, 0x2e, + 0x67, 0x3f, 0x67, 0x36, 0x67, 0x41, 0x67, 0x38, 0x67, 0x37, 0x67, 0x46, 0x67, 0x5e, 0x67, 0x60, 0x67, 0x59, 0x67, + 0x63, 0x67, 0x64, 0x67, 0x89, 0x67, 0x70, 0x67, 0xa9, 0x67, 0x7c, 0x67, 0x6a, 0x67, 0x8c, 0x67, 0x8b, 0x67, 0xa6, + 0x67, 0xa1, 0x67, 0x85, 0x67, 0xb7, 0x67, 0xef, 0x67, 0xb4, 0x67, 0xec, 0x67, 0xb3, 0x67, 0xe9, 0x67, 0xb8, 0x67, + 0xe4, 0x67, 0xde, 0x67, 0xdd, 0x67, 0xe2, 0x67, 0xee, 0x67, 0xb9, 0x67, 0xce, 0x67, 0xc6, 0x67, 0xe7, 0x6a, 0x9c, + 0x68, 0x1e, 0x68, 0x46, 0x68, 0x29, 0x68, 0x40, 0x68, 0x4d, 0x68, 0x32, 0x68, 0x4e, 0x00, 0x20, 0x68, 0xb3, 0x68, + 0x2b, 0x68, 0x59, 0x68, 0x63, 0x68, 0x77, 0x68, 0x7f, 0x68, 0x9f, 0x68, 0x8f, 0x68, 0xad, 0x68, 0x94, 0x68, 0x9d, + 0x68, 0x9b, 0x68, 0x83, 0x6a, 0xae, 0x68, 0xb9, 0x68, 0x74, 0x68, 0xb5, 0x68, 0xa0, 0x68, 0xba, 0x69, 0x0f, 0x68, + 0x8d, 0x68, 0x7e, 0x69, 0x01, 0x68, 0xca, 0x69, 0x08, 0x68, 0xd8, 0x69, 0x22, 0x69, 0x26, 0x68, 0xe1, 0x69, 0x0c, + 0x68, 0xcd, 0x68, 0xd4, 0x68, 0xe7, 0x68, 0xd5, 0x69, 0x36, 0x69, 0x12, 0x69, 0x04, 0x68, 0xd7, 0x68, 0xe3, 0x69, + 0x25, 0x68, 0xf9, 0x68, 0xe0, 0x68, 0xef, 0x69, 0x28, 0x69, 0x2a, 0x69, 0x1a, 0x69, 0x23, 0x69, 0x21, 0x68, 0xc6, + 0x69, 0x79, 0x69, 0x77, 0x69, 0x5c, 0x69, 0x78, 0x69, 0x6b, 0x69, 0x54, 0x69, 0x7e, 0x69, 0x6e, 0x69, 0x39, 0x69, + 0x74, 0x69, 0x3d, 0x69, 0x59, 0x69, 0x30, 0x69, 0x61, 0x69, 0x5e, 0x69, 0x5d, 0x69, 0x81, 0x69, 0x6a, 0x69, 0xb2, + 0x69, 0xae, 0x69, 0xd0, 0x69, 0xbf, 0x69, 0xc1, 0x69, 0xd3, 0x69, 0xbe, 0x69, 0xce, 0x5b, 0xe8, 0x69, 0xca, 0x69, + 0xdd, 0x69, 0xbb, 0x69, 0xc3, 0x69, 0xa7, 0x6a, 0x2e, 0x69, 0x91, 0x69, 0xa0, 0x69, 0x9c, 0x69, 0x95, 0x69, 0xb4, + 0x69, 0xde, 0x69, 0xe8, 0x6a, 0x02, 0x6a, 0x1b, 0x69, 0xff, 0x6b, 0x0a, 0x69, 0xf9, 0x69, 0xf2, 0x69, 0xe7, 0x6a, + 0x05, 0x69, 0xb1, 0x6a, 0x1e, 0x69, 0xed, 0x6a, 0x14, 0x69, 0xeb, 0x6a, 0x0a, 0x6a, 0x12, 0x6a, 0xc1, 0x6a, 0x23, + 0x6a, 0x13, 0x6a, 0x44, 0x6a, 0x0c, 0x6a, 0x72, 0x6a, 0x36, 0x6a, 0x78, 0x6a, 0x47, 0x6a, 0x62, 0x6a, 0x59, 0x6a, + 0x66, 0x6a, 0x48, 0x6a, 0x38, 0x6a, 0x22, 0x6a, 0x90, 0x6a, 0x8d, 0x6a, 0xa0, 0x6a, 0x84, 0x6a, 0xa2, 0x6a, 0xa3, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x6a, 0x97, 0x86, 0x17, 0x6a, 0xbb, 0x6a, 0xc3, 0x6a, 0xc2, 0x6a, 0xb8, 0x6a, 0xb3, 0x6a, 0xac, 0x6a, 0xde, + 0x6a, 0xd1, 0x6a, 0xdf, 0x6a, 0xaa, 0x6a, 0xda, 0x6a, 0xea, 0x6a, 0xfb, 0x6b, 0x05, 0x86, 0x16, 0x6a, 0xfa, 0x6b, + 0x12, 0x6b, 0x16, 0x9b, 0x31, 0x6b, 0x1f, 0x6b, 0x38, 0x6b, 0x37, 0x76, 0xdc, 0x6b, 0x39, 0x98, 0xee, 0x6b, 0x47, + 0x6b, 0x43, 0x6b, 0x49, 0x6b, 0x50, 0x6b, 0x59, 0x6b, 0x54, 0x6b, 0x5b, 0x6b, 0x5f, 0x6b, 0x61, 0x6b, 0x78, 0x6b, + 0x79, 0x6b, 0x7f, 0x6b, 0x80, 0x6b, 0x84, 0x6b, 0x83, 0x6b, 0x8d, 0x6b, 0x98, 0x6b, 0x95, 0x6b, 0x9e, 0x6b, 0xa4, + 0x6b, 0xaa, 0x6b, 0xab, 0x6b, 0xaf, 0x6b, 0xb2, 0x6b, 0xb1, 0x6b, 0xb3, 0x6b, 0xb7, 0x6b, 0xbc, 0x6b, 0xc6, 0x6b, + 0xcb, 0x6b, 0xd3, 0x6b, 0xdf, 0x6b, 0xec, 0x6b, 0xeb, 0x6b, 0xf3, 0x6b, 0xef, 0x00, 0x20, 0x9e, 0xbe, 0x6c, 0x08, + 0x6c, 0x13, 0x6c, 0x14, 0x6c, 0x1b, 0x6c, 0x24, 0x6c, 0x23, 0x6c, 0x5e, 0x6c, 0x55, 0x6c, 0x62, 0x6c, 0x6a, 0x6c, + 0x82, 0x6c, 0x8d, 0x6c, 0x9a, 0x6c, 0x81, 0x6c, 0x9b, 0x6c, 0x7e, 0x6c, 0x68, 0x6c, 0x73, 0x6c, 0x92, 0x6c, 0x90, + 0x6c, 0xc4, 0x6c, 0xf1, 0x6c, 0xd3, 0x6c, 0xbd, 0x6c, 0xd7, 0x6c, 0xc5, 0x6c, 0xdd, 0x6c, 0xae, 0x6c, 0xb1, 0x6c, + 0xbe, 0x6c, 0xba, 0x6c, 0xdb, 0x6c, 0xef, 0x6c, 0xd9, 0x6c, 0xea, 0x6d, 0x1f, 0x88, 0x4d, 0x6d, 0x36, 0x6d, 0x2b, + 0x6d, 0x3d, 0x6d, 0x38, 0x6d, 0x19, 0x6d, 0x35, 0x6d, 0x33, 0x6d, 0x12, 0x6d, 0x0c, 0x6d, 0x63, 0x6d, 0x93, 0x6d, + 0x64, 0x6d, 0x5a, 0x6d, 0x79, 0x6d, 0x59, 0x6d, 0x8e, 0x6d, 0x95, 0x6f, 0xe4, 0x6d, 0x85, 0x6d, 0xf9, 0x6e, 0x15, + 0x6e, 0x0a, 0x6d, 0xb5, 0x6d, 0xc7, 0x6d, 0xe6, 0x6d, 0xb8, 0x6d, 0xc6, 0x6d, 0xec, 0x6d, 0xde, 0x6d, 0xcc, 0x6d, + 0xe8, 0x6d, 0xd2, 0x6d, 0xc5, 0x6d, 0xfa, 0x6d, 0xd9, 0x6d, 0xe4, 0x6d, 0xd5, 0x6d, 0xea, 0x6d, 0xee, 0x6e, 0x2d, + 0x6e, 0x6e, 0x6e, 0x2e, 0x6e, 0x19, 0x6e, 0x72, 0x6e, 0x5f, 0x6e, 0x3e, 0x6e, 0x23, 0x6e, 0x6b, 0x6e, 0x2b, 0x6e, + 0x76, 0x6e, 0x4d, 0x6e, 0x1f, 0x6e, 0x43, 0x6e, 0x3a, 0x6e, 0x4e, 0x6e, 0x24, 0x6e, 0xff, 0x6e, 0x1d, 0x6e, 0x38, + 0x6e, 0x82, 0x6e, 0xaa, 0x6e, 0x98, 0x6e, 0xc9, 0x6e, 0xb7, 0x6e, 0xd3, 0x6e, 0xbd, 0x6e, 0xaf, 0x6e, 0xc4, 0x6e, + 0xb2, 0x6e, 0xd4, 0x6e, 0xd5, 0x6e, 0x8f, 0x6e, 0xa5, 0x6e, 0xc2, 0x6e, 0x9f, 0x6f, 0x41, 0x6f, 0x11, 0x70, 0x4c, + 0x6e, 0xec, 0x6e, 0xf8, 0x6e, 0xfe, 0x6f, 0x3f, 0x6e, 0xf2, 0x6f, 0x31, 0x6e, 0xef, 0x6f, 0x32, 0x6e, 0xcc, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x6f, 0x3e, 0x6f, 0x13, 0x6e, 0xf7, 0x6f, 0x86, 0x6f, 0x7a, 0x6f, 0x78, 0x6f, 0x81, 0x6f, 0x80, 0x6f, 0x6f, 0x6f, + 0x5b, 0x6f, 0xf3, 0x6f, 0x6d, 0x6f, 0x82, 0x6f, 0x7c, 0x6f, 0x58, 0x6f, 0x8e, 0x6f, 0x91, 0x6f, 0xc2, 0x6f, 0x66, + 0x6f, 0xb3, 0x6f, 0xa3, 0x6f, 0xa1, 0x6f, 0xa4, 0x6f, 0xb9, 0x6f, 0xc6, 0x6f, 0xaa, 0x6f, 0xdf, 0x6f, 0xd5, 0x6f, + 0xec, 0x6f, 0xd4, 0x6f, 0xd8, 0x6f, 0xf1, 0x6f, 0xee, 0x6f, 0xdb, 0x70, 0x09, 0x70, 0x0b, 0x6f, 0xfa, 0x70, 0x11, + 0x70, 0x01, 0x70, 0x0f, 0x6f, 0xfe, 0x70, 0x1b, 0x70, 0x1a, 0x6f, 0x74, 0x70, 0x1d, 0x70, 0x18, 0x70, 0x1f, 0x70, + 0x30, 0x70, 0x3e, 0x70, 0x32, 0x70, 0x51, 0x70, 0x63, 0x70, 0x99, 0x70, 0x92, 0x70, 0xaf, 0x70, 0xf1, 0x70, 0xac, + 0x70, 0xb8, 0x70, 0xb3, 0x70, 0xae, 0x70, 0xdf, 0x70, 0xcb, 0x70, 0xdd, 0x00, 0x20, 0x70, 0xd9, 0x71, 0x09, 0x70, + 0xfd, 0x71, 0x1c, 0x71, 0x19, 0x71, 0x65, 0x71, 0x55, 0x71, 0x88, 0x71, 0x66, 0x71, 0x62, 0x71, 0x4c, 0x71, 0x56, + 0x71, 0x6c, 0x71, 0x8f, 0x71, 0xfb, 0x71, 0x84, 0x71, 0x95, 0x71, 0xa8, 0x71, 0xac, 0x71, 0xd7, 0x71, 0xb9, 0x71, + 0xbe, 0x71, 0xd2, 0x71, 0xc9, 0x71, 0xd4, 0x71, 0xce, 0x71, 0xe0, 0x71, 0xec, 0x71, 0xe7, 0x71, 0xf5, 0x71, 0xfc, + 0x71, 0xf9, 0x71, 0xff, 0x72, 0x0d, 0x72, 0x10, 0x72, 0x1b, 0x72, 0x28, 0x72, 0x2d, 0x72, 0x2c, 0x72, 0x30, 0x72, + 0x32, 0x72, 0x3b, 0x72, 0x3c, 0x72, 0x3f, 0x72, 0x40, 0x72, 0x46, 0x72, 0x4b, 0x72, 0x58, 0x72, 0x74, 0x72, 0x7e, + 0x72, 0x82, 0x72, 0x81, 0x72, 0x87, 0x72, 0x92, 0x72, 0x96, 0x72, 0xa2, 0x72, 0xa7, 0x72, 0xb9, 0x72, 0xb2, 0x72, + 0xc3, 0x72, 0xc6, 0x72, 0xc4, 0x72, 0xce, 0x72, 0xd2, 0x72, 0xe2, 0x72, 0xe0, 0x72, 0xe1, 0x72, 0xf9, 0x72, 0xf7, + 0x50, 0x0f, 0x73, 0x17, 0x73, 0x0a, 0x73, 0x1c, 0x73, 0x16, 0x73, 0x1d, 0x73, 0x34, 0x73, 0x2f, 0x73, 0x29, 0x73, + 0x25, 0x73, 0x3e, 0x73, 0x4e, 0x73, 0x4f, 0x9e, 0xd8, 0x73, 0x57, 0x73, 0x6a, 0x73, 0x68, 0x73, 0x70, 0x73, 0x78, + 0x73, 0x75, 0x73, 0x7b, 0x73, 0x7a, 0x73, 0xc8, 0x73, 0xb3, 0x73, 0xce, 0x73, 0xbb, 0x73, 0xc0, 0x73, 0xe5, 0x73, + 0xee, 0x73, 0xde, 0x74, 0xa2, 0x74, 0x05, 0x74, 0x6f, 0x74, 0x25, 0x73, 0xf8, 0x74, 0x32, 0x74, 0x3a, 0x74, 0x55, + 0x74, 0x3f, 0x74, 0x5f, 0x74, 0x59, 0x74, 0x41, 0x74, 0x5c, 0x74, 0x69, 0x74, 0x70, 0x74, 0x63, 0x74, 0x6a, 0x74, + 0x76, 0x74, 0x7e, 0x74, 0x8b, 0x74, 0x9e, 0x74, 0xa7, 0x74, 0xca, 0x74, 0xcf, 0x74, 0xd4, 0x73, 0xf1, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x74, + 0xe0, 0x74, 0xe3, 0x74, 0xe7, 0x74, 0xe9, 0x74, 0xee, 0x74, 0xf2, 0x74, 0xf0, 0x74, 0xf1, 0x74, 0xf8, 0x74, 0xf7, + 0x75, 0x04, 0x75, 0x03, 0x75, 0x05, 0x75, 0x0c, 0x75, 0x0e, 0x75, 0x0d, 0x75, 0x15, 0x75, 0x13, 0x75, 0x1e, 0x75, + 0x26, 0x75, 0x2c, 0x75, 0x3c, 0x75, 0x44, 0x75, 0x4d, 0x75, 0x4a, 0x75, 0x49, 0x75, 0x5b, 0x75, 0x46, 0x75, 0x5a, + 0x75, 0x69, 0x75, 0x64, 0x75, 0x67, 0x75, 0x6b, 0x75, 0x6d, 0x75, 0x78, 0x75, 0x76, 0x75, 0x86, 0x75, 0x87, 0x75, + 0x74, 0x75, 0x8a, 0x75, 0x89, 0x75, 0x82, 0x75, 0x94, 0x75, 0x9a, 0x75, 0x9d, 0x75, 0xa5, 0x75, 0xa3, 0x75, 0xc2, + 0x75, 0xb3, 0x75, 0xc3, 0x75, 0xb5, 0x75, 0xbd, 0x75, 0xb8, 0x75, 0xbc, 0x75, 0xb1, 0x75, 0xcd, 0x75, 0xca, 0x75, + 0xd2, 0x75, 0xd9, 0x75, 0xe3, 0x75, 0xde, 0x75, 0xfe, 0x75, 0xff, 0x00, 0x20, 0x75, 0xfc, 0x76, 0x01, 0x75, 0xf0, + 0x75, 0xfa, 0x75, 0xf2, 0x75, 0xf3, 0x76, 0x0b, 0x76, 0x0d, 0x76, 0x09, 0x76, 0x1f, 0x76, 0x27, 0x76, 0x20, 0x76, + 0x21, 0x76, 0x22, 0x76, 0x24, 0x76, 0x34, 0x76, 0x30, 0x76, 0x3b, 0x76, 0x47, 0x76, 0x48, 0x76, 0x46, 0x76, 0x5c, + 0x76, 0x58, 0x76, 0x61, 0x76, 0x62, 0x76, 0x68, 0x76, 0x69, 0x76, 0x6a, 0x76, 0x67, 0x76, 0x6c, 0x76, 0x70, 0x76, + 0x72, 0x76, 0x76, 0x76, 0x78, 0x76, 0x7c, 0x76, 0x80, 0x76, 0x83, 0x76, 0x88, 0x76, 0x8b, 0x76, 0x8e, 0x76, 0x96, + 0x76, 0x93, 0x76, 0x99, 0x76, 0x9a, 0x76, 0xb0, 0x76, 0xb4, 0x76, 0xb8, 0x76, 0xb9, 0x76, 0xba, 0x76, 0xc2, 0x76, + 0xcd, 0x76, 0xd6, 0x76, 0xd2, 0x76, 0xde, 0x76, 0xe1, 0x76, 0xe5, 0x76, 0xe7, 0x76, 0xea, 0x86, 0x2f, 0x76, 0xfb, + 0x77, 0x08, 0x77, 0x07, 0x77, 0x04, 0x77, 0x29, 0x77, 0x24, 0x77, 0x1e, 0x77, 0x25, 0x77, 0x26, 0x77, 0x1b, 0x77, + 0x37, 0x77, 0x38, 0x77, 0x47, 0x77, 0x5a, 0x77, 0x68, 0x77, 0x6b, 0x77, 0x5b, 0x77, 0x65, 0x77, 0x7f, 0x77, 0x7e, + 0x77, 0x79, 0x77, 0x8e, 0x77, 0x8b, 0x77, 0x91, 0x77, 0xa0, 0x77, 0x9e, 0x77, 0xb0, 0x77, 0xb6, 0x77, 0xb9, 0x77, + 0xbf, 0x77, 0xbc, 0x77, 0xbd, 0x77, 0xbb, 0x77, 0xc7, 0x77, 0xcd, 0x77, 0xd7, 0x77, 0xda, 0x77, 0xdc, 0x77, 0xe3, + 0x77, 0xee, 0x77, 0xfc, 0x78, 0x0c, 0x78, 0x12, 0x79, 0x26, 0x78, 0x20, 0x79, 0x2a, 0x78, 0x45, 0x78, 0x8e, 0x78, + 0x74, 0x78, 0x86, 0x78, 0x7c, 0x78, 0x9a, 0x78, 0x8c, 0x78, 0xa3, 0x78, 0xb5, 0x78, 0xaa, 0x78, 0xaf, 0x78, 0xd1, + 0x78, 0xc6, 0x78, 0xcb, 0x78, 0xd4, 0x78, 0xbe, 0x78, 0xbc, 0x78, 0xc5, 0x78, 0xca, 0x78, 0xec, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x78, 0xe7, + 0x78, 0xda, 0x78, 0xfd, 0x78, 0xf4, 0x79, 0x07, 0x79, 0x12, 0x79, 0x11, 0x79, 0x19, 0x79, 0x2c, 0x79, 0x2b, 0x79, + 0x40, 0x79, 0x60, 0x79, 0x57, 0x79, 0x5f, 0x79, 0x5a, 0x79, 0x55, 0x79, 0x53, 0x79, 0x7a, 0x79, 0x7f, 0x79, 0x8a, + 0x79, 0x9d, 0x79, 0xa7, 0x9f, 0x4b, 0x79, 0xaa, 0x79, 0xae, 0x79, 0xb3, 0x79, 0xb9, 0x79, 0xba, 0x79, 0xc9, 0x79, + 0xd5, 0x79, 0xe7, 0x79, 0xec, 0x79, 0xe1, 0x79, 0xe3, 0x7a, 0x08, 0x7a, 0x0d, 0x7a, 0x18, 0x7a, 0x19, 0x7a, 0x20, + 0x7a, 0x1f, 0x79, 0x80, 0x7a, 0x31, 0x7a, 0x3b, 0x7a, 0x3e, 0x7a, 0x37, 0x7a, 0x43, 0x7a, 0x57, 0x7a, 0x49, 0x7a, + 0x61, 0x7a, 0x62, 0x7a, 0x69, 0x9f, 0x9d, 0x7a, 0x70, 0x7a, 0x79, 0x7a, 0x7d, 0x7a, 0x88, 0x7a, 0x97, 0x7a, 0x95, + 0x7a, 0x98, 0x7a, 0x96, 0x7a, 0xa9, 0x7a, 0xc8, 0x7a, 0xb0, 0x00, 0x20, 0x7a, 0xb6, 0x7a, 0xc5, 0x7a, 0xc4, 0x7a, + 0xbf, 0x90, 0x83, 0x7a, 0xc7, 0x7a, 0xca, 0x7a, 0xcd, 0x7a, 0xcf, 0x7a, 0xd5, 0x7a, 0xd3, 0x7a, 0xd9, 0x7a, 0xda, + 0x7a, 0xdd, 0x7a, 0xe1, 0x7a, 0xe2, 0x7a, 0xe6, 0x7a, 0xed, 0x7a, 0xf0, 0x7b, 0x02, 0x7b, 0x0f, 0x7b, 0x0a, 0x7b, + 0x06, 0x7b, 0x33, 0x7b, 0x18, 0x7b, 0x19, 0x7b, 0x1e, 0x7b, 0x35, 0x7b, 0x28, 0x7b, 0x36, 0x7b, 0x50, 0x7b, 0x7a, + 0x7b, 0x04, 0x7b, 0x4d, 0x7b, 0x0b, 0x7b, 0x4c, 0x7b, 0x45, 0x7b, 0x75, 0x7b, 0x65, 0x7b, 0x74, 0x7b, 0x67, 0x7b, + 0x70, 0x7b, 0x71, 0x7b, 0x6c, 0x7b, 0x6e, 0x7b, 0x9d, 0x7b, 0x98, 0x7b, 0x9f, 0x7b, 0x8d, 0x7b, 0x9c, 0x7b, 0x9a, + 0x7b, 0x8b, 0x7b, 0x92, 0x7b, 0x8f, 0x7b, 0x5d, 0x7b, 0x99, 0x7b, 0xcb, 0x7b, 0xc1, 0x7b, 0xcc, 0x7b, 0xcf, 0x7b, + 0xb4, 0x7b, 0xc6, 0x7b, 0xdd, 0x7b, 0xe9, 0x7c, 0x11, 0x7c, 0x14, 0x7b, 0xe6, 0x7b, 0xe5, 0x7c, 0x60, 0x7c, 0x00, + 0x7c, 0x07, 0x7c, 0x13, 0x7b, 0xf3, 0x7b, 0xf7, 0x7c, 0x17, 0x7c, 0x0d, 0x7b, 0xf6, 0x7c, 0x23, 0x7c, 0x27, 0x7c, + 0x2a, 0x7c, 0x1f, 0x7c, 0x37, 0x7c, 0x2b, 0x7c, 0x3d, 0x7c, 0x4c, 0x7c, 0x43, 0x7c, 0x54, 0x7c, 0x4f, 0x7c, 0x40, + 0x7c, 0x50, 0x7c, 0x58, 0x7c, 0x5f, 0x7c, 0x64, 0x7c, 0x56, 0x7c, 0x65, 0x7c, 0x6c, 0x7c, 0x75, 0x7c, 0x83, 0x7c, + 0x90, 0x7c, 0xa4, 0x7c, 0xad, 0x7c, 0xa2, 0x7c, 0xab, 0x7c, 0xa1, 0x7c, 0xa8, 0x7c, 0xb3, 0x7c, 0xb2, 0x7c, 0xb1, + 0x7c, 0xae, 0x7c, 0xb9, 0x7c, 0xbd, 0x7c, 0xc0, 0x7c, 0xc5, 0x7c, 0xc2, 0x7c, 0xd8, 0x7c, 0xd2, 0x7c, 0xdc, 0x7c, + 0xe2, 0x9b, 0x3b, 0x7c, 0xef, 0x7c, 0xf2, 0x7c, 0xf4, 0x7c, 0xf6, 0x7c, 0xfa, 0x7d, 0x06, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x7d, 0x02, 0x7d, + 0x1c, 0x7d, 0x15, 0x7d, 0x0a, 0x7d, 0x45, 0x7d, 0x4b, 0x7d, 0x2e, 0x7d, 0x32, 0x7d, 0x3f, 0x7d, 0x35, 0x7d, 0x46, + 0x7d, 0x73, 0x7d, 0x56, 0x7d, 0x4e, 0x7d, 0x72, 0x7d, 0x68, 0x7d, 0x6e, 0x7d, 0x4f, 0x7d, 0x63, 0x7d, 0x93, 0x7d, + 0x89, 0x7d, 0x5b, 0x7d, 0x8f, 0x7d, 0x7d, 0x7d, 0x9b, 0x7d, 0xba, 0x7d, 0xae, 0x7d, 0xa3, 0x7d, 0xb5, 0x7d, 0xc7, + 0x7d, 0xbd, 0x7d, 0xab, 0x7e, 0x3d, 0x7d, 0xa2, 0x7d, 0xaf, 0x7d, 0xdc, 0x7d, 0xb8, 0x7d, 0x9f, 0x7d, 0xb0, 0x7d, + 0xd8, 0x7d, 0xdd, 0x7d, 0xe4, 0x7d, 0xde, 0x7d, 0xfb, 0x7d, 0xf2, 0x7d, 0xe1, 0x7e, 0x05, 0x7e, 0x0a, 0x7e, 0x23, + 0x7e, 0x21, 0x7e, 0x12, 0x7e, 0x31, 0x7e, 0x1f, 0x7e, 0x09, 0x7e, 0x0b, 0x7e, 0x22, 0x7e, 0x46, 0x7e, 0x66, 0x7e, + 0x3b, 0x7e, 0x35, 0x7e, 0x39, 0x7e, 0x43, 0x7e, 0x37, 0x00, 0x20, 0x7e, 0x32, 0x7e, 0x3a, 0x7e, 0x67, 0x7e, 0x5d, + 0x7e, 0x56, 0x7e, 0x5e, 0x7e, 0x59, 0x7e, 0x5a, 0x7e, 0x79, 0x7e, 0x6a, 0x7e, 0x69, 0x7e, 0x7c, 0x7e, 0x7b, 0x7e, + 0x83, 0x7d, 0xd5, 0x7e, 0x7d, 0x8f, 0xae, 0x7e, 0x7f, 0x7e, 0x88, 0x7e, 0x89, 0x7e, 0x8c, 0x7e, 0x92, 0x7e, 0x90, + 0x7e, 0x93, 0x7e, 0x94, 0x7e, 0x96, 0x7e, 0x8e, 0x7e, 0x9b, 0x7e, 0x9c, 0x7f, 0x38, 0x7f, 0x3a, 0x7f, 0x45, 0x7f, + 0x4c, 0x7f, 0x4d, 0x7f, 0x4e, 0x7f, 0x50, 0x7f, 0x51, 0x7f, 0x55, 0x7f, 0x54, 0x7f, 0x58, 0x7f, 0x5f, 0x7f, 0x60, + 0x7f, 0x68, 0x7f, 0x69, 0x7f, 0x67, 0x7f, 0x78, 0x7f, 0x82, 0x7f, 0x86, 0x7f, 0x83, 0x7f, 0x88, 0x7f, 0x87, 0x7f, + 0x8c, 0x7f, 0x94, 0x7f, 0x9e, 0x7f, 0x9d, 0x7f, 0x9a, 0x7f, 0xa3, 0x7f, 0xaf, 0x7f, 0xb2, 0x7f, 0xb9, 0x7f, 0xae, + 0x7f, 0xb6, 0x7f, 0xb8, 0x8b, 0x71, 0x7f, 0xc5, 0x7f, 0xc6, 0x7f, 0xca, 0x7f, 0xd5, 0x7f, 0xd4, 0x7f, 0xe1, 0x7f, + 0xe6, 0x7f, 0xe9, 0x7f, 0xf3, 0x7f, 0xf9, 0x98, 0xdc, 0x80, 0x06, 0x80, 0x04, 0x80, 0x0b, 0x80, 0x12, 0x80, 0x18, + 0x80, 0x19, 0x80, 0x1c, 0x80, 0x21, 0x80, 0x28, 0x80, 0x3f, 0x80, 0x3b, 0x80, 0x4a, 0x80, 0x46, 0x80, 0x52, 0x80, + 0x58, 0x80, 0x5a, 0x80, 0x5f, 0x80, 0x62, 0x80, 0x68, 0x80, 0x73, 0x80, 0x72, 0x80, 0x70, 0x80, 0x76, 0x80, 0x79, + 0x80, 0x7d, 0x80, 0x7f, 0x80, 0x84, 0x80, 0x86, 0x80, 0x85, 0x80, 0x9b, 0x80, 0x93, 0x80, 0x9a, 0x80, 0xad, 0x51, + 0x90, 0x80, 0xac, 0x80, 0xdb, 0x80, 0xe5, 0x80, 0xd9, 0x80, 0xdd, 0x80, 0xc4, 0x80, 0xda, 0x80, 0xd6, 0x81, 0x09, + 0x80, 0xef, 0x80, 0xf1, 0x81, 0x1b, 0x81, 0x29, 0x81, 0x23, 0x81, 0x2f, 0x81, 0x4b, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x96, 0x8b, 0x81, 0x46, + 0x81, 0x3e, 0x81, 0x53, 0x81, 0x51, 0x80, 0xfc, 0x81, 0x71, 0x81, 0x6e, 0x81, 0x65, 0x81, 0x66, 0x81, 0x74, 0x81, + 0x83, 0x81, 0x88, 0x81, 0x8a, 0x81, 0x80, 0x81, 0x82, 0x81, 0xa0, 0x81, 0x95, 0x81, 0xa4, 0x81, 0xa3, 0x81, 0x5f, + 0x81, 0x93, 0x81, 0xa9, 0x81, 0xb0, 0x81, 0xb5, 0x81, 0xbe, 0x81, 0xb8, 0x81, 0xbd, 0x81, 0xc0, 0x81, 0xc2, 0x81, + 0xba, 0x81, 0xc9, 0x81, 0xcd, 0x81, 0xd1, 0x81, 0xd9, 0x81, 0xd8, 0x81, 0xc8, 0x81, 0xda, 0x81, 0xdf, 0x81, 0xe0, + 0x81, 0xe7, 0x81, 0xfa, 0x81, 0xfb, 0x81, 0xfe, 0x82, 0x01, 0x82, 0x02, 0x82, 0x05, 0x82, 0x07, 0x82, 0x0a, 0x82, + 0x0d, 0x82, 0x10, 0x82, 0x16, 0x82, 0x29, 0x82, 0x2b, 0x82, 0x38, 0x82, 0x33, 0x82, 0x40, 0x82, 0x59, 0x82, 0x58, + 0x82, 0x5d, 0x82, 0x5a, 0x82, 0x5f, 0x82, 0x64, 0x00, 0x20, 0x82, 0x62, 0x82, 0x68, 0x82, 0x6a, 0x82, 0x6b, 0x82, + 0x2e, 0x82, 0x71, 0x82, 0x77, 0x82, 0x78, 0x82, 0x7e, 0x82, 0x8d, 0x82, 0x92, 0x82, 0xab, 0x82, 0x9f, 0x82, 0xbb, + 0x82, 0xac, 0x82, 0xe1, 0x82, 0xe3, 0x82, 0xdf, 0x82, 0xd2, 0x82, 0xf4, 0x82, 0xf3, 0x82, 0xfa, 0x83, 0x93, 0x83, + 0x03, 0x82, 0xfb, 0x82, 0xf9, 0x82, 0xde, 0x83, 0x06, 0x82, 0xdc, 0x83, 0x09, 0x82, 0xd9, 0x83, 0x35, 0x83, 0x34, + 0x83, 0x16, 0x83, 0x32, 0x83, 0x31, 0x83, 0x40, 0x83, 0x39, 0x83, 0x50, 0x83, 0x45, 0x83, 0x2f, 0x83, 0x2b, 0x83, + 0x17, 0x83, 0x18, 0x83, 0x85, 0x83, 0x9a, 0x83, 0xaa, 0x83, 0x9f, 0x83, 0xa2, 0x83, 0x96, 0x83, 0x23, 0x83, 0x8e, + 0x83, 0x87, 0x83, 0x8a, 0x83, 0x7c, 0x83, 0xb5, 0x83, 0x73, 0x83, 0x75, 0x83, 0xa0, 0x83, 0x89, 0x83, 0xa8, 0x83, + 0xf4, 0x84, 0x13, 0x83, 0xeb, 0x83, 0xce, 0x83, 0xfd, 0x84, 0x03, 0x83, 0xd8, 0x84, 0x0b, 0x83, 0xc1, 0x83, 0xf7, + 0x84, 0x07, 0x83, 0xe0, 0x83, 0xf2, 0x84, 0x0d, 0x84, 0x22, 0x84, 0x20, 0x83, 0xbd, 0x84, 0x38, 0x85, 0x06, 0x83, + 0xfb, 0x84, 0x6d, 0x84, 0x2a, 0x84, 0x3c, 0x85, 0x5a, 0x84, 0x84, 0x84, 0x77, 0x84, 0x6b, 0x84, 0xad, 0x84, 0x6e, + 0x84, 0x82, 0x84, 0x69, 0x84, 0x46, 0x84, 0x2c, 0x84, 0x6f, 0x84, 0x79, 0x84, 0x35, 0x84, 0xca, 0x84, 0x62, 0x84, + 0xb9, 0x84, 0xbf, 0x84, 0x9f, 0x84, 0xd9, 0x84, 0xcd, 0x84, 0xbb, 0x84, 0xda, 0x84, 0xd0, 0x84, 0xc1, 0x84, 0xc6, + 0x84, 0xd6, 0x84, 0xa1, 0x85, 0x21, 0x84, 0xff, 0x84, 0xf4, 0x85, 0x17, 0x85, 0x18, 0x85, 0x2c, 0x85, 0x1f, 0x85, + 0x15, 0x85, 0x14, 0x84, 0xfc, 0x85, 0x40, 0x85, 0x63, 0x85, 0x58, 0x85, 0x48, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x85, 0x41, 0x86, 0x02, 0x85, + 0x4b, 0x85, 0x55, 0x85, 0x80, 0x85, 0xa4, 0x85, 0x88, 0x85, 0x91, 0x85, 0x8a, 0x85, 0xa8, 0x85, 0x6d, 0x85, 0x94, + 0x85, 0x9b, 0x85, 0xea, 0x85, 0x87, 0x85, 0x9c, 0x85, 0x77, 0x85, 0x7e, 0x85, 0x90, 0x85, 0xc9, 0x85, 0xba, 0x85, + 0xcf, 0x85, 0xb9, 0x85, 0xd0, 0x85, 0xd5, 0x85, 0xdd, 0x85, 0xe5, 0x85, 0xdc, 0x85, 0xf9, 0x86, 0x0a, 0x86, 0x13, + 0x86, 0x0b, 0x85, 0xfe, 0x85, 0xfa, 0x86, 0x06, 0x86, 0x22, 0x86, 0x1a, 0x86, 0x30, 0x86, 0x3f, 0x86, 0x4d, 0x4e, + 0x55, 0x86, 0x54, 0x86, 0x5f, 0x86, 0x67, 0x86, 0x71, 0x86, 0x93, 0x86, 0xa3, 0x86, 0xa9, 0x86, 0xaa, 0x86, 0x8b, + 0x86, 0x8c, 0x86, 0xb6, 0x86, 0xaf, 0x86, 0xc4, 0x86, 0xc6, 0x86, 0xb0, 0x86, 0xc9, 0x88, 0x23, 0x86, 0xab, 0x86, + 0xd4, 0x86, 0xde, 0x86, 0xe9, 0x86, 0xec, 0x00, 0x20, 0x86, 0xdf, 0x86, 0xdb, 0x86, 0xef, 0x87, 0x12, 0x87, 0x06, + 0x87, 0x08, 0x87, 0x00, 0x87, 0x03, 0x86, 0xfb, 0x87, 0x11, 0x87, 0x09, 0x87, 0x0d, 0x86, 0xf9, 0x87, 0x0a, 0x87, + 0x34, 0x87, 0x3f, 0x87, 0x37, 0x87, 0x3b, 0x87, 0x25, 0x87, 0x29, 0x87, 0x1a, 0x87, 0x60, 0x87, 0x5f, 0x87, 0x78, + 0x87, 0x4c, 0x87, 0x4e, 0x87, 0x74, 0x87, 0x57, 0x87, 0x68, 0x87, 0x6e, 0x87, 0x59, 0x87, 0x53, 0x87, 0x63, 0x87, + 0x6a, 0x88, 0x05, 0x87, 0xa2, 0x87, 0x9f, 0x87, 0x82, 0x87, 0xaf, 0x87, 0xcb, 0x87, 0xbd, 0x87, 0xc0, 0x87, 0xd0, + 0x96, 0xd6, 0x87, 0xab, 0x87, 0xc4, 0x87, 0xb3, 0x87, 0xc7, 0x87, 0xc6, 0x87, 0xbb, 0x87, 0xef, 0x87, 0xf2, 0x87, + 0xe0, 0x88, 0x0f, 0x88, 0x0d, 0x87, 0xfe, 0x87, 0xf6, 0x87, 0xf7, 0x88, 0x0e, 0x87, 0xd2, 0x88, 0x11, 0x88, 0x16, + 0x88, 0x15, 0x88, 0x22, 0x88, 0x21, 0x88, 0x31, 0x88, 0x36, 0x88, 0x39, 0x88, 0x27, 0x88, 0x3b, 0x88, 0x44, 0x88, + 0x42, 0x88, 0x52, 0x88, 0x59, 0x88, 0x5e, 0x88, 0x62, 0x88, 0x6b, 0x88, 0x81, 0x88, 0x7e, 0x88, 0x9e, 0x88, 0x75, + 0x88, 0x7d, 0x88, 0xb5, 0x88, 0x72, 0x88, 0x82, 0x88, 0x97, 0x88, 0x92, 0x88, 0xae, 0x88, 0x99, 0x88, 0xa2, 0x88, + 0x8d, 0x88, 0xa4, 0x88, 0xb0, 0x88, 0xbf, 0x88, 0xb1, 0x88, 0xc3, 0x88, 0xc4, 0x88, 0xd4, 0x88, 0xd8, 0x88, 0xd9, + 0x88, 0xdd, 0x88, 0xf9, 0x89, 0x02, 0x88, 0xfc, 0x88, 0xf4, 0x88, 0xe8, 0x88, 0xf2, 0x89, 0x04, 0x89, 0x0c, 0x89, + 0x0a, 0x89, 0x13, 0x89, 0x43, 0x89, 0x1e, 0x89, 0x25, 0x89, 0x2a, 0x89, 0x2b, 0x89, 0x41, 0x89, 0x44, 0x89, 0x3b, + 0x89, 0x36, 0x89, 0x38, 0x89, 0x4c, 0x89, 0x1d, 0x89, 0x60, 0x89, 0x5e, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x89, 0x66, 0x89, 0x64, 0x89, 0x6d, + 0x89, 0x6a, 0x89, 0x6f, 0x89, 0x74, 0x89, 0x77, 0x89, 0x7e, 0x89, 0x83, 0x89, 0x88, 0x89, 0x8a, 0x89, 0x93, 0x89, + 0x98, 0x89, 0xa1, 0x89, 0xa9, 0x89, 0xa6, 0x89, 0xac, 0x89, 0xaf, 0x89, 0xb2, 0x89, 0xba, 0x89, 0xbd, 0x89, 0xbf, + 0x89, 0xc0, 0x89, 0xda, 0x89, 0xdc, 0x89, 0xdd, 0x89, 0xe7, 0x89, 0xf4, 0x89, 0xf8, 0x8a, 0x03, 0x8a, 0x16, 0x8a, + 0x10, 0x8a, 0x0c, 0x8a, 0x1b, 0x8a, 0x1d, 0x8a, 0x25, 0x8a, 0x36, 0x8a, 0x41, 0x8a, 0x5b, 0x8a, 0x52, 0x8a, 0x46, + 0x8a, 0x48, 0x8a, 0x7c, 0x8a, 0x6d, 0x8a, 0x6c, 0x8a, 0x62, 0x8a, 0x85, 0x8a, 0x82, 0x8a, 0x84, 0x8a, 0xa8, 0x8a, + 0xa1, 0x8a, 0x91, 0x8a, 0xa5, 0x8a, 0xa6, 0x8a, 0x9a, 0x8a, 0xa3, 0x8a, 0xc4, 0x8a, 0xcd, 0x8a, 0xc2, 0x8a, 0xda, + 0x8a, 0xeb, 0x8a, 0xf3, 0x8a, 0xe7, 0x00, 0x20, 0x8a, 0xe4, 0x8a, 0xf1, 0x8b, 0x14, 0x8a, 0xe0, 0x8a, 0xe2, 0x8a, + 0xf7, 0x8a, 0xde, 0x8a, 0xdb, 0x8b, 0x0c, 0x8b, 0x07, 0x8b, 0x1a, 0x8a, 0xe1, 0x8b, 0x16, 0x8b, 0x10, 0x8b, 0x17, + 0x8b, 0x20, 0x8b, 0x33, 0x97, 0xab, 0x8b, 0x26, 0x8b, 0x2b, 0x8b, 0x3e, 0x8b, 0x28, 0x8b, 0x41, 0x8b, 0x4c, 0x8b, + 0x4f, 0x8b, 0x4e, 0x8b, 0x49, 0x8b, 0x56, 0x8b, 0x5b, 0x8b, 0x5a, 0x8b, 0x6b, 0x8b, 0x5f, 0x8b, 0x6c, 0x8b, 0x6f, + 0x8b, 0x74, 0x8b, 0x7d, 0x8b, 0x80, 0x8b, 0x8c, 0x8b, 0x8e, 0x8b, 0x92, 0x8b, 0x93, 0x8b, 0x96, 0x8b, 0x99, 0x8b, + 0x9a, 0x8c, 0x3a, 0x8c, 0x41, 0x8c, 0x3f, 0x8c, 0x48, 0x8c, 0x4c, 0x8c, 0x4e, 0x8c, 0x50, 0x8c, 0x55, 0x8c, 0x62, + 0x8c, 0x6c, 0x8c, 0x78, 0x8c, 0x7a, 0x8c, 0x82, 0x8c, 0x89, 0x8c, 0x85, 0x8c, 0x8a, 0x8c, 0x8d, 0x8c, 0x8e, 0x8c, + 0x94, 0x8c, 0x7c, 0x8c, 0x98, 0x62, 0x1d, 0x8c, 0xad, 0x8c, 0xaa, 0x8c, 0xbd, 0x8c, 0xb2, 0x8c, 0xb3, 0x8c, 0xae, + 0x8c, 0xb6, 0x8c, 0xc8, 0x8c, 0xc1, 0x8c, 0xe4, 0x8c, 0xe3, 0x8c, 0xda, 0x8c, 0xfd, 0x8c, 0xfa, 0x8c, 0xfb, 0x8d, + 0x04, 0x8d, 0x05, 0x8d, 0x0a, 0x8d, 0x07, 0x8d, 0x0f, 0x8d, 0x0d, 0x8d, 0x10, 0x9f, 0x4e, 0x8d, 0x13, 0x8c, 0xcd, + 0x8d, 0x14, 0x8d, 0x16, 0x8d, 0x67, 0x8d, 0x6d, 0x8d, 0x71, 0x8d, 0x73, 0x8d, 0x81, 0x8d, 0x99, 0x8d, 0xc2, 0x8d, + 0xbe, 0x8d, 0xba, 0x8d, 0xcf, 0x8d, 0xda, 0x8d, 0xd6, 0x8d, 0xcc, 0x8d, 0xdb, 0x8d, 0xcb, 0x8d, 0xea, 0x8d, 0xeb, + 0x8d, 0xdf, 0x8d, 0xe3, 0x8d, 0xfc, 0x8e, 0x08, 0x8e, 0x09, 0x8d, 0xff, 0x8e, 0x1d, 0x8e, 0x1e, 0x8e, 0x10, 0x8e, + 0x1f, 0x8e, 0x42, 0x8e, 0x35, 0x8e, 0x30, 0x8e, 0x34, 0x8e, 0x4a, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x8e, 0x47, 0x8e, 0x49, 0x8e, 0x4c, 0x8e, + 0x50, 0x8e, 0x48, 0x8e, 0x59, 0x8e, 0x64, 0x8e, 0x60, 0x8e, 0x2a, 0x8e, 0x63, 0x8e, 0x55, 0x8e, 0x76, 0x8e, 0x72, + 0x8e, 0x7c, 0x8e, 0x81, 0x8e, 0x87, 0x8e, 0x85, 0x8e, 0x84, 0x8e, 0x8b, 0x8e, 0x8a, 0x8e, 0x93, 0x8e, 0x91, 0x8e, + 0x94, 0x8e, 0x99, 0x8e, 0xaa, 0x8e, 0xa1, 0x8e, 0xac, 0x8e, 0xb0, 0x8e, 0xc6, 0x8e, 0xb1, 0x8e, 0xbe, 0x8e, 0xc5, + 0x8e, 0xc8, 0x8e, 0xcb, 0x8e, 0xdb, 0x8e, 0xe3, 0x8e, 0xfc, 0x8e, 0xfb, 0x8e, 0xeb, 0x8e, 0xfe, 0x8f, 0x0a, 0x8f, + 0x05, 0x8f, 0x15, 0x8f, 0x12, 0x8f, 0x19, 0x8f, 0x13, 0x8f, 0x1c, 0x8f, 0x1f, 0x8f, 0x1b, 0x8f, 0x0c, 0x8f, 0x26, + 0x8f, 0x33, 0x8f, 0x3b, 0x8f, 0x39, 0x8f, 0x45, 0x8f, 0x42, 0x8f, 0x3e, 0x8f, 0x4c, 0x8f, 0x49, 0x8f, 0x46, 0x8f, + 0x4e, 0x8f, 0x57, 0x8f, 0x5c, 0x00, 0x20, 0x8f, 0x62, 0x8f, 0x63, 0x8f, 0x64, 0x8f, 0x9c, 0x8f, 0x9f, 0x8f, 0xa3, + 0x8f, 0xad, 0x8f, 0xaf, 0x8f, 0xb7, 0x8f, 0xda, 0x8f, 0xe5, 0x8f, 0xe2, 0x8f, 0xea, 0x8f, 0xef, 0x90, 0x87, 0x8f, + 0xf4, 0x90, 0x05, 0x8f, 0xf9, 0x8f, 0xfa, 0x90, 0x11, 0x90, 0x15, 0x90, 0x21, 0x90, 0x0d, 0x90, 0x1e, 0x90, 0x16, + 0x90, 0x0b, 0x90, 0x27, 0x90, 0x36, 0x90, 0x35, 0x90, 0x39, 0x8f, 0xf8, 0x90, 0x4f, 0x90, 0x50, 0x90, 0x51, 0x90, + 0x52, 0x90, 0x0e, 0x90, 0x49, 0x90, 0x3e, 0x90, 0x56, 0x90, 0x58, 0x90, 0x5e, 0x90, 0x68, 0x90, 0x6f, 0x90, 0x76, + 0x96, 0xa8, 0x90, 0x72, 0x90, 0x82, 0x90, 0x7d, 0x90, 0x81, 0x90, 0x80, 0x90, 0x8a, 0x90, 0x89, 0x90, 0x8f, 0x90, + 0xa8, 0x90, 0xaf, 0x90, 0xb1, 0x90, 0xb5, 0x90, 0xe2, 0x90, 0xe4, 0x62, 0x48, 0x90, 0xdb, 0x91, 0x02, 0x91, 0x12, + 0x91, 0x19, 0x91, 0x32, 0x91, 0x30, 0x91, 0x4a, 0x91, 0x56, 0x91, 0x58, 0x91, 0x63, 0x91, 0x65, 0x91, 0x69, 0x91, + 0x73, 0x91, 0x72, 0x91, 0x8b, 0x91, 0x89, 0x91, 0x82, 0x91, 0xa2, 0x91, 0xab, 0x91, 0xaf, 0x91, 0xaa, 0x91, 0xb5, + 0x91, 0xb4, 0x91, 0xba, 0x91, 0xc0, 0x91, 0xc1, 0x91, 0xc9, 0x91, 0xcb, 0x91, 0xd0, 0x91, 0xd6, 0x91, 0xdf, 0x91, + 0xe1, 0x91, 0xdb, 0x91, 0xfc, 0x91, 0xf5, 0x91, 0xf6, 0x92, 0x1e, 0x91, 0xff, 0x92, 0x14, 0x92, 0x2c, 0x92, 0x15, + 0x92, 0x11, 0x92, 0x5e, 0x92, 0x57, 0x92, 0x45, 0x92, 0x49, 0x92, 0x64, 0x92, 0x48, 0x92, 0x95, 0x92, 0x3f, 0x92, + 0x4b, 0x92, 0x50, 0x92, 0x9c, 0x92, 0x96, 0x92, 0x93, 0x92, 0x9b, 0x92, 0x5a, 0x92, 0xcf, 0x92, 0xb9, 0x92, 0xb7, + 0x92, 0xe9, 0x93, 0x0f, 0x92, 0xfa, 0x93, 0x44, 0x93, 0x2e, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x93, 0x19, 0x93, 0x22, 0x93, 0x1a, 0x93, 0x23, + 0x93, 0x3a, 0x93, 0x35, 0x93, 0x3b, 0x93, 0x5c, 0x93, 0x60, 0x93, 0x7c, 0x93, 0x6e, 0x93, 0x56, 0x93, 0xb0, 0x93, + 0xac, 0x93, 0xad, 0x93, 0x94, 0x93, 0xb9, 0x93, 0xd6, 0x93, 0xd7, 0x93, 0xe8, 0x93, 0xe5, 0x93, 0xd8, 0x93, 0xc3, + 0x93, 0xdd, 0x93, 0xd0, 0x93, 0xc8, 0x93, 0xe4, 0x94, 0x1a, 0x94, 0x14, 0x94, 0x13, 0x94, 0x03, 0x94, 0x07, 0x94, + 0x10, 0x94, 0x36, 0x94, 0x2b, 0x94, 0x35, 0x94, 0x21, 0x94, 0x3a, 0x94, 0x41, 0x94, 0x52, 0x94, 0x44, 0x94, 0x5b, + 0x94, 0x60, 0x94, 0x62, 0x94, 0x5e, 0x94, 0x6a, 0x92, 0x29, 0x94, 0x70, 0x94, 0x75, 0x94, 0x77, 0x94, 0x7d, 0x94, + 0x5a, 0x94, 0x7c, 0x94, 0x7e, 0x94, 0x81, 0x94, 0x7f, 0x95, 0x82, 0x95, 0x87, 0x95, 0x8a, 0x95, 0x94, 0x95, 0x96, + 0x95, 0x98, 0x95, 0x99, 0x00, 0x20, 0x95, 0xa0, 0x95, 0xa8, 0x95, 0xa7, 0x95, 0xad, 0x95, 0xbc, 0x95, 0xbb, 0x95, + 0xb9, 0x95, 0xbe, 0x95, 0xca, 0x6f, 0xf6, 0x95, 0xc3, 0x95, 0xcd, 0x95, 0xcc, 0x95, 0xd5, 0x95, 0xd4, 0x95, 0xd6, + 0x95, 0xdc, 0x95, 0xe1, 0x95, 0xe5, 0x95, 0xe2, 0x96, 0x21, 0x96, 0x28, 0x96, 0x2e, 0x96, 0x2f, 0x96, 0x42, 0x96, + 0x4c, 0x96, 0x4f, 0x96, 0x4b, 0x96, 0x77, 0x96, 0x5c, 0x96, 0x5e, 0x96, 0x5d, 0x96, 0x5f, 0x96, 0x66, 0x96, 0x72, + 0x96, 0x6c, 0x96, 0x8d, 0x96, 0x98, 0x96, 0x95, 0x96, 0x97, 0x96, 0xaa, 0x96, 0xa7, 0x96, 0xb1, 0x96, 0xb2, 0x96, + 0xb0, 0x96, 0xb4, 0x96, 0xb6, 0x96, 0xb8, 0x96, 0xb9, 0x96, 0xce, 0x96, 0xcb, 0x96, 0xc9, 0x96, 0xcd, 0x89, 0x4d, + 0x96, 0xdc, 0x97, 0x0d, 0x96, 0xd5, 0x96, 0xf9, 0x97, 0x04, 0x97, 0x06, 0x97, 0x08, 0x97, 0x13, 0x97, 0x0e, 0x97, + 0x11, 0x97, 0x0f, 0x97, 0x16, 0x97, 0x19, 0x97, 0x24, 0x97, 0x2a, 0x97, 0x30, 0x97, 0x39, 0x97, 0x3d, 0x97, 0x3e, + 0x97, 0x44, 0x97, 0x46, 0x97, 0x48, 0x97, 0x42, 0x97, 0x49, 0x97, 0x5c, 0x97, 0x60, 0x97, 0x64, 0x97, 0x66, 0x97, + 0x68, 0x52, 0xd2, 0x97, 0x6b, 0x97, 0x71, 0x97, 0x79, 0x97, 0x85, 0x97, 0x7c, 0x97, 0x81, 0x97, 0x7a, 0x97, 0x86, + 0x97, 0x8b, 0x97, 0x8f, 0x97, 0x90, 0x97, 0x9c, 0x97, 0xa8, 0x97, 0xa6, 0x97, 0xa3, 0x97, 0xb3, 0x97, 0xb4, 0x97, + 0xc3, 0x97, 0xc6, 0x97, 0xc8, 0x97, 0xcb, 0x97, 0xdc, 0x97, 0xed, 0x9f, 0x4f, 0x97, 0xf2, 0x7a, 0xdf, 0x97, 0xf6, + 0x97, 0xf5, 0x98, 0x0f, 0x98, 0x0c, 0x98, 0x38, 0x98, 0x24, 0x98, 0x21, 0x98, 0x37, 0x98, 0x3d, 0x98, 0x46, 0x98, + 0x4f, 0x98, 0x4b, 0x98, 0x6b, 0x98, 0x6f, 0x98, 0x70, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x98, 0x71, 0x98, 0x74, 0x98, 0x73, 0x98, 0xaa, 0x98, + 0xaf, 0x98, 0xb1, 0x98, 0xb6, 0x98, 0xc4, 0x98, 0xc3, 0x98, 0xc6, 0x98, 0xe9, 0x98, 0xeb, 0x99, 0x03, 0x99, 0x09, + 0x99, 0x12, 0x99, 0x14, 0x99, 0x18, 0x99, 0x21, 0x99, 0x1d, 0x99, 0x1e, 0x99, 0x24, 0x99, 0x20, 0x99, 0x2c, 0x99, + 0x2e, 0x99, 0x3d, 0x99, 0x3e, 0x99, 0x42, 0x99, 0x49, 0x99, 0x45, 0x99, 0x50, 0x99, 0x4b, 0x99, 0x51, 0x99, 0x52, + 0x99, 0x4c, 0x99, 0x55, 0x99, 0x97, 0x99, 0x98, 0x99, 0xa5, 0x99, 0xad, 0x99, 0xae, 0x99, 0xbc, 0x99, 0xdf, 0x99, + 0xdb, 0x99, 0xdd, 0x99, 0xd8, 0x99, 0xd1, 0x99, 0xed, 0x99, 0xee, 0x99, 0xf1, 0x99, 0xf2, 0x99, 0xfb, 0x99, 0xf8, + 0x9a, 0x01, 0x9a, 0x0f, 0x9a, 0x05, 0x99, 0xe2, 0x9a, 0x19, 0x9a, 0x2b, 0x9a, 0x37, 0x9a, 0x45, 0x9a, 0x42, 0x9a, + 0x40, 0x9a, 0x43, 0x00, 0x20, 0x9a, 0x3e, 0x9a, 0x55, 0x9a, 0x4d, 0x9a, 0x5b, 0x9a, 0x57, 0x9a, 0x5f, 0x9a, 0x62, + 0x9a, 0x65, 0x9a, 0x64, 0x9a, 0x69, 0x9a, 0x6b, 0x9a, 0x6a, 0x9a, 0xad, 0x9a, 0xb0, 0x9a, 0xbc, 0x9a, 0xc0, 0x9a, + 0xcf, 0x9a, 0xd1, 0x9a, 0xd3, 0x9a, 0xd4, 0x9a, 0xde, 0x9a, 0xdf, 0x9a, 0xe2, 0x9a, 0xe3, 0x9a, 0xe6, 0x9a, 0xef, + 0x9a, 0xeb, 0x9a, 0xee, 0x9a, 0xf4, 0x9a, 0xf1, 0x9a, 0xf7, 0x9a, 0xfb, 0x9b, 0x06, 0x9b, 0x18, 0x9b, 0x1a, 0x9b, + 0x1f, 0x9b, 0x22, 0x9b, 0x23, 0x9b, 0x25, 0x9b, 0x27, 0x9b, 0x28, 0x9b, 0x29, 0x9b, 0x2a, 0x9b, 0x2e, 0x9b, 0x2f, + 0x9b, 0x32, 0x9b, 0x44, 0x9b, 0x43, 0x9b, 0x4f, 0x9b, 0x4d, 0x9b, 0x4e, 0x9b, 0x51, 0x9b, 0x58, 0x9b, 0x74, 0x9b, + 0x93, 0x9b, 0x83, 0x9b, 0x91, 0x9b, 0x96, 0x9b, 0x97, 0x9b, 0x9f, 0x9b, 0xa0, 0x9b, 0xa8, 0x9b, 0xb4, 0x9b, 0xc0, + 0x9b, 0xca, 0x9b, 0xb9, 0x9b, 0xc6, 0x9b, 0xcf, 0x9b, 0xd1, 0x9b, 0xd2, 0x9b, 0xe3, 0x9b, 0xe2, 0x9b, 0xe4, 0x9b, + 0xd4, 0x9b, 0xe1, 0x9c, 0x3a, 0x9b, 0xf2, 0x9b, 0xf1, 0x9b, 0xf0, 0x9c, 0x15, 0x9c, 0x14, 0x9c, 0x09, 0x9c, 0x13, + 0x9c, 0x0c, 0x9c, 0x06, 0x9c, 0x08, 0x9c, 0x12, 0x9c, 0x0a, 0x9c, 0x04, 0x9c, 0x2e, 0x9c, 0x1b, 0x9c, 0x25, 0x9c, + 0x24, 0x9c, 0x21, 0x9c, 0x30, 0x9c, 0x47, 0x9c, 0x32, 0x9c, 0x46, 0x9c, 0x3e, 0x9c, 0x5a, 0x9c, 0x60, 0x9c, 0x67, + 0x9c, 0x76, 0x9c, 0x78, 0x9c, 0xe7, 0x9c, 0xec, 0x9c, 0xf0, 0x9d, 0x09, 0x9d, 0x08, 0x9c, 0xeb, 0x9d, 0x03, 0x9d, + 0x06, 0x9d, 0x2a, 0x9d, 0x26, 0x9d, 0xaf, 0x9d, 0x23, 0x9d, 0x1f, 0x9d, 0x44, 0x9d, 0x15, 0x9d, 0x12, 0x9d, 0x41, + 0x9d, 0x3f, 0x9d, 0x3e, 0x9d, 0x46, 0x9d, 0x48, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x9d, 0x5d, 0x9d, 0x5e, 0x9d, 0x64, 0x9d, 0x51, 0x9d, 0x50, + 0x9d, 0x59, 0x9d, 0x72, 0x9d, 0x89, 0x9d, 0x87, 0x9d, 0xab, 0x9d, 0x6f, 0x9d, 0x7a, 0x9d, 0x9a, 0x9d, 0xa4, 0x9d, + 0xa9, 0x9d, 0xb2, 0x9d, 0xc4, 0x9d, 0xc1, 0x9d, 0xbb, 0x9d, 0xb8, 0x9d, 0xba, 0x9d, 0xc6, 0x9d, 0xcf, 0x9d, 0xc2, + 0x9d, 0xd9, 0x9d, 0xd3, 0x9d, 0xf8, 0x9d, 0xe6, 0x9d, 0xed, 0x9d, 0xef, 0x9d, 0xfd, 0x9e, 0x1a, 0x9e, 0x1b, 0x9e, + 0x1e, 0x9e, 0x75, 0x9e, 0x79, 0x9e, 0x7d, 0x9e, 0x81, 0x9e, 0x88, 0x9e, 0x8b, 0x9e, 0x8c, 0x9e, 0x92, 0x9e, 0x95, + 0x9e, 0x91, 0x9e, 0x9d, 0x9e, 0xa5, 0x9e, 0xa9, 0x9e, 0xb8, 0x9e, 0xaa, 0x9e, 0xad, 0x97, 0x61, 0x9e, 0xcc, 0x9e, + 0xce, 0x9e, 0xcf, 0x9e, 0xd0, 0x9e, 0xd4, 0x9e, 0xdc, 0x9e, 0xde, 0x9e, 0xdd, 0x9e, 0xe0, 0x9e, 0xe5, 0x9e, 0xe8, + 0x9e, 0xef, 0x00, 0x20, 0x9e, 0xf4, 0x9e, 0xf6, 0x9e, 0xf7, 0x9e, 0xf9, 0x9e, 0xfb, 0x9e, 0xfc, 0x9e, 0xfd, 0x9f, + 0x07, 0x9f, 0x08, 0x76, 0xb7, 0x9f, 0x15, 0x9f, 0x21, 0x9f, 0x2c, 0x9f, 0x3e, 0x9f, 0x4a, 0x9f, 0x52, 0x9f, 0x54, + 0x9f, 0x63, 0x9f, 0x5f, 0x9f, 0x60, 0x9f, 0x61, 0x9f, 0x66, 0x9f, 0x67, 0x9f, 0x6c, 0x9f, 0x6a, 0x9f, 0x77, 0x9f, + 0x72, 0x9f, 0x76, 0x9f, 0x95, 0x9f, 0x9c, 0x9f, 0xa0, 0x58, 0x2f, 0x69, 0xc7, 0x90, 0x59, 0x74, 0x64, 0x51, 0xdc, + 0x71, 0x99, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + +}; \ No newline at end of file diff --git a/jni/common/shiftjis.h b/jni/common/shiftjis.h new file mode 100644 index 0000000..038eae3 --- /dev/null +++ b/jni/common/shiftjis.h @@ -0,0 +1,4 @@ +#pragma once + +void sjis2ascii(char* bData); +char* sjis2utf8(char* input); diff --git a/jni/common/state_wrapper.cpp b/jni/common/state_wrapper.cpp new file mode 100644 index 0000000..ef9439e --- /dev/null +++ b/jni/common/state_wrapper.cpp @@ -0,0 +1,80 @@ +#include "state_wrapper.h" +#include "log.h" +#include "string.h" +#include +#include +Log_SetChannel(StateWrapper); + +StateWrapper::StateWrapper(ByteStream* stream, Mode mode, u32 version) + : m_stream(stream), m_mode(mode), m_version(version) +{ +} + +StateWrapper::~StateWrapper() = default; + +void StateWrapper::DoBytes(void* data, size_t length) +{ + if (m_mode == Mode::Read) + { + if (m_error || (m_error |= !m_stream->Read2(data, static_cast(length))) == true) + std::memset(data, 0, length); + } + else + { + if (!m_error) + m_error |= !m_stream->Write2(data, static_cast(length)); + } +} + +void StateWrapper::Do(bool* value_ptr) +{ + if (m_mode == Mode::Read) + { + u8 value = 0; + if (!m_error) + m_error |= !m_stream->ReadByte(&value); + *value_ptr = (value != 0); + } + else + { + u8 value = static_cast(*value_ptr); + if (!m_error) + m_error |= !m_stream->WriteByte(value); + } +} + +void StateWrapper::Do(std::string* value_ptr) +{ + u32 length = static_cast(value_ptr->length()); + Do(&length); + if (m_mode == Mode::Read) + value_ptr->resize(length); + DoBytes(&(*value_ptr)[0], length); + value_ptr->resize(std::strlen(&(*value_ptr)[0])); +} + +void StateWrapper::Do(String* value_ptr) +{ + u32 length = static_cast(value_ptr->GetLength()); + Do(&length); + if (m_mode == Mode::Read) + value_ptr->Resize(length); + DoBytes(value_ptr->GetWriteableCharArray(), length); + value_ptr->UpdateSize(); +} + +bool StateWrapper::DoMarker(const char* marker) +{ + SmallString file_value(marker); + Do(&file_value); + if (m_error) + return false; + + if (m_mode == Mode::Write || file_value.Compare(marker)) + return true; + + Log_ErrorPrintf("Marker mismatch at offset %" PRIu64 ": found '%s' expected '%s'", m_stream->GetPosition(), + file_value.GetCharArray(), marker); + + return false; +} diff --git a/jni/common/state_wrapper.h b/jni/common/state_wrapper.h new file mode 100644 index 0000000..38ad4a3 --- /dev/null +++ b/jni/common/state_wrapper.h @@ -0,0 +1,196 @@ +#pragma once +#include "byte_stream.h" +#include "fifo_queue.h" +#include "heap_array.h" +#include "types.h" +#include +#include +#include +#include +#include + +class String; + +class StateWrapper +{ +public: + enum class Mode + { + Read, + Write + }; + + StateWrapper(ByteStream* stream, Mode mode, u32 version); + StateWrapper(const StateWrapper&) = delete; + ~StateWrapper(); + + ByteStream* GetStream() const { return m_stream; } + bool HasError() const { return m_error; } + bool IsReading() const { return (m_mode == Mode::Read); } + bool IsWriting() const { return (m_mode == Mode::Write); } + Mode GetMode() const { return m_mode; } + void SetMode(Mode mode) { m_mode = mode; } + u32 GetVersion() const { return m_version; } + + /// Overload for integral or floating-point types. Writes bytes as-is. + template || std::is_floating_point_v, int> = 0> + void Do(T* value_ptr) + { + if (m_mode == Mode::Read) + { + if (m_error || (m_error |= !m_stream->Read2(value_ptr, sizeof(T))) == true) + *value_ptr = static_cast(0); + } + else + { + if (!m_error) + m_error |= !m_stream->Write2(value_ptr, sizeof(T)); + } + } + + /// Overload for enum types. Uses the underlying type. + template, int> = 0> + void Do(T* value_ptr) + { + using TType = std::underlying_type_t; + if (m_mode == Mode::Read) + { + TType temp; + if (m_error || (m_error |= !m_stream->Read2(&temp, sizeof(TType))) == true) + temp = static_cast(0); + + *value_ptr = static_cast(temp); + } + else + { + TType temp; + std::memcpy(&temp, value_ptr, sizeof(TType)); + if (!m_error) + m_error |= !m_stream->Write2(&temp, sizeof(TType)); + } + } + + /// Overload for POD types, such as structs. + template, int> = 0> + void DoPOD(T* value_ptr) + { + if (m_mode == Mode::Read) + { + if (m_error || (m_error |= !m_stream->Read2(value_ptr, sizeof(T))) == true) + std::memset(value_ptr, 0, sizeof(*value_ptr)); + } + else + { + if (!m_error) + m_error |= !m_stream->Write2(value_ptr, sizeof(T)); + } + } + + template + void DoArray(T* values, size_t count) + { + for (size_t i = 0; i < count; i++) + Do(&values[i]); + } + + template + void DoPODArray(T* values, size_t count) + { + for (size_t i = 0; i < count; i++) + DoPOD(&values[i]); + } + + void DoBytes(void* data, size_t length); + + void Do(bool* value_ptr); + void Do(std::string* value_ptr); + void Do(String* value_ptr); + + template + void Do(std::array* data) + { + DoArray(data->data(), data->size()); + } + + template + void Do(HeapArray* data) + { + DoArray(data->data(), data->size()); + } + + template + void Do(std::vector* data) + { + u32 length = static_cast(data->size()); + Do(&length); + if (m_mode == Mode::Read) + data->resize(length); + DoArray(data->data(), data->size()); + } + + template + void Do(std::deque* data) + { + u32 length = static_cast(data->size()); + Do(&length); + if (m_mode == Mode::Read) + { + data->clear(); + for (u32 i = 0; i < length; i++) + { + T value; + Do(&value); + data->push_back(value); + } + } + else + { + for (u32 i = 0; i < length; i++) + Do(&data[i]); + } + } + + template + void Do(FIFOQueue* data) + { + u32 size = data->GetSize(); + Do(&size); + + if (m_mode == Mode::Read) + { + T* temp = new T[size]; + DoArray(temp, size); + data->Clear(); + data->PushRange(temp, size); + delete[] temp; + } + else + { + for (u32 i = 0; i < size; i++) + { + T temp(data->Peek(i)); + Do(&temp); + } + } + } + + bool DoMarker(const char* marker); + + template + void DoEx(T* data, u32 version_introduced, T default_value) + { + if (m_version < version_introduced) + { + *data = std::move(default_value); + return; + } + + Do(data); + } + +private: + ByteStream* m_stream; + Mode m_mode; + u32 m_version; + bool m_error = false; +}; diff --git a/jni/common/string.cpp b/jni/common/string.cpp new file mode 100644 index 0000000..ecde9ee --- /dev/null +++ b/jni/common/string.cpp @@ -0,0 +1,1049 @@ +#include "string.h" +#include "assert.h" +#include +#include +#include +#include + +#ifdef _MSC_VER +#define CASE_COMPARE _stricmp +#define CASE_N_COMPARE _strnicmp +#else +#define CASE_COMPARE strcasecmp +#define CASE_N_COMPARE strncasecmp +#endif + +// globals +const String::StringData String::s_EmptyStringData = {const_cast(""), 0, 1, -1, true}; +const String EmptyString; + +// helper functions +static String::StringData* StringDataAllocate(u32 allocSize) +{ + DebugAssert(allocSize > 0); + + String::StringData* pStringData = + reinterpret_cast(std::malloc(sizeof(String::StringData) + allocSize)); + pStringData->pBuffer = reinterpret_cast(pStringData + 1); + pStringData->StringLength = 0; + pStringData->BufferSize = allocSize; + pStringData->ReadOnly = false; + pStringData->ReferenceCount = 1; + + // if in debug build, set all to zero, otherwise only the first to zero. +#ifdef _DEBUG + std::memset(pStringData->pBuffer, 0, allocSize); +#else + pStringData->pBuffer[0] = 0; + if (allocSize > 1) + pStringData->pBuffer[allocSize - 1] = 0; +#endif + + return pStringData; +} + +static inline void StringDataAddRef(String::StringData* pStringData) +{ + DebugAssert(pStringData->ReferenceCount > 0); + ++pStringData->ReferenceCount; +} + +static inline void StringDataRelease(String::StringData* pStringData) +{ + if (pStringData->ReferenceCount == -1) + return; + + DebugAssert(pStringData->ReferenceCount > 0); + u32 newRefCount = --pStringData->ReferenceCount; + if (!newRefCount) + std::free(pStringData); +} + +static String::StringData* StringDataClone(const String::StringData* pStringData, u32 newSize, bool copyPastString) +{ + DebugAssert(newSize >= 0); + + String::StringData* pClone = StringDataAllocate(newSize); + if (pStringData->StringLength > 0) + { + u32 copyLength; + + if (copyPastString) + { + copyLength = std::min(newSize, pStringData->BufferSize); + if (copyLength > 0) + { + std::memcpy(pClone->pBuffer, pStringData->pBuffer, copyLength); + if (copyLength < pStringData->BufferSize) + pClone->pBuffer[copyLength - 1] = 0; + } + } + else + { + copyLength = std::min(newSize, pStringData->StringLength); + if (copyLength > 0) + { + std::memcpy(pClone->pBuffer, pStringData->pBuffer, copyLength); + pClone->pBuffer[copyLength] = 0; + } + } + + pClone->StringLength = copyLength; + } + + return pClone; +} + +static String::StringData* StringDataReallocate(String::StringData* pStringData, u32 newSize) +{ + DebugAssert(newSize > pStringData->StringLength); + DebugAssert(pStringData->ReferenceCount == 1); + + // perform realloc + pStringData = reinterpret_cast(std::realloc(pStringData, sizeof(String::StringData) + newSize)); + pStringData->pBuffer = reinterpret_cast(pStringData + 1); + + // zero bytes in debug +#ifdef _DEBUG + if (newSize > pStringData->BufferSize) + { + u32 bytesToZero = newSize - pStringData->BufferSize; + std::memset(pStringData->pBuffer + (newSize - bytesToZero), 0, bytesToZero); + } +#else + if (newSize > pStringData->BufferSize) + { + pStringData->pBuffer[newSize - 1] = 0; + } +#endif + + // update size + pStringData->BufferSize = newSize; + return pStringData; +} + +static bool StringDataIsSharable(const String::StringData* pStringData) +{ + return pStringData->ReadOnly || pStringData->ReferenceCount != -1; +} + +static bool StringDataIsShared(const String::StringData* pStringData) +{ + return pStringData->ReferenceCount > 1; +} + +String::String() : m_pStringData(const_cast(&s_EmptyStringData)) {} + +String::String(const String& copyString) +{ + // special case: empty strings + if (copyString.IsEmpty()) + { + m_pStringData = const_cast(&s_EmptyStringData); + } + // is the string data sharable? + else if (StringDataIsSharable(copyString.m_pStringData)) + { + m_pStringData = copyString.m_pStringData; + if (!m_pStringData->ReadOnly) + StringDataAddRef(m_pStringData); + } + // create a clone for ourselves + else + { + // since we're going to the effort of creating a clone, we might as well create it as the smallest size possible + m_pStringData = StringDataClone(copyString.m_pStringData, copyString.m_pStringData->StringLength + 1, false); + } +} + +String::String(const char* Text) : m_pStringData(const_cast(&s_EmptyStringData)) +{ + Assign(Text); +} + +String::String(String&& moveString) +{ + Assign(moveString); +} + +String::String(const std::string_view& sv) +{ + AppendString(sv.data(), static_cast(sv.size())); +} + +String::~String() +{ + StringDataRelease(m_pStringData); +} + +void String::EnsureOwnWritableCopy() +{ + if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly) + { + StringData* pNewStringData = StringDataClone(m_pStringData, m_pStringData->StringLength + 1, false); + StringDataRelease(m_pStringData); + m_pStringData = pNewStringData; + } +} + +void String::EnsureRemainingSpace(u32 spaceRequired) +{ + StringData* pNewStringData; + u32 requiredReserve = m_pStringData->StringLength + spaceRequired + 1; + + if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly) + { + pNewStringData = StringDataClone(m_pStringData, std::max(requiredReserve, m_pStringData->BufferSize), false); + StringDataRelease(m_pStringData); + m_pStringData = pNewStringData; + } + else if (m_pStringData->BufferSize < requiredReserve) + { + u32 newSize = std::max(requiredReserve, m_pStringData->BufferSize * 2); + + // if we are the only owner of the buffer, we can simply realloc it + if (m_pStringData->ReferenceCount == 1) + { + // do realloc and update pointer + m_pStringData = StringDataReallocate(m_pStringData, newSize); + } + else + { + // clone and release old + pNewStringData = StringDataClone(m_pStringData, std::max(requiredReserve, newSize), false); + StringDataRelease(m_pStringData); + m_pStringData = pNewStringData; + } + } +} + +void String::InternalAppend(const char* pString, u32 Length) +{ + EnsureRemainingSpace(Length); + + DebugAssert((Length + m_pStringData->StringLength) < m_pStringData->BufferSize); + DebugAssert(m_pStringData->ReferenceCount <= 1 && !m_pStringData->ReadOnly); + + std::memcpy(m_pStringData->pBuffer + m_pStringData->StringLength, pString, Length); + m_pStringData->StringLength += Length; + m_pStringData->pBuffer[m_pStringData->StringLength] = 0; +} + +void String::InternalPrepend(const char* pString, u32 Length) +{ + EnsureRemainingSpace(Length); + + DebugAssert((Length + m_pStringData->StringLength) < m_pStringData->BufferSize); + DebugAssert(m_pStringData->ReferenceCount <= 1 && !m_pStringData->ReadOnly); + + std::memmove(m_pStringData->pBuffer + Length, m_pStringData->pBuffer, m_pStringData->StringLength); + std::memcpy(m_pStringData->pBuffer, pString, Length); + m_pStringData->StringLength += Length; + m_pStringData->pBuffer[m_pStringData->StringLength] = 0; +} + +void String::AppendCharacter(char c) +{ + InternalAppend(&c, 1); +} + +void String::AppendString(const String& appendStr) +{ + if (appendStr.GetLength() > 0) + InternalAppend(appendStr.GetCharArray(), appendStr.GetLength()); +} + +void String::AppendString(const char* appendText) +{ + u32 textLength = static_cast(std::strlen(appendText)); + if (textLength > 0) + InternalAppend(appendText, textLength); +} + +void String::AppendString(const char* appendString, u32 Count) +{ + if (Count > 0) + InternalAppend(appendString, Count); +} + +void String::AppendSubString(const String& appendStr, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */) +{ + u32 appendStrLength = appendStr.GetLength(); + + // calc real offset + u32 realOffset; + if (Offset < 0) + realOffset = (u32)std::max((s32)0, (s32)appendStrLength + Offset); + else + realOffset = std::min((u32)Offset, appendStrLength); + + // calc real count + u32 realCount; + if (Count < 0) + realCount = std::min(appendStrLength - realOffset, (u32)std::max((s32)0, (s32)appendStrLength + Count)); + else + realCount = std::min(appendStrLength - realOffset, (u32)Count); + + // should be safe + DebugAssert((realOffset + realCount) <= appendStrLength); + if (realCount > 0) + InternalAppend(appendStr.GetCharArray() + realOffset, realCount); +} + +void String::AppendSubString(const char* appendText, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */) +{ + u32 appendTextLength = static_cast(std::strlen(appendText)); + + // calc real offset + u32 realOffset; + if (Offset < 0) + realOffset = (u32)std::max((s32)0, (s32)appendTextLength + Offset); + else + realOffset = std::min((u32)Offset, appendTextLength); + + // calc real count + u32 realCount; + if (Count < 0) + realCount = std::min(appendTextLength - realOffset, (u32)std::max((s32)0, (s32)appendTextLength + Count)); + else + realCount = std::min(appendTextLength - realOffset, (u32)Count); + + // should be safe + DebugAssert((realOffset + realCount) <= appendTextLength); + if (realCount > 0) + InternalAppend(appendText + realOffset, realCount); +} + +void String::AppendFormattedString(const char* FormatString, ...) +{ + va_list ap; + va_start(ap, FormatString); + AppendFormattedStringVA(FormatString, ap); + va_end(ap); +} + +void String::AppendFormattedStringVA(const char* FormatString, va_list ArgPtr) +{ + // We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap, + // but 1KB should be enough for most strings. + char stackBuffer[1024]; + char* pHeapBuffer = NULL; + char* pBuffer = stackBuffer; + u32 currentBufferSize = countof(stackBuffer); + u32 charsWritten; + + for (;;) + { + va_list ArgPtrCopy; + va_copy(ArgPtrCopy, ArgPtr); + int ret = std::vsnprintf(pBuffer, currentBufferSize, FormatString, ArgPtrCopy); + va_end(ArgPtrCopy); + if (ret < 0 || ((u32)ret >= (currentBufferSize - 1))) + { + currentBufferSize *= 2; + pBuffer = pHeapBuffer = reinterpret_cast(std::realloc(pHeapBuffer, currentBufferSize)); + continue; + } + + charsWritten = (u32)ret; + break; + } + + InternalAppend(pBuffer, charsWritten); + + if (pHeapBuffer != NULL) + std::free(pHeapBuffer); +} + +void String::PrependCharacter(char c) +{ + InternalPrepend(&c, 1); +} + +void String::PrependString(const String& appendStr) +{ + if (appendStr.GetLength() > 0) + InternalPrepend(appendStr.GetCharArray(), appendStr.GetLength()); +} + +void String::PrependString(const char* appendText) +{ + u32 textLength = static_cast(std::strlen(appendText)); + if (textLength > 0) + InternalPrepend(appendText, textLength); +} + +void String::PrependString(const char* appendString, u32 Count) +{ + if (Count > 0) + InternalPrepend(appendString, Count); +} + +void String::PrependSubString(const String& appendStr, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */) +{ + u32 appendStrLength = appendStr.GetLength(); + + // calc real offset + u32 realOffset; + if (Offset < 0) + realOffset = (u32)std::max((s32)0, (s32)appendStrLength + Offset); + else + realOffset = std::min((u32)Offset, appendStrLength); + + // calc real count + u32 realCount; + if (Count < 0) + realCount = std::min(appendStrLength - realOffset, (u32)std::max((s32)0, (s32)appendStrLength + Count)); + else + realCount = std::min(appendStrLength - realOffset, (u32)Count); + + // should be safe + DebugAssert((realOffset + realCount) <= appendStrLength); + if (realCount > 0) + InternalPrepend(appendStr.GetCharArray() + realOffset, realCount); +} + +void String::PrependSubString(const char* appendText, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */) +{ + u32 appendTextLength = static_cast(std::strlen(appendText)); + + // calc real offset + u32 realOffset; + if (Offset < 0) + realOffset = (u32)std::max((s32)0, (s32)appendTextLength + Offset); + else + realOffset = std::min((u32)Offset, appendTextLength); + + // calc real count + u32 realCount; + if (Count < 0) + realCount = std::min(appendTextLength - realOffset, (u32)std::max((s32)0, (s32)appendTextLength + Count)); + else + realCount = std::min(appendTextLength - realOffset, (u32)Count); + + // should be safe + DebugAssert((realOffset + realCount) <= appendTextLength); + if (realCount > 0) + InternalPrepend(appendText + realOffset, realCount); +} + +void String::PrependFormattedString(const char* FormatString, ...) +{ + va_list ap; + va_start(ap, FormatString); + PrependFormattedStringVA(FormatString, ap); + va_end(ap); +} + +void String::PrependFormattedStringVA(const char* FormatString, va_list ArgPtr) +{ + // We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap, + // but 1KB should be enough for most strings. + char stackBuffer[1024]; + char* pHeapBuffer = NULL; + char* pBuffer = stackBuffer; + u32 currentBufferSize = countof(stackBuffer); + u32 charsWritten; + + for (;;) + { + int ret = std::vsnprintf(pBuffer, currentBufferSize, FormatString, ArgPtr); + if (ret < 0 || ((u32)ret >= (currentBufferSize - 1))) + { + currentBufferSize *= 2; + pBuffer = pHeapBuffer = reinterpret_cast(std::realloc(pHeapBuffer, currentBufferSize)); + continue; + } + + charsWritten = (u32)ret; + break; + } + + InternalPrepend(pBuffer, charsWritten); + + if (pHeapBuffer != NULL) + std::free(pHeapBuffer); +} + +void String::InsertString(s32 offset, const String& appendStr) +{ + InsertString(offset, appendStr, appendStr.GetLength()); +} + +void String::InsertString(s32 offset, const char* appendStr) +{ + InsertString(offset, appendStr, static_cast(std::strlen(appendStr))); +} + +void String::InsertString(s32 offset, const char* appendStr, u32 appendStrLength) +{ + if (appendStrLength == 0) + return; + + EnsureRemainingSpace(appendStrLength); + + // calc real offset + u32 realOffset; + if (offset < 0) + realOffset = (u32)std::max((s32)0, (s32)m_pStringData->StringLength + offset); + else + realOffset = std::min((u32)offset, m_pStringData->StringLength); + + // determine number of characters after offset + DebugAssert(realOffset <= m_pStringData->StringLength); + u32 charactersAfterOffset = m_pStringData->StringLength - realOffset; + if (charactersAfterOffset > 0) + std::memmove(m_pStringData->pBuffer + offset + appendStrLength, m_pStringData->pBuffer + offset, + charactersAfterOffset); + + // insert the string + std::memcpy(m_pStringData->pBuffer + realOffset, appendStr, appendStrLength); + m_pStringData->StringLength += appendStrLength; + + // ensure null termination + m_pStringData->pBuffer[m_pStringData->StringLength] = 0; +} + +void String::Format(const char* FormatString, ...) +{ + va_list ap; + va_start(ap, FormatString); + FormatVA(FormatString, ap); + va_end(ap); +} + +void String::FormatVA(const char* FormatString, va_list ArgPtr) +{ + if (GetLength() > 0) + Clear(); + + AppendFormattedStringVA(FormatString, ArgPtr); +} + +void String::Assign(const String& copyString) +{ + // special case: empty strings + if (copyString.IsEmpty()) + { + m_pStringData = const_cast(&s_EmptyStringData); + } + // is the string data sharable? + else if (StringDataIsSharable(copyString.m_pStringData)) + { + m_pStringData = copyString.m_pStringData; + if (!m_pStringData->ReadOnly) + StringDataAddRef(m_pStringData); + } + // create a clone for ourselves + else + { + // since we're going to the effort of creating a clone, we might as well create it as the smallest size possible + m_pStringData = StringDataClone(copyString.m_pStringData, copyString.m_pStringData->StringLength + 1, false); + } +} + +void String::Assign(const char* copyText) +{ + Clear(); + AppendString(copyText); +} + +void String::Assign(String&& moveString) +{ + Clear(); + m_pStringData = moveString.m_pStringData; + moveString.m_pStringData = const_cast(&s_EmptyStringData); +} + +void String::AssignCopy(const String& copyString) +{ + Clear(); + AppendString(copyString); +} + +void String::Swap(String& swapString) +{ + std::swap(m_pStringData, swapString.m_pStringData); +} + +bool String::Compare(const String& otherString) const +{ + return (std::strcmp(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer) == 0); +} + +bool String::Compare(const char* otherText) const +{ + return (std::strcmp(m_pStringData->pBuffer, otherText) == 0); +} + +bool String::SubCompare(const String& otherString, u32 Length) const +{ + return (std::strncmp(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer, Length) == 0); +} + +bool String::SubCompare(const char* otherText, u32 Length) const +{ + return (std::strncmp(m_pStringData->pBuffer, otherText, Length) == 0); +} + +bool String::CompareInsensitive(const String& otherString) const +{ + return (CASE_COMPARE(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer) == 0); +} + +bool String::CompareInsensitive(const char* otherText) const +{ + return (CASE_COMPARE(m_pStringData->pBuffer, otherText) == 0); +} + +bool String::SubCompareInsensitive(const String& otherString, u32 Length) const +{ + return (CASE_N_COMPARE(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer, Length) == 0); +} + +bool String::SubCompareInsensitive(const char* otherText, u32 Length) const +{ + return (CASE_N_COMPARE(m_pStringData->pBuffer, otherText, Length) == 0); +} + +int String::NumericCompare(const String& otherString) const +{ + return std::strcmp(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer); +} + +int String::NumericCompare(const char* otherText) const +{ + return std::strcmp(m_pStringData->pBuffer, otherText); +} + +int String::NumericCompareInsensitive(const String& otherString) const +{ + return CASE_COMPARE(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer); +} + +int String::NumericCompareInsensitive(const char* otherText) const +{ + return CASE_COMPARE(m_pStringData->pBuffer, otherText); +} + +bool String::StartsWith(const char* compareString, bool caseSensitive /*= true*/) const +{ + u32 compareStringLength = static_cast(std::strlen(compareString)); + if (compareStringLength > m_pStringData->StringLength) + return false; + + return (caseSensitive) ? (std::strncmp(compareString, m_pStringData->pBuffer, compareStringLength) == 0) : + (CASE_N_COMPARE(compareString, m_pStringData->pBuffer, compareStringLength) == 0); +} + +bool String::StartsWith(const String& compareString, bool caseSensitive /*= true*/) const +{ + u32 compareStringLength = compareString.GetLength(); + if (compareStringLength > m_pStringData->StringLength) + return false; + + return (caseSensitive) ? + (std::strncmp(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer, compareStringLength) == 0) : + (CASE_N_COMPARE(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer, compareStringLength) == 0); +} + +bool String::EndsWith(const char* compareString, bool caseSensitive /*= true*/) const +{ + u32 compareStringLength = static_cast(std::strlen(compareString)); + if (compareStringLength > m_pStringData->StringLength) + return false; + + u32 startOffset = m_pStringData->StringLength - compareStringLength; + return (caseSensitive) ? + (std::strncmp(compareString, m_pStringData->pBuffer + startOffset, compareStringLength) == 0) : + (CASE_N_COMPARE(compareString, m_pStringData->pBuffer + startOffset, compareStringLength) == 0); +} + +bool String::EndsWith(const String& compareString, bool caseSensitive /*= true*/) const +{ + u32 compareStringLength = compareString.GetLength(); + if (compareStringLength > m_pStringData->StringLength) + return false; + + u32 startOffset = m_pStringData->StringLength - compareStringLength; + return (caseSensitive) ? (std::strncmp(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer + startOffset, + compareStringLength) == 0) : + (CASE_N_COMPARE(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer + startOffset, + compareStringLength) == 0); +} + +void String::Clear() +{ + if (m_pStringData == &s_EmptyStringData) + return; + + // Do we have a shared buffer? If so, cancel it and allocate a new one when we need to. + // Otherwise, clear the current buffer. + if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly) + { + // replace with empty string data + Obliterate(); + } + else + { + // in debug, zero whole string, in release, zero only the first character +#if _DEBUG + std::memset(m_pStringData->pBuffer, 0, m_pStringData->BufferSize); +#else + m_pStringData->pBuffer[0] = '\0'; +#endif + m_pStringData->StringLength = 0; + } +} + +void String::Obliterate() +{ + if (m_pStringData == &s_EmptyStringData) + return; + + // Force a release of the current buffer. + StringDataRelease(m_pStringData); + m_pStringData = const_cast(&s_EmptyStringData); +} + +s32 String::Find(char c, u32 Offset /* = 0*/) const +{ + DebugAssert(Offset <= m_pStringData->StringLength); + char* pAt = std::strchr(m_pStringData->pBuffer + Offset, c); + return (pAt == NULL) ? -1 : s32(pAt - m_pStringData->pBuffer); +} + +s32 String::RFind(char c, u32 Offset /* = 0*/) const +{ + DebugAssert(Offset <= m_pStringData->StringLength); + char* pAt = std::strrchr(m_pStringData->pBuffer + Offset, c); + return (pAt == NULL) ? -1 : s32(pAt - m_pStringData->pBuffer); +} + +s32 String::Find(const char* str, u32 Offset /* = 0 */) const +{ + DebugAssert(Offset <= m_pStringData->StringLength); + char* pAt = std::strstr(m_pStringData->pBuffer + Offset, str); + return (pAt == NULL) ? -1 : s32(pAt - m_pStringData->pBuffer); +} + +void String::Reserve(u32 newReserve, bool Force /* = false */) +{ + DebugAssert(!Force || newReserve >= m_pStringData->StringLength); + + u32 newSize = (Force) ? newReserve + 1 : std::max(newReserve + 1, m_pStringData->BufferSize); + StringData* pNewStringData; + + if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly) + { + pNewStringData = StringDataClone(m_pStringData, newSize, false); + StringDataRelease(m_pStringData); + m_pStringData = pNewStringData; + } + else + { + // skip if smaller, and not forced + if (newSize <= m_pStringData->BufferSize && !Force) + return; + + // if we are the only owner of the buffer, we can simply realloc it + if (m_pStringData->ReferenceCount == 1) + { + // do realloc and update pointer + m_pStringData = StringDataReallocate(m_pStringData, newSize); + } + else + { + // clone and release old + pNewStringData = StringDataClone(m_pStringData, newSize, false); + StringDataRelease(m_pStringData); + m_pStringData = pNewStringData; + } + } +} + +void String::Resize(u32 newSize, char fillerCharacter /* = ' ' */, bool skrinkIfSmaller /* = false */) +{ + StringData* pNewStringData; + + // if going larger, or we don't own the buffer, realloc + if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly || newSize >= m_pStringData->BufferSize) + { + pNewStringData = StringDataClone(m_pStringData, newSize + 1, true); + StringDataRelease(m_pStringData); + m_pStringData = pNewStringData; + + if (m_pStringData->StringLength < newSize) + { + std::memset(m_pStringData->pBuffer + m_pStringData->StringLength, fillerCharacter, + m_pStringData->BufferSize - m_pStringData->StringLength - 1); + } + + m_pStringData->StringLength = newSize; + } + else + { + // owns the buffer, and going smaller + DebugAssert(newSize < m_pStringData->BufferSize); + + // update length and terminator +#if _DEBUG + std::memset(m_pStringData->pBuffer + newSize, 0, m_pStringData->BufferSize - newSize); +#else + m_pStringData->pBuffer[newSize] = 0; +#endif + m_pStringData->StringLength = newSize; + + // shrink if requested + if (skrinkIfSmaller) + Shrink(false); + } +} + +void String::UpdateSize() +{ + EnsureOwnWritableCopy(); + m_pStringData->StringLength = static_cast(std::strlen(m_pStringData->pBuffer)); +} + +void String::Shrink(bool Force /* = false */) +{ + // only shrink of we own the buffer, or forced + if (Force || m_pStringData->ReferenceCount == 1) + Reserve(m_pStringData->StringLength); +} + +String String::SubString(s32 Offset, s32 Count /* = -1 */) const +{ + String returnStr; + returnStr.AppendSubString(*this, Offset, Count); + return returnStr; +} + +void String::Erase(s32 Offset, s32 Count /* = INT_std::max */) +{ + u32 currentLength = m_pStringData->StringLength; + + // calc real offset + u32 realOffset; + if (Offset < 0) + realOffset = (u32)std::max((s32)0, (s32)currentLength + Offset); + else + realOffset = std::min((u32)Offset, currentLength); + + // calc real count + u32 realCount; + if (Count < 0) + realCount = std::min(currentLength - realOffset, (u32)std::max((s32)0, (s32)currentLength + Count)); + else + realCount = std::min(currentLength - realOffset, (u32)Count); + + // Fastpath: offset == 0, count < 0, wipe whole string. + if (realOffset == 0 && realCount == currentLength) + { + Clear(); + return; + } + + // Fastpath: offset >= 0, count < 0, wipe everything after offset + count + if ((realOffset + realCount) == m_pStringData->StringLength) + { + m_pStringData->StringLength -= realCount; +#ifdef _DEBUG + std::memset(m_pStringData->pBuffer + m_pStringData->StringLength, 0, + m_pStringData->BufferSize - m_pStringData->StringLength); +#else + m_pStringData->pBuffer[m_pStringData->StringLength] = 0; +#endif + } + // Slowpath: offset >= 0, count < length + else + { + u32 afterEraseBlock = m_pStringData->StringLength - realOffset - realCount; + DebugAssert(afterEraseBlock > 0); + + std::memmove(m_pStringData->pBuffer + Offset, m_pStringData->pBuffer + realOffset + realCount, afterEraseBlock); + m_pStringData->StringLength = m_pStringData->StringLength - realCount; + +#ifdef _DEBUG + std::memset(m_pStringData->pBuffer + m_pStringData->StringLength, 0, + m_pStringData->BufferSize - m_pStringData->StringLength); +#else + m_pStringData->pBuffer[m_pStringData->StringLength] = 0; +#endif + } +} + +u32 String::Replace(char searchCharacter, char replaceCharacter) +{ + u32 nReplacements = 0; + char* pCurrent = std::strchr(m_pStringData->pBuffer, searchCharacter); + while (pCurrent != NULL) + { + if ((nReplacements++) == 0) + EnsureOwnWritableCopy(); + + *pCurrent = replaceCharacter; + pCurrent = std::strchr(pCurrent + 1, searchCharacter); + } + + return nReplacements; +} + +u32 String::Replace(const char* searchString, const char* replaceString) +{ + u32 nReplacements = 0; + u32 searchStringLength = static_cast(std::strlen(searchString)); + +#if 0 + u32 replaceStringLength = static_cast(std::strlen(replaceString)); + s32 lengthDifference = (s32)replaceStringLength - (s32)searchStringLength; + + char *pCurrent = std::strchr(m_pStringData->pBuffer, searchString); + while (pCurrent != NULL) + { + if ((nReplacements++) == 0) + { + if (lengthDifference > 0) + EnsureRemainingSpace(lengthDifference); + else + EnsureOwnCopy(); + } + else if (lengthDifference > 0) + EnsureRemainingSpace(lengthDifference); + } +#endif + + // TODO: Fastpath if strlen(searchString) == strlen(replaceString) + + String tempString; + char* pStart = m_pStringData->pBuffer; + char* pCurrent = std::strstr(pStart, searchString); + char* pLast = NULL; + while (pCurrent != NULL) + { + if ((nReplacements++) == 0) + tempString.Reserve(m_pStringData->StringLength); + + tempString.AppendSubString(*this, s32(pStart - pCurrent), s32(pStart - pCurrent - 1)); + tempString.AppendString(replaceString); + pLast = pCurrent + searchStringLength; + nReplacements++; + + pCurrent = std::strstr(pLast, searchString); + } + + if (pLast != NULL) + tempString.AppendSubString(*this, s32(pLast - pStart)); + + if (nReplacements) + Swap(tempString); + + return nReplacements; +} + +void String::ToLower() +{ + // fixme for utf8 + EnsureOwnWritableCopy(); + for (u32 i = 0; i < m_pStringData->StringLength; i++) + { + if (std::isprint(m_pStringData->pBuffer[i])) + m_pStringData->pBuffer[i] = static_cast(std::tolower(m_pStringData->pBuffer[i])); + } +} + +void String::ToUpper() +{ + // fixme for utf8 + EnsureOwnWritableCopy(); + for (u32 i = 0; i < m_pStringData->StringLength; i++) + { + if (std::isprint(m_pStringData->pBuffer[i])) + m_pStringData->pBuffer[i] = static_cast(std::toupper(m_pStringData->pBuffer[i])); + } +} + +void String::LStrip(const char* szStripCharacters /* = " " */) +{ + u32 stripCharactersLen = static_cast(std::strlen(szStripCharacters)); + u32 removeCount = 0; + u32 i = 0; + u32 j; + + // for each character in str + for (i = 0; i < m_pStringData->StringLength; i++) + { + char ch = m_pStringData->pBuffer[i]; + + // if it exists in szStripCharacters + for (j = 0; j < stripCharactersLen; j++) + { + if (ch == szStripCharacters[j]) + { + removeCount++; + goto OUTER; + } + } + + // not found, exit + break; + OUTER: + continue; + } + + // chars to remove? + if (removeCount > 0) + Erase(0, removeCount); +} + +void String::RStrip(const char* szStripCharacters /* = " " */) +{ + u32 stripCharactersLen = static_cast(std::strlen(szStripCharacters)); + u32 removeCount = 0; + u32 i = 0; + u32 j; + + // for each character in str + for (i = 0; i < m_pStringData->StringLength; i++) + { + char ch = m_pStringData->pBuffer[m_pStringData->StringLength - i - 1]; + + // if it exists in szStripCharacters + for (j = 0; j < stripCharactersLen; j++) + { + if (ch == szStripCharacters[j]) + { + removeCount++; + goto OUTER; + } + } + + // not found, exit + break; + OUTER: + continue; + } + + // chars to remove? + if (removeCount > 0) + Erase(m_pStringData->StringLength - removeCount); +} + +void String::Strip(const char* szStripCharacters /* = " " */) +{ + RStrip(szStripCharacters); + LStrip(szStripCharacters); +} + +String String::FromFormat(const char* FormatString, ...) +{ + String returnStr; + va_list ap; + + va_start(ap, FormatString); + returnStr.FormatVA(FormatString, ap); + va_end(ap); + + return returnStr; +} diff --git a/jni/common/string.h b/jni/common/string.h new file mode 100644 index 0000000..90d3c59 --- /dev/null +++ b/jni/common/string.h @@ -0,0 +1,379 @@ +#pragma once +#include "types.h" +#include +#include +#include +#include +#include + +// +// String +// Implements a UTF-8 string container with copy-on-write behavior. +// The data class is not currently threadsafe (creating a mutex on each container would be overkill), +// so locking is still required when multiple threads are involved. +// +class String +{ +public: + // Internal StringData class. + struct StringData + { + // Pointer to memory where the string is located + char* pBuffer; + + // Length of the string located in pBuffer (in characters) + u32 StringLength; + + // Size of the buffer pointed to by pBuffer + u32 BufferSize; + + // Reference count of this data object. If set to -1, + // it is considered noncopyable and any copies of the string + // will always create their own copy. + s32 ReferenceCount; + + // Whether the memory pointed to by pBuffer is writable. + bool ReadOnly; + }; + +public: + // Creates an empty string. + String(); + + // Creates a string containing the specified text. + // Note that this will incur a heap allocation, even if Text is on the stack. + // For strings that do not allocate any space on the heap, see StaticString. + String(const char* Text); + + // Creates a string using the same buffer as another string (copy-on-write). + String(const String& copyString); + + // Move constructor, take reference from other string. + String(String&& moveString); + + // Construct a string from a data object, does not increment the reference count on the string data, use carefully. + explicit String(StringData* pStringData) : m_pStringData(pStringData) {} + + // Creates string from string_view. + String(const std::string_view& sv); + + // Destructor. Child classes may not have any destructors, as this is not virtual. + ~String(); + + // manual assignment + void Assign(const String& copyString); + void Assign(const char* copyText); + void Assign(String&& moveString); + + // assignment but ensures that we have our own copy. + void AssignCopy(const String& copyString); + + // Ensures that the string has its own unique copy of the data. + void EnsureOwnWritableCopy(); + + // Ensures that we have our own copy of the buffer, and spaceRequired bytes free in the buffer. + void EnsureRemainingSpace(u32 spaceRequired); + + // clears the contents of the string + void Clear(); + + // clear the contents of the string, and free any memory currently being used + void Obliterate(); + + // swaps strings + void Swap(String& swapString); + + // append a single character to this string + void AppendCharacter(char c); + + // append a string to this string + void AppendString(const String& appendStr); + void AppendString(const char* appendText); + void AppendString(const char* appendString, u32 Count); + + // append a substring of the specified string to this string + void AppendSubString(const String& appendStr, s32 Offset = 0, s32 Count = std::numeric_limits::max()); + void AppendSubString(const char* appendText, s32 Offset = 0, s32 Count = std::numeric_limits::max()); + + // append formatted string to this string + void AppendFormattedString(const char* FormatString, ...); + void AppendFormattedStringVA(const char* FormatString, va_list ArgPtr); + + // append a single character to this string + void PrependCharacter(char c); + + // append a string to this string + void PrependString(const String& appendStr); + void PrependString(const char* appendText); + void PrependString(const char* appendString, u32 Count); + + // append a substring of the specified string to this string + void PrependSubString(const String& appendStr, s32 Offset = 0, s32 Count = std::numeric_limits::max()); + void PrependSubString(const char* appendText, s32 Offset = 0, s32 Count = std::numeric_limits::max()); + + // append formatted string to this string + void PrependFormattedString(const char* FormatString, ...); + void PrependFormattedStringVA(const char* FormatString, va_list ArgPtr); + + // insert a string at the specified offset + void InsertString(s32 offset, const String& appendStr); + void InsertString(s32 offset, const char* appendStr); + void InsertString(s32 offset, const char* appendStr, u32 appendStrLength); + + // set to formatted string + void Format(const char* FormatString, ...); + void FormatVA(const char* FormatString, va_list ArgPtr); + + // compare one string to another + bool Compare(const String& otherString) const; + bool Compare(const char* otherText) const; + bool SubCompare(const String& otherString, u32 Length) const; + bool SubCompare(const char* otherText, u32 Length) const; + bool CompareInsensitive(const String& otherString) const; + bool CompareInsensitive(const char* otherText) const; + bool SubCompareInsensitive(const String& otherString, u32 Length) const; + bool SubCompareInsensitive(const char* otherText, u32 Length) const; + + // numerical compares + int NumericCompare(const String& otherString) const; + int NumericCompare(const char* otherText) const; + int NumericCompareInsensitive(const String& otherString) const; + int NumericCompareInsensitive(const char* otherText) const; + + // starts with / ends with + bool StartsWith(const char* compareString, bool caseSensitive = true) const; + bool StartsWith(const String& compareString, bool caseSensitive = true) const; + bool EndsWith(const char* compareString, bool caseSensitive = true) const; + bool EndsWith(const String& compareString, bool caseSensitive = true) const; + + // searches for a character inside a string + // rfind is the same except it starts at the end instead of the start + // returns -1 if it is not found, otherwise the offset in the string + s32 Find(char c, u32 Offset = 0) const; + s32 RFind(char c, u32 Offset = 0) const; + + // searches for a string inside a string + // rfind is the same except it starts at the end instead of the start + // returns -1 if it is not found, otherwise the offset in the string + s32 Find(const char* str, u32 Offset = 0) const; + + // alters the length of the string to be at least len bytes long + void Reserve(u32 newReserve, bool Force = false); + + // Cuts characters off the string to reduce it to len bytes long. + void Resize(u32 newSize, char fillerCharacter = ' ', bool skrinkIfSmaller = false); + + // updates the internal length counter when the string is externally modified + void UpdateSize(); + + // shrink the string to the minimum size possible + void Shrink(bool Force = false); + + // gets the size of the string + u32 GetLength() const { return m_pStringData->StringLength; } + bool IsEmpty() const { return (m_pStringData->StringLength == 0); } + + // gets the maximum number of bytes we can write to the string, currently + u32 GetBufferSize() const { return m_pStringData->BufferSize; } + u32 GetWritableBufferSize() + { + EnsureOwnWritableCopy(); + return m_pStringData->BufferSize; + } + + // creates a new string using part of this string + String SubString(s32 Offset, s32 Count = std::numeric_limits::max()) const; + + // erase count characters at offset from this string. if count is less than zero, everything past offset is erased + void Erase(s32 Offset, s32 Count = std::numeric_limits::max()); + + // replaces all instances of character c with character r in this string + // returns the number of changes + u32 Replace(char searchCharacter, char replaceCharacter); + + // replaces all instances of string s with string r in this string + // returns the number of changes + u32 Replace(const char* searchString, const char* replaceString); + + // convert string to lowercase + void ToLower(); + + // convert string to upper + void ToUpper(); + + // strip characters from start and end of the string + void LStrip(const char* szStripCharacters = " \t\r\n"); + void RStrip(const char* szStripCharacters = " \t\r\n"); + void Strip(const char* szStripCharacters = " \t\r\n"); + + // gets a constant pointer to the string + const char* GetCharArray() const { return m_pStringData->pBuffer; } + + // gets a writable char array, do not write more than reserve characters to it. + char* GetWriteableCharArray() + { + EnsureOwnWritableCopy(); + return m_pStringData->pBuffer; + } + + // creates a new string from the specified format + static String FromFormat(const char* FormatString, ...); + + // accessor operators + // const char &operator[](u32 i) const { DebugAssert(i < m_pStringData->StringLength); return + // m_pStringData->pBuffer[i]; } char &operator[](u32 i) { DebugAssert(i < m_pStringData->StringLength); return + // m_pStringData->pBuffer[i]; } + operator const char*() const { return GetCharArray(); } + operator char*() { return GetWriteableCharArray(); } + operator std::string_view() const + { + return IsEmpty() ? std::string_view() : std::string_view(GetCharArray(), GetLength()); + } + + // Will use the string data provided. + String& operator=(const String& copyString) + { + Assign(copyString); + return *this; + } + + // Allocates own buffer and copies text. + String& operator=(const char* Text) + { + Assign(Text); + return *this; + } + + // Move operator. + String& operator=(String&& moveString) + { + Assign(moveString); + return *this; + } + + // comparative operators + bool operator==(const String& compString) const { return Compare(compString); } + bool operator==(const char* compString) const { return Compare(compString); } + bool operator!=(const String& compString) const { return !Compare(compString); } + bool operator!=(const char* compString) const { return !Compare(compString); } + bool operator<(const String& compString) const { return (NumericCompare(compString) < 0); } + bool operator<(const char* compString) const { return (NumericCompare(compString) < 0); } + bool operator>(const String& compString) const { return (NumericCompare(compString) > 0); } + bool operator>(const char* compString) const { return (NumericCompare(compString) > 0); } + +protected: + // Internal append function. + void InternalPrepend(const char* pString, u32 Length); + void InternalAppend(const char* pString, u32 Length); + + // Pointer to string data. + StringData* m_pStringData; + + // Empty string data. + static const StringData s_EmptyStringData; +}; + +// static string, stored in .rodata +#define StaticString(Text) \ + []() noexcept -> String { \ + static constexpr u32 buffer_size = sizeof(Text); \ + static constexpr u32 length = buffer_size - 1; \ + static constexpr String::StringData data{const_cast(Text), length, buffer_size, static_cast(-1), \ + true}; \ + return String(const_cast(&data)); \ + }() + +// stack-allocated string +template +class StackString : public String +{ +public: + StackString() : String(&m_sStringData) { InitStackStringData(); } + + StackString(const char* Text) : String(&m_sStringData) + { + InitStackStringData(); + Assign(Text); + } + + StackString(const String& copyString) : String(&m_sStringData) + { + // force a copy by passing it a string pointer, instead of a string object + InitStackStringData(); + Assign(copyString.GetCharArray()); + } + + StackString(const StackString& copyString) : String(&m_sStringData) + { + // force a copy by passing it a string pointer, instead of a string object + InitStackStringData(); + Assign(copyString.GetCharArray()); + } + + StackString(const std::string_view& sv) : String(&m_sStringData) + { + InitStackStringData(); + AppendString(sv.data(), static_cast(sv.size())); + } + + // Override the fromstring method + static StackString FromFormat(const char* FormatString, ...) + { + va_list argPtr; + va_start(argPtr, FormatString); + + StackString returnValue; + returnValue.FormatVA(FormatString, argPtr); + + va_end(argPtr); + + return returnValue; + } + + // Will use the string data provided. + StackString& operator=(const StackString& copyString) + { + Assign(copyString.GetCharArray()); + return *this; + } + StackString& operator=(const String& copyString) + { + Assign(copyString.GetCharArray()); + return *this; + } + + // Allocates own buffer and copies text. + StackString& operator=(const char* Text) + { + Assign(Text); + return *this; + } + +private: + StringData m_sStringData; + char m_strStackBuffer[L + 1]; + + inline void InitStackStringData() + { + m_sStringData.pBuffer = m_strStackBuffer; + m_sStringData.StringLength = 0; + m_sStringData.BufferSize = countof(m_strStackBuffer); + m_sStringData.ReadOnly = false; + m_sStringData.ReferenceCount = -1; + +#ifdef _DEBUG + std::memset(m_strStackBuffer, 0, sizeof(m_strStackBuffer)); +#else + m_strStackBuffer[0] = '\0'; +#endif + } +}; + +// stack string types +typedef StackString<64> TinyString; +typedef StackString<256> SmallString; +typedef StackString<512> LargeString; +typedef StackString<512> PathString; + +// empty string global +extern const String EmptyString; diff --git a/jni/common/string_util.cpp b/jni/common/string_util.cpp new file mode 100644 index 0000000..420830d --- /dev/null +++ b/jni/common/string_util.cpp @@ -0,0 +1,217 @@ +#include "string_util.h" +#include +#include +#include + +#ifdef WIN32 +#include "windows_headers.h" +#endif + +namespace StringUtil { + +std::string StdStringFromFormat(const char* format, ...) +{ + std::va_list ap; + va_start(ap, format); + std::string ret = StdStringFromFormatV(format, ap); + va_end(ap); + return ret; +} + +std::string StdStringFromFormatV(const char* format, std::va_list ap) +{ + std::va_list ap_copy; + va_copy(ap_copy, ap); + +#ifdef WIN32 + int len = _vscprintf(format, ap_copy); +#else + int len = std::vsnprintf(nullptr, 0, format, ap_copy); +#endif + va_end(ap_copy); + + std::string ret; + ret.resize(len); + std::vsnprintf(ret.data(), ret.size() + 1, format, ap); + return ret; +} + +bool WildcardMatch(const char* subject, const char* mask, bool case_sensitive /*= true*/) +{ + if (case_sensitive) + { + const char* cp = nullptr; + const char* mp = nullptr; + + while ((*subject) && (*mask != '*')) + { + if ((*mask != '?') && (std::tolower(*mask) != std::tolower(*subject))) + return false; + + mask++; + subject++; + } + + while (*subject) + { + if (*mask == '*') + { + if (*++mask == 0) + return true; + + mp = mask; + cp = subject + 1; + } + else + { + if ((*mask == '?') || (std::tolower(*mask) == std::tolower(*subject))) + { + mask++; + subject++; + } + else + { + mask = mp; + subject = cp++; + } + } + } + + while (*mask == '*') + { + mask++; + } + + return *mask == 0; + } + else + { + const char* cp = nullptr; + const char* mp = nullptr; + + while ((*subject) && (*mask != '*')) + { + if ((*mask != *subject) && (*mask != '?')) + return false; + + mask++; + subject++; + } + + while (*subject) + { + if (*mask == '*') + { + if (*++mask == 0) + return true; + + mp = mask; + cp = subject + 1; + } + else + { + if ((*mask == *subject) || (*mask == '?')) + { + mask++; + subject++; + } + else + { + mask = mp; + subject = cp++; + } + } + } + + while (*mask == '*') + { + mask++; + } + + return *mask == 0; + } +} + +std::size_t Strlcpy(char* dst, const char* src, std::size_t size) +{ + std::size_t len = std::strlen(src); + if (len < size) + { + std::memcpy(dst, src, len + 1); + } + else + { + std::memcpy(dst, src, size - 1); + dst[size - 1] = '\0'; + } + return len; +} + +std::size_t Strlcpy(char* dst, const std::string_view& src, std::size_t size) +{ + std::size_t len = src.length(); + if (len < size) + { + std::memcpy(dst, src.data(), len); + dst[len] = '\0'; + } + else + { + std::memcpy(dst, src.data(), size - 1); + dst[size - 1] = '\0'; + } + return len; +} + +#ifdef WIN32 + +std::wstring UTF8StringToWideString(const std::string_view& str) +{ + std::wstring ret; + if (!UTF8StringToWideString(ret, str)) + return {}; + + return ret; +} + +bool UTF8StringToWideString(std::wstring& dest, const std::string_view& str) +{ + int wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.length()), nullptr, 0); + if (wlen < 0) + return false; + + dest.resize(wlen); + if (wlen > 0 && MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.length()), dest.data(), wlen) < 0) + return false; + + return true; +} + +std::string WideStringToUTF8String(const std::wstring_view& str) +{ + std::string ret; + if (!WideStringToUTF8String(ret, str)) + return {}; + + return ret; +} + +bool WideStringToUTF8String(std::string& dest, const std::wstring_view& str) +{ + int mblen = WideCharToMultiByte(CP_UTF8, 0, str.data(), static_cast(str.length()), nullptr, 0, nullptr, nullptr); + if (mblen < 0) + return false; + + dest.resize(mblen); + if (mblen > 0 && WideCharToMultiByte(CP_UTF8, 0, str.data(), static_cast(str.length()), dest.data(), mblen, + nullptr, nullptr) < 0) + { + return false; + } + + return true; +} + +#endif + +} // namespace StringUtil diff --git a/jni/common/string_util.h b/jni/common/string_util.h new file mode 100644 index 0000000..f1a4311 --- /dev/null +++ b/jni/common/string_util.h @@ -0,0 +1,133 @@ +#pragma once +#include "types.h" +#include +#include +#include +#include +#include +#include + +#if defined(__has_include) && __has_include() +#include +#ifndef _MSC_VER +#include +#endif +#else +#include +#endif + +namespace StringUtil { + +/// Constructs a std::string from a format string. +std::string StdStringFromFormat(const char* format, ...); +std::string StdStringFromFormatV(const char* format, std::va_list ap); + +/// Checks if a wildcard matches a search string. +bool WildcardMatch(const char* subject, const char* mask, bool case_sensitive = true); + +/// Safe version of strlcpy. +std::size_t Strlcpy(char* dst, const char* src, std::size_t size); + +/// Strlcpy from string_view. +std::size_t Strlcpy(char* dst, const std::string_view& src, std::size_t size); + +/// Platform-independent strcasecmp +static inline int Strcasecmp(const char* s1, const char* s2) +{ +#ifdef _MSC_VER + return _stricmp(s1, s2); +#else + return strcasecmp(s1, s2); +#endif +} + +/// Platform-independent strcasecmp +static inline int Strncasecmp(const char* s1, const char* s2, std::size_t n) +{ +#ifdef _MSC_VER + return _strnicmp(s1, s2, n); +#else + return strncasecmp(s1, s2, n); +#endif +} + +/// Wrapper arond std::from_chars +template +inline std::optional FromChars(const std::string_view& str) +{ + T value; + +#if defined(__has_include) && __has_include() + const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value); + if (result.ec != std::errc()) + return std::nullopt; +#else + std::string temp(str); + std::istringstream ss(temp); + ss >> value; + if (ss.fail()) + return std::nullopt; +#endif + + return value; +} + +/// Explicit override for booleans +template<> +inline std::optional FromChars(const std::string_view& str) +{ + if (Strncasecmp("true", str.data(), str.length()) == 0 || Strncasecmp("yes", str.data(), str.length()) == 0 || + Strncasecmp("on", str.data(), str.length()) == 0 || Strncasecmp("1", str.data(), str.length()) == 0) + { + return true; + } + + if (Strncasecmp("false", str.data(), str.length()) == 0 || Strncasecmp("no", str.data(), str.length()) == 0 || + Strncasecmp("off", str.data(), str.length()) == 0 || Strncasecmp("0", str.data(), str.length()) == 0) + { + return false; + } + + return std::nullopt; +} + +#ifndef _MSC_VER +/// from_chars doesn't seem to work with floats on gcc +template<> +inline std::optional FromChars(const std::string_view& str) +{ + float value; + std::string temp(str); + std::istringstream ss(temp); + ss >> value; + if (ss.fail()) + return std::nullopt; + else + return value; +} +#endif + +/// starts_with from C++20 +ALWAYS_INLINE static bool StartsWith(const std::string_view& str, const char* prefix) +{ + return (str.compare(0, std::strlen(prefix), prefix) == 0); +} +ALWAYS_INLINE static bool EndsWith(const std::string_view& str, const char* suffix) +{ + const std::size_t suffix_length = std::strlen(suffix); + return (str.length() >= suffix_length && str.compare(str.length() - suffix_length, suffix_length, suffix) == 0); +} + +#ifdef WIN32 + +/// Converts the specified UTF-8 string to a wide string. +std::wstring UTF8StringToWideString(const std::string_view& str); +bool UTF8StringToWideString(std::wstring& dest, const std::string_view& str); + +/// Converts the specified wide string to a UTF-8 string. +std::string WideStringToUTF8String(const std::wstring_view& str); +bool WideStringToUTF8String(std::string& dest, const std::wstring_view& str); + +#endif + +} // namespace StringUtil diff --git a/jni/common/timer.cpp b/jni/common/timer.cpp new file mode 100644 index 0000000..84589ea --- /dev/null +++ b/jni/common/timer.cpp @@ -0,0 +1,127 @@ +#include "timer.h" + +#ifdef WIN32 +#include "windows_headers.h" +#else +#include +#include +#endif + +namespace Common { + +#ifdef WIN32 + +static double s_counter_frequency; +static bool s_counter_initialized = false; + +Timer::Value Timer::GetValue() +{ + // even if this races, it should still result in the same value.. + if (!s_counter_initialized) + { + LARGE_INTEGER Freq; + QueryPerformanceFrequency(&Freq); + s_counter_frequency = static_cast(Freq.QuadPart) / 1000000000.0; + s_counter_initialized = true; + } + + Timer::Value ReturnValue; + QueryPerformanceCounter(reinterpret_cast(&ReturnValue)); + return ReturnValue; +} + +double Timer::ConvertValueToNanoseconds(Timer::Value value) +{ + return (static_cast(value) / s_counter_frequency); +} + +double Timer::ConvertValueToMilliseconds(Timer::Value value) +{ + return ((static_cast(value) / s_counter_frequency) / 1000000.0); +} + +double Timer::ConvertValueToSeconds(Timer::Value value) +{ + return ((static_cast(value) / s_counter_frequency) / 1000000000.0); +} + +#else + +#if 1 // using clock_gettime() + +Timer::Value Timer::GetValue() +{ + struct timespec tv; + clock_gettime(CLOCK_MONOTONIC, &tv); + return ((Value)tv.tv_nsec + (Value)tv.tv_sec * 1000000000); +} + +double Timer::ConvertValueToNanoseconds(Timer::Value value) +{ + return static_cast(value); +} + +double Timer::ConvertValueToMilliseconds(Timer::Value value) +{ + return (static_cast(value) / 1000000.0); +} + +double Timer::ConvertValueToSeconds(Timer::Value value) +{ + return (static_cast(value) / 1000000000.0); +} + +#else // using gettimeofday() + +Timer::Value Timer::GetValue() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return ((Value)tv.tv_usec) + ((Value)tv.tv_sec * (Value)1000000); +} + +double Timer::ConvertValueToNanoseconds(Timer::Value value) +{ + return ((double)value * 1000.0); +} + +double Timer::ConvertValueToMilliseconds(Timer::Value value) +{ + return ((double)value / 1000.0); +} + +double Timer::ConvertValueToSeconds(Timer::Value value) +{ + return ((double)value / 1000000.0); +} + +#endif + +#endif + +Timer::Timer() +{ + Reset(); +} + +void Timer::Reset() +{ + m_tvStartValue = GetValue(); +} + +double Timer::GetTimeSeconds() const +{ + return ConvertValueToSeconds(GetValue() - m_tvStartValue); +} + +double Timer::GetTimeMilliseconds() const +{ + return ConvertValueToMilliseconds(GetValue() - m_tvStartValue); +} + +double Timer::GetTimeNanoseconds() const +{ + return ConvertValueToNanoseconds(GetValue() - m_tvStartValue); +} + +} // namespace Common \ No newline at end of file diff --git a/jni/common/timer.h b/jni/common/timer.h new file mode 100644 index 0000000..5d105d8 --- /dev/null +++ b/jni/common/timer.h @@ -0,0 +1,29 @@ +#pragma once +#include "types.h" +#include + +namespace Common { + +class Timer +{ +public: + using Value = u64; + + Timer(); + + static Value GetValue(); + static double ConvertValueToSeconds(Value value); + static double ConvertValueToMilliseconds(Value value); + static double ConvertValueToNanoseconds(Value value); + + void Reset(); + + double GetTimeSeconds() const; + double GetTimeMilliseconds() const; + double GetTimeNanoseconds() const; + +private: + Value m_tvStartValue; +}; + +} // namespace Common \ No newline at end of file diff --git a/jni/common/timestamp.cpp b/jni/common/timestamp.cpp new file mode 100644 index 0000000..d980a65 --- /dev/null +++ b/jni/common/timestamp.cpp @@ -0,0 +1,514 @@ +#include "timestamp.h" +#include +#include + +#if defined(WIN32) + +static void UnixTimeToSystemTime(time_t t, LPSYSTEMTIME pst); +static time_t SystemTimeToUnixTime(const SYSTEMTIME* pst); + +#endif + +Timestamp::Timestamp() +{ +#if defined(WIN32) + m_value.wYear = 1970; + m_value.wMonth = 1; + m_value.wDayOfWeek = 0; + m_value.wDay = 1; + m_value.wHour = 0; + m_value.wMinute = 0; + m_value.wSecond = 0; + m_value.wMilliseconds = 0; +#else + m_value.tv_sec = 0; + m_value.tv_usec = 0; +#endif +} + +Timestamp::Timestamp(const Timestamp& copy) +{ +#if defined(WIN32) + std::memcpy(&m_value, ©.m_value, sizeof(m_value)); +#else + std::memcpy(&m_value, ©.m_value, sizeof(m_value)); +#endif +} + +double Timestamp::DifferenceInSeconds(Timestamp& other) const +{ +#if defined(WIN32) + FILETIME lft, rft; + SystemTimeToFileTime(&m_value, &lft); + SystemTimeToFileTime(&other.m_value, &rft); + + u64 lval = ((u64)lft.dwHighDateTime) << 32 | (u64)lft.dwLowDateTime; + u64 rval = ((u64)rft.dwHighDateTime) << 32 | (u64)rft.dwLowDateTime; + s64 diff = ((s64)lval - (s64)rval); + return double(diff / 10000000ULL) + (double(diff % 10000000ULL) / 10000000.0); + +#else + return (double)(m_value.tv_sec - other.m_value.tv_sec) + + (((double)(m_value.tv_usec - other.m_value.tv_usec)) / 1000000.0); +#endif +} + +s64 Timestamp::DifferenceInSecondsInt(Timestamp& other) const +{ +#if defined(WIN32) + FILETIME lft, rft; + SystemTimeToFileTime(&m_value, &lft); + SystemTimeToFileTime(&other.m_value, &rft); + + u64 lval = ((u64)lft.dwHighDateTime) << 32 | (u64)lft.dwLowDateTime; + u64 rval = ((u64)rft.dwHighDateTime) << 32 | (u64)rft.dwLowDateTime; + s64 diff = ((s64)lval - (s64)rval); + return diff / 10000000ULL; + +#else + return static_cast(m_value.tv_sec - other.m_value.tv_sec); +#endif +} + +Timestamp::UnixTimestampValue Timestamp::AsUnixTimestamp() const +{ +#if defined(WIN32) + return (UnixTimestampValue)SystemTimeToUnixTime(&m_value); +#else + return (UnixTimestampValue)m_value.tv_sec; +#endif +} + +Timestamp::ExpandedTime Timestamp::AsExpandedTime() const +{ + ExpandedTime et; + +#if defined(WIN32) + et.Year = m_value.wYear; + et.Month = m_value.wMonth; + et.DayOfMonth = m_value.wDay; + et.DayOfWeek = m_value.wDayOfWeek; + et.Hour = m_value.wHour; + et.Minute = m_value.wMinute; + et.Second = m_value.wSecond; + et.Milliseconds = m_value.wMilliseconds; +#else + struct tm t; + time_t unixTime = (time_t)m_value.tv_sec; + gmtime_r(&unixTime, &t); + et.Year = t.tm_year + 1900; + et.Month = t.tm_mon + 1; + et.DayOfMonth = t.tm_mday; + et.DayOfWeek = t.tm_wday; + et.Hour = t.tm_hour; + et.Minute = t.tm_min; + et.Second = t.tm_sec; + et.Milliseconds = m_value.tv_usec / 1000; +#endif + + return et; +} + +void Timestamp::SetNow() +{ +#if defined(WIN32) + GetSystemTime(&m_value); +#else + gettimeofday(&m_value, NULL); +#endif +} + +void Timestamp::SetUnixTimestamp(UnixTimestampValue value) +{ +#if defined(WIN32) + UnixTimeToSystemTime((time_t)value, &m_value); +#else + m_value.tv_sec = (time_t)value; + m_value.tv_usec = 0; +#endif +} + +void Timestamp::SetExpandedTime(const ExpandedTime& value) +{ +#if defined(WIN32) + // bit of a hacky way to fill in the missing fields + SYSTEMTIME st; + st.wYear = (WORD)value.Year; + st.wMonth = (WORD)value.Month; + st.wDay = (WORD)value.DayOfMonth; + st.wDayOfWeek = (WORD)0; + st.wHour = (WORD)value.Hour; + st.wMinute = (WORD)value.Minute; + st.wSecond = (WORD)value.Second; + st.wMilliseconds = (WORD)value.Milliseconds; + FILETIME ft; + SystemTimeToFileTime(&st, &ft); + FileTimeToSystemTime(&ft, &m_value); +#else + struct tm t; + std::memset(&t, 0, sizeof(t)); + t.tm_sec = value.Second; + t.tm_min = value.Minute; + t.tm_hour = value.Hour; + t.tm_mday = value.DayOfMonth; + t.tm_mon = value.Month - 1; + t.tm_year = value.Year - 1900; + time_t unixTime = mktime(&t); + SetUnixTimestamp((UnixTimestampValue)unixTime); +#endif +} + +String Timestamp::ToString(const char* format) const +{ + SmallString destination; + ToString(destination, format); + return String(destination); +} + +void Timestamp::ToString(String& destination, const char* format) const +{ + time_t unixTime = (time_t)AsUnixTimestamp(); + tm localTime; + +#if defined(WIN32) + localtime_s(&localTime, &unixTime); +#else + localtime_r(&unixTime, &localTime); +#endif + + char buffer[256]; + strftime(buffer, countof(buffer) - 1, format, &localTime); + buffer[countof(buffer) - 1] = 0; + + destination.Clear(); + destination.AppendString(buffer); +} + +Timestamp Timestamp::Now() +{ + Timestamp t; + t.SetNow(); + return t; +} + +Timestamp Timestamp::FromUnixTimestamp(UnixTimestampValue value) +{ + Timestamp t; + t.SetUnixTimestamp(value); + return t; +} + +Timestamp Timestamp::FromExpandedTime(const ExpandedTime& value) +{ + Timestamp t; + t.SetExpandedTime(value); + return t; +} + +bool Timestamp::operator==(const Timestamp& other) const +{ +#if defined(WIN32) + return std::memcmp(&m_value, &other.m_value, sizeof(m_value)) == 0; +#else + return std::memcmp(&m_value, &other.m_value, sizeof(m_value)) == 0; +#endif +} + +bool Timestamp::operator!=(const Timestamp& other) const +{ + return !operator==(other); +} + +bool Timestamp::operator<(const Timestamp& other) const +{ +#if defined(WIN32) + + if (m_value.wYear > other.m_value.wYear) + return false; + else if (m_value.wYear < other.m_value.wYear) + return true; + + if (m_value.wMonth > other.m_value.wMonth) + return false; + else if (m_value.wMonth < other.m_value.wMonth) + return true; + + if (m_value.wDay > other.m_value.wDay) + return false; + else if (m_value.wDay < other.m_value.wDay) + return true; + + if (m_value.wHour > other.m_value.wHour) + return false; + else if (m_value.wHour < other.m_value.wHour) + return true; + + if (m_value.wMinute > other.m_value.wMinute) + return false; + else if (m_value.wMinute < other.m_value.wMinute) + return true; + + if (m_value.wSecond > other.m_value.wSecond) + return false; + else if (m_value.wSecond < other.m_value.wSecond) + return true; + + if (m_value.wMilliseconds > other.m_value.wMilliseconds) + return false; + else if (m_value.wMilliseconds < other.m_value.wMilliseconds) + return true; + + return false; + +#else + + if (m_value.tv_sec > other.m_value.tv_sec) + return false; + else if (m_value.tv_sec < other.m_value.tv_sec) + return true; + + if (m_value.tv_usec > other.m_value.tv_usec) + return false; + else if (m_value.tv_usec < other.m_value.tv_usec) + return true; + + return false; + +#endif +} + +bool Timestamp::operator<=(const Timestamp& other) const +{ +#if defined(WIN32) + + if (m_value.wYear > other.m_value.wYear) + return false; + else if (m_value.wYear < other.m_value.wYear) + return true; + + if (m_value.wMonth > other.m_value.wMonth) + return false; + else if (m_value.wMonth < other.m_value.wMonth) + return true; + + if (m_value.wDay > other.m_value.wDay) + return false; + else if (m_value.wDay < other.m_value.wDay) + return true; + + if (m_value.wHour > other.m_value.wHour) + return false; + else if (m_value.wHour < other.m_value.wHour) + return true; + + if (m_value.wMinute > other.m_value.wMinute) + return false; + else if (m_value.wMinute < other.m_value.wMinute) + return true; + + if (m_value.wSecond > other.m_value.wSecond) + return false; + else if (m_value.wSecond <= other.m_value.wSecond) + return true; + + if (m_value.wMilliseconds > other.m_value.wMilliseconds) + return false; + else if (m_value.wMilliseconds < other.m_value.wMilliseconds) + return true; + + return false; + +#else + + if (m_value.tv_sec > other.m_value.tv_sec) + return false; + else if (m_value.tv_sec < other.m_value.tv_sec) + return true; + + if (m_value.tv_usec > other.m_value.tv_usec) + return false; + else if (m_value.tv_usec <= other.m_value.tv_usec) + return true; + + return false; + +#endif +} + +bool Timestamp::operator>(const Timestamp& other) const +{ +#if defined(WIN32) + + if (m_value.wYear < other.m_value.wYear) + return false; + else if (m_value.wYear > other.m_value.wYear) + return true; + + if (m_value.wMonth < other.m_value.wMonth) + return false; + else if (m_value.wMonth > other.m_value.wMonth) + return true; + + if (m_value.wDay < other.m_value.wDay) + return false; + else if (m_value.wDay > other.m_value.wDay) + return true; + + if (m_value.wHour < other.m_value.wHour) + return false; + else if (m_value.wHour > other.m_value.wHour) + return true; + + if (m_value.wMinute < other.m_value.wMinute) + return false; + else if (m_value.wMinute > other.m_value.wMinute) + return true; + + if (m_value.wSecond < other.m_value.wSecond) + return false; + else if (m_value.wSecond > other.m_value.wSecond) + return true; + + if (m_value.wMilliseconds < other.m_value.wMilliseconds) + return false; + else if (m_value.wMilliseconds > other.m_value.wMilliseconds) + return true; + + return false; + +#else + + if (m_value.tv_sec < other.m_value.tv_sec) + return false; + else if (m_value.tv_sec > other.m_value.tv_sec) + return true; + + if (m_value.tv_usec < other.m_value.tv_usec) + return false; + else if (m_value.tv_usec > other.m_value.tv_usec) + return true; + + return false; + +#endif +} + +bool Timestamp::operator>=(const Timestamp& other) const +{ +#if defined(WIN32) + + if (m_value.wYear < other.m_value.wYear) + return false; + else if (m_value.wYear > other.m_value.wYear) + return true; + + if (m_value.wMonth < other.m_value.wMonth) + return false; + else if (m_value.wMonth > other.m_value.wMonth) + return true; + + if (m_value.wDay < other.m_value.wDay) + return false; + else if (m_value.wDay > other.m_value.wDay) + return true; + + if (m_value.wHour < other.m_value.wHour) + return false; + else if (m_value.wHour > other.m_value.wHour) + return true; + + if (m_value.wMinute < other.m_value.wMinute) + return false; + else if (m_value.wMinute > other.m_value.wMinute) + return true; + + if (m_value.wSecond < other.m_value.wSecond) + return false; + else if (m_value.wSecond >= other.m_value.wSecond) + return true; + + if (m_value.wMilliseconds < other.m_value.wMilliseconds) + return false; + else if (m_value.wMilliseconds > other.m_value.wMilliseconds) + return true; + + return false; + +#else + + if (m_value.tv_sec < other.m_value.tv_sec) + return false; + else if (m_value.tv_sec > other.m_value.tv_sec) + return true; + + if (m_value.tv_usec < other.m_value.tv_usec) + return false; + else if (m_value.tv_usec >= other.m_value.tv_usec) + return true; + + return false; + +#endif +} + +Timestamp& Timestamp::operator=(const Timestamp& other) +{ +#if defined(WIN32) + std::memcpy(&m_value, &other.m_value, sizeof(m_value)); +#else + std::memcpy(&m_value, &other.m_value, sizeof(m_value)); +#endif + + return *this; +} + +#if defined(WIN32) + +// http://support.microsoft.com/kb/167296 +static void UnixTimeToFileTime(time_t t, LPFILETIME pft) +{ + LONGLONG ll; + ll = Int32x32To64(t, 10000000ULL) + 116444736000000000ULL; + pft->dwLowDateTime = (DWORD)ll; + pft->dwHighDateTime = ll >> 32; +} +static void UnixTimeToSystemTime(time_t t, LPSYSTEMTIME pst) +{ + FILETIME ft; + UnixTimeToFileTime(t, &ft); + FileTimeToSystemTime(&ft, pst); +} +static time_t FileTimeToUnixTime(const FILETIME* pft) +{ + LONGLONG ll = ((LONGLONG)pft->dwHighDateTime) << 32 | (LONGLONG)pft->dwLowDateTime; + ll -= 116444736000000000ULL; + ll /= 10000000ULL; + return (time_t)ll; +} +static time_t SystemTimeToUnixTime(const SYSTEMTIME* pst) +{ + FILETIME ft; + SystemTimeToFileTime(pst, &ft); + return FileTimeToUnixTime(&ft); +} + +FILETIME Timestamp::AsFileTime() +{ + FILETIME ft; + SystemTimeToFileTime(&m_value, &ft); + return ft; +} + +void Timestamp::SetWindowsFileTime(const FILETIME* pFileTime) +{ + FileTimeToSystemTime(pFileTime, &m_value); +} + +Timestamp Timestamp::FromWindowsFileTime(const FILETIME* pFileTime) +{ + Timestamp ts; + ts.SetWindowsFileTime(pFileTime); + return ts; +} + +#endif diff --git a/jni/common/timestamp.h b/jni/common/timestamp.h new file mode 100644 index 0000000..73d4622 --- /dev/null +++ b/jni/common/timestamp.h @@ -0,0 +1,75 @@ +#pragma once +#include "types.h" +#include "string.h" + +#if defined(WIN32) +#include "windows_headers.h" +#else +#include +#endif + +class Timestamp +{ +public: + using UnixTimestampValue = u64; + struct ExpandedTime + { + u32 Year; // 0-... + u32 Month; // 1-12 + u32 DayOfMonth; // 1-31 + u32 DayOfWeek; // 0-6, starting at Sunday + u32 Hour; // 0-23 + u32 Minute; // 0-59 + u32 Second; // 0-59 + u32 Milliseconds; // 0-999 + }; + +public: + Timestamp(); + Timestamp(const Timestamp& copy); + + // readers + UnixTimestampValue AsUnixTimestamp() const; + ExpandedTime AsExpandedTime() const; + + // calculators + double DifferenceInSeconds(Timestamp& other) const; + s64 DifferenceInSecondsInt(Timestamp& other) const; + + // setters + void SetNow(); + void SetUnixTimestamp(UnixTimestampValue value); + void SetExpandedTime(const ExpandedTime& value); + + // string conversion + String ToString(const char* format) const; + void ToString(String& destination, const char* format) const; + + // creators + static Timestamp Now(); + static Timestamp FromUnixTimestamp(UnixTimestampValue value); + static Timestamp FromExpandedTime(const ExpandedTime& value); + +// windows-specific +#ifdef WIN32 + FILETIME AsFileTime(); + void SetWindowsFileTime(const FILETIME* pFileTime); + static Timestamp FromWindowsFileTime(const FILETIME* pFileTime); +#endif + + // operators + bool operator==(const Timestamp& other) const; + bool operator!=(const Timestamp& other) const; + bool operator<(const Timestamp& other) const; + bool operator<=(const Timestamp& other) const; + bool operator>(const Timestamp& other) const; + bool operator>=(const Timestamp& other) const; + Timestamp& operator=(const Timestamp& other); + +private: +#if defined(WIN32) + SYSTEMTIME m_value; +#else + struct timeval m_value; +#endif +}; diff --git a/jni/common/types.h b/jni/common/types.h new file mode 100644 index 0000000..839b585 --- /dev/null +++ b/jni/common/types.h @@ -0,0 +1,241 @@ +#pragma once +#include +#include +#include +#include + +// Force inline helper +#ifndef ALWAYS_INLINE +#if defined(_MSC_VER) +#define ALWAYS_INLINE __forceinline +#elif defined(__GNUC__) || defined(__clang__) +#define ALWAYS_INLINE __attribute__((always_inline)) inline +#else +#define ALWAYS_INLINE inline +#endif +#endif + +// Force inline in non-debug helper +#ifdef _DEBUG +#define ALWAYS_INLINE_RELEASE inline +#else +#define ALWAYS_INLINE_RELEASE ALWAYS_INLINE +#endif + +// unreferenced parameter macro +#ifndef UNREFERENCED_VARIABLE +#if defined(_MSC_VER) +#define UNREFERENCED_VARIABLE(P) (P) +#elif defined(__GNUC__) || defined(__clang__) || defined(__EMSCRIPTEN__) +#define UNREFERENCED_VARIABLE(P) (void)(P) +#else +#define UNREFERENCED_VARIABLE(P) (P) +#endif +#endif + +// countof macro +#ifndef countof +#ifdef _countof +#define countof _countof +#else +template +char (&__countof_ArraySizeHelper(T (&array)[N]))[N]; +#define countof(array) (sizeof(__countof_ArraySizeHelper(array))) +#endif +#endif + +// offsetof macro +#ifndef offsetof +#define offsetof(st, m) ((size_t)((char*)&((st*)(0))->m - (char*)0)) +#endif + +// disable warnings that show up at warning level 4 +// TODO: Move to build system instead +#ifdef _MSC_VER +#pragma warning(disable : 4201) // warning C4201: nonstandard extension used : nameless struct/union +#pragma warning(disable : 4100) // warning C4100: 'Platform' : unreferenced formal parameter +#pragma warning(disable : 4355) // warning C4355: 'this' : used in base member initializer list +#endif + +using s8 = int8_t; +using u8 = uint8_t; +using s16 = int16_t; +using u16 = uint16_t; +using s32 = int32_t; +using u32 = uint32_t; +using s64 = int64_t; +using u64 = uint64_t; + +// Enable use of static_assert in constexpr if +template +struct dependent_false : std::false_type +{ +}; +template +struct dependent_int_false : std::false_type +{ +}; + +// Zero-extending helper +template +ALWAYS_INLINE constexpr TReturn ZeroExtend(TValue value) +{ + return static_cast(static_cast::type>( + static_cast::type>(value))); +} +// Sign-extending helper +template +ALWAYS_INLINE constexpr TReturn SignExtend(TValue value) +{ + return static_cast( + static_cast::type>(static_cast::type>(value))); +} + +// Type-specific helpers +template +ALWAYS_INLINE constexpr u16 ZeroExtend16(TValue value) +{ + return ZeroExtend(value); +} +template +ALWAYS_INLINE constexpr u32 ZeroExtend32(TValue value) +{ + return ZeroExtend(value); +} +template +ALWAYS_INLINE constexpr u64 ZeroExtend64(TValue value) +{ + return ZeroExtend(value); +} +template +ALWAYS_INLINE constexpr u16 SignExtend16(TValue value) +{ + return SignExtend(value); +} +template +ALWAYS_INLINE constexpr u32 SignExtend32(TValue value) +{ + return SignExtend(value); +} +template +ALWAYS_INLINE constexpr u64 SignExtend64(TValue value) +{ + return SignExtend(value); +} +template +ALWAYS_INLINE constexpr u8 Truncate8(TValue value) +{ + return static_cast(static_cast::type>(value)); +} +template +ALWAYS_INLINE constexpr u16 Truncate16(TValue value) +{ + return static_cast(static_cast::type>(value)); +} +template +ALWAYS_INLINE constexpr u32 Truncate32(TValue value) +{ + return static_cast(static_cast::type>(value)); +} + +// BCD helpers +ALWAYS_INLINE constexpr u8 BinaryToBCD(u8 value) +{ + return ((value / 10) << 4) + (value % 10); +} +ALWAYS_INLINE constexpr u8 PackedBCDToBinary(u8 value) +{ + return ((value >> 4) * 10) + (value % 16); +} +ALWAYS_INLINE constexpr u8 IsValidBCDDigit(u8 digit) +{ + return (digit <= 9); +} +ALWAYS_INLINE constexpr u8 IsValidPackedBCD(u8 value) +{ + return IsValidBCDDigit(value & 0x0F) && IsValidBCDDigit(value >> 4); +} + +// Boolean to integer +ALWAYS_INLINE constexpr u8 BoolToUInt8(bool value) +{ + return static_cast(value); +} +ALWAYS_INLINE constexpr u16 BoolToUInt16(bool value) +{ + return static_cast(value); +} +ALWAYS_INLINE constexpr u32 BoolToUInt32(bool value) +{ + return static_cast(value); +} +ALWAYS_INLINE constexpr u64 BoolToUInt64(bool value) +{ + return static_cast(value); +} + +// Integer to boolean +template +ALWAYS_INLINE constexpr bool ConvertToBool(TValue value) +{ + return static_cast(value); +} + +// Unsafe integer to boolean +template +ALWAYS_INLINE bool ConvertToBoolUnchecked(TValue value) +{ + // static_assert(sizeof(uint8) == sizeof(bool)); + bool ret; + std::memcpy(&ret, &value, sizeof(bool)); + return ret; +} + +// Generic sign extension +template +ALWAYS_INLINE constexpr T SignExtendN(T value) +{ + // http://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend + constexpr int shift = 8 * sizeof(T) - NBITS; + return static_cast((static_cast>(value) << shift) >> shift); +} + +// Enum class bitwise operators +#define IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(type_) \ + ALWAYS_INLINE constexpr type_ operator&(type_ lhs, type_ rhs) \ + { \ + return static_cast(static_cast::type>(lhs) & \ + static_cast::type>(rhs)); \ + } \ + ALWAYS_INLINE constexpr type_ operator|(type_ lhs, type_ rhs) \ + { \ + return static_cast(static_cast::type>(lhs) | \ + static_cast::type>(rhs)); \ + } \ + ALWAYS_INLINE constexpr type_ operator^(type_ lhs, type_ rhs) \ + { \ + return static_cast(static_cast::type>(lhs) ^ \ + static_cast::type>(rhs)); \ + } \ + ALWAYS_INLINE constexpr type_ operator~(type_ val) \ + { \ + return static_cast(~static_cast::type>(val)); \ + } \ + ALWAYS_INLINE constexpr type_& operator&=(type_& lhs, type_ rhs) \ + { \ + lhs = static_cast(static_cast::type>(lhs) & \ + static_cast::type>(rhs)); \ + return lhs; \ + } \ + ALWAYS_INLINE constexpr type_& operator|=(type_& lhs, type_ rhs) \ + { \ + lhs = static_cast(static_cast::type>(lhs) | \ + static_cast::type>(rhs)); \ + return lhs; \ + } \ + ALWAYS_INLINE constexpr type_& operator^=(type_& lhs, type_ rhs) \ + { \ + lhs = static_cast(static_cast::type>(lhs) ^ \ + static_cast::type>(rhs)); \ + return lhs; \ + } diff --git a/jni/common/vulkan/builders.cpp b/jni/common/vulkan/builders.cpp new file mode 100644 index 0000000..c0947e4 --- /dev/null +++ b/jni/common/vulkan/builders.cpp @@ -0,0 +1,747 @@ +#include "builders.h" +#include "../assert.h" +#include "util.h" + +namespace Vulkan { + +DescriptorSetLayoutBuilder::DescriptorSetLayoutBuilder() +{ + Clear(); +} + +void DescriptorSetLayoutBuilder::Clear() +{ + m_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + m_ci.pNext = nullptr; + m_ci.flags = 0; + m_ci.pBindings = nullptr; + m_ci.bindingCount = 0; +} + +VkDescriptorSetLayout DescriptorSetLayoutBuilder::Create(VkDevice device) +{ + VkDescriptorSetLayout layout; + VkResult res = vkCreateDescriptorSetLayout(device, &m_ci, nullptr, &layout); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateDescriptorSetLayout() failed: "); + return VK_NULL_HANDLE; + } + + Clear(); + return layout; +} + +void DescriptorSetLayoutBuilder::AddBinding(u32 binding, VkDescriptorType dtype, u32 dcount, VkShaderStageFlags stages) +{ + Assert(m_ci.bindingCount < MAX_BINDINGS); + + VkDescriptorSetLayoutBinding& b = m_bindings[m_ci.bindingCount]; + b.binding = binding; + b.descriptorType = dtype; + b.descriptorCount = dcount; + b.stageFlags = stages; + b.pImmutableSamplers = nullptr; + + m_ci.pBindings = m_bindings.data(); + m_ci.bindingCount++; +} + +PipelineLayoutBuilder::PipelineLayoutBuilder() +{ + Clear(); +} + +void PipelineLayoutBuilder::Clear() +{ + m_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + m_ci.pNext = nullptr; + m_ci.flags = 0; + m_ci.pSetLayouts = nullptr; + m_ci.setLayoutCount = 0; + m_ci.pPushConstantRanges = nullptr; + m_ci.pushConstantRangeCount = 0; +} + +VkPipelineLayout PipelineLayoutBuilder::Create(VkDevice device) +{ + VkPipelineLayout layout; + VkResult res = vkCreatePipelineLayout(device, &m_ci, nullptr, &layout); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreatePipelineLayout() failed: "); + return VK_NULL_HANDLE; + } + + Clear(); + return layout; +} + +void PipelineLayoutBuilder::AddDescriptorSet(VkDescriptorSetLayout layout) +{ + Assert(m_ci.setLayoutCount < MAX_SETS); + + m_sets[m_ci.setLayoutCount] = layout; + + m_ci.setLayoutCount++; + m_ci.pSetLayouts = m_sets.data(); +} + +void PipelineLayoutBuilder::AddPushConstants(VkShaderStageFlags stages, u32 offset, u32 size) +{ + Assert(m_ci.pushConstantRangeCount < MAX_PUSH_CONSTANTS); + + VkPushConstantRange& r = m_push_constants[m_ci.pushConstantRangeCount]; + r.stageFlags = stages; + r.offset = offset; + r.size = size; + + m_ci.pushConstantRangeCount++; + m_ci.pPushConstantRanges = m_push_constants.data(); +} + +GraphicsPipelineBuilder::GraphicsPipelineBuilder() +{ + Clear(); +} + +void GraphicsPipelineBuilder::Clear() +{ + m_ci = {}; + m_ci.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + + m_shader_stages = {}; + + m_vertex_input_state = {}; + m_vertex_input_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + m_ci.pVertexInputState = &m_vertex_input_state; + m_vertex_attributes = {}; + m_vertex_buffers = {}; + + m_input_assembly = {}; + m_input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + + m_rasterization_state = {}; + m_rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + m_rasterization_state.lineWidth = 1.0f; + m_depth_state = {}; + m_depth_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + m_blend_state = {}; + m_blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + m_blend_attachments = {}; + + m_viewport_state = {}; + m_viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + m_viewport = {}; + m_scissor = {}; + + m_dynamic_state = {}; + m_dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + m_dynamic_state_values = {}; + + m_multisample_state = {}; + m_multisample_state.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + + // set defaults + SetNoCullRasterizationState(); + SetNoDepthTestState(); + SetNoBlendingState(); + SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + + // have to be specified even if dynamic + SetViewport(0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f); + SetScissorRect(0, 0, 1, 1); + SetMultisamples(VK_SAMPLE_COUNT_1_BIT); +} + +VkPipeline GraphicsPipelineBuilder::Create(VkDevice device, VkPipelineCache pipeline_cache, bool clear /* = true */) +{ + VkPipeline pipeline; + VkResult res = vkCreateGraphicsPipelines(device, pipeline_cache, 1, &m_ci, nullptr, &pipeline); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines() failed: "); + return VK_NULL_HANDLE; + } + + if (clear) + Clear(); + + return pipeline; +} + +void GraphicsPipelineBuilder::SetShaderStage(VkShaderStageFlagBits stage, VkShaderModule module, + const char* entry_point) +{ + Assert(m_ci.stageCount < MAX_SHADER_STAGES); + + u32 index = 0; + for (; index < m_ci.stageCount; index++) + { + if (m_shader_stages[index].stage == stage) + break; + } + if (index == m_ci.stageCount) + { + m_ci.stageCount++; + m_ci.pStages = m_shader_stages.data(); + } + + VkPipelineShaderStageCreateInfo& s = m_shader_stages[index]; + s.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + s.stage = stage; + s.module = module; + s.pName = entry_point; +} + +void GraphicsPipelineBuilder::AddVertexBuffer(u32 binding, u32 stride, + VkVertexInputRate input_rate /*= VK_VERTEX_INPUT_RATE_VERTEX*/) +{ + Assert(m_vertex_input_state.vertexAttributeDescriptionCount < MAX_VERTEX_BUFFERS); + + VkVertexInputBindingDescription& b = m_vertex_buffers[m_vertex_input_state.vertexBindingDescriptionCount]; + b.binding = binding; + b.stride = stride; + b.inputRate = input_rate; + + m_vertex_input_state.vertexBindingDescriptionCount++; + m_vertex_input_state.pVertexBindingDescriptions = m_vertex_buffers.data(); + m_ci.pVertexInputState = &m_vertex_input_state; +} + +void GraphicsPipelineBuilder::AddVertexAttribute(u32 location, u32 binding, VkFormat format, u32 offset) +{ + Assert(m_vertex_input_state.vertexAttributeDescriptionCount < MAX_VERTEX_BUFFERS); + + VkVertexInputAttributeDescription& a = m_vertex_attributes[m_vertex_input_state.vertexAttributeDescriptionCount]; + a.location = location; + a.binding = binding; + a.format = format; + a.offset = offset; + + m_vertex_input_state.vertexAttributeDescriptionCount++; + m_vertex_input_state.pVertexAttributeDescriptions = m_vertex_attributes.data(); + m_ci.pVertexInputState = &m_vertex_input_state; +} + +void GraphicsPipelineBuilder::SetPrimitiveTopology(VkPrimitiveTopology topology, + bool enable_primitive_restart /*= false*/) +{ + m_input_assembly.topology = topology; + m_input_assembly.primitiveRestartEnable = enable_primitive_restart; + + m_ci.pInputAssemblyState = &m_input_assembly; +} + +void GraphicsPipelineBuilder::SetRasterizationState(VkPolygonMode polygon_mode, VkCullModeFlags cull_mode, + VkFrontFace front_face) +{ + m_rasterization_state.polygonMode = polygon_mode; + m_rasterization_state.cullMode = cull_mode; + m_rasterization_state.frontFace = front_face; + + m_ci.pRasterizationState = &m_rasterization_state; +} + +void GraphicsPipelineBuilder::SetLineWidth(float width) +{ + m_rasterization_state.lineWidth = width; +} + +void GraphicsPipelineBuilder::SetMultisamples(u32 multisamples, bool per_sample_shading) +{ + m_multisample_state.rasterizationSamples = static_cast(multisamples); + m_multisample_state.sampleShadingEnable = per_sample_shading; + m_multisample_state.minSampleShading = (multisamples > 1) ? 0.0f : 1.0f; +} + +void GraphicsPipelineBuilder::SetNoCullRasterizationState() +{ + SetRasterizationState(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE); +} + +void GraphicsPipelineBuilder::SetDepthState(bool depth_test, bool depth_write, VkCompareOp compare_op) +{ + m_depth_state.depthTestEnable = depth_test; + m_depth_state.depthWriteEnable = depth_write; + m_depth_state.depthCompareOp = compare_op; + + m_ci.pDepthStencilState = &m_depth_state; +} + +void GraphicsPipelineBuilder::SetNoDepthTestState() +{ + SetDepthState(false, false, VK_COMPARE_OP_ALWAYS); +} + +void GraphicsPipelineBuilder::SetBlendConstants(float r, float g, float b, float a) +{ + m_blend_state.blendConstants[0] = r; + m_blend_state.blendConstants[1] = g; + m_blend_state.blendConstants[2] = b; + m_blend_state.blendConstants[3] = a; + m_ci.pColorBlendState = &m_blend_state; +} + +void GraphicsPipelineBuilder::AddBlendAttachment( + bool blend_enable, VkBlendFactor src_factor, VkBlendFactor dst_factor, VkBlendOp op, + VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor, VkBlendOp alpha_op, VkColorComponentFlags write_mask /* = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT */) +{ + Assert(m_blend_state.attachmentCount < MAX_ATTACHMENTS); + + VkPipelineColorBlendAttachmentState& bs = m_blend_attachments[m_blend_state.attachmentCount]; + bs.blendEnable = blend_enable; + bs.srcColorBlendFactor = src_factor; + bs.dstColorBlendFactor = dst_factor; + bs.colorBlendOp = op; + bs.srcAlphaBlendFactor = alpha_src_factor; + bs.dstAlphaBlendFactor = alpha_dst_factor; + bs.alphaBlendOp = alpha_op; + bs.colorWriteMask = write_mask; + + m_blend_state.attachmentCount++; + m_blend_state.pAttachments = m_blend_attachments.data(); + m_ci.pColorBlendState = &m_blend_state; +} + +void GraphicsPipelineBuilder::SetBlendAttachment( + u32 attachment, bool blend_enable, VkBlendFactor src_factor, VkBlendFactor dst_factor, VkBlendOp op, + VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor, VkBlendOp alpha_op, VkColorComponentFlags write_mask /*= VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT*/) +{ + Assert(attachment < MAX_ATTACHMENTS); + + VkPipelineColorBlendAttachmentState& bs = m_blend_attachments[attachment]; + bs.blendEnable = blend_enable; + bs.srcColorBlendFactor = src_factor; + bs.dstColorBlendFactor = dst_factor; + bs.colorBlendOp = op; + bs.srcAlphaBlendFactor = alpha_src_factor; + bs.dstAlphaBlendFactor = alpha_dst_factor; + bs.alphaBlendOp = alpha_op; + bs.colorWriteMask = write_mask; + + if (attachment >= m_blend_state.attachmentCount) + { + m_blend_state.attachmentCount = attachment + 1u; + m_blend_state.pAttachments = m_blend_attachments.data(); + m_ci.pColorBlendState = &m_blend_state; + } +} + +void GraphicsPipelineBuilder::ClearBlendAttachments() +{ + m_blend_attachments = {}; + m_blend_state.attachmentCount = 0; +} + +void GraphicsPipelineBuilder::SetNoBlendingState() +{ + ClearBlendAttachments(); + SetBlendAttachment(0, false, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE, + VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | + VK_COLOR_COMPONENT_A_BIT); +} + +void GraphicsPipelineBuilder::AddDynamicState(VkDynamicState state) +{ + Assert(m_dynamic_state.dynamicStateCount < MAX_DYNAMIC_STATE); + + m_dynamic_state_values[m_dynamic_state.dynamicStateCount] = state; + m_dynamic_state.dynamicStateCount++; + m_dynamic_state.pDynamicStates = m_dynamic_state_values.data(); + m_ci.pDynamicState = &m_dynamic_state; +} + +void GraphicsPipelineBuilder::SetDynamicViewportAndScissorState() +{ + AddDynamicState(VK_DYNAMIC_STATE_VIEWPORT); + AddDynamicState(VK_DYNAMIC_STATE_SCISSOR); +} + +void GraphicsPipelineBuilder::SetViewport(float x, float y, float width, float height, float min_depth, float max_depth) +{ + m_viewport.x = x; + m_viewport.y = y; + m_viewport.width = width; + m_viewport.height = height; + m_viewport.minDepth = min_depth; + m_viewport.maxDepth = max_depth; + + m_viewport_state.pViewports = &m_viewport; + m_viewport_state.viewportCount = 1u; + m_ci.pViewportState = &m_viewport_state; +} + +void GraphicsPipelineBuilder::SetScissorRect(s32 x, s32 y, u32 width, u32 height) +{ + m_scissor.offset.x = x; + m_scissor.offset.y = y; + m_scissor.extent.width = width; + m_scissor.extent.height = height; + + m_viewport_state.pScissors = &m_scissor; + m_viewport_state.scissorCount = 1u; + m_ci.pViewportState = &m_viewport_state; +} + +void GraphicsPipelineBuilder::SetMultisamples(VkSampleCountFlagBits samples) +{ + m_multisample_state.rasterizationSamples = samples; + m_ci.pMultisampleState = &m_multisample_state; +} + +void GraphicsPipelineBuilder::SetPipelineLayout(VkPipelineLayout layout) +{ + m_ci.layout = layout; +} + +void GraphicsPipelineBuilder::SetRenderPass(VkRenderPass render_pass, u32 subpass) +{ + m_ci.renderPass = render_pass; + m_ci.subpass = subpass; +} + +SamplerBuilder::SamplerBuilder() +{ + Clear(); +} + +void SamplerBuilder::Clear() +{ + m_ci = {}; + m_ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; +} + +VkSampler SamplerBuilder::Create(VkDevice device, bool clear /* = true */) +{ + VkSampler sampler; + VkResult res = vkCreateSampler(device, &m_ci, nullptr, &sampler); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateSampler() failed: "); + return VK_NULL_HANDLE; + } + + return sampler; +} + +void SamplerBuilder::SetFilter(VkFilter mag_filter, VkFilter min_filter, VkSamplerMipmapMode mip_filter) +{ + m_ci.magFilter = mag_filter; + m_ci.minFilter = min_filter; + m_ci.mipmapMode = mip_filter; +} + +void SamplerBuilder::SetAddressMode(VkSamplerAddressMode u, VkSamplerAddressMode v, VkSamplerAddressMode w) +{ + m_ci.addressModeU = u; + m_ci.addressModeV = v; + m_ci.addressModeW = w; +} + +void SamplerBuilder::SetPointSampler(VkSamplerAddressMode address_mode /* = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER */) +{ + Clear(); + SetFilter(VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST); + SetAddressMode(address_mode, address_mode, address_mode); +} + +void SamplerBuilder::SetLinearSampler(bool mipmaps, + VkSamplerAddressMode address_mode /* = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER */) +{ + Clear(); + SetFilter(VK_FILTER_LINEAR, VK_FILTER_LINEAR, + mipmaps ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST); + SetAddressMode(address_mode, address_mode, address_mode); +} + +DescriptorSetUpdateBuilder::DescriptorSetUpdateBuilder() +{ + Clear(); +} + +void DescriptorSetUpdateBuilder::Clear() +{ + m_writes = {}; + m_num_writes = 0; +} + +void DescriptorSetUpdateBuilder::Update(VkDevice device, bool clear /*= true*/) +{ + Assert(m_num_writes > 0); + + vkUpdateDescriptorSets(device, m_num_writes, (m_num_writes > 0) ? m_writes.data() : nullptr, 0, nullptr); + + if (clear) + Clear(); +} + +void DescriptorSetUpdateBuilder::AddImageDescriptorWrite( + VkDescriptorSet set, u32 binding, VkImageView view, + VkImageLayout layout /*= VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL*/) +{ + Assert(m_num_writes < MAX_WRITES && m_num_infos < MAX_INFOS); + + VkDescriptorImageInfo& ii = m_infos[m_num_infos++].image; + ii.imageView = view; + ii.imageLayout = layout; + ii.sampler = VK_NULL_HANDLE; + + VkWriteDescriptorSet& dw = m_writes[m_num_writes++]; + dw.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + dw.dstSet = set; + dw.dstBinding = binding; + dw.descriptorCount = 1; + dw.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + dw.pImageInfo = ⅈ +} + +void DescriptorSetUpdateBuilder::AddSamplerDescriptorWrite(VkDescriptorSet set, u32 binding, VkSampler sampler) +{ + Assert(m_num_writes < MAX_WRITES && m_num_infos < MAX_INFOS); + + VkDescriptorImageInfo& ii = m_infos[m_num_infos++].image; + ii.imageView = VK_NULL_HANDLE; + ii.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + ii.sampler = sampler; + + VkWriteDescriptorSet& dw = m_writes[m_num_writes++]; + dw.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + dw.dstSet = set; + dw.dstBinding = binding; + dw.descriptorCount = 1; + dw.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + dw.pImageInfo = ⅈ +} + +void DescriptorSetUpdateBuilder::AddCombinedImageSamplerDescriptorWrite( + VkDescriptorSet set, u32 binding, VkImageView view, VkSampler sampler, + VkImageLayout layout /*= VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL*/) +{ + Assert(m_num_writes < MAX_WRITES && m_num_infos < MAX_INFOS); + + VkDescriptorImageInfo& ii = m_infos[m_num_infos++].image; + ii.imageView = view; + ii.imageLayout = layout; + ii.sampler = sampler; + + VkWriteDescriptorSet& dw = m_writes[m_num_writes++]; + dw.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + dw.dstSet = set; + dw.dstBinding = binding; + dw.descriptorCount = 1; + dw.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + dw.pImageInfo = ⅈ +} + +void DescriptorSetUpdateBuilder::AddBufferDescriptorWrite(VkDescriptorSet set, u32 binding, VkDescriptorType dtype, + VkBuffer buffer, u32 offset, u32 size) +{ + Assert(m_num_writes < MAX_WRITES && m_num_infos < MAX_INFOS); + + VkDescriptorBufferInfo& bi = m_infos[m_num_infos++].buffer; + bi.buffer = buffer; + bi.offset = offset; + bi.range = size; + + VkWriteDescriptorSet& dw = m_writes[m_num_writes++]; + dw.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + dw.dstSet = set; + dw.dstBinding = binding; + dw.descriptorCount = 1; + dw.descriptorType = dtype; + dw.pBufferInfo = &bi; +} + +void DescriptorSetUpdateBuilder::AddBufferViewDescriptorWrite(VkDescriptorSet set, u32 binding, VkDescriptorType dtype, + VkBufferView view) +{ + Assert(m_num_writes < MAX_WRITES && m_num_infos < MAX_INFOS); + + VkBufferView& bi = m_infos[m_num_infos++].buffer_view; + bi = view; + + VkWriteDescriptorSet& dw = m_writes[m_num_writes++]; + dw.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + dw.dstSet = set; + dw.dstBinding = binding; + dw.descriptorCount = 1; + dw.descriptorType = dtype; + dw.pTexelBufferView = &bi; +} + +FramebufferBuilder::FramebufferBuilder() +{ + Clear(); +} + +void FramebufferBuilder::Clear() +{ + m_ci = {}; + m_ci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + m_images = {}; +} + +VkFramebuffer FramebufferBuilder::Create(VkDevice device, bool clear /*= true*/) +{ + VkFramebuffer fb; + VkResult res = vkCreateFramebuffer(device, &m_ci, nullptr, &fb); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateFramebuffer() failed: "); + return VK_NULL_HANDLE; + } + + if (clear) + Clear(); + + return fb; +} + +void FramebufferBuilder::AddAttachment(VkImageView image) +{ + Assert(m_ci.attachmentCount < MAX_ATTACHMENTS); + + m_images[m_ci.attachmentCount] = image; + + m_ci.attachmentCount++; + m_ci.pAttachments = m_images.data(); +} + +void FramebufferBuilder::SetSize(u32 width, u32 height, u32 layers) +{ + m_ci.width = width; + m_ci.height = height; + m_ci.layers = layers; +} + +void FramebufferBuilder::SetRenderPass(VkRenderPass render_pass) +{ + m_ci.renderPass = render_pass; +} + +RenderPassBuilder::RenderPassBuilder() +{ + Clear(); +} + +void RenderPassBuilder::Clear() +{ + m_ci = {}; + m_ci.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + m_attachments = {}; + m_attachment_references = {}; + m_num_attachment_references = 0; + m_subpasses = {}; +} + +VkRenderPass RenderPassBuilder::Create(VkDevice device, bool clear /*= true*/) +{ + VkRenderPass rp; + VkResult res = vkCreateRenderPass(device, &m_ci, nullptr, &rp); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateRenderPass() failed: "); + return VK_NULL_HANDLE; + } + + return rp; +} + +u32 RenderPassBuilder::AddAttachment(VkFormat format, VkSampleCountFlagBits samples, VkAttachmentLoadOp load_op, + VkAttachmentStoreOp store_op, VkImageLayout initial_layout, + VkImageLayout final_layout) +{ + Assert(m_ci.attachmentCount < MAX_ATTACHMENTS); + + const u32 index = m_ci.attachmentCount; + VkAttachmentDescription& ad = m_attachments[index]; + ad.format = format; + ad.samples = samples; + ad.loadOp = load_op; + ad.storeOp = store_op; + ad.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + ad.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + ad.initialLayout = initial_layout; + ad.finalLayout = final_layout; + + m_ci.attachmentCount++; + m_ci.pAttachments = m_attachments.data(); + + return index; +} + +u32 RenderPassBuilder::AddSubpass() +{ + Assert(m_ci.subpassCount < MAX_SUBPASSES); + + const u32 index = m_ci.subpassCount; + VkSubpassDescription& sp = m_subpasses[index]; + sp.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + + m_ci.subpassCount++; + m_ci.pSubpasses = m_subpasses.data(); + + return index; +} + +void RenderPassBuilder::AddSubpassColorAttachment(u32 subpass, u32 attachment, VkImageLayout layout) +{ + Assert(subpass < m_ci.subpassCount && m_num_attachment_references < MAX_ATTACHMENT_REFERENCES); + + VkAttachmentReference& ar = m_attachment_references[m_num_attachment_references++]; + ar.attachment = attachment; + ar.layout = layout; + + VkSubpassDescription& sp = m_subpasses[subpass]; + if (sp.colorAttachmentCount == 0) + sp.pColorAttachments = &ar; + sp.colorAttachmentCount++; +} + +void RenderPassBuilder::AddSubpassDepthAttachment(u32 subpass, u32 attachment, VkImageLayout layout) +{ + Assert(subpass < m_ci.subpassCount && m_num_attachment_references < MAX_ATTACHMENT_REFERENCES); + + VkAttachmentReference& ar = m_attachment_references[m_num_attachment_references++]; + ar.attachment = attachment; + ar.layout = layout; + + VkSubpassDescription& sp = m_subpasses[subpass]; + sp.pDepthStencilAttachment = &ar; +} + +BufferViewBuilder::BufferViewBuilder() +{ + Clear(); +} + +void BufferViewBuilder::Clear() +{ + m_ci = {}; + m_ci.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO; +} + +VkBufferView BufferViewBuilder::Create(VkDevice device, bool clear /*= true*/) +{ + VkBufferView bv; + VkResult res = vkCreateBufferView(device, &m_ci, nullptr, &bv); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateBufferView() failed: "); + return VK_NULL_HANDLE; + } + + return bv; +} + +void BufferViewBuilder::Set(VkBuffer buffer, VkFormat format, u32 offset, u32 size) +{ + m_ci.buffer = buffer; + m_ci.format = format; + m_ci.offset = offset; + m_ci.range = size; +} + +} // namespace Vulkan \ No newline at end of file diff --git a/jni/common/vulkan/builders.h b/jni/common/vulkan/builders.h new file mode 100644 index 0000000..217e3e7 --- /dev/null +++ b/jni/common/vulkan/builders.h @@ -0,0 +1,270 @@ +#pragma once +#include "../types.h" +#include "vulkan_loader.h" +#include + +namespace Vulkan { + +class DescriptorSetLayoutBuilder +{ +public: + enum : u32 + { + MAX_BINDINGS = 16, + }; + + DescriptorSetLayoutBuilder(); + + void Clear(); + + VkDescriptorSetLayout Create(VkDevice device); + + void AddBinding(u32 binding, VkDescriptorType dtype, u32 dcount, VkShaderStageFlags stages); + +private: + VkDescriptorSetLayoutCreateInfo m_ci{}; + std::array m_bindings{}; +}; + +class PipelineLayoutBuilder +{ +public: + enum : u32 + { + MAX_SETS = 8, + MAX_PUSH_CONSTANTS = 1 + }; + + PipelineLayoutBuilder(); + + void Clear(); + + VkPipelineLayout Create(VkDevice device); + + void AddDescriptorSet(VkDescriptorSetLayout layout); + + void AddPushConstants(VkShaderStageFlags stages, u32 offset, u32 size); + +private: + VkPipelineLayoutCreateInfo m_ci{}; + std::array m_sets{}; + std::array m_push_constants{}; +}; + +class GraphicsPipelineBuilder +{ +public: + enum : u32 + { + MAX_SHADER_STAGES = 3, + MAX_VERTEX_ATTRIBUTES = 16, + MAX_VERTEX_BUFFERS = 8, + MAX_ATTACHMENTS = 2, + MAX_DYNAMIC_STATE = 8 + }; + + GraphicsPipelineBuilder(); + + void Clear(); + + VkPipeline Create(VkDevice device, VkPipelineCache pipeline_cache = VK_NULL_HANDLE, bool clear = true); + + void SetShaderStage(VkShaderStageFlagBits stage, VkShaderModule module, const char* entry_point); + void SetVertexShader(VkShaderModule module) { SetShaderStage(VK_SHADER_STAGE_VERTEX_BIT, module, "main"); } + void SetGeometryShader(VkShaderModule module) { SetShaderStage(VK_SHADER_STAGE_GEOMETRY_BIT, module, "main"); } + void SetFragmentShader(VkShaderModule module) { SetShaderStage(VK_SHADER_STAGE_FRAGMENT_BIT, module, "main"); } + + void AddVertexBuffer(u32 binding, u32 stride, VkVertexInputRate input_rate = VK_VERTEX_INPUT_RATE_VERTEX); + void AddVertexAttribute(u32 location, u32 binding, VkFormat format, u32 offset); + + void SetPrimitiveTopology(VkPrimitiveTopology topology, bool enable_primitive_restart = false); + + void SetRasterizationState(VkPolygonMode polygon_mode, VkCullModeFlags cull_mode, VkFrontFace front_face); + void SetLineWidth(float width); + void SetMultisamples(u32 multisamples, bool per_sample_shading); + void SetNoCullRasterizationState(); + + void SetDepthState(bool depth_test, bool depth_write, VkCompareOp compare_op); + void SetNoDepthTestState(); + + void AddBlendAttachment(bool blend_enable, VkBlendFactor src_factor, VkBlendFactor dst_factor, VkBlendOp op, + VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor, VkBlendOp alpha_op, + VkColorComponentFlags write_mask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT); + void SetBlendAttachment(u32 attachment, bool blend_enable, VkBlendFactor src_factor, VkBlendFactor dst_factor, + VkBlendOp op, VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor, + VkBlendOp alpha_op, + VkColorComponentFlags write_mask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT); + void ClearBlendAttachments(); + + void SetBlendConstants(float r, float g, float b, float a); + void SetNoBlendingState(); + + void AddDynamicState(VkDynamicState state); + + void SetDynamicViewportAndScissorState(); + void SetViewport(float x, float y, float width, float height, float min_depth, float max_depth); + void SetScissorRect(s32 x, s32 y, u32 width, u32 height); + + void SetMultisamples(VkSampleCountFlagBits samples); + + void SetPipelineLayout(VkPipelineLayout layout); + void SetRenderPass(VkRenderPass render_pass, u32 subpass); + +private: + VkGraphicsPipelineCreateInfo m_ci; + std::array m_shader_stages; + + VkPipelineVertexInputStateCreateInfo m_vertex_input_state; + std::array m_vertex_buffers; + std::array m_vertex_attributes; + + VkPipelineInputAssemblyStateCreateInfo m_input_assembly; + + VkPipelineRasterizationStateCreateInfo m_rasterization_state; + VkPipelineDepthStencilStateCreateInfo m_depth_state; + + VkPipelineColorBlendStateCreateInfo m_blend_state; + std::array m_blend_attachments; + + VkPipelineViewportStateCreateInfo m_viewport_state; + VkViewport m_viewport; + VkRect2D m_scissor; + + VkPipelineDynamicStateCreateInfo m_dynamic_state; + std::array m_dynamic_state_values; + + VkPipelineMultisampleStateCreateInfo m_multisample_state; +}; + +class SamplerBuilder +{ +public: + SamplerBuilder(); + + void Clear(); + + VkSampler Create(VkDevice device, bool clear = true); + + void SetFilter(VkFilter mag_filter, VkFilter min_filter, VkSamplerMipmapMode mip_filter); + void SetAddressMode(VkSamplerAddressMode u, VkSamplerAddressMode v, VkSamplerAddressMode w); + + void SetPointSampler(VkSamplerAddressMode address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); + void SetLinearSampler(bool mipmaps, VkSamplerAddressMode address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); + +private: + VkSamplerCreateInfo m_ci; +}; + +class DescriptorSetUpdateBuilder +{ + enum : u32 + { + MAX_WRITES = 16, + MAX_INFOS = 16, + }; + +public: + DescriptorSetUpdateBuilder(); + + void Clear(); + + void Update(VkDevice device, bool clear = true); + + void AddImageDescriptorWrite(VkDescriptorSet set, u32 binding, VkImageView view, + VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + void AddSamplerDescriptorWrite(VkDescriptorSet set, u32 binding, VkSampler sampler); + void AddCombinedImageSamplerDescriptorWrite(VkDescriptorSet set, u32 binding, VkImageView view, VkSampler sampler, + VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + void AddBufferDescriptorWrite(VkDescriptorSet set, u32 binding, VkDescriptorType dtype, VkBuffer buffer, u32 offset, + u32 size); + void AddBufferViewDescriptorWrite(VkDescriptorSet set, u32 binding, VkDescriptorType dtype, VkBufferView view); + +private: + union InfoUnion + { + VkDescriptorBufferInfo buffer; + VkDescriptorImageInfo image; + VkBufferView buffer_view; + }; + + std::array m_writes; + u32 m_num_writes = 0; + + std::array m_infos; + u32 m_num_infos = 0; +}; + +class FramebufferBuilder +{ + enum : u32 + { + MAX_ATTACHMENTS = 2, + }; + +public: + FramebufferBuilder(); + + void Clear(); + + VkFramebuffer Create(VkDevice device, bool clear = true); + + void AddAttachment(VkImageView image); + + void SetSize(u32 width, u32 height, u32 layers); + + void SetRenderPass(VkRenderPass render_pass); + +private: + VkFramebufferCreateInfo m_ci; + std::array m_images; +}; + +class RenderPassBuilder +{ + enum : u32 + { + MAX_ATTACHMENTS = 2, + MAX_ATTACHMENT_REFERENCES = 2, + MAX_SUBPASSES = 1, + }; + +public: + RenderPassBuilder(); + + void Clear(); + + VkRenderPass Create(VkDevice device, bool clear = true); + + u32 AddAttachment(VkFormat format, VkSampleCountFlagBits samples, VkAttachmentLoadOp load_op, + VkAttachmentStoreOp store_op, VkImageLayout initial_layout, VkImageLayout final_layout); + + u32 AddSubpass(); + void AddSubpassColorAttachment(u32 subpass, u32 attachment, VkImageLayout layout); + void AddSubpassDepthAttachment(u32 subpass, u32 attachment, VkImageLayout layout); + +private: + VkRenderPassCreateInfo m_ci; + std::array m_attachments; + std::array m_attachment_references; + u32 m_num_attachment_references = 0; + std::array m_subpasses; +}; + +class BufferViewBuilder +{ +public: + BufferViewBuilder(); + + void Clear(); + + VkBufferView Create(VkDevice device, bool clear = true); + + void Set(VkBuffer buffer, VkFormat format, u32 offset, u32 size); + +private: + VkBufferViewCreateInfo m_ci; +}; + +} // namespace Vulkan \ No newline at end of file diff --git a/jni/common/vulkan/context.cpp b/jni/common/vulkan/context.cpp new file mode 100644 index 0000000..35d1cf8 --- /dev/null +++ b/jni/common/vulkan/context.cpp @@ -0,0 +1,1239 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#include "context.h" +#include "../assert.h" +#include "../log.h" +#include "../string_util.h" +#include "../window_info.h" +#include "swap_chain.h" +#include "util.h" +#include +#include +#include +Log_SetChannel(Vulkan::Context); + +std::unique_ptr g_vulkan_context; + +namespace Vulkan { + +Context::Context(VkInstance instance, VkPhysicalDevice physical_device, bool owns_device) + : m_instance(instance), m_physical_device(physical_device), m_owns_device(owns_device) +{ + // Read device physical memory properties, we need it for allocating buffers + vkGetPhysicalDeviceProperties(physical_device, &m_device_properties); + vkGetPhysicalDeviceMemoryProperties(physical_device, &m_device_memory_properties); + + // Would any drivers be this silly? I hope not... + m_device_properties.limits.minUniformBufferOffsetAlignment = + std::max(m_device_properties.limits.minUniformBufferOffsetAlignment, static_cast(1)); + m_device_properties.limits.minTexelBufferOffsetAlignment = + std::max(m_device_properties.limits.minTexelBufferOffsetAlignment, static_cast(1)); + m_device_properties.limits.optimalBufferCopyOffsetAlignment = + std::max(m_device_properties.limits.optimalBufferCopyOffsetAlignment, static_cast(1)); + m_device_properties.limits.optimalBufferCopyRowPitchAlignment = + std::max(m_device_properties.limits.optimalBufferCopyRowPitchAlignment, static_cast(1)); +} + +Context::~Context() +{ + if (m_device != VK_NULL_HANDLE) + WaitForGPUIdle(); + + DestroyRenderPassCache(); + DestroyGlobalDescriptorPool(); + DestroyCommandBuffers(); + + if (m_owns_device && m_device != VK_NULL_HANDLE) + vkDestroyDevice(m_device, nullptr); + + if (m_debug_report_callback != VK_NULL_HANDLE) + DisableDebugReports(); + + if (m_owns_device) + { + vkDestroyInstance(m_instance, nullptr); + Vulkan::UnloadVulkanLibrary(); + } +} + +bool Context::CheckValidationLayerAvailablility() +{ + u32 extension_count = 0; + VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: "); + return false; + } + + std::vector extension_list(extension_count); + res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, extension_list.data()); + Assert(res == VK_SUCCESS); + + u32 layer_count = 0; + res = vkEnumerateInstanceLayerProperties(&layer_count, nullptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: "); + return false; + } + + std::vector layer_list(layer_count); + res = vkEnumerateInstanceLayerProperties(&layer_count, layer_list.data()); + Assert(res == VK_SUCCESS); + + // Check for both VK_EXT_debug_report and VK_LAYER_LUNARG_standard_validation + return (std::find_if(extension_list.begin(), extension_list.end(), + [](const auto& it) { + return strcmp(it.extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME) == 0; + }) != extension_list.end() && + std::find_if(layer_list.begin(), layer_list.end(), [](const auto& it) { + return strcmp(it.layerName, "VK_LAYER_KHRONOS_validation") == 0; + }) != layer_list.end()); +} + +VkInstance Context::CreateVulkanInstance(bool enable_surface, bool enable_debug_report, bool enable_validation_layer) +{ + ExtensionList enabled_extensions; + if (!SelectInstanceExtensions(&enabled_extensions, enable_surface, enable_debug_report)) + return VK_NULL_HANDLE; + + VkApplicationInfo app_info = {}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pNext = nullptr; + app_info.pApplicationName = "DuckStation"; + app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0); + app_info.pEngineName = "DuckStation"; + app_info.engineVersion = VK_MAKE_VERSION(0, 1, 0); + app_info.apiVersion = VK_MAKE_VERSION(1, 0, 0); + + VkInstanceCreateInfo instance_create_info = {}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_create_info.pNext = nullptr; + instance_create_info.flags = 0; + instance_create_info.pApplicationInfo = &app_info; + instance_create_info.enabledExtensionCount = static_cast(enabled_extensions.size()); + instance_create_info.ppEnabledExtensionNames = enabled_extensions.data(); + instance_create_info.enabledLayerCount = 0; + instance_create_info.ppEnabledLayerNames = nullptr; + + // Enable debug layer on debug builds + if (enable_validation_layer) + { + static const char* layer_names[] = {"VK_LAYER_KHRONOS_validation"}; + instance_create_info.enabledLayerCount = 1; + instance_create_info.ppEnabledLayerNames = layer_names; + } + + VkInstance instance; + VkResult res = vkCreateInstance(&instance_create_info, nullptr, &instance); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateInstance failed: "); + return nullptr; + } + + return instance; +} + +bool Context::SelectInstanceExtensions(ExtensionList* extension_list, bool enable_surface, bool enable_debug_report) +{ + u32 extension_count = 0; + VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: "); + return false; + } + + if (extension_count == 0) + { + Log_ErrorPrintf("Vulkan: No extensions supported by instance."); + return false; + } + + std::vector available_extension_list(extension_count); + res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, available_extension_list.data()); + Assert(res == VK_SUCCESS); + + for (const auto& extension_properties : available_extension_list) + Log_InfoPrintf("Available extension: %s", extension_properties.extensionName); + + auto SupportsExtension = [&](const char* name, bool required) { + if (std::find_if(available_extension_list.begin(), available_extension_list.end(), + [&](const VkExtensionProperties& properties) { + return !strcmp(name, properties.extensionName); + }) != available_extension_list.end()) + { + Log_InfoPrintf("Enabling extension: %s", name); + extension_list->push_back(name); + return true; + } + + if (required) + Log_ErrorPrintf("Vulkan: Missing required extension %s.", name); + + return false; + }; + + // Common extensions + if (enable_surface && !SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true)) + return false; + +#if defined(VK_USE_PLATFORM_WIN32_KHR) + if (enable_surface && !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true)) + return false; +#endif +#if defined(VK_USE_PLATFORM_XLIB_KHR) + if (enable_surface && !SupportsExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true)) + return false; +#endif +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (enable_surface && !SupportsExtension(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, true)) + return false; +#endif +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + if (enable_surface && !SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true)) + return false; +#endif +#if defined(VK_USE_PLATFORM_MACOS_MVK) + if (enable_surface && !SupportsExtension(VK_MVK_MACOS_SURFACE_EXTENSION_NAME, true)) + return false; +#endif +#if defined(VK_USE_PLATFORM_METAL_EXT) + if (enable_surface && !SupportsExtension(VK_EXT_METAL_SURFACE_EXTENSION_NAME, true)) + return false; +#endif + + // VK_EXT_debug_report + if (enable_debug_report && !SupportsExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, false)) + Log_WarningPrintf("Vulkan: Debug report requested, but extension is not available."); + + return true; +} + +Context::GPUList Context::EnumerateGPUs(VkInstance instance) +{ + u32 gpu_count = 0; + VkResult res = vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr); + if (res != VK_SUCCESS || gpu_count == 0) + { + LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: "); + return {}; + } + + GPUList gpus; + gpus.resize(gpu_count); + + res = vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data()); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: "); + return {}; + } + + return gpus; +} + +Context::GPUNameList Context::EnumerateGPUNames(VkInstance instance) +{ + u32 gpu_count = 0; + VkResult res = vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr); + if (res != VK_SUCCESS || gpu_count == 0) + { + LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: "); + return {}; + } + + GPUList gpus; + gpus.resize(gpu_count); + + res = vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data()); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: "); + return {}; + } + + GPUNameList gpu_names; + gpu_names.reserve(gpu_count); + for (u32 i = 0; i < gpu_count; i++) + { + VkPhysicalDeviceProperties props = {}; + vkGetPhysicalDeviceProperties(gpus[i], &props); + + std::string gpu_name(props.deviceName); + + // handle duplicate adapter names + if (std::any_of(gpu_names.begin(), gpu_names.end(), + [&gpu_name](const std::string& other) { return (gpu_name == other); })) + { + std::string original_adapter_name = std::move(gpu_name); + + u32 current_extra = 2; + do + { + gpu_name = StringUtil::StdStringFromFormat("%s (%u)", original_adapter_name.c_str(), current_extra); + current_extra++; + } while (std::any_of(gpu_names.begin(), gpu_names.end(), + [&gpu_name](const std::string& other) { return (gpu_name == other); })); + } + + gpu_names.push_back(std::move(gpu_name)); + } + + return gpu_names; +} + +bool Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr* out_swap_chain, + bool enable_debug_reports, bool enable_validation_layer) +{ + AssertMsg(!g_vulkan_context, "Has no current context"); + + if (!Vulkan::LoadVulkanLibrary()) + { + Log_ErrorPrintf("Failed to load Vulkan library"); + return false; + } + + const bool enable_surface = (wi && wi->type != WindowInfo::Type::Surfaceless); + VkInstance instance = CreateVulkanInstance(enable_surface, enable_debug_reports, enable_validation_layer); + if (instance == VK_NULL_HANDLE) + { + Vulkan::UnloadVulkanLibrary(); + return false; + } + + if (!Vulkan::LoadVulkanInstanceFunctions(instance)) + { + Log_ErrorPrintf("Failed to load Vulkan instance functions"); + vkDestroyInstance(instance, nullptr); + Vulkan::UnloadVulkanLibrary(); + return false; + } + + GPUList gpus = EnumerateGPUs(instance); + if (gpus.empty()) + { + vkDestroyInstance(instance, nullptr); + Vulkan::UnloadVulkanLibrary(); + return false; + } + + u32 gpu_index = 0; + GPUNameList gpu_names = EnumerateGPUNames(instance); + if (!gpu_name.empty()) + { + for (; gpu_index < static_cast(gpu_names.size()); gpu_index++) + { + Log_InfoPrintf("GPU %u: %s", static_cast(gpu_index), gpu_names[gpu_index].c_str()); + if (gpu_names[gpu_index] == gpu_name) + break; + } + + if (gpu_index == static_cast(gpu_names.size())) + { + Log_WarningPrintf("Requested GPU '%s' not found, using first (%s)", std::string(gpu_name).c_str(), + gpu_names[0].c_str()); + gpu_index = 0; + } + } + else + { + Log_InfoPrintf("No GPU requested, using first (%s)", gpu_names[0].c_str()); + } + + VkSurfaceKHR surface = VK_NULL_HANDLE; + WindowInfo wi_copy(*wi); + if (enable_surface && (surface = SwapChain::CreateVulkanSurface(instance, wi_copy)) == VK_NULL_HANDLE) + { + vkDestroyInstance(instance, nullptr); + Vulkan::UnloadVulkanLibrary(); + return false; + } + + g_vulkan_context.reset(new Context(instance, gpus[gpu_index], true)); + + // Enable debug reports if the "Host GPU" log category is enabled. + if (enable_debug_reports) + g_vulkan_context->EnableDebugReports(); + + // Attempt to create the device. + if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, nullptr, 0, nullptr, 0, nullptr) || + !g_vulkan_context->CreateGlobalDescriptorPool() || !g_vulkan_context->CreateCommandBuffers() || + (enable_surface && (*out_swap_chain = SwapChain::Create(wi_copy, surface, true)) == nullptr)) + { + // Since we are destroying the instance, we're also responsible for destroying the surface. + if (surface != VK_NULL_HANDLE) + vkDestroySurfaceKHR(instance, surface, nullptr); + + g_vulkan_context.reset(); + return false; + } + + return true; +} + +bool Context::CreateFromExistingInstance(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, + bool take_ownership, bool enable_validation_layer, bool enable_debug_reports, + const char** required_device_extensions /* = nullptr */, + u32 num_required_device_extensions /* = 0 */, + const char** required_device_layers /* = nullptr */, + u32 num_required_device_layers /* = 0 */, + const VkPhysicalDeviceFeatures* required_features /* = nullptr */) +{ + g_vulkan_context.reset(new Context(instance, gpu, take_ownership)); + + // Enable debug reports if the "Host GPU" log category is enabled. + if (enable_debug_reports) + g_vulkan_context->EnableDebugReports(); + + // Attempt to create the device. + if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, required_device_extensions, + num_required_device_extensions, required_device_layers, + num_required_device_layers, required_features) || + !g_vulkan_context->CreateGlobalDescriptorPool() || !g_vulkan_context->CreateCommandBuffers()) + { + g_vulkan_context.reset(); + return false; + } + + return true; +} + +void Context::Destroy() +{ + AssertMsg(g_vulkan_context, "Has context"); + g_vulkan_context.reset(); +} + +bool Context::SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface) +{ + u32 extension_count = 0; + VkResult res = vkEnumerateDeviceExtensionProperties(m_physical_device, nullptr, &extension_count, nullptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkEnumerateDeviceExtensionProperties failed: "); + return false; + } + + if (extension_count == 0) + { + Log_ErrorPrintf("Vulkan: No extensions supported by device."); + return false; + } + + std::vector available_extension_list(extension_count); + res = + vkEnumerateDeviceExtensionProperties(m_physical_device, nullptr, &extension_count, available_extension_list.data()); + Assert(res == VK_SUCCESS); + + for (const auto& extension_properties : available_extension_list) + Log_InfoPrintf("Available extension: %s", extension_properties.extensionName); + + auto SupportsExtension = [&](const char* name, bool required) { + if (std::find_if(available_extension_list.begin(), available_extension_list.end(), + [&](const VkExtensionProperties& properties) { + return !strcmp(name, properties.extensionName); + }) != available_extension_list.end()) + { + if (std::none_of(extension_list->begin(), extension_list->end(), + [&](const char* existing_name) { return (std::strcmp(existing_name, name) == 0); })) + { + Log_InfoPrintf("Enabling extension: %s", name); + extension_list->push_back(name); + } + + return true; + } + + if (required) + Log_ErrorPrintf("Vulkan: Missing required extension %s.", name); + + return false; + }; + + if (enable_surface && !SupportsExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, true)) + return false; + + return true; +} + +bool Context::SelectDeviceFeatures(const VkPhysicalDeviceFeatures* required_features) +{ + VkPhysicalDeviceFeatures available_features; + vkGetPhysicalDeviceFeatures(m_physical_device, &available_features); + + if (required_features) + std::memcpy(&m_device_features, required_features, sizeof(m_device_features)); + + // Enable the features we use. + m_device_features.dualSrcBlend = available_features.dualSrcBlend; + m_device_features.sampleRateShading = available_features.sampleRateShading; + return true; +} + +bool Context::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, const char** required_device_extensions, + u32 num_required_device_extensions, const char** required_device_layers, + u32 num_required_device_layers, const VkPhysicalDeviceFeatures* required_features) +{ + u32 queue_family_count; + vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count, nullptr); + if (queue_family_count == 0) + { + Log_ErrorPrintf("No queue families found on specified vulkan physical device."); + return false; + } + + std::vector queue_family_properties(queue_family_count); + vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count, queue_family_properties.data()); + Log_InfoPrintf("%u vulkan queue families", queue_family_count); + + // Find graphics and present queues. + m_graphics_queue_family_index = queue_family_count; + m_present_queue_family_index = queue_family_count; + for (uint32_t i = 0; i < queue_family_count; i++) + { + VkBool32 graphics_supported = queue_family_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT; + if (graphics_supported) + { + m_graphics_queue_family_index = i; + // Quit now, no need for a present queue. + if (!surface) + { + break; + } + } + + if (surface) + { + VkBool32 present_supported; + VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR(m_physical_device, i, surface, &present_supported); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: "); + return false; + } + + if (present_supported) + { + m_present_queue_family_index = i; + } + + // Prefer one queue family index that does both graphics and present. + if (graphics_supported && present_supported) + { + break; + } + } + } + if (m_graphics_queue_family_index == queue_family_count) + { + Log_ErrorPrintf("Vulkan: Failed to find an acceptable graphics queue."); + return false; + } + if (surface && m_present_queue_family_index == queue_family_count) + { + Log_ErrorPrintf("Vulkan: Failed to find an acceptable present queue."); + return false; + } + + VkDeviceCreateInfo device_info = {}; + device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_info.pNext = nullptr; + device_info.flags = 0; + + static constexpr float queue_priorities[] = {1.0f}; + VkDeviceQueueCreateInfo graphics_queue_info = {}; + graphics_queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + graphics_queue_info.pNext = nullptr; + graphics_queue_info.flags = 0; + graphics_queue_info.queueFamilyIndex = m_graphics_queue_family_index; + graphics_queue_info.queueCount = 1; + graphics_queue_info.pQueuePriorities = queue_priorities; + + VkDeviceQueueCreateInfo present_queue_info = {}; + present_queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + present_queue_info.pNext = nullptr; + present_queue_info.flags = 0; + present_queue_info.queueFamilyIndex = m_present_queue_family_index; + present_queue_info.queueCount = 1; + present_queue_info.pQueuePriorities = queue_priorities; + + std::array queue_infos = {{ + graphics_queue_info, + present_queue_info, + }}; + + device_info.queueCreateInfoCount = 1; + if (m_graphics_queue_family_index != m_present_queue_family_index) + { + device_info.queueCreateInfoCount = 2; + } + device_info.pQueueCreateInfos = queue_infos.data(); + + ExtensionList enabled_extensions; + for (u32 i = 0; i < num_required_device_extensions; i++) + enabled_extensions.emplace_back(required_device_extensions[i]); + if (!SelectDeviceExtensions(&enabled_extensions, surface != VK_NULL_HANDLE)) + return false; + + device_info.enabledLayerCount = num_required_device_layers; + device_info.ppEnabledLayerNames = required_device_layers; + device_info.enabledExtensionCount = static_cast(enabled_extensions.size()); + device_info.ppEnabledExtensionNames = enabled_extensions.data(); + + // Check for required features before creating. + if (!SelectDeviceFeatures(required_features)) + return false; + + device_info.pEnabledFeatures = &m_device_features; + + // Enable debug layer on debug builds + if (enable_validation_layer) + { + static const char* layer_names[] = {"VK_LAYER_LUNARG_standard_validation"}; + device_info.enabledLayerCount = 1; + device_info.ppEnabledLayerNames = layer_names; + } + + VkResult res = vkCreateDevice(m_physical_device, &device_info, nullptr, &m_device); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateDevice failed: "); + return false; + } + + // With the device created, we can fill the remaining entry points. + if (!LoadVulkanDeviceFunctions(m_device)) + return false; + + // Grab the graphics and present queues. + vkGetDeviceQueue(m_device, m_graphics_queue_family_index, 0, &m_graphics_queue); + if (surface) + { + vkGetDeviceQueue(m_device, m_present_queue_family_index, 0, &m_present_queue); + } + return true; +} + +bool Context::CreateCommandBuffers() +{ + VkResult res; + + for (FrameResources& resources : m_frame_resources) + { + resources.needs_fence_wait = false; + + VkCommandPoolCreateInfo pool_info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, 0, + m_graphics_queue_family_index}; + res = vkCreateCommandPool(m_device, &pool_info, nullptr, &resources.command_pool); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateCommandPool failed: "); + return false; + } + + VkCommandBufferAllocateInfo buffer_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, + resources.command_pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1}; + + res = vkAllocateCommandBuffers(m_device, &buffer_info, &resources.command_buffer); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkAllocateCommandBuffers failed: "); + return false; + } + + VkFenceCreateInfo fence_info = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, VK_FENCE_CREATE_SIGNALED_BIT}; + + res = vkCreateFence(m_device, &fence_info, nullptr, &resources.fence); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateFence failed: "); + return false; + } + + // TODO: A better way to choose the number of descriptors. + VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1024}, + {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1024}, + {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 16}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16}}; + + VkDescriptorPoolCreateInfo pool_create_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + nullptr, + 0, + 1024, // TODO: tweak this + static_cast(countof(pool_sizes)), + pool_sizes}; + + res = vkCreateDescriptorPool(m_device, &pool_create_info, nullptr, &resources.descriptor_pool); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateDescriptorPool failed: "); + return false; + } + } + + ActivateCommandBuffer(0); + return true; +} + +void Context::DestroyCommandBuffers() +{ + for (FrameResources& resources : m_frame_resources) + { + for (auto& it : resources.cleanup_resources) + it(); + resources.cleanup_resources.clear(); + + if (resources.fence != VK_NULL_HANDLE) + { + vkDestroyFence(m_device, resources.fence, nullptr); + resources.fence = VK_NULL_HANDLE; + } + if (resources.descriptor_pool != VK_NULL_HANDLE) + { + vkDestroyDescriptorPool(m_device, resources.descriptor_pool, nullptr); + resources.descriptor_pool = VK_NULL_HANDLE; + } + if (resources.command_buffer != VK_NULL_HANDLE) + { + vkFreeCommandBuffers(m_device, resources.command_pool, 1, &resources.command_buffer); + resources.command_buffer = VK_NULL_HANDLE; + } + if (resources.command_pool != VK_NULL_HANDLE) + { + vkDestroyCommandPool(m_device, resources.command_pool, nullptr); + resources.command_pool = VK_NULL_HANDLE; + } + } +} + +bool Context::CreateGlobalDescriptorPool() +{ + // TODO: A better way to choose the number of descriptors. + VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1024}, + {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1024}, + {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 16}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16}}; + + VkDescriptorPoolCreateInfo pool_create_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + nullptr, + VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + 1024, // TODO: tweak this + static_cast(countof(pool_sizes)), + pool_sizes}; + + VkResult res = vkCreateDescriptorPool(m_device, &pool_create_info, nullptr, &m_global_descriptor_pool); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateDescriptorPool failed: "); + return false; + } + + return true; +} + +void Context::DestroyGlobalDescriptorPool() +{ + if (m_global_descriptor_pool != VK_NULL_HANDLE) + { + vkDestroyDescriptorPool(m_device, m_global_descriptor_pool, nullptr); + m_global_descriptor_pool = VK_NULL_HANDLE; + } +} + +void Context::DestroyRenderPassCache() +{ + for (auto& it : m_render_pass_cache) + vkDestroyRenderPass(m_device, it.second, nullptr); + + m_render_pass_cache.clear(); +} + +VkDescriptorSet Context::AllocateDescriptorSet(VkDescriptorSetLayout set_layout) +{ + VkDescriptorSetAllocateInfo allocate_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr, + m_frame_resources[m_current_frame].descriptor_pool, 1, &set_layout}; + + VkDescriptorSet descriptor_set; + VkResult res = vkAllocateDescriptorSets(m_device, &allocate_info, &descriptor_set); + if (res != VK_SUCCESS) + { + // Failing to allocate a descriptor set is not a fatal error, we can + // recover by moving to the next command buffer. + return VK_NULL_HANDLE; + } + + return descriptor_set; +} + +VkDescriptorSet Context::AllocateGlobalDescriptorSet(VkDescriptorSetLayout set_layout) +{ + VkDescriptorSetAllocateInfo allocate_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr, + m_global_descriptor_pool, 1, &set_layout}; + + VkDescriptorSet descriptor_set; + VkResult res = vkAllocateDescriptorSets(m_device, &allocate_info, &descriptor_set); + if (res != VK_SUCCESS) + return VK_NULL_HANDLE; + + return descriptor_set; +} + +void Context::FreeGlobalDescriptorSet(VkDescriptorSet set) +{ + vkFreeDescriptorSets(m_device, m_global_descriptor_pool, 1, &set); +} + +void Context::WaitForFenceCounter(u64 fence_counter) +{ + if (m_completed_fence_counter >= fence_counter) + return; + + // Find the first command buffer which covers this counter value. + u32 index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS; + while (index != m_current_frame) + { + if (m_frame_resources[index].fence_counter >= fence_counter) + break; + + index = (index + 1) % NUM_COMMAND_BUFFERS; + } + + Assert(index != m_current_frame); + WaitForCommandBufferCompletion(index); +} + +void Context::WaitForGPUIdle() +{ + vkDeviceWaitIdle(m_device); +} + +void Context::WaitForCommandBufferCompletion(u32 index) +{ + // Wait for this command buffer to be completed. + VkResult res = vkWaitForFences(m_device, 1, &m_frame_resources[index].fence, VK_TRUE, UINT64_MAX); + if (res != VK_SUCCESS) + LOG_VULKAN_ERROR(res, "vkWaitForFences failed: "); + + // Clean up any resources for command buffers between the last known completed buffer and this + // now-completed command buffer. If we use >2 buffers, this may be more than one buffer. + const u64 now_completed_counter = m_frame_resources[index].fence_counter; + u32 cleanup_index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS; + while (cleanup_index != m_current_frame) + { + FrameResources& resources = m_frame_resources[cleanup_index]; + if (resources.fence_counter > now_completed_counter) + break; + + if (resources.fence_counter > m_completed_fence_counter) + { + for (auto& it : resources.cleanup_resources) + it(); + resources.cleanup_resources.clear(); + } + + cleanup_index = (cleanup_index + 1) % NUM_COMMAND_BUFFERS; + } + + m_completed_fence_counter = now_completed_counter; +} + +void Context::SubmitCommandBuffer(VkSemaphore wait_semaphore, VkSemaphore signal_semaphore, + VkSwapchainKHR present_swap_chain, uint32_t present_image_index) +{ + FrameResources& resources = m_frame_resources[m_current_frame]; + + // End the current command buffer. + VkResult res = vkEndCommandBuffer(resources.command_buffer); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkEndCommandBuffer failed: "); + Panic("Failed to end command buffer"); + } + + // This command buffer now has commands, so can't be re-used without waiting. + resources.needs_fence_wait = true; + + // This may be executed on the worker thread, so don't modify any state of the manager class. + uint32_t wait_bits = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO, nullptr, 0, nullptr, &wait_bits, 1u, + &resources.command_buffer, 0, nullptr}; + + if (wait_semaphore != VK_NULL_HANDLE) + { + submit_info.pWaitSemaphores = &wait_semaphore; + submit_info.waitSemaphoreCount = 1; + } + + if (signal_semaphore != VK_NULL_HANDLE) + { + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &signal_semaphore; + } + + res = vkQueueSubmit(m_graphics_queue, 1, &submit_info, resources.fence); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: "); + Panic("Failed to submit command buffer."); + } + + // Do we have a swap chain to present? + if (present_swap_chain != VK_NULL_HANDLE) + { + // Should have a signal semaphore. + Assert(signal_semaphore != VK_NULL_HANDLE); + VkPresentInfoKHR present_info = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + nullptr, + 1, + &signal_semaphore, + 1, + &present_swap_chain, + &present_image_index, + nullptr}; + + res = vkQueuePresentKHR(m_present_queue, &present_info); + if (res != VK_SUCCESS) + { + // VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain. + if (res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR) + LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: "); + + m_last_present_failed = true; + } + } +} + +void Context::MoveToNextCommandBuffer() +{ + ActivateCommandBuffer((m_current_frame + 1) % NUM_COMMAND_BUFFERS); +} + +void Context::ActivateCommandBuffer(u32 index) +{ + FrameResources& resources = m_frame_resources[index]; + + // Wait for the GPU to finish with all resources for this command buffer. + if (resources.fence_counter > m_completed_fence_counter) + WaitForCommandBufferCompletion(index); + + // Reset fence to unsignaled before starting. + VkResult res = vkResetFences(m_device, 1, &resources.fence); + if (res != VK_SUCCESS) + LOG_VULKAN_ERROR(res, "vkResetFences failed: "); + + // Reset command pools to beginning since we can re-use the memory now + res = vkResetCommandPool(m_device, resources.command_pool, 0); + if (res != VK_SUCCESS) + LOG_VULKAN_ERROR(res, "vkResetCommandPool failed: "); + + // Enable commands to be recorded to the two buffers again. + VkCommandBufferBeginInfo begin_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr}; + res = vkBeginCommandBuffer(resources.command_buffer, &begin_info); + if (res != VK_SUCCESS) + LOG_VULKAN_ERROR(res, "vkBeginCommandBuffer failed: "); + + // Also can do the same for the descriptor pools + res = vkResetDescriptorPool(m_device, resources.descriptor_pool, 0); + if (res != VK_SUCCESS) + LOG_VULKAN_ERROR(res, "vkResetDescriptorPool failed: "); + + m_current_frame = index; + m_current_command_buffer = resources.command_buffer; + resources.fence_counter = m_next_fence_counter++; +} + +void Context::ExecuteCommandBuffer(bool wait_for_completion) +{ + // If we're waiting for completion, don't bother waking the worker thread. + const u32 current_frame = m_current_frame; + SubmitCommandBuffer(); + MoveToNextCommandBuffer(); + + if (wait_for_completion) + WaitForCommandBufferCompletion(current_frame); +} + +bool Context::CheckLastPresentFail() +{ + bool res = m_last_present_failed; + m_last_present_failed = false; + return res; +} + +void Context::DeferBufferDestruction(VkBuffer object) +{ + FrameResources& resources = m_frame_resources[m_current_frame]; + resources.cleanup_resources.push_back([this, object]() { vkDestroyBuffer(m_device, object, nullptr); }); +} + +void Context::DeferBufferViewDestruction(VkBufferView object) +{ + FrameResources& resources = m_frame_resources[m_current_frame]; + resources.cleanup_resources.push_back([this, object]() { vkDestroyBufferView(m_device, object, nullptr); }); +} + +void Context::DeferDeviceMemoryDestruction(VkDeviceMemory object) +{ + FrameResources& resources = m_frame_resources[m_current_frame]; + resources.cleanup_resources.push_back([this, object]() { vkFreeMemory(m_device, object, nullptr); }); +} + +void Context::DeferFramebufferDestruction(VkFramebuffer object) +{ + FrameResources& resources = m_frame_resources[m_current_frame]; + resources.cleanup_resources.push_back([this, object]() { vkDestroyFramebuffer(m_device, object, nullptr); }); +} + +void Context::DeferImageDestruction(VkImage object) +{ + FrameResources& resources = m_frame_resources[m_current_frame]; + resources.cleanup_resources.push_back([this, object]() { vkDestroyImage(m_device, object, nullptr); }); +} + +void Context::DeferImageViewDestruction(VkImageView object) +{ + FrameResources& resources = m_frame_resources[m_current_frame]; + resources.cleanup_resources.push_back([this, object]() { vkDestroyImageView(m_device, object, nullptr); }); +} + +void Context::DeferPipelineDestruction(VkPipeline pipeline) +{ + FrameResources& resources = m_frame_resources[m_current_frame]; + resources.cleanup_resources.push_back([this, pipeline]() { vkDestroyPipeline(m_device, pipeline, nullptr); }); +} + +static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objectType, uint64_t object, + size_t location, int32_t messageCode, + const char* pLayerPrefix, const char* pMessage, + void* pUserData) +{ + LOGLEVEL level; + if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) + level = LOGLEVEL_ERROR; + else if (flags & (VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)) + level = LOGLEVEL_WARNING; + else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) + level = LOGLEVEL_INFO; + else + level = LOGLEVEL_DEBUG; + + Log::Writef("Vulkan", __func__, level, "Vulkan debug report: (%s) %s", pLayerPrefix ? pLayerPrefix : "", pMessage); + return VK_FALSE; +} + +bool Context::EnableDebugReports() +{ + // Already enabled? + if (m_debug_report_callback != VK_NULL_HANDLE) + return true; + + // Check for presence of the functions before calling + if (!vkCreateDebugReportCallbackEXT || !vkDestroyDebugReportCallbackEXT || !vkDebugReportMessageEXT) + { + return false; + } + + VkDebugReportCallbackCreateInfoEXT callback_info = { + VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, nullptr, + VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | + VK_DEBUG_REPORT_INFORMATION_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT, + DebugReportCallback, nullptr}; + + VkResult res = vkCreateDebugReportCallbackEXT(m_instance, &callback_info, nullptr, &m_debug_report_callback); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateDebugReportCallbackEXT failed: "); + return false; + } + + return true; +} + +void Context::DisableDebugReports() +{ + if (m_debug_report_callback != VK_NULL_HANDLE) + { + vkDestroyDebugReportCallbackEXT(m_instance, m_debug_report_callback, nullptr); + m_debug_report_callback = VK_NULL_HANDLE; + } +} + +bool Context::GetMemoryType(u32 bits, VkMemoryPropertyFlags properties, u32* out_type_index) +{ + for (u32 i = 0; i < VK_MAX_MEMORY_TYPES; i++) + { + if ((bits & (1 << i)) != 0) + { + u32 supported = m_device_memory_properties.memoryTypes[i].propertyFlags & properties; + if (supported == properties) + { + *out_type_index = i; + return true; + } + } + } + + return false; +} + +u32 Context::GetMemoryType(u32 bits, VkMemoryPropertyFlags properties) +{ + u32 type_index = VK_MAX_MEMORY_TYPES; + if (!GetMemoryType(bits, properties, &type_index)) + { + Log_ErrorPrintf("Unable to find memory type for %x:%x", bits, properties); + Panic("Unable to find memory type"); + } + + return type_index; +} + +u32 Context::GetUploadMemoryType(u32 bits, bool* is_coherent) +{ + // Try for coherent memory first. + VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + u32 type_index; + if (!GetMemoryType(bits, flags, &type_index)) + { + Log_WarningPrintf("Vulkan: Failed to find a coherent memory type for uploads, this will affect performance."); + + // Try non-coherent memory. + flags &= ~VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + if (!GetMemoryType(bits, flags, &type_index)) + { + // We shouldn't have any memory types that aren't host-visible. + Panic("Unable to get memory type for upload."); + type_index = 0; + } + } + + if (is_coherent) + *is_coherent = ((flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0); + + return type_index; +} + +u32 Context::GetReadbackMemoryType(u32 bits, bool* is_coherent, bool* is_cached) +{ + // Try for cached and coherent memory first. + VkMemoryPropertyFlags flags = + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + u32 type_index; + if (!GetMemoryType(bits, flags, &type_index)) + { + // For readbacks, caching is more important than coherency. + flags &= ~VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + if (!GetMemoryType(bits, flags, &type_index)) + { + Log_WarningPrintf("Vulkan: Failed to find a cached memory type for readbacks, this will affect " + "performance."); + + // Remove the cached bit as well. + flags &= ~VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + if (!GetMemoryType(bits, flags, &type_index)) + { + // We shouldn't have any memory types that aren't host-visible. + Panic("Unable to get memory type for upload."); + type_index = 0; + } + } + } + + if (is_coherent) + *is_coherent = ((flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0); + if (is_cached) + *is_cached = ((flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0); + + return type_index; +} + +VkRenderPass Context::GetRenderPass(VkFormat color_format, VkFormat depth_format, VkSampleCountFlagBits samples, + VkAttachmentLoadOp load_op) +{ + auto key = std::tie(color_format, depth_format, samples, load_op); + auto it = m_render_pass_cache.find(key); + if (it != m_render_pass_cache.end()) + return it->second; + + VkAttachmentReference color_reference; + VkAttachmentReference* color_reference_ptr = nullptr; + VkAttachmentReference depth_reference; + VkAttachmentReference* depth_reference_ptr = nullptr; + std::array attachments; + u32 num_attachments = 0; + if (color_format != VK_FORMAT_UNDEFINED) + { + attachments[num_attachments] = {0, + color_format, + samples, + load_op, + VK_ATTACHMENT_STORE_OP_STORE, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_DONT_CARE, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + color_reference.attachment = num_attachments; + color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + color_reference_ptr = &color_reference; + num_attachments++; + } + if (depth_format != VK_FORMAT_UNDEFINED) + { + attachments[num_attachments] = {0, + depth_format, + samples, + load_op, + VK_ATTACHMENT_STORE_OP_STORE, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_DONT_CARE, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL}; + depth_reference.attachment = num_attachments; + depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depth_reference_ptr = &depth_reference; + num_attachments++; + } + + VkSubpassDescription subpass = {0, + VK_PIPELINE_BIND_POINT_GRAPHICS, + 0, + nullptr, + color_reference_ptr ? 1u : 0u, + color_reference_ptr ? color_reference_ptr : nullptr, + nullptr, + depth_reference_ptr, + 0, + nullptr}; + VkRenderPassCreateInfo pass_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + nullptr, + 0, + num_attachments, + attachments.data(), + 1, + &subpass, + 0, + nullptr}; + + VkRenderPass pass; + VkResult res = vkCreateRenderPass(m_device, &pass_info, nullptr, &pass); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateRenderPass failed: "); + return VK_NULL_HANDLE; + } + + m_render_pass_cache.emplace(key, pass); + return pass; +} + +} // namespace Vulkan diff --git a/jni/common/vulkan/context.h b/jni/common/vulkan/context.h new file mode 100644 index 0000000..6fec690 --- /dev/null +++ b/jni/common/vulkan/context.h @@ -0,0 +1,242 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#pragma once + +#include "../types.h" +#include "vulkan_loader.h" +#include +#include +#include +#include +#include +#include + +struct WindowInfo; + +namespace Vulkan { + +class SwapChain; + +class Context +{ +public: + enum : u32 + { + NUM_COMMAND_BUFFERS = 2 + }; + + ~Context(); + + // Determines if the Vulkan validation layer is available on the system. + static bool CheckValidationLayerAvailablility(); + + // Helper method to create a Vulkan instance. + static VkInstance CreateVulkanInstance(bool enable_surface, bool enable_debug_report, bool enable_validation_layer); + + // Returns a list of Vulkan-compatible GPUs. + using GPUList = std::vector; + using GPUNameList = std::vector; + static GPUList EnumerateGPUs(VkInstance instance); + static GPUNameList EnumerateGPUNames(VkInstance instance); + + // Creates a new context and sets it up as global. + static bool Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr* out_swap_chain, + bool enable_debug_reports, bool enable_validation_layer); + + // Creates a new context from a pre-existing instance. + static bool CreateFromExistingInstance(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, + bool take_ownership, bool enable_validation_layer, bool enable_debug_reports, + const char** required_device_extensions = nullptr, + u32 num_required_device_extensions = 0, + const char** required_device_layers = nullptr, + u32 num_required_device_layers = 0, + const VkPhysicalDeviceFeatures* required_features = nullptr); + + // Destroys context. + static void Destroy(); + + // Enable/disable debug message runtime. + bool EnableDebugReports(); + void DisableDebugReports(); + + // Global state accessors + ALWAYS_INLINE VkInstance GetVulkanInstance() const { return m_instance; } + ALWAYS_INLINE VkPhysicalDevice GetPhysicalDevice() const { return m_physical_device; } + ALWAYS_INLINE VkDevice GetDevice() const { return m_device; } + ALWAYS_INLINE VkQueue GetGraphicsQueue() const { return m_graphics_queue; } + ALWAYS_INLINE u32 GetGraphicsQueueFamilyIndex() const { return m_graphics_queue_family_index; } + ALWAYS_INLINE VkQueue GetPresentQueue() const { return m_present_queue; } + ALWAYS_INLINE u32 GetPresentQueueFamilyIndex() const { return m_present_queue_family_index; } + ALWAYS_INLINE const VkQueueFamilyProperties& GetGraphicsQueueProperties() const + { + return m_graphics_queue_properties; + } + ALWAYS_INLINE const VkPhysicalDeviceMemoryProperties& GetDeviceMemoryProperties() const + { + return m_device_memory_properties; + } + ALWAYS_INLINE const VkPhysicalDeviceProperties& GetDeviceProperties() const { return m_device_properties; } + ALWAYS_INLINE const VkPhysicalDeviceFeatures& GetDeviceFeatures() const { return m_device_features; } + ALWAYS_INLINE const VkPhysicalDeviceLimits& GetDeviceLimits() const { return m_device_properties.limits; } + + // Support bits + ALWAYS_INLINE bool SupportsGeometryShaders() const { return m_device_features.geometryShader == VK_TRUE; } + ALWAYS_INLINE bool SupportsDualSourceBlend() const { return m_device_features.dualSrcBlend == VK_TRUE; } + + // Helpers for getting constants + ALWAYS_INLINE VkDeviceSize GetUniformBufferAlignment() const + { + return m_device_properties.limits.minUniformBufferOffsetAlignment; + } + ALWAYS_INLINE VkDeviceSize GetTexelBufferAlignment() const + { + return m_device_properties.limits.minUniformBufferOffsetAlignment; + } + ALWAYS_INLINE VkDeviceSize GetBufferImageGranularity() const + { + return m_device_properties.limits.bufferImageGranularity; + } + + // Finds a memory type index for the specified memory properties and the bits returned by + // vkGetImageMemoryRequirements + bool GetMemoryType(u32 bits, VkMemoryPropertyFlags properties, u32* out_type_index); + u32 GetMemoryType(u32 bits, VkMemoryPropertyFlags properties); + + // Finds a memory type for upload or readback buffers. + u32 GetUploadMemoryType(u32 bits, bool* is_coherent = nullptr); + u32 GetReadbackMemoryType(u32 bits, bool* is_coherent = nullptr, bool* is_cached = nullptr); + + // Creates a simple render pass. + VkRenderPass GetRenderPass(VkFormat color_format, VkFormat depth_format, VkSampleCountFlagBits samples, + VkAttachmentLoadOp load_op); + + // These command buffers are allocated per-frame. They are valid until the command buffer + // is submitted, after that you should call these functions again. + ALWAYS_INLINE VkDescriptorPool GetGlobalDescriptorPool() const { return m_global_descriptor_pool; } + ALWAYS_INLINE VkCommandBuffer GetCurrentCommandBuffer() const { return m_current_command_buffer; } + ALWAYS_INLINE VkDescriptorPool GetCurrentDescriptorPool() const + { + return m_frame_resources[m_current_frame].descriptor_pool; + } + + /// Allocates a descriptor set from the pool reserved for the current frame. + VkDescriptorSet AllocateDescriptorSet(VkDescriptorSetLayout set_layout); + + /// Allocates a descriptor set from the pool reserved for the current frame. + VkDescriptorSet AllocateGlobalDescriptorSet(VkDescriptorSetLayout set_layout); + + /// Frees a descriptor set allocated from the global pool. + void FreeGlobalDescriptorSet(VkDescriptorSet set); + + // Gets the fence that will be signaled when the currently executing command buffer is + // queued and executed. Do not wait for this fence before the buffer is executed. + ALWAYS_INLINE VkFence GetCurrentCommandBufferFence() const { return m_frame_resources[m_current_frame].fence; } + + // Fence "counters" are used to track which commands have been completed by the GPU. + // If the last completed fence counter is greater or equal to N, it means that the work + // associated counter N has been completed by the GPU. The value of N to associate with + // commands can be retreived by calling GetCurrentFenceCounter(). + u64 GetCompletedFenceCounter() const { return m_completed_fence_counter; } + + // Gets the fence that will be signaled when the currently executing command buffer is + // queued and executed. Do not wait for this fence before the buffer is executed. + u64 GetCurrentFenceCounter() const { return m_frame_resources[m_current_frame].fence_counter; } + + void SubmitCommandBuffer(VkSemaphore wait_semaphore = VK_NULL_HANDLE, VkSemaphore signal_semaphore = VK_NULL_HANDLE, + VkSwapchainKHR present_swap_chain = VK_NULL_HANDLE, + uint32_t present_image_index = 0xFFFFFFFF); + void MoveToNextCommandBuffer(); + + void ExecuteCommandBuffer(bool wait_for_completion); + + // Was the last present submitted to the queue a failure? If so, we must recreate our swapchain. + bool CheckLastPresentFail(); + + // Schedule a vulkan resource for destruction later on. This will occur when the command buffer + // is next re-used, and the GPU has finished working with the specified resource. + void DeferBufferDestruction(VkBuffer object); + void DeferBufferViewDestruction(VkBufferView object); + void DeferDeviceMemoryDestruction(VkDeviceMemory object); + void DeferFramebufferDestruction(VkFramebuffer object); + void DeferImageDestruction(VkImage object); + void DeferImageViewDestruction(VkImageView object); + void DeferPipelineDestruction(VkPipeline pipeline); + + // Wait for a fence to be completed. + // Also invokes callbacks for completion. + void WaitForFenceCounter(u64 fence_counter); + + void WaitForGPUIdle(); + +private: + Context(VkInstance instance, VkPhysicalDevice physical_device, bool owns_device); + + using ExtensionList = std::vector; + static bool SelectInstanceExtensions(ExtensionList* extension_list, bool enable_surface, bool enable_debug_report); + bool SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface); + bool SelectDeviceFeatures(const VkPhysicalDeviceFeatures* required_features); + bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, const char** required_device_extensions, + u32 num_required_device_extensions, const char** required_device_layers, + u32 num_required_device_layers, const VkPhysicalDeviceFeatures* required_features); + + bool CreateCommandBuffers(); + void DestroyCommandBuffers(); + bool CreateGlobalDescriptorPool(); + void DestroyGlobalDescriptorPool(); + void DestroyRenderPassCache(); + + void ActivateCommandBuffer(u32 index); + void WaitForCommandBufferCompletion(u32 index); + + struct FrameResources + { + // [0] - Init (upload) command buffer, [1] - draw command buffer + VkCommandPool command_pool = VK_NULL_HANDLE; + VkCommandBuffer command_buffer = VK_NULL_HANDLE; + VkDescriptorPool descriptor_pool = VK_NULL_HANDLE; + VkFence fence = VK_NULL_HANDLE; + u64 fence_counter = 0; + bool needs_fence_wait = false; + + std::vector> cleanup_resources; + }; + + VkInstance m_instance = VK_NULL_HANDLE; + VkPhysicalDevice m_physical_device = VK_NULL_HANDLE; + VkDevice m_device = VK_NULL_HANDLE; + + VkCommandBuffer m_current_command_buffer = VK_NULL_HANDLE; + + VkDescriptorPool m_global_descriptor_pool = VK_NULL_HANDLE; + + VkQueue m_graphics_queue = VK_NULL_HANDLE; + u32 m_graphics_queue_family_index = 0; + VkQueue m_present_queue = VK_NULL_HANDLE; + u32 m_present_queue_family_index = 0; + + std::array m_frame_resources; + u64 m_next_fence_counter = 1; + u64 m_completed_fence_counter = 0; + u32 m_current_frame; + + bool m_owns_device = false; + bool m_last_present_failed = false; + + // Render pass cache + using RenderPassCacheKey = std::tuple; + std::map m_render_pass_cache; + + VkDebugReportCallbackEXT m_debug_report_callback = VK_NULL_HANDLE; + + VkQueueFamilyProperties m_graphics_queue_properties = {}; + VkPhysicalDeviceFeatures m_device_features = {}; + VkPhysicalDeviceProperties m_device_properties = {}; + VkPhysicalDeviceMemoryProperties m_device_memory_properties = {}; +}; + +} // namespace Vulkan + +extern std::unique_ptr g_vulkan_context; diff --git a/jni/common/vulkan/shader_cache.cpp b/jni/common/vulkan/shader_cache.cpp new file mode 100644 index 0000000..80951cd --- /dev/null +++ b/jni/common/vulkan/shader_cache.cpp @@ -0,0 +1,518 @@ +#include "shader_cache.h" +#include "../assert.h" +#include "../file_system.h" +#include "../log.h" +#include "../md5_digest.h" +#include "context.h" +#include "shader_compiler.h" +#include "util.h" +Log_SetChannel(Vulkan::ShaderCache); + +// TODO: store the driver version and stuff in the shader header + +std::unique_ptr g_vulkan_shader_cache; + +namespace Vulkan { + +using ShaderCompiler::SPIRVCodeType; +using ShaderCompiler::SPIRVCodeVector; + +#pragma pack(push, 4) +struct VK_PIPELINE_CACHE_HEADER +{ + u32 header_length; + u32 header_version; + u32 vendor_id; + u32 device_id; + u8 uuid[VK_UUID_SIZE]; +}; + +struct CacheIndexEntry +{ + u64 source_hash_low; + u64 source_hash_high; + u32 source_length; + u32 shader_type; + u32 file_offset; + u32 blob_size; +}; +#pragma pack(pop) + +static bool ValidatePipelineCacheHeader(const VK_PIPELINE_CACHE_HEADER& header) +{ + if (header.header_length < sizeof(VK_PIPELINE_CACHE_HEADER)) + { + Log_ErrorPrintf("Pipeline cache failed validation: Invalid header length"); + return false; + } + + if (header.header_version != VK_PIPELINE_CACHE_HEADER_VERSION_ONE) + { + Log_ErrorPrintf("Pipeline cache failed validation: Invalid header version"); + return false; + } + + if (header.vendor_id != g_vulkan_context->GetDeviceProperties().vendorID) + { + Log_ErrorPrintf("Pipeline cache failed validation: Incorrect vendor ID (file: 0x%X, device: 0x%X)", + header.vendor_id, g_vulkan_context->GetDeviceProperties().vendorID); + return false; + } + + if (header.device_id != g_vulkan_context->GetDeviceProperties().deviceID) + { + Log_ErrorPrintf("Pipeline cache failed validation: Incorrect device ID (file: 0x%X, device: 0x%X)", + header.device_id, g_vulkan_context->GetDeviceProperties().deviceID); + return false; + } + + if (std::memcmp(header.uuid, g_vulkan_context->GetDeviceProperties().pipelineCacheUUID, VK_UUID_SIZE) != 0) + { + Log_ErrorPrintf("Pipeline cache failed validation: Incorrect UUID"); + return false; + } + + return true; +} + +static void FillPipelineCacheHeader(VK_PIPELINE_CACHE_HEADER* header) +{ + header->header_length = sizeof(VK_PIPELINE_CACHE_HEADER); + header->header_version = VK_PIPELINE_CACHE_HEADER_VERSION_ONE; + header->vendor_id = g_vulkan_context->GetDeviceProperties().vendorID; + header->device_id = g_vulkan_context->GetDeviceProperties().deviceID; + std::memcpy(header->uuid, g_vulkan_context->GetDeviceProperties().pipelineCacheUUID, VK_UUID_SIZE); +} + +ShaderCache::ShaderCache() = default; + +ShaderCache::~ShaderCache() +{ + CloseShaderCache(); + FlushPipelineCache(); + ClosePipelineCache(); +} + +bool ShaderCache::CacheIndexKey::operator==(const CacheIndexKey& key) const +{ + return (source_hash_low == key.source_hash_low && source_hash_high == key.source_hash_high && + source_length == key.source_length && shader_type == key.shader_type); +} + +bool ShaderCache::CacheIndexKey::operator!=(const CacheIndexKey& key) const +{ + return (source_hash_low != key.source_hash_low || source_hash_high != key.source_hash_high || + source_length != key.source_length || shader_type != key.shader_type); +} + +void ShaderCache::Create(std::string_view base_path, bool debug) +{ + Assert(!g_vulkan_shader_cache); + g_vulkan_shader_cache.reset(new ShaderCache()); + g_vulkan_shader_cache->Open(base_path, debug); +} + +void ShaderCache::Destroy() +{ + g_vulkan_shader_cache.reset(); +} + +void ShaderCache::Open(std::string_view base_path, bool debug) +{ + m_debug = debug; + + if (!base_path.empty()) + { + m_pipeline_cache_filename = GetPipelineCacheBaseFileName(base_path, debug); + + const std::string base_filename = GetShaderCacheBaseFileName(base_path, debug); + const std::string index_filename = base_filename + ".idx"; + const std::string blob_filename = base_filename + ".bin"; + + if (!ReadExistingShaderCache(index_filename, blob_filename)) + CreateNewShaderCache(index_filename, blob_filename); + + if (!ReadExistingPipelineCache()) + CreateNewPipelineCache(); + } + else + { + CreateNewPipelineCache(); + } +} + +VkPipelineCache ShaderCache::GetPipelineCache(bool set_dirty /*= true*/) +{ + if (m_pipeline_cache == VK_NULL_HANDLE) + return VK_NULL_HANDLE; + + m_pipeline_cache_dirty |= set_dirty; + return m_pipeline_cache; +} + +bool ShaderCache::CreateNewShaderCache(const std::string& index_filename, const std::string& blob_filename) +{ + if (FileSystem::FileExists(index_filename.c_str())) + { + Log_WarningPrintf("Removing existing index file '%s'", index_filename.c_str()); + FileSystem::DeleteFile(index_filename.c_str()); + } + if (FileSystem::FileExists(blob_filename.c_str())) + { + Log_WarningPrintf("Removing existing blob file '%s'", blob_filename.c_str()); + FileSystem::DeleteFile(blob_filename.c_str()); + } + + m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "wb"); + if (!m_index_file) + { + Log_ErrorPrintf("Failed to open index file '%s' for writing", index_filename.c_str()); + return false; + } + + const u32 index_version = FILE_VERSION; + VK_PIPELINE_CACHE_HEADER header; + FillPipelineCacheHeader(&header); + + if (std::fwrite(&index_version, sizeof(index_version), 1, m_index_file) != 1 || + std::fwrite(&header, sizeof(header), 1, m_index_file) != 1) + { + Log_ErrorPrintf("Failed to write header to index file '%s'", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + FileSystem::DeleteFile(index_filename.c_str()); + return false; + } + + m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "w+b"); + if (!m_blob_file) + { + Log_ErrorPrintf("Failed to open blob file '%s' for writing", blob_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + FileSystem::DeleteFile(index_filename.c_str()); + return false; + } + + return true; +} + +bool ShaderCache::ReadExistingShaderCache(const std::string& index_filename, const std::string& blob_filename) +{ + m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "r+b"); + if (!m_index_file) + return false; + + u32 file_version; + if (std::fread(&file_version, sizeof(file_version), 1, m_index_file) != 1 || file_version != FILE_VERSION) + { + Log_ErrorPrintf("Bad file version in '%s'", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + VK_PIPELINE_CACHE_HEADER header; + if (std::fread(&header, sizeof(header), 1, m_index_file) != 1 || !ValidatePipelineCacheHeader(header)) + { + Log_ErrorPrintf("Mismatched pipeline cache header in '%s' (GPU/driver changed?)", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "a+b"); + if (!m_blob_file) + { + Log_ErrorPrintf("Blob file '%s' is missing", blob_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + std::fseek(m_blob_file, 0, SEEK_END); + const u32 blob_file_size = static_cast(std::ftell(m_blob_file)); + + for (;;) + { + CacheIndexEntry entry; + if (std::fread(&entry, sizeof(entry), 1, m_index_file) != 1 || + (entry.file_offset + entry.blob_size) > blob_file_size) + { + if (std::feof(m_index_file)) + break; + + Log_ErrorPrintf("Failed to read entry from '%s', corrupt file?", index_filename.c_str()); + m_index.clear(); + std::fclose(m_blob_file); + m_blob_file = nullptr; + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + const CacheIndexKey key{entry.source_hash_low, entry.source_hash_high, entry.source_length, + static_cast(entry.shader_type)}; + const CacheIndexData data{entry.file_offset, entry.blob_size}; + m_index.emplace(key, data); + } + + // ensure we don't write before seeking + std::fseek(m_index_file, 0, SEEK_END); + + Log_InfoPrintf("Read %zu entries from '%s'", m_index.size(), index_filename.c_str()); + return true; +} + +void ShaderCache::CloseShaderCache() +{ + if (m_index_file) + { + std::fclose(m_index_file); + m_index_file = nullptr; + } + if (m_blob_file) + { + std::fclose(m_blob_file); + m_blob_file = nullptr; + } +} + +bool ShaderCache::CreateNewPipelineCache() +{ + if (!m_pipeline_cache_filename.empty() && FileSystem::FileExists(m_pipeline_cache_filename.c_str())) + { + Log_WarningPrintf("Removing existing pipeline cache '%s'", m_pipeline_cache_filename.c_str()); + FileSystem::DeleteFile(m_pipeline_cache_filename.c_str()); + } + + const VkPipelineCacheCreateInfo ci{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, nullptr, 0, 0, nullptr}; + VkResult res = vkCreatePipelineCache(g_vulkan_context->GetDevice(), &ci, nullptr, &m_pipeline_cache); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreatePipelineCache() failed: "); + return false; + } + + m_pipeline_cache_dirty = true; + return true; +} + +bool ShaderCache::ReadExistingPipelineCache() +{ + std::optional> data = FileSystem::ReadBinaryFile(m_pipeline_cache_filename.c_str()); + if (!data.has_value()) + return false; + + if (data->size() < sizeof(VK_PIPELINE_CACHE_HEADER)) + { + Log_ErrorPrintf("Pipeline cache at '%s' is too small", m_pipeline_cache_filename.c_str()); + return false; + } + + VK_PIPELINE_CACHE_HEADER header; + std::memcpy(&header, data->data(), sizeof(header)); + if (!ValidatePipelineCacheHeader(header)) + return false; + + const VkPipelineCacheCreateInfo ci{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, nullptr, 0, data->size(), + data->data()}; + VkResult res = vkCreatePipelineCache(g_vulkan_context->GetDevice(), &ci, nullptr, &m_pipeline_cache); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreatePipelineCache() failed: "); + return false; + } + + return true; +} + +bool ShaderCache::FlushPipelineCache() +{ + if (m_pipeline_cache == VK_NULL_HANDLE || !m_pipeline_cache_dirty || m_pipeline_cache_filename.empty()) + return false; + + size_t data_size; + VkResult res = vkGetPipelineCacheData(g_vulkan_context->GetDevice(), m_pipeline_cache, &data_size, nullptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData() failed: "); + return false; + } + + std::vector data(data_size); + res = vkGetPipelineCacheData(g_vulkan_context->GetDevice(), m_pipeline_cache, &data_size, data.data()); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData() (2) failed: "); + return false; + } + + data.resize(data_size); + + // Save disk writes if it hasn't changed, think of the poor SSDs. + FILESYSTEM_STAT_DATA sd; + if (!FileSystem::StatFile(m_pipeline_cache_filename.c_str(), &sd) || sd.Size != static_cast(data_size)) + { + Log_InfoPrintf("Writing %zu bytes to '%s'", data_size, m_pipeline_cache_filename.c_str()); + if (!FileSystem::WriteBinaryFile(m_pipeline_cache_filename.c_str(), data.data(), data.size())) + { + Log_ErrorPrintf("Failed to write pipeline cache to '%s'", m_pipeline_cache_filename.c_str()); + return false; + } + } + else + { + Log_InfoPrintf("Skipping updating pipeline cache '%s' due to no changes.", m_pipeline_cache_filename.c_str()); + } + + m_pipeline_cache_dirty = false; + return true; +} + +void ShaderCache::ClosePipelineCache() +{ + if (m_pipeline_cache == VK_NULL_HANDLE) + return; + + vkDestroyPipelineCache(g_vulkan_context->GetDevice(), m_pipeline_cache, nullptr); + m_pipeline_cache = VK_NULL_HANDLE; +} + +std::string ShaderCache::GetShaderCacheBaseFileName(const std::string_view& base_path, bool debug) +{ + std::string base_filename(base_path); + base_filename += "vulkan_shaders"; + + if (debug) + base_filename += "_debug"; + + return base_filename; +} + +std::string ShaderCache::GetPipelineCacheBaseFileName(const std::string_view& base_path, bool debug) +{ + std::string base_filename(base_path); + base_filename += "vulkan_pipelines"; + + if (debug) + base_filename += "_debug"; + + base_filename += ".bin"; + return base_filename; +} + +ShaderCache::CacheIndexKey ShaderCache::GetCacheKey(ShaderCompiler::Type type, const std::string_view& shader_code) +{ + union HashParts + { + struct + { + u64 hash_low; + u64 hash_high; + }; + u8 hash[16]; + }; + HashParts h; + + MD5Digest digest; + digest.Update(shader_code.data(), static_cast(shader_code.length())); + digest.Final(h.hash); + + return CacheIndexKey{h.hash_low, h.hash_high, static_cast(shader_code.length()), type}; +} + +std::optional ShaderCache::GetShaderSPV(ShaderCompiler::Type type, + std::string_view shader_code) +{ + const auto key = GetCacheKey(type, shader_code); + auto iter = m_index.find(key); + if (iter == m_index.end()) + return CompileAndAddShaderSPV(key, shader_code); + + SPIRVCodeVector spv(iter->second.blob_size); + if (std::fseek(m_blob_file, iter->second.file_offset, SEEK_SET) != 0 || + std::fread(spv.data(), sizeof(SPIRVCodeType), iter->second.blob_size, m_blob_file) != iter->second.blob_size) + { + Log_ErrorPrintf("Read blob from file failed, recompiling"); + return ShaderCompiler::CompileShader(type, shader_code, m_debug); + } + + return spv; +} + +VkShaderModule ShaderCache::GetShaderModule(ShaderCompiler::Type type, std::string_view shader_code) +{ + std::optional spv = GetShaderSPV(type, shader_code); + if (!spv.has_value()) + return VK_NULL_HANDLE; + + const VkShaderModuleCreateInfo ci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, nullptr, 0, + spv->size() * sizeof(SPIRVCodeType), spv->data()}; + + VkShaderModule mod; + VkResult res = vkCreateShaderModule(g_vulkan_context->GetDevice(), &ci, nullptr, &mod); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateShaderModule() failed: "); + return VK_NULL_HANDLE; + } + + return mod; +} + +VkShaderModule ShaderCache::GetVertexShader(std::string_view shader_code) +{ + return GetShaderModule(ShaderCompiler::Type::Vertex, std::move(shader_code)); +} + +VkShaderModule ShaderCache::GetGeometryShader(std::string_view shader_code) +{ + return GetShaderModule(ShaderCompiler::Type::Geometry, std::move(shader_code)); +} + +VkShaderModule ShaderCache::GetFragmentShader(std::string_view shader_code) +{ + return GetShaderModule(ShaderCompiler::Type::Fragment, std::move(shader_code)); +} + +VkShaderModule ShaderCache::GetComputeShader(std::string_view shader_code) +{ + return GetShaderModule(ShaderCompiler::Type::Compute, std::move(shader_code)); +} + +std::optional ShaderCache::CompileAndAddShaderSPV(const CacheIndexKey& key, + std::string_view shader_code) +{ + std::optional spv = ShaderCompiler::CompileShader(key.shader_type, shader_code, m_debug); + if (!spv.has_value()) + return {}; + + if (!m_blob_file || std::fseek(m_blob_file, 0, SEEK_END) != 0) + return spv; + + CacheIndexData data; + data.file_offset = static_cast(std::ftell(m_blob_file)); + data.blob_size = static_cast(spv->size()); + + CacheIndexEntry entry = {}; + entry.source_hash_low = key.source_hash_low; + entry.source_hash_high = key.source_hash_high; + entry.source_length = key.source_length; + entry.shader_type = static_cast(key.shader_type); + entry.blob_size = data.blob_size; + entry.file_offset = data.file_offset; + + if (std::fwrite(spv->data(), sizeof(SPIRVCodeType), entry.blob_size, m_blob_file) != entry.blob_size || + std::fflush(m_blob_file) != 0 || std::fwrite(&entry, sizeof(entry), 1, m_index_file) != 1 || + std::fflush(m_index_file) != 0) + { + Log_ErrorPrintf("Failed to write shader blob to file"); + return spv; + } + + m_index.emplace(key, data); + return spv; +} + +} // namespace Vulkan \ No newline at end of file diff --git a/jni/common/vulkan/shader_cache.h b/jni/common/vulkan/shader_cache.h new file mode 100644 index 0000000..7fdefc0 --- /dev/null +++ b/jni/common/vulkan/shader_cache.h @@ -0,0 +1,102 @@ +#pragma once +#include "../hash_combine.h" +#include "../types.h" +#include "shader_compiler.h" +#include "vulkan_loader.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Vulkan { + +class ShaderCache +{ +public: + ~ShaderCache(); + + static void Create(std::string_view base_path, bool debug); + static void Destroy(); + + /// Returns a handle to the pipeline cache. Set set_dirty to true if you are planning on writing to it externally. + VkPipelineCache GetPipelineCache(bool set_dirty = true); + + /// Writes pipeline cache to file, saving all newly compiled pipelines. + bool FlushPipelineCache(); + + std::optional GetShaderSPV(ShaderCompiler::Type type, std::string_view shader_code); + VkShaderModule GetShaderModule(ShaderCompiler::Type type, std::string_view shader_code); + + VkShaderModule GetVertexShader(std::string_view shader_code); + VkShaderModule GetGeometryShader(std::string_view shader_code); + VkShaderModule GetFragmentShader(std::string_view shader_code); + VkShaderModule GetComputeShader(std::string_view shader_code); + +private: + static constexpr u32 FILE_VERSION = 1; + + struct CacheIndexKey + { + u64 source_hash_low; + u64 source_hash_high; + u32 source_length; + ShaderCompiler::Type shader_type; + + bool operator==(const CacheIndexKey& key) const; + bool operator!=(const CacheIndexKey& key) const; + }; + + struct CacheIndexEntryHasher + { + std::size_t operator()(const CacheIndexKey& e) const noexcept + { + std::size_t h = 0; + hash_combine(h, e.source_hash_low, e.source_hash_high, e.source_length, e.shader_type); + return h; + } + }; + + struct CacheIndexData + { + u32 file_offset; + u32 blob_size; + }; + + using CacheIndex = std::unordered_map; + + ShaderCache(); + + static std::string GetShaderCacheBaseFileName(const std::string_view& base_path, bool debug); + static std::string GetPipelineCacheBaseFileName(const std::string_view& base_path, bool debug); + static CacheIndexKey GetCacheKey(ShaderCompiler::Type type, const std::string_view& shader_code); + + void Open(std::string_view base_path, bool debug); + + bool CreateNewShaderCache(const std::string& index_filename, const std::string& blob_filename); + bool ReadExistingShaderCache(const std::string& index_filename, const std::string& blob_filename); + void CloseShaderCache(); + + bool CreateNewPipelineCache(); + bool ReadExistingPipelineCache(); + void ClosePipelineCache(); + + std::optional CompileAndAddShaderSPV(const CacheIndexKey& key, + std::string_view shader_code); + + std::FILE* m_index_file = nullptr; + std::FILE* m_blob_file = nullptr; + std::string m_pipeline_cache_filename; + + CacheIndex m_index; + + VkPipelineCache m_pipeline_cache = VK_NULL_HANDLE; + bool m_debug = false; + bool m_pipeline_cache_dirty = false; +}; + +} // namespace Vulkan + +extern std::unique_ptr g_vulkan_shader_cache; diff --git a/jni/common/vulkan/shader_compiler.cpp b/jni/common/vulkan/shader_compiler.cpp new file mode 100644 index 0000000..9340522 --- /dev/null +++ b/jni/common/vulkan/shader_compiler.cpp @@ -0,0 +1,185 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#include "shader_compiler.h" +#include "../assert.h" +#include "../log.h" +#include "../string_util.h" +#include "util.h" +#include +#include +#include +Log_SetChannel(Vulkan::ShaderCompiler); + +// glslang includes +#include "SPIRV/GlslangToSpv.h" +#include "StandAlone/ResourceLimits.h" +#include "glslang/Public/ShaderLang.h" + +namespace Vulkan::ShaderCompiler { +// Registers itself for cleanup via atexit +bool InitializeGlslang(); + +static unsigned s_next_bad_shader_id = 1; + +static bool glslang_initialized = false; + +static std::optional CompileShaderToSPV(EShLanguage stage, const char* stage_filename, + std::string_view source) +{ + if (!InitializeGlslang()) + return std::nullopt; + + std::unique_ptr shader = std::make_unique(stage); + std::unique_ptr program; + glslang::TShader::ForbidIncluder includer; + EProfile profile = ECoreProfile; + EShMessages messages = static_cast(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules); + int default_version = 450; + + std::string full_source_code; + const char* pass_source_code = source.data(); + int pass_source_code_length = static_cast(source.size()); + shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1); + + auto DumpBadShader = [&](const char* msg) { + std::string filename = StringUtil::StdStringFromFormat("bad_shader_%u.txt", s_next_bad_shader_id++); + Log::Writef("Vulkan", "CompileShaderToSPV", LOGLEVEL_ERROR, "%s, writing to %s", msg, filename.c_str()); + + std::ofstream ofs(filename.c_str(), std::ofstream::out | std::ofstream::binary); + if (ofs.is_open()) + { + ofs << source; + ofs << "\n"; + + ofs << msg << std::endl; + ofs << "Shader Info Log:" << std::endl; + ofs << shader->getInfoLog() << std::endl; + ofs << shader->getInfoDebugLog() << std::endl; + if (program) + { + ofs << "Program Info Log:" << std::endl; + ofs << program->getInfoLog() << std::endl; + ofs << program->getInfoDebugLog() << std::endl; + } + + ofs.close(); + } + }; + + if (!shader->parse(&glslang::DefaultTBuiltInResource, default_version, profile, false, true, messages, includer)) + { + DumpBadShader("Failed to parse shader"); + return std::nullopt; + } + + // Even though there's only a single shader, we still need to link it to generate SPV + program = std::make_unique(); + program->addShader(shader.get()); + if (!program->link(messages)) + { + DumpBadShader("Failed to link program"); + return std::nullopt; + } + + glslang::TIntermediate* intermediate = program->getIntermediate(stage); + if (!intermediate) + { + DumpBadShader("Failed to generate SPIR-V"); + return std::nullopt; + } + + SPIRVCodeVector out_code; + spv::SpvBuildLogger logger; + glslang::GlslangToSpv(*intermediate, out_code, &logger); + + // Write out messages + // Temporary: skip if it contains "Warning, version 450 is not yet complete; most version-specific + // features are present, but some are missing." + if (std::strlen(shader->getInfoLog()) > 108) + Log_WarningPrintf("Shader info log: %s", shader->getInfoLog()); + if (std::strlen(shader->getInfoDebugLog()) > 0) + Log_WarningPrintf("Shader debug info log: %s", shader->getInfoDebugLog()); + if (std::strlen(program->getInfoLog()) > 25) + Log_WarningPrintf("Program info log: %s", program->getInfoLog()); + if (std::strlen(program->getInfoDebugLog()) > 0) + Log_WarningPrintf("Program debug info log: %s", program->getInfoDebugLog()); + std::string spv_messages = logger.getAllMessages(); + if (!spv_messages.empty()) + Log_WarningPrintf("SPIR-V conversion messages: %s", spv_messages.c_str()); + + return out_code; +} + +bool InitializeGlslang() +{ + if (glslang_initialized) + return true; + + if (!glslang::InitializeProcess()) + { + Panic("Failed to initialize glslang shader compiler"); + return false; + } + +#ifndef LIBRETRO + std::atexit([]() { glslang::FinalizeProcess(); }); +#endif + + glslang_initialized = true; + return true; +} + +void DeinitializeGlslang() +{ + if (!glslang_initialized) + return; + + glslang::FinalizeProcess(); + glslang_initialized = false; +} + +std::optional CompileVertexShader(std::string_view source_code) +{ + return CompileShaderToSPV(EShLangVertex, "vs", source_code); +} + +std::optional CompileGeometryShader(std::string_view source_code) +{ + return CompileShaderToSPV(EShLangGeometry, "gs", source_code); +} + +std::optional CompileFragmentShader(std::string_view source_code) +{ + return CompileShaderToSPV(EShLangFragment, "ps", source_code); +} + +std::optional CompileComputeShader(std::string_view source_code) +{ + return CompileShaderToSPV(EShLangCompute, "cs", source_code); +} + +std::optional CompileShader(Type type, std::string_view source_code, bool debug) +{ + switch (type) + { + case Type::Vertex: + return CompileShaderToSPV(EShLangVertex, "vs", source_code); + + case Type::Geometry: + return CompileShaderToSPV(EShLangGeometry, "gs", source_code); + + case Type::Fragment: + return CompileShaderToSPV(EShLangFragment, "ps", source_code); + + case Type::Compute: + return CompileShaderToSPV(EShLangCompute, "cs", source_code); + + default: + return std::nullopt; + } +} + +} // namespace Vulkan::ShaderCompiler diff --git a/jni/common/vulkan/shader_compiler.h b/jni/common/vulkan/shader_compiler.h new file mode 100644 index 0000000..8d37bdb --- /dev/null +++ b/jni/common/vulkan/shader_compiler.h @@ -0,0 +1,44 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#pragma once + +#include "../types.h" +#include +#include +#include + +namespace Vulkan::ShaderCompiler { + +// Shader types +enum class Type +{ + Vertex, + Geometry, + Fragment, + Compute +}; + +void DeinitializeGlslang(); + +// SPIR-V compiled code type +using SPIRVCodeType = u32; +using SPIRVCodeVector = std::vector; + +// Compile a vertex shader to SPIR-V. +std::optional CompileVertexShader(std::string_view source_code); + +// Compile a geometry shader to SPIR-V. +std::optional CompileGeometryShader(std::string_view source_code); + +// Compile a fragment shader to SPIR-V. +std::optional CompileFragmentShader(std::string_view source_code); + +// Compile a compute shader to SPIR-V. +std::optional CompileComputeShader(std::string_view source_code); + +std::optional CompileShader(Type type, std::string_view source_code, bool debug); + +} // namespace Vulkan::ShaderCompiler diff --git a/jni/common/vulkan/staging_buffer.cpp b/jni/common/vulkan/staging_buffer.cpp new file mode 100644 index 0000000..45b3291 --- /dev/null +++ b/jni/common/vulkan/staging_buffer.cpp @@ -0,0 +1,253 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#include "staging_buffer.h" +#include "../assert.h" +#include "context.h" +#include "util.h" + +namespace Vulkan { +StagingBuffer::StagingBuffer() = default; + +StagingBuffer::StagingBuffer(StagingBuffer&& move) + : m_type(move.m_type), m_buffer(move.m_buffer), m_memory(move.m_memory), m_size(move.m_size), + m_coherent(move.m_coherent), m_map_pointer(move.m_map_pointer), m_map_offset(move.m_map_offset), + m_map_size(move.m_map_size) +{ + move.m_type = Type::Upload; + move.m_buffer = VK_NULL_HANDLE; + move.m_memory = VK_NULL_HANDLE; + move.m_size = 0; + move.m_coherent = false; + move.m_map_pointer = nullptr; + move.m_map_offset = 0; + move.m_map_size = 0; +} + +StagingBuffer::~StagingBuffer() +{ + if (IsValid()) + Destroy(true); +} + +StagingBuffer& StagingBuffer::operator=(StagingBuffer&& move) +{ + if (IsValid()) + Destroy(true); + + std::swap(m_type, move.m_type); + std::swap(m_buffer, move.m_buffer); + std::swap(m_memory, move.m_memory); + std::swap(m_size, move.m_size); + std::swap(m_coherent, move.m_coherent); + std::swap(m_map_pointer, move.m_map_pointer); + std::swap(m_map_offset, move.m_map_offset); + std::swap(m_map_size, move.m_map_size); + return *this; +} + +bool StagingBuffer::Map(VkDeviceSize offset, VkDeviceSize size) +{ + m_map_offset = offset; + if (size == VK_WHOLE_SIZE) + m_map_size = m_size - offset; + else + m_map_size = size; + + Assert(!m_map_pointer); + Assert(m_map_offset + m_map_size <= m_size); + + void* map_pointer; + VkResult res = vkMapMemory(g_vulkan_context->GetDevice(), m_memory, m_map_offset, m_map_size, 0, &map_pointer); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkMapMemory failed: "); + return false; + } + + m_map_pointer = reinterpret_cast(map_pointer); + return true; +} + +void StagingBuffer::Unmap() +{ + Assert(m_map_pointer); + + vkUnmapMemory(g_vulkan_context->GetDevice(), m_memory); + m_map_pointer = nullptr; + m_map_offset = 0; + m_map_size = 0; +} + +void StagingBuffer::FlushCPUCache(VkDeviceSize offset, VkDeviceSize size) +{ + Assert(offset >= m_map_offset); + if (m_coherent || !IsMapped()) + return; + + VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, nullptr, m_memory, offset - m_map_offset, size}; + vkFlushMappedMemoryRanges(g_vulkan_context->GetDevice(), 1, &range); +} + +void StagingBuffer::InvalidateGPUCache(VkCommandBuffer command_buffer, VkAccessFlagBits dest_access_flags, + VkPipelineStageFlagBits dest_pipeline_stage, VkDeviceSize offset, + VkDeviceSize size) +{ + if (m_coherent) + return; + + Assert((offset + size) <= m_size || (offset < m_size && size == VK_WHOLE_SIZE)); + Util::BufferMemoryBarrier(command_buffer, m_buffer, VK_ACCESS_HOST_WRITE_BIT, dest_access_flags, offset, size, + VK_PIPELINE_STAGE_HOST_BIT, dest_pipeline_stage); +} + +void StagingBuffer::PrepareForGPUWrite(VkCommandBuffer command_buffer, VkAccessFlagBits dst_access_flags, + VkPipelineStageFlagBits dst_pipeline_stage, VkDeviceSize offset, + VkDeviceSize size) +{ + if (m_coherent) + return; + + Assert((offset + size) <= m_size || (offset < m_size && size == VK_WHOLE_SIZE)); + Util::BufferMemoryBarrier(command_buffer, m_buffer, 0, dst_access_flags, offset, size, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, dst_pipeline_stage); +} + +void StagingBuffer::FlushGPUCache(VkCommandBuffer command_buffer, VkAccessFlagBits src_access_flags, + VkPipelineStageFlagBits src_pipeline_stage, VkDeviceSize offset, VkDeviceSize size) +{ + if (m_coherent) + return; + + Assert((offset + size) <= m_size || (offset < m_size && size == VK_WHOLE_SIZE)); + Util::BufferMemoryBarrier(command_buffer, m_buffer, src_access_flags, VK_ACCESS_HOST_READ_BIT, offset, size, + src_pipeline_stage, VK_PIPELINE_STAGE_HOST_BIT); +} + +void StagingBuffer::InvalidateCPUCache(VkDeviceSize offset, VkDeviceSize size) +{ + Assert(offset >= m_map_offset); + if (m_coherent || !IsMapped()) + return; + + VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, nullptr, m_memory, offset - m_map_offset, size}; + vkInvalidateMappedMemoryRanges(g_vulkan_context->GetDevice(), 1, &range); +} + +void StagingBuffer::Read(VkDeviceSize offset, void* data, size_t size, bool invalidate_caches) +{ + Assert((offset + size) <= m_size); + Assert(offset >= m_map_offset && size <= (m_map_size + (offset - m_map_offset))); + if (invalidate_caches) + InvalidateCPUCache(offset, size); + + memcpy(data, m_map_pointer + (offset - m_map_offset), size); +} + +void StagingBuffer::Write(VkDeviceSize offset, const void* data, size_t size, bool invalidate_caches) +{ + Assert((offset + size) <= m_size); + Assert(offset >= m_map_offset && size <= (m_map_size + (offset - m_map_offset))); + + memcpy(m_map_pointer + (offset - m_map_offset), data, size); + if (invalidate_caches) + FlushCPUCache(offset, size); +} + +bool StagingBuffer::AllocateBuffer(Type type, VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer* out_buffer, + VkDeviceMemory* out_memory, bool* out_coherent) +{ + VkBufferCreateInfo buffer_create_info = { + VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkBufferCreateFlags flags + size, // VkDeviceSize size + usage, // VkBufferUsageFlags usage + VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode + 0, // uint32_t queueFamilyIndexCount + nullptr // const uint32_t* pQueueFamilyIndices + }; + VkResult res = vkCreateBuffer(g_vulkan_context->GetDevice(), &buffer_create_info, nullptr, out_buffer); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateBuffer failed: "); + return false; + } + + VkMemoryRequirements requirements; + vkGetBufferMemoryRequirements(g_vulkan_context->GetDevice(), *out_buffer, &requirements); + + u32 type_index; + if (type == Type::Upload) + type_index = g_vulkan_context->GetUploadMemoryType(requirements.memoryTypeBits, out_coherent); + else + type_index = g_vulkan_context->GetReadbackMemoryType(requirements.memoryTypeBits, out_coherent); + + VkMemoryAllocateInfo memory_allocate_info = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + requirements.size, // VkDeviceSize allocationSize + type_index // uint32_t memoryTypeIndex + }; + res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_allocate_info, nullptr, out_memory); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: "); + vkDestroyBuffer(g_vulkan_context->GetDevice(), *out_buffer, nullptr); + return false; + } + + res = vkBindBufferMemory(g_vulkan_context->GetDevice(), *out_buffer, *out_memory, 0); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkBindBufferMemory failed: "); + vkDestroyBuffer(g_vulkan_context->GetDevice(), *out_buffer, nullptr); + vkFreeMemory(g_vulkan_context->GetDevice(), *out_memory, nullptr); + return false; + } + + return true; +} + +bool StagingBuffer::Create(Type type, VkDeviceSize size, VkBufferUsageFlags usage) +{ + if (!AllocateBuffer(type, size, usage, &m_buffer, &m_memory, &m_coherent)) + return false; + + m_type = type; + m_size = size; + return true; +} + +void StagingBuffer::Destroy(bool defer /* = true */) +{ + if (!IsValid()) + return; + + // Unmap before destroying + if (m_map_pointer) + Unmap(); + + if (defer) + g_vulkan_context->DeferBufferDestruction(m_buffer); + else + vkDestroyBuffer(g_vulkan_context->GetDevice(), m_buffer, nullptr); + + if (defer) + g_vulkan_context->DeferDeviceMemoryDestruction(m_memory); + else + vkFreeMemory(g_vulkan_context->GetDevice(), m_memory, nullptr); + + m_type = Type::Upload; + m_buffer = VK_NULL_HANDLE; + m_memory = VK_NULL_HANDLE; + m_size = 0; + m_coherent = false; + m_map_pointer = nullptr; + m_map_offset = 0; + m_map_size = 0; +} + +} // namespace Vulkan diff --git a/jni/common/vulkan/staging_buffer.h b/jni/common/vulkan/staging_buffer.h new file mode 100644 index 0000000..421272e --- /dev/null +++ b/jni/common/vulkan/staging_buffer.h @@ -0,0 +1,91 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#pragma once +#include "../types.h" +#include "vulkan_loader.h" +#include + +namespace Vulkan { + +class StagingBuffer +{ +public: + enum class Type + { + Upload, + Readback, + Mutable + }; + + StagingBuffer(); + StagingBuffer(StagingBuffer&& move); + StagingBuffer(const StagingBuffer&) = delete; + virtual ~StagingBuffer(); + + StagingBuffer& operator=(StagingBuffer&& move); + StagingBuffer& operator=(const StagingBuffer&) = delete; + + ALWAYS_INLINE Type GetType() const { return m_type; } + ALWAYS_INLINE VkDeviceSize GetSize() const { return m_size; } + ALWAYS_INLINE VkBuffer GetBuffer() const { return m_buffer; } + ALWAYS_INLINE bool IsMapped() const { return m_map_pointer != nullptr; } + ALWAYS_INLINE const char* GetMapPointer() const { return m_map_pointer; } + ALWAYS_INLINE char* GetMapPointer() { return m_map_pointer; } + ALWAYS_INLINE VkDeviceSize GetMapOffset() const { return m_map_offset; } + ALWAYS_INLINE VkDeviceSize GetMapSize() const { return m_map_size; } + ALWAYS_INLINE bool IsValid() const { return (m_buffer != VK_NULL_HANDLE); } + ALWAYS_INLINE bool IsCoherent() const { return m_coherent; } + + bool Map(VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE); + void Unmap(); + + // Upload part 1: Prepare from device read from the CPU side + void FlushCPUCache(VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE); + + // Upload part 2: Prepare for device read from the GPU side + // Implicit when submitting the command buffer, so rarely needed. + void InvalidateGPUCache(VkCommandBuffer command_buffer, VkAccessFlagBits dst_access_flags, + VkPipelineStageFlagBits dst_pipeline_stage, VkDeviceSize offset = 0, + VkDeviceSize size = VK_WHOLE_SIZE); + + // Readback part 0: Prepare for GPU usage (if necessary) + void PrepareForGPUWrite(VkCommandBuffer command_buffer, VkAccessFlagBits dst_access_flags, + VkPipelineStageFlagBits dst_pipeline_stage, VkDeviceSize offset = 0, + VkDeviceSize size = VK_WHOLE_SIZE); + + // Readback part 1: Prepare for host readback from the GPU side + void FlushGPUCache(VkCommandBuffer command_buffer, VkAccessFlagBits src_access_flags, + VkPipelineStageFlagBits src_pipeline_stage, VkDeviceSize offset = 0, + VkDeviceSize size = VK_WHOLE_SIZE); + + // Readback part 2: Prepare for host readback from the CPU side + void InvalidateCPUCache(VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE); + + // offset is from the start of the buffer, not from the map offset + void Read(VkDeviceSize offset, void* data, size_t size, bool invalidate_caches = true); + void Write(VkDeviceSize offset, const void* data, size_t size, bool invalidate_caches = true); + + // Creates the optimal format of image copy. + bool Create(Type type, VkDeviceSize size, VkBufferUsageFlags usage); + + void Destroy(bool defer = true); + + // Allocates the resources needed to create a staging buffer. + static bool AllocateBuffer(Type type, VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer* out_buffer, + VkDeviceMemory* out_memory, bool* out_coherent); + +protected: + Type m_type = Type::Upload; + VkBuffer m_buffer = VK_NULL_HANDLE; + VkDeviceMemory m_memory = VK_NULL_HANDLE; + VkDeviceSize m_size = 0; + bool m_coherent = false; + + char* m_map_pointer = nullptr; + VkDeviceSize m_map_offset = 0; + VkDeviceSize m_map_size = 0; +}; +} // namespace Vulkan diff --git a/jni/common/vulkan/staging_texture.cpp b/jni/common/vulkan/staging_texture.cpp new file mode 100644 index 0000000..43c7b90 --- /dev/null +++ b/jni/common/vulkan/staging_texture.cpp @@ -0,0 +1,281 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#include "staging_texture.h" +#include "../assert.h" +#include "context.h" +#include "util.h" + +namespace Vulkan { + +StagingTexture::StagingTexture() = default; + +StagingTexture::StagingTexture(StagingTexture&& move) + : m_staging_buffer(std::move(move.m_staging_buffer)), m_flush_fence_counter(move.m_flush_fence_counter), + m_width(move.m_width), m_height(move.m_height), m_texel_size(move.m_texel_size), m_map_stride(move.m_map_stride) +{ + move.m_flush_fence_counter = 0; + move.m_width = 0; + move.m_height = 0; + move.m_texel_size = 0; + move.m_map_stride = 0; +} + +StagingTexture& StagingTexture::operator=(StagingTexture&& move) +{ + if (IsValid()) + Destroy(true); + + std::swap(m_staging_buffer, move.m_staging_buffer); + std::swap(m_flush_fence_counter, move.m_flush_fence_counter); + std::swap(m_width, move.m_width); + std::swap(m_height, move.m_height); + std::swap(m_texel_size, move.m_texel_size); + std::swap(m_map_stride, move.m_map_stride); + return *this; +} + +StagingTexture::~StagingTexture() +{ + if (IsValid()) + Destroy(true); +} + +bool StagingTexture::Create(StagingBuffer::Type type, VkFormat format, u32 width, u32 height) +{ + const u32 texel_size = Util::GetTexelSize(format); + const u32 map_stride = texel_size * width; + const u32 buffer_size = map_stride * height; + + VkBufferUsageFlags usage_flags; + switch (type) + { + case StagingBuffer::Type::Readback: + usage_flags = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + break; + case StagingBuffer::Type::Upload: + usage_flags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + break; + case StagingBuffer::Type::Mutable: + default: + usage_flags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + break; + } + + StagingBuffer new_buffer; + if (!new_buffer.Create(type, buffer_size, usage_flags) || !new_buffer.Map()) + return false; + + if (IsValid()) + Destroy(true); + + m_staging_buffer = std::move(new_buffer); + m_width = width; + m_height = height; + m_texel_size = texel_size; + m_map_stride = map_stride; + return true; +} + +void StagingTexture::Destroy(bool defer /* = true */) +{ + if (!IsValid()) + return; + + m_staging_buffer.Destroy(defer); + m_flush_fence_counter = 0; + m_width = 0; + m_height = 0; + m_texel_size = 0; + m_map_stride = 0; +} + +void StagingTexture::CopyFromTexture(VkCommandBuffer command_buffer, Texture& src_texture, u32 src_x, u32 src_y, + u32 src_layer, u32 src_level, u32 dst_x, u32 dst_y, u32 width, u32 height) +{ + Assert(m_staging_buffer.GetType() == StagingBuffer::Type::Readback || + m_staging_buffer.GetType() == StagingBuffer::Type::Mutable); + Assert((src_x + width) <= src_texture.GetWidth() && (src_y + height) <= src_texture.GetHeight()); + Assert((dst_x + width) <= m_width && (dst_y + height) <= m_height); + + VkImageLayout old_layout = src_texture.GetLayout(); + src_texture.TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + // Issue the image->buffer copy, but delay it for now. + VkBufferImageCopy image_copy = {}; + const VkImageAspectFlags aspect = + Util ::IsDepthFormat(src_texture.GetFormat()) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; + image_copy.bufferOffset = static_cast(dst_y * m_map_stride + dst_x * m_texel_size); + image_copy.bufferRowLength = m_width; + image_copy.bufferImageHeight = 0; + image_copy.imageSubresource = {aspect, src_level, src_layer, 1}; + image_copy.imageOffset = {static_cast(src_x), static_cast(src_y), 0}; + image_copy.imageExtent = {width, height, 1u}; + vkCmdCopyImageToBuffer(command_buffer, src_texture.GetImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + m_staging_buffer.GetBuffer(), 1, &image_copy); + + // Restore old source texture layout. + src_texture.TransitionToLayout(command_buffer, old_layout); +} + +void StagingTexture::CopyFromTexture(Texture& src_texture, u32 src_x, u32 src_y, u32 src_layer, u32 src_level, + u32 dst_x, u32 dst_y, u32 width, u32 height) +{ + CopyFromTexture(g_vulkan_context->GetCurrentCommandBuffer(), src_texture, src_x, src_y, src_layer, src_level, dst_x, + dst_y, width, height); + + m_needs_flush = true; + m_flush_fence_counter = g_vulkan_context->GetCurrentFenceCounter(); +} + +void StagingTexture::CopyToTexture(VkCommandBuffer command_buffer, u32 src_x, u32 src_y, Texture& dst_texture, + u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, u32 width, u32 height) +{ + Assert(m_staging_buffer.GetType() == StagingBuffer::Type::Upload || + m_staging_buffer.GetType() == StagingBuffer::Type::Mutable); + Assert((dst_x + width) <= dst_texture.GetWidth() && (dst_y + height) <= dst_texture.GetHeight()); + Assert((src_x + width) <= m_width && (src_y + height) <= m_height); + + // Flush caches before copying. + m_staging_buffer.FlushCPUCache(); + + VkImageLayout old_layout = dst_texture.GetLayout(); + dst_texture.TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + // Issue the image->buffer copy, but delay it for now. + VkBufferImageCopy image_copy = {}; + image_copy.bufferOffset = static_cast(src_y * m_map_stride + src_x * m_texel_size); + image_copy.bufferRowLength = m_width; + image_copy.bufferImageHeight = 0; + image_copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, dst_level, dst_layer, 1}; + image_copy.imageOffset = {static_cast(dst_x), static_cast(dst_y), 0}; + image_copy.imageExtent = {width, height, 1u}; + vkCmdCopyBufferToImage(command_buffer, m_staging_buffer.GetBuffer(), dst_texture.GetImage(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); + + // Restore old source texture layout. + dst_texture.TransitionToLayout(command_buffer, old_layout); +} + +void StagingTexture::CopyToTexture(u32 src_x, u32 src_y, Texture& dst_texture, u32 dst_x, u32 dst_y, u32 dst_layer, + u32 dst_level, u32 width, u32 height) +{ + CopyToTexture(g_vulkan_context->GetCurrentCommandBuffer(), src_x, src_y, dst_texture, dst_x, dst_y, dst_layer, + dst_level, width, height); + + m_needs_flush = true; + m_flush_fence_counter = g_vulkan_context->GetCurrentFenceCounter(); +} + +void StagingTexture::Flush() +{ + if (!m_needs_flush) + return; + + // Is this copy in the current command buffer? + if (g_vulkan_context->GetCurrentFenceCounter() == m_flush_fence_counter) + { + // Execute the command buffer and wait for it to finish. + g_vulkan_context->ExecuteCommandBuffer(true); + } + else + { + // Wait for the GPU to finish with it. + g_vulkan_context->WaitForFenceCounter(m_flush_fence_counter); + } + + // For readback textures, invalidate the CPU cache as there is new data there. + if (m_staging_buffer.GetType() == StagingBuffer::Type::Readback || + m_staging_buffer.GetType() == StagingBuffer::Type::Mutable) + { + m_staging_buffer.InvalidateCPUCache(); + } + + m_needs_flush = false; +} + +void StagingTexture::ReadTexels(u32 src_x, u32 src_y, u32 width, u32 height, void* out_ptr, u32 out_stride) +{ + Assert(m_staging_buffer.GetType() != StagingBuffer::Type::Upload); + Assert((src_x + width) <= m_width && (src_y + height) <= m_height); + PrepareForAccess(); + + // Offset pointer to point to start of region being copied out. + const char* current_ptr = m_staging_buffer.GetMapPointer(); + current_ptr += src_y * m_map_stride; + current_ptr += src_x * m_texel_size; + + // Optimal path: same dimensions, same stride. + if (src_x == 0 && width == m_width && m_map_stride == out_stride) + { + std::memcpy(out_ptr, current_ptr, m_map_stride * height); + return; + } + + size_t copy_size = std::min(width * m_texel_size, m_map_stride); + char* dst_ptr = reinterpret_cast(out_ptr); + for (u32 row = 0; row < height; row++) + { + std::memcpy(dst_ptr, current_ptr, copy_size); + current_ptr += m_map_stride; + dst_ptr += out_stride; + } +} + +void StagingTexture::ReadTexel(u32 x, u32 y, void* out_ptr) +{ + Assert(m_staging_buffer.GetType() != StagingBuffer::Type::Upload); + Assert(x < m_width && y < m_height); + PrepareForAccess(); + + const char* src_ptr = GetMappedPointer() + y * GetMappedStride() + x * m_texel_size; + std::memcpy(out_ptr, src_ptr, m_texel_size); +} + +void StagingTexture::WriteTexels(u32 dst_x, u32 dst_y, u32 width, u32 height, const void* in_ptr, u32 in_stride) +{ + Assert(m_staging_buffer.GetType() != StagingBuffer::Type::Readback); + Assert((dst_x + width) <= m_width && (dst_y + height) <= m_height); + PrepareForAccess(); + + // Offset pointer to point to start of region being copied to. + char* current_ptr = GetMappedPointer(); + current_ptr += dst_y * m_map_stride; + current_ptr += dst_x * m_texel_size; + + // Optimal path: same dimensions, same stride. + if (dst_x == 0 && width == m_width && m_map_stride == in_stride) + { + std::memcpy(current_ptr, in_ptr, m_map_stride * height); + return; + } + + size_t copy_size = std::min(width * m_texel_size, m_map_stride); + const char* src_ptr = reinterpret_cast(in_ptr); + for (u32 row = 0; row < height; row++) + { + std::memcpy(current_ptr, src_ptr, copy_size); + current_ptr += m_map_stride; + src_ptr += in_stride; + } +} + +void StagingTexture::WriteTexel(u32 x, u32 y, const void* in_ptr) +{ + Assert(x < m_width && y < m_height); + PrepareForAccess(); + + char* dest_ptr = GetMappedPointer() + y * m_map_stride + x * m_texel_size; + std::memcpy(dest_ptr, in_ptr, m_texel_size); +} + +void StagingTexture::PrepareForAccess() +{ + Assert(IsMapped()); + if (m_needs_flush) + Flush(); +} + +} // namespace Vulkan \ No newline at end of file diff --git a/jni/common/vulkan/staging_texture.h b/jni/common/vulkan/staging_texture.h new file mode 100644 index 0000000..f2a7b69 --- /dev/null +++ b/jni/common/vulkan/staging_texture.h @@ -0,0 +1,77 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#pragma once +#include "staging_buffer.h" +#include "texture.h" + +namespace Vulkan { + +class StagingTexture final +{ +public: + StagingTexture(); + StagingTexture(StagingTexture&& move); + StagingTexture(const StagingTexture&) = delete; + ~StagingTexture(); + + StagingTexture& operator=(StagingTexture&& move); + StagingTexture& operator=(const StagingTexture&) = delete; + + ALWAYS_INLINE bool IsValid() const { return m_staging_buffer.IsValid(); } + ALWAYS_INLINE bool IsMapped() const { return m_staging_buffer.IsMapped(); } + ALWAYS_INLINE const char* GetMappedPointer() const { return m_staging_buffer.GetMapPointer(); } + ALWAYS_INLINE char* GetMappedPointer() { return m_staging_buffer.GetMapPointer(); } + ALWAYS_INLINE u32 GetMappedStride() const { return m_map_stride; } + ALWAYS_INLINE u32 GetWidth() const { return m_width; } + ALWAYS_INLINE u32 GetHeight() const { return m_height; } + + bool Create(StagingBuffer::Type type, VkFormat format, u32 width, u32 height); + void Destroy(bool defer = true); + + // Copies from the GPU texture object to the staging texture, which can be mapped/read by the CPU. + // Both src_rect and dst_rect must be with within the bounds of the the specified textures. + void CopyFromTexture(VkCommandBuffer command_buffer, Texture& src_texture, u32 src_x, u32 src_y, u32 src_layer, + u32 src_level, u32 dst_x, u32 dst_y, u32 width, u32 height); + void CopyFromTexture(Texture& src_texture, u32 src_x, u32 src_y, u32 src_layer, u32 src_level, u32 dst_x, u32 dst_y, + u32 width, u32 height); + + // Wrapper for copying a whole layer of a texture to a readback texture. + // Assumes that the level of src texture and this texture have the same dimensions. + void CopyToTexture(VkCommandBuffer command_buffer, u32 src_x, u32 src_y, Texture& dst_texture, u32 dst_x, u32 dst_y, + u32 dst_layer, u32 dst_level, u32 width, u32 height); + void CopyToTexture(u32 src_x, u32 src_y, Texture& dst_texture, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, + u32 width, u32 height); + + // Flushes pending writes from the CPU to the GPU, and reads from the GPU to the CPU. + // This may cause a command buffer flush depending on if one has occurred between the last + // call to CopyFromTexture()/CopyToTexture() and the Flush() call. + void Flush(); + + // Reads the specified rectangle from the staging texture to out_ptr, with the specified stride + // (length in bytes of each row). CopyFromTexture must be called first. The contents of any + // texels outside of the rectangle used for CopyFromTexture is undefined. + void ReadTexels(u32 src_x, u32 src_y, u32 width, u32 height, void* out_ptr, u32 out_stride); + void ReadTexel(u32 x, u32 y, void* out_ptr); + + // Copies the texels from in_ptr to the staging texture, which can be read by the GPU, with the + // specified stride (length in bytes of each row). After updating the staging texture with all + // changes, call CopyToTexture() to update the GPU copy. + void WriteTexels(u32 dst_x, u32 dst_y, u32 width, u32 height, const void* in_ptr, u32 in_stride); + void WriteTexel(u32 x, u32 y, const void* in_ptr); + +private: + void PrepareForAccess(); + + StagingBuffer m_staging_buffer; + u64 m_flush_fence_counter = 0; + u32 m_width = 0; + u32 m_height = 0; + u32 m_texel_size = 0; + u32 m_map_stride = 0; + bool m_needs_flush = false; +}; + +} // namespace Vulkan \ No newline at end of file diff --git a/jni/common/vulkan/stream_buffer.cpp b/jni/common/vulkan/stream_buffer.cpp new file mode 100644 index 0000000..2340210 --- /dev/null +++ b/jni/common/vulkan/stream_buffer.cpp @@ -0,0 +1,363 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#include "stream_buffer.h" +#include "../align.h" +#include "../assert.h" +#include "../log.h" +#include "context.h" +#include "util.h" +Log_SetChannel(Vulkan::StreamBuffer); + +namespace Vulkan { + +StreamBuffer::StreamBuffer() = default; + +StreamBuffer::StreamBuffer(StreamBuffer&& move) + : m_usage(move.m_usage), m_size(move.m_size), m_current_offset(move.m_current_offset), + m_current_space(move.m_current_space), m_current_gpu_position(move.m_current_gpu_position), m_buffer(move.m_buffer), + m_memory(move.m_memory), m_host_pointer(move.m_host_pointer), m_tracked_fences(std::move(move.m_tracked_fences)), + m_coherent_mapping(move.m_coherent_mapping) +{ +} + +StreamBuffer::~StreamBuffer() +{ + if (IsValid()) + Destroy(true); +} + +StreamBuffer& StreamBuffer::operator=(StreamBuffer&& move) +{ + if (IsValid()) + Destroy(true); + + std::swap(m_usage, move.m_usage); + std::swap(m_size, move.m_size); + std::swap(m_current_offset, move.m_current_offset); + std::swap(m_current_space, move.m_current_space); + std::swap(m_current_gpu_position, move.m_current_gpu_position); + std::swap(m_buffer, move.m_buffer); + std::swap(m_memory, move.m_memory); + std::swap(m_host_pointer, move.m_host_pointer); + std::swap(m_tracked_fences, move.m_tracked_fences); + std::swap(m_coherent_mapping, move.m_coherent_mapping); + + return *this; +} + +bool StreamBuffer::Create(VkBufferUsageFlags usage, u32 size) +{ + // Create the buffer descriptor + VkBufferCreateInfo buffer_create_info = { + VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkBufferCreateFlags flags + static_cast(size), // VkDeviceSize size + usage, // VkBufferUsageFlags usage + VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode + 0, // uint32_t queueFamilyIndexCount + nullptr // const uint32_t* pQueueFamilyIndices + }; + + VkBuffer buffer = VK_NULL_HANDLE; + VkResult res = vkCreateBuffer(g_vulkan_context->GetDevice(), &buffer_create_info, nullptr, &buffer); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateBuffer failed: "); + return false; + } + + // Get memory requirements (types etc) for this buffer + VkMemoryRequirements memory_requirements; + vkGetBufferMemoryRequirements(g_vulkan_context->GetDevice(), buffer, &memory_requirements); + + // Aim for a coherent mapping if possible. + u32 memory_type_index = + g_vulkan_context->GetUploadMemoryType(memory_requirements.memoryTypeBits, &m_coherent_mapping); + + // Allocate memory for backing this buffer + VkMemoryAllocateInfo memory_allocate_info = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + memory_requirements.size, // VkDeviceSize allocationSize + memory_type_index // uint32_t memoryTypeIndex + }; + VkDeviceMemory memory = VK_NULL_HANDLE; + res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_allocate_info, nullptr, &memory); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: "); + vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); + return false; + } + + // Bind memory to buffer + res = vkBindBufferMemory(g_vulkan_context->GetDevice(), buffer, memory, 0); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkBindBufferMemory failed: "); + vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); + vkFreeMemory(g_vulkan_context->GetDevice(), memory, nullptr); + return false; + } + + // Map this buffer into user-space + void* mapped_ptr = nullptr; + res = vkMapMemory(g_vulkan_context->GetDevice(), memory, 0, size, 0, &mapped_ptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkMapMemory failed: "); + vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); + vkFreeMemory(g_vulkan_context->GetDevice(), memory, nullptr); + return false; + } + + // Unmap current host pointer (if there was a previous buffer) + if (m_host_pointer) + vkUnmapMemory(g_vulkan_context->GetDevice(), m_memory); + + if (IsValid()) + Destroy(true); + + // Replace with the new buffer + m_usage = usage; + m_size = size; + m_buffer = buffer; + m_memory = memory; + m_host_pointer = reinterpret_cast(mapped_ptr); + m_current_offset = 0; + m_current_gpu_position = 0; + m_tracked_fences.clear(); + return true; +} + +void StreamBuffer::Destroy(bool defer) +{ + if (m_host_pointer) + { + vkUnmapMemory(g_vulkan_context->GetDevice(), m_memory); + m_host_pointer = nullptr; + } + + if (m_buffer != VK_NULL_HANDLE) + { + if (defer) + g_vulkan_context->DeferBufferDestruction(m_buffer); + else + vkDestroyBuffer(g_vulkan_context->GetDevice(), m_buffer, nullptr); + m_buffer = VK_NULL_HANDLE; + } + if (m_memory != VK_NULL_HANDLE) + { + if (defer) + g_vulkan_context->DeferDeviceMemoryDestruction(m_memory); + else + vkFreeMemory(g_vulkan_context->GetDevice(), m_memory, nullptr); + m_memory = VK_NULL_HANDLE; + } +} + +bool StreamBuffer::ReserveMemory(u32 num_bytes, u32 alignment) +{ + const u32 required_bytes = num_bytes + alignment; + + // Check for sane allocations + if (required_bytes > m_size) + { + Log_ErrorPrintf("Attempting to allocate %u bytes from a %u byte stream buffer", static_cast(num_bytes), + static_cast(m_size)); + Panic("Stream buffer overflow"); + return false; + } + + // Is the GPU behind or up to date with our current offset? + UpdateCurrentFencePosition(); + if (m_current_offset >= m_current_gpu_position) + { + const u32 remaining_bytes = m_size - m_current_offset; + if (required_bytes <= remaining_bytes) + { + // Place at the current position, after the GPU position. + m_current_offset = Common::AlignUp(m_current_offset, alignment); + m_current_space = m_size - m_current_offset; + return true; + } + + // Check for space at the start of the buffer + // We use < here because we don't want to have the case of m_current_offset == + // m_current_gpu_position. That would mean the code above would assume the + // GPU has caught up to us, which it hasn't. + if (required_bytes < m_current_gpu_position) + { + // Reset offset to zero, since we're allocating behind the gpu now + m_current_offset = 0; + m_current_space = m_current_gpu_position; + return true; + } + } + + // Is the GPU ahead of our current offset? + if (m_current_offset < m_current_gpu_position) + { + // We have from m_current_offset..m_current_gpu_position space to use. + const u32 remaining_bytes = m_current_gpu_position - m_current_offset; + if (required_bytes < remaining_bytes) + { + // Place at the current position, since this is still behind the GPU. + m_current_offset = Common::AlignUp(m_current_offset, alignment); + m_current_space = m_current_gpu_position - m_current_offset; + return true; + } + } + + // Can we find a fence to wait on that will give us enough memory? + if (WaitForClearSpace(required_bytes)) + { + const u32 align_diff = Common::AlignUp(m_current_offset, alignment) - m_current_offset; + m_current_offset += align_diff; + m_current_space -= align_diff; + return true; + } + + // We tried everything we could, and still couldn't get anything. This means that too much space + // in the buffer is being used by the command buffer currently being recorded. Therefore, the + // only option is to execute it, and wait until it's done. + return false; +} + +void StreamBuffer::CommitMemory(u32 final_num_bytes) +{ + Assert((m_current_offset + final_num_bytes) <= m_size); + Assert(final_num_bytes <= m_current_space); + + // For non-coherent mappings, flush the memory range + if (!m_coherent_mapping) + { + VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, nullptr, m_memory, m_current_offset, + final_num_bytes}; + vkFlushMappedMemoryRanges(g_vulkan_context->GetDevice(), 1, &range); + } + + m_current_offset += final_num_bytes; + m_current_space -= final_num_bytes; +} + +void StreamBuffer::UpdateCurrentFencePosition() +{ + // Don't create a tracking entry if the GPU is caught up with the buffer. + if (m_current_offset == m_current_gpu_position) + return; + + // Has the offset changed since the last fence? + const u64 counter = g_vulkan_context->GetCurrentFenceCounter(); + if (!m_tracked_fences.empty() && m_tracked_fences.back().first == counter) + { + // Still haven't executed a command buffer, so just update the offset. + m_tracked_fences.back().second = m_current_offset; + return; + } + + // New buffer, so update the GPU position while we're at it. + UpdateGPUPosition(); + m_tracked_fences.emplace_back(counter, m_current_offset); +} + +void StreamBuffer::UpdateGPUPosition() +{ + auto start = m_tracked_fences.begin(); + auto end = start; + + const u64 completed_counter = g_vulkan_context->GetCompletedFenceCounter(); + while (end != m_tracked_fences.end() && completed_counter >= end->first) + { + m_current_gpu_position = end->second; + ++end; + } + + if (start != end) + m_tracked_fences.erase(start, end); +} + +bool StreamBuffer::WaitForClearSpace(u32 num_bytes) +{ + u32 new_offset = 0; + u32 new_space = 0; + u32 new_gpu_position = 0; + + auto iter = m_tracked_fences.begin(); + for (; iter != m_tracked_fences.end(); ++iter) + { + // Would this fence bring us in line with the GPU? + // This is the "last resort" case, where a command buffer execution has been forced + // after no additional data has been written to it, so we can assume that after the + // fence has been signaled the entire buffer is now consumed. + u32 gpu_position = iter->second; + if (m_current_offset == gpu_position) + { + new_offset = 0; + new_space = m_size; + new_gpu_position = 0; + break; + } + + // Assuming that we wait for this fence, are we allocating in front of the GPU? + if (m_current_offset > gpu_position) + { + // This would suggest the GPU has now followed us and wrapped around, so we have from + // m_current_position..m_size free, as well as and 0..gpu_position. + const u32 remaining_space_after_offset = m_size - m_current_offset; + if (remaining_space_after_offset >= num_bytes) + { + // Switch to allocating in front of the GPU, using the remainder of the buffer. + new_offset = m_current_offset; + new_space = m_size - m_current_offset; + new_gpu_position = gpu_position; + break; + } + + // We can wrap around to the start, behind the GPU, if there is enough space. + // We use > here because otherwise we'd end up lining up with the GPU, and then the + // allocator would assume that the GPU has consumed what we just wrote. + if (gpu_position > num_bytes) + { + new_offset = 0; + new_space = gpu_position; + new_gpu_position = gpu_position; + break; + } + } + else + { + // We're currently allocating behind the GPU. This would give us between the current + // offset and the GPU position worth of space to work with. Again, > because we can't + // align the GPU position with the buffer offset. + u32 available_space_inbetween = gpu_position - m_current_offset; + if (available_space_inbetween > num_bytes) + { + // Leave the offset as-is, but update the GPU position. + new_offset = m_current_offset; + new_space = gpu_position - m_current_offset; + new_gpu_position = gpu_position; + break; + } + } + } + + // Did any fences satisfy this condition? + // Has the command buffer been executed yet? If not, the caller should execute it. + if (iter == m_tracked_fences.end() || iter->first == g_vulkan_context->GetCurrentFenceCounter()) + return false; + + // Wait until this fence is signaled. This will fire the callback, updating the GPU position. + g_vulkan_context->WaitForFenceCounter(iter->first); + m_tracked_fences.erase(m_tracked_fences.begin(), m_current_offset == iter->second ? m_tracked_fences.end() : ++iter); + m_current_offset = new_offset; + m_current_space = new_space; + m_current_gpu_position = new_gpu_position; + return true; +} + +} // namespace Vulkan diff --git a/jni/common/vulkan/stream_buffer.h b/jni/common/vulkan/stream_buffer.h new file mode 100644 index 0000000..c1895a9 --- /dev/null +++ b/jni/common/vulkan/stream_buffer.h @@ -0,0 +1,66 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#pragma once + +#include "../types.h" +#include "vulkan_loader.h" +#include +#include + +namespace Vulkan { + +class StreamBuffer +{ +public: + StreamBuffer(); + StreamBuffer(StreamBuffer&& move); + StreamBuffer(const StreamBuffer&) = delete; + ~StreamBuffer(); + + StreamBuffer& operator=(StreamBuffer&& move); + StreamBuffer& operator=(const StreamBuffer&) = delete; + + ALWAYS_INLINE bool IsValid() const { return (m_buffer != VK_NULL_HANDLE); } + ALWAYS_INLINE VkBuffer GetBuffer() const { return m_buffer; } + ALWAYS_INLINE const VkBuffer* GetBufferPointer() const { return &m_buffer; } + ALWAYS_INLINE VkDeviceMemory GetDeviceMemory() const { return m_memory; } + ALWAYS_INLINE void* GetHostPointer() const { return m_host_pointer; } + ALWAYS_INLINE void* GetCurrentHostPointer() const { return m_host_pointer + m_current_offset; } + ALWAYS_INLINE u32 GetCurrentSize() const { return m_size; } + ALWAYS_INLINE u32 GetCurrentSpace() const { return m_current_space; } + ALWAYS_INLINE u32 GetCurrentOffset() const { return m_current_offset; } + + bool Create(VkBufferUsageFlags usage, u32 size); + void Destroy(bool defer); + + bool ReserveMemory(u32 num_bytes, u32 alignment); + void CommitMemory(u32 final_num_bytes); + +private: + bool AllocateBuffer(VkBufferUsageFlags usage, u32 size); + void UpdateCurrentFencePosition(); + void UpdateGPUPosition(); + + // Waits for as many fences as needed to allocate num_bytes bytes from the buffer. + bool WaitForClearSpace(u32 num_bytes); + + VkBufferUsageFlags m_usage = 0; + u32 m_size = 0; + u32 m_current_offset = 0; + u32 m_current_space = 0; + u32 m_current_gpu_position = 0; + + VkBuffer m_buffer = VK_NULL_HANDLE; + VkDeviceMemory m_memory = VK_NULL_HANDLE; + u8* m_host_pointer = nullptr; + + // List of fences and the corresponding positions in the buffer + std::deque> m_tracked_fences; + + bool m_coherent_mapping = false; +}; + +} // namespace Vulkan diff --git a/jni/common/vulkan/swap_chain.cpp b/jni/common/vulkan/swap_chain.cpp new file mode 100644 index 0000000..cb06a38 --- /dev/null +++ b/jni/common/vulkan/swap_chain.cpp @@ -0,0 +1,630 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#include "swap_chain.h" +#include "../assert.h" +#include "../log.h" +#include "context.h" +#include "util.h" +#include +#include +Log_SetChannel(Vulkan::SwapChain); + +#if defined(VK_USE_PLATFORM_XLIB_KHR) +#include +#endif + +#if defined(__APPLE__) +#include + +static bool CreateMetalLayer(WindowInfo& wi) +{ + id view = reinterpret_cast(wi.window_handle); + + Class clsCAMetalLayer = objc_getClass("CAMetalLayer"); + if (!clsCAMetalLayer) + { + Log_ErrorPrint("Failed to get CAMetalLayer class."); + return false; + } + + // [CAMetalLayer layer] + id layer = reinterpret_cast(objc_msgSend)(objc_getClass("CAMetalLayer"), sel_getUid("layer")); + if (!layer) + { + Log_ErrorPrint("Failed to create Metal layer."); + return false; + } + + // [view setWantsLayer:YES] + reinterpret_cast(objc_msgSend)(view, sel_getUid("setWantsLayer:"), YES); + + // [view setLayer:layer] + reinterpret_cast(objc_msgSend)(view, sel_getUid("setLayer:"), layer); + + // NSScreen* screen = [NSScreen mainScreen] + id screen = reinterpret_cast(objc_msgSend)(objc_getClass("NSScreen"), sel_getUid("mainScreen")); + + // CGFloat factor = [screen backingScaleFactor] + double factor = reinterpret_cast(objc_msgSend)(screen, sel_getUid("backingScaleFactor")); + + // layer.contentsScale = factor + reinterpret_cast(objc_msgSend)(layer, sel_getUid("setContentsScale:"), factor); + + // Store the layer pointer, that way MoltenVK doesn't call [NSView layer] outside the main thread. + wi.surface_handle = layer; + return true; +} + +static void DestroyMetalLayer(WindowInfo& wi) +{ + id view = reinterpret_cast(wi.window_handle); + id layer = reinterpret_cast(wi.surface_handle); + if (layer == nil) + return; + + reinterpret_cast(objc_msgSend)(view, sel_getUid("setLayer:"), nil); + reinterpret_cast(objc_msgSend)(view, sel_getUid("setWantsLayer:"), NO); + reinterpret_cast(objc_msgSend)(layer, sel_getUid("release")); + wi.surface_handle = nullptr; +} + +#endif + +namespace Vulkan { +SwapChain::SwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync) + : m_wi(wi), m_vsync_enabled(vsync), m_surface(surface) +{ +} + +SwapChain::~SwapChain() +{ + DestroySemaphores(); + DestroySwapChainImages(); + DestroySwapChain(); + DestroySurface(); +} + +VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, WindowInfo& wi) +{ +#if defined(VK_USE_PLATFORM_WIN32_KHR) + if (wi.type == WindowInfo::Type::Win32) + { + VkWin32SurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkWin32SurfaceCreateFlagsKHR flags + nullptr, // HINSTANCE hinstance + reinterpret_cast(wi.window_handle) // HWND hwnd + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#endif + +#if defined(VK_USE_PLATFORM_XLIB_KHR) + if (wi.type == WindowInfo::Type::X11) + { + VkXlibSurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkXlibSurfaceCreateFlagsKHR flags + static_cast(wi.display_connection), // Display* dpy + reinterpret_cast(wi.window_handle) // Window window + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#endif + +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (wi.type == WindowInfo::Type::Wayland) + { + VkWaylandSurfaceCreateInfoKHR surface_create_info = {VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0, + static_cast(wi.display_connection), + static_cast(wi.window_handle)}; + + VkSurfaceKHR surface; + VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateWaylandSurfaceEXT failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#endif + +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + if (wi.type == WindowInfo::Type::Android) + { + VkAndroidSurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkAndroidSurfaceCreateFlagsKHR flags + reinterpret_cast(wi.window_handle) // ANativeWindow* window + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#endif + +#if defined(VK_USE_PLATFORM_METAL_EXT) + if (wi.type == WindowInfo::Type::MacOS) + { + if (!wi.surface_handle && !CreateMetalLayer(wi)) + return VK_NULL_HANDLE; + + VkMetalSurfaceCreateInfoEXT surface_create_info = {VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0, + static_cast(wi.surface_handle)}; + + VkSurfaceKHR surface; + VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#elif defined(VK_USE_PLATFORM_MACOS_MVK) + if (wi.type == WindowInfo::Type::MacOS) + { + VkMacOSSurfaceCreateInfoMVK surface_create_info = {VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, nullptr, 0, + wi.window_handle}; + + VkSurfaceKHR surface; + VkResult res = vkCreateMacOSSurfaceMVK(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateMacOSSurfaceMVK failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#endif + + return VK_NULL_HANDLE; +} + +void SwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo& wi, VkSurfaceKHR surface) +{ + vkDestroySurfaceKHR(g_vulkan_context->GetVulkanInstance(), surface, nullptr); + +#if defined(__APPLE__) + if (wi.type == WindowInfo::Type::MacOS && wi.surface_handle) + DestroyMetalLayer(wi); +#endif +} + +std::unique_ptr SwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync) +{ + std::unique_ptr swap_chain = std::make_unique(wi, surface, vsync); + if (!swap_chain->CreateSwapChain() || !swap_chain->SetupSwapChainImages() || !swap_chain->CreateSemaphores()) + return nullptr; + + return swap_chain; +} + +bool SwapChain::SelectSurfaceFormat() +{ + u32 format_count; + VkResult res = + vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &format_count, nullptr); + if (res != VK_SUCCESS || format_count == 0) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); + return false; + } + + std::vector surface_formats(format_count); + res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &format_count, + surface_formats.data()); + Assert(res == VK_SUCCESS); + + // If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA + if (surface_formats[0].format == VK_FORMAT_UNDEFINED) + { + m_surface_format.format = VK_FORMAT_R8G8B8A8_UNORM; + m_surface_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + return true; + } + + // Try to find a suitable format. + for (const VkSurfaceFormatKHR& surface_format : surface_formats) + { + // Some drivers seem to return a SRGB format here (Intel Mesa). + // This results in gamma correction when presenting to the screen, which we don't want. + // Use a linear format instead, if this is the case. + m_surface_format.format = Util::GetLinearFormat(surface_format.format); + m_surface_format.colorSpace = surface_format.colorSpace; + return true; + } + + Panic("Failed to find a suitable format for swap chain buffers."); + return false; +} + +bool SwapChain::SelectPresentMode() +{ + VkResult res; + u32 mode_count; + res = + vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &mode_count, nullptr); + if (res != VK_SUCCESS || mode_count == 0) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); + return false; + } + + std::vector present_modes(mode_count); + res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &mode_count, + present_modes.data()); + Assert(res == VK_SUCCESS); + + // Checks if a particular mode is supported, if it is, returns that mode. + auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) { + auto it = std::find_if(present_modes.begin(), present_modes.end(), + [check_mode](VkPresentModeKHR mode) { return check_mode == mode; }); + return it != present_modes.end(); + }; + + // If vsync is enabled, use VK_PRESENT_MODE_FIFO_KHR. + // This check should not fail with conforming drivers, as the FIFO present mode is mandated by + // the specification (VK_KHR_swapchain). In case it isn't though, fall through to any other mode. + if (m_vsync_enabled && CheckForMode(VK_PRESENT_MODE_FIFO_KHR)) + { + m_present_mode = VK_PRESENT_MODE_FIFO_KHR; + return true; + } + + // Prefer screen-tearing, if possible, for lowest latency. + if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR)) + { + m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; + return true; + } + + // Use optimized-vsync above vsync. + if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) + { + m_present_mode = VK_PRESENT_MODE_MAILBOX_KHR; + return true; + } + + // Fall back to whatever is available. + m_present_mode = present_modes[0]; + return true; +} + +bool SwapChain::CreateSwapChain() +{ + // Look up surface properties to determine image count and dimensions + VkSurfaceCapabilitiesKHR surface_capabilities; + VkResult res = + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, &surface_capabilities); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: "); + return false; + } + + // Select swap chain format and present mode + if (!SelectSurfaceFormat() || !SelectPresentMode()) + return false; + + // Select number of images in swap chain, we prefer one buffer in the background to work on + u32 image_count = surface_capabilities.minImageCount + 1u; + + // maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers. + if (surface_capabilities.maxImageCount > 0) + image_count = std::min(image_count, surface_capabilities.maxImageCount); + + // Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here + // determines window size? + VkExtent2D size = surface_capabilities.currentExtent; +#ifndef ANDROID + if (size.width == UINT32_MAX) +#endif + { + size.width = m_wi.surface_width; + size.height = m_wi.surface_height; + } + size.width = + std::clamp(size.width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width); + size.height = + std::clamp(size.height, surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height); + + // Prefer identity transform if possible + VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + if (!(surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)) + transform = surface_capabilities.currentTransform; + + // Select swap chain flags, we only need a colour attachment + VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + if (!(surface_capabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) + { + Log_ErrorPrintf("Vulkan: Swap chain does not support usage as color attachment"); + return false; + } + + // Store the old/current swap chain when recreating for resize + VkSwapchainKHR old_swap_chain = m_swap_chain; + m_swap_chain = VK_NULL_HANDLE; + + // Now we can actually create the swap chain + VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + nullptr, + 0, + m_surface, + image_count, + m_surface_format.format, + m_surface_format.colorSpace, + size, + 1u, + image_usage, + VK_SHARING_MODE_EXCLUSIVE, + 0, + nullptr, + transform, + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + m_present_mode, + VK_TRUE, + old_swap_chain}; + std::array indices = {{ + g_vulkan_context->GetGraphicsQueueFamilyIndex(), + g_vulkan_context->GetPresentQueueFamilyIndex(), + }}; + if (g_vulkan_context->GetGraphicsQueueFamilyIndex() != g_vulkan_context->GetPresentQueueFamilyIndex()) + { + swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + swap_chain_info.queueFamilyIndexCount = 2; + swap_chain_info.pQueueFamilyIndices = indices.data(); + } + + if (m_swap_chain == VK_NULL_HANDLE) + { + res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, &m_swap_chain); + } + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: "); + return false; + } + + // Now destroy the old swap chain, since it's been recreated. + // We can do this immediately since all work should have been completed before calling resize. + if (old_swap_chain != VK_NULL_HANDLE) + vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), old_swap_chain, nullptr); + + m_width = size.width; + m_height = size.height; + return true; +} + +bool SwapChain::SetupSwapChainImages() +{ + Assert(m_images.empty()); + + u32 image_count; + VkResult res = vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, nullptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: "); + return false; + } + + std::vector images(image_count); + res = vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, images.data()); + Assert(res == VK_SUCCESS); + + m_load_render_pass = g_vulkan_context->GetRenderPass(m_surface_format.format, VK_FORMAT_UNDEFINED, + VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_LOAD); + m_clear_render_pass = g_vulkan_context->GetRenderPass(m_surface_format.format, VK_FORMAT_UNDEFINED, + VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_CLEAR); + if (m_load_render_pass == VK_NULL_HANDLE || m_clear_render_pass == VK_NULL_HANDLE) + { + Panic("Failed to get swap chain render passes."); + return false; + } + + m_images.reserve(image_count); + for (u32 i = 0; i < image_count; i++) + { + SwapChainImage image; + image.image = images[i]; + + // Create texture object, which creates a view of the backbuffer + if (!image.texture.Adopt(image.image, VK_IMAGE_VIEW_TYPE_2D, m_width, m_height, 1, 1, m_surface_format.format, + VK_SAMPLE_COUNT_1_BIT)) + { + return false; + } + + image.framebuffer = image.texture.CreateFramebuffer(m_load_render_pass); + if (image.framebuffer == VK_NULL_HANDLE) + return false; + + m_images.emplace_back(std::move(image)); + } + + return true; +} + +void SwapChain::DestroySwapChainImages() +{ + for (auto& it : m_images) + { + // Images themselves are cleaned up by the swap chain object + vkDestroyFramebuffer(g_vulkan_context->GetDevice(), it.framebuffer, nullptr); + } + m_images.clear(); +} + +void SwapChain::DestroySwapChain() +{ + if (m_swap_chain == VK_NULL_HANDLE) + return; + + vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), m_swap_chain, nullptr); + m_swap_chain = VK_NULL_HANDLE; +} + +VkResult SwapChain::AcquireNextImage() +{ + return vkAcquireNextImageKHR(g_vulkan_context->GetDevice(), m_swap_chain, UINT64_MAX, m_image_available_semaphore, + VK_NULL_HANDLE, &m_current_image); +} + +bool SwapChain::ResizeSwapChain(u32 new_width /* = 0 */, u32 new_height /* = 0 */) +{ + DestroySwapChainImages(); + + if (new_width != 0 && new_height != 0) + { + m_wi.surface_width = new_width; + m_wi.surface_height = new_height; + } + + if (!CreateSwapChain() || !SetupSwapChainImages()) + { + Panic("Failed to re-configure swap chain images, this is fatal (for now)"); + return false; + } + + return true; +} + +bool SwapChain::RecreateSwapChain() +{ + DestroySwapChainImages(); + DestroySwapChain(); + if (!CreateSwapChain() || !SetupSwapChainImages()) + { + Panic("Failed to re-configure swap chain images, this is fatal (for now)"); + return false; + } + + return true; +} + +bool SwapChain::SetVSync(bool enabled) +{ + if (m_vsync_enabled == enabled) + return true; + + // Recreate the swap chain with the new present mode. + m_vsync_enabled = enabled; + return RecreateSwapChain(); +} + +bool SwapChain::RecreateSurface(const WindowInfo& new_wi) +{ + // Destroy the old swap chain, images, and surface. + DestroySwapChainImages(); + DestroySwapChain(); + DestroySurface(); + + // Re-create the surface with the new native handle + m_wi = new_wi; + m_surface = CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), m_wi); + if (m_surface == VK_NULL_HANDLE) + return false; + + // The validation layers get angry at us if we don't call this before creating the swapchain. + VkBool32 present_supported = VK_TRUE; + VkResult res = + vkGetPhysicalDeviceSurfaceSupportKHR(g_vulkan_context->GetPhysicalDevice(), + g_vulkan_context->GetPresentQueueFamilyIndex(), m_surface, &present_supported); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: "); + return false; + } + if (!present_supported) + { + Panic("Recreated surface does not support presenting."); + return false; + } + + // Finally re-create the swap chain + if (!CreateSwapChain() || !SetupSwapChainImages()) + return false; + + return true; +} + +void SwapChain::DestroySurface() +{ + DestroyVulkanSurface(g_vulkan_context->GetVulkanInstance(), m_wi, m_surface); + m_surface = VK_NULL_HANDLE; +} + +bool SwapChain::CreateSemaphores() +{ + // Create two semaphores, one that is triggered when the swapchain buffer is ready, another after + // submit and before present + VkSemaphoreCreateInfo semaphore_info = { + VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0 // VkSemaphoreCreateFlags flags + }; + + VkResult res; + if ((res = vkCreateSemaphore(g_vulkan_context->GetDevice(), &semaphore_info, nullptr, + &m_image_available_semaphore)) != VK_SUCCESS || + (res = vkCreateSemaphore(g_vulkan_context->GetDevice(), &semaphore_info, nullptr, + &m_rendering_finished_semaphore)) != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: "); + return false; + } + + return true; +} + +void SwapChain::DestroySemaphores() +{ + if (m_image_available_semaphore != VK_NULL_HANDLE) + { + vkDestroySemaphore(g_vulkan_context->GetDevice(), m_image_available_semaphore, nullptr); + m_image_available_semaphore = VK_NULL_HANDLE; + } + + if (m_rendering_finished_semaphore != VK_NULL_HANDLE) + { + vkDestroySemaphore(g_vulkan_context->GetDevice(), m_rendering_finished_semaphore, nullptr); + m_rendering_finished_semaphore = VK_NULL_HANDLE; + } +} + +} // namespace Vulkan diff --git a/jni/common/vulkan/swap_chain.h b/jni/common/vulkan/swap_chain.h new file mode 100644 index 0000000..eb2ab33 --- /dev/null +++ b/jni/common/vulkan/swap_chain.h @@ -0,0 +1,100 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#pragma once + +#include "../types.h" +#include "../window_info.h" +#include "texture.h" +#include "vulkan_loader.h" +#include +#include + +namespace Vulkan { + +class SwapChain +{ +public: + SwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync); + ~SwapChain(); + + // Creates a vulkan-renderable surface for the specified window handle. + static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, WindowInfo& wi); + + // Destroys a previously-created surface. + static void DestroyVulkanSurface(VkInstance instance, WindowInfo& wi, VkSurfaceKHR surface); + + // Create a new swap chain from a pre-existing surface. + static std::unique_ptr Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync); + + ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; } + ALWAYS_INLINE VkSurfaceFormatKHR GetSurfaceFormat() const { return m_surface_format; } + ALWAYS_INLINE VkFormat GetTextureFormat() const { return m_surface_format.format; } + ALWAYS_INLINE bool IsVSyncEnabled() const { return m_vsync_enabled; } + ALWAYS_INLINE VkSwapchainKHR GetSwapChain() const { return m_swap_chain; } + ALWAYS_INLINE u32 GetWidth() const { return m_width; } + ALWAYS_INLINE u32 GetHeight() const { return m_height; } + ALWAYS_INLINE u32 GetCurrentImageIndex() const { return m_current_image; } + ALWAYS_INLINE u32 GetImageCount() const { return static_cast(m_images.size()); } + ALWAYS_INLINE VkImage GetCurrentImage() const { return m_images[m_current_image].image; } + ALWAYS_INLINE const Texture& GetCurrentTexture() const { return m_images[m_current_image].texture; } + ALWAYS_INLINE Texture& GetCurrentTexture() { return m_images[m_current_image].texture; } + ALWAYS_INLINE VkFramebuffer GetCurrentFramebuffer() const { return m_images[m_current_image].framebuffer; } + ALWAYS_INLINE VkRenderPass GetLoadRenderPass() const { return m_load_render_pass; } + ALWAYS_INLINE VkRenderPass GetClearRenderPass() const { return m_clear_render_pass; } + ALWAYS_INLINE VkSemaphore GetImageAvailableSemaphore() const { return m_image_available_semaphore; } + ALWAYS_INLINE VkSemaphore GetRenderingFinishedSemaphore() const { return m_rendering_finished_semaphore; } + VkResult AcquireNextImage(); + + bool RecreateSurface(const WindowInfo& new_wi); + bool ResizeSwapChain(u32 new_width = 0, u32 new_height = 0); + bool RecreateSwapChain(); + + // Change vsync enabled state. This may fail as it causes a swapchain recreation. + bool SetVSync(bool enabled); + +private: + bool SelectSurfaceFormat(); + bool SelectPresentMode(); + + bool CreateSwapChain(); + void DestroySwapChain(); + + bool SetupSwapChainImages(); + void DestroySwapChainImages(); + + void DestroySurface(); + + bool CreateSemaphores(); + void DestroySemaphores(); + + struct SwapChainImage + { + VkImage image; + Texture texture; + VkFramebuffer framebuffer; + }; + + u32 m_width = 0; + u32 m_height = 0; + WindowInfo m_wi; + bool m_vsync_enabled = false; + + VkSurfaceKHR m_surface = VK_NULL_HANDLE; + VkSurfaceFormatKHR m_surface_format = {}; + VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; + + VkRenderPass m_load_render_pass = VK_NULL_HANDLE; + VkRenderPass m_clear_render_pass = VK_NULL_HANDLE; + + VkSemaphore m_image_available_semaphore = VK_NULL_HANDLE; + VkSemaphore m_rendering_finished_semaphore = VK_NULL_HANDLE; + + VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE; + std::vector m_images; + u32 m_current_image = 0; +}; + +} // namespace Vulkan diff --git a/jni/common/vulkan/texture.cpp b/jni/common/vulkan/texture.cpp new file mode 100644 index 0000000..7078b8e --- /dev/null +++ b/jni/common/vulkan/texture.cpp @@ -0,0 +1,374 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#include "texture.h" +#include "../assert.h" +#include "context.h" +#include "util.h" +#include + +namespace Vulkan { +Texture::Texture() = default; + +Texture::Texture(Texture&& move) + : m_width(move.m_width), m_height(move.m_height), m_levels(move.m_levels), m_layers(move.m_layers), + m_format(move.m_format), m_samples(move.m_samples), m_view_type(move.m_view_type), m_layout(move.m_layout), + m_image(move.m_image), m_device_memory(move.m_device_memory), m_view(move.m_view) +{ + move.m_width = 0; + move.m_height = 0; + move.m_levels = 0; + move.m_layers = 0; + move.m_format = VK_FORMAT_UNDEFINED; + move.m_samples = VK_SAMPLE_COUNT_1_BIT; + move.m_view_type = VK_IMAGE_VIEW_TYPE_2D; + move.m_layout = VK_IMAGE_LAYOUT_UNDEFINED; + move.m_image = VK_NULL_HANDLE; + move.m_device_memory = VK_NULL_HANDLE; + move.m_view = VK_NULL_HANDLE; +} + +Texture::~Texture() +{ + if (IsValid()) + Destroy(true); +} + +Vulkan::Texture& Texture::operator=(Texture&& move) +{ + if (IsValid()) + Destroy(true); + + std::swap(m_width, move.m_width); + std::swap(m_height, move.m_height); + std::swap(m_levels, move.m_levels); + std::swap(m_layers, move.m_layers); + std::swap(m_format, move.m_format); + std::swap(m_samples, move.m_samples); + std::swap(m_view_type, move.m_view_type); + std::swap(m_layout, move.m_layout); + std::swap(m_image, move.m_image); + std::swap(m_device_memory, move.m_device_memory); + std::swap(m_view, move.m_view); + + return *this; +} + +bool Texture::Create(u32 width, u32 height, u32 levels, u32 layers, VkFormat format, VkSampleCountFlagBits samples, + VkImageViewType view_type, VkImageTiling tiling, VkImageUsageFlags usage) +{ + VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + nullptr, + 0, + VK_IMAGE_TYPE_2D, + format, + {width, height, 1}, + levels, + layers, + samples, + tiling, + usage, + VK_SHARING_MODE_EXCLUSIVE, + 0, + nullptr, + VK_IMAGE_LAYOUT_UNDEFINED}; + + VkImage image = VK_NULL_HANDLE; + VkResult res = vkCreateImage(g_vulkan_context->GetDevice(), &image_info, nullptr, &image); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateImage failed: "); + return false; + } + + // Allocate memory to back this texture, we want device local memory in this case + VkMemoryRequirements memory_requirements; + vkGetImageMemoryRequirements(g_vulkan_context->GetDevice(), image, &memory_requirements); + + VkMemoryAllocateInfo memory_info = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, nullptr, memory_requirements.size, + g_vulkan_context->GetMemoryType(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)}; + + VkDeviceMemory device_memory; + res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_info, nullptr, &device_memory); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: "); + vkDestroyImage(g_vulkan_context->GetDevice(), image, nullptr); + return false; + } + + res = vkBindImageMemory(g_vulkan_context->GetDevice(), image, device_memory, 0); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkBindImageMemory failed: "); + vkDestroyImage(g_vulkan_context->GetDevice(), image, nullptr); + vkFreeMemory(g_vulkan_context->GetDevice(), device_memory, nullptr); + return false; + } + + VkImageViewCreateInfo view_info = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + nullptr, + 0, + image, + view_type, + format, + {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}, + {Util::IsDepthFormat(format) ? + static_cast(VK_IMAGE_ASPECT_DEPTH_BIT) : + static_cast(VK_IMAGE_ASPECT_COLOR_BIT), + 0, levels, 0, layers}}; + + VkImageView view = VK_NULL_HANDLE; + res = vkCreateImageView(g_vulkan_context->GetDevice(), &view_info, nullptr, &view); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateImageView failed: "); + vkDestroyImage(g_vulkan_context->GetDevice(), image, nullptr); + vkFreeMemory(g_vulkan_context->GetDevice(), device_memory, nullptr); + return false; + } + + if (IsValid()) + Destroy(true); + + m_width = width; + m_height = height; + m_levels = levels; + m_layers = layers; + m_format = format; + m_samples = samples; + m_view_type = view_type; + m_image = image; + m_device_memory = device_memory; + m_view = view; + return true; +} + +bool Texture::Adopt(VkImage existing_image, VkImageViewType view_type, u32 width, u32 height, u32 levels, u32 layers, + VkFormat format, VkSampleCountFlagBits samples) +{ + // Only need to create the image view, this is mainly for swap chains. + VkImageViewCreateInfo view_info = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + nullptr, + 0, + existing_image, + view_type, + format, + {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}, + {Util::IsDepthFormat(format) ? + static_cast(VK_IMAGE_ASPECT_DEPTH_BIT) : + static_cast(VK_IMAGE_ASPECT_COLOR_BIT), + 0, levels, 0, layers}}; + + // Memory is managed by the owner of the image. + VkImageView view = VK_NULL_HANDLE; + VkResult res = vkCreateImageView(g_vulkan_context->GetDevice(), &view_info, nullptr, &view); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateImageView failed: "); + return false; + } + + if (IsValid()) + Destroy(true); + + m_width = width; + m_height = height; + m_levels = levels; + m_layers = layers; + m_format = format; + m_samples = samples; + m_view_type = view_type; + m_image = existing_image; + m_view = view; + return true; +} + +void Texture::Destroy(bool defer /* = true */) +{ + if (m_view != VK_NULL_HANDLE) + { + if (defer) + g_vulkan_context->DeferImageViewDestruction(m_view); + else + vkDestroyImageView(g_vulkan_context->GetDevice(), m_view, nullptr); + m_view = VK_NULL_HANDLE; + } + + // If we don't have device memory allocated, the image is not owned by us (e.g. swapchain) + if (m_device_memory != VK_NULL_HANDLE) + { + DebugAssert(m_image != VK_NULL_HANDLE); + if (defer) + g_vulkan_context->DeferImageDestruction(m_image); + else + vkDestroyImage(g_vulkan_context->GetDevice(), m_image, nullptr); + m_image = VK_NULL_HANDLE; + + if (defer) + g_vulkan_context->DeferDeviceMemoryDestruction(m_device_memory); + else + vkFreeMemory(g_vulkan_context->GetDevice(), m_device_memory, nullptr); + m_device_memory = VK_NULL_HANDLE; + } + + m_width = 0; + m_height = 0; + m_levels = 0; + m_layers = 0; + m_format = VK_FORMAT_UNDEFINED; + m_samples = VK_SAMPLE_COUNT_1_BIT; + m_view_type = VK_IMAGE_VIEW_TYPE_2D; + m_layout = VK_IMAGE_LAYOUT_UNDEFINED; + m_image = VK_NULL_HANDLE; + m_device_memory = VK_NULL_HANDLE; + m_view = VK_NULL_HANDLE; +} + +void Texture::OverrideImageLayout(VkImageLayout new_layout) +{ + m_layout = new_layout; +} + +void Texture::TransitionToLayout(VkCommandBuffer command_buffer, VkImageLayout new_layout) +{ + if (m_layout == new_layout) + return; + + VkImageMemoryBarrier barrier = { + VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkAccessFlags srcAccessMask + 0, // VkAccessFlags dstAccessMask + m_layout, // VkImageLayout oldLayout + new_layout, // VkImageLayout newLayout + VK_QUEUE_FAMILY_IGNORED, // uint32_t srcQueueFamilyIndex + VK_QUEUE_FAMILY_IGNORED, // uint32_t dstQueueFamilyIndex + m_image, // VkImage image + {static_cast(Util::IsDepthFormat(m_format) ? VK_IMAGE_ASPECT_DEPTH_BIT : + VK_IMAGE_ASPECT_COLOR_BIT), + 0, m_levels, 0, m_layers} // VkImageSubresourceRange subresourceRange + }; + + // srcStageMask -> Stages that must complete before the barrier + // dstStageMask -> Stages that must wait for after the barrier before beginning + VkPipelineStageFlags srcStageMask, dstStageMask; + switch (m_layout) + { + case VK_IMAGE_LAYOUT_UNDEFINED: + // Layout undefined therefore contents undefined, and we don't care what happens to it. + barrier.srcAccessMask = 0; + srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + break; + + case VK_IMAGE_LAYOUT_PREINITIALIZED: + // Image has been pre-initialized by the host, so ensure all writes have completed. + barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + srcStageMask = VK_PIPELINE_STAGE_HOST_BIT; + break; + + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + // Image was being used as a color attachment, so ensure all writes have completed. + barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + break; + + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + // Image was being used as a depthstencil attachment, so ensure all writes have completed. + barrier.srcAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + break; + + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + // Image was being used as a shader resource, make sure all reads have finished. + barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + break; + + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + // Image was being used as a copy source, ensure all reads have finished. + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + // Image was being used as a copy destination, ensure all writes have finished. + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + + default: + srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + break; + } + + switch (new_layout) + { + case VK_IMAGE_LAYOUT_UNDEFINED: + barrier.dstAccessMask = 0; + dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + break; + + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + break; + + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + barrier.dstAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + break; + + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + break; + + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + + case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: + srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + break; + + default: + dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + break; + } + + vkCmdPipelineBarrier(command_buffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier); + + m_layout = new_layout; +} + +VkFramebuffer Texture::CreateFramebuffer(VkRenderPass render_pass) +{ + const VkFramebufferCreateInfo ci = { + VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, 0u, render_pass, 1, &m_view, m_width, m_height, m_layers}; + VkFramebuffer fb = VK_NULL_HANDLE; + VkResult res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &ci, nullptr, &fb); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateFramebuffer() failed: "); + return VK_NULL_HANDLE; + } + + return fb; +} + +} // namespace Vulkan diff --git a/jni/common/vulkan/texture.h b/jni/common/vulkan/texture.h new file mode 100644 index 0000000..15d8dcd --- /dev/null +++ b/jni/common/vulkan/texture.h @@ -0,0 +1,72 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#pragma once +#include "../types.h" +#include "vulkan_loader.h" +#include + +namespace Vulkan { +class Texture +{ +public: + Texture(); + Texture(Texture&& move); + Texture(const Texture&) = delete; + ~Texture(); + + Texture& operator=(Texture&& move); + Texture& operator=(const Texture&) = delete; + + ALWAYS_INLINE bool IsValid() const { return (m_image != VK_NULL_HANDLE); } + + /// An image is considered owned/managed if we control the memory. + ALWAYS_INLINE bool IsOwned() const { return (m_device_memory != VK_NULL_HANDLE); } + + ALWAYS_INLINE u32 GetWidth() const { return m_width; } + ALWAYS_INLINE u32 GetHeight() const { return m_height; } + ALWAYS_INLINE u32 GetLevels() const { return m_levels; } + ALWAYS_INLINE u32 GetLayers() const { return m_layers; } + ALWAYS_INLINE VkFormat GetFormat() const { return m_format; } + ALWAYS_INLINE VkSampleCountFlagBits GetSamples() const { return m_samples; } + ALWAYS_INLINE VkImageLayout GetLayout() const { return m_layout; } + ALWAYS_INLINE VkImageViewType GetViewType() const { return m_view_type; } + ALWAYS_INLINE VkImage GetImage() const { return m_image; } + ALWAYS_INLINE VkDeviceMemory GetDeviceMemory() const { return m_device_memory; } + ALWAYS_INLINE VkImageView GetView() const { return m_view; } + + bool Create(u32 width, u32 height, u32 levels, u32 layers, VkFormat format, VkSampleCountFlagBits samples, + VkImageViewType view_type, VkImageTiling tiling, VkImageUsageFlags usage); + + bool Adopt(VkImage existing_image, VkImageViewType view_type, u32 width, u32 height, u32 levels, u32 layers, + VkFormat format, VkSampleCountFlagBits samples); + + void Destroy(bool defer = true); + + // Used when the render pass is changing the image layout, or to force it to + // VK_IMAGE_LAYOUT_UNDEFINED, if the existing contents of the image is + // irrelevant and will not be loaded. + void OverrideImageLayout(VkImageLayout new_layout); + + void TransitionToLayout(VkCommandBuffer command_buffer, VkImageLayout new_layout); + + VkFramebuffer CreateFramebuffer(VkRenderPass render_pass); + +private: + u32 m_width = 0; + u32 m_height = 0; + u32 m_levels = 0; + u32 m_layers = 0; + VkFormat m_format = VK_FORMAT_UNDEFINED; + VkSampleCountFlagBits m_samples = VK_SAMPLE_COUNT_1_BIT; + VkImageViewType m_view_type = VK_IMAGE_VIEW_TYPE_2D; + VkImageLayout m_layout = VK_IMAGE_LAYOUT_UNDEFINED; + + VkImage m_image = VK_NULL_HANDLE; + VkDeviceMemory m_device_memory = VK_NULL_HANDLE; + VkImageView m_view = VK_NULL_HANDLE; +}; + +} // namespace Vulkan diff --git a/jni/common/vulkan/util.cpp b/jni/common/vulkan/util.cpp new file mode 100644 index 0000000..c0f4188 --- /dev/null +++ b/jni/common/vulkan/util.cpp @@ -0,0 +1,426 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#include "util.h" +#include "../assert.h" +#include "../log.h" +#include "../string_util.h" +#include "context.h" +#include "shader_compiler.h" +Log_SetChannel(Vulkan::Util); + +namespace Vulkan { +namespace Util { +bool IsDepthFormat(VkFormat format) +{ + switch (format) + { + case VK_FORMAT_D16_UNORM: + case VK_FORMAT_D16_UNORM_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return true; + default: + return false; + } +} + +bool IsCompressedFormat(VkFormat format) +{ + switch (format) + { + case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: + case VK_FORMAT_BC2_UNORM_BLOCK: + case VK_FORMAT_BC3_UNORM_BLOCK: + case VK_FORMAT_BC7_UNORM_BLOCK: + return true; + + default: + return false; + } +} + +VkFormat GetLinearFormat(VkFormat format) +{ + switch (format) + { + case VK_FORMAT_R8_SRGB: + return VK_FORMAT_R8_UNORM; + case VK_FORMAT_R8G8_SRGB: + return VK_FORMAT_R8G8_UNORM; + case VK_FORMAT_R8G8B8_SRGB: + return VK_FORMAT_R8G8B8_UNORM; + case VK_FORMAT_R8G8B8A8_SRGB: + return VK_FORMAT_R8G8B8A8_UNORM; + case VK_FORMAT_B8G8R8_SRGB: + return VK_FORMAT_B8G8R8_UNORM; + case VK_FORMAT_B8G8R8A8_SRGB: + return VK_FORMAT_B8G8R8A8_UNORM; + default: + return format; + } +} + +u32 GetTexelSize(VkFormat format) +{ + // Only contains pixel formats we use. + switch (format) + { + case VK_FORMAT_R32_SFLOAT: + return 4; + + case VK_FORMAT_D32_SFLOAT: + return 4; + + case VK_FORMAT_R8G8B8A8_UNORM: + return 4; + + case VK_FORMAT_B8G8R8A8_UNORM: + return 4; + + case VK_FORMAT_R5G5B5A1_UNORM_PACK16: + case VK_FORMAT_A1R5G5B5_UNORM_PACK16: + case VK_FORMAT_R5G6B5_UNORM_PACK16: + case VK_FORMAT_B5G6R5_UNORM_PACK16: + return 2; + + case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: + return 8; + + case VK_FORMAT_BC2_UNORM_BLOCK: + case VK_FORMAT_BC3_UNORM_BLOCK: + case VK_FORMAT_BC7_UNORM_BLOCK: + return 16; + + default: + Panic("Unhandled pixel format"); + return 1; + } +} + +u32 GetBlockSize(VkFormat format) +{ + switch (format) + { + case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: + case VK_FORMAT_BC2_UNORM_BLOCK: + case VK_FORMAT_BC3_UNORM_BLOCK: + case VK_FORMAT_BC7_UNORM_BLOCK: + return 4; + + default: + return 1; + } +} + +VkRect2D ClampRect2D(const VkRect2D& rect, u32 width, u32 height) +{ + VkRect2D out; + out.offset.x = std::clamp(rect.offset.x, 0, static_cast(width - 1)); + out.offset.y = std::clamp(rect.offset.y, 0, static_cast(height - 1)); + out.extent.width = std::min(rect.extent.width, width - static_cast(rect.offset.x)); + out.extent.height = std::min(rect.extent.height, height - static_cast(rect.offset.y)); + return out; +} + +VkBlendFactor GetAlphaBlendFactor(VkBlendFactor factor) +{ + switch (factor) + { + case VK_BLEND_FACTOR_SRC_COLOR: + return VK_BLEND_FACTOR_SRC_ALPHA; + case VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + case VK_BLEND_FACTOR_DST_COLOR: + return VK_BLEND_FACTOR_DST_ALPHA; + case VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR: + return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + default: + return factor; + } +} + +void SetViewport(VkCommandBuffer command_buffer, int x, int y, int width, int height, float min_depth /*= 0.0f*/, + float max_depth /*= 1.0f*/) +{ + const VkViewport vp{static_cast(x), + static_cast(y), + static_cast(width), + static_cast(height), + min_depth, + max_depth}; + vkCmdSetViewport(command_buffer, 0, 1, &vp); +} + +void SetScissor(VkCommandBuffer command_buffer, int x, int y, int width, int height) +{ + const VkRect2D scissor{{x, y}, {static_cast(width), static_cast(height)}}; + vkCmdSetScissor(command_buffer, 0, 1, &scissor); +} + +void SetViewportAndScissor(VkCommandBuffer command_buffer, int x, int y, int width, int height, + float min_depth /* = 0.0f */, float max_depth /* = 1.0f */) +{ + const VkViewport vp{static_cast(x), + static_cast(y), + static_cast(width), + static_cast(height), + min_depth, + max_depth}; + const VkRect2D scissor{{x, y}, {static_cast(width), static_cast(height)}}; + vkCmdSetViewport(command_buffer, 0, 1, &vp); + vkCmdSetScissor(command_buffer, 0, 1, &scissor); +} + +void SafeDestroyFramebuffer(VkFramebuffer& fb) +{ + if (fb != VK_NULL_HANDLE) + { + vkDestroyFramebuffer(g_vulkan_context->GetDevice(), fb, nullptr); + fb = VK_NULL_HANDLE; + } +} + +void SafeDestroyShaderModule(VkShaderModule& sm) +{ + if (sm != VK_NULL_HANDLE) + { + vkDestroyShaderModule(g_vulkan_context->GetDevice(), sm, nullptr); + sm = VK_NULL_HANDLE; + } +} + +void SafeDestroyPipeline(VkPipeline& p) +{ + if (p != VK_NULL_HANDLE) + { + vkDestroyPipeline(g_vulkan_context->GetDevice(), p, nullptr); + p = VK_NULL_HANDLE; + } +} + +void SafeDestroyPipelineLayout(VkPipelineLayout& pl) +{ + if (pl != VK_NULL_HANDLE) + { + vkDestroyPipelineLayout(g_vulkan_context->GetDevice(), pl, nullptr); + pl = VK_NULL_HANDLE; + } +} + +void SafeDestroyDescriptorSetLayout(VkDescriptorSetLayout& dsl) +{ + if (dsl != VK_NULL_HANDLE) + { + vkDestroyDescriptorSetLayout(g_vulkan_context->GetDevice(), dsl, nullptr); + dsl = VK_NULL_HANDLE; + } +} + +void SafeDestroyBufferView(VkBufferView& bv) +{ + if (bv != VK_NULL_HANDLE) + { + vkDestroyBufferView(g_vulkan_context->GetDevice(), bv, nullptr); + bv = VK_NULL_HANDLE; + } +} + +void SafeDestroySampler(VkSampler& samp) +{ + if (samp != VK_NULL_HANDLE) + { + vkDestroySampler(g_vulkan_context->GetDevice(), samp, nullptr); + samp = VK_NULL_HANDLE; + } +} + +void SafeDestroySemaphore(VkSemaphore& sem) +{ + if (sem != VK_NULL_HANDLE) + { + vkDestroySemaphore(g_vulkan_context->GetDevice(), sem, nullptr); + sem = VK_NULL_HANDLE; + } +} + +void SafeFreeGlobalDescriptorSet(VkDescriptorSet& ds) +{ + if (ds != VK_NULL_HANDLE) + { + g_vulkan_context->FreeGlobalDescriptorSet(ds); + ds = VK_NULL_HANDLE; + } +} + +void BufferMemoryBarrier(VkCommandBuffer command_buffer, VkBuffer buffer, VkAccessFlags src_access_mask, + VkAccessFlags dst_access_mask, VkDeviceSize offset, VkDeviceSize size, + VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask) +{ + VkBufferMemoryBarrier buffer_info = { + VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, // VkStructureType sType + nullptr, // const void* pNext + src_access_mask, // VkAccessFlags srcAccessMask + dst_access_mask, // VkAccessFlags dstAccessMask + VK_QUEUE_FAMILY_IGNORED, // uint32_t srcQueueFamilyIndex + VK_QUEUE_FAMILY_IGNORED, // uint32_t dstQueueFamilyIndex + buffer, // VkBuffer buffer + offset, // VkDeviceSize offset + size // VkDeviceSize size + }; + + vkCmdPipelineBarrier(command_buffer, src_stage_mask, dst_stage_mask, 0, 0, nullptr, 1, &buffer_info, 0, nullptr); +} + +VkShaderModule CreateShaderModule(const u32* spv, size_t spv_word_count) +{ + VkShaderModuleCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + info.codeSize = spv_word_count * sizeof(u32); + info.pCode = spv; + + VkShaderModule module; + VkResult res = vkCreateShaderModule(g_vulkan_context->GetDevice(), &info, nullptr, &module); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateShaderModule failed: "); + return VK_NULL_HANDLE; + } + + return module; +} + +VkShaderModule CompileAndCreateVertexShader(std::string_view source_code) +{ + std::optional code = ShaderCompiler::CompileVertexShader(source_code); + if (!code) + return VK_NULL_HANDLE; + + return CreateShaderModule(code->data(), code->size()); +} + +VkShaderModule CompileAndCreateGeometryShader(std::string_view source_code) +{ + std::optional code = ShaderCompiler::CompileGeometryShader(source_code); + if (!code) + return VK_NULL_HANDLE; + + return CreateShaderModule(code->data(), code->size()); +} + +VkShaderModule CompileAndCreateFragmentShader(std::string_view source_code) +{ + std::optional code = ShaderCompiler::CompileFragmentShader(source_code); + if (!code) + return VK_NULL_HANDLE; + + return CreateShaderModule(code->data(), code->size()); +} + +VkShaderModule CompileAndCreateComputeShader(std::string_view source_code) +{ + std::optional code = ShaderCompiler::CompileComputeShader(source_code); + if (!code) + return VK_NULL_HANDLE; + + return CreateShaderModule(code->data(), code->size()); +} + +const char* VkResultToString(VkResult res) +{ + switch (res) + { + case VK_SUCCESS: + return "VK_SUCCESS"; + + case VK_NOT_READY: + return "VK_NOT_READY"; + + case VK_TIMEOUT: + return "VK_TIMEOUT"; + + case VK_EVENT_SET: + return "VK_EVENT_SET"; + + case VK_EVENT_RESET: + return "VK_EVENT_RESET"; + + case VK_INCOMPLETE: + return "VK_INCOMPLETE"; + + case VK_ERROR_OUT_OF_HOST_MEMORY: + return "VK_ERROR_OUT_OF_HOST_MEMORY"; + + case VK_ERROR_OUT_OF_DEVICE_MEMORY: + return "VK_ERROR_OUT_OF_DEVICE_MEMORY"; + + case VK_ERROR_INITIALIZATION_FAILED: + return "VK_ERROR_INITIALIZATION_FAILED"; + + case VK_ERROR_DEVICE_LOST: + return "VK_ERROR_DEVICE_LOST"; + + case VK_ERROR_MEMORY_MAP_FAILED: + return "VK_ERROR_MEMORY_MAP_FAILED"; + + case VK_ERROR_LAYER_NOT_PRESENT: + return "VK_ERROR_LAYER_NOT_PRESENT"; + + case VK_ERROR_EXTENSION_NOT_PRESENT: + return "VK_ERROR_EXTENSION_NOT_PRESENT"; + + case VK_ERROR_FEATURE_NOT_PRESENT: + return "VK_ERROR_FEATURE_NOT_PRESENT"; + + case VK_ERROR_INCOMPATIBLE_DRIVER: + return "VK_ERROR_INCOMPATIBLE_DRIVER"; + + case VK_ERROR_TOO_MANY_OBJECTS: + return "VK_ERROR_TOO_MANY_OBJECTS"; + + case VK_ERROR_FORMAT_NOT_SUPPORTED: + return "VK_ERROR_FORMAT_NOT_SUPPORTED"; + + case VK_ERROR_SURFACE_LOST_KHR: + return "VK_ERROR_SURFACE_LOST_KHR"; + + case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: + return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; + + case VK_SUBOPTIMAL_KHR: + return "VK_SUBOPTIMAL_KHR"; + + case VK_ERROR_OUT_OF_DATE_KHR: + return "VK_ERROR_OUT_OF_DATE_KHR"; + + case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: + return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; + + case VK_ERROR_VALIDATION_FAILED_EXT: + return "VK_ERROR_VALIDATION_FAILED_EXT"; + + case VK_ERROR_INVALID_SHADER_NV: + return "VK_ERROR_INVALID_SHADER_NV"; + + default: + return "UNKNOWN_VK_RESULT"; + } +} + +void LogVulkanResult(int level, const char* func_name, VkResult res, const char* msg, ...) +{ + std::va_list ap; + va_start(ap, msg); + std::string real_msg = StringUtil::StdStringFromFormatV(msg, ap); + va_end(ap); + + Log::Writef("Vulkan", func_name, static_cast(level), "(%s) %s (%d: %s)", func_name, real_msg.c_str(), + static_cast(res), VkResultToString(res)); +} + +} // namespace Util + +} // namespace Vulkan diff --git a/jni/common/vulkan/util.h b/jni/common/vulkan/util.h new file mode 100644 index 0000000..1b45e24 --- /dev/null +++ b/jni/common/vulkan/util.h @@ -0,0 +1,82 @@ +// Copyright 2016 Dolphin Emulator Project +// Copyright 2020 DuckStation Emulator Project +// Licensed under GPLv2+ +// Refer to the LICENSE file included. + +#pragma once + +#include "../types.h" +#include "vulkan_loader.h" +#include +#include + +namespace Vulkan { +namespace Util { + +inline constexpr u32 MakeRGBA8Color(float r, float g, float b, float a) +{ + return (static_cast(std::clamp(static_cast(r * 255.0f), 0, 255)) << 0) | + (static_cast(std::clamp(static_cast(g * 255.0f), 0, 255)) << 8) | + (static_cast(std::clamp(static_cast(b * 255.0f), 0, 255)) << 16) | + (static_cast(std::clamp(static_cast(a * 255.0f), 0, 255)) << 24); +} + +bool IsDepthFormat(VkFormat format); +bool IsCompressedFormat(VkFormat format); +VkFormat GetLinearFormat(VkFormat format); +u32 GetTexelSize(VkFormat format); +u32 GetBlockSize(VkFormat format); + +// Clamps a VkRect2D to the specified dimensions. +VkRect2D ClampRect2D(const VkRect2D& rect, u32 width, u32 height); + +// Map {SRC,DST}_COLOR to {SRC,DST}_ALPHA +VkBlendFactor GetAlphaBlendFactor(VkBlendFactor factor); + +// Safe destroy helpers +void SafeDestroyFramebuffer(VkFramebuffer& fb); +void SafeDestroyShaderModule(VkShaderModule& sm); +void SafeDestroyPipeline(VkPipeline& p); +void SafeDestroyPipelineLayout(VkPipelineLayout& pl); +void SafeDestroyDescriptorSetLayout(VkDescriptorSetLayout& dsl); +void SafeDestroyBufferView(VkBufferView& bv); +void SafeDestroySampler(VkSampler& samp); +void SafeDestroySemaphore(VkSemaphore& sem); +void SafeFreeGlobalDescriptorSet(VkDescriptorSet& ds); + +void SetViewport(VkCommandBuffer command_buffer, int x, int y, int width, int height, float min_depth = 0.0f, + float max_depth = 1.0f); +void SetScissor(VkCommandBuffer command_buffer, int x, int y, int width, int height); + +// Combines viewport and scissor updates +void SetViewportAndScissor(VkCommandBuffer command_buffer, int x, int y, int width, int height, float min_depth = 0.0f, + float max_depth = 1.0f); + +// Wrapper for creating an barrier on a buffer +void BufferMemoryBarrier(VkCommandBuffer command_buffer, VkBuffer buffer, VkAccessFlags src_access_mask, + VkAccessFlags dst_access_mask, VkDeviceSize offset, VkDeviceSize size, + VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask); + +// Create a shader module from the specified SPIR-V. +VkShaderModule CreateShaderModule(const u32* spv, size_t spv_word_count); + +// Compile a vertex shader and create a shader module, discarding the intermediate SPIR-V. +VkShaderModule CompileAndCreateVertexShader(std::string_view source_code); + +// Compile a geometry shader and create a shader module, discarding the intermediate SPIR-V. +VkShaderModule CompileAndCreateGeometryShader(std::string_view source_code); + +// Compile a fragment shader and create a shader module, discarding the intermediate SPIR-V. +VkShaderModule CompileAndCreateFragmentShader(std::string_view source_code); + +// Compile a compute shader and create a shader module, discarding the intermediate SPIR-V. +VkShaderModule CompileAndCreateComputeShader(std::string_view source_code); + +const char* VkResultToString(VkResult res); +void LogVulkanResult(int level, const char* func_name, VkResult res, const char* msg, ...); + +#define LOG_VULKAN_ERROR(res, ...) ::Vulkan::Util::LogVulkanResult(1, __func__, res, __VA_ARGS__) + +} // namespace Util + +} // namespace Vulkan diff --git a/jni/common/wav_writer.cpp b/jni/common/wav_writer.cpp new file mode 100644 index 0000000..5f51e9c --- /dev/null +++ b/jni/common/wav_writer.cpp @@ -0,0 +1,115 @@ +#include "wav_writer.h" +#include "file_system.h" +#include "log.h" +Log_SetChannel(WAVWriter); + +#pragma pack(push, 1) +struct WAV_HEADER +{ + u32 chunk_id; // RIFF + u32 chunk_size; + u32 format; // WAVE + + struct FormatChunk + { + u32 chunk_id; // "fmt " + u32 chunk_size; + u16 audio_format; // pcm = 1 + u16 num_channels; + u32 sample_rate; + u32 byte_rate; + u16 block_align; + u16 bits_per_sample; + } fmt_chunk; + + struct DataChunkHeader + { + u32 chunk_id; // "data " + u32 chunk_size; + } data_chunk_header; +}; +#pragma pack(pop) + +namespace Common { + +WAVWriter::WAVWriter() = default; + +WAVWriter::~WAVWriter() +{ + if (IsOpen()) + Close(); +} + +bool WAVWriter::Open(const char* filename, u32 sample_rate, u32 num_channels) +{ + if (IsOpen()) + Close(); + + m_file = FileSystem::OpenCFile(filename, "wb"); + if (!m_file) + return false; + + m_sample_rate = sample_rate; + m_num_channels = num_channels; + + if (!WriteHeader()) + { + Log_ErrorPrintf("Failed to write header to file"); + m_sample_rate = 0; + m_num_channels = 0; + std::fclose(m_file); + m_file = nullptr; + return false; + } + + return true; +} + +void WAVWriter::Close() +{ + if (!IsOpen()) + return; + + if (std::fseek(m_file, 0, SEEK_SET) != 0 || !WriteHeader()) + Log_ErrorPrintf("Failed to re-write header on file, file may be unplayable"); + + std::fclose(m_file); + m_file = nullptr; + m_sample_rate = 0; + m_num_channels = 0; + m_num_frames = 0; +} + +void WAVWriter::WriteFrames(const s16* samples, u32 num_frames) +{ + const u32 num_frames_written = + static_cast(std::fwrite(samples, sizeof(s16) * m_num_channels, num_frames, m_file)); + if (num_frames_written != num_frames) + Log_ErrorPrintf("Only wrote %u of %u frames to output file", num_frames_written); + + m_num_frames += num_frames_written; +} + +bool WAVWriter::WriteHeader() +{ + const u32 data_size = sizeof(SampleType) * m_num_channels * m_num_frames; + + WAV_HEADER header = {}; + header.chunk_id = 0x46464952; // 0x52494646 + header.chunk_size = sizeof(WAV_HEADER) - 8 + data_size; + header.format = 0x45564157; // 0x57415645 + header.fmt_chunk.chunk_id = 0x20746d66; // 0x666d7420 + header.fmt_chunk.chunk_size = sizeof(header.fmt_chunk) - 8; + header.fmt_chunk.audio_format = 1; + header.fmt_chunk.num_channels = static_cast(m_num_channels); + header.fmt_chunk.sample_rate = m_sample_rate; + header.fmt_chunk.byte_rate = m_sample_rate * m_num_channels * sizeof(SampleType); + header.fmt_chunk.block_align = static_cast(m_num_channels * sizeof(SampleType)); + header.fmt_chunk.bits_per_sample = 16; + header.data_chunk_header.chunk_id = 0x61746164; // 0x64617461 + header.data_chunk_header.chunk_size = data_size; + + return (std::fwrite(&header, sizeof(header), 1, m_file) == 1); +} + +} // namespace Common \ No newline at end of file diff --git a/jni/common/wav_writer.h b/jni/common/wav_writer.h new file mode 100644 index 0000000..d81c030 --- /dev/null +++ b/jni/common/wav_writer.h @@ -0,0 +1,34 @@ +#pragma once +#include "types.h" +#include + +namespace Common { + +class WAVWriter +{ +public: + WAVWriter(); + ~WAVWriter(); + + ALWAYS_INLINE u32 GetSampleRate() const { return m_sample_rate; } + ALWAYS_INLINE u32 GetNumChannels() const { return m_num_channels; } + ALWAYS_INLINE u32 GetNumFrames() const { return m_num_frames; } + ALWAYS_INLINE bool IsOpen() const { return (m_file != nullptr); } + + bool Open(const char* filename, u32 sample_rate, u32 num_channels); + void Close(); + + void WriteFrames(const s16* samples, u32 num_frames); + +private: + using SampleType = s16; + + bool WriteHeader(); + + std::FILE* m_file = nullptr; + u32 m_sample_rate = 0; + u32 m_num_channels = 0; + u32 m_num_frames = 0; +}; + +} // namespace Common \ No newline at end of file diff --git a/jni/common/win32_progress_callback.cpp b/jni/common/win32_progress_callback.cpp new file mode 100644 index 0000000..0d580f2 --- /dev/null +++ b/jni/common/win32_progress_callback.cpp @@ -0,0 +1,233 @@ +#include "win32_progress_callback.h" +#include "common/log.h" +#include +#pragma comment(lib, "Comctl32.lib") +Log_SetChannel(Win32ProgressCallback); + +Win32ProgressCallback::Win32ProgressCallback() : BaseProgressCallback() +{ + Create(); +} + +void Win32ProgressCallback::PushState() +{ + BaseProgressCallback::PushState(); +} + +void Win32ProgressCallback::PopState() +{ + BaseProgressCallback::PopState(); + Redraw(true); +} + +void Win32ProgressCallback::SetCancellable(bool cancellable) +{ + BaseProgressCallback::SetCancellable(cancellable); + Redraw(true); +} + +void Win32ProgressCallback::SetTitle(const char* title) +{ + SetWindowTextA(m_window_hwnd, title); +} + +void Win32ProgressCallback::SetStatusText(const char* text) +{ + BaseProgressCallback::SetStatusText(text); + Redraw(true); +} + +void Win32ProgressCallback::SetProgressRange(u32 range) +{ + BaseProgressCallback::SetProgressRange(range); + Redraw(false); +} + +void Win32ProgressCallback::SetProgressValue(u32 value) +{ + BaseProgressCallback::SetProgressValue(value); + Redraw(false); +} + +bool Win32ProgressCallback::Create() +{ + static const char* CLASS_NAME = "DSWin32ProgressCallbackWindow"; + static bool class_registered = false; + + if (!class_registered) + { + InitCommonControls(); + + WNDCLASSEX wc = {}; + wc.cbSize = sizeof(WNDCLASSEX); + wc.lpfnWndProc = WndProcThunk; + wc.hInstance = GetModuleHandle(nullptr); + // wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON)); + // wc.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON)); + wc.hCursor = LoadCursor(NULL, IDC_WAIT); + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.lpszClassName = CLASS_NAME; + if (!RegisterClassExA(&wc)) + { + Log_ErrorPrint("Failed to register window class"); + return false; + } + + class_registered = true; + } + + m_window_hwnd = + CreateWindowExA(WS_EX_CLIENTEDGE, CLASS_NAME, "Win32ProgressCallback", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, + CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, nullptr, nullptr, GetModuleHandle(nullptr), this); + if (!m_window_hwnd) + { + Log_ErrorPrint("Failed to create window"); + return false; + } + + SetWindowLongPtr(m_window_hwnd, GWLP_USERDATA, reinterpret_cast(this)); + ShowWindow(m_window_hwnd, SW_SHOW); + PumpMessages(); + return true; +} + +void Win32ProgressCallback::Destroy() +{ + if (!m_window_hwnd) + return; + + DestroyWindow(m_window_hwnd); + m_window_hwnd = {}; + m_text_hwnd = {}; + m_progress_hwnd = {}; +} + +void Win32ProgressCallback::PumpMessages() +{ + MSG msg; + while (PeekMessageA(&msg, m_window_hwnd, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } +} + +void Win32ProgressCallback::Redraw(bool force) +{ + const int percent = + static_cast((static_cast(m_progress_value) / static_cast(m_progress_range)) * 100.0f); + if (percent == m_last_progress_percent && !force) + { + PumpMessages(); + return; + } + + m_last_progress_percent = percent; + + SendMessageA(m_progress_hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, m_progress_range)); + SendMessageA(m_progress_hwnd, PBM_SETPOS, static_cast(m_progress_value), 0); + SetWindowTextA(m_text_hwnd, m_status_text); + RedrawWindow(m_text_hwnd, nullptr, nullptr, RDW_INVALIDATE); + PumpMessages(); +} + +LRESULT CALLBACK Win32ProgressCallback::WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + Win32ProgressCallback* cb; + if (msg == WM_CREATE) + { + const CREATESTRUCTA* cs = reinterpret_cast(lparam); + cb = static_cast(cs->lpCreateParams); + } + else + { + cb = reinterpret_cast(GetWindowLongPtrA(hwnd, GWLP_USERDATA)); + } + + return cb->WndProc(hwnd, msg, wparam, lparam); +} + +LRESULT CALLBACK Win32ProgressCallback::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + switch (msg) + { + case WM_CREATE: + { + const CREATESTRUCTA* cs = reinterpret_cast(lparam); + HFONT default_font = reinterpret_cast(GetStockObject(ANSI_VAR_FONT)); + SendMessageA(hwnd, WM_SETFONT, WPARAM(default_font), TRUE); + + int y = WINDOW_MARGIN; + + m_text_hwnd = CreateWindowExA(0, "Static", nullptr, WS_VISIBLE | WS_CHILD, WINDOW_MARGIN, y, SUBWINDOW_WIDTH, 16, + hwnd, nullptr, cs->hInstance, nullptr); + SendMessageA(m_text_hwnd, WM_SETFONT, WPARAM(default_font), TRUE); + y += 16 + WINDOW_MARGIN; + + m_progress_hwnd = CreateWindowExA(0, PROGRESS_CLASSA, nullptr, WS_VISIBLE | WS_CHILD, WINDOW_MARGIN, y, + SUBWINDOW_WIDTH, 32, hwnd, nullptr, cs->hInstance, nullptr); + y += 32 + WINDOW_MARGIN; + + m_list_box_hwnd = + CreateWindowExA(0, "LISTBOX", nullptr, WS_VISIBLE | WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_BORDER | LBS_NOSEL, + WINDOW_MARGIN, y, SUBWINDOW_WIDTH, 170, hwnd, nullptr, cs->hInstance, nullptr); + SendMessageA(m_list_box_hwnd, WM_SETFONT, WPARAM(default_font), TRUE); + y += 170; + } + break; + + default: + return DefWindowProcA(hwnd, msg, wparam, lparam); + } + + return 0; +} + +void Win32ProgressCallback::DisplayError(const char* message) +{ + Log_ErrorPrint(message); + SendMessageA(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast(message)); + SendMessageA(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0); + PumpMessages(); +} + +void Win32ProgressCallback::DisplayWarning(const char* message) +{ + Log_WarningPrint(message); + SendMessageA(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast(message)); + SendMessageA(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0); + PumpMessages(); +} + +void Win32ProgressCallback::DisplayInformation(const char* message) +{ + Log_InfoPrint(message); + SendMessageA(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast(message)); + SendMessageA(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0); + PumpMessages(); +} + +void Win32ProgressCallback::DisplayDebugMessage(const char* message) +{ + Log_DevPrint(message); +} + +void Win32ProgressCallback::ModalError(const char* message) +{ + PumpMessages(); + MessageBoxA(m_window_hwnd, message, "Error", MB_ICONERROR | MB_OK); + PumpMessages(); +} + +bool Win32ProgressCallback::ModalConfirmation(const char* message) +{ + PumpMessages(); + bool result = MessageBoxA(m_window_hwnd, message, "Confirmation", MB_ICONQUESTION | MB_YESNO) == IDYES; + PumpMessages(); + return result; +} + +void Win32ProgressCallback::ModalInformation(const char* message) +{ + MessageBoxA(m_window_hwnd, message, "Information", MB_ICONINFORMATION | MB_OK); +} diff --git a/jni/common/win32_progress_callback.h b/jni/common/win32_progress_callback.h new file mode 100644 index 0000000..ffb046f --- /dev/null +++ b/jni/common/win32_progress_callback.h @@ -0,0 +1,51 @@ +#pragma once +#include "common/progress_callback.h" +#include "windows_headers.h" + +class Win32ProgressCallback final : public BaseProgressCallback +{ +public: + Win32ProgressCallback(); + + void PushState() override; + void PopState() override; + + void SetCancellable(bool cancellable) override; + void SetTitle(const char* title) override; + void SetStatusText(const char* text) override; + void SetProgressRange(u32 range) override; + void SetProgressValue(u32 value) override; + + void DisplayError(const char* message) override; + void DisplayWarning(const char* message) override; + void DisplayInformation(const char* message) override; + void DisplayDebugMessage(const char* message) override; + + void ModalError(const char* message) override; + bool ModalConfirmation(const char* message) override; + void ModalInformation(const char* message) override; + +private: + enum : int + { + WINDOW_WIDTH = 600, + WINDOW_HEIGHT = 300, + WINDOW_MARGIN = 10, + SUBWINDOW_WIDTH = WINDOW_WIDTH - 20 - WINDOW_MARGIN - WINDOW_MARGIN, + }; + + bool Create(); + void Destroy(); + void Redraw(bool force); + void PumpMessages(); + + static LRESULT CALLBACK WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + HWND m_window_hwnd{}; + HWND m_text_hwnd{}; + HWND m_progress_hwnd{}; + HWND m_list_box_hwnd{}; + + int m_last_progress_percent = -1; +}; diff --git a/jni/common/window_info.h b/jni/common/window_info.h new file mode 100644 index 0000000..258bd17 --- /dev/null +++ b/jni/common/window_info.h @@ -0,0 +1,39 @@ +#pragma once +#include "types.h" + +// Contains the information required to create a graphics context in a window. +struct WindowInfo +{ + enum class Type + { + Surfaceless, + Win32, + X11, + Wayland, + MacOS, + Android, + Libretro, + }; + + enum class SurfaceFormat + { + None, + RGB8, + RGBA8, + RGB565, + Count + }; + + Type type = Type::Surfaceless; + void* display_connection = nullptr; + void* window_handle = nullptr; + u32 surface_width = 0; + u32 surface_height = 0; + float surface_scale = 1.0f; + SurfaceFormat surface_format = SurfaceFormat::RGB8; + + // Needed for macOS. +#ifdef __APPLE__ + void* surface_handle = nullptr; +#endif +}; diff --git a/jni/common/windows_headers.h b/jni/common/windows_headers.h new file mode 100644 index 0000000..178a816 --- /dev/null +++ b/jni/common/windows_headers.h @@ -0,0 +1,39 @@ +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#ifndef NOMINMAX +#define NOMINMAX 1 +#endif + +// require vista+ +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT _WIN32_WINNT_VISTA + +#include + +// hurr i'm windows, i like to conflict, fixme properly later please... +#if defined(FindTexture) +#undef FindTexture +#endif +#if defined(DrawText) +#undef DrawText +#endif +#if defined(CreateDirectory) +#undef CreateDirectory +#endif +#if defined(CopyFile) +#undef CopyFile +#endif +#if defined(DeleteFile) +#undef DeleteFile +#endif +#if defined(Yield) +#undef Yield +#endif +#if defined(LoadIcon) +#undef LoadIcon +#endif diff --git a/jni/core/CMakeLists.txt b/jni/core/CMakeLists.txt new file mode 100644 index 0000000..84bf926 --- /dev/null +++ b/jni/core/CMakeLists.txt @@ -0,0 +1,151 @@ +add_library(core + analog_controller.cpp + analog_controller.h + analog_joystick.cpp + analog_joystick.h + bios.cpp + bios.h + bus.cpp + bus.h + cdrom.cpp + cdrom.h + cdrom_async_reader.cpp + cdrom_async_reader.h + cheats.cpp + cheats.h + controller.cpp + controller.h + cpu_code_cache.cpp + cpu_code_cache.h + cpu_core.cpp + cpu_core.h + cpu_core_private.h + cpu_disasm.cpp + cpu_disasm.h + cpu_types.cpp + cpu_types.h + digital_controller.cpp + digital_controller.h + dma.cpp + dma.h + gpu.cpp + gpu.h + gpu_backend.cpp + gpu_backend.h + gpu_commands.cpp + gpu_hw.cpp + gpu_hw.h + gpu_hw_opengl.cpp + gpu_hw_opengl.h + gpu_hw_shadergen.cpp + gpu_hw_shadergen.h + gpu_hw_vulkan.cpp + gpu_hw_vulkan.h + gpu_sw.cpp + gpu_sw.h + gpu_sw_backend.cpp + gpu_sw_backend.h + gpu_types.h + gte.cpp + gte.h + gte_types.h + host_display.cpp + host_display.h + host_interface.cpp + host_interface.h + host_interface_progress_callback.cpp + host_interface_progress_callback.h + interrupt_controller.cpp + interrupt_controller.h + libcrypt_game_codes.cpp + libcrypt_game_codes.h + mdec.cpp + mdec.h + memory_card.cpp + memory_card.h + memory_card_image.cpp + memory_card_image.h + namco_guncon.cpp + namco_guncon.h + negcon.cpp + negcon.h + pad.cpp + pad.h + pgxp.cpp + pgxp.h + playstation_mouse.cpp + playstation_mouse.h + psf_loader.cpp + psf_loader.h + resources.cpp + resources.h + save_state_version.h + settings.cpp + settings.h + shadergen.cpp + shadergen.h + sio.cpp + sio.h + spu.cpp + spu.h + system.cpp + system.h + timers.cpp + timers.h + timing_event.cpp + timing_event.h + types.h +) + +set(RECOMPILER_SRCS + cpu_recompiler_code_generator.cpp + cpu_recompiler_code_generator.h + cpu_recompiler_code_generator_generic.cpp + cpu_recompiler_register_cache.cpp + cpu_recompiler_register_cache.h + cpu_recompiler_thunks.h + cpu_recompiler_types.h +) + +target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") +target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") +target_link_libraries(core PUBLIC Threads::Threads common zlib vulkan-loader) +target_link_libraries(core PRIVATE glad stb) + +if(WIN32) + target_sources(core PRIVATE + gpu_hw_d3d11.cpp + gpu_hw_d3d11.h + ) + target_link_libraries(core PRIVATE winmm.lib) +endif() + +if(${CPU_ARCH} STREQUAL "x64") + target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../../dep/xbyak/xbyak") + target_compile_definitions(core PUBLIC "WITH_RECOMPILER=1" "WITH_MMAP_FASTMEM=1") + target_sources(core PRIVATE ${RECOMPILER_SRCS} + cpu_recompiler_code_generator_x64.cpp + ) + message("Building x64 recompiler") +elseif(${CPU_ARCH} STREQUAL "aarch32") + target_compile_definitions(core PUBLIC "WITH_RECOMPILER=1") + target_sources(core PRIVATE ${RECOMPILER_SRCS} + cpu_recompiler_code_generator_aarch32.cpp + ) + target_link_libraries(core PUBLIC vixl) + message("Building AArch32 recompiler") +elseif(${CPU_ARCH} STREQUAL "aarch64") + target_compile_definitions(core PUBLIC "WITH_RECOMPILER=1" "WITH_MMAP_FASTMEM=1") + target_sources(core PRIVATE ${RECOMPILER_SRCS} + cpu_recompiler_code_generator_aarch64.cpp + ) + target_link_libraries(core PUBLIC vixl) + message("Building AArch64 recompiler") +else() + message("Not building recompiler") +endif() + +if(NOT BUILD_LIBRETRO_CORE) + target_link_libraries(core PRIVATE imgui) + target_compile_definitions(core PRIVATE "WITH_IMGUI=1") +endif() diff --git a/jni/core/analog_controller.cpp b/jni/core/analog_controller.cpp new file mode 100644 index 0000000..e522689 --- /dev/null +++ b/jni/core/analog_controller.cpp @@ -0,0 +1,744 @@ +#include "analog_controller.h" +#include "common/log.h" +#include "common/state_wrapper.h" +#include "common/string_util.h" +#include "host_interface.h" +#include "settings.h" +#include "system.h" +#include +Log_SetChannel(AnalogController); + +AnalogController::AnalogController(u32 index) : m_index(index) +{ + m_axis_state.fill(0x80); + Reset(); +} + +AnalogController::~AnalogController() = default; + +ControllerType AnalogController::GetType() const +{ + return ControllerType::AnalogController; +} + +void AnalogController::Reset() +{ + m_state = State::Idle; + m_analog_mode = false; + m_configuration_mode = false; + m_command_param = 0; + m_motor_state.fill(0); + + ResetRumbleConfig(); + + if (m_force_analog_on_reset) + { + if (g_settings.controller_disable_analog_mode_forcing) + { + g_host_interface->AddOSDMessage( + g_host_interface->TranslateStdString( + "OSDMessage", "Analog mode forcing is disabled by game settings. Controller will start in digital mode."), + 10.0f); + } + else + SetAnalogMode(true); + } +} + +bool AnalogController::DoState(StateWrapper& sw, bool apply_input_state) +{ + if (!Controller::DoState(sw, apply_input_state)) + return false; + + const bool old_analog_mode = m_analog_mode; + + sw.Do(&m_analog_mode); + sw.Do(&m_rumble_unlocked); + sw.DoEx(&m_legacy_rumble_unlocked, 44, false); + sw.Do(&m_configuration_mode); + sw.Do(&m_command_param); + + u16 button_state = m_button_state; + sw.DoEx(&button_state, 44, static_cast(0xFFFF)); + if (apply_input_state) + m_button_state = button_state; + + sw.Do(&m_state); + + sw.DoEx(&m_rumble_config, 45, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + sw.DoEx(&m_rumble_config_large_motor_index, 45, -1); + sw.DoEx(&m_rumble_config_small_motor_index, 45, -1); + sw.DoEx(&m_analog_toggle_queued, 45, false); + + MotorState motor_state = m_motor_state; + sw.Do(&motor_state); + + if (sw.IsReading()) + { + for (u8 i = 0; i < NUM_MOTORS; i++) + SetMotorState(i, motor_state[i]); + + if (old_analog_mode != m_analog_mode) + { + g_host_interface->AddFormattedOSDMessage( + 5.0f, + m_analog_mode ? + g_host_interface->TranslateString("AnalogController", "Controller %u switched to analog mode.") : + g_host_interface->TranslateString("AnalogController", "Controller %u switched to digital mode."), + m_index + 1u); + } + } + return true; +} + +std::optional AnalogController::GetAxisCodeByName(std::string_view axis_name) const +{ + return StaticGetAxisCodeByName(axis_name); +} + +std::optional AnalogController::GetButtonCodeByName(std::string_view button_name) const +{ + return StaticGetButtonCodeByName(button_name); +} + +void AnalogController::SetAxisState(s32 axis_code, float value) +{ + if (axis_code < 0 || axis_code >= static_cast(Axis::Count)) + return; + + // -1..1 -> 0..255 + const float scaled_value = std::clamp(value * m_axis_scale, -1.0f, 1.0f); + const u8 u8_value = static_cast(std::clamp(((scaled_value + 1.0f) / 2.0f) * 255.0f, 0.0f, 255.0f)); + + SetAxisState(static_cast(axis_code), u8_value); +} + +void AnalogController::SetAxisState(Axis axis, u8 value) +{ + m_axis_state[static_cast(axis)] = value; +} + +void AnalogController::SetButtonState(Button button, bool pressed) +{ + if (button == Button::Analog) + { + // analog toggle + if (pressed) + m_analog_toggle_queued = true; + + return; + } + + if (pressed) + m_button_state &= ~(u16(1) << static_cast(button)); + else + m_button_state |= u16(1) << static_cast(button); +} + +void AnalogController::SetButtonState(s32 button_code, bool pressed) +{ + if (button_code < 0 || button_code >= static_cast(Button::Count)) + return; + + SetButtonState(static_cast