mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Custom textures rewrite (#6452)
* common: Add thread pool from yuzu * Is really useful for asynchronous operations like shader compilation and custom textures, will be used in following PRs * core: Improve ImageInterface * Provide a default implementation so frontends don't have to duplicate code registering the lodepng version * Add a dds version too which we will use in the next commit * rasterizer_cache: Rewrite custom textures * There's just too much to talk about here, look at the PR description for more details * rasterizer_cache: Implement basic pack configuration file * custom_tex_manager: Flip dumped textures * custom_tex_manager: Optimize custom texture hashing * If no convertions are needed then we can hash the decoded data directly removing the needed for duplicate decode * custom_tex_manager: Implement asynchronous texture loading * The file loading and decoding is offloaded into worker threads, while the upload itself still occurs in the main thread to avoid having to manage shared contexts * Address review comments * custom_tex_manager: Introduce custom material support * video_core: Move custom textures to separate directory * Also split the files to make the code cleaner * gl_texture_runtime: Generate mipmaps for material * custom_tex_manager: Prevent memory overflow when preloading * externals: Add dds-ktx as submodule * string_util: Return vector from SplitString * No code benefits from passing it as an argument * custom_textures: Use json config file * gl_rasterizer: Only bind material for unit 0 * Address review comments
This commit is contained in:
		
							parent
							
								
									d16dce6d99
								
							
						
					
					
						commit
						06f3c90cfb
					
				
					 87 changed files with 2154 additions and 544 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							|  | @ -61,3 +61,6 @@ | ||||||
| [submodule "cryptopp"] | [submodule "cryptopp"] | ||||||
| 	path = externals/cryptopp | 	path = externals/cryptopp | ||||||
| 	url = https://github.com/weidai11/cryptopp.git | 	url = https://github.com/weidai11/cryptopp.git | ||||||
|  | [submodule "dds-ktx"] | ||||||
|  |     path = externals/dds-ktx | ||||||
|  |     url = https://github.com/septag/dds-ktx | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								externals/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								externals/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							|  | @ -64,6 +64,10 @@ if(ANDROID) | ||||||
|     endforeach() |     endforeach() | ||||||
| endif() | endif() | ||||||
| 
 | 
 | ||||||
|  | # dds-ktx | ||||||
|  | add_library(dds-ktx INTERFACE) | ||||||
|  | target_include_directories(dds-ktx INTERFACE ./dds-ktx) | ||||||
|  | 
 | ||||||
| # fmt and Xbyak need to be added before dynarmic | # fmt and Xbyak need to be added before dynarmic | ||||||
| # libfmt | # libfmt | ||||||
| option(FMT_INSTALL "" ON) | option(FMT_INSTALL "" ON) | ||||||
|  | @ -141,6 +145,10 @@ if (USE_DISCORD_PRESENCE) | ||||||
|     target_include_directories(discord-rpc INTERFACE ./discord-rpc/include) |     target_include_directories(discord-rpc INTERFACE ./discord-rpc/include) | ||||||
| endif() | endif() | ||||||
| 
 | 
 | ||||||
|  | # JSON | ||||||
|  | add_library(json-headers INTERFACE) | ||||||
|  | target_include_directories(json-headers INTERFACE ./json) | ||||||
|  | 
 | ||||||
| if (ENABLE_WEB_SERVICE) | if (ENABLE_WEB_SERVICE) | ||||||
|     find_package(OpenSSL 1.1) |     find_package(OpenSSL 1.1) | ||||||
|     if (OPENSSL_FOUND) |     if (OPENSSL_FOUND) | ||||||
|  | @ -156,9 +164,6 @@ if (ENABLE_WEB_SERVICE) | ||||||
|             DIRECTORY libressl |             DIRECTORY libressl | ||||||
|             DEFINITION OPENSSL_LIBS) |             DEFINITION OPENSSL_LIBS) | ||||||
|     endif() |     endif() | ||||||
|     # JSON |  | ||||||
|     add_library(json-headers INTERFACE) |  | ||||||
|     target_include_directories(json-headers INTERFACE ./json) |  | ||||||
| 
 | 
 | ||||||
|     if(ANDROID) |     if(ANDROID) | ||||||
|         add_subdirectory(android-ifaddrs) |         add_subdirectory(android-ifaddrs) | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								externals/dds-ktx
									
										
									
									
										vendored
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								externals/dds-ktx
									
										
									
									
										vendored
									
									
										Submodule
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | Subproject commit 42dd8aa6ded90b1ec06091522774feff51e83fc5 | ||||||
							
								
								
									
										2
									
								
								externals/zstd
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								externals/zstd
									
										
									
									
										vendored
									
									
								
							|  | @ -1 +1 @@ | ||||||
| Subproject commit 63779c798237346c2b245c546c40b72a5a5913fe | Subproject commit e47e674cd09583ff0503f0f6defd6d23d8b718d3 | ||||||
|  | @ -368,6 +368,7 @@ public final class SettingsFragmentPresenter { | ||||||
|         SettingSection utilitySection = mSettings.getSection(Settings.SECTION_UTILITY); |         SettingSection utilitySection = mSettings.getSection(Settings.SECTION_UTILITY); | ||||||
|         Setting dumpTextures = utilitySection.getSetting(SettingsFile.KEY_DUMP_TEXTURES); |         Setting dumpTextures = utilitySection.getSetting(SettingsFile.KEY_DUMP_TEXTURES); | ||||||
|         Setting customTextures = utilitySection.getSetting(SettingsFile.KEY_CUSTOM_TEXTURES); |         Setting customTextures = utilitySection.getSetting(SettingsFile.KEY_CUSTOM_TEXTURES); | ||||||
|  |         Setting asyncCustomLoading = utilitySection.getSetting(SettingsFile.KEY_ASYNC_CUSTOM_LOADING); | ||||||
|         //Setting preloadTextures = utilitySection.getSetting(SettingsFile.KEY_PRELOAD_TEXTURES); |         //Setting preloadTextures = utilitySection.getSetting(SettingsFile.KEY_PRELOAD_TEXTURES); | ||||||
| 
 | 
 | ||||||
|         sl.add(new HeaderSetting(null, null, R.string.renderer, 0)); |         sl.add(new HeaderSetting(null, null, R.string.renderer, 0)); | ||||||
|  | @ -389,6 +390,7 @@ public final class SettingsFragmentPresenter { | ||||||
|         sl.add(new HeaderSetting(null, null, R.string.utility, 0)); |         sl.add(new HeaderSetting(null, null, R.string.utility, 0)); | ||||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_DUMP_TEXTURES, Settings.SECTION_UTILITY, R.string.dump_textures, R.string.dump_textures_description, false, dumpTextures)); |         sl.add(new CheckBoxSetting(SettingsFile.KEY_DUMP_TEXTURES, Settings.SECTION_UTILITY, R.string.dump_textures, R.string.dump_textures_description, false, dumpTextures)); | ||||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_CUSTOM_TEXTURES, Settings.SECTION_UTILITY, R.string.custom_textures, R.string.custom_textures_description, false, customTextures)); |         sl.add(new CheckBoxSetting(SettingsFile.KEY_CUSTOM_TEXTURES, Settings.SECTION_UTILITY, R.string.custom_textures, R.string.custom_textures_description, false, customTextures)); | ||||||
|  |         sl.add(new CheckBoxSetting(SettingsFile.KEY_ASYNC_CUSTOM_LOADING, Settings.SECTION_UTILITY, R.string.async_custom_loading, R.string.async_custom_loading_description, true, asyncCustomLoading)); | ||||||
|         //Disabled until custom texture implementation gets rewrite, current one overloads RAM and crashes Citra. |         //Disabled until custom texture implementation gets rewrite, current one overloads RAM and crashes Citra. | ||||||
|         //sl.add(new CheckBoxSetting(SettingsFile.KEY_PRELOAD_TEXTURES, Settings.SECTION_UTILITY, R.string.preload_textures, R.string.preload_textures_description, false, preloadTextures)); |         //sl.add(new CheckBoxSetting(SettingsFile.KEY_PRELOAD_TEXTURES, Settings.SECTION_UTILITY, R.string.preload_textures, R.string.preload_textures_description, false, preloadTextures)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -73,6 +73,7 @@ public final class SettingsFile { | ||||||
|     public static final String KEY_DUMP_TEXTURES = "dump_textures"; |     public static final String KEY_DUMP_TEXTURES = "dump_textures"; | ||||||
|     public static final String KEY_CUSTOM_TEXTURES = "custom_textures"; |     public static final String KEY_CUSTOM_TEXTURES = "custom_textures"; | ||||||
|     public static final String KEY_PRELOAD_TEXTURES = "preload_textures"; |     public static final String KEY_PRELOAD_TEXTURES = "preload_textures"; | ||||||
|  |     public static final String KEY_ASYNC_CUSTOM_LOADING = "async_custom_loading"; | ||||||
| 
 | 
 | ||||||
|     public static final String KEY_AUDIO_OUTPUT_ENGINE = "output_engine"; |     public static final String KEY_AUDIO_OUTPUT_ENGINE = "output_engine"; | ||||||
|     public static final String KEY_ENABLE_AUDIO_STRETCHING = "enable_audio_stretching"; |     public static final String KEY_ENABLE_AUDIO_STRETCHING = "enable_audio_stretching"; | ||||||
|  |  | ||||||
|  | @ -25,8 +25,6 @@ add_library(citra-android SHARED | ||||||
|     game_settings.h |     game_settings.h | ||||||
|     id_cache.cpp |     id_cache.cpp | ||||||
|     id_cache.h |     id_cache.h | ||||||
|     lodepng_image_interface.cpp |  | ||||||
|     lodepng_image_interface.h |  | ||||||
|     mic.cpp |     mic.cpp | ||||||
|     mic.h |     mic.h | ||||||
|     native.cpp |     native.cpp | ||||||
|  | @ -36,6 +34,6 @@ add_library(citra-android SHARED | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| target_link_libraries(citra-android PRIVATE audio_core common core input_common network) | target_link_libraries(citra-android PRIVATE audio_core common core input_common network) | ||||||
| target_link_libraries(citra-android PRIVATE android camera2ndk EGL glad inih jnigraphics lodepng log mediandk yuv) | target_link_libraries(citra-android PRIVATE android camera2ndk EGL glad inih jnigraphics log mediandk yuv) | ||||||
| 
 | 
 | ||||||
| set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} citra-android) | set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} citra-android) | ||||||
|  |  | ||||||
|  | @ -62,8 +62,7 @@ JNIEXPORT void JNICALL Java_org_citra_citra_1emu_features_cheats_model_Cheat_set | ||||||
| JNIEXPORT jint JNICALL Java_org_citra_citra_1emu_features_cheats_model_Cheat_isValidGatewayCode( | JNIEXPORT jint JNICALL Java_org_citra_citra_1emu_features_cheats_model_Cheat_isValidGatewayCode( | ||||||
|     JNIEnv* env, jclass, jstring j_code) { |     JNIEnv* env, jclass, jstring j_code) { | ||||||
|     const std::string code = GetJString(env, j_code); |     const std::string code = GetJString(env, j_code); | ||||||
|     std::vector<std::string> code_lines; |     const auto code_lines = Common::SplitString(code, '\n'); | ||||||
|     Common::SplitString(code, '\n', code_lines); |  | ||||||
| 
 | 
 | ||||||
|     for (int i = 0; i < code_lines.size(); ++i) { |     for (int i = 0; i < code_lines.size(); ++i) { | ||||||
|         Cheats::GatewayCheat::CheatLine cheat_line(code_lines[i]); |         Cheats::GatewayCheat::CheatLine cheat_line(code_lines[i]); | ||||||
|  |  | ||||||
|  | @ -195,6 +195,7 @@ void Config::ReadValues() { | ||||||
|     ReadSetting("Utility", Settings::values.dump_textures); |     ReadSetting("Utility", Settings::values.dump_textures); | ||||||
|     ReadSetting("Utility", Settings::values.custom_textures); |     ReadSetting("Utility", Settings::values.custom_textures); | ||||||
|     ReadSetting("Utility", Settings::values.preload_textures); |     ReadSetting("Utility", Settings::values.preload_textures); | ||||||
|  |     ReadSetting("Utility", Settings::values.async_custom_loading); | ||||||
| 
 | 
 | ||||||
|     // Audio
 |     // Audio
 | ||||||
|     ReadSetting("Audio", Settings::values.audio_emulation); |     ReadSetting("Audio", Settings::values.audio_emulation); | ||||||
|  |  | ||||||
|  | @ -213,6 +213,10 @@ custom_textures = | ||||||
| # 0 (default): Off, 1: On | # 0 (default): Off, 1: On | ||||||
| preload_textures = | preload_textures = | ||||||
| 
 | 
 | ||||||
|  | # Loads custom textures asynchronously with background threads. | ||||||
|  | # 0: Off, 1 (default): On | ||||||
|  | async_custom_loading = | ||||||
|  | 
 | ||||||
| [Audio] | [Audio] | ||||||
| # Whether or not to enable DSP LLE | # Whether or not to enable DSP LLE | ||||||
| # 0 (default): No, 1: Yes | # 0 (default): No, 1: Yes | ||||||
|  |  | ||||||
|  | @ -1,44 +0,0 @@ | ||||||
| // Copyright 2019 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <lodepng.h> |  | ||||||
| #include "common/file_util.h" |  | ||||||
| #include "common/logging/log.h" |  | ||||||
| #include "jni/lodepng_image_interface.h" |  | ||||||
| 
 |  | ||||||
| bool LodePNGImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height, |  | ||||||
|                                       const std::string& path) { |  | ||||||
|     FileUtil::IOFile file(path, "rb"); |  | ||||||
|     size_t read_size = file.GetSize(); |  | ||||||
|     std::vector<u8> in(read_size); |  | ||||||
|     if (file.ReadBytes(&in[0], read_size) != read_size) { |  | ||||||
|         LOG_CRITICAL(Frontend, "Failed to decode {}", path); |  | ||||||
|     } |  | ||||||
|     u32 lodepng_ret = lodepng::decode(dst, width, height, in); |  | ||||||
|     if (lodepng_ret) { |  | ||||||
|         LOG_CRITICAL(Frontend, "Failed to decode {} because {}", path, |  | ||||||
|                      lodepng_error_text(lodepng_ret)); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool LodePNGImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src, |  | ||||||
|                                       u32 width, u32 height) { |  | ||||||
|     std::vector<u8> out; |  | ||||||
|     u32 lodepng_ret = lodepng::encode(out, src, width, height); |  | ||||||
|     if (lodepng_ret) { |  | ||||||
|         LOG_CRITICAL(Frontend, "Failed to encode {} because {}", path, |  | ||||||
|                      lodepng_error_text(lodepng_ret)); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     FileUtil::IOFile file(path, "wb"); |  | ||||||
|     if (file.WriteBytes(&out[0], out.size()) != out.size()) { |  | ||||||
|         LOG_CRITICAL(Frontend, "Failed to save encode to path={}", path); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| // Copyright 2019 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include "core/frontend/image_interface.h" |  | ||||||
| 
 |  | ||||||
| class LodePNGImageInterface final : public Frontend::ImageInterface { |  | ||||||
| public: |  | ||||||
|     bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override; |  | ||||||
|     bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width, |  | ||||||
|                    u32 height) override; |  | ||||||
| }; |  | ||||||
|  | @ -39,7 +39,6 @@ | ||||||
| #include "jni/game_settings.h" | #include "jni/game_settings.h" | ||||||
| #include "jni/id_cache.h" | #include "jni/id_cache.h" | ||||||
| #include "jni/input_manager.h" | #include "jni/input_manager.h" | ||||||
| #include "jni/lodepng_image_interface.h" |  | ||||||
| #include "jni/mic.h" | #include "jni/mic.h" | ||||||
| #include "jni/native.h" | #include "jni/native.h" | ||||||
| #include "jni/ndk_motion.h" | #include "jni/ndk_motion.h" | ||||||
|  | @ -184,9 +183,6 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { | ||||||
|     system.RegisterMiiSelector(std::make_shared<MiiSelector::AndroidMiiSelector>()); |     system.RegisterMiiSelector(std::make_shared<MiiSelector::AndroidMiiSelector>()); | ||||||
|     system.RegisterSoftwareKeyboard(std::make_shared<SoftwareKeyboard::AndroidKeyboard>()); |     system.RegisterSoftwareKeyboard(std::make_shared<SoftwareKeyboard::AndroidKeyboard>()); | ||||||
| 
 | 
 | ||||||
|     // Register generic image interface
 |  | ||||||
|     Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>()); |  | ||||||
| 
 |  | ||||||
|     // Register real Mic factory
 |     // Register real Mic factory
 | ||||||
|     Frontend::Mic::RegisterRealMicFactory(std::make_unique<Mic::AndroidFactory>()); |     Frontend::Mic::RegisterRealMicFactory(std::make_unique<Mic::AndroidFactory>()); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -116,6 +116,8 @@ | ||||||
|     <string name="custom_textures_description">Uses custom textures found in load/textures/[GAME ID]</string> |     <string name="custom_textures_description">Uses custom textures found in load/textures/[GAME ID]</string> | ||||||
|     <string name="preload_textures">Preload custom textures</string> |     <string name="preload_textures">Preload custom textures</string> | ||||||
|     <string name="preload_textures_description">Loads all custom textures into memory. This feature can use a lot of memory.</string> |     <string name="preload_textures_description">Loads all custom textures into memory. This feature can use a lot of memory.</string> | ||||||
|  |     <string name="async_custom_loading">Async custom texture loading</string> | ||||||
|  |     <string name="async_custom_loading_description">Loads custom textures in the background with worker threads to reduce loading stutter.</string> | ||||||
|     <!-- Premium strings --> |     <!-- Premium strings --> | ||||||
|     <string name="premium_text">Premium</string> |     <string name="premium_text">Premium</string> | ||||||
|     <string name="premium_settings_upsell">Upgrade to Premium and support Citra!</string> |     <string name="premium_settings_upsell">Upgrade to Premium and support Citra!</string> | ||||||
|  |  | ||||||
|  | @ -12,8 +12,6 @@ add_executable(citra | ||||||
|     emu_window/emu_window_sdl2_gl.h |     emu_window/emu_window_sdl2_gl.h | ||||||
|     emu_window/emu_window_sdl2_sw.cpp |     emu_window/emu_window_sdl2_sw.cpp | ||||||
|     emu_window/emu_window_sdl2_sw.h |     emu_window/emu_window_sdl2_sw.h | ||||||
|     lodepng_image_interface.cpp |  | ||||||
|     lodepng_image_interface.h |  | ||||||
|     precompiled_headers.h |     precompiled_headers.h | ||||||
|     resource.h |     resource.h | ||||||
| ) | ) | ||||||
|  | @ -21,7 +19,7 @@ add_executable(citra | ||||||
| create_target_directory_groups(citra) | create_target_directory_groups(citra) | ||||||
| 
 | 
 | ||||||
| target_link_libraries(citra PRIVATE common core input_common network) | target_link_libraries(citra PRIVATE common core input_common network) | ||||||
| target_link_libraries(citra PRIVATE inih glad lodepng) | target_link_libraries(citra PRIVATE inih glad) | ||||||
| if (MSVC) | if (MSVC) | ||||||
|     target_link_libraries(citra PRIVATE getopt) |     target_link_libraries(citra PRIVATE getopt) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
|  | @ -15,7 +15,6 @@ | ||||||
| #include "citra/emu_window/emu_window_sdl2.h" | #include "citra/emu_window/emu_window_sdl2.h" | ||||||
| #include "citra/emu_window/emu_window_sdl2_gl.h" | #include "citra/emu_window/emu_window_sdl2_gl.h" | ||||||
| #include "citra/emu_window/emu_window_sdl2_sw.h" | #include "citra/emu_window/emu_window_sdl2_sw.h" | ||||||
| #include "citra/lodepng_image_interface.h" |  | ||||||
| #include "common/common_paths.h" | #include "common/common_paths.h" | ||||||
| #include "common/detached_tasks.h" | #include "common/detached_tasks.h" | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
|  | @ -359,9 +358,6 @@ int main(int argc, char** argv) { | ||||||
|     // Register frontend applets
 |     // Register frontend applets
 | ||||||
|     Frontend::RegisterDefaultApplets(); |     Frontend::RegisterDefaultApplets(); | ||||||
| 
 | 
 | ||||||
|     // Register generic image interface
 |  | ||||||
|     Core::System::GetInstance().RegisterImageInterface(std::make_shared<LodePNGImageInterface>()); |  | ||||||
| 
 |  | ||||||
|     EmuWindow_SDL2::InitializeSDL2(); |     EmuWindow_SDL2::InitializeSDL2(); | ||||||
| 
 | 
 | ||||||
|     const auto create_emu_window = [](bool fullscreen, |     const auto create_emu_window = [](bool fullscreen, | ||||||
|  |  | ||||||
|  | @ -175,6 +175,7 @@ void Config::ReadValues() { | ||||||
|     ReadSetting("Utility", Settings::values.dump_textures); |     ReadSetting("Utility", Settings::values.dump_textures); | ||||||
|     ReadSetting("Utility", Settings::values.custom_textures); |     ReadSetting("Utility", Settings::values.custom_textures); | ||||||
|     ReadSetting("Utility", Settings::values.preload_textures); |     ReadSetting("Utility", Settings::values.preload_textures); | ||||||
|  |     ReadSetting("Utility", Settings::values.async_custom_loading); | ||||||
| 
 | 
 | ||||||
|     // Audio
 |     // Audio
 | ||||||
|     ReadSetting("Audio", Settings::values.audio_emulation); |     ReadSetting("Audio", Settings::values.audio_emulation); | ||||||
|  |  | ||||||
|  | @ -232,6 +232,10 @@ custom_textures = | ||||||
| # 0 (default): Off, 1: On | # 0 (default): Off, 1: On | ||||||
| preload_textures = | preload_textures = | ||||||
| 
 | 
 | ||||||
|  | # Loads custom textures asynchronously with background threads. | ||||||
|  | # 0: Off, 1 (default): On | ||||||
|  | async_custom_loading = | ||||||
|  | 
 | ||||||
| [Audio] | [Audio] | ||||||
| # Whether or not to enable DSP LLE | # Whether or not to enable DSP LLE | ||||||
| # 0 (default): No, 1: Yes | # 0 (default): No, 1: Yes | ||||||
|  |  | ||||||
|  | @ -1,29 +0,0 @@ | ||||||
| // Copyright 2019 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <lodepng.h> |  | ||||||
| #include "citra/lodepng_image_interface.h" |  | ||||||
| #include "common/logging/log.h" |  | ||||||
| 
 |  | ||||||
| bool LodePNGImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height, |  | ||||||
|                                       const std::string& path) { |  | ||||||
|     u32 lodepng_ret = lodepng::decode(dst, width, height, path); |  | ||||||
|     if (lodepng_ret) { |  | ||||||
|         LOG_CRITICAL(Frontend, "Failed to decode {} because {}", path, |  | ||||||
|                      lodepng_error_text(lodepng_ret)); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool LodePNGImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src, |  | ||||||
|                                       u32 width, u32 height) { |  | ||||||
|     u32 lodepng_ret = lodepng::encode(path, src, width, height); |  | ||||||
|     if (lodepng_ret) { |  | ||||||
|         LOG_CRITICAL(Frontend, "Failed to encode {} because {}", path, |  | ||||||
|                      lodepng_error_text(lodepng_ret)); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| // Copyright 2019 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include "core/frontend/image_interface.h" |  | ||||||
| 
 |  | ||||||
| class LodePNGImageInterface final : public Frontend::ImageInterface { |  | ||||||
| public: |  | ||||||
|     bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override; |  | ||||||
|     bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width, |  | ||||||
|                    u32 height) override; |  | ||||||
| }; |  | ||||||
|  | @ -56,7 +56,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config: | ||||||
| // This must be in alphabetical order according to action name as it must have the same order as
 | // This must be in alphabetical order according to action name as it must have the same order as
 | ||||||
| // UISetting::values.shortcuts, which is alphabetically ordered.
 | // UISetting::values.shortcuts, which is alphabetically ordered.
 | ||||||
| // clang-format off
 | // clang-format off
 | ||||||
| const std::array<UISettings::Shortcut, 27> Config::default_hotkeys {{ | const std::array<UISettings::Shortcut, 28> Config::default_hotkeys {{ | ||||||
|      {QStringLiteral("Advance Frame"),            QStringLiteral("Main Window"), {QStringLiteral(""),     Qt::ApplicationShortcut}}, |      {QStringLiteral("Advance Frame"),            QStringLiteral("Main Window"), {QStringLiteral(""),     Qt::ApplicationShortcut}}, | ||||||
|      {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, |      {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, | ||||||
|      {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"),     Qt::WindowShortcut}}, |      {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"),     Qt::WindowShortcut}}, | ||||||
|  | @ -84,6 +84,7 @@ const std::array<UISettings::Shortcut, 27> Config::default_hotkeys {{ | ||||||
|      {QStringLiteral("Toggle Screen Layout"),     QStringLiteral("Main Window"), {QStringLiteral("F10"),    Qt::WindowShortcut}}, |      {QStringLiteral("Toggle Screen Layout"),     QStringLiteral("Main Window"), {QStringLiteral("F10"),    Qt::WindowShortcut}}, | ||||||
|      {QStringLiteral("Toggle Status Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, |      {QStringLiteral("Toggle Status Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, | ||||||
|      {QStringLiteral("Toggle Texture Dumping"),   QStringLiteral("Main Window"), {QStringLiteral(""),       Qt::ApplicationShortcut}}, |      {QStringLiteral("Toggle Texture Dumping"),   QStringLiteral("Main Window"), {QStringLiteral(""),       Qt::ApplicationShortcut}}, | ||||||
|  |      {QStringLiteral("Toggle Custom Textures"),   QStringLiteral("Main Window"), {QStringLiteral("F7"),     Qt::ApplicationShortcut}}, | ||||||
|     }}; |     }}; | ||||||
| // clang-format on
 | // clang-format on
 | ||||||
| 
 | 
 | ||||||
|  | @ -439,6 +440,7 @@ void Config::ReadUtilityValues() { | ||||||
|     ReadGlobalSetting(Settings::values.dump_textures); |     ReadGlobalSetting(Settings::values.dump_textures); | ||||||
|     ReadGlobalSetting(Settings::values.custom_textures); |     ReadGlobalSetting(Settings::values.custom_textures); | ||||||
|     ReadGlobalSetting(Settings::values.preload_textures); |     ReadGlobalSetting(Settings::values.preload_textures); | ||||||
|  |     ReadGlobalSetting(Settings::values.async_custom_loading); | ||||||
| 
 | 
 | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
| } | } | ||||||
|  | @ -949,6 +951,7 @@ void Config::SaveUtilityValues() { | ||||||
|     WriteGlobalSetting(Settings::values.dump_textures); |     WriteGlobalSetting(Settings::values.dump_textures); | ||||||
|     WriteGlobalSetting(Settings::values.custom_textures); |     WriteGlobalSetting(Settings::values.custom_textures); | ||||||
|     WriteGlobalSetting(Settings::values.preload_textures); |     WriteGlobalSetting(Settings::values.preload_textures); | ||||||
|  |     WriteGlobalSetting(Settings::values.async_custom_loading); | ||||||
| 
 | 
 | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ public: | ||||||
| 
 | 
 | ||||||
|     static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; |     static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; | ||||||
|     static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; |     static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; | ||||||
|     static const std::array<UISettings::Shortcut, 27> default_hotkeys; |     static const std::array<UISettings::Shortcut, 28> default_hotkeys; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     void Initialize(const std::string& config_name); |     void Initialize(const std::string& config_name); | ||||||
|  |  | ||||||
|  | @ -41,8 +41,10 @@ ConfigureEnhancements::ConfigureEnhancements(QWidget* parent) | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     ui->toggle_preload_textures->setEnabled(ui->toggle_custom_textures->isChecked()); |     ui->toggle_preload_textures->setEnabled(ui->toggle_custom_textures->isChecked()); | ||||||
|  |     ui->toggle_async_custom_loading->setEnabled(ui->toggle_custom_textures->isChecked()); | ||||||
|     connect(ui->toggle_custom_textures, &QCheckBox::toggled, this, [this] { |     connect(ui->toggle_custom_textures, &QCheckBox::toggled, this, [this] { | ||||||
|         ui->toggle_preload_textures->setEnabled(ui->toggle_custom_textures->isChecked()); |         ui->toggle_preload_textures->setEnabled(ui->toggle_custom_textures->isChecked()); | ||||||
|  |         ui->toggle_async_custom_loading->setEnabled(ui->toggle_custom_textures->isChecked()); | ||||||
|         if (!ui->toggle_preload_textures->isEnabled()) |         if (!ui->toggle_preload_textures->isEnabled()) | ||||||
|             ui->toggle_preload_textures->setChecked(false); |             ui->toggle_preload_textures->setChecked(false); | ||||||
|     }); |     }); | ||||||
|  | @ -83,6 +85,7 @@ void ConfigureEnhancements::SetConfiguration() { | ||||||
|     ui->toggle_dump_textures->setChecked(Settings::values.dump_textures.GetValue()); |     ui->toggle_dump_textures->setChecked(Settings::values.dump_textures.GetValue()); | ||||||
|     ui->toggle_custom_textures->setChecked(Settings::values.custom_textures.GetValue()); |     ui->toggle_custom_textures->setChecked(Settings::values.custom_textures.GetValue()); | ||||||
|     ui->toggle_preload_textures->setChecked(Settings::values.preload_textures.GetValue()); |     ui->toggle_preload_textures->setChecked(Settings::values.preload_textures.GetValue()); | ||||||
|  |     ui->toggle_async_custom_loading->setChecked(Settings::values.async_custom_loading.GetValue()); | ||||||
|     bg_color = |     bg_color = | ||||||
|         QColor::fromRgbF(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(), |         QColor::fromRgbF(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(), | ||||||
|                          Settings::values.bg_blue.GetValue()); |                          Settings::values.bg_blue.GetValue()); | ||||||
|  | @ -159,6 +162,8 @@ void ConfigureEnhancements::ApplyConfiguration() { | ||||||
|                                              ui->toggle_custom_textures, custom_textures); |                                              ui->toggle_custom_textures, custom_textures); | ||||||
|     ConfigurationShared::ApplyPerGameSetting(&Settings::values.preload_textures, |     ConfigurationShared::ApplyPerGameSetting(&Settings::values.preload_textures, | ||||||
|                                              ui->toggle_preload_textures, preload_textures); |                                              ui->toggle_preload_textures, preload_textures); | ||||||
|  |     ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_custom_loading, | ||||||
|  |                                              ui->toggle_async_custom_loading, async_custom_loading); | ||||||
| 
 | 
 | ||||||
|     Settings::values.bg_red = static_cast<float>(bg_color.redF()); |     Settings::values.bg_red = static_cast<float>(bg_color.redF()); | ||||||
|     Settings::values.bg_green = static_cast<float>(bg_color.greenF()); |     Settings::values.bg_green = static_cast<float>(bg_color.greenF()); | ||||||
|  | @ -176,6 +181,8 @@ void ConfigureEnhancements::SetupPerGameUI() { | ||||||
|         ui->toggle_dump_textures->setEnabled(Settings::values.dump_textures.UsingGlobal()); |         ui->toggle_dump_textures->setEnabled(Settings::values.dump_textures.UsingGlobal()); | ||||||
|         ui->toggle_custom_textures->setEnabled(Settings::values.custom_textures.UsingGlobal()); |         ui->toggle_custom_textures->setEnabled(Settings::values.custom_textures.UsingGlobal()); | ||||||
|         ui->toggle_preload_textures->setEnabled(Settings::values.preload_textures.UsingGlobal()); |         ui->toggle_preload_textures->setEnabled(Settings::values.preload_textures.UsingGlobal()); | ||||||
|  |         ui->toggle_async_custom_loading->setEnabled( | ||||||
|  |             Settings::values.async_custom_loading.UsingGlobal()); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -195,6 +202,9 @@ void ConfigureEnhancements::SetupPerGameUI() { | ||||||
|                                             Settings::values.custom_textures, custom_textures); |                                             Settings::values.custom_textures, custom_textures); | ||||||
|     ConfigurationShared::SetColoredTristate(ui->toggle_preload_textures, |     ConfigurationShared::SetColoredTristate(ui->toggle_preload_textures, | ||||||
|                                             Settings::values.preload_textures, preload_textures); |                                             Settings::values.preload_textures, preload_textures); | ||||||
|  |     ConfigurationShared::SetColoredTristate(ui->toggle_async_custom_loading, | ||||||
|  |                                             Settings::values.async_custom_loading, | ||||||
|  |                                             async_custom_loading); | ||||||
| 
 | 
 | ||||||
|     ConfigurationShared::SetColoredComboBox( |     ConfigurationShared::SetColoredComboBox( | ||||||
|         ui->resolution_factor_combobox, ui->widget_resolution, |         ui->resolution_factor_combobox, ui->widget_resolution, | ||||||
|  |  | ||||||
|  | @ -44,5 +44,6 @@ private: | ||||||
|     ConfigurationShared::CheckState dump_textures; |     ConfigurationShared::CheckState dump_textures; | ||||||
|     ConfigurationShared::CheckState custom_textures; |     ConfigurationShared::CheckState custom_textures; | ||||||
|     ConfigurationShared::CheckState preload_textures; |     ConfigurationShared::CheckState preload_textures; | ||||||
|  |     ConfigurationShared::CheckState async_custom_loading; | ||||||
|     QColor bg_color; |     QColor bg_color; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -494,6 +494,16 @@ | ||||||
|         </property> |         </property> | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </item> | ||||||
|  |       <item> | ||||||
|  |        <widget class="QCheckBox" name="toggle_async_custom_loading"> | ||||||
|  |         <property name="toolTip"> | ||||||
|  |          <string><html><head/><body><p>Load custom textures asynchronously with background threads to reduce loading stutter</p></body></html></string> | ||||||
|  |         </property> | ||||||
|  |         <property name="text"> | ||||||
|  |          <string>Async Custom Texture Loading</string> | ||||||
|  |         </property> | ||||||
|  |        </widget> | ||||||
|  |       </item> | ||||||
|      </layout> |      </layout> | ||||||
|     </widget> |     </widget> | ||||||
|    </item> |    </item> | ||||||
|  |  | ||||||
|  | @ -206,13 +206,11 @@ void OptionSetDialog::SetCheckBoxDefaults(const std::string& initial_value) { | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         // This is a combination of constants, splitted with + or |
 |         // This is a combination of constants, splitted with + or |
 | ||||||
|         std::vector<std::string> tmp; |         const auto tmp = Common::SplitString(initial_value, '+'); | ||||||
|         Common::SplitString(initial_value, '+', tmp); |  | ||||||
| 
 | 
 | ||||||
|         std::vector<std::string> out; |         std::vector<std::string> out; | ||||||
|         std::vector<std::string> tmp2; |  | ||||||
|         for (const auto& str : tmp) { |         for (const auto& str : tmp) { | ||||||
|             Common::SplitString(str, '|', tmp2); |             const auto tmp2 = Common::SplitString(str, '|'); | ||||||
|             out.insert(out.end(), tmp2.begin(), tmp2.end()); |             out.insert(out.end(), tmp2.begin(), tmp2.end()); | ||||||
|         } |         } | ||||||
|         for (int i = 0; i < ui->checkBoxLayout->count(); ++i) { |         for (int i = 0; i < ui->checkBoxLayout->count(); ++i) { | ||||||
|  |  | ||||||
|  | @ -2,13 +2,13 @@ | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include <QKeySequence> |  | ||||||
| #include <QShortcut> | #include <QShortcut> | ||||||
| #include <QtGlobal> | #include <QtGlobal> | ||||||
| #include "citra_qt/hotkeys.h" | #include "citra_qt/hotkeys.h" | ||||||
| #include "citra_qt/uisettings.h" | #include "citra_qt/uisettings.h" | ||||||
| 
 | 
 | ||||||
| HotkeyRegistry::HotkeyRegistry() = default; | HotkeyRegistry::HotkeyRegistry() = default; | ||||||
|  | 
 | ||||||
| HotkeyRegistry::~HotkeyRegistry() = default; | HotkeyRegistry::~HotkeyRegistry() = default; | ||||||
| 
 | 
 | ||||||
| void HotkeyRegistry::SaveHotkeys() { | void HotkeyRegistry::SaveHotkeys() { | ||||||
|  |  | ||||||
|  | @ -5,11 +5,13 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <map> | #include <map> | ||||||
|  | #include <QKeySequence> | ||||||
|  | #include <QString> | ||||||
| 
 | 
 | ||||||
| class QDialog; | class QDialog; | ||||||
| class QKeySequence; |  | ||||||
| class QSettings; | class QSettings; | ||||||
| class QShortcut; | class QShortcut; | ||||||
|  | class QWidget; | ||||||
| 
 | 
 | ||||||
| class HotkeyRegistry final { | class HotkeyRegistry final { | ||||||
| public: | public: | ||||||
|  |  | ||||||
|  | @ -578,6 +578,8 @@ void GMainWindow::InitializeHotkeys() { | ||||||
|     }); |     }); | ||||||
|     connect_shortcut(QStringLiteral("Toggle Texture Dumping"), |     connect_shortcut(QStringLiteral("Toggle Texture Dumping"), | ||||||
|                      [&] { Settings::values.dump_textures = !Settings::values.dump_textures; }); |                      [&] { Settings::values.dump_textures = !Settings::values.dump_textures; }); | ||||||
|  |     connect_shortcut(QStringLiteral("Toggle Custom Textures"), | ||||||
|  |                      [&] { Settings::values.custom_textures = !Settings::values.custom_textures; }); | ||||||
|     // We use "static" here in order to avoid capturing by lambda due to a MSVC bug, which makes
 |     // We use "static" here in order to avoid capturing by lambda due to a MSVC bug, which makes
 | ||||||
|     // the variable hold a garbage value after this function exits
 |     // the variable hold a garbage value after this function exits
 | ||||||
|     static constexpr u16 SPEED_LIMIT_STEP = 5; |     static constexpr u16 SPEED_LIMIT_STEP = 5; | ||||||
|  |  | ||||||
|  | @ -8,11 +8,10 @@ | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| 
 | 
 | ||||||
| bool QtImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height, | bool QtImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height, | ||||||
|                                  const std::string& path) { |                                  std::span<const u8> src) { | ||||||
|     QImage image(QString::fromStdString(path)); |     QImage image(QImage::fromData(src.data(), static_cast<int>(src.size()))); | ||||||
| 
 |  | ||||||
|     if (image.isNull()) { |     if (image.isNull()) { | ||||||
|         LOG_ERROR(Frontend, "Failed to open {} for decoding", path); |         LOG_ERROR(Frontend, "Failed to decode png because image is null"); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|     width = image.width(); |     width = image.width(); | ||||||
|  | @ -21,13 +20,15 @@ bool QtImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height, | ||||||
|     image = image.convertToFormat(QImage::Format_RGBA8888); |     image = image.convertToFormat(QImage::Format_RGBA8888); | ||||||
| 
 | 
 | ||||||
|     // Write RGBA8 to vector
 |     // Write RGBA8 to vector
 | ||||||
|     dst = std::vector<u8>(image.constBits(), image.constBits() + (width * height * 4)); |     const size_t image_size = width * height * 4; | ||||||
|  |     dst.resize(image_size); | ||||||
|  |     std::memcpy(dst.data(), image.constBits(), image_size); | ||||||
| 
 | 
 | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool QtImageInterface::EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width, | bool QtImageInterface::EncodePNG(const std::string& path, u32 width, u32 height, | ||||||
|                                  u32 height) { |                                  std::span<const u8> src) { | ||||||
|     QImage image(src.data(), width, height, QImage::Format_RGBA8888); |     QImage image(src.data(), width, height, QImage::Format_RGBA8888); | ||||||
| 
 | 
 | ||||||
|     if (!image.save(QString::fromStdString(path), "PNG")) { |     if (!image.save(QString::fromStdString(path), "PNG")) { | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
| 
 | 
 | ||||||
| class QtImageInterface final : public Frontend::ImageInterface { | class QtImageInterface final : public Frontend::ImageInterface { | ||||||
| public: | public: | ||||||
|     bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, const std::string& path) override; |     bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, std::span<const u8> src) override; | ||||||
|     bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width, |     bool EncodePNG(const std::string& path, u32 width, u32 height, | ||||||
|                    u32 height) override; |                    std::span<const u8> src) override; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -65,6 +65,8 @@ add_library(common STATIC | ||||||
|     common_precompiled_headers.h |     common_precompiled_headers.h | ||||||
|     common_types.h |     common_types.h | ||||||
|     construct.h |     construct.h | ||||||
|  |     error.cpp | ||||||
|  |     error.h | ||||||
|     file_util.cpp |     file_util.cpp | ||||||
|     file_util.h |     file_util.h | ||||||
|     hash.h |     hash.h | ||||||
|  | @ -89,6 +91,7 @@ add_library(common STATIC | ||||||
|     misc.cpp |     misc.cpp | ||||||
|     param_package.cpp |     param_package.cpp | ||||||
|     param_package.h |     param_package.h | ||||||
|  |     polyfill_thread.h | ||||||
|     precompiled_headers.h |     precompiled_headers.h | ||||||
|     quaternion.h |     quaternion.h | ||||||
|     ring_buffer.h |     ring_buffer.h | ||||||
|  | @ -113,9 +116,11 @@ add_library(common STATIC | ||||||
|     thread.cpp |     thread.cpp | ||||||
|     thread.h |     thread.h | ||||||
|     thread_queue_list.h |     thread_queue_list.h | ||||||
|  |     thread_worker.h | ||||||
|     threadsafe_queue.h |     threadsafe_queue.h | ||||||
|     timer.cpp |     timer.cpp | ||||||
|     timer.h |     timer.h | ||||||
|  |     unique_function.h | ||||||
|     vector_math.h |     vector_math.h | ||||||
|     web_result.h |     web_result.h | ||||||
|     x64/cpu_detect.cpp |     x64/cpu_detect.cpp | ||||||
|  |  | ||||||
							
								
								
									
										57
									
								
								src/common/error.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/common/error.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | ||||||
|  | // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <cstddef> | ||||||
|  | #ifdef _WIN32 | ||||||
|  | #include <windows.h> | ||||||
|  | #else | ||||||
|  | #include <cerrno> | ||||||
|  | #include <cstring> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include "common/error.h" | ||||||
|  | 
 | ||||||
|  | namespace Common { | ||||||
|  | 
 | ||||||
|  | std::string NativeErrorToString(int e) { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |     LPSTR err_str; | ||||||
|  | 
 | ||||||
|  |     DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | | ||||||
|  |                                    FORMAT_MESSAGE_IGNORE_INSERTS, | ||||||
|  |                                nullptr, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | ||||||
|  |                                reinterpret_cast<LPSTR>(&err_str), 1, nullptr); | ||||||
|  |     if (!res) { | ||||||
|  |         return "(FormatMessageA failed to format error)"; | ||||||
|  |     } | ||||||
|  |     std::string ret(err_str); | ||||||
|  |     LocalFree(err_str); | ||||||
|  |     return ret; | ||||||
|  | #else | ||||||
|  |     char err_str[255]; | ||||||
|  | #if defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) ||   \ | ||||||
|  |     defined(ANDROID) | ||||||
|  |     // Thread safe (GNU-specific)
 | ||||||
|  |     const char* str = strerror_r(e, err_str, sizeof(err_str)); | ||||||
|  |     return std::string(str); | ||||||
|  | #else | ||||||
|  |     // Thread safe (XSI-compliant)
 | ||||||
|  |     int second_err = strerror_r(e, err_str, sizeof(err_str)); | ||||||
|  |     if (second_err != 0) { | ||||||
|  |         return "(strerror_r failed to format error)"; | ||||||
|  |     } | ||||||
|  |     return std::string(err_str); | ||||||
|  | #endif // GLIBC etc.
 | ||||||
|  | #endif // _WIN32
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string GetLastErrorMsg() { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |     return NativeErrorToString(GetLastError()); | ||||||
|  | #else | ||||||
|  |     return NativeErrorToString(errno); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace Common
 | ||||||
							
								
								
									
										21
									
								
								src/common/error.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/common/error.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <string> | ||||||
|  | 
 | ||||||
|  | namespace Common { | ||||||
|  | 
 | ||||||
|  | // Generic function to get last error message.
 | ||||||
|  | // Call directly after the command or use the error num.
 | ||||||
|  | // This function might change the error code.
 | ||||||
|  | // Defined in error.cpp.
 | ||||||
|  | [[nodiscard]] std::string GetLastErrorMsg(); | ||||||
|  | 
 | ||||||
|  | // Like GetLastErrorMsg(), but passing an explicit error code.
 | ||||||
|  | // Defined in error.cpp.
 | ||||||
|  | [[nodiscard]] std::string NativeErrorToString(int e); | ||||||
|  | 
 | ||||||
|  | } // namespace Common
 | ||||||
|  | @ -960,7 +960,7 @@ std::string_view GetFilename(std::string_view path) { | ||||||
|     const auto name_index = path.find_last_of("\\/"); |     const auto name_index = path.find_last_of("\\/"); | ||||||
| 
 | 
 | ||||||
|     if (name_index == std::string_view::npos) { |     if (name_index == std::string_view::npos) { | ||||||
|         return {}; |         return path; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return path.substr(name_index + 1); |     return path.substr(name_index + 1); | ||||||
|  |  | ||||||
|  | @ -28,12 +28,9 @@ ParamPackage::ParamPackage(const std::string& serialized) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::vector<std::string> pairs; |     const auto pairs = Common::SplitString(serialized, PARAM_SEPARATOR); | ||||||
|     Common::SplitString(serialized, PARAM_SEPARATOR, pairs); |  | ||||||
| 
 |  | ||||||
|     for (const std::string& pair : pairs) { |     for (const std::string& pair : pairs) { | ||||||
|         std::vector<std::string> key_value; |         auto key_value = Common::SplitString(pair, KEY_VALUE_SEPARATOR); | ||||||
|         Common::SplitString(pair, KEY_VALUE_SEPARATOR, key_value); |  | ||||||
|         if (key_value.size() != 2) { |         if (key_value.size() != 2) { | ||||||
|             LOG_ERROR(Common, "invalid key pair {}", pair); |             LOG_ERROR(Common, "invalid key pair {}", pair); | ||||||
|             continue; |             continue; | ||||||
|  |  | ||||||
							
								
								
									
										338
									
								
								src/common/polyfill_thread.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										338
									
								
								src/common/polyfill_thread.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,338 @@ | ||||||
|  | // Copyright 2022 yuzu Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // TODO: remove this file when jthread is supported by all compilation targets
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <version> | ||||||
|  | 
 | ||||||
|  | #ifdef __cpp_lib_jthread | ||||||
|  | 
 | ||||||
|  | #include <stop_token> | ||||||
|  | #include <thread> | ||||||
|  | 
 | ||||||
|  | namespace Common { | ||||||
|  | 
 | ||||||
|  | template <typename Condvar, typename Lock, typename Pred> | ||||||
|  | void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) { | ||||||
|  |     cv.wait(lock, token, std::move(pred)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace Common
 | ||||||
|  | 
 | ||||||
|  | #else | ||||||
|  | 
 | ||||||
|  | #include <atomic> | ||||||
|  | #include <functional> | ||||||
|  | #include <map> | ||||||
|  | #include <memory> | ||||||
|  | #include <mutex> | ||||||
|  | #include <optional> | ||||||
|  | #include <thread> | ||||||
|  | #include <type_traits> | ||||||
|  | #include <utility> | ||||||
|  | 
 | ||||||
|  | namespace std { | ||||||
|  | namespace polyfill { | ||||||
|  | 
 | ||||||
|  | using stop_state_callback = size_t; | ||||||
|  | 
 | ||||||
|  | class stop_state { | ||||||
|  | public: | ||||||
|  |     stop_state() = default; | ||||||
|  |     ~stop_state() = default; | ||||||
|  | 
 | ||||||
|  |     bool request_stop() { | ||||||
|  |         unique_lock lk{m_lock}; | ||||||
|  | 
 | ||||||
|  |         if (m_stop_requested) { | ||||||
|  |             // Already set, nothing to do.
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Mark stop requested.
 | ||||||
|  |         m_stop_requested = true; | ||||||
|  | 
 | ||||||
|  |         while (!m_callbacks.empty()) { | ||||||
|  |             // Get an iterator to the first element.
 | ||||||
|  |             const auto it = m_callbacks.begin(); | ||||||
|  | 
 | ||||||
|  |             // Move the callback function out of the map.
 | ||||||
|  |             function<void()> f; | ||||||
|  |             swap(it->second, f); | ||||||
|  | 
 | ||||||
|  |             // Erase the now-empty map element.
 | ||||||
|  |             m_callbacks.erase(it); | ||||||
|  | 
 | ||||||
|  |             // Run the callback.
 | ||||||
|  |             if (f) { | ||||||
|  |                 f(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool stop_requested() const { | ||||||
|  |         unique_lock lk{m_lock}; | ||||||
|  |         return m_stop_requested; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     stop_state_callback insert_callback(function<void()> f) { | ||||||
|  |         unique_lock lk{m_lock}; | ||||||
|  | 
 | ||||||
|  |         if (m_stop_requested) { | ||||||
|  |             // Stop already requested. Don't insert anything,
 | ||||||
|  |             // just run the callback synchronously.
 | ||||||
|  |             if (f) { | ||||||
|  |                 f(); | ||||||
|  |             } | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Insert the callback.
 | ||||||
|  |         stop_state_callback ret = ++m_next_callback; | ||||||
|  |         m_callbacks.emplace(ret, move(f)); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void remove_callback(stop_state_callback cb) { | ||||||
|  |         unique_lock lk{m_lock}; | ||||||
|  |         m_callbacks.erase(cb); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     mutable recursive_mutex m_lock; | ||||||
|  |     map<stop_state_callback, function<void()>> m_callbacks; | ||||||
|  |     stop_state_callback m_next_callback{0}; | ||||||
|  |     bool m_stop_requested{false}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace polyfill
 | ||||||
|  | 
 | ||||||
|  | #ifndef __cpp_lib_concepts | ||||||
|  | template <class T, class... Args> | ||||||
|  | concept constructible_from = is_nothrow_destructible_v<T> && is_constructible_v<T, Args...>; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | class stop_token; | ||||||
|  | class stop_source; | ||||||
|  | struct nostopstate_t { | ||||||
|  |     explicit nostopstate_t() = default; | ||||||
|  | }; | ||||||
|  | inline constexpr nostopstate_t nostopstate{}; | ||||||
|  | 
 | ||||||
|  | template <class Callback> | ||||||
|  | class stop_callback; | ||||||
|  | 
 | ||||||
|  | class stop_token { | ||||||
|  | public: | ||||||
|  |     stop_token() noexcept = default; | ||||||
|  | 
 | ||||||
|  |     stop_token(const stop_token&) noexcept = default; | ||||||
|  |     stop_token(stop_token&&) noexcept = default; | ||||||
|  |     stop_token& operator=(const stop_token&) noexcept = default; | ||||||
|  |     stop_token& operator=(stop_token&&) noexcept = default; | ||||||
|  |     ~stop_token() = default; | ||||||
|  | 
 | ||||||
|  |     void swap(stop_token& other) noexcept { | ||||||
|  |         m_stop_state.swap(other.m_stop_state); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [[nodiscard]] bool stop_requested() const noexcept { | ||||||
|  |         return m_stop_state && m_stop_state->stop_requested(); | ||||||
|  |     } | ||||||
|  |     [[nodiscard]] bool stop_possible() const noexcept { | ||||||
|  |         return m_stop_state != nullptr; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     friend class stop_source; | ||||||
|  |     template <typename Callback> | ||||||
|  |     friend class stop_callback; | ||||||
|  |     stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {} | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     shared_ptr<polyfill::stop_state> m_stop_state; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class stop_source { | ||||||
|  | public: | ||||||
|  |     stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {} | ||||||
|  |     explicit stop_source(nostopstate_t) noexcept {} | ||||||
|  | 
 | ||||||
|  |     stop_source(const stop_source&) noexcept = default; | ||||||
|  |     stop_source(stop_source&&) noexcept = default; | ||||||
|  |     stop_source& operator=(const stop_source&) noexcept = default; | ||||||
|  |     stop_source& operator=(stop_source&&) noexcept = default; | ||||||
|  |     ~stop_source() = default; | ||||||
|  |     void swap(stop_source& other) noexcept { | ||||||
|  |         m_stop_state.swap(other.m_stop_state); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [[nodiscard]] stop_token get_token() const noexcept { | ||||||
|  |         return stop_token(m_stop_state); | ||||||
|  |     } | ||||||
|  |     [[nodiscard]] bool stop_possible() const noexcept { | ||||||
|  |         return m_stop_state != nullptr; | ||||||
|  |     } | ||||||
|  |     [[nodiscard]] bool stop_requested() const noexcept { | ||||||
|  |         return m_stop_state && m_stop_state->stop_requested(); | ||||||
|  |     } | ||||||
|  |     bool request_stop() noexcept { | ||||||
|  |         return m_stop_state && m_stop_state->request_stop(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     friend class jthread; | ||||||
|  |     explicit stop_source(shared_ptr<polyfill::stop_state> stop_state) | ||||||
|  |         : m_stop_state(move(stop_state)) {} | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     shared_ptr<polyfill::stop_state> m_stop_state; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template <typename Callback> | ||||||
|  | class stop_callback { | ||||||
|  |     static_assert(is_nothrow_destructible_v<Callback>); | ||||||
|  |     static_assert(is_invocable_v<Callback>); | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |     using callback_type = Callback; | ||||||
|  | 
 | ||||||
|  |     template <typename C> | ||||||
|  |     requires constructible_from<Callback, C> | ||||||
|  |     explicit stop_callback(const stop_token& st, | ||||||
|  |                            C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) | ||||||
|  |         : m_stop_state(st.m_stop_state) { | ||||||
|  |         if (m_stop_state) { | ||||||
|  |             m_callback = m_stop_state->insert_callback(move(cb)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     template <typename C> | ||||||
|  |     requires constructible_from<Callback, C> | ||||||
|  |     explicit stop_callback(stop_token&& st, | ||||||
|  |                            C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) | ||||||
|  |         : m_stop_state(move(st.m_stop_state)) { | ||||||
|  |         if (m_stop_state) { | ||||||
|  |             m_callback = m_stop_state->insert_callback(move(cb)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     ~stop_callback() { | ||||||
|  |         if (m_stop_state && m_callback) { | ||||||
|  |             m_stop_state->remove_callback(m_callback); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     stop_callback(const stop_callback&) = delete; | ||||||
|  |     stop_callback(stop_callback&&) = delete; | ||||||
|  |     stop_callback& operator=(const stop_callback&) = delete; | ||||||
|  |     stop_callback& operator=(stop_callback&&) = delete; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     shared_ptr<polyfill::stop_state> m_stop_state; | ||||||
|  |     polyfill::stop_state_callback m_callback; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template <typename Callback> | ||||||
|  | stop_callback(stop_token, Callback) -> stop_callback<Callback>; | ||||||
|  | 
 | ||||||
|  | class jthread { | ||||||
|  | public: | ||||||
|  |     using id = thread::id; | ||||||
|  |     using native_handle_type = thread::native_handle_type; | ||||||
|  | 
 | ||||||
|  |     jthread() noexcept = default; | ||||||
|  | 
 | ||||||
|  |     template <typename F, typename... Args, | ||||||
|  |               typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>> | ||||||
|  |     explicit jthread(F&& f, Args&&... args) | ||||||
|  |         : m_stop_state(make_shared<polyfill::stop_state>()), | ||||||
|  |           m_thread(make_thread(move(f), move(args)...)) {} | ||||||
|  | 
 | ||||||
|  |     ~jthread() { | ||||||
|  |         if (joinable()) { | ||||||
|  |             request_stop(); | ||||||
|  |             join(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     jthread(const jthread&) = delete; | ||||||
|  |     jthread(jthread&&) noexcept = default; | ||||||
|  |     jthread& operator=(const jthread&) = delete; | ||||||
|  | 
 | ||||||
|  |     jthread& operator=(jthread&& other) noexcept { | ||||||
|  |         m_thread.swap(other.m_thread); | ||||||
|  |         m_stop_state.swap(other.m_stop_state); | ||||||
|  |         return *this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void swap(jthread& other) noexcept { | ||||||
|  |         m_thread.swap(other.m_thread); | ||||||
|  |         m_stop_state.swap(other.m_stop_state); | ||||||
|  |     } | ||||||
|  |     [[nodiscard]] bool joinable() const noexcept { | ||||||
|  |         return m_thread.joinable(); | ||||||
|  |     } | ||||||
|  |     void join() { | ||||||
|  |         m_thread.join(); | ||||||
|  |     } | ||||||
|  |     void detach() { | ||||||
|  |         m_thread.detach(); | ||||||
|  |         m_stop_state.reset(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [[nodiscard]] id get_id() const noexcept { | ||||||
|  |         return m_thread.get_id(); | ||||||
|  |     } | ||||||
|  |     [[nodiscard]] native_handle_type native_handle() { | ||||||
|  |         return m_thread.native_handle(); | ||||||
|  |     } | ||||||
|  |     [[nodiscard]] stop_source get_stop_source() noexcept { | ||||||
|  |         return stop_source(m_stop_state); | ||||||
|  |     } | ||||||
|  |     [[nodiscard]] stop_token get_stop_token() const noexcept { | ||||||
|  |         return stop_source(m_stop_state).get_token(); | ||||||
|  |     } | ||||||
|  |     bool request_stop() noexcept { | ||||||
|  |         return get_stop_source().request_stop(); | ||||||
|  |     } | ||||||
|  |     [[nodiscard]] static unsigned int hardware_concurrency() noexcept { | ||||||
|  |         return thread::hardware_concurrency(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     template <typename F, typename... Args> | ||||||
|  |     thread make_thread(F&& f, Args&&... args) { | ||||||
|  |         if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) { | ||||||
|  |             return thread(move(f), get_stop_token(), move(args)...); | ||||||
|  |         } else { | ||||||
|  |             return thread(move(f), move(args)...); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     shared_ptr<polyfill::stop_state> m_stop_state; | ||||||
|  |     thread m_thread; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace std
 | ||||||
|  | 
 | ||||||
|  | namespace Common { | ||||||
|  | 
 | ||||||
|  | template <typename Condvar, typename Lock, typename Pred> | ||||||
|  | void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) { | ||||||
|  |     if (token.stop_requested()) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::stop_callback callback(token, [&] { cv.notify_all(); }); | ||||||
|  |     cv.wait(lock, [&] { return pred() || token.stop_requested(); }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace Common
 | ||||||
|  | 
 | ||||||
|  | #endif // __cpp_lib_jthread
 | ||||||
|  | @ -477,6 +477,7 @@ struct Values { | ||||||
|     SwitchableSetting<bool> dump_textures{false, "dump_textures"}; |     SwitchableSetting<bool> dump_textures{false, "dump_textures"}; | ||||||
|     SwitchableSetting<bool> custom_textures{false, "custom_textures"}; |     SwitchableSetting<bool> custom_textures{false, "custom_textures"}; | ||||||
|     SwitchableSetting<bool> preload_textures{false, "preload_textures"}; |     SwitchableSetting<bool> preload_textures{false, "preload_textures"}; | ||||||
|  |     SwitchableSetting<bool> async_custom_loading{true, "async_custom_loading"}; | ||||||
| 
 | 
 | ||||||
|     // Audio
 |     // Audio
 | ||||||
|     bool audio_muted; |     bool audio_muted; | ||||||
|  |  | ||||||
|  | @ -102,15 +102,16 @@ void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _P | ||||||
|     _CompleteFilename += _Filename; |     _CompleteFilename += _Filename; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SplitString(const std::string& str, const char delim, std::vector<std::string>& output) { | std::vector<std::string> SplitString(const std::string& str, const char delim) { | ||||||
|     std::istringstream iss(str); |     std::istringstream iss(str); | ||||||
|     output.resize(1); |     std::vector<std::string> output(1); | ||||||
| 
 | 
 | ||||||
|     while (std::getline(iss, *output.rbegin(), delim)) { |     while (std::getline(iss, *output.rbegin(), delim)) { | ||||||
|         output.emplace_back(); |         output.emplace_back(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     output.pop_back(); |     output.pop_back(); | ||||||
|  |     return output; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::string TabsToSpaces(int tab_size, std::string in) { | std::string TabsToSpaces(int tab_size, std::string in) { | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ namespace Common { | ||||||
| 
 | 
 | ||||||
| [[nodiscard]] bool EndsWith(const std::string& value, const std::string& ending); | [[nodiscard]] bool EndsWith(const std::string& value, const std::string& ending); | ||||||
| 
 | 
 | ||||||
| void SplitString(const std::string& str, char delim, std::vector<std::string>& output); | [[nodiscard]] std::vector<std::string> SplitString(const std::string& str, const char delim); | ||||||
| 
 | 
 | ||||||
| // "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"
 | // "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"
 | ||||||
| bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, | bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, | ||||||
|  |  | ||||||
|  | @ -3,12 +3,12 @@ | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <vector> |  | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
| #include "common/common_types.h" | #include "common/texture.h" | ||||||
| 
 | 
 | ||||||
| namespace Common { | namespace Common { | ||||||
| void FlipRGBA8Texture(std::vector<u8>& tex, u32 width, u32 height) { | 
 | ||||||
|  | void FlipRGBA8Texture(std::span<u8> tex, u32 width, u32 height) { | ||||||
|     ASSERT(tex.size() == width * height * 4); |     ASSERT(tex.size() == width * height * 4); | ||||||
|     const u32 line_size = width * 4; |     const u32 line_size = width * 4; | ||||||
|     for (u32 line = 0; line < height / 2; line++) { |     for (u32 line = 0; line < height / 2; line++) { | ||||||
|  | @ -19,4 +19,5 @@ void FlipRGBA8Texture(std::vector<u8>& tex, u32 width, u32 height) { | ||||||
|                          tex.begin() + offset_2); |                          tex.begin() + offset_2); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
| } // namespace Common
 | } // namespace Common
 | ||||||
|  |  | ||||||
|  | @ -4,9 +4,11 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <vector> | #include <span> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| 
 | 
 | ||||||
| namespace Common { | namespace Common { | ||||||
| void FlipRGBA8Texture(std::vector<u8>& tex, u32 width, u32 height); | 
 | ||||||
| } | void FlipRGBA8Texture(std::span<u8> tex, u32 width, u32 height); | ||||||
|  | 
 | ||||||
|  | } // namespace Common
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,9 @@ | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include "common/common_funcs.h" | #include <string> | ||||||
|  | 
 | ||||||
|  | #include "common/error.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/thread.h" | #include "common/thread.h" | ||||||
| #ifdef __APPLE__ | #ifdef __APPLE__ | ||||||
|  | @ -20,7 +22,6 @@ | ||||||
| #ifndef _WIN32 | #ifndef _WIN32 | ||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
| #endif | #endif | ||||||
| #include <string> |  | ||||||
| 
 | 
 | ||||||
| #ifdef __FreeBSD__ | #ifdef __FreeBSD__ | ||||||
| #define cpu_set_t cpuset_t | #define cpu_set_t cpuset_t | ||||||
|  | @ -28,6 +29,56 @@ | ||||||
| 
 | 
 | ||||||
| namespace Common { | namespace Common { | ||||||
| 
 | 
 | ||||||
|  | #ifdef _WIN32 | ||||||
|  | 
 | ||||||
|  | void SetCurrentThreadPriority(ThreadPriority new_priority) { | ||||||
|  |     auto handle = GetCurrentThread(); | ||||||
|  |     int windows_priority = 0; | ||||||
|  |     switch (new_priority) { | ||||||
|  |     case ThreadPriority::Low: | ||||||
|  |         windows_priority = THREAD_PRIORITY_BELOW_NORMAL; | ||||||
|  |         break; | ||||||
|  |     case ThreadPriority::Normal: | ||||||
|  |         windows_priority = THREAD_PRIORITY_NORMAL; | ||||||
|  |         break; | ||||||
|  |     case ThreadPriority::High: | ||||||
|  |         windows_priority = THREAD_PRIORITY_ABOVE_NORMAL; | ||||||
|  |         break; | ||||||
|  |     case ThreadPriority::VeryHigh: | ||||||
|  |         windows_priority = THREAD_PRIORITY_HIGHEST; | ||||||
|  |         break; | ||||||
|  |     case ThreadPriority::Critical: | ||||||
|  |         windows_priority = THREAD_PRIORITY_TIME_CRITICAL; | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         windows_priority = THREAD_PRIORITY_NORMAL; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     SetThreadPriority(handle, windows_priority); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #else | ||||||
|  | 
 | ||||||
|  | void SetCurrentThreadPriority(ThreadPriority new_priority) { | ||||||
|  |     pthread_t this_thread = pthread_self(); | ||||||
|  | 
 | ||||||
|  |     const auto scheduling_type = SCHED_OTHER; | ||||||
|  |     s32 max_prio = sched_get_priority_max(scheduling_type); | ||||||
|  |     s32 min_prio = sched_get_priority_min(scheduling_type); | ||||||
|  |     u32 level = std::max(static_cast<u32>(new_priority) + 1, 4U); | ||||||
|  | 
 | ||||||
|  |     struct sched_param params; | ||||||
|  |     if (max_prio > min_prio) { | ||||||
|  |         params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4; | ||||||
|  |     } else { | ||||||
|  |         params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pthread_setschedparam(this_thread, scheduling_type, ¶ms); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifdef _MSC_VER | #ifdef _MSC_VER | ||||||
| 
 | 
 | ||||||
| // Sets the debugger-visible name of the current thread.
 | // Sets the debugger-visible name of the current thread.
 | ||||||
|  | @ -47,7 +98,7 @@ void SetCurrentThreadName(const char* name) { | ||||||
| 
 | 
 | ||||||
|     info.dwType = 0x1000; |     info.dwType = 0x1000; | ||||||
|     info.szName = name; |     info.szName = name; | ||||||
|     info.dwThreadID = static_cast<DWORD>(-1); |     info.dwThreadID = std::numeric_limits<DWORD>::max(); | ||||||
|     info.dwFlags = 0; |     info.dwFlags = 0; | ||||||
| 
 | 
 | ||||||
|     __try { |     __try { | ||||||
|  | @ -81,6 +132,12 @@ void SetCurrentThreadName(const char* name) { | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | #if defined(_WIN32) | ||||||
|  | void SetCurrentThreadName(const char*) { | ||||||
|  |     // Do Nothing on MingW
 | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| } // namespace Common
 | } // namespace Common
 | ||||||
|  |  | ||||||
|  | @ -10,13 +10,15 @@ | ||||||
| #include <cstddef> | #include <cstddef> | ||||||
| #include <mutex> | #include <mutex> | ||||||
| #include <thread> | #include <thread> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/polyfill_thread.h" | ||||||
| 
 | 
 | ||||||
| namespace Common { | namespace Common { | ||||||
| 
 | 
 | ||||||
| class Event { | class Event { | ||||||
| public: | public: | ||||||
|     void Set() { |     void Set() { | ||||||
|         std::lock_guard lk{mutex}; |         std::scoped_lock lk{mutex}; | ||||||
|         if (!is_set) { |         if (!is_set) { | ||||||
|             is_set = true; |             is_set = true; | ||||||
|             condvar.notify_one(); |             condvar.notify_one(); | ||||||
|  | @ -54,6 +56,10 @@ public: | ||||||
|         is_set = false; |         is_set = false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     [[nodiscard]] bool IsSet() { | ||||||
|  |         return is_set; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     std::condition_variable condvar; |     std::condition_variable condvar; | ||||||
|     std::mutex mutex; |     std::mutex mutex; | ||||||
|  | @ -65,7 +71,7 @@ public: | ||||||
|     explicit Barrier(std::size_t count_) : count(count_) {} |     explicit Barrier(std::size_t count_) : count(count_) {} | ||||||
| 
 | 
 | ||||||
|     /// Blocks until all "count" threads have called Sync()
 |     /// Blocks until all "count" threads have called Sync()
 | ||||||
|     void Sync() { |     bool Sync(std::stop_token token = {}) { | ||||||
|         std::unique_lock lk{mutex}; |         std::unique_lock lk{mutex}; | ||||||
|         const std::size_t current_generation = generation; |         const std::size_t current_generation = generation; | ||||||
| 
 | 
 | ||||||
|  | @ -73,25 +79,37 @@ public: | ||||||
|             generation++; |             generation++; | ||||||
|             waiting = 0; |             waiting = 0; | ||||||
|             condvar.notify_all(); |             condvar.notify_all(); | ||||||
|  |             return true; | ||||||
|         } else { |         } else { | ||||||
|             condvar.wait(lk, |             CondvarWait(condvar, lk, token, | ||||||
|                         [this, current_generation] { return current_generation != generation; }); |                         [this, current_generation] { return current_generation != generation; }); | ||||||
|  |             return !token.stop_requested(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::size_t Generation() const { |     std::size_t Generation() { | ||||||
|         std::unique_lock lk(mutex); |         std::unique_lock lk{mutex}; | ||||||
|         return generation; |         return generation; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::condition_variable condvar; |     std::condition_variable_any condvar; | ||||||
|     mutable std::mutex mutex; |     std::mutex mutex; | ||||||
|     std::size_t count; |     std::size_t count; | ||||||
|     std::size_t waiting = 0; |     std::size_t waiting = 0; | ||||||
|     std::size_t generation = 0; // Incremented once each time the barrier is used
 |     std::size_t generation = 0; // Incremented once each time the barrier is used
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | enum class ThreadPriority : u32 { | ||||||
|  |     Low = 0, | ||||||
|  |     Normal = 1, | ||||||
|  |     High = 2, | ||||||
|  |     VeryHigh = 3, | ||||||
|  |     Critical = 4, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void SetCurrentThreadPriority(ThreadPriority new_priority); | ||||||
|  | 
 | ||||||
| void SetCurrentThreadName(const char* name); | void SetCurrentThreadName(const char* name); | ||||||
| 
 | 
 | ||||||
| } // namespace Common
 | } // namespace Common
 | ||||||
|  |  | ||||||
							
								
								
									
										123
									
								
								src/common/thread_worker.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/common/thread_worker.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,123 @@ | ||||||
|  | // Copyright 2020 yuzu Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <atomic> | ||||||
|  | #include <condition_variable> | ||||||
|  | #include <functional> | ||||||
|  | #include <mutex> | ||||||
|  | #include <string> | ||||||
|  | #include <thread> | ||||||
|  | #include <type_traits> | ||||||
|  | #include <vector> | ||||||
|  | #include <queue> | ||||||
|  | 
 | ||||||
|  | #include "common/polyfill_thread.h" | ||||||
|  | #include "common/thread.h" | ||||||
|  | #include "common/unique_function.h" | ||||||
|  | 
 | ||||||
|  | namespace Common { | ||||||
|  | 
 | ||||||
|  | template <class StateType = void> | ||||||
|  | class StatefulThreadWorker { | ||||||
|  |     static constexpr bool with_state = !std::is_same_v<StateType, void>; | ||||||
|  | 
 | ||||||
|  |     struct DummyCallable { | ||||||
|  |         int operator()(size_t) const noexcept { | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     using Task = | ||||||
|  |         std::conditional_t<with_state, UniqueFunction<void, StateType*>, UniqueFunction<void>>; | ||||||
|  |     using StateMaker = | ||||||
|  |         std::conditional_t<with_state, std::function<StateType(size_t)>, DummyCallable>; | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |     explicit StatefulThreadWorker(size_t num_workers, std::string_view name, StateMaker func = {}) | ||||||
|  |         : workers_queued{num_workers}, thread_name{name} { | ||||||
|  |         const auto lambda = [this, func](std::stop_token stop_token, size_t index) { | ||||||
|  |             Common::SetCurrentThreadName(thread_name.data()); | ||||||
|  |             { | ||||||
|  |                 [[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func(index)}; | ||||||
|  |                 while (!stop_token.stop_requested()) { | ||||||
|  |                     Task task; | ||||||
|  |                     { | ||||||
|  |                         std::unique_lock lock{queue_mutex}; | ||||||
|  |                         if (requests.empty()) { | ||||||
|  |                             wait_condition.notify_all(); | ||||||
|  |                         } | ||||||
|  |                         Common::CondvarWait(condition, lock, stop_token, | ||||||
|  |                                             [this] { return !requests.empty(); }); | ||||||
|  |                         if (stop_token.stop_requested()) { | ||||||
|  |                             break; | ||||||
|  |                         } | ||||||
|  |                         task = std::move(requests.front()); | ||||||
|  |                         requests.pop(); | ||||||
|  |                     } | ||||||
|  |                     if constexpr (with_state) { | ||||||
|  |                         task(&state); | ||||||
|  |                     } else { | ||||||
|  |                         task(); | ||||||
|  |                     } | ||||||
|  |                     ++work_done; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             ++workers_stopped; | ||||||
|  |             wait_condition.notify_all(); | ||||||
|  |         }; | ||||||
|  |         threads.reserve(num_workers); | ||||||
|  |         for (size_t i = 0; i < num_workers; ++i) { | ||||||
|  |             threads.emplace_back(lambda, i); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     StatefulThreadWorker& operator=(const StatefulThreadWorker&) = delete; | ||||||
|  |     StatefulThreadWorker(const StatefulThreadWorker&) = delete; | ||||||
|  | 
 | ||||||
|  |     StatefulThreadWorker& operator=(StatefulThreadWorker&&) = delete; | ||||||
|  |     StatefulThreadWorker(StatefulThreadWorker&&) = delete; | ||||||
|  | 
 | ||||||
|  |     void QueueWork(Task work) { | ||||||
|  |         { | ||||||
|  |             std::unique_lock lock{queue_mutex}; | ||||||
|  |             requests.emplace(std::move(work)); | ||||||
|  |             ++work_scheduled; | ||||||
|  |         } | ||||||
|  |         condition.notify_one(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void WaitForRequests(std::stop_token stop_token = {}) { | ||||||
|  |         std::stop_callback callback(stop_token, [this] { | ||||||
|  |             for (auto& thread : threads) { | ||||||
|  |                 thread.request_stop(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         std::unique_lock lock{queue_mutex}; | ||||||
|  |         wait_condition.wait(lock, [this] { | ||||||
|  |             return workers_stopped >= workers_queued || work_done >= work_scheduled; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const std::size_t NumWorkers() const noexcept { | ||||||
|  |         return threads.size(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::queue<Task> requests; | ||||||
|  |     std::mutex queue_mutex; | ||||||
|  |     std::condition_variable_any condition; | ||||||
|  |     std::condition_variable wait_condition; | ||||||
|  |     std::atomic<size_t> work_scheduled{}; | ||||||
|  |     std::atomic<size_t> work_done{}; | ||||||
|  |     std::atomic<size_t> workers_stopped{}; | ||||||
|  |     std::atomic<size_t> workers_queued{}; | ||||||
|  |     std::string_view thread_name; | ||||||
|  |     std::vector<std::jthread> threads; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | using ThreadWorker = StatefulThreadWorker<>; | ||||||
|  | 
 | ||||||
|  | } // namespace Common
 | ||||||
							
								
								
									
										62
									
								
								src/common/unique_function.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/common/unique_function.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | ||||||
|  | // Copyright 2021 yuzu Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | #include <utility> | ||||||
|  | 
 | ||||||
|  | namespace Common { | ||||||
|  | 
 | ||||||
|  | /// General purpose function wrapper similar to std::function.
 | ||||||
|  | /// Unlike std::function, the captured values don't have to be copyable.
 | ||||||
|  | /// This class can be moved but not copied.
 | ||||||
|  | template <typename ResultType, typename... Args> | ||||||
|  | class UniqueFunction { | ||||||
|  |     class CallableBase { | ||||||
|  |     public: | ||||||
|  |         virtual ~CallableBase() = default; | ||||||
|  |         virtual ResultType operator()(Args&&...) = 0; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     template <typename Functor> | ||||||
|  |     class Callable final : public CallableBase { | ||||||
|  |     public: | ||||||
|  |         Callable(Functor&& functor_) : functor{std::move(functor_)} {} | ||||||
|  |         ~Callable() override = default; | ||||||
|  | 
 | ||||||
|  |         ResultType operator()(Args&&... args) override { | ||||||
|  |             return functor(std::forward<Args>(args)...); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     private: | ||||||
|  |         Functor functor; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |     UniqueFunction() = default; | ||||||
|  | 
 | ||||||
|  |     template <typename Functor> | ||||||
|  |     UniqueFunction(Functor&& functor) | ||||||
|  |         : callable{std::make_unique<Callable<Functor>>(std::move(functor))} {} | ||||||
|  | 
 | ||||||
|  |     UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default; | ||||||
|  |     UniqueFunction(UniqueFunction&& rhs) noexcept = default; | ||||||
|  | 
 | ||||||
|  |     UniqueFunction& operator=(const UniqueFunction&) = delete; | ||||||
|  |     UniqueFunction(const UniqueFunction&) = delete; | ||||||
|  | 
 | ||||||
|  |     ResultType operator()(Args&&... args) const { | ||||||
|  |         return (*callable)(std::forward<Args>(args)...); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     explicit operator bool() const noexcept { | ||||||
|  |         return static_cast<bool>(callable); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::unique_ptr<CallableBase> callable; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace Common
 | ||||||
|  | @ -36,8 +36,6 @@ add_library(core STATIC | ||||||
|     core.h |     core.h | ||||||
|     core_timing.cpp |     core_timing.cpp | ||||||
|     core_timing.h |     core_timing.h | ||||||
|     custom_tex_cache.cpp |  | ||||||
|     custom_tex_cache.h |  | ||||||
|     dumping/backend.cpp |     dumping/backend.cpp | ||||||
|     dumping/backend.h |     dumping/backend.h | ||||||
|     file_sys/archive_backend.cpp |     file_sys/archive_backend.cpp | ||||||
|  | @ -109,6 +107,7 @@ add_library(core STATIC | ||||||
|     frontend/emu_window.h |     frontend/emu_window.h | ||||||
|     frontend/framebuffer_layout.cpp |     frontend/framebuffer_layout.cpp | ||||||
|     frontend/framebuffer_layout.h |     frontend/framebuffer_layout.h | ||||||
|  |     frontend/image_interface.cpp | ||||||
|     frontend/image_interface.h |     frontend/image_interface.h | ||||||
|     frontend/input.h |     frontend/input.h | ||||||
|     frontend/mic.cpp |     frontend/mic.cpp | ||||||
|  | @ -481,7 +480,8 @@ endif() | ||||||
| create_target_directory_groups(core) | create_target_directory_groups(core) | ||||||
| 
 | 
 | ||||||
| target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core) | target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core) | ||||||
| target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt::fmt open_source_archives Boost::serialization Boost::iostreams) | target_link_libraries(core PRIVATE Boost::boost Boost::serialization Boost::iostreams) | ||||||
|  | target_link_libraries(core PUBLIC dds-ktx PRIVATE cryptopp fmt::fmt lodepng open_source_archives) | ||||||
| set_target_properties(core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO}) | set_target_properties(core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO}) | ||||||
| 
 | 
 | ||||||
| if (ENABLE_WEB_SERVICE) | if (ENABLE_WEB_SERVICE) | ||||||
|  |  | ||||||
|  | @ -216,13 +216,12 @@ GatewayCheat::GatewayCheat(std::string name_, std::vector<CheatLine> cheat_lines | ||||||
| GatewayCheat::GatewayCheat(std::string name_, std::string code, std::string comments_) | GatewayCheat::GatewayCheat(std::string name_, std::string code, std::string comments_) | ||||||
|     : name(std::move(name_)), comments(std::move(comments_)) { |     : name(std::move(name_)), comments(std::move(comments_)) { | ||||||
| 
 | 
 | ||||||
|     std::vector<std::string> code_lines; |     const auto code_lines = Common::SplitString(code, '\n'); | ||||||
|     Common::SplitString(code, '\n', code_lines); |  | ||||||
| 
 | 
 | ||||||
|     std::vector<CheatLine> temp_cheat_lines; |     std::vector<CheatLine> temp_cheat_lines; | ||||||
|     for (std::size_t i = 0; i < code_lines.size(); ++i) { |     for (const std::string& line : code_lines) { | ||||||
|         if (!code_lines[i].empty()) |         if (!line.empty()) | ||||||
|             temp_cheat_lines.emplace_back(code_lines[i]); |             temp_cheat_lines.emplace_back(line); | ||||||
|     } |     } | ||||||
|     cheat_lines = std::move(temp_cheat_lines); |     cheat_lines = std::move(temp_cheat_lines); | ||||||
| } | } | ||||||
|  | @ -464,10 +463,10 @@ std::string GatewayCheat::ToString() const { | ||||||
|         result += EnabledText; |         result += EnabledText; | ||||||
|         result += '\n'; |         result += '\n'; | ||||||
|     } |     } | ||||||
|     std::vector<std::string> comment_lines; |     const auto comment_lines = Common::SplitString(comments, '\n'); | ||||||
|     Common::SplitString(comments, '\n', comment_lines); |     for (const auto& comment_line : comment_lines) { | ||||||
|     for (const auto& comment_line : comment_lines) |  | ||||||
|         result += "*" + comment_line + '\n'; |         result += "*" + comment_line + '\n'; | ||||||
|  |     } | ||||||
|     result += GetCode() + '\n'; |     result += GetCode() + '\n'; | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ | ||||||
| #include "core/dumping/ffmpeg_backend.h" | #include "core/dumping/ffmpeg_backend.h" | ||||||
| #endif | #endif | ||||||
| #include "common/settings.h" | #include "common/settings.h" | ||||||
| #include "core/custom_tex_cache.h" | #include "core/frontend/image_interface.h" | ||||||
| #include "core/gdbstub/gdbstub.h" | #include "core/gdbstub/gdbstub.h" | ||||||
| #include "core/global.h" | #include "core/global.h" | ||||||
| #include "core/hle/kernel/client_port.h" | #include "core/hle/kernel/client_port.h" | ||||||
|  | @ -48,6 +48,7 @@ | ||||||
| #include "core/movie.h" | #include "core/movie.h" | ||||||
| #include "core/rpc/rpc_server.h" | #include "core/rpc/rpc_server.h" | ||||||
| #include "network/network.h" | #include "network/network.h" | ||||||
|  | #include "video_core/custom_textures/custom_tex_manager.h" | ||||||
| #include "video_core/renderer_base.h" | #include "video_core/renderer_base.h" | ||||||
| #include "video_core/video_core.h" | #include "video_core/video_core.h" | ||||||
| 
 | 
 | ||||||
|  | @ -318,16 +319,15 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st | ||||||
|                   static_cast<u32>(load_result)); |                   static_cast<u32>(load_result)); | ||||||
|     } |     } | ||||||
|     perf_stats = std::make_unique<PerfStats>(title_id); |     perf_stats = std::make_unique<PerfStats>(title_id); | ||||||
|     custom_tex_cache = std::make_unique<Core::CustomTexCache>(); |  | ||||||
| 
 | 
 | ||||||
|     if (Settings::values.custom_textures) { |     if (Settings::values.custom_textures) { | ||||||
|         const u64 program_id = Kernel().GetCurrentProcess()->codeset->program_id; |         custom_tex_manager->FindCustomTextures(); | ||||||
|         FileUtil::CreateFullPath(fmt::format( |  | ||||||
|             "{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), program_id)); |  | ||||||
|         custom_tex_cache->FindCustomTextures(program_id); |  | ||||||
|     } |     } | ||||||
|     if (Settings::values.preload_textures) { |     if (Settings::values.preload_textures) { | ||||||
|         custom_tex_cache->PreloadTextures(*GetImageInterface()); |         custom_tex_manager->PreloadTextures(); | ||||||
|  |     } | ||||||
|  |     if (Settings::values.dump_textures) { | ||||||
|  |         custom_tex_manager->WriteConfig(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     status = ResultStatus::Success; |     status = ResultStatus::Success; | ||||||
|  | @ -432,6 +432,12 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, | ||||||
|     video_dumper = std::make_unique<VideoDumper::NullBackend>(); |     video_dumper = std::make_unique<VideoDumper::NullBackend>(); | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  |     if (!registered_image_interface) { | ||||||
|  |         registered_image_interface = std::make_shared<Frontend::ImageInterface>(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     custom_tex_manager = std::make_unique<VideoCore::CustomTexManager>(*this); | ||||||
|  | 
 | ||||||
|     VideoCore::Init(emu_window, secondary_window, *this); |     VideoCore::Init(emu_window, secondary_window, *this); | ||||||
| 
 | 
 | ||||||
|     LOG_DEBUG(Core, "Initialized OK"); |     LOG_DEBUG(Core, "Initialized OK"); | ||||||
|  | @ -505,12 +511,12 @@ const VideoDumper::Backend& System::VideoDumper() const { | ||||||
|     return *video_dumper; |     return *video_dumper; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Core::CustomTexCache& System::CustomTexCache() { | VideoCore::CustomTexManager& System::CustomTexManager() { | ||||||
|     return *custom_tex_cache; |     return *custom_tex_manager; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const Core::CustomTexCache& System::CustomTexCache() const { | const VideoCore::CustomTexManager& System::CustomTexManager() const { | ||||||
|     return *custom_tex_cache; |     return *custom_tex_manager; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void System::RegisterMiiSelector(std::shared_ptr<Frontend::MiiSelector> mii_selector) { | void System::RegisterMiiSelector(std::shared_ptr<Frontend::MiiSelector> mii_selector) { | ||||||
|  |  | ||||||
|  | @ -10,10 +10,8 @@ | ||||||
| #include <string> | #include <string> | ||||||
| #include <boost/serialization/version.hpp> | #include <boost/serialization/version.hpp> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/custom_tex_cache.h" |  | ||||||
| #include "core/frontend/applets/mii_selector.h" | #include "core/frontend/applets/mii_selector.h" | ||||||
| #include "core/frontend/applets/swkbd.h" | #include "core/frontend/applets/swkbd.h" | ||||||
| #include "core/frontend/image_interface.h" |  | ||||||
| #include "core/loader/loader.h" | #include "core/loader/loader.h" | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
| #include "core/perf_stats.h" | #include "core/perf_stats.h" | ||||||
|  | @ -23,7 +21,8 @@ class ARM_Interface; | ||||||
| 
 | 
 | ||||||
| namespace Frontend { | namespace Frontend { | ||||||
| class EmuWindow; | class EmuWindow; | ||||||
| } | class ImageInterface; | ||||||
|  | } // namespace Frontend
 | ||||||
| 
 | 
 | ||||||
| namespace Memory { | namespace Memory { | ||||||
| class MemorySystem; | class MemorySystem; | ||||||
|  | @ -59,8 +58,9 @@ class Backend; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| namespace VideoCore { | namespace VideoCore { | ||||||
|  | class CustomTexManager; | ||||||
| class RendererBase; | class RendererBase; | ||||||
| } | } // namespace VideoCore
 | ||||||
| 
 | 
 | ||||||
| namespace Core { | namespace Core { | ||||||
| 
 | 
 | ||||||
|  | @ -253,10 +253,10 @@ public: | ||||||
|     [[nodiscard]] const Cheats::CheatEngine& CheatEngine() const; |     [[nodiscard]] const Cheats::CheatEngine& CheatEngine() const; | ||||||
| 
 | 
 | ||||||
|     /// Gets a reference to the custom texture cache system
 |     /// Gets a reference to the custom texture cache system
 | ||||||
|     [[nodiscard]] Core::CustomTexCache& CustomTexCache(); |     [[nodiscard]] VideoCore::CustomTexManager& CustomTexManager(); | ||||||
| 
 | 
 | ||||||
|     /// Gets a const reference to the custom texture cache system
 |     /// Gets a const reference to the custom texture cache system
 | ||||||
|     [[nodiscard]] const Core::CustomTexCache& CustomTexCache() const; |     [[nodiscard]] const VideoCore::CustomTexManager& CustomTexManager() const; | ||||||
| 
 | 
 | ||||||
|     /// Gets a reference to the video dumper backend
 |     /// Gets a reference to the video dumper backend
 | ||||||
|     [[nodiscard]] VideoDumper::Backend& VideoDumper(); |     [[nodiscard]] VideoDumper::Backend& VideoDumper(); | ||||||
|  | @ -362,7 +362,7 @@ private: | ||||||
|     std::unique_ptr<VideoDumper::Backend> video_dumper; |     std::unique_ptr<VideoDumper::Backend> video_dumper; | ||||||
| 
 | 
 | ||||||
|     /// Custom texture cache system
 |     /// Custom texture cache system
 | ||||||
|     std::unique_ptr<Core::CustomTexCache> custom_tex_cache; |     std::unique_ptr<VideoCore::CustomTexManager> custom_tex_manager; | ||||||
| 
 | 
 | ||||||
|     /// Image interface
 |     /// Image interface
 | ||||||
|     std::shared_ptr<Frontend::ImageInterface> registered_image_interface; |     std::shared_ptr<Frontend::ImageInterface> registered_image_interface; | ||||||
|  |  | ||||||
|  | @ -1,109 +0,0 @@ | ||||||
| // Copyright 2019 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <fmt/format.h> |  | ||||||
| #include "common/file_util.h" |  | ||||||
| #include "common/texture.h" |  | ||||||
| #include "core.h" |  | ||||||
| #include "core/custom_tex_cache.h" |  | ||||||
| 
 |  | ||||||
| namespace Core { |  | ||||||
| CustomTexCache::CustomTexCache() = default; |  | ||||||
| 
 |  | ||||||
| CustomTexCache::~CustomTexCache() = default; |  | ||||||
| 
 |  | ||||||
| bool CustomTexCache::IsTextureDumped(u64 hash) const { |  | ||||||
|     return dumped_textures.count(hash); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void CustomTexCache::SetTextureDumped(const u64 hash) { |  | ||||||
|     dumped_textures.insert(hash); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool CustomTexCache::IsTextureCached(u64 hash) const { |  | ||||||
|     return custom_textures.count(hash); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const CustomTexInfo& CustomTexCache::LookupTexture(u64 hash) const { |  | ||||||
|     return custom_textures.at(hash); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void CustomTexCache::CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height) { |  | ||||||
|     custom_textures[hash] = {width, height, tex}; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void CustomTexCache::AddTexturePath(u64 hash, const std::string& path) { |  | ||||||
|     if (custom_texture_paths.count(hash)) |  | ||||||
|         LOG_ERROR(Core, "Textures {} and {} conflict!", custom_texture_paths[hash].path, path); |  | ||||||
|     else |  | ||||||
|         custom_texture_paths[hash] = {path, hash}; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void CustomTexCache::FindCustomTextures(u64 program_id) { |  | ||||||
|     // Custom textures are currently stored as
 |  | ||||||
|     // [TitleID]/tex1_[width]x[height]_[64-bit hash]_[format].png
 |  | ||||||
| 
 |  | ||||||
|     const std::string load_path = fmt::format( |  | ||||||
|         "{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), program_id); |  | ||||||
| 
 |  | ||||||
|     if (FileUtil::Exists(load_path)) { |  | ||||||
|         FileUtil::FSTEntry texture_dir; |  | ||||||
|         std::vector<FileUtil::FSTEntry> textures; |  | ||||||
|         // 64 nested folders should be plenty for most cases
 |  | ||||||
|         FileUtil::ScanDirectoryTree(load_path, texture_dir, 64); |  | ||||||
|         FileUtil::GetAllFilesFromNestedEntries(texture_dir, textures); |  | ||||||
| 
 |  | ||||||
|         for (const auto& file : textures) { |  | ||||||
|             if (file.isDirectory) |  | ||||||
|                 continue; |  | ||||||
|             if (file.virtualName.substr(0, 5) != "tex1_") |  | ||||||
|                 continue; |  | ||||||
| 
 |  | ||||||
|             u32 width; |  | ||||||
|             u32 height; |  | ||||||
|             u64 hash; |  | ||||||
|             u32 format; // unused
 |  | ||||||
|             // TODO: more modern way of doing this
 |  | ||||||
|             if (std::sscanf(file.virtualName.c_str(), "tex1_%ux%u_%llX_%u.png", &width, &height, |  | ||||||
|                             &hash, &format) == 4) { |  | ||||||
|                 AddTexturePath(hash, file.physicalName); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void CustomTexCache::PreloadTextures(Frontend::ImageInterface& image_interface) { |  | ||||||
|     for (const auto& path : custom_texture_paths) { |  | ||||||
|         const auto& path_info = path.second; |  | ||||||
|         Core::CustomTexInfo tex_info; |  | ||||||
|         if (image_interface.DecodePNG(tex_info.tex, tex_info.width, tex_info.height, |  | ||||||
|                                       path_info.path)) { |  | ||||||
|             // Make sure the texture size is a power of 2
 |  | ||||||
|             std::bitset<32> width_bits(tex_info.width); |  | ||||||
|             std::bitset<32> height_bits(tex_info.height); |  | ||||||
|             if (width_bits.count() == 1 && height_bits.count() == 1) { |  | ||||||
|                 LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path); |  | ||||||
|                 Common::FlipRGBA8Texture(tex_info.tex, tex_info.width, tex_info.height); |  | ||||||
|                 CacheTexture(path_info.hash, tex_info.tex, tex_info.width, tex_info.height); |  | ||||||
|             } else { |  | ||||||
|                 LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool CustomTexCache::CustomTextureExists(u64 hash) const { |  | ||||||
|     return custom_texture_paths.count(hash); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const CustomTexPathInfo& CustomTexCache::LookupTexturePathInfo(u64 hash) const { |  | ||||||
|     return custom_texture_paths.at(hash); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool CustomTexCache::IsTexturePathMapEmpty() const { |  | ||||||
|     return custom_texture_paths.size() == 0; |  | ||||||
| } |  | ||||||
| } // namespace Core
 |  | ||||||
|  | @ -1,55 +0,0 @@ | ||||||
| // Copyright 2019 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <string> |  | ||||||
| #include <unordered_map> |  | ||||||
| #include <unordered_set> |  | ||||||
| #include <vector> |  | ||||||
| #include "common/common_types.h" |  | ||||||
| 
 |  | ||||||
| namespace Frontend { |  | ||||||
| class ImageInterface; |  | ||||||
| } // namespace Frontend
 |  | ||||||
| 
 |  | ||||||
| namespace Core { |  | ||||||
| struct CustomTexInfo { |  | ||||||
|     u32 width; |  | ||||||
|     u32 height; |  | ||||||
|     std::vector<u8> tex; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // This is to avoid parsing the filename multiple times
 |  | ||||||
| struct CustomTexPathInfo { |  | ||||||
|     std::string path; |  | ||||||
|     u64 hash; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // TODO: think of a better name for this class...
 |  | ||||||
| class CustomTexCache { |  | ||||||
| public: |  | ||||||
|     explicit CustomTexCache(); |  | ||||||
|     ~CustomTexCache(); |  | ||||||
| 
 |  | ||||||
|     bool IsTextureDumped(u64 hash) const; |  | ||||||
|     void SetTextureDumped(u64 hash); |  | ||||||
| 
 |  | ||||||
|     bool IsTextureCached(u64 hash) const; |  | ||||||
|     const CustomTexInfo& LookupTexture(u64 hash) const; |  | ||||||
|     void CacheTexture(u64 hash, const std::vector<u8>& tex, u32 width, u32 height); |  | ||||||
| 
 |  | ||||||
|     void AddTexturePath(u64 hash, const std::string& path); |  | ||||||
|     void FindCustomTextures(u64 program_id); |  | ||||||
|     void PreloadTextures(Frontend::ImageInterface& image_interface); |  | ||||||
|     bool CustomTextureExists(u64 hash) const; |  | ||||||
|     const CustomTexPathInfo& LookupTexturePathInfo(u64 hash) const; |  | ||||||
|     bool IsTexturePathMapEmpty() const; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::unordered_set<u64> dumped_textures; |  | ||||||
|     std::unordered_map<u64, CustomTexInfo> custom_textures; |  | ||||||
|     std::unordered_map<u64, CustomTexPathInfo> custom_texture_paths; |  | ||||||
| }; |  | ||||||
| } // namespace Core
 |  | ||||||
|  | @ -813,8 +813,7 @@ std::vector<FormatInfo> ListFormats() { | ||||||
|     void* data = nullptr; // For libavformat to save the iteration state
 |     void* data = nullptr; // For libavformat to save the iteration state
 | ||||||
|     while ((current = av_muxer_iterate(&data))) { |     while ((current = av_muxer_iterate(&data))) { | ||||||
| #endif | #endif | ||||||
|         std::vector<std::string> extensions; |         const auto extensions = Common::SplitString(ToStdString(current->extensions), ','); | ||||||
|         Common::SplitString(ToStdString(current->extensions), ',', extensions); |  | ||||||
| 
 | 
 | ||||||
|         std::set<AVCodecID> supported_video_codecs; |         std::set<AVCodecID> supported_video_codecs; | ||||||
|         std::set<AVCodecID> supported_audio_codecs; |         std::set<AVCodecID> supported_audio_codecs; | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ PathParser::PathParser(const Path& path) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Common::SplitString(path_string, '/', path_sequence); |     path_sequence = Common::SplitString(path_string, '/'); | ||||||
| 
 | 
 | ||||||
|     auto begin = path_sequence.begin(); |     auto begin = path_sequence.begin(); | ||||||
|     auto end = path_sequence.end(); |     auto end = path_sequence.end(); | ||||||
|  |  | ||||||
							
								
								
									
										65
									
								
								src/core/frontend/image_interface.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/core/frontend/image_interface.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | // Copyright 2023 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #define DDSKTX_IMPLEMENT | ||||||
|  | #include <dds-ktx.h> | ||||||
|  | #include <lodepng.h> | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "core/frontend/image_interface.h" | ||||||
|  | 
 | ||||||
|  | namespace Frontend { | ||||||
|  | 
 | ||||||
|  | bool ImageInterface::DecodePNG(std::vector<u8>& dst, u32& width, u32& height, | ||||||
|  |                                std::span<const u8> src) { | ||||||
|  |     const u32 lodepng_ret = lodepng::decode(dst, width, height, src.data(), src.size()); | ||||||
|  |     if (lodepng_ret) { | ||||||
|  |         LOG_ERROR(Frontend, "Failed to decode because {}", lodepng_error_text(lodepng_ret)); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ImageInterface::DecodeDDS(std::vector<u8>& dst, u32& width, u32& height, ddsktx_format& format, | ||||||
|  |                                std::span<const u8> src) { | ||||||
|  |     ddsktx_texture_info tc{}; | ||||||
|  |     const int size = static_cast<int>(src.size()); | ||||||
|  | 
 | ||||||
|  |     if (!ddsktx_parse(&tc, src.data(), size, nullptr)) { | ||||||
|  |         LOG_ERROR(Frontend, "Failed to decode"); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     width = tc.width; | ||||||
|  |     height = tc.height; | ||||||
|  |     format = tc.format; | ||||||
|  | 
 | ||||||
|  |     ddsktx_sub_data sub_data{}; | ||||||
|  |     ddsktx_get_sub(&tc, &sub_data, src.data(), size, 0, 0, 0); | ||||||
|  | 
 | ||||||
|  |     dst.resize(sub_data.size_bytes); | ||||||
|  |     std::memcpy(dst.data(), sub_data.buff, sub_data.size_bytes); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ImageInterface::EncodePNG(const std::string& path, u32 width, u32 height, | ||||||
|  |                                std::span<const u8> src) { | ||||||
|  |     std::vector<u8> out; | ||||||
|  |     const u32 lodepng_ret = lodepng::encode(out, src.data(), width, height); | ||||||
|  |     if (lodepng_ret) { | ||||||
|  |         LOG_ERROR(Frontend, "Failed to encode {} because {}", path, | ||||||
|  |                   lodepng_error_text(lodepng_ret)); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     FileUtil::IOFile file{path, "wb"}; | ||||||
|  |     if (file.WriteBytes(out.data(), out.size()) != out.size()) { | ||||||
|  |         LOG_ERROR(Frontend, "Failed to save encode to path {}", path); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace Frontend
 | ||||||
|  | @ -4,21 +4,26 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <span> | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
|  | #include <dds-ktx.h> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| 
 | 
 | ||||||
| namespace Frontend { | namespace Frontend { | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Utility class that provides image decoding/encoding to the custom texture manager. | ||||||
|  |  * Can be optionally overriden by frontends to provide a custom implementation. | ||||||
|  |  */ | ||||||
| class ImageInterface { | class ImageInterface { | ||||||
| public: | public: | ||||||
|     virtual ~ImageInterface() = default; |     virtual ~ImageInterface() = default; | ||||||
| 
 | 
 | ||||||
|     // Error logging should be handled by the frontend
 |     virtual bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, std::span<const u8> src); | ||||||
|     virtual bool DecodePNG(std::vector<u8>& dst, u32& width, u32& height, |     virtual bool DecodeDDS(std::vector<u8>& dst, u32& width, u32& height, ddsktx_format& format, | ||||||
|                            const std::string& path) = 0; |                            std::span<const u8> src); | ||||||
|     virtual bool EncodePNG(const std::string& path, const std::vector<u8>& src, u32 width, |     virtual bool EncodePNG(const std::string& path, u32 width, u32 height, std::span<const u8> src); | ||||||
|                            u32 height) = 0; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace Frontend
 | } // namespace Frontend
 | ||||||
|  |  | ||||||
|  | @ -136,8 +136,7 @@ void HandleHioReply(const u8* const command_buffer, const u32 command_length) { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const std::string command_str{command_pos, command_buffer + command_length}; |     const std::string command_str{command_pos, command_buffer + command_length}; | ||||||
|     std::vector<std::string> command_parts; |     const auto command_parts = Common::SplitString(command_str, ','); | ||||||
|     Common::SplitString(command_str, ',', command_parts); |  | ||||||
| 
 | 
 | ||||||
|     if (command_parts.empty() || command_parts.size() > 3) { |     if (command_parts.empty() || command_parts.size() > 3) { | ||||||
|         LOG_WARNING(Debug_GDBStub, "Unexpected reply packet size: {}", command_parts); |         LOG_WARNING(Debug_GDBStub, "Unexpected reply packet size: {}", command_parts); | ||||||
|  |  | ||||||
|  | @ -458,8 +458,7 @@ void LoadPresetKeys() { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         std::vector<std::string> parts; |         const auto parts = Common::SplitString(line, '='); | ||||||
|         Common::SplitString(line, '=', parts); |  | ||||||
|         if (parts.size() != 2) { |         if (parts.size() != 2) { | ||||||
|             LOG_ERROR(HW_AES, "Failed to parse {}", line); |             LOG_ERROR(HW_AES, "Failed to parse {}", line); | ||||||
|             continue; |             continue; | ||||||
|  |  | ||||||
|  | @ -3,6 +3,12 @@ add_subdirectory(host_shaders) | ||||||
| add_library(video_core STATIC | add_library(video_core STATIC | ||||||
|     command_processor.cpp |     command_processor.cpp | ||||||
|     command_processor.h |     command_processor.h | ||||||
|  |     custom_textures/custom_format.cpp | ||||||
|  |     custom_textures/custom_format.h | ||||||
|  |     custom_textures/custom_tex_manager.cpp | ||||||
|  |     custom_textures/custom_tex_manager.h | ||||||
|  |     custom_textures/material.cpp | ||||||
|  |     custom_textures/material.h | ||||||
|     debug_utils/debug_utils.cpp |     debug_utils/debug_utils.cpp | ||||||
|     debug_utils/debug_utils.h |     debug_utils/debug_utils.h | ||||||
|     geometry_pipeline.cpp |     geometry_pipeline.cpp | ||||||
|  | @ -120,7 +126,7 @@ target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE}) | ||||||
| create_target_directory_groups(video_core) | create_target_directory_groups(video_core) | ||||||
| 
 | 
 | ||||||
| target_link_libraries(video_core PUBLIC common core) | target_link_libraries(video_core PUBLIC common core) | ||||||
| target_link_libraries(video_core PRIVATE glad nihstro-headers Boost::serialization) | target_link_libraries(video_core PRIVATE glad json-headers dds-ktx nihstro-headers Boost::serialization) | ||||||
| set_target_properties(video_core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO}) | set_target_properties(video_core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO}) | ||||||
| 
 | 
 | ||||||
| if ("x86_64" IN_LIST ARCHITECTURE) | if ("x86_64" IN_LIST ARCHITECTURE) | ||||||
|  |  | ||||||
							
								
								
									
										36
									
								
								src/video_core/custom_textures/custom_format.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/video_core/custom_textures/custom_format.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | // Copyright 2023 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include "video_core/custom_textures/custom_format.h" | ||||||
|  | 
 | ||||||
|  | namespace VideoCore { | ||||||
|  | 
 | ||||||
|  | std::string_view CustomPixelFormatAsString(CustomPixelFormat format) { | ||||||
|  |     switch (format) { | ||||||
|  |     case CustomPixelFormat::RGBA8: | ||||||
|  |         return "RGBA8"; | ||||||
|  |     case CustomPixelFormat::BC1: | ||||||
|  |         return "BC1"; | ||||||
|  |     case CustomPixelFormat::BC3: | ||||||
|  |         return "BC3"; | ||||||
|  |     case CustomPixelFormat::BC5: | ||||||
|  |         return "BC5"; | ||||||
|  |     case CustomPixelFormat::BC7: | ||||||
|  |         return "BC7"; | ||||||
|  |     case CustomPixelFormat::ASTC4: | ||||||
|  |         return "ASTC4"; | ||||||
|  |     case CustomPixelFormat::ASTC6: | ||||||
|  |         return "ASTC6"; | ||||||
|  |     case CustomPixelFormat::ASTC8: | ||||||
|  |         return "ASTC8"; | ||||||
|  |     default: | ||||||
|  |         return "NotReal"; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool IsCustomFormatCompressed(CustomPixelFormat format) { | ||||||
|  |     return format != CustomPixelFormat::RGBA8 && format != CustomPixelFormat::Invalid; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace VideoCore
 | ||||||
							
								
								
									
										36
									
								
								src/video_core/custom_textures/custom_format.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/video_core/custom_textures/custom_format.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | // Copyright 2023 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <limits> | ||||||
|  | #include <string_view> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | 
 | ||||||
|  | namespace VideoCore { | ||||||
|  | 
 | ||||||
|  | enum class CustomPixelFormat : u32 { | ||||||
|  |     RGBA8 = 0, | ||||||
|  |     BC1 = 1, | ||||||
|  |     BC3 = 2, | ||||||
|  |     BC5 = 3, | ||||||
|  |     BC7 = 4, | ||||||
|  |     ASTC4 = 5, | ||||||
|  |     ASTC6 = 6, | ||||||
|  |     ASTC8 = 7, | ||||||
|  |     Invalid = std::numeric_limits<u32>::max(), | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | enum class CustomFileFormat : u32 { | ||||||
|  |     None = 0, | ||||||
|  |     PNG = 1, | ||||||
|  |     DDS = 2, | ||||||
|  |     KTX = 3, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | std::string_view CustomPixelFormatAsString(CustomPixelFormat format); | ||||||
|  | 
 | ||||||
|  | bool IsCustomFormatCompressed(CustomPixelFormat format); | ||||||
|  | 
 | ||||||
|  | } // namespace VideoCore
 | ||||||
							
								
								
									
										352
									
								
								src/video_core/custom_textures/custom_tex_manager.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										352
									
								
								src/video_core/custom_textures/custom_tex_manager.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,352 @@ | ||||||
|  | // Copyright 2023 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <json.hpp> | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/memory_detect.h" | ||||||
|  | #include "common/microprofile.h" | ||||||
|  | #include "common/settings.h" | ||||||
|  | #include "common/string_util.h" | ||||||
|  | #include "common/texture.h" | ||||||
|  | #include "core/core.h" | ||||||
|  | #include "core/frontend/image_interface.h" | ||||||
|  | #include "video_core/custom_textures/custom_tex_manager.h" | ||||||
|  | #include "video_core/rasterizer_cache/surface_params.h" | ||||||
|  | 
 | ||||||
|  | namespace VideoCore { | ||||||
|  | 
 | ||||||
|  | namespace { | ||||||
|  | 
 | ||||||
|  | MICROPROFILE_DEFINE(CustomTexManager_TickFrame, "CustomTexManager", "TickFrame", | ||||||
|  |                     MP_RGB(54, 16, 32)); | ||||||
|  | 
 | ||||||
|  | constexpr std::size_t MAX_UPLOADS_PER_TICK = 16; | ||||||
|  | 
 | ||||||
|  | bool IsPow2(u32 value) { | ||||||
|  |     return value != 0 && (value & (value - 1)) == 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | CustomFileFormat MakeFileFormat(std::string_view ext) { | ||||||
|  |     if (ext == "png") { | ||||||
|  |         return CustomFileFormat::PNG; | ||||||
|  |     } else if (ext == "dds") { | ||||||
|  |         return CustomFileFormat::DDS; | ||||||
|  |     } else if (ext == "ktx") { | ||||||
|  |         return CustomFileFormat::KTX; | ||||||
|  |     } | ||||||
|  |     return CustomFileFormat::None; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | MapType MakeMapType(std::string_view ext) { | ||||||
|  |     if (ext == "norm") { | ||||||
|  |         return MapType::Normal; | ||||||
|  |     } | ||||||
|  |     LOG_ERROR(Render, "Unknown material extension {}", ext); | ||||||
|  |     return MapType::Color; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // Anonymous namespace
 | ||||||
|  | 
 | ||||||
|  | CustomTexManager::CustomTexManager(Core::System& system_) | ||||||
|  |     : system{system_}, image_interface{*system.GetImageInterface()}, | ||||||
|  |       async_custom_loading{Settings::values.async_custom_loading.GetValue()} {} | ||||||
|  | 
 | ||||||
|  | CustomTexManager::~CustomTexManager() = default; | ||||||
|  | 
 | ||||||
|  | void CustomTexManager::TickFrame() { | ||||||
|  |     MICROPROFILE_SCOPE(CustomTexManager_TickFrame); | ||||||
|  |     if (!textures_loaded) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     std::size_t num_uploads = 0; | ||||||
|  |     for (auto it = async_uploads.begin(); it != async_uploads.end();) { | ||||||
|  |         if (num_uploads >= MAX_UPLOADS_PER_TICK) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         switch (it->material->state) { | ||||||
|  |         case DecodeState::Decoded: | ||||||
|  |             it->func(); | ||||||
|  |             num_uploads++; | ||||||
|  |             [[fallthrough]]; | ||||||
|  |         case DecodeState::Failed: | ||||||
|  |             it = async_uploads.erase(it); | ||||||
|  |             continue; | ||||||
|  |         default: | ||||||
|  |             it++; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CustomTexManager::FindCustomTextures() { | ||||||
|  |     if (textures_loaded) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     if (!workers) { | ||||||
|  |         CreateWorkers(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const u64 program_id = system.Kernel().GetCurrentProcess()->codeset->program_id; | ||||||
|  |     const std::string load_path = | ||||||
|  |         fmt::format("{}textures/{:016X}/", GetUserPath(FileUtil::UserPath::LoadDir), program_id); | ||||||
|  | 
 | ||||||
|  |     if (!FileUtil::Exists(load_path)) { | ||||||
|  |         FileUtil::CreateFullPath(load_path); | ||||||
|  |     } | ||||||
|  |     ReadConfig(load_path); | ||||||
|  | 
 | ||||||
|  |     FileUtil::FSTEntry texture_dir; | ||||||
|  |     std::vector<FileUtil::FSTEntry> textures; | ||||||
|  |     FileUtil::ScanDirectoryTree(load_path, texture_dir, 64); | ||||||
|  |     FileUtil::GetAllFilesFromNestedEntries(texture_dir, textures); | ||||||
|  | 
 | ||||||
|  |     custom_textures.reserve(textures.size()); | ||||||
|  |     for (const FileUtil::FSTEntry& file : textures) { | ||||||
|  |         if (file.isDirectory) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         custom_textures.push_back(std::make_unique<CustomTexture>(image_interface)); | ||||||
|  |         CustomTexture* const texture{custom_textures.back().get()}; | ||||||
|  |         if (!ParseFilename(file, texture)) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         auto& material = material_map[texture->hash]; | ||||||
|  |         if (!material) { | ||||||
|  |             material = std::make_unique<Material>(); | ||||||
|  |         } | ||||||
|  |         material->AddMapTexture(texture); | ||||||
|  |     } | ||||||
|  |     textures_loaded = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool CustomTexManager::ParseFilename(const FileUtil::FSTEntry& file, CustomTexture* texture) { | ||||||
|  |     auto parts = Common::SplitString(file.virtualName, '.'); | ||||||
|  |     if (parts.size() > 3) { | ||||||
|  |         LOG_ERROR(Render, "Invalid filename {}, ignoring", file.virtualName); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     // The last string should always be the file extension.
 | ||||||
|  |     const CustomFileFormat file_format = MakeFileFormat(parts.back()); | ||||||
|  |     if (file_format == CustomFileFormat::None) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     if (file_format == CustomFileFormat::DDS && refuse_dds) { | ||||||
|  |         LOG_ERROR(Render, "Legacy pack is attempting to use DDS textures, skipping!"); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     texture->file_format = file_format; | ||||||
|  |     parts.pop_back(); | ||||||
|  | 
 | ||||||
|  |     // This means the texture is a material type other than color.
 | ||||||
|  |     texture->type = MapType::Color; | ||||||
|  |     if (parts.size() > 1) { | ||||||
|  |         texture->type = MakeMapType(parts.back()); | ||||||
|  |         parts.pop_back(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // First check if the path is mapped directly to a hash
 | ||||||
|  |     // before trying to parse the texture filename.
 | ||||||
|  |     const auto it = path_to_hash_map.find(file.virtualName); | ||||||
|  |     if (it != path_to_hash_map.end()) { | ||||||
|  |         texture->hash = it->second; | ||||||
|  |     } else { | ||||||
|  |         u32 width; | ||||||
|  |         u32 height; | ||||||
|  |         u32 format; | ||||||
|  |         unsigned long long hash{}; | ||||||
|  |         if (std::sscanf(parts.back().c_str(), "tex1_%ux%u_%llX_%u", &width, &height, &hash, | ||||||
|  |                         &format) != 4) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         texture->hash = hash; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     texture->path = file.physicalName; | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CustomTexManager::WriteConfig() { | ||||||
|  |     const u64 program_id = system.Kernel().GetCurrentProcess()->codeset->program_id; | ||||||
|  |     const std::string dump_path = | ||||||
|  |         fmt::format("{}textures/{:016X}/", GetUserPath(FileUtil::UserPath::DumpDir), program_id); | ||||||
|  |     const std::string pack_config = dump_path + "pack.json"; | ||||||
|  |     if (FileUtil::Exists(pack_config)) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     nlohmann::ordered_json json; | ||||||
|  |     json["author"] = "citra"; | ||||||
|  |     json["version"] = "1.0.0"; | ||||||
|  |     json["description"] = "A graphics pack"; | ||||||
|  | 
 | ||||||
|  |     auto& options = json["options"]; | ||||||
|  |     options["skip_mipmap"] = skip_mipmap; | ||||||
|  |     options["flip_png_files"] = flip_png_files; | ||||||
|  |     options["use_new_hash"] = use_new_hash; | ||||||
|  | 
 | ||||||
|  |     FileUtil::IOFile file{pack_config, "w"}; | ||||||
|  |     const std::string output = json.dump(4); | ||||||
|  |     file.WriteString(output); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CustomTexManager::PreloadTextures() { | ||||||
|  |     u64 size_sum = 0; | ||||||
|  |     const u64 sys_mem = Common::GetMemInfo().total_physical_memory; | ||||||
|  |     const u64 recommended_min_mem = 2 * size_t(1024 * 1024 * 1024); | ||||||
|  | 
 | ||||||
|  |     // keep 2GB memory for system stability if system RAM is 4GB+ - use half of memory in other
 | ||||||
|  |     // cases
 | ||||||
|  |     const u64 max_mem = | ||||||
|  |         (sys_mem / 2 < recommended_min_mem) ? (sys_mem / 2) : (sys_mem - recommended_min_mem); | ||||||
|  | 
 | ||||||
|  |     workers->QueueWork([&]() { | ||||||
|  |         for (auto& [hash, material] : material_map) { | ||||||
|  |             if (size_sum > max_mem) { | ||||||
|  |                 LOG_WARNING(Render, "Aborting texture preload due to insufficient memory"); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             material->LoadFromDisk(flip_png_files); | ||||||
|  |             size_sum += material->size; | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |     workers->WaitForRequests(); | ||||||
|  |     async_custom_loading = false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CustomTexManager::DumpTexture(const SurfaceParams& params, u32 level, std::span<u8> data, | ||||||
|  |                                    u64 data_hash) { | ||||||
|  |     const u64 program_id = system.Kernel().GetCurrentProcess()->codeset->program_id; | ||||||
|  |     const u32 data_size = static_cast<u32>(data.size()); | ||||||
|  |     const u32 width = params.width; | ||||||
|  |     const u32 height = params.height; | ||||||
|  |     const PixelFormat format = params.pixel_format; | ||||||
|  | 
 | ||||||
|  |     std::string dump_path = fmt::format( | ||||||
|  |         "{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id); | ||||||
|  |     if (!FileUtil::CreateFullPath(dump_path)) { | ||||||
|  |         LOG_ERROR(Render, "Unable to create {}", dump_path); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dump_path += | ||||||
|  |         fmt::format("tex1_{}x{}_{:016X}_{}_mip{}.png", width, height, data_hash, format, level); | ||||||
|  |     if (dumped_textures.contains(data_hash) || FileUtil::Exists(dump_path)) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Make sure the texture size is a power of 2.
 | ||||||
|  |     // If not, the surface is probably a framebuffer
 | ||||||
|  |     if (!IsPow2(width) || !IsPow2(height)) { | ||||||
|  |         LOG_WARNING(Render, "Not dumping {:016X} because size isn't a power of 2 ({}x{})", | ||||||
|  |                     data_hash, width, height); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const u32 decoded_size = width * height * 4; | ||||||
|  |     std::vector<u8> pixels(data_size + decoded_size); | ||||||
|  |     std::memcpy(pixels.data(), data.data(), data_size); | ||||||
|  | 
 | ||||||
|  |     auto dump = [this, width, height, params, data_size, decoded_size, pixels = std::move(pixels), | ||||||
|  |                  dump_path = std::move(dump_path)]() mutable { | ||||||
|  |         const std::span encoded = std::span{pixels}.first(data_size); | ||||||
|  |         const std::span decoded = std::span{pixels}.last(decoded_size); | ||||||
|  |         DecodeTexture(params, params.addr, params.end, encoded, decoded, | ||||||
|  |                       params.type == SurfaceType::Color); | ||||||
|  |         Common::FlipRGBA8Texture(decoded, width, height); | ||||||
|  |         image_interface.EncodePNG(dump_path, width, height, decoded); | ||||||
|  |     }; | ||||||
|  |     if (!workers) { | ||||||
|  |         CreateWorkers(); | ||||||
|  |     } | ||||||
|  |     workers->QueueWork(std::move(dump)); | ||||||
|  |     dumped_textures.insert(data_hash); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Material* CustomTexManager::GetMaterial(u64 data_hash) { | ||||||
|  |     const auto it = material_map.find(data_hash); | ||||||
|  |     if (it == material_map.end()) { | ||||||
|  |         LOG_WARNING(Render, "Unable to find replacement for surface with hash {:016X}", data_hash); | ||||||
|  |         return nullptr; | ||||||
|  |     } | ||||||
|  |     return it->second.get(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool CustomTexManager::Decode(Material* material, std::function<bool()>&& upload) { | ||||||
|  |     if (!async_custom_loading) { | ||||||
|  |         material->LoadFromDisk(flip_png_files); | ||||||
|  |         return upload(); | ||||||
|  |     } | ||||||
|  |     if (material->IsUnloaded()) { | ||||||
|  |         material->state = DecodeState::Pending; | ||||||
|  |         workers->QueueWork([material, this] { material->LoadFromDisk(flip_png_files); }); | ||||||
|  |     } | ||||||
|  |     async_uploads.push_back({ | ||||||
|  |         .material = material, | ||||||
|  |         .func = std::move(upload), | ||||||
|  |     }); | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CustomTexManager::ReadConfig(const std::string& load_path) { | ||||||
|  |     const std::string config_path = load_path + "pack.json"; | ||||||
|  |     FileUtil::IOFile file{config_path, "r"}; | ||||||
|  |     if (!file.IsOpen()) { | ||||||
|  |         LOG_INFO(Render, "Unable to find pack config file, using legacy defaults"); | ||||||
|  |         refuse_dds = true; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     std::string config(file.GetSize(), '\0'); | ||||||
|  |     const std::size_t read_size = file.ReadBytes(config.data(), config.size()); | ||||||
|  |     if (!read_size) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     nlohmann::json json = nlohmann::json::parse(config); | ||||||
|  | 
 | ||||||
|  |     const auto& options = json["options"]; | ||||||
|  |     skip_mipmap = options["skip_mipmap"].get<bool>(); | ||||||
|  |     flip_png_files = options["flip_png_files"].get<bool>(); | ||||||
|  |     use_new_hash = options["use_new_hash"].get<bool>(); | ||||||
|  |     refuse_dds = skip_mipmap || !use_new_hash; | ||||||
|  | 
 | ||||||
|  |     const auto& textures = json["textures"]; | ||||||
|  |     for (const auto& material : textures.items()) { | ||||||
|  |         size_t idx{}; | ||||||
|  |         const u64 hash = std::stoull(material.key(), &idx, 16); | ||||||
|  |         if (!idx) { | ||||||
|  |             LOG_ERROR(Render, "Key {} is invalid, skipping", material.key()); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         const auto parse = [&](const std::string& file) { | ||||||
|  |             const std::string filename{FileUtil::GetFilename(file)}; | ||||||
|  |             auto [it, new_hash] = path_to_hash_map.try_emplace(filename); | ||||||
|  |             if (!new_hash) { | ||||||
|  |                 LOG_ERROR(Render, | ||||||
|  |                           "File {} with key {} already exists and is mapped to {:#016X}, skipping", | ||||||
|  |                           file, material.key(), path_to_hash_map[filename]); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             it->second = hash; | ||||||
|  |         }; | ||||||
|  |         const auto value = material.value(); | ||||||
|  |         if (value.is_string()) { | ||||||
|  |             const auto file = value.get<std::string>(); | ||||||
|  |             parse(file); | ||||||
|  |         } else if (value.is_array()) { | ||||||
|  |             const auto files = value.get<std::vector<std::string>>(); | ||||||
|  |             for (const std::string& file : files) { | ||||||
|  |                 parse(file); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             LOG_ERROR(Render, "Material with key {} is invalid", material.key()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CustomTexManager::CreateWorkers() { | ||||||
|  |     const std::size_t num_workers = std::max(std::thread::hardware_concurrency(), 2U) - 1; | ||||||
|  |     workers = std::make_unique<Common::ThreadWorker>(num_workers, "Custom textures"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace VideoCore
 | ||||||
							
								
								
									
										94
									
								
								src/video_core/custom_textures/custom_tex_manager.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/video_core/custom_textures/custom_tex_manager.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | ||||||
|  | // Copyright 2023 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <list> | ||||||
|  | #include <span> | ||||||
|  | #include <unordered_map> | ||||||
|  | #include <unordered_set> | ||||||
|  | #include "common/thread_worker.h" | ||||||
|  | #include "video_core/custom_textures/material.h" | ||||||
|  | 
 | ||||||
|  | namespace Core { | ||||||
|  | class System; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | namespace FileUtil { | ||||||
|  | struct FSTEntry; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | namespace VideoCore { | ||||||
|  | 
 | ||||||
|  | class SurfaceParams; | ||||||
|  | 
 | ||||||
|  | struct AsyncUpload { | ||||||
|  |     const Material* material; | ||||||
|  |     std::function<bool()> func; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class CustomTexManager { | ||||||
|  | public: | ||||||
|  |     explicit CustomTexManager(Core::System& system); | ||||||
|  |     ~CustomTexManager(); | ||||||
|  | 
 | ||||||
|  |     /// Processes queued texture uploads
 | ||||||
|  |     void TickFrame(); | ||||||
|  | 
 | ||||||
|  |     /// Searches the load directory assigned to program_id for any custom textures and loads them
 | ||||||
|  |     void FindCustomTextures(); | ||||||
|  | 
 | ||||||
|  |     /// Saves the pack configuration file template to the dump directory if it doesn't exist.
 | ||||||
|  |     void WriteConfig(); | ||||||
|  | 
 | ||||||
|  |     /// Preloads all registered custom textures
 | ||||||
|  |     void PreloadTextures(); | ||||||
|  | 
 | ||||||
|  |     /// Saves the provided pixel data described by params to disk as png
 | ||||||
|  |     void DumpTexture(const SurfaceParams& params, u32 level, std::span<u8> data, u64 data_hash); | ||||||
|  | 
 | ||||||
|  |     /// Returns the material assigned to the provided data hash
 | ||||||
|  |     Material* GetMaterial(u64 data_hash); | ||||||
|  | 
 | ||||||
|  |     /// Decodes the textures in material to a consumable format and uploads it.
 | ||||||
|  |     bool Decode(Material* material, std::function<bool()>&& upload); | ||||||
|  | 
 | ||||||
|  |     /// True when mipmap uploads should be skipped (legacy packs only)
 | ||||||
|  |     bool SkipMipmaps() const noexcept { | ||||||
|  |         return skip_mipmap; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Returns true if the pack uses the new hashing method.
 | ||||||
|  |     bool UseNewHash() const noexcept { | ||||||
|  |         return use_new_hash; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     /// Parses the custom texture filename (hash, material type, etc).
 | ||||||
|  |     bool ParseFilename(const FileUtil::FSTEntry& file, CustomTexture* texture); | ||||||
|  | 
 | ||||||
|  |     /// Reads the pack configuration file
 | ||||||
|  |     void ReadConfig(const std::string& load_path); | ||||||
|  | 
 | ||||||
|  |     /// Creates the thread workers.
 | ||||||
|  |     void CreateWorkers(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     Core::System& system; | ||||||
|  |     Frontend::ImageInterface& image_interface; | ||||||
|  |     std::unordered_set<u64> dumped_textures; | ||||||
|  |     std::unordered_map<u64, std::unique_ptr<Material>> material_map; | ||||||
|  |     std::unordered_map<std::string, u64> path_to_hash_map; | ||||||
|  |     std::vector<std::unique_ptr<CustomTexture>> custom_textures; | ||||||
|  |     std::list<AsyncUpload> async_uploads; | ||||||
|  |     std::unique_ptr<Common::ThreadWorker> workers; | ||||||
|  |     bool textures_loaded{false}; | ||||||
|  |     bool async_custom_loading{true}; | ||||||
|  |     bool skip_mipmap{true}; | ||||||
|  |     bool flip_png_files{true}; | ||||||
|  |     bool use_new_hash{false}; | ||||||
|  |     bool refuse_dds{false}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace VideoCore
 | ||||||
							
								
								
									
										151
									
								
								src/video_core/custom_textures/material.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								src/video_core/custom_textures/material.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,151 @@ | ||||||
|  | // Copyright 2023 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "common/texture.h" | ||||||
|  | #include "core/frontend/image_interface.h" | ||||||
|  | #include "video_core/custom_textures/material.h" | ||||||
|  | 
 | ||||||
|  | namespace VideoCore { | ||||||
|  | 
 | ||||||
|  | namespace { | ||||||
|  | 
 | ||||||
|  | CustomPixelFormat ToCustomPixelFormat(ddsktx_format format) { | ||||||
|  |     switch (format) { | ||||||
|  |     case DDSKTX_FORMAT_RGBA8: | ||||||
|  |         return CustomPixelFormat::RGBA8; | ||||||
|  |     case DDSKTX_FORMAT_BC1: | ||||||
|  |         return CustomPixelFormat::BC1; | ||||||
|  |     case DDSKTX_FORMAT_BC3: | ||||||
|  |         return CustomPixelFormat::BC3; | ||||||
|  |     case DDSKTX_FORMAT_BC5: | ||||||
|  |         return CustomPixelFormat::BC5; | ||||||
|  |     case DDSKTX_FORMAT_BC7: | ||||||
|  |         return CustomPixelFormat::BC7; | ||||||
|  |     case DDSKTX_FORMAT_ASTC4x4: | ||||||
|  |         return CustomPixelFormat::ASTC4; | ||||||
|  |     case DDSKTX_FORMAT_ASTC6x6: | ||||||
|  |         return CustomPixelFormat::ASTC6; | ||||||
|  |     case DDSKTX_FORMAT_ASTC8x6: | ||||||
|  |         return CustomPixelFormat::ASTC8; | ||||||
|  |     default: | ||||||
|  |         LOG_ERROR(Common, "Unknown dds/ktx pixel format {}", format); | ||||||
|  |         return CustomPixelFormat::RGBA8; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string_view MapTypeName(MapType type) { | ||||||
|  |     switch (type) { | ||||||
|  |     case MapType::Color: | ||||||
|  |         return "Color"; | ||||||
|  |     case MapType::Normal: | ||||||
|  |         return "Normal"; | ||||||
|  |     default: | ||||||
|  |         return "Invalid"; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // Anonymous namespace
 | ||||||
|  | 
 | ||||||
|  | CustomTexture::CustomTexture(Frontend::ImageInterface& image_interface_) | ||||||
|  |     : image_interface{image_interface_} {} | ||||||
|  | 
 | ||||||
|  | CustomTexture::~CustomTexture() = default; | ||||||
|  | 
 | ||||||
|  | void CustomTexture::LoadFromDisk(bool flip_png) { | ||||||
|  |     FileUtil::IOFile file{path, "rb"}; | ||||||
|  |     std::vector<u8> input(file.GetSize()); | ||||||
|  |     if (file.ReadBytes(input.data(), input.size()) != input.size()) { | ||||||
|  |         LOG_CRITICAL(Render, "Failed to open custom texture: {}", path); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     switch (file_format) { | ||||||
|  |     case CustomFileFormat::PNG: | ||||||
|  |         LoadPNG(input, flip_png); | ||||||
|  |         break; | ||||||
|  |     case CustomFileFormat::DDS: | ||||||
|  |     case CustomFileFormat::KTX: | ||||||
|  |         LoadDDS(input); | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         LOG_ERROR(Render, "Unknown file format {}", file_format); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CustomTexture::LoadPNG(std::span<const u8> input, bool flip_png) { | ||||||
|  |     if (!image_interface.DecodePNG(data, width, height, input)) { | ||||||
|  |         LOG_ERROR(Render, "Failed to decode png: {}", path); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     if (flip_png) { | ||||||
|  |         Common::FlipRGBA8Texture(data, width, height); | ||||||
|  |     } | ||||||
|  |     format = CustomPixelFormat::RGBA8; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CustomTexture::LoadDDS(std::span<const u8> input) { | ||||||
|  |     ddsktx_format dds_format{}; | ||||||
|  |     image_interface.DecodeDDS(data, width, height, dds_format, input); | ||||||
|  |     format = ToCustomPixelFormat(dds_format); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Material::LoadFromDisk(bool flip_png) noexcept { | ||||||
|  |     if (IsDecoded()) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     for (CustomTexture* const texture : textures) { | ||||||
|  |         if (!texture || texture->IsLoaded()) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         texture->LoadFromDisk(flip_png); | ||||||
|  |         size += texture->data.size(); | ||||||
|  |         LOG_DEBUG(Render, "Loading {} map {} with hash {:#016X}", MapTypeName(texture->type), | ||||||
|  |                   texture->path, texture->hash); | ||||||
|  |     } | ||||||
|  |     if (!textures[0]) { | ||||||
|  |         LOG_ERROR(Render, "Unable to create material without color texture!"); | ||||||
|  |         state = DecodeState::Failed; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     width = textures[0]->width; | ||||||
|  |     height = textures[0]->height; | ||||||
|  |     format = textures[0]->format; | ||||||
|  |     for (const CustomTexture* texture : textures) { | ||||||
|  |         if (!texture) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         if (texture->width != width || texture->height != height) { | ||||||
|  |             LOG_ERROR(Render, | ||||||
|  |                       "{} map {} of material with hash {:#016X} has dimentions {}x{} " | ||||||
|  |                       "which do not match the color texture dimentions {}x{}", | ||||||
|  |                       MapTypeName(texture->type), texture->path, texture->hash, texture->width, | ||||||
|  |                       texture->height, width, height); | ||||||
|  |             state = DecodeState::Failed; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if (texture->format != format) { | ||||||
|  |             LOG_ERROR( | ||||||
|  |                 Render, "{} map {} is stored with {} format which does not match color format {}", | ||||||
|  |                 MapTypeName(texture->type), texture->path, | ||||||
|  |                 CustomPixelFormatAsString(texture->format), CustomPixelFormatAsString(format)); | ||||||
|  |             state = DecodeState::Failed; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     state = DecodeState::Decoded; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Material::AddMapTexture(CustomTexture* texture) noexcept { | ||||||
|  |     const std::size_t index = static_cast<std::size_t>(texture->type); | ||||||
|  |     if (textures[index]) { | ||||||
|  |         LOG_ERROR(Render, "Textures {} and {} are assigned to the same material, ignoring!", | ||||||
|  |                   textures[index]->path, texture->path); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     textures[index] = texture; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace VideoCore
 | ||||||
							
								
								
									
										99
									
								
								src/video_core/custom_textures/material.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/video_core/custom_textures/material.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,99 @@ | ||||||
|  | // Copyright 2023 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <array> | ||||||
|  | #include <atomic> | ||||||
|  | #include <span> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  | #include "video_core/custom_textures/custom_format.h" | ||||||
|  | 
 | ||||||
|  | namespace Frontend { | ||||||
|  | class ImageInterface; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | namespace VideoCore { | ||||||
|  | 
 | ||||||
|  | enum class MapType : u32 { | ||||||
|  |     Color = 0, | ||||||
|  |     Normal = 1, | ||||||
|  |     MapCount = 2, | ||||||
|  | }; | ||||||
|  | constexpr std::size_t MAX_MAPS = static_cast<std::size_t>(MapType::MapCount); | ||||||
|  | 
 | ||||||
|  | enum class DecodeState : u32 { | ||||||
|  |     None = 0, | ||||||
|  |     Pending = 1, | ||||||
|  |     Decoded = 2, | ||||||
|  |     Failed = 3, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class CustomTexture { | ||||||
|  | public: | ||||||
|  |     explicit CustomTexture(Frontend::ImageInterface& image_interface); | ||||||
|  |     ~CustomTexture(); | ||||||
|  | 
 | ||||||
|  |     void LoadFromDisk(bool flip_png); | ||||||
|  | 
 | ||||||
|  |     [[nodiscard]] bool IsParsed() const noexcept { | ||||||
|  |         return file_format != CustomFileFormat::None && hash != 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [[nodiscard]] bool IsLoaded() const noexcept { | ||||||
|  |         return !data.empty(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     void LoadPNG(std::span<const u8> input, bool flip_png); | ||||||
|  | 
 | ||||||
|  |     void LoadDDS(std::span<const u8> input); | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |     Frontend::ImageInterface& image_interface; | ||||||
|  |     std::string path; | ||||||
|  |     u32 width; | ||||||
|  |     u32 height; | ||||||
|  |     u64 hash; | ||||||
|  |     CustomPixelFormat format; | ||||||
|  |     CustomFileFormat file_format; | ||||||
|  |     std::vector<u8> data; | ||||||
|  |     MapType type; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct Material { | ||||||
|  |     u32 width; | ||||||
|  |     u32 height; | ||||||
|  |     u64 size; | ||||||
|  |     CustomPixelFormat format; | ||||||
|  |     std::array<CustomTexture*, MAX_MAPS> textures; | ||||||
|  |     std::atomic<DecodeState> state{}; | ||||||
|  | 
 | ||||||
|  |     void LoadFromDisk(bool flip_png) noexcept; | ||||||
|  | 
 | ||||||
|  |     void AddMapTexture(CustomTexture* texture) noexcept; | ||||||
|  | 
 | ||||||
|  |     [[nodiscard]] CustomTexture* Map(MapType type) const noexcept { | ||||||
|  |         return textures.at(static_cast<std::size_t>(type)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [[nodiscard]] bool IsPending() const noexcept { | ||||||
|  |         return state == DecodeState::Pending; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [[nodiscard]] bool IsFailed() const noexcept { | ||||||
|  |         return state == DecodeState::Failed; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [[nodiscard]] bool IsDecoded() const noexcept { | ||||||
|  |         return state == DecodeState::Decoded; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [[nodiscard]] bool IsUnloaded() const noexcept { | ||||||
|  |         return state == DecodeState::None; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace VideoCore
 | ||||||
|  | @ -44,7 +44,7 @@ enum class SurfaceType : u32 { | ||||||
|     Invalid = 5, |     Invalid = 5, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| enum class TextureType : u32 { | enum class TextureType : u16 { | ||||||
|     Texture2D = 0, |     Texture2D = 0, | ||||||
|     CubeMap = 1, |     CubeMap = 1, | ||||||
| }; | }; | ||||||
|  | @ -99,10 +99,10 @@ constexpr SurfaceType GetFormatType(PixelFormat format) { | ||||||
|     return FORMAT_MAP[index].type; |     return FORMAT_MAP[index].type; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::string_view PixelFormatAsString(PixelFormat format); |  | ||||||
| 
 |  | ||||||
| bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format); | bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format); | ||||||
| 
 | 
 | ||||||
|  | std::string_view PixelFormatAsString(PixelFormat format); | ||||||
|  | 
 | ||||||
| PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format); | PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format); | ||||||
| 
 | 
 | ||||||
| PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format); | PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format); | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/microprofile.h" | #include "common/microprofile.h" | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
|  | #include "video_core/custom_textures/custom_tex_manager.h" | ||||||
| #include "video_core/rasterizer_cache/rasterizer_cache.h" | #include "video_core/rasterizer_cache/rasterizer_cache.h" | ||||||
| #include "video_core/regs.h" | #include "video_core/regs.h" | ||||||
| #include "video_core/renderer_base.h" | #include "video_core/renderer_base.h" | ||||||
|  | @ -19,6 +20,15 @@ namespace { | ||||||
| 
 | 
 | ||||||
| MICROPROFILE_DEFINE(RasterizerCache_CopySurface, "RasterizerCache", "CopySurface", | MICROPROFILE_DEFINE(RasterizerCache_CopySurface, "RasterizerCache", "CopySurface", | ||||||
|                     MP_RGB(128, 192, 64)); |                     MP_RGB(128, 192, 64)); | ||||||
|  | MICROPROFILE_DEFINE(RasterizerCache_UploadSurface, "RasterizerCache", "UploadSurface", | ||||||
|  |                     MP_RGB(128, 192, 64)); | ||||||
|  | MICROPROFILE_DEFINE(RasterizerCache_ComputeHash, "RasterizerCache", "ComputeHash", | ||||||
|  |                     MP_RGB(32, 64, 192)); | ||||||
|  | MICROPROFILE_DEFINE(RasterizerCache_DownloadSurface, "RasterizerCache", "DownloadSurface", | ||||||
|  |                     MP_RGB(128, 192, 64)); | ||||||
|  | MICROPROFILE_DEFINE(RasterizerCache_Invalidation, "RasterizerCache", "Invalidation", | ||||||
|  |                     MP_RGB(128, 64, 192)); | ||||||
|  | MICROPROFILE_DEFINE(RasterizerCache_Flush, "RasterizerCache", "Flush", MP_RGB(128, 64, 192)); | ||||||
| 
 | 
 | ||||||
| constexpr auto RangeFromInterval(const auto& map, const auto& interval) { | constexpr auto RangeFromInterval(const auto& map, const auto& interval) { | ||||||
|     return boost::make_iterator_range(map.equal_range(interval)); |     return boost::make_iterator_range(map.equal_range(interval)); | ||||||
|  | @ -119,11 +129,15 @@ auto FindMatch(const auto& surface_cache, const SurfaceParams& params, ScaleMatc | ||||||
| 
 | 
 | ||||||
| } // Anonymous namespace
 | } // Anonymous namespace
 | ||||||
| 
 | 
 | ||||||
| RasterizerCache::RasterizerCache(Memory::MemorySystem& memory_, OpenGL::TextureRuntime& runtime_, | RasterizerCache::RasterizerCache(Memory::MemorySystem& memory_, | ||||||
|                                  Pica::Regs& regs_, RendererBase& renderer_) |                                  CustomTexManager& custom_tex_manager_, | ||||||
|     : memory{memory_}, runtime{runtime_}, regs{regs_}, renderer{renderer_}, |                                  OpenGL::TextureRuntime& runtime_, Pica::Regs& regs_, | ||||||
|       resolution_scale_factor{renderer.GetResolutionScaleFactor()}, |                                  RendererBase& renderer_) | ||||||
|       use_filter{Settings::values.texture_filter.GetValue() != Settings::TextureFilter::None} {} |     : memory{memory_}, custom_tex_manager{custom_tex_manager_}, runtime{runtime_}, regs{regs_}, | ||||||
|  |       renderer{renderer_}, resolution_scale_factor{renderer.GetResolutionScaleFactor()}, | ||||||
|  |       use_filter{Settings::values.texture_filter.GetValue() != Settings::TextureFilter::None}, | ||||||
|  |       dump_textures{Settings::values.dump_textures.GetValue()}, | ||||||
|  |       use_custom_textures{Settings::values.custom_textures.GetValue()} {} | ||||||
| 
 | 
 | ||||||
| RasterizerCache::~RasterizerCache() { | RasterizerCache::~RasterizerCache() { | ||||||
| #ifndef ANDROID | #ifndef ANDROID | ||||||
|  | @ -132,6 +146,32 @@ RasterizerCache::~RasterizerCache() { | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void RasterizerCache::TickFrame() { | ||||||
|  |     custom_tex_manager.TickFrame(); | ||||||
|  | 
 | ||||||
|  |     const u32 scale_factor = renderer.GetResolutionScaleFactor(); | ||||||
|  |     const bool resolution_scale_changed = resolution_scale_factor != scale_factor; | ||||||
|  |     const bool use_custom_texture_changed = | ||||||
|  |         Settings::values.custom_textures.GetValue() != use_custom_textures; | ||||||
|  |     const bool texture_filter_changed = | ||||||
|  |         renderer.Settings().texture_filter_update_requested.exchange(false); | ||||||
|  | 
 | ||||||
|  |     if (resolution_scale_changed || texture_filter_changed || use_custom_texture_changed) { | ||||||
|  |         resolution_scale_factor = scale_factor; | ||||||
|  |         use_filter = Settings::values.texture_filter.GetValue() != Settings::TextureFilter::None; | ||||||
|  |         use_custom_textures = Settings::values.custom_textures.GetValue(); | ||||||
|  |         if (use_custom_textures) { | ||||||
|  |             custom_tex_manager.FindCustomTextures(); | ||||||
|  |         } | ||||||
|  |         FlushAll(); | ||||||
|  |         while (!surface_cache.empty()) { | ||||||
|  |             UnregisterSurface(*surface_cache.begin()->second.begin()); | ||||||
|  |         } | ||||||
|  |         texture_cube_cache.clear(); | ||||||
|  |         runtime.Reset(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool RasterizerCache::AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config) { | bool RasterizerCache::AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config) { | ||||||
|     // Texture copy size is aligned to 16 byte units
 |     // Texture copy size is aligned to 16 byte units
 | ||||||
|     const u32 copy_size = Common::AlignDown(config.texture_copy.size, 16); |     const u32 copy_size = Common::AlignDown(config.texture_copy.size, 16); | ||||||
|  | @ -572,21 +612,6 @@ OpenGL::Framebuffer RasterizerCache::GetFramebufferSurfaces(bool using_color_fb, | ||||||
|                                                             bool using_depth_fb) { |                                                             bool using_depth_fb) { | ||||||
|     const auto& config = regs.framebuffer.framebuffer; |     const auto& config = regs.framebuffer.framebuffer; | ||||||
| 
 | 
 | ||||||
|     // Update resolution_scale_factor and reset cache if changed
 |  | ||||||
|     const u32 scale_factor = renderer.GetResolutionScaleFactor(); |  | ||||||
|     const bool resolution_scale_changed = resolution_scale_factor != scale_factor; |  | ||||||
|     const bool texture_filter_changed = |  | ||||||
|         renderer.Settings().texture_filter_update_requested.exchange(false); |  | ||||||
| 
 |  | ||||||
|     if (resolution_scale_changed || texture_filter_changed) { |  | ||||||
|         resolution_scale_factor = scale_factor; |  | ||||||
|         use_filter = Settings::values.texture_filter.GetValue() != Settings::TextureFilter::None; |  | ||||||
|         FlushAll(); |  | ||||||
|         while (!surface_cache.empty()) |  | ||||||
|             UnregisterSurface(*surface_cache.begin()->second.begin()); |  | ||||||
|         texture_cube_cache.clear(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const s32 framebuffer_width = config.GetWidth(); |     const s32 framebuffer_width = config.GetWidth(); | ||||||
|     const s32 framebuffer_height = config.GetHeight(); |     const s32 framebuffer_height = config.GetHeight(); | ||||||
|     const auto viewport_rect = regs.rasterizer.GetViewportRect(); |     const auto viewport_rect = regs.rasterizer.GetViewportRect(); | ||||||
|  | @ -818,11 +843,13 @@ void RasterizerCache::ValidateSurface(const SurfaceRef& surface, PAddr addr, u32 | ||||||
|     // Filtered mipmaps often look really bad. We can achieve better quality by
 |     // Filtered mipmaps often look really bad. We can achieve better quality by
 | ||||||
|     // generating them from the base level.
 |     // generating them from the base level.
 | ||||||
|     if (surface->res_scale != 1 && level != 0) { |     if (surface->res_scale != 1 && level != 0) { | ||||||
|         runtime.GenerateMipmaps(*surface, surface->levels - 1); |         runtime.GenerateMipmaps(*surface); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RasterizerCache::UploadSurface(const SurfaceRef& surface, SurfaceInterval interval) { | void RasterizerCache::UploadSurface(const SurfaceRef& surface, SurfaceInterval interval) { | ||||||
|  |     MICROPROFILE_SCOPE(RasterizerCache_UploadSurface); | ||||||
|  | 
 | ||||||
|     const SurfaceParams load_info = surface->FromInterval(interval); |     const SurfaceParams load_info = surface->FromInterval(interval); | ||||||
|     ASSERT(load_info.addr >= surface->addr && load_info.end <= surface->end); |     ASSERT(load_info.addr >= surface->addr && load_info.end <= surface->end); | ||||||
| 
 | 
 | ||||||
|  | @ -838,6 +865,18 @@ void RasterizerCache::UploadSurface(const SurfaceRef& surface, SurfaceInterval i | ||||||
|     DecodeTexture(load_info, load_info.addr, load_info.end, upload_data, staging.mapped, |     DecodeTexture(load_info, load_info.addr, load_info.end, upload_data, staging.mapped, | ||||||
|                   runtime.NeedsConversion(surface->pixel_format)); |                   runtime.NeedsConversion(surface->pixel_format)); | ||||||
| 
 | 
 | ||||||
|  |     if (use_custom_textures) { | ||||||
|  |         const u64 hash = ComputeCustomHash(load_info, staging.mapped, upload_data); | ||||||
|  |         if (UploadCustomSurface(surface, load_info, hash)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     if (dump_textures && !surface->is_custom) { | ||||||
|  |         const u64 hash = Common::ComputeHash64(upload_data.data(), upload_data.size()); | ||||||
|  |         const u32 level = surface->LevelOf(load_info.addr); | ||||||
|  |         custom_tex_manager.DumpTexture(load_info, level, upload_data, hash); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const BufferTextureCopy upload = { |     const BufferTextureCopy upload = { | ||||||
|         .buffer_offset = 0, |         .buffer_offset = 0, | ||||||
|         .buffer_size = staging.size, |         .buffer_size = staging.size, | ||||||
|  | @ -847,7 +886,49 @@ void RasterizerCache::UploadSurface(const SurfaceRef& surface, SurfaceInterval i | ||||||
|     surface->Upload(upload, staging); |     surface->Upload(upload, staging); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool RasterizerCache::UploadCustomSurface(const SurfaceRef& surface, const SurfaceParams& load_info, | ||||||
|  |                                           u64 hash) { | ||||||
|  |     const u32 level = surface->LevelOf(load_info.addr); | ||||||
|  |     const bool is_base_level = level == 0; | ||||||
|  |     Material* material = custom_tex_manager.GetMaterial(hash); | ||||||
|  | 
 | ||||||
|  |     if (!material) { | ||||||
|  |         return surface->IsCustom(); | ||||||
|  |     } | ||||||
|  |     if (!is_base_level && custom_tex_manager.SkipMipmaps()) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     surface->is_custom = true; | ||||||
|  | 
 | ||||||
|  |     const auto upload = [this, level, surface, material]() -> bool { | ||||||
|  |         if (!surface->IsCustom() && !surface->Swap(material)) { | ||||||
|  |             LOG_ERROR(HW_GPU, "Custom compressed format {} unsupported by host GPU", | ||||||
|  |                       material->format); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         surface->UploadCustom(material, level); | ||||||
|  |         if (custom_tex_manager.SkipMipmaps()) { | ||||||
|  |             runtime.GenerateMipmaps(*surface); | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |     }; | ||||||
|  |     return custom_tex_manager.Decode(material, std::move(upload)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | u64 RasterizerCache::ComputeCustomHash(const SurfaceParams& load_info, std::span<u8> decoded, | ||||||
|  |                                        std::span<u8> upload_data) { | ||||||
|  |     MICROPROFILE_SCOPE(RasterizerCache_ComputeHash); | ||||||
|  | 
 | ||||||
|  |     if (custom_tex_manager.UseNewHash()) { | ||||||
|  |         return Common::ComputeHash64(upload_data.data(), upload_data.size()); | ||||||
|  |     } | ||||||
|  |     return Common::ComputeHash64(decoded.data(), decoded.size()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void RasterizerCache::DownloadSurface(const SurfaceRef& surface, SurfaceInterval interval) { | void RasterizerCache::DownloadSurface(const SurfaceRef& surface, SurfaceInterval interval) { | ||||||
|  |     MICROPROFILE_SCOPE(RasterizerCache_DownloadSurface); | ||||||
|  | 
 | ||||||
|     const SurfaceParams flush_info = surface->FromInterval(interval); |     const SurfaceParams flush_info = surface->FromInterval(interval); | ||||||
|     const u32 flush_start = boost::icl::first(interval); |     const u32 flush_start = boost::icl::first(interval); | ||||||
|     const u32 flush_end = boost::icl::last_next(interval); |     const u32 flush_end = boost::icl::last_next(interval); | ||||||
|  |  | ||||||
|  | @ -4,7 +4,9 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <functional> | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
|  | #include <vector> | ||||||
| #include <boost/icl/interval_map.hpp> | #include <boost/icl/interval_map.hpp> | ||||||
| #include <boost/icl/interval_set.hpp> | #include <boost/icl/interval_set.hpp> | ||||||
| #include "video_core/rasterizer_cache/surface_base.h" | #include "video_core/rasterizer_cache/surface_base.h" | ||||||
|  | @ -28,6 +30,8 @@ enum class ScaleMatch { | ||||||
|     Ignore   // accept every scaled res
 |     Ignore   // accept every scaled res
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | class CustomTexManager; | ||||||
|  | struct CustomTexture; | ||||||
| class RendererBase; | class RendererBase; | ||||||
| 
 | 
 | ||||||
| class RasterizerCache : NonCopyable { | class RasterizerCache : NonCopyable { | ||||||
|  | @ -62,10 +66,13 @@ public: | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
|     RasterizerCache(Memory::MemorySystem& memory, OpenGL::TextureRuntime& runtime, Pica::Regs& regs, |     RasterizerCache(Memory::MemorySystem& memory, CustomTexManager& custom_tex_manager, | ||||||
|                     RendererBase& renderer); |                     OpenGL::TextureRuntime& runtime, Pica::Regs& regs, RendererBase& renderer); | ||||||
|     ~RasterizerCache(); |     ~RasterizerCache(); | ||||||
| 
 | 
 | ||||||
|  |     /// Notify the cache that a new frame has been queued
 | ||||||
|  |     void TickFrame(); | ||||||
|  | 
 | ||||||
|     /// Perform hardware accelerated texture copy according to the provided configuration
 |     /// Perform hardware accelerated texture copy according to the provided configuration
 | ||||||
|     bool AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config); |     bool AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config); | ||||||
| 
 | 
 | ||||||
|  | @ -126,6 +133,13 @@ private: | ||||||
|     /// Copies pixel data in interval from the guest VRAM to the host GPU surface
 |     /// Copies pixel data in interval from the guest VRAM to the host GPU surface
 | ||||||
|     void UploadSurface(const SurfaceRef& surface, SurfaceInterval interval); |     void UploadSurface(const SurfaceRef& surface, SurfaceInterval interval); | ||||||
| 
 | 
 | ||||||
|  |     /// Uploads a custom texture identified with hash to the target surface
 | ||||||
|  |     bool UploadCustomSurface(const SurfaceRef& surface, const SurfaceParams& load_info, u64 hash); | ||||||
|  | 
 | ||||||
|  |     /// Returns the hash used to lookup the custom surface.
 | ||||||
|  |     u64 ComputeCustomHash(const SurfaceParams& load_info, std::span<u8> decoded, | ||||||
|  |                           std::span<u8> upload_data); | ||||||
|  | 
 | ||||||
|     /// Copies pixel data in interval from the host GPU surface to the guest VRAM
 |     /// Copies pixel data in interval from the host GPU surface to the guest VRAM
 | ||||||
|     void DownloadSurface(const SurfaceRef& surface, SurfaceInterval interval); |     void DownloadSurface(const SurfaceRef& surface, SurfaceInterval interval); | ||||||
| 
 | 
 | ||||||
|  | @ -158,6 +172,7 @@ private: | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     Memory::MemorySystem& memory; |     Memory::MemorySystem& memory; | ||||||
|  |     CustomTexManager& custom_tex_manager; | ||||||
|     OpenGL::TextureRuntime& runtime; |     OpenGL::TextureRuntime& runtime; | ||||||
|     Pica::Regs& regs; |     Pica::Regs& regs; | ||||||
|     RendererBase& renderer; |     RendererBase& renderer; | ||||||
|  | @ -168,7 +183,9 @@ private: | ||||||
|     u32 resolution_scale_factor; |     u32 resolution_scale_factor; | ||||||
|     RenderTargets render_targets; |     RenderTargets render_targets; | ||||||
|     std::unordered_map<TextureCubeConfig, TextureCube> texture_cube_cache; |     std::unordered_map<TextureCubeConfig, TextureCube> texture_cube_cache; | ||||||
|     bool use_filter{}; |     bool use_filter; | ||||||
|  |     bool dump_textures; | ||||||
|  |     bool use_custom_textures; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace VideoCore
 | } // namespace VideoCore
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include "common/alignment.h" | #include "common/alignment.h" | ||||||
|  | #include "video_core/custom_textures/material.h" | ||||||
| #include "video_core/rasterizer_cache/surface_base.h" | #include "video_core/rasterizer_cache/surface_base.h" | ||||||
| #include "video_core/texture/texture_decode.h" | #include "video_core/texture/texture_decode.h" | ||||||
| 
 | 
 | ||||||
|  | @ -101,6 +102,10 @@ SurfaceInterval SurfaceBase::GetCopyableInterval(const SurfaceParams& params) co | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool SurfaceBase::HasNormalMap() const noexcept { | ||||||
|  |     return material && material->Map(MapType::Normal) != nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| ClearValue SurfaceBase::MakeClearValue(PAddr copy_addr, PixelFormat dst_format) { | ClearValue SurfaceBase::MakeClearValue(PAddr copy_addr, PixelFormat dst_format) { | ||||||
|     const SurfaceType dst_type = GetFormatType(dst_format); |     const SurfaceType dst_type = GetFormatType(dst_format); | ||||||
|     const std::array fill_buffer = MakeFillBuffer(copy_addr); |     const std::array fill_buffer = MakeFillBuffer(copy_addr); | ||||||
|  |  | ||||||
|  | @ -11,6 +11,8 @@ namespace VideoCore { | ||||||
| 
 | 
 | ||||||
| using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>; | using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>; | ||||||
| 
 | 
 | ||||||
|  | struct Material; | ||||||
|  | 
 | ||||||
| class SurfaceBase : public SurfaceParams { | class SurfaceBase : public SurfaceParams { | ||||||
| public: | public: | ||||||
|     SurfaceBase(const SurfaceParams& params); |     SurfaceBase(const SurfaceParams& params); | ||||||
|  | @ -28,10 +30,17 @@ public: | ||||||
|     /// Returns the clear value used to validate another surface from this fill surface
 |     /// Returns the clear value used to validate another surface from this fill surface
 | ||||||
|     ClearValue MakeClearValue(PAddr copy_addr, PixelFormat dst_format); |     ClearValue MakeClearValue(PAddr copy_addr, PixelFormat dst_format); | ||||||
| 
 | 
 | ||||||
|  |     /// Returns true if the surface contains a custom material with a normal map.
 | ||||||
|  |     bool HasNormalMap() const noexcept; | ||||||
|  | 
 | ||||||
|     u64 ModificationTick() const noexcept { |     u64 ModificationTick() const noexcept { | ||||||
|         return modification_tick; |         return modification_tick; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     bool IsCustom() const noexcept { | ||||||
|  |         return is_custom && custom_format != CustomPixelFormat::Invalid; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     bool IsRegionValid(SurfaceInterval interval) const { |     bool IsRegionValid(SurfaceInterval interval) const { | ||||||
|         return (invalid_regions.find(interval) == invalid_regions.end()); |         return (invalid_regions.find(interval) == invalid_regions.end()); | ||||||
|     } |     } | ||||||
|  | @ -57,6 +66,8 @@ private: | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
|     bool registered = false; |     bool registered = false; | ||||||
|  |     bool is_custom = false; | ||||||
|  |     const Material* material = nullptr; | ||||||
|     SurfaceRegions invalid_regions; |     SurfaceRegions invalid_regions; | ||||||
|     u32 fill_size = 0; |     u32 fill_size = 0; | ||||||
|     std::array<u8, 4> fill_data; |     std::array<u8, 4> fill_data; | ||||||
|  |  | ||||||
|  | @ -215,12 +215,12 @@ u32 SurfaceParams::LevelOf(PAddr level_addr) const { | ||||||
|     return level; |     return level; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::string SurfaceParams::DebugName(bool scaled) const noexcept { | std::string SurfaceParams::DebugName(bool scaled, bool custom) const noexcept { | ||||||
|     const u32 scaled_width = scaled ? GetScaledWidth() : width; |     const u32 scaled_width = scaled ? GetScaledWidth() : width; | ||||||
|     const u32 scaled_height = scaled ? GetScaledHeight() : height; |     const u32 scaled_height = scaled ? GetScaledHeight() : height; | ||||||
|     return fmt::format("Surface: {}x{} {} {} levels from {:#x} to {:#x} ({})", scaled_width, |     return fmt::format("Surface: {}x{} {} {} levels from {:#x} to {:#x} ({}{})", scaled_width, | ||||||
|                        scaled_height, PixelFormatAsString(pixel_format), levels, addr, end, |                        scaled_height, PixelFormatAsString(pixel_format), levels, addr, end, | ||||||
|                        scaled ? "scaled" : "unscaled"); |                        custom ? "custom," : "", scaled ? "scaled" : "unscaled"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace VideoCore
 | } // namespace VideoCore
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include "video_core/custom_textures/custom_format.h" | ||||||
| #include "video_core/rasterizer_cache/utils.h" | #include "video_core/rasterizer_cache/utils.h" | ||||||
| 
 | 
 | ||||||
| namespace VideoCore { | namespace VideoCore { | ||||||
|  | @ -46,7 +47,7 @@ public: | ||||||
|     u32 LevelOf(PAddr addr) const; |     u32 LevelOf(PAddr addr) const; | ||||||
| 
 | 
 | ||||||
|     /// Returns a string identifier of the params object
 |     /// Returns a string identifier of the params object
 | ||||||
|     std::string DebugName(bool scaled) const noexcept; |     std::string DebugName(bool scaled, bool custom = false) const noexcept; | ||||||
| 
 | 
 | ||||||
|     [[nodiscard]] SurfaceInterval GetInterval() const noexcept { |     [[nodiscard]] SurfaceInterval GetInterval() const noexcept { | ||||||
|         return SurfaceInterval{addr, end}; |         return SurfaceInterval{addr, end}; | ||||||
|  | @ -101,6 +102,7 @@ public: | ||||||
|     bool is_tiled = false; |     bool is_tiled = false; | ||||||
|     TextureType texture_type = TextureType::Texture2D; |     TextureType texture_type = TextureType::Texture2D; | ||||||
|     PixelFormat pixel_format = PixelFormat::Invalid; |     PixelFormat pixel_format = PixelFormat::Invalid; | ||||||
|  |     CustomPixelFormat custom_format = CustomPixelFormat::Invalid; | ||||||
|     SurfaceType type = SurfaceType::Invalid; |     SurfaceType type = SurfaceType::Invalid; | ||||||
| 
 | 
 | ||||||
|     std::array<u32, MAX_PICA_LEVELS> mipmap_offsets{}; |     std::array<u32, MAX_PICA_LEVELS> mipmap_offsets{}; | ||||||
|  |  | ||||||
|  | @ -13,7 +13,6 @@ | ||||||
| #include "video_core/host_shaders/texture_filtering/nearest_neighbor_frag.h" | #include "video_core/host_shaders/texture_filtering/nearest_neighbor_frag.h" | ||||||
| #include "video_core/host_shaders/texture_filtering/refine_frag.h" | #include "video_core/host_shaders/texture_filtering/refine_frag.h" | ||||||
| #include "video_core/host_shaders/texture_filtering/scale_force_frag.h" | #include "video_core/host_shaders/texture_filtering/scale_force_frag.h" | ||||||
| #include "video_core/host_shaders/texture_filtering/tex_coord_vert.h" |  | ||||||
| #include "video_core/host_shaders/texture_filtering/x_gradient_frag.h" | #include "video_core/host_shaders/texture_filtering/x_gradient_frag.h" | ||||||
| #include "video_core/host_shaders/texture_filtering/xbrz_freescale_frag.h" | #include "video_core/host_shaders/texture_filtering/xbrz_freescale_frag.h" | ||||||
| #include "video_core/host_shaders/texture_filtering/y_gradient_frag.h" | #include "video_core/host_shaders/texture_filtering/y_gradient_frag.h" | ||||||
|  | @ -81,7 +80,7 @@ bool BlitHelper::Filter(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const OpenGLState prev_state = OpenGLState::GetCurState(); |     const OpenGLState prev_state = OpenGLState::GetCurState(); | ||||||
|     state.texture_units[0].texture_2d = surface.Handle(false); |     state.texture_units[0].texture_2d = surface.Handle(0); | ||||||
| 
 | 
 | ||||||
|     const auto filter{Settings::values.texture_filter.GetValue()}; |     const auto filter{Settings::values.texture_filter.GetValue()}; | ||||||
|     switch (filter) { |     switch (filter) { | ||||||
|  | @ -135,7 +134,7 @@ void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& b | ||||||
|     auto LUMAD = setup_temp_tex(GL_R16F, GL_RED, temp_rect.GetWidth(), temp_rect.GetHeight()); |     auto LUMAD = setup_temp_tex(GL_R16F, GL_RED, temp_rect.GetWidth(), temp_rect.GetHeight()); | ||||||
| 
 | 
 | ||||||
|     // Copy to SRC
 |     // Copy to SRC
 | ||||||
|     glCopyImageSubData(surface.Handle(false), GL_TEXTURE_2D, 0, blit.src_rect.left, |     glCopyImageSubData(surface.Handle(0), GL_TEXTURE_2D, 0, blit.src_rect.left, | ||||||
|                        blit.src_rect.bottom, 0, SRC.tex.handle, GL_TEXTURE_2D, 0, 0, 0, 0, |                        blit.src_rect.bottom, 0, SRC.tex.handle, GL_TEXTURE_2D, 0, 0, 0, 0, | ||||||
|                        src_width, src_height, 1); |                        src_width, src_height, 1); | ||||||
| 
 | 
 | ||||||
|  | @ -161,47 +160,42 @@ void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& b | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit) { | void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||||
|     SetParams(bicubic_program, surface.width, surface.height, blit.src_rect); |     SetParams(bicubic_program, surface.Extent(), blit.src_rect); | ||||||
|     Draw(bicubic_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); |     Draw(bicubic_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void BlitHelper::FilterNearest(Surface& surface, const VideoCore::TextureBlit& blit) { | void BlitHelper::FilterNearest(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||||
|     state.texture_units[2].texture_2d = surface.Handle(false); |     state.texture_units[2].texture_2d = surface.Handle(0); | ||||||
|     SetParams(nearest_program, surface.width, surface.height, blit.src_rect); |     SetParams(nearest_program, surface.Extent(), blit.src_rect); | ||||||
|     Draw(nearest_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); |     Draw(nearest_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit) { | void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||||
|     SetParams(scale_force_program, surface.width, surface.height, blit.src_rect); |     SetParams(scale_force_program, surface.Extent(), blit.src_rect); | ||||||
|     Draw(scale_force_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); |     Draw(scale_force_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit) { | void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||||
|     glProgramUniform1f(xbrz_program.handle, 2, static_cast<GLfloat>(surface.res_scale)); |     glProgramUniform1f(xbrz_program.handle, 2, static_cast<GLfloat>(surface.res_scale)); | ||||||
|     SetParams(xbrz_program, surface.width, surface.height, blit.src_rect); |     SetParams(xbrz_program, surface.Extent(), blit.src_rect); | ||||||
|     Draw(xbrz_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); |     Draw(xbrz_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void BlitHelper::SetParams(OGLProgram& program, u32 src_width, u32 src_height, | void BlitHelper::SetParams(OGLProgram& program, const VideoCore::Extent& src_extent, | ||||||
|                            Common::Rectangle<u32> src_rect) { |                            Common::Rectangle<u32> src_rect) { | ||||||
|     glProgramUniform2f( |     glProgramUniform2f( | ||||||
|         program.handle, 0, |         program.handle, 0, | ||||||
|         static_cast<float>(src_rect.right - src_rect.left) / static_cast<float>(src_width), |         static_cast<float>(src_rect.right - src_rect.left) / static_cast<float>(src_extent.width), | ||||||
|         static_cast<float>(src_rect.top - src_rect.bottom) / static_cast<float>(src_height)); |         static_cast<float>(src_rect.top - src_rect.bottom) / static_cast<float>(src_extent.height)); | ||||||
|     glProgramUniform2f(program.handle, 1, |     glProgramUniform2f(program.handle, 1, | ||||||
|                        static_cast<float>(src_rect.left) / static_cast<float>(src_width), |                        static_cast<float>(src_rect.left) / static_cast<float>(src_extent.width), | ||||||
|                        static_cast<float>(src_rect.bottom) / static_cast<float>(src_height)); |                        static_cast<float>(src_rect.bottom) / static_cast<float>(src_extent.height)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void BlitHelper::Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 dst_level, | void BlitHelper::Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 dst_level, | ||||||
|                       Common::Rectangle<u32> dst_rect) { |                       Common::Rectangle<u32> dst_rect) { | ||||||
|     state.draw.draw_framebuffer = dst_fbo; |     state.draw.draw_framebuffer = dst_fbo; | ||||||
|     state.draw.shader_program = program.handle; |     state.draw.shader_program = program.handle; | ||||||
|     state.scissor.enabled = true; |  | ||||||
|     state.scissor.x = dst_rect.left; |  | ||||||
|     state.scissor.y = dst_rect.bottom; |  | ||||||
|     state.scissor.width = dst_rect.GetWidth(); |  | ||||||
|     state.scissor.height = dst_rect.GetHeight(); |  | ||||||
|     state.viewport.x = dst_rect.left; |     state.viewport.x = dst_rect.left; | ||||||
|     state.viewport.y = dst_rect.bottom; |     state.viewport.y = dst_rect.bottom; | ||||||
|     state.viewport.width = dst_rect.GetWidth(); |     state.viewport.width = dst_rect.GetWidth(); | ||||||
|  | @ -212,7 +206,6 @@ void BlitHelper::Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 d | ||||||
|                            dst_level); |                            dst_level); | ||||||
|     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); |     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||||||
| 
 | 
 | ||||||
|     glClear(GL_COLOR_BUFFER_BIT); |  | ||||||
|     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,8 +9,9 @@ | ||||||
| #include "video_core/renderer_opengl/gl_state.h" | #include "video_core/renderer_opengl/gl_state.h" | ||||||
| 
 | 
 | ||||||
| namespace VideoCore { | namespace VideoCore { | ||||||
|  | struct Extent; | ||||||
| struct TextureBlit; | struct TextureBlit; | ||||||
| } | } // namespace VideoCore
 | ||||||
| 
 | 
 | ||||||
| namespace OpenGL { | namespace OpenGL { | ||||||
| 
 | 
 | ||||||
|  | @ -35,7 +36,7 @@ private: | ||||||
| 
 | 
 | ||||||
|     void FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit); |     void FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit); | ||||||
| 
 | 
 | ||||||
|     void SetParams(OGLProgram& program, u32 src_width, u32 src_height, |     void SetParams(OGLProgram& program, const VideoCore::Extent& src_extent, | ||||||
|                    Common::Rectangle<u32> src_rect); |                    Common::Rectangle<u32> src_rect); | ||||||
| 
 | 
 | ||||||
|     void Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 dst_level, |     void Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 dst_level, | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
| #include "common/settings.h" | #include "common/settings.h" | ||||||
| #include "core/telemetry_session.h" | #include "core/telemetry_session.h" | ||||||
|  | #include "video_core/custom_textures/custom_format.h" | ||||||
| #include "video_core/renderer_opengl/gl_driver.h" | #include "video_core/renderer_opengl/gl_driver.h" | ||||||
| 
 | 
 | ||||||
| namespace OpenGL { | namespace OpenGL { | ||||||
|  | @ -103,6 +104,25 @@ bool Driver::HasDebugTool() { | ||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool Driver::IsCustomFormatSupported(VideoCore::CustomPixelFormat format) const { | ||||||
|  |     switch (format) { | ||||||
|  |     case VideoCore::CustomPixelFormat::RGBA8: | ||||||
|  |         return true; | ||||||
|  |     case VideoCore::CustomPixelFormat::BC1: | ||||||
|  |     case VideoCore::CustomPixelFormat::BC3: | ||||||
|  |     case VideoCore::CustomPixelFormat::BC5: | ||||||
|  |         return ext_texture_compression_s3tc; | ||||||
|  |     case VideoCore::CustomPixelFormat::BC7: | ||||||
|  |         return arb_texture_compression_bptc; | ||||||
|  |     case VideoCore::CustomPixelFormat::ASTC4: | ||||||
|  |     case VideoCore::CustomPixelFormat::ASTC6: | ||||||
|  |     case VideoCore::CustomPixelFormat::ASTC8: | ||||||
|  |         return is_gles; | ||||||
|  |     default: | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void Driver::ReportDriverInfo() { | void Driver::ReportDriverInfo() { | ||||||
|     // Report the context version and the vendor string
 |     // Report the context version and the vendor string
 | ||||||
|     gl_version = std::string_view{reinterpret_cast<const char*>(glGetString(GL_VERSION))}; |     gl_version = std::string_view{reinterpret_cast<const char*>(glGetString(GL_VERSION))}; | ||||||
|  | @ -145,7 +165,9 @@ void Driver::CheckExtensionSupport() { | ||||||
|     arb_buffer_storage = GLAD_GL_ARB_buffer_storage; |     arb_buffer_storage = GLAD_GL_ARB_buffer_storage; | ||||||
|     arb_clear_texture = GLAD_GL_ARB_clear_texture; |     arb_clear_texture = GLAD_GL_ARB_clear_texture; | ||||||
|     arb_get_texture_sub_image = GLAD_GL_ARB_get_texture_sub_image; |     arb_get_texture_sub_image = GLAD_GL_ARB_get_texture_sub_image; | ||||||
|  |     arb_texture_compression_bptc = GLAD_GL_ARB_texture_compression_bptc; | ||||||
|     ext_clip_cull_distance = GLAD_GL_EXT_clip_cull_distance; |     ext_clip_cull_distance = GLAD_GL_EXT_clip_cull_distance; | ||||||
|  |     ext_texture_compression_s3tc = GLAD_GL_EXT_texture_compression_s3tc; | ||||||
|     is_suitable = GLAD_GL_VERSION_4_3 || GLAD_GL_ES_VERSION_3_1; |     is_suitable = GLAD_GL_VERSION_4_3 || GLAD_GL_ES_VERSION_3_1; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,6 +11,10 @@ namespace Core { | ||||||
| class TelemetrySession; | class TelemetrySession; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | namespace VideoCore { | ||||||
|  | enum class CustomPixelFormat : u32; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| namespace OpenGL { | namespace OpenGL { | ||||||
| 
 | 
 | ||||||
| enum class Vendor { | enum class Vendor { | ||||||
|  | @ -51,6 +55,9 @@ public: | ||||||
|     /// Returns true if any debug tool is attached
 |     /// Returns true if any debug tool is attached
 | ||||||
|     bool HasDebugTool(); |     bool HasDebugTool(); | ||||||
| 
 | 
 | ||||||
|  |     /// Returns true if the driver supports the provided custom format
 | ||||||
|  |     bool IsCustomFormatSupported(VideoCore::CustomPixelFormat format) const; | ||||||
|  | 
 | ||||||
|     /// Returns the vendor of the currently selected physical device
 |     /// Returns the vendor of the currently selected physical device
 | ||||||
|     Vendor GetVendor() const { |     Vendor GetVendor() const { | ||||||
|         return vendor; |         return vendor; | ||||||
|  | @ -114,6 +121,8 @@ private: | ||||||
|     bool arb_clear_texture{}; |     bool arb_clear_texture{}; | ||||||
|     bool arb_get_texture_sub_image{}; |     bool arb_get_texture_sub_image{}; | ||||||
|     bool ext_clip_cull_distance{}; |     bool ext_clip_cull_distance{}; | ||||||
|  |     bool ext_texture_compression_s3tc{}; | ||||||
|  |     bool arb_texture_compression_bptc{}; | ||||||
| 
 | 
 | ||||||
|     std::string_view gl_version{}; |     std::string_view gl_version{}; | ||||||
|     std::string_view gpu_vendor{}; |     std::string_view gpu_vendor{}; | ||||||
|  |  | ||||||
|  | @ -73,11 +73,13 @@ GLenum MakeAttributeType(Pica::PipelineRegs::VertexAttributeFormat format) { | ||||||
| 
 | 
 | ||||||
| } // Anonymous namespace
 | } // Anonymous namespace
 | ||||||
| 
 | 
 | ||||||
| RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, VideoCore::RendererBase& renderer, | RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, | ||||||
|                                    Driver& driver_) |                                    VideoCore::CustomTexManager& custom_tex_manager, | ||||||
|  |                                    VideoCore::RendererBase& renderer, Driver& driver_) | ||||||
|     : VideoCore::RasterizerAccelerated{memory}, driver{driver_}, runtime{driver, renderer}, |     : VideoCore::RasterizerAccelerated{memory}, driver{driver_}, runtime{driver, renderer}, | ||||||
|       res_cache{memory, runtime, regs, renderer}, texture_buffer_size{TextureBufferSize()}, |       res_cache{memory, custom_tex_manager, runtime, regs, renderer}, | ||||||
|       vertex_buffer{driver, GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE}, |       texture_buffer_size{TextureBufferSize()}, vertex_buffer{driver, GL_ARRAY_BUFFER, | ||||||
|  |                                                               VERTEX_BUFFER_SIZE}, | ||||||
|       uniform_buffer{driver, GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE}, |       uniform_buffer{driver, GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE}, | ||||||
|       index_buffer{driver, GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE}, |       index_buffer{driver, GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE}, | ||||||
|       texture_buffer{driver, GL_TEXTURE_BUFFER, texture_buffer_size}, texture_lf_buffer{ |       texture_buffer{driver, GL_TEXTURE_BUFFER, texture_buffer_size}, texture_lf_buffer{ | ||||||
|  | @ -183,6 +185,10 @@ RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, VideoCore::Rend | ||||||
| 
 | 
 | ||||||
| RasterizerOpenGL::~RasterizerOpenGL() = default; | RasterizerOpenGL::~RasterizerOpenGL() = default; | ||||||
| 
 | 
 | ||||||
|  | void RasterizerOpenGL::TickFrame() { | ||||||
|  |     res_cache.TickFrame(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading, | void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading, | ||||||
|                                          const VideoCore::DiskResourceLoadCallback& callback) { |                                          const VideoCore::DiskResourceLoadCallback& callback) { | ||||||
|     shader_program_manager->LoadDiskCache(stop_loading, callback); |     shader_program_manager->LoadDiskCache(stop_loading, callback); | ||||||
|  | @ -420,7 +426,6 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { | ||||||
|     state.scissor.y = draw_rect.bottom; |     state.scissor.y = draw_rect.bottom; | ||||||
|     state.scissor.width = draw_rect.GetWidth(); |     state.scissor.width = draw_rect.GetWidth(); | ||||||
|     state.scissor.height = draw_rect.GetHeight(); |     state.scissor.height = draw_rect.GetHeight(); | ||||||
|     state.Apply(); |  | ||||||
| 
 | 
 | ||||||
|     const u32 res_scale = framebuffer.ResolutionScale(); |     const u32 res_scale = framebuffer.ResolutionScale(); | ||||||
|     if (uniform_block_data.data.framebuffer_scale != res_scale) { |     if (uniform_block_data.data.framebuffer_scale != res_scale) { | ||||||
|  | @ -444,10 +449,11 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { | ||||||
| 
 | 
 | ||||||
|     // Sync and bind the texture surfaces
 |     // Sync and bind the texture surfaces
 | ||||||
|     SyncTextureUnits(framebuffer); |     SyncTextureUnits(framebuffer); | ||||||
|  |     state.Apply(); | ||||||
| 
 | 
 | ||||||
|     // Sync and bind the shader
 |     // Sync and bind the shader
 | ||||||
|     if (shader_dirty) { |     if (shader_dirty) { | ||||||
|         SetShader(); |         shader_program_manager->UseFragmentShader(regs, use_custom_normal); | ||||||
|         shader_dirty = false; |         shader_dirty = false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -546,6 +552,7 @@ void RasterizerOpenGL::SyncTextureUnits(const Framebuffer& framebuffer) { | ||||||
|             // on the male character's face, which in the OpenGL default appear black.
 |             // on the male character's face, which in the OpenGL default appear black.
 | ||||||
|             state.texture_units[texture_index].texture_2d = default_texture; |             state.texture_units[texture_index].texture_2d = default_texture; | ||||||
|         } else if (!IsFeedbackLoop(texture_index, framebuffer, *surface)) { |         } else if (!IsFeedbackLoop(texture_index, framebuffer, *surface)) { | ||||||
|  |             BindMaterial(texture_index, *surface); | ||||||
|             state.texture_units[texture_index].texture_2d = surface->Handle(); |             state.texture_units[texture_index].texture_2d = surface->Handle(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -589,6 +596,33 @@ void RasterizerOpenGL::BindTextureCube(const Pica::TexturingRegs::FullTextureCon | ||||||
|     state.texture_units[0].texture_2d = 0; |     state.texture_units[0].texture_2d = 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void RasterizerOpenGL::BindMaterial(u32 texture_index, Surface& surface) { | ||||||
|  |     if (!surface.IsCustom() || texture_index != 0) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const auto bind_texture = [&](const TextureUnits::TextureUnit& unit, GLuint texture, | ||||||
|  |                                   GLuint sampler) { | ||||||
|  |         glActiveTexture(unit.Enum()); | ||||||
|  |         glBindTexture(GL_TEXTURE_2D, texture); | ||||||
|  |         glBindSampler(unit.id, sampler); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     const GLuint sampler = texture_samplers[texture_index].sampler.handle; | ||||||
|  |     if (surface.HasNormalMap()) { | ||||||
|  |         if (regs.lighting.disable) { | ||||||
|  |             LOG_WARNING(Render_OpenGL, "Custom normal map used but scene has no light enabled"); | ||||||
|  |         } | ||||||
|  |         bind_texture(TextureUnits::TextureNormalMap, surface.Handle(2), sampler); | ||||||
|  |         use_custom_normal = true; | ||||||
|  |     } else { | ||||||
|  |         if (use_custom_normal) { | ||||||
|  |             bind_texture(TextureUnits::TextureNormalMap, 0, 0); | ||||||
|  |         } | ||||||
|  |         use_custom_normal = false; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool RasterizerOpenGL::IsFeedbackLoop(u32 texture_index, const Framebuffer& framebuffer, | bool RasterizerOpenGL::IsFeedbackLoop(u32 texture_index, const Framebuffer& framebuffer, | ||||||
|                                       Surface& surface) { |                                       Surface& surface) { | ||||||
|     const GLuint color_attachment = framebuffer.Attachment(SurfaceType::Color); |     const GLuint color_attachment = framebuffer.Attachment(SurfaceType::Color); | ||||||
|  | @ -622,7 +656,6 @@ void RasterizerOpenGL::UnbindSpecial() { | ||||||
|     state.image_shadow_texture_pz = 0; |     state.image_shadow_texture_pz = 0; | ||||||
|     state.image_shadow_texture_nz = 0; |     state.image_shadow_texture_nz = 0; | ||||||
|     state.image_shadow_buffer = 0; |     state.image_shadow_buffer = 0; | ||||||
|     state.Apply(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RasterizerOpenGL::NotifyFixedFunctionPicaRegisterChanged(u32 id) { | void RasterizerOpenGL::NotifyFixedFunctionPicaRegisterChanged(u32 id) { | ||||||
|  | @ -823,10 +856,6 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig( | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RasterizerOpenGL::SetShader() { |  | ||||||
|     shader_program_manager->UseFragmentShader(Pica::g_state.regs); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void RasterizerOpenGL::SyncClipEnabled() { | void RasterizerOpenGL::SyncClipEnabled() { | ||||||
|     state.clip_distance[1] = Pica::g_state.regs.rasterizer.clip_enable != 0; |     state.clip_distance[1] = Pica::g_state.regs.rasterizer.clip_enable != 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -18,6 +18,10 @@ namespace VideoCore { | ||||||
| class RendererBase; | class RendererBase; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | namespace VideoCore { | ||||||
|  | class CustomTexManager; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| namespace OpenGL { | namespace OpenGL { | ||||||
| 
 | 
 | ||||||
| class Driver; | class Driver; | ||||||
|  | @ -25,10 +29,12 @@ class ShaderProgramManager; | ||||||
| 
 | 
 | ||||||
| class RasterizerOpenGL : public VideoCore::RasterizerAccelerated { | class RasterizerOpenGL : public VideoCore::RasterizerAccelerated { | ||||||
| public: | public: | ||||||
|     explicit RasterizerOpenGL(Memory::MemorySystem& memory, VideoCore::RendererBase& renderer, |     explicit RasterizerOpenGL(Memory::MemorySystem& memory, | ||||||
|                               Driver& driver); |                               VideoCore::CustomTexManager& custom_tex_manager, | ||||||
|  |                               VideoCore::RendererBase& renderer, Driver& driver); | ||||||
|     ~RasterizerOpenGL() override; |     ~RasterizerOpenGL() override; | ||||||
| 
 | 
 | ||||||
|  |     void TickFrame(); | ||||||
|     void LoadDiskResources(const std::atomic_bool& stop_loading, |     void LoadDiskResources(const std::atomic_bool& stop_loading, | ||||||
|                            const VideoCore::DiskResourceLoadCallback& callback) override; |                            const VideoCore::DiskResourceLoadCallback& callback) override; | ||||||
| 
 | 
 | ||||||
|  | @ -74,9 +80,6 @@ private: | ||||||
|     /// Syncs the clip enabled status to match the PICA register
 |     /// Syncs the clip enabled status to match the PICA register
 | ||||||
|     void SyncClipEnabled(); |     void SyncClipEnabled(); | ||||||
| 
 | 
 | ||||||
|     /// Sets the OpenGL shader in accordance with the current PICA register state
 |  | ||||||
|     void SetShader(); |  | ||||||
| 
 |  | ||||||
|     /// Syncs the cull mode to match the PICA register
 |     /// Syncs the cull mode to match the PICA register
 | ||||||
|     void SyncCullMode(); |     void SyncCullMode(); | ||||||
| 
 | 
 | ||||||
|  | @ -126,6 +129,9 @@ private: | ||||||
|     /// Unbinds all special texture unit 0 texture configurations
 |     /// Unbinds all special texture unit 0 texture configurations
 | ||||||
|     void UnbindSpecial(); |     void UnbindSpecial(); | ||||||
| 
 | 
 | ||||||
|  |     /// Binds the custom material referenced by surface if it exists.
 | ||||||
|  |     void BindMaterial(u32 texture_index, Surface& surface); | ||||||
|  | 
 | ||||||
|     /// Upload the uniform blocks to the uniform buffer object
 |     /// Upload the uniform blocks to the uniform buffer object
 | ||||||
|     void UploadUniforms(bool accelerate_draw); |     void UploadUniforms(bool accelerate_draw); | ||||||
| 
 | 
 | ||||||
|  | @ -174,6 +180,7 @@ private: | ||||||
|     OGLTexture texture_buffer_lut_lf; |     OGLTexture texture_buffer_lut_lf; | ||||||
|     OGLTexture texture_buffer_lut_rg; |     OGLTexture texture_buffer_lut_rg; | ||||||
|     OGLTexture texture_buffer_lut_rgba; |     OGLTexture texture_buffer_lut_rgba; | ||||||
|  |     bool use_custom_normal{}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace OpenGL
 | } // namespace OpenGL
 | ||||||
|  |  | ||||||
|  | @ -59,7 +59,7 @@ out gl_PerVertex { | ||||||
|     return out; |     return out; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs) { | PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs, bool use_normal) { | ||||||
|     PicaFSConfig res{}; |     PicaFSConfig res{}; | ||||||
| 
 | 
 | ||||||
|     auto& state = res.state; |     auto& state = res.state; | ||||||
|  | @ -204,6 +204,8 @@ PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs) { | ||||||
| 
 | 
 | ||||||
|     state.shadow_texture_orthographic = regs.texturing.shadow.orthographic != 0; |     state.shadow_texture_orthographic = regs.texturing.shadow.orthographic != 0; | ||||||
| 
 | 
 | ||||||
|  |     state.use_custom_normal_map = use_normal; | ||||||
|  | 
 | ||||||
|     return res; |     return res; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -297,6 +299,8 @@ static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_un | ||||||
|             LOG_DEBUG(Render_OpenGL, "Using Texture3 without enabling it"); |             LOG_DEBUG(Render_OpenGL, "Using Texture3 without enabling it"); | ||||||
|             return "vec4(0.0)"; |             return "vec4(0.0)"; | ||||||
|         } |         } | ||||||
|  |     case 4: | ||||||
|  |         return "texture(tex_normal, texcoord0)"; | ||||||
|     default: |     default: | ||||||
|         UNREACHABLE(); |         UNREACHABLE(); | ||||||
|         return ""; |         return ""; | ||||||
|  | @ -642,7 +646,12 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) { | ||||||
|     const auto Perturbation = [&] { |     const auto Perturbation = [&] { | ||||||
|         return fmt::format("2.0 * ({}).rgb - 1.0", SampleTexture(config, lighting.bump_selector)); |         return fmt::format("2.0 * ({}).rgb - 1.0", SampleTexture(config, lighting.bump_selector)); | ||||||
|     }; |     }; | ||||||
|     if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) { |     if (config.state.use_custom_normal_map) { | ||||||
|  |         const std::string normal_texel = | ||||||
|  |             fmt::format("2.0 * ({}).rgb - 1.0", SampleTexture(config, 4)); | ||||||
|  |         out += fmt::format("vec3 surface_normal = {};\n", normal_texel); | ||||||
|  |         out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n"; | ||||||
|  |     } else if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) { | ||||||
|         // Bump mapping is enabled using a normal map
 |         // Bump mapping is enabled using a normal map
 | ||||||
|         out += fmt::format("vec3 surface_normal = {};\n", Perturbation()); |         out += fmt::format("vec3 surface_normal = {};\n", Perturbation()); | ||||||
| 
 | 
 | ||||||
|  | @ -1207,6 +1216,7 @@ out vec4 color; | ||||||
| uniform sampler2D tex0; | uniform sampler2D tex0; | ||||||
| uniform sampler2D tex1; | uniform sampler2D tex1; | ||||||
| uniform sampler2D tex2; | uniform sampler2D tex2; | ||||||
|  | uniform sampler2D tex_normal; //< Used for custom normal maps
 | ||||||
| uniform samplerCube tex_cube; | uniform samplerCube tex_cube; | ||||||
| uniform samplerBuffer texture_buffer_lut_lf; | uniform samplerBuffer texture_buffer_lut_lf; | ||||||
| uniform samplerBuffer texture_buffer_lut_rg; | uniform samplerBuffer texture_buffer_lut_rg; | ||||||
|  |  | ||||||
|  | @ -117,6 +117,7 @@ struct PicaFSConfigState { | ||||||
| 
 | 
 | ||||||
|     bool shadow_rendering; |     bool shadow_rendering; | ||||||
|     bool shadow_texture_orthographic; |     bool shadow_texture_orthographic; | ||||||
|  |     bool use_custom_normal_map; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  | @ -130,7 +131,7 @@ struct PicaFSConfigState { | ||||||
| struct PicaFSConfig : Common::HashableStruct<PicaFSConfigState> { | struct PicaFSConfig : Common::HashableStruct<PicaFSConfigState> { | ||||||
| 
 | 
 | ||||||
|     /// Construct a PicaFSConfig with the given Pica register configuration.
 |     /// Construct a PicaFSConfig with the given Pica register configuration.
 | ||||||
|     static PicaFSConfig BuildFromRegs(const Pica::Regs& regs); |     static PicaFSConfig BuildFromRegs(const Pica::Regs& regs, bool use_normal = false); | ||||||
| 
 | 
 | ||||||
|     bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { |     bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const { | ||||||
|         return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index)); |         return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index)); | ||||||
|  |  | ||||||
|  | @ -133,6 +133,7 @@ static void SetShaderSamplerBindings(GLuint shader) { | ||||||
|     SetShaderSamplerBinding(shader, "tex1", TextureUnits::PicaTexture(1)); |     SetShaderSamplerBinding(shader, "tex1", TextureUnits::PicaTexture(1)); | ||||||
|     SetShaderSamplerBinding(shader, "tex2", TextureUnits::PicaTexture(2)); |     SetShaderSamplerBinding(shader, "tex2", TextureUnits::PicaTexture(2)); | ||||||
|     SetShaderSamplerBinding(shader, "tex_cube", TextureUnits::TextureCube); |     SetShaderSamplerBinding(shader, "tex_cube", TextureUnits::TextureCube); | ||||||
|  |     SetShaderSamplerBinding(shader, "tex_normal", TextureUnits::TextureNormalMap); | ||||||
| 
 | 
 | ||||||
|     // Set the texture samplers to correspond to different lookup table texture units
 |     // Set the texture samplers to correspond to different lookup table texture units
 | ||||||
|     SetShaderSamplerBinding(shader, "texture_buffer_lut_lf", TextureUnits::TextureBufferLUT_LF); |     SetShaderSamplerBinding(shader, "texture_buffer_lut_lf", TextureUnits::TextureBufferLUT_LF); | ||||||
|  | @ -415,8 +416,8 @@ void ShaderProgramManager::UseTrivialGeometryShader() { | ||||||
|     impl->current.gs_hash = 0; |     impl->current.gs_hash = 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs) { | void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs, bool use_normal) { | ||||||
|     PicaFSConfig config = PicaFSConfig::BuildFromRegs(regs); |     PicaFSConfig config = PicaFSConfig::BuildFromRegs(regs, use_normal); | ||||||
|     auto [handle, result] = impl->fragment_shaders.Get(config); |     auto [handle, result] = impl->fragment_shaders.Get(config); | ||||||
|     impl->current.fs = handle; |     impl->current.fs = handle; | ||||||
|     impl->current.fs_hash = config.Hash(); |     impl->current.fs_hash = config.Hash(); | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ public: | ||||||
| 
 | 
 | ||||||
|     void UseTrivialGeometryShader(); |     void UseTrivialGeometryShader(); | ||||||
| 
 | 
 | ||||||
|     void UseFragmentShader(const Pica::Regs& config); |     void UseFragmentShader(const Pica::Regs& config, bool use_normal); | ||||||
| 
 | 
 | ||||||
|     void ApplyTo(OpenGLState& state); |     void ApplyTo(OpenGLState& state); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ constexpr TextureUnit TextureCube{6}; | ||||||
| constexpr TextureUnit TextureBufferLUT_LF{3}; | constexpr TextureUnit TextureBufferLUT_LF{3}; | ||||||
| constexpr TextureUnit TextureBufferLUT_RG{4}; | constexpr TextureUnit TextureBufferLUT_RG{4}; | ||||||
| constexpr TextureUnit TextureBufferLUT_RGBA{5}; | constexpr TextureUnit TextureBufferLUT_RGBA{5}; | ||||||
|  | constexpr TextureUnit TextureNormalMap{7}; | ||||||
| 
 | 
 | ||||||
| } // namespace TextureUnits
 | } // namespace TextureUnits
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include "common/scope_exit.h" | #include "common/scope_exit.h" | ||||||
| #include "common/settings.h" | #include "video_core/custom_textures/material.h" | ||||||
| #include "video_core/regs.h" | #include "video_core/regs.h" | ||||||
| #include "video_core/renderer_base.h" | #include "video_core/renderer_base.h" | ||||||
| #include "video_core/renderer_opengl/gl_driver.h" | #include "video_core/renderer_opengl/gl_driver.h" | ||||||
|  | @ -14,8 +14,10 @@ namespace OpenGL { | ||||||
| 
 | 
 | ||||||
| namespace { | namespace { | ||||||
| 
 | 
 | ||||||
|  | using VideoCore::MapType; | ||||||
| using VideoCore::PixelFormat; | using VideoCore::PixelFormat; | ||||||
| using VideoCore::SurfaceType; | using VideoCore::SurfaceType; | ||||||
|  | using VideoCore::TextureType; | ||||||
| 
 | 
 | ||||||
| constexpr FormatTuple DEFAULT_TUPLE = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}; | constexpr FormatTuple DEFAULT_TUPLE = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}; | ||||||
| 
 | 
 | ||||||
|  | @ -42,6 +44,17 @@ static constexpr std::array<FormatTuple, 5> COLOR_TUPLES_OES = {{ | ||||||
|     {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4},   // RGBA4
 |     {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4},   // RGBA4
 | ||||||
| }}; | }}; | ||||||
| 
 | 
 | ||||||
|  | static constexpr std::array<FormatTuple, 8> CUSTOM_TUPLES = {{ | ||||||
|  |     DEFAULT_TUPLE, | ||||||
|  |     {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_UNSIGNED_BYTE}, | ||||||
|  |     {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_UNSIGNED_BYTE}, | ||||||
|  |     {GL_COMPRESSED_RG_RGTC2, GL_COMPRESSED_RG_RGTC2, GL_UNSIGNED_BYTE}, | ||||||
|  |     {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_UNSIGNED_BYTE}, | ||||||
|  |     {GL_COMPRESSED_RGBA_ASTC_4x4, GL_COMPRESSED_RGBA_ASTC_4x4, GL_UNSIGNED_BYTE}, | ||||||
|  |     {GL_COMPRESSED_RGBA_ASTC_6x6, GL_COMPRESSED_RGBA_ASTC_6x6, GL_UNSIGNED_BYTE}, | ||||||
|  |     {GL_COMPRESSED_RGBA_ASTC_8x6, GL_COMPRESSED_RGBA_ASTC_8x6, GL_UNSIGNED_BYTE}, | ||||||
|  | }}; | ||||||
|  | 
 | ||||||
| struct FramebufferInfo { | struct FramebufferInfo { | ||||||
|     GLuint color; |     GLuint color; | ||||||
|     GLuint depth; |     GLuint depth; | ||||||
|  | @ -109,17 +122,23 @@ TextureRuntime::TextureRuntime(const Driver& driver_, VideoCore::RendererBase& r | ||||||
|         read_fbos[i].Create(); |         read_fbos[i].Create(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     auto Register = [this](PixelFormat dest, std::unique_ptr<FormatReinterpreterBase>&& obj) { |     auto add_reinterpreter = [this](PixelFormat dest, | ||||||
|  |                                     std::unique_ptr<FormatReinterpreterBase>&& obj) { | ||||||
|         const u32 dst_index = static_cast<u32>(dest); |         const u32 dst_index = static_cast<u32>(dest); | ||||||
|         return reinterpreters[dst_index].push_back(std::move(obj)); |         return reinterpreters[dst_index].push_back(std::move(obj)); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     Register(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>()); |     add_reinterpreter(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>()); | ||||||
|     Register(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>()); |     add_reinterpreter(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| TextureRuntime::~TextureRuntime() = default; | TextureRuntime::~TextureRuntime() = default; | ||||||
| 
 | 
 | ||||||
|  | void TextureRuntime::Reset() { | ||||||
|  |     alloc_cache.clear(); | ||||||
|  |     framebuffer_cache.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat pixel_format) const { | bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat pixel_format) const { | ||||||
|     const bool should_convert = pixel_format == PixelFormat::RGBA8 || // Needs byteswap
 |     const bool should_convert = pixel_format == PixelFormat::RGBA8 || // Needs byteswap
 | ||||||
|                                 pixel_format == PixelFormat::RGB8;    // Is converted to RGBA8
 |                                 pixel_format == PixelFormat::RGB8;    // Is converted to RGBA8
 | ||||||
|  | @ -153,51 +172,64 @@ const FormatTuple& TextureRuntime::GetFormatTuple(PixelFormat pixel_format) cons | ||||||
|     return DEFAULT_TUPLE; |     return DEFAULT_TUPLE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void TextureRuntime::Recycle(const HostTextureTag tag, Allocation&& alloc) { | const FormatTuple& TextureRuntime::GetFormatTuple(VideoCore::CustomPixelFormat pixel_format) { | ||||||
|     recycler.emplace(tag, std::move(alloc)); |     const std::size_t format_index = static_cast<std::size_t>(pixel_format); | ||||||
|  |     return CUSTOM_TUPLES[format_index]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Allocation TextureRuntime::Allocate(const VideoCore::SurfaceParams& params) { | void TextureRuntime::Recycle(const HostTextureTag tag, Allocation&& alloc) { | ||||||
|     const u32 width = params.width; |     alloc_cache.emplace(tag, std::move(alloc)); | ||||||
|     const u32 height = params.height; | } | ||||||
|     const u32 levels = params.levels; | 
 | ||||||
|  | Allocation TextureRuntime::Allocate(const VideoCore::SurfaceParams& params, | ||||||
|  |                                     const VideoCore::Material* material) { | ||||||
|     const GLenum target = params.texture_type == VideoCore::TextureType::CubeMap |     const GLenum target = params.texture_type == VideoCore::TextureType::CubeMap | ||||||
|                               ? GL_TEXTURE_CUBE_MAP |                               ? GL_TEXTURE_CUBE_MAP | ||||||
|                               : GL_TEXTURE_2D; |                               : GL_TEXTURE_2D; | ||||||
|     const auto& tuple = GetFormatTuple(params.pixel_format); |     const bool is_custom = material != nullptr; | ||||||
| 
 |     const bool has_normal = material && material->Map(MapType::Normal); | ||||||
|  |     const auto& tuple = | ||||||
|  |         is_custom ? GetFormatTuple(params.custom_format) : GetFormatTuple(params.pixel_format); | ||||||
|     const HostTextureTag key = { |     const HostTextureTag key = { | ||||||
|  |         .width = params.width, | ||||||
|  |         .height = params.height, | ||||||
|  |         .levels = params.levels, | ||||||
|  |         .res_scale = params.res_scale, | ||||||
|         .tuple = tuple, |         .tuple = tuple, | ||||||
|         .type = params.texture_type, |         .type = params.texture_type, | ||||||
|         .width = width, |         .is_custom = is_custom, | ||||||
|         .height = height, |         .has_normal = has_normal, | ||||||
|         .levels = levels, |  | ||||||
|         .res_scale = params.res_scale, |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     if (auto it = recycler.find(key); it != recycler.end()) { |     if (auto it = alloc_cache.find(key); it != alloc_cache.end()) { | ||||||
|         Allocation alloc = std::move(it->second); |         auto alloc{std::move(it->second)}; | ||||||
|         ASSERT(alloc.res_scale == params.res_scale); |         alloc_cache.erase(it); | ||||||
|         recycler.erase(it); |  | ||||||
|         return alloc; |         return alloc; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d; |     const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d; | ||||||
|     glActiveTexture(GL_TEXTURE0); |     glActiveTexture(GL_TEXTURE0); | ||||||
| 
 | 
 | ||||||
|     std::array<OGLTexture, 2> textures{}; |     std::array<OGLTexture, 3> textures{}; | ||||||
|     std::array<GLuint, 2> handles{}; |     std::array<GLuint, 3> handles{}; | ||||||
| 
 | 
 | ||||||
|     textures[0] = MakeHandle(target, width, height, levels, tuple, params.DebugName(false)); |     textures[0] = MakeHandle(target, params.width, params.height, params.levels, tuple, | ||||||
|  |                              params.DebugName(false)); | ||||||
|     handles.fill(textures[0].handle); |     handles.fill(textures[0].handle); | ||||||
| 
 | 
 | ||||||
|     if (params.res_scale != 1) { |     if (params.res_scale != 1) { | ||||||
|         const u32 scaled_width = params.GetScaledWidth(); |         const u32 scaled_width = is_custom ? params.width : params.GetScaledWidth(); | ||||||
|         const u32 scaled_height = params.GetScaledHeight(); |         const u32 scaled_height = is_custom ? params.height : params.GetScaledHeight(); | ||||||
|         textures[1] = |         const auto& scaled_tuple = is_custom ? GetFormatTuple(PixelFormat::RGBA8) : tuple; | ||||||
|             MakeHandle(target, scaled_width, scaled_height, levels, tuple, params.DebugName(true)); |         textures[1] = MakeHandle(target, scaled_width, scaled_height, params.levels, scaled_tuple, | ||||||
|  |                                  params.DebugName(true, is_custom)); | ||||||
|         handles[1] = textures[1].handle; |         handles[1] = textures[1].handle; | ||||||
|     } |     } | ||||||
|  |     if (has_normal) { | ||||||
|  |         textures[2] = MakeHandle(target, params.width, params.height, params.levels, tuple, | ||||||
|  |                                  params.DebugName(true, is_custom)); | ||||||
|  |         handles[2] = textures[2].handle; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     glBindTexture(GL_TEXTURE_2D, old_tex); |     glBindTexture(GL_TEXTURE_2D, old_tex); | ||||||
| 
 | 
 | ||||||
|  | @ -311,15 +343,20 @@ bool TextureRuntime::BlitTextures(Surface& source, Surface& dest, | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void TextureRuntime::GenerateMipmaps(Surface& surface, u32 max_level) { | void TextureRuntime::GenerateMipmaps(Surface& surface) { | ||||||
|     OpenGLState state = OpenGLState::GetCurState(); |     OpenGLState state = OpenGLState::GetCurState(); | ||||||
|     state.texture_units[0].texture_2d = surface.Handle(); | 
 | ||||||
|  |     const auto generate = [&](u32 index) { | ||||||
|  |         state.texture_units[0].texture_2d = surface.Handle(index); | ||||||
|         state.Apply(); |         state.Apply(); | ||||||
| 
 |         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, surface.levels - 1); | ||||||
|     glActiveTexture(GL_TEXTURE0); |  | ||||||
|     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level); |  | ||||||
| 
 |  | ||||||
|         glGenerateMipmap(GL_TEXTURE_2D); |         glGenerateMipmap(GL_TEXTURE_2D); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     generate(1); | ||||||
|  |     if (surface.HasNormalMap()) { | ||||||
|  |         generate(2); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const ReinterpreterList& TextureRuntime::GetPossibleReinterpretations( | const ReinterpreterList& TextureRuntime::GetPossibleReinterpretations( | ||||||
|  | @ -340,21 +377,11 @@ Surface::~Surface() { | ||||||
|     if (pixel_format == PixelFormat::Invalid || !alloc) { |     if (pixel_format == PixelFormat::Invalid || !alloc) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 |     runtime->Recycle(MakeTag(), std::move(alloc)); | ||||||
|     const HostTextureTag tag = { |  | ||||||
|         .tuple = alloc.tuple, |  | ||||||
|         .type = texture_type, |  | ||||||
|         .width = alloc.width, |  | ||||||
|         .height = alloc.height, |  | ||||||
|         .levels = alloc.levels, |  | ||||||
|         .res_scale = alloc.res_scale, |  | ||||||
|     }; |  | ||||||
|     runtime->Recycle(tag, std::move(alloc)); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Surface::Upload(const VideoCore::BufferTextureCopy& upload, | void Surface::Upload(const VideoCore::BufferTextureCopy& upload, | ||||||
|                      const VideoCore::StagingData& staging) { |                      const VideoCore::StagingData& staging) { | ||||||
|     // Ensure no bad interactions with GL_UNPACK_ALIGNMENT
 |  | ||||||
|     ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0); |     ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0); | ||||||
| 
 | 
 | ||||||
|     const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d; |     const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d; | ||||||
|  | @ -362,12 +389,10 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, | ||||||
|     const u32 unscaled_height = upload.texture_rect.GetHeight(); |     const u32 unscaled_height = upload.texture_rect.GetHeight(); | ||||||
|     glPixelStorei(GL_UNPACK_ROW_LENGTH, unscaled_width); |     glPixelStorei(GL_UNPACK_ROW_LENGTH, unscaled_width); | ||||||
| 
 | 
 | ||||||
|     // Bind the unscaled texture
 |  | ||||||
|     glActiveTexture(GL_TEXTURE0); |     glActiveTexture(GL_TEXTURE0); | ||||||
|     glBindTexture(GL_TEXTURE_2D, Handle(false)); |     glBindTexture(GL_TEXTURE_2D, Handle(0)); | ||||||
| 
 | 
 | ||||||
|     // Upload the requested rectangle of pixels
 |     const auto& tuple = alloc.tuple; | ||||||
|     const auto& tuple = runtime->GetFormatTuple(pixel_format); |  | ||||||
|     glTexSubImage2D(GL_TEXTURE_2D, upload.texture_level, upload.texture_rect.left, |     glTexSubImage2D(GL_TEXTURE_2D, upload.texture_level, upload.texture_rect.left, | ||||||
|                     upload.texture_rect.bottom, unscaled_width, unscaled_height, tuple.format, |                     upload.texture_rect.bottom, unscaled_width, unscaled_height, tuple.format, | ||||||
|                     tuple.type, staging.mapped.data()); |                     tuple.type, staging.mapped.data()); | ||||||
|  | @ -381,17 +406,61 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, | ||||||
|         .src_rect = upload.texture_rect, |         .src_rect = upload.texture_rect, | ||||||
|         .dst_rect = upload.texture_rect * res_scale, |         .dst_rect = upload.texture_rect * res_scale, | ||||||
|     }; |     }; | ||||||
| 
 |  | ||||||
|     // If texture filtering is enabled attempt to upscale with that, otherwise fallback
 |  | ||||||
|     // to normal blit.
 |  | ||||||
|     if (res_scale != 1 && !runtime->blit_helper.Filter(*this, blit)) { |     if (res_scale != 1 && !runtime->blit_helper.Filter(*this, blit)) { | ||||||
|         BlitScale(blit, true); |         BlitScale(blit, true); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void Surface::UploadCustom(const VideoCore::Material* material, u32 level) { | ||||||
|  |     const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d; | ||||||
|  |     const auto& tuple = alloc.tuple; | ||||||
|  |     const u32 width = material->width; | ||||||
|  |     const u32 height = material->height; | ||||||
|  |     const auto color = material->textures[0]; | ||||||
|  |     const Common::Rectangle filter_rect{0U, height, width, 0U}; | ||||||
|  | 
 | ||||||
|  |     glActiveTexture(GL_TEXTURE0); | ||||||
|  |     glPixelStorei(GL_UNPACK_ROW_LENGTH, width); | ||||||
|  | 
 | ||||||
|  |     glBindTexture(GL_TEXTURE_2D, Handle(0)); | ||||||
|  |     if (VideoCore::IsCustomFormatCompressed(custom_format)) { | ||||||
|  |         const GLsizei image_size = static_cast<GLsizei>(color->data.size()); | ||||||
|  |         glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, | ||||||
|  |                                   image_size, color->data.data()); | ||||||
|  |     } else { | ||||||
|  |         glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, tuple.type, | ||||||
|  |                         color->data.data()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const VideoCore::TextureBlit blit = { | ||||||
|  |         .src_rect = filter_rect, | ||||||
|  |         .dst_rect = filter_rect, | ||||||
|  |     }; | ||||||
|  |     if (res_scale != 1 && !runtime->blit_helper.Filter(*this, blit)) { | ||||||
|  |         BlitScale(blit, true); | ||||||
|  |     } | ||||||
|  |     for (u32 i = 1; i < VideoCore::MAX_MAPS; i++) { | ||||||
|  |         const auto texture = material->textures[i]; | ||||||
|  |         if (!texture) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         glBindTexture(GL_TEXTURE_2D, Handle(i + 1)); | ||||||
|  |         if (VideoCore::IsCustomFormatCompressed(custom_format)) { | ||||||
|  |             const GLsizei image_size = static_cast<GLsizei>(texture->data.size()); | ||||||
|  |             glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, | ||||||
|  |                                       image_size, texture->data.data()); | ||||||
|  |         } else { | ||||||
|  |             glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, tuple.type, | ||||||
|  |                             texture->data.data()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | ||||||
|  |     glBindTexture(GL_TEXTURE_2D, old_tex); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void Surface::Download(const VideoCore::BufferTextureCopy& download, | void Surface::Download(const VideoCore::BufferTextureCopy& download, | ||||||
|                        const VideoCore::StagingData& staging) { |                        const VideoCore::StagingData& staging) { | ||||||
|     // Ensure no bad interactions with GL_PACK_ALIGNMENT
 |  | ||||||
|     ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0); |     ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0); | ||||||
| 
 | 
 | ||||||
|     const u32 unscaled_width = download.texture_rect.GetWidth(); |     const u32 unscaled_width = download.texture_rect.GetWidth(); | ||||||
|  | @ -414,11 +483,9 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download, | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const u32 fbo_index = FboIndex(type); |  | ||||||
| 
 |  | ||||||
|     OpenGLState state = OpenGLState::GetCurState(); |     OpenGLState state = OpenGLState::GetCurState(); | ||||||
|     state.scissor.enabled = false; |     state.scissor.enabled = false; | ||||||
|     state.draw.read_framebuffer = runtime->read_fbos[fbo_index].handle; |     state.draw.read_framebuffer = runtime->read_fbos[FboIndex(type)].handle; | ||||||
|     state.Apply(); |     state.Apply(); | ||||||
| 
 | 
 | ||||||
|     Attach(GL_READ_FRAMEBUFFER, download.texture_level, 0, false); |     Attach(GL_READ_FRAMEBUFFER, download.texture_level, 0, false); | ||||||
|  | @ -428,14 +495,11 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download, | ||||||
|     glReadPixels(download.texture_rect.left, download.texture_rect.bottom, unscaled_width, |     glReadPixels(download.texture_rect.left, download.texture_rect.bottom, unscaled_width, | ||||||
|                  unscaled_height, tuple.format, tuple.type, staging.mapped.data()); |                  unscaled_height, tuple.format, tuple.type, staging.mapped.data()); | ||||||
| 
 | 
 | ||||||
|     // Restore previous state
 |  | ||||||
|     glPixelStorei(GL_PACK_ROW_LENGTH, 0); |     glPixelStorei(GL_PACK_ROW_LENGTH, 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download, | bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download, | ||||||
|                                  const VideoCore::StagingData& staging) { |                                  const VideoCore::StagingData& staging) { | ||||||
|     // If a partial download is requested and ARB_get_texture_sub_image (core in 4.5)
 |  | ||||||
|     // is not available we cannot proceed further.
 |  | ||||||
|     const bool is_full_download = download.texture_rect == GetRect(); |     const bool is_full_download = download.texture_rect == GetRect(); | ||||||
|     const bool has_sub_image = driver->HasArbGetTextureSubImage(); |     const bool has_sub_image = driver->HasArbGetTextureSubImage(); | ||||||
|     if (driver->IsOpenGLES() || (!is_full_download && !has_sub_image)) { |     if (driver->IsOpenGLES() || (!is_full_download && !has_sub_image)) { | ||||||
|  | @ -452,7 +516,7 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download, | ||||||
|     // Prefer glGetTextureSubImage in most cases since it's the fastest and most convenient option
 |     // Prefer glGetTextureSubImage in most cases since it's the fastest and most convenient option
 | ||||||
|     if (has_sub_image) { |     if (has_sub_image) { | ||||||
|         const GLsizei buf_size = static_cast<GLsizei>(staging.mapped.size()); |         const GLsizei buf_size = static_cast<GLsizei>(staging.mapped.size()); | ||||||
|         glGetTextureSubImage(Handle(false), download.texture_level, download.texture_rect.left, |         glGetTextureSubImage(Handle(0), download.texture_level, download.texture_rect.left, | ||||||
|                              download.texture_rect.bottom, 0, download.texture_rect.GetWidth(), |                              download.texture_rect.bottom, 0, download.texture_rect.GetWidth(), | ||||||
|                              download.texture_rect.GetHeight(), 1, tuple.format, tuple.type, |                              download.texture_rect.GetHeight(), 1, tuple.format, tuple.type, | ||||||
|                              buf_size, staging.mapped.data()); |                              buf_size, staging.mapped.data()); | ||||||
|  | @ -461,7 +525,7 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download, | ||||||
| 
 | 
 | ||||||
|     // This should only trigger for full texture downloads in oldish intel drivers
 |     // This should only trigger for full texture downloads in oldish intel drivers
 | ||||||
|     // that only support up to 4.3
 |     // that only support up to 4.3
 | ||||||
|     glBindTexture(GL_TEXTURE_2D, Handle(false)); |     glBindTexture(GL_TEXTURE_2D, Handle(0)); | ||||||
|     glGetTexImage(GL_TEXTURE_2D, download.texture_level, tuple.format, tuple.type, |     glGetTexImage(GL_TEXTURE_2D, download.texture_level, tuple.format, tuple.type, | ||||||
|                   staging.mapped.data()); |                   staging.mapped.data()); | ||||||
|     glBindTexture(GL_TEXTURE_2D, old_tex); |     glBindTexture(GL_TEXTURE_2D, old_tex); | ||||||
|  | @ -470,20 +534,20 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) { | void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) { | ||||||
|     const GLuint handle = Handle(scaled); |     const GLuint handle = Handle(static_cast<u32>(scaled)); | ||||||
|     const GLenum textarget = texture_type == VideoCore::TextureType::CubeMap |     const GLenum textarget = texture_type == TextureType::CubeMap | ||||||
|                                  ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer |                                  ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer | ||||||
|                                  : GL_TEXTURE_2D; |                                  : GL_TEXTURE_2D; | ||||||
| 
 | 
 | ||||||
|     switch (type) { |     switch (type) { | ||||||
|     case VideoCore::SurfaceType::Color: |     case SurfaceType::Color: | ||||||
|     case VideoCore::SurfaceType::Texture: |     case SurfaceType::Texture: | ||||||
|         glFramebufferTexture2D(target, GL_COLOR_ATTACHMENT0, textarget, handle, level); |         glFramebufferTexture2D(target, GL_COLOR_ATTACHMENT0, textarget, handle, level); | ||||||
|         break; |         break; | ||||||
|     case VideoCore::SurfaceType::Depth: |     case SurfaceType::Depth: | ||||||
|         glFramebufferTexture2D(target, GL_DEPTH_ATTACHMENT, textarget, handle, level); |         glFramebufferTexture2D(target, GL_DEPTH_ATTACHMENT, textarget, handle, level); | ||||||
|         break; |         break; | ||||||
|     case VideoCore::SurfaceType::DepthStencil: |     case SurfaceType::DepthStencil: | ||||||
|         glFramebufferTexture2D(target, GL_DEPTH_STENCIL_ATTACHMENT, textarget, handle, level); |         glFramebufferTexture2D(target, GL_DEPTH_STENCIL_ATTACHMENT, textarget, handle, level); | ||||||
|         break; |         break; | ||||||
|     default: |     default: | ||||||
|  | @ -491,6 +555,30 @@ void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool Surface::Swap(const VideoCore::Material* mat) { | ||||||
|  |     const VideoCore::CustomPixelFormat format{mat->format}; | ||||||
|  |     if (!driver->IsCustomFormatSupported(format)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     runtime->Recycle(MakeTag(), std::move(alloc)); | ||||||
|  | 
 | ||||||
|  |     SurfaceParams params = *this; | ||||||
|  |     params.width = mat->width; | ||||||
|  |     params.height = mat->height; | ||||||
|  |     params.custom_format = mat->format; | ||||||
|  |     alloc = runtime->Allocate(params, mat); | ||||||
|  | 
 | ||||||
|  |     LOG_DEBUG(Render_OpenGL, "Swapped {}x{} {} surface at address {:#x} to {}x{} {}", | ||||||
|  |               GetScaledWidth(), GetScaledHeight(), VideoCore::PixelFormatAsString(pixel_format), | ||||||
|  |               addr, width, height, VideoCore::CustomPixelFormatAsString(format)); | ||||||
|  | 
 | ||||||
|  |     is_custom = true; | ||||||
|  |     custom_format = format; | ||||||
|  |     material = mat; | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| u32 Surface::GetInternalBytesPerPixel() const { | u32 Surface::GetInternalBytesPerPixel() const { | ||||||
|     // RGB8 is converted to RGBA8 on OpenGL ES since it doesn't support BGR8
 |     // RGB8 is converted to RGBA8 on OpenGL ES since it doesn't support BGR8
 | ||||||
|     if (driver->IsOpenGLES() && pixel_format == VideoCore::PixelFormat::RGB8) { |     if (driver->IsOpenGLES() && pixel_format == VideoCore::PixelFormat::RGB8) { | ||||||
|  | @ -518,6 +606,19 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) { | ||||||
|                       blit.dst_rect.right, blit.dst_rect.top, buffer_mask, filter); |                       blit.dst_rect.right, blit.dst_rect.top, buffer_mask, filter); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | HostTextureTag Surface::MakeTag() const noexcept { | ||||||
|  |     return HostTextureTag{ | ||||||
|  |         .width = alloc.width, | ||||||
|  |         .height = alloc.height, | ||||||
|  |         .levels = alloc.levels, | ||||||
|  |         .res_scale = alloc.res_scale, | ||||||
|  |         .tuple = alloc.tuple, | ||||||
|  |         .type = texture_type, | ||||||
|  |         .is_custom = is_custom, | ||||||
|  |         .has_normal = HasNormalMap(), | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 color_level, | Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 color_level, | ||||||
|                          Surface* const depth_stencil, u32 depth_level, const Pica::Regs& regs, |                          Surface* const depth_stencil, u32 depth_level, const Pica::Regs& regs, | ||||||
|                          Common::Rectangle<u32> surfaces_rect) |                          Common::Rectangle<u32> surfaces_rect) | ||||||
|  | @ -527,7 +628,7 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 colo | ||||||
|     const bool shadow_rendering = regs.framebuffer.IsShadowRendering(); |     const bool shadow_rendering = regs.framebuffer.IsShadowRendering(); | ||||||
|     const bool has_stencil = regs.framebuffer.HasStencil(); |     const bool has_stencil = regs.framebuffer.HasStencil(); | ||||||
|     if (shadow_rendering && !color) { |     if (shadow_rendering && !color) { | ||||||
|         return; // Framebuffer won't get used
 |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (color) { |     if (color) { | ||||||
|  | @ -574,19 +675,15 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 colo | ||||||
|                                color ? color->Handle() : 0, color_level); |                                color ? color->Handle() : 0, color_level); | ||||||
|         if (depth_stencil) { |         if (depth_stencil) { | ||||||
|             if (has_stencil) { |             if (has_stencil) { | ||||||
|                 // Attach both depth and stencil
 |  | ||||||
|                 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, |                 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, | ||||||
|                                        GL_TEXTURE_2D, depth_stencil->Handle(), depth_level); |                                        GL_TEXTURE_2D, depth_stencil->Handle(), depth_level); | ||||||
|             } else { |             } else { | ||||||
|                 // Attach depth
 |  | ||||||
|                 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, |                 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, | ||||||
|                                        depth_stencil->Handle(), depth_level); |                                        depth_stencil->Handle(), depth_level); | ||||||
|                 // Clear stencil attachment
 |  | ||||||
|                 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, |                 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, | ||||||
|                                        0); |                                        0); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             // Clear both depth and stencil attachment
 |  | ||||||
|             glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, |             glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, | ||||||
|                                    0, 0); |                                    0, 0); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -10,8 +10,9 @@ | ||||||
| #include "video_core/renderer_opengl/gl_format_reinterpreter.h" | #include "video_core/renderer_opengl/gl_format_reinterpreter.h" | ||||||
| 
 | 
 | ||||||
| namespace VideoCore { | namespace VideoCore { | ||||||
|  | struct Material; | ||||||
| class RendererBase; | class RendererBase; | ||||||
| } | } // namespace VideoCore
 | ||||||
| 
 | 
 | ||||||
| namespace OpenGL { | namespace OpenGL { | ||||||
| 
 | 
 | ||||||
|  | @ -27,17 +28,19 @@ struct FormatTuple { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| struct HostTextureTag { | struct HostTextureTag { | ||||||
|     FormatTuple tuple{}; |     u32 width; | ||||||
|     VideoCore::TextureType type{}; |     u32 height; | ||||||
|     u32 width = 0; |     u32 levels; | ||||||
|     u32 height = 0; |     u32 res_scale; | ||||||
|     u32 levels = 1; |     FormatTuple tuple; | ||||||
|     u32 res_scale = 1; |     VideoCore::TextureType type; | ||||||
|  |     bool is_custom; | ||||||
|  |     bool has_normal; | ||||||
| 
 | 
 | ||||||
|     bool operator==(const HostTextureTag& other) const noexcept { |     bool operator==(const HostTextureTag& other) const noexcept { | ||||||
|         return std::tie(tuple, type, width, height, levels, res_scale) == |         return std::tie(tuple, type, width, height, levels, res_scale, is_custom, has_normal) == | ||||||
|                std::tie(other.tuple, other.type, other.width, other.height, other.levels, |                std::tie(other.tuple, other.type, other.width, other.height, other.levels, | ||||||
|                         other.res_scale); |                         other.res_scale, other.is_custom, other.has_normal); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     struct Hash { |     struct Hash { | ||||||
|  | @ -46,10 +49,12 @@ struct HostTextureTag { | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
|  | static_assert(std::has_unique_object_representations_v<HostTextureTag>, | ||||||
|  |               "HostTextureTag is not suitable for hashing!"); | ||||||
| 
 | 
 | ||||||
| struct Allocation { | struct Allocation { | ||||||
|     std::array<OGLTexture, 2> textures; |     std::array<OGLTexture, 3> textures; | ||||||
|     std::array<GLuint, 2> handles; |     std::array<GLuint, 3> handles; | ||||||
|     FormatTuple tuple; |     FormatTuple tuple; | ||||||
|     u32 width; |     u32 width; | ||||||
|     u32 height; |     u32 height; | ||||||
|  | @ -59,15 +64,10 @@ struct Allocation { | ||||||
|     operator bool() const noexcept { |     operator bool() const noexcept { | ||||||
|         return textures[0].handle; |         return textures[0].handle; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     bool Matches(u32 width_, u32 height_, u32 levels_, const FormatTuple& tuple_) const { |  | ||||||
|         return std::tie(width, height, levels, tuple) == std::tie(width_, height_, levels_, tuple_); |  | ||||||
|     } |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class Surface; | class Surface; | ||||||
| class Driver; | class Driver; | ||||||
| struct CachedTextureCube; |  | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Provides texture manipulation functions to the rasterizer cache |  * Provides texture manipulation functions to the rasterizer cache | ||||||
|  | @ -82,6 +82,9 @@ public: | ||||||
|     explicit TextureRuntime(const Driver& driver, VideoCore::RendererBase& renderer); |     explicit TextureRuntime(const Driver& driver, VideoCore::RendererBase& renderer); | ||||||
|     ~TextureRuntime(); |     ~TextureRuntime(); | ||||||
| 
 | 
 | ||||||
|  |     /// Clears all cached runtime resources
 | ||||||
|  |     void Reset(); | ||||||
|  | 
 | ||||||
|     /// Returns true if the provided pixel format cannot be used natively by the runtime.
 |     /// Returns true if the provided pixel format cannot be used natively by the runtime.
 | ||||||
|     bool NeedsConversion(VideoCore::PixelFormat pixel_format) const; |     bool NeedsConversion(VideoCore::PixelFormat pixel_format) const; | ||||||
| 
 | 
 | ||||||
|  | @ -90,12 +93,14 @@ public: | ||||||
| 
 | 
 | ||||||
|     /// Returns the OpenGL format tuple associated with the provided pixel format
 |     /// Returns the OpenGL format tuple associated with the provided pixel format
 | ||||||
|     const FormatTuple& GetFormatTuple(VideoCore::PixelFormat pixel_format) const; |     const FormatTuple& GetFormatTuple(VideoCore::PixelFormat pixel_format) const; | ||||||
|  |     const FormatTuple& GetFormatTuple(VideoCore::CustomPixelFormat pixel_format); | ||||||
| 
 | 
 | ||||||
|     /// Takes back ownership of the allocation for recycling
 |     /// Takes back ownership of the allocation for recycling
 | ||||||
|     void Recycle(const HostTextureTag tag, Allocation&& alloc); |     void Recycle(const HostTextureTag tag, Allocation&& alloc); | ||||||
| 
 | 
 | ||||||
|     /// Allocates an OpenGL texture with the specified dimentions and format
 |     /// Allocates a texture with the specified dimentions and format
 | ||||||
|     Allocation Allocate(const VideoCore::SurfaceParams& params); |     Allocation Allocate(const VideoCore::SurfaceParams& params, | ||||||
|  |                         const VideoCore::Material* material = nullptr); | ||||||
| 
 | 
 | ||||||
|     /// Fills the rectangle of the texture with the clear value provided
 |     /// Fills the rectangle of the texture with the clear value provided
 | ||||||
|     bool ClearTexture(Surface& surface, const VideoCore::TextureClear& clear); |     bool ClearTexture(Surface& surface, const VideoCore::TextureClear& clear); | ||||||
|  | @ -107,7 +112,7 @@ public: | ||||||
|     bool BlitTextures(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); |     bool BlitTextures(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); | ||||||
| 
 | 
 | ||||||
|     /// Generates mipmaps for all the available levels of the texture
 |     /// Generates mipmaps for all the available levels of the texture
 | ||||||
|     void GenerateMipmaps(Surface& surface, u32 max_level); |     void GenerateMipmaps(Surface& surface); | ||||||
| 
 | 
 | ||||||
|     /// Returns all source formats that support reinterpretation to the dest format
 |     /// Returns all source formats that support reinterpretation to the dest format
 | ||||||
|     const ReinterpreterList& GetPossibleReinterpretations(VideoCore::PixelFormat dest_format) const; |     const ReinterpreterList& GetPossibleReinterpretations(VideoCore::PixelFormat dest_format) const; | ||||||
|  | @ -123,7 +128,7 @@ private: | ||||||
|     BlitHelper blit_helper; |     BlitHelper blit_helper; | ||||||
|     std::vector<u8> staging_buffer; |     std::vector<u8> staging_buffer; | ||||||
|     std::array<ReinterpreterList, VideoCore::PIXEL_FORMAT_COUNT> reinterpreters; |     std::array<ReinterpreterList, VideoCore::PIXEL_FORMAT_COUNT> reinterpreters; | ||||||
|     std::unordered_multimap<HostTextureTag, Allocation, HostTextureTag::Hash> recycler; |     std::unordered_multimap<HostTextureTag, Allocation, HostTextureTag::Hash> alloc_cache; | ||||||
|     std::unordered_map<u64, OGLFramebuffer, Common::IdentityHash<u64>> framebuffer_cache; |     std::unordered_map<u64, OGLFramebuffer, Common::IdentityHash<u64>> framebuffer_cache; | ||||||
|     std::array<OGLFramebuffer, 3> draw_fbos; |     std::array<OGLFramebuffer, 3> draw_fbos; | ||||||
|     std::array<OGLFramebuffer, 3> read_fbos; |     std::array<OGLFramebuffer, 3> read_fbos; | ||||||
|  | @ -140,9 +145,9 @@ public: | ||||||
|     Surface(Surface&& o) noexcept = default; |     Surface(Surface&& o) noexcept = default; | ||||||
|     Surface& operator=(Surface&& o) noexcept = default; |     Surface& operator=(Surface&& o) noexcept = default; | ||||||
| 
 | 
 | ||||||
|     /// Returns the surface image handle
 |     /// Returns the surface image handle at the provided index.
 | ||||||
|     GLuint Handle(bool scaled = true) const noexcept { |     GLuint Handle(u32 index = 1) const noexcept { | ||||||
|         return alloc.handles[static_cast<u32>(scaled)]; |         return alloc.handles[index]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Returns the tuple of the surface allocation.
 |     /// Returns the tuple of the surface allocation.
 | ||||||
|  | @ -150,9 +155,20 @@ public: | ||||||
|         return alloc.tuple; |         return alloc.tuple; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// Returns the extent of the underlying surface allocation
 | ||||||
|  |     VideoCore::Extent Extent() const noexcept { | ||||||
|  |         return { | ||||||
|  |             .width = alloc.width, | ||||||
|  |             .height = alloc.height, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /// Uploads pixel data in staging to a rectangle region of the surface texture
 |     /// Uploads pixel data in staging to a rectangle region of the surface texture
 | ||||||
|     void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging); |     void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging); | ||||||
| 
 | 
 | ||||||
|  |     /// Uploads the custom material to the surface allocation.
 | ||||||
|  |     void UploadCustom(const VideoCore::Material* material, u32 level); | ||||||
|  | 
 | ||||||
|     /// Downloads pixel data to staging from a rectangle region of the surface texture
 |     /// Downloads pixel data to staging from a rectangle region of the surface texture
 | ||||||
|     void Download(const VideoCore::BufferTextureCopy& download, |     void Download(const VideoCore::BufferTextureCopy& download, | ||||||
|                   const VideoCore::StagingData& staging); |                   const VideoCore::StagingData& staging); | ||||||
|  | @ -160,6 +176,9 @@ public: | ||||||
|     /// Attaches a handle of surface to the specified framebuffer target
 |     /// Attaches a handle of surface to the specified framebuffer target
 | ||||||
|     void Attach(GLenum target, u32 level, u32 layer, bool scaled = true); |     void Attach(GLenum target, u32 level, u32 layer, bool scaled = true); | ||||||
| 
 | 
 | ||||||
|  |     /// Swaps the internal allocation to match the provided material
 | ||||||
|  |     bool Swap(const VideoCore::Material* material); | ||||||
|  | 
 | ||||||
|     /// Returns the bpp of the internal surface format
 |     /// Returns the bpp of the internal surface format
 | ||||||
|     u32 GetInternalBytesPerPixel() const; |     u32 GetInternalBytesPerPixel() const; | ||||||
| 
 | 
 | ||||||
|  | @ -171,6 +190,9 @@ private: | ||||||
|     bool DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download, |     bool DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download, | ||||||
|                             const VideoCore::StagingData& staging); |                             const VideoCore::StagingData& staging); | ||||||
| 
 | 
 | ||||||
|  |     /// Returns the texture tag of the current allocation
 | ||||||
|  |     HostTextureTag MakeTag() const noexcept; | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     const Driver* driver; |     const Driver* driver; | ||||||
|     TextureRuntime* runtime; |     TextureRuntime* runtime; | ||||||
|  |  | ||||||
|  | @ -313,7 +313,8 @@ RendererOpenGL::RendererOpenGL(Core::System& system, Frontend::EmuWindow& window | ||||||
|     } |     } | ||||||
|     frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>(); |     frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>(); | ||||||
|     InitOpenGLObjects(); |     InitOpenGLObjects(); | ||||||
|     rasterizer = std::make_unique<RasterizerOpenGL>(system.Memory(), *this, driver); |     rasterizer = std::make_unique<RasterizerOpenGL>(system.Memory(), system.CustomTexManager(), | ||||||
|  |                                                     *this, driver); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| RendererOpenGL::~RendererOpenGL() = default; | RendererOpenGL::~RendererOpenGL() = default; | ||||||
|  | @ -347,6 +348,8 @@ void RendererOpenGL::SwapBuffers() { | ||||||
| 
 | 
 | ||||||
|     EndFrame(); |     EndFrame(); | ||||||
|     prev_state.Apply(); |     prev_state.Apply(); | ||||||
|  | 
 | ||||||
|  |     rasterizer->TickFrame(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RendererOpenGL::RenderScreenshot() { | void RendererOpenGL::RenderScreenshot() { | ||||||
|  | @ -1169,9 +1172,6 @@ void RendererOpenGL::TryPresent(int timeout_ms, bool is_secondary) { | ||||||
|     glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); |     glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Updates the framerate
 |  | ||||||
| void RendererOpenGL::UpdateFramerate() {} |  | ||||||
| 
 |  | ||||||
| void RendererOpenGL::PrepareVideoDumping() { | void RendererOpenGL::PrepareVideoDumping() { | ||||||
|     auto* mailbox = static_cast<OGLVideoDumpingMailbox*>(frame_dumper.mailbox.get()); |     auto* mailbox = static_cast<OGLVideoDumpingMailbox*>(frame_dumper.mailbox.get()); | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -94,7 +94,6 @@ private: | ||||||
|                                        float h); |                                        float h); | ||||||
|     void DrawSingleScreenStereo(const ScreenInfo& screen_info_l, const ScreenInfo& screen_info_r, |     void DrawSingleScreenStereo(const ScreenInfo& screen_info_l, const ScreenInfo& screen_info_r, | ||||||
|                                 float x, float y, float w, float h); |                                 float x, float y, float w, float h); | ||||||
|     void UpdateFramerate(); |  | ||||||
| 
 | 
 | ||||||
|     // Loads framebuffer from emulated memory into the display information structure
 |     // Loads framebuffer from emulated memory into the display information structure
 | ||||||
|     void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, |     void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue