mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +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,6 +3,12 @@ add_subdirectory(host_shaders) | |||
| add_library(video_core STATIC | ||||
|     command_processor.cpp | ||||
|     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.h | ||||
|     geometry_pipeline.cpp | ||||
|  | @ -120,7 +126,7 @@ target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE}) | |||
| create_target_directory_groups(video_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}) | ||||
| 
 | ||||
| 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, | ||||
| }; | ||||
| 
 | ||||
| enum class TextureType : u32 { | ||||
| enum class TextureType : u16 { | ||||
|     Texture2D = 0, | ||||
|     CubeMap = 1, | ||||
| }; | ||||
|  | @ -99,10 +99,10 @@ constexpr SurfaceType GetFormatType(PixelFormat format) { | |||
|     return FORMAT_MAP[index].type; | ||||
| } | ||||
| 
 | ||||
| std::string_view PixelFormatAsString(PixelFormat format); | ||||
| 
 | ||||
| bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format); | ||||
| 
 | ||||
| std::string_view PixelFormatAsString(PixelFormat format); | ||||
| 
 | ||||
| PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format); | ||||
| 
 | ||||
| PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format); | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| #include "common/logging/log.h" | ||||
| #include "common/microprofile.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/regs.h" | ||||
| #include "video_core/renderer_base.h" | ||||
|  | @ -19,6 +20,15 @@ namespace { | |||
| 
 | ||||
| MICROPROFILE_DEFINE(RasterizerCache_CopySurface, "RasterizerCache", "CopySurface", | ||||
|                     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) { | ||||
|     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
 | ||||
| 
 | ||||
| RasterizerCache::RasterizerCache(Memory::MemorySystem& memory_, OpenGL::TextureRuntime& runtime_, | ||||
|                                  Pica::Regs& regs_, RendererBase& renderer_) | ||||
|     : memory{memory_}, runtime{runtime_}, regs{regs_}, renderer{renderer_}, | ||||
|       resolution_scale_factor{renderer.GetResolutionScaleFactor()}, | ||||
|       use_filter{Settings::values.texture_filter.GetValue() != Settings::TextureFilter::None} {} | ||||
| RasterizerCache::RasterizerCache(Memory::MemorySystem& memory_, | ||||
|                                  CustomTexManager& custom_tex_manager_, | ||||
|                                  OpenGL::TextureRuntime& runtime_, Pica::Regs& regs_, | ||||
|                                  RendererBase& renderer_) | ||||
|     : 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() { | ||||
| #ifndef ANDROID | ||||
|  | @ -132,6 +146,32 @@ RasterizerCache::~RasterizerCache() { | |||
| #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) { | ||||
|     // Texture copy size is aligned to 16 byte units
 | ||||
|     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) { | ||||
|     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_height = config.GetHeight(); | ||||
|     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
 | ||||
|     // generating them from the base level.
 | ||||
|     if (surface->res_scale != 1 && level != 0) { | ||||
|         runtime.GenerateMipmaps(*surface, surface->levels - 1); | ||||
|         runtime.GenerateMipmaps(*surface); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void RasterizerCache::UploadSurface(const SurfaceRef& surface, SurfaceInterval interval) { | ||||
|     MICROPROFILE_SCOPE(RasterizerCache_UploadSurface); | ||||
| 
 | ||||
|     const SurfaceParams load_info = surface->FromInterval(interval); | ||||
|     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, | ||||
|                   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 = { | ||||
|         .buffer_offset = 0, | ||||
|         .buffer_size = staging.size, | ||||
|  | @ -847,7 +886,49 @@ void RasterizerCache::UploadSurface(const SurfaceRef& surface, SurfaceInterval i | |||
|     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) { | ||||
|     MICROPROFILE_SCOPE(RasterizerCache_DownloadSurface); | ||||
| 
 | ||||
|     const SurfaceParams flush_info = surface->FromInterval(interval); | ||||
|     const u32 flush_start = boost::icl::first(interval); | ||||
|     const u32 flush_end = boost::icl::last_next(interval); | ||||
|  |  | |||
|  | @ -4,7 +4,9 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| #include <boost/icl/interval_map.hpp> | ||||
| #include <boost/icl/interval_set.hpp> | ||||
| #include "video_core/rasterizer_cache/surface_base.h" | ||||
|  | @ -28,6 +30,8 @@ enum class ScaleMatch { | |||
|     Ignore   // accept every scaled res
 | ||||
| }; | ||||
| 
 | ||||
| class CustomTexManager; | ||||
| struct CustomTexture; | ||||
| class RendererBase; | ||||
| 
 | ||||
| class RasterizerCache : NonCopyable { | ||||
|  | @ -62,10 +66,13 @@ public: | |||
|     }; | ||||
| 
 | ||||
| public: | ||||
|     RasterizerCache(Memory::MemorySystem& memory, OpenGL::TextureRuntime& runtime, Pica::Regs& regs, | ||||
|                     RendererBase& renderer); | ||||
|     RasterizerCache(Memory::MemorySystem& memory, CustomTexManager& custom_tex_manager, | ||||
|                     OpenGL::TextureRuntime& runtime, Pica::Regs& regs, RendererBase& renderer); | ||||
|     ~RasterizerCache(); | ||||
| 
 | ||||
|     /// Notify the cache that a new frame has been queued
 | ||||
|     void TickFrame(); | ||||
| 
 | ||||
|     /// Perform hardware accelerated texture copy according to the provided configuration
 | ||||
|     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
 | ||||
|     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
 | ||||
|     void DownloadSurface(const SurfaceRef& surface, SurfaceInterval interval); | ||||
| 
 | ||||
|  | @ -158,6 +172,7 @@ private: | |||
| 
 | ||||
| private: | ||||
|     Memory::MemorySystem& memory; | ||||
|     CustomTexManager& custom_tex_manager; | ||||
|     OpenGL::TextureRuntime& runtime; | ||||
|     Pica::Regs& regs; | ||||
|     RendererBase& renderer; | ||||
|  | @ -168,7 +183,9 @@ private: | |||
|     u32 resolution_scale_factor; | ||||
|     RenderTargets render_targets; | ||||
|     std::unordered_map<TextureCubeConfig, TextureCube> texture_cube_cache; | ||||
|     bool use_filter{}; | ||||
|     bool use_filter; | ||||
|     bool dump_textures; | ||||
|     bool use_custom_textures; | ||||
| }; | ||||
| 
 | ||||
| } // namespace VideoCore
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/alignment.h" | ||||
| #include "video_core/custom_textures/material.h" | ||||
| #include "video_core/rasterizer_cache/surface_base.h" | ||||
| #include "video_core/texture/texture_decode.h" | ||||
| 
 | ||||
|  | @ -101,6 +102,10 @@ SurfaceInterval SurfaceBase::GetCopyableInterval(const SurfaceParams& params) co | |||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool SurfaceBase::HasNormalMap() const noexcept { | ||||
|     return material && material->Map(MapType::Normal) != nullptr; | ||||
| } | ||||
| 
 | ||||
| ClearValue SurfaceBase::MakeClearValue(PAddr copy_addr, PixelFormat dst_format) { | ||||
|     const SurfaceType dst_type = GetFormatType(dst_format); | ||||
|     const std::array fill_buffer = MakeFillBuffer(copy_addr); | ||||
|  |  | |||
|  | @ -11,6 +11,8 @@ namespace VideoCore { | |||
| 
 | ||||
| using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>; | ||||
| 
 | ||||
| struct Material; | ||||
| 
 | ||||
| class SurfaceBase : public SurfaceParams { | ||||
| public: | ||||
|     SurfaceBase(const SurfaceParams& params); | ||||
|  | @ -28,10 +30,17 @@ public: | |||
|     /// Returns the clear value used to validate another surface from this fill surface
 | ||||
|     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 { | ||||
|         return modification_tick; | ||||
|     } | ||||
| 
 | ||||
|     bool IsCustom() const noexcept { | ||||
|         return is_custom && custom_format != CustomPixelFormat::Invalid; | ||||
|     } | ||||
| 
 | ||||
|     bool IsRegionValid(SurfaceInterval interval) const { | ||||
|         return (invalid_regions.find(interval) == invalid_regions.end()); | ||||
|     } | ||||
|  | @ -57,6 +66,8 @@ private: | |||
| 
 | ||||
| public: | ||||
|     bool registered = false; | ||||
|     bool is_custom = false; | ||||
|     const Material* material = nullptr; | ||||
|     SurfaceRegions invalid_regions; | ||||
|     u32 fill_size = 0; | ||||
|     std::array<u8, 4> fill_data; | ||||
|  |  | |||
|  | @ -215,12 +215,12 @@ u32 SurfaceParams::LevelOf(PAddr level_addr) const { | |||
|     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_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 ? "scaled" : "unscaled"); | ||||
|                        custom ? "custom," : "", scaled ? "scaled" : "unscaled"); | ||||
| } | ||||
| 
 | ||||
| } // namespace VideoCore
 | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "video_core/custom_textures/custom_format.h" | ||||
| #include "video_core/rasterizer_cache/utils.h" | ||||
| 
 | ||||
| namespace VideoCore { | ||||
|  | @ -46,7 +47,7 @@ public: | |||
|     u32 LevelOf(PAddr addr) const; | ||||
| 
 | ||||
|     /// 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 { | ||||
|         return SurfaceInterval{addr, end}; | ||||
|  | @ -101,6 +102,7 @@ public: | |||
|     bool is_tiled = false; | ||||
|     TextureType texture_type = TextureType::Texture2D; | ||||
|     PixelFormat pixel_format = PixelFormat::Invalid; | ||||
|     CustomPixelFormat custom_format = CustomPixelFormat::Invalid; | ||||
|     SurfaceType type = SurfaceType::Invalid; | ||||
| 
 | ||||
|     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/refine_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/xbrz_freescale_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(); | ||||
|     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()}; | ||||
|     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()); | ||||
| 
 | ||||
|     // 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, | ||||
|                        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) { | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| void BlitHelper::FilterNearest(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||
|     state.texture_units[2].texture_2d = surface.Handle(false); | ||||
|     SetParams(nearest_program, surface.width, surface.height, blit.src_rect); | ||||
|     state.texture_units[2].texture_2d = surface.Handle(0); | ||||
|     SetParams(nearest_program, surface.Extent(), blit.src_rect); | ||||
|     Draw(nearest_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
|     glProgramUniform2f( | ||||
|         program.handle, 0, | ||||
|         static_cast<float>(src_rect.right - src_rect.left) / static_cast<float>(src_width), | ||||
|         static_cast<float>(src_rect.top - src_rect.bottom) / static_cast<float>(src_height)); | ||||
|         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_extent.height)); | ||||
|     glProgramUniform2f(program.handle, 1, | ||||
|                        static_cast<float>(src_rect.left) / static_cast<float>(src_width), | ||||
|                        static_cast<float>(src_rect.bottom) / static_cast<float>(src_height)); | ||||
|                        static_cast<float>(src_rect.left) / static_cast<float>(src_extent.width), | ||||
|                        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, | ||||
|                       Common::Rectangle<u32> dst_rect) { | ||||
|     state.draw.draw_framebuffer = dst_fbo; | ||||
|     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.y = dst_rect.bottom; | ||||
|     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); | ||||
|     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||||
| 
 | ||||
|     glClear(GL_COLOR_BUFFER_BIT); | ||||
|     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,8 +9,9 @@ | |||
| #include "video_core/renderer_opengl/gl_state.h" | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| struct Extent; | ||||
| struct TextureBlit; | ||||
| } | ||||
| } // namespace VideoCore
 | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
|  | @ -35,7 +36,7 @@ private: | |||
| 
 | ||||
|     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); | ||||
| 
 | ||||
|     void Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 dst_level, | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include "common/assert.h" | ||||
| #include "common/settings.h" | ||||
| #include "core/telemetry_session.h" | ||||
| #include "video_core/custom_textures/custom_format.h" | ||||
| #include "video_core/renderer_opengl/gl_driver.h" | ||||
| 
 | ||||
| namespace OpenGL { | ||||
|  | @ -103,6 +104,25 @@ bool Driver::HasDebugTool() { | |||
|     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() { | ||||
|     // Report the context version and the vendor string
 | ||||
|     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_clear_texture = GLAD_GL_ARB_clear_texture; | ||||
|     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_texture_compression_s3tc = GLAD_GL_EXT_texture_compression_s3tc; | ||||
|     is_suitable = GLAD_GL_VERSION_4_3 || GLAD_GL_ES_VERSION_3_1; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,6 +11,10 @@ namespace Core { | |||
| class TelemetrySession; | ||||
| } | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| enum class CustomPixelFormat : u32; | ||||
| } | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| enum class Vendor { | ||||
|  | @ -51,6 +55,9 @@ public: | |||
|     /// Returns true if any debug tool is attached
 | ||||
|     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
 | ||||
|     Vendor GetVendor() const { | ||||
|         return vendor; | ||||
|  | @ -114,6 +121,8 @@ private: | |||
|     bool arb_clear_texture{}; | ||||
|     bool arb_get_texture_sub_image{}; | ||||
|     bool ext_clip_cull_distance{}; | ||||
|     bool ext_texture_compression_s3tc{}; | ||||
|     bool arb_texture_compression_bptc{}; | ||||
| 
 | ||||
|     std::string_view gl_version{}; | ||||
|     std::string_view gpu_vendor{}; | ||||
|  |  | |||
|  | @ -73,11 +73,13 @@ GLenum MakeAttributeType(Pica::PipelineRegs::VertexAttributeFormat format) { | |||
| 
 | ||||
| } // Anonymous namespace
 | ||||
| 
 | ||||
| RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, VideoCore::RendererBase& renderer, | ||||
|                                    Driver& driver_) | ||||
| RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, | ||||
|                                    VideoCore::CustomTexManager& custom_tex_manager, | ||||
|                                    VideoCore::RendererBase& renderer, Driver& driver_) | ||||
|     : VideoCore::RasterizerAccelerated{memory}, driver{driver_}, runtime{driver, renderer}, | ||||
|       res_cache{memory, runtime, regs, renderer}, texture_buffer_size{TextureBufferSize()}, | ||||
|       vertex_buffer{driver, GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE}, | ||||
|       res_cache{memory, custom_tex_manager, runtime, regs, renderer}, | ||||
|       texture_buffer_size{TextureBufferSize()}, vertex_buffer{driver, GL_ARRAY_BUFFER, | ||||
|                                                               VERTEX_BUFFER_SIZE}, | ||||
|       uniform_buffer{driver, GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE}, | ||||
|       index_buffer{driver, GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE}, | ||||
|       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; | ||||
| 
 | ||||
| void RasterizerOpenGL::TickFrame() { | ||||
|     res_cache.TickFrame(); | ||||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading, | ||||
|                                          const VideoCore::DiskResourceLoadCallback& 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.width = draw_rect.GetWidth(); | ||||
|     state.scissor.height = draw_rect.GetHeight(); | ||||
|     state.Apply(); | ||||
| 
 | ||||
|     const u32 res_scale = framebuffer.ResolutionScale(); | ||||
|     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
 | ||||
|     SyncTextureUnits(framebuffer); | ||||
|     state.Apply(); | ||||
| 
 | ||||
|     // Sync and bind the shader
 | ||||
|     if (shader_dirty) { | ||||
|         SetShader(); | ||||
|         shader_program_manager->UseFragmentShader(regs, use_custom_normal); | ||||
|         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.
 | ||||
|             state.texture_units[texture_index].texture_2d = default_texture; | ||||
|         } else if (!IsFeedbackLoop(texture_index, framebuffer, *surface)) { | ||||
|             BindMaterial(texture_index, *surface); | ||||
|             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; | ||||
| } | ||||
| 
 | ||||
| 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, | ||||
|                                       Surface& surface) { | ||||
|     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_nz = 0; | ||||
|     state.image_shadow_buffer = 0; | ||||
|     state.Apply(); | ||||
| } | ||||
| 
 | ||||
| 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() { | ||||
|     state.clip_distance[1] = Pica::g_state.regs.rasterizer.clip_enable != 0; | ||||
| } | ||||
|  |  | |||
|  | @ -18,6 +18,10 @@ namespace VideoCore { | |||
| class RendererBase; | ||||
| } | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| class CustomTexManager; | ||||
| } | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| class Driver; | ||||
|  | @ -25,10 +29,12 @@ class ShaderProgramManager; | |||
| 
 | ||||
| class RasterizerOpenGL : public VideoCore::RasterizerAccelerated { | ||||
| public: | ||||
|     explicit RasterizerOpenGL(Memory::MemorySystem& memory, VideoCore::RendererBase& renderer, | ||||
|                               Driver& driver); | ||||
|     explicit RasterizerOpenGL(Memory::MemorySystem& memory, | ||||
|                               VideoCore::CustomTexManager& custom_tex_manager, | ||||
|                               VideoCore::RendererBase& renderer, Driver& driver); | ||||
|     ~RasterizerOpenGL() override; | ||||
| 
 | ||||
|     void TickFrame(); | ||||
|     void LoadDiskResources(const std::atomic_bool& stop_loading, | ||||
|                            const VideoCore::DiskResourceLoadCallback& callback) override; | ||||
| 
 | ||||
|  | @ -74,9 +80,6 @@ private: | |||
|     /// Syncs the clip enabled status to match the PICA register
 | ||||
|     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
 | ||||
|     void SyncCullMode(); | ||||
| 
 | ||||
|  | @ -126,6 +129,9 @@ private: | |||
|     /// Unbinds all special texture unit 0 texture configurations
 | ||||
|     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
 | ||||
|     void UploadUniforms(bool accelerate_draw); | ||||
| 
 | ||||
|  | @ -174,6 +180,7 @@ private: | |||
|     OGLTexture texture_buffer_lut_lf; | ||||
|     OGLTexture texture_buffer_lut_rg; | ||||
|     OGLTexture texture_buffer_lut_rgba; | ||||
|     bool use_custom_normal{}; | ||||
| }; | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ out gl_PerVertex { | |||
|     return out; | ||||
| } | ||||
| 
 | ||||
| PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs) { | ||||
| PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs, bool use_normal) { | ||||
|     PicaFSConfig res{}; | ||||
| 
 | ||||
|     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.use_custom_normal_map = use_normal; | ||||
| 
 | ||||
|     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"); | ||||
|             return "vec4(0.0)"; | ||||
|         } | ||||
|     case 4: | ||||
|         return "texture(tex_normal, texcoord0)"; | ||||
|     default: | ||||
|         UNREACHABLE(); | ||||
|         return ""; | ||||
|  | @ -642,7 +646,12 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) { | |||
|     const auto Perturbation = [&] { | ||||
|         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
 | ||||
|         out += fmt::format("vec3 surface_normal = {};\n", Perturbation()); | ||||
| 
 | ||||
|  | @ -1207,6 +1216,7 @@ out vec4 color; | |||
| uniform sampler2D tex0; | ||||
| uniform sampler2D tex1; | ||||
| uniform sampler2D tex2; | ||||
| uniform sampler2D tex_normal; //< Used for custom normal maps
 | ||||
| uniform samplerCube tex_cube; | ||||
| uniform samplerBuffer texture_buffer_lut_lf; | ||||
| uniform samplerBuffer texture_buffer_lut_rg; | ||||
|  |  | |||
|  | @ -117,6 +117,7 @@ struct PicaFSConfigState { | |||
| 
 | ||||
|     bool shadow_rendering; | ||||
|     bool shadow_texture_orthographic; | ||||
|     bool use_custom_normal_map; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -130,7 +131,7 @@ struct PicaFSConfigState { | |||
| struct PicaFSConfig : Common::HashableStruct<PicaFSConfigState> { | ||||
| 
 | ||||
|     /// 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 { | ||||
|         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, "tex2", TextureUnits::PicaTexture(2)); | ||||
|     SetShaderSamplerBinding(shader, "tex_cube", TextureUnits::TextureCube); | ||||
|     SetShaderSamplerBinding(shader, "tex_normal", TextureUnits::TextureNormalMap); | ||||
| 
 | ||||
|     // Set the texture samplers to correspond to different lookup table texture units
 | ||||
|     SetShaderSamplerBinding(shader, "texture_buffer_lut_lf", TextureUnits::TextureBufferLUT_LF); | ||||
|  | @ -415,8 +416,8 @@ void ShaderProgramManager::UseTrivialGeometryShader() { | |||
|     impl->current.gs_hash = 0; | ||||
| } | ||||
| 
 | ||||
| void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs) { | ||||
|     PicaFSConfig config = PicaFSConfig::BuildFromRegs(regs); | ||||
| void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs, bool use_normal) { | ||||
|     PicaFSConfig config = PicaFSConfig::BuildFromRegs(regs, use_normal); | ||||
|     auto [handle, result] = impl->fragment_shaders.Get(config); | ||||
|     impl->current.fs = handle; | ||||
|     impl->current.fs_hash = config.Hash(); | ||||
|  |  | |||
|  | @ -41,7 +41,7 @@ public: | |||
| 
 | ||||
|     void UseTrivialGeometryShader(); | ||||
| 
 | ||||
|     void UseFragmentShader(const Pica::Regs& config); | ||||
|     void UseFragmentShader(const Pica::Regs& config, bool use_normal); | ||||
| 
 | ||||
|     void ApplyTo(OpenGLState& state); | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ constexpr TextureUnit TextureCube{6}; | |||
| constexpr TextureUnit TextureBufferLUT_LF{3}; | ||||
| constexpr TextureUnit TextureBufferLUT_RG{4}; | ||||
| constexpr TextureUnit TextureBufferLUT_RGBA{5}; | ||||
| constexpr TextureUnit TextureNormalMap{7}; | ||||
| 
 | ||||
| } // namespace TextureUnits
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/scope_exit.h" | ||||
| #include "common/settings.h" | ||||
| #include "video_core/custom_textures/material.h" | ||||
| #include "video_core/regs.h" | ||||
| #include "video_core/renderer_base.h" | ||||
| #include "video_core/renderer_opengl/gl_driver.h" | ||||
|  | @ -14,8 +14,10 @@ namespace OpenGL { | |||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| using VideoCore::MapType; | ||||
| using VideoCore::PixelFormat; | ||||
| using VideoCore::SurfaceType; | ||||
| using VideoCore::TextureType; | ||||
| 
 | ||||
| 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
 | ||||
| }}; | ||||
| 
 | ||||
| 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 { | ||||
|     GLuint color; | ||||
|     GLuint depth; | ||||
|  | @ -109,17 +122,23 @@ TextureRuntime::TextureRuntime(const Driver& driver_, VideoCore::RendererBase& r | |||
|         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); | ||||
|         return reinterpreters[dst_index].push_back(std::move(obj)); | ||||
|     }; | ||||
| 
 | ||||
|     Register(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>()); | ||||
|     Register(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>()); | ||||
|     add_reinterpreter(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>()); | ||||
|     add_reinterpreter(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>()); | ||||
| } | ||||
| 
 | ||||
| TextureRuntime::~TextureRuntime() = default; | ||||
| 
 | ||||
| void TextureRuntime::Reset() { | ||||
|     alloc_cache.clear(); | ||||
|     framebuffer_cache.clear(); | ||||
| } | ||||
| 
 | ||||
| bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat pixel_format) const { | ||||
|     const bool should_convert = pixel_format == PixelFormat::RGBA8 || // Needs byteswap
 | ||||
|                                 pixel_format == PixelFormat::RGB8;    // Is converted to RGBA8
 | ||||
|  | @ -153,51 +172,64 @@ const FormatTuple& TextureRuntime::GetFormatTuple(PixelFormat pixel_format) cons | |||
|     return DEFAULT_TUPLE; | ||||
| } | ||||
| 
 | ||||
| void TextureRuntime::Recycle(const HostTextureTag tag, Allocation&& alloc) { | ||||
|     recycler.emplace(tag, std::move(alloc)); | ||||
| const FormatTuple& TextureRuntime::GetFormatTuple(VideoCore::CustomPixelFormat pixel_format) { | ||||
|     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) { | ||||
|     const u32 width = params.width; | ||||
|     const u32 height = params.height; | ||||
|     const u32 levels = params.levels; | ||||
| void TextureRuntime::Recycle(const HostTextureTag tag, Allocation&& alloc) { | ||||
|     alloc_cache.emplace(tag, std::move(alloc)); | ||||
| } | ||||
| 
 | ||||
| Allocation TextureRuntime::Allocate(const VideoCore::SurfaceParams& params, | ||||
|                                     const VideoCore::Material* material) { | ||||
|     const GLenum target = params.texture_type == VideoCore::TextureType::CubeMap | ||||
|                               ? GL_TEXTURE_CUBE_MAP | ||||
|                               : 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 = { | ||||
|         .width = params.width, | ||||
|         .height = params.height, | ||||
|         .levels = params.levels, | ||||
|         .res_scale = params.res_scale, | ||||
|         .tuple = tuple, | ||||
|         .type = params.texture_type, | ||||
|         .width = width, | ||||
|         .height = height, | ||||
|         .levels = levels, | ||||
|         .res_scale = params.res_scale, | ||||
|         .is_custom = is_custom, | ||||
|         .has_normal = has_normal, | ||||
|     }; | ||||
| 
 | ||||
|     if (auto it = recycler.find(key); it != recycler.end()) { | ||||
|         Allocation alloc = std::move(it->second); | ||||
|         ASSERT(alloc.res_scale == params.res_scale); | ||||
|         recycler.erase(it); | ||||
|     if (auto it = alloc_cache.find(key); it != alloc_cache.end()) { | ||||
|         auto alloc{std::move(it->second)}; | ||||
|         alloc_cache.erase(it); | ||||
|         return alloc; | ||||
|     } | ||||
| 
 | ||||
|     const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d; | ||||
|     glActiveTexture(GL_TEXTURE0); | ||||
| 
 | ||||
|     std::array<OGLTexture, 2> textures{}; | ||||
|     std::array<GLuint, 2> handles{}; | ||||
|     std::array<OGLTexture, 3> textures{}; | ||||
|     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); | ||||
| 
 | ||||
|     if (params.res_scale != 1) { | ||||
|         const u32 scaled_width = params.GetScaledWidth(); | ||||
|         const u32 scaled_height = params.GetScaledHeight(); | ||||
|         textures[1] = | ||||
|             MakeHandle(target, scaled_width, scaled_height, levels, tuple, params.DebugName(true)); | ||||
|         const u32 scaled_width = is_custom ? params.width : params.GetScaledWidth(); | ||||
|         const u32 scaled_height = is_custom ? params.height : params.GetScaledHeight(); | ||||
|         const auto& scaled_tuple = is_custom ? GetFormatTuple(PixelFormat::RGBA8) : tuple; | ||||
|         textures[1] = MakeHandle(target, scaled_width, scaled_height, params.levels, scaled_tuple, | ||||
|                                  params.DebugName(true, is_custom)); | ||||
|         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); | ||||
| 
 | ||||
|  | @ -311,15 +343,20 @@ bool TextureRuntime::BlitTextures(Surface& source, Surface& dest, | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void TextureRuntime::GenerateMipmaps(Surface& surface, u32 max_level) { | ||||
| void TextureRuntime::GenerateMipmaps(Surface& surface) { | ||||
|     OpenGLState state = OpenGLState::GetCurState(); | ||||
|     state.texture_units[0].texture_2d = surface.Handle(); | ||||
|     state.Apply(); | ||||
| 
 | ||||
|     glActiveTexture(GL_TEXTURE0); | ||||
|     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level); | ||||
|     const auto generate = [&](u32 index) { | ||||
|         state.texture_units[0].texture_2d = surface.Handle(index); | ||||
|         state.Apply(); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, surface.levels - 1); | ||||
|         glGenerateMipmap(GL_TEXTURE_2D); | ||||
|     }; | ||||
| 
 | ||||
|     glGenerateMipmap(GL_TEXTURE_2D); | ||||
|     generate(1); | ||||
|     if (surface.HasNormalMap()) { | ||||
|         generate(2); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const ReinterpreterList& TextureRuntime::GetPossibleReinterpretations( | ||||
|  | @ -340,21 +377,11 @@ Surface::~Surface() { | |||
|     if (pixel_format == PixelFormat::Invalid || !alloc) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     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)); | ||||
|     runtime->Recycle(MakeTag(), std::move(alloc)); | ||||
| } | ||||
| 
 | ||||
| void Surface::Upload(const VideoCore::BufferTextureCopy& upload, | ||||
|                      const VideoCore::StagingData& staging) { | ||||
|     // Ensure no bad interactions with GL_UNPACK_ALIGNMENT
 | ||||
|     ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0); | ||||
| 
 | ||||
|     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(); | ||||
|     glPixelStorei(GL_UNPACK_ROW_LENGTH, unscaled_width); | ||||
| 
 | ||||
|     // Bind the unscaled texture
 | ||||
|     glActiveTexture(GL_TEXTURE0); | ||||
|     glBindTexture(GL_TEXTURE_2D, Handle(false)); | ||||
|     glBindTexture(GL_TEXTURE_2D, Handle(0)); | ||||
| 
 | ||||
|     // Upload the requested rectangle of pixels
 | ||||
|     const auto& tuple = runtime->GetFormatTuple(pixel_format); | ||||
|     const auto& tuple = alloc.tuple; | ||||
|     glTexSubImage2D(GL_TEXTURE_2D, upload.texture_level, upload.texture_rect.left, | ||||
|                     upload.texture_rect.bottom, unscaled_width, unscaled_height, tuple.format, | ||||
|                     tuple.type, staging.mapped.data()); | ||||
|  | @ -381,17 +406,61 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, | |||
|         .src_rect = upload.texture_rect, | ||||
|         .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)) { | ||||
|         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, | ||||
|                        const VideoCore::StagingData& staging) { | ||||
|     // Ensure no bad interactions with GL_PACK_ALIGNMENT
 | ||||
|     ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0); | ||||
| 
 | ||||
|     const u32 unscaled_width = download.texture_rect.GetWidth(); | ||||
|  | @ -414,11 +483,9 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download, | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const u32 fbo_index = FboIndex(type); | ||||
| 
 | ||||
|     OpenGLState state = OpenGLState::GetCurState(); | ||||
|     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(); | ||||
| 
 | ||||
|     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, | ||||
|                  unscaled_height, tuple.format, tuple.type, staging.mapped.data()); | ||||
| 
 | ||||
|     // Restore previous state
 | ||||
|     glPixelStorei(GL_PACK_ROW_LENGTH, 0); | ||||
| } | ||||
| 
 | ||||
| bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download, | ||||
|                                  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 has_sub_image = driver->HasArbGetTextureSubImage(); | ||||
|     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
 | ||||
|     if (has_sub_image) { | ||||
|         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.GetHeight(), 1, tuple.format, tuple.type, | ||||
|                              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
 | ||||
|     // 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, | ||||
|                   staging.mapped.data()); | ||||
|     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) { | ||||
|     const GLuint handle = Handle(scaled); | ||||
|     const GLenum textarget = texture_type == VideoCore::TextureType::CubeMap | ||||
|     const GLuint handle = Handle(static_cast<u32>(scaled)); | ||||
|     const GLenum textarget = texture_type == TextureType::CubeMap | ||||
|                                  ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer | ||||
|                                  : GL_TEXTURE_2D; | ||||
| 
 | ||||
|     switch (type) { | ||||
|     case VideoCore::SurfaceType::Color: | ||||
|     case VideoCore::SurfaceType::Texture: | ||||
|     case SurfaceType::Color: | ||||
|     case SurfaceType::Texture: | ||||
|         glFramebufferTexture2D(target, GL_COLOR_ATTACHMENT0, textarget, handle, level); | ||||
|         break; | ||||
|     case VideoCore::SurfaceType::Depth: | ||||
|     case SurfaceType::Depth: | ||||
|         glFramebufferTexture2D(target, GL_DEPTH_ATTACHMENT, textarget, handle, level); | ||||
|         break; | ||||
|     case VideoCore::SurfaceType::DepthStencil: | ||||
|     case SurfaceType::DepthStencil: | ||||
|         glFramebufferTexture2D(target, GL_DEPTH_STENCIL_ATTACHMENT, textarget, handle, level); | ||||
|         break; | ||||
|     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 { | ||||
|     // RGB8 is converted to RGBA8 on OpenGL ES since it doesn't support BGR8
 | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| 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, | ||||
|                          Surface* const depth_stencil, u32 depth_level, const Pica::Regs& regs, | ||||
|                          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 has_stencil = regs.framebuffer.HasStencil(); | ||||
|     if (shadow_rendering && !color) { | ||||
|         return; // Framebuffer won't get used
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (color) { | ||||
|  | @ -574,19 +675,15 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 colo | |||
|                                color ? color->Handle() : 0, color_level); | ||||
|         if (depth_stencil) { | ||||
|             if (has_stencil) { | ||||
|                 // Attach both depth and stencil
 | ||||
|                 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, | ||||
|                                        GL_TEXTURE_2D, depth_stencil->Handle(), depth_level); | ||||
|             } else { | ||||
|                 // Attach depth
 | ||||
|                 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, | ||||
|                                        depth_stencil->Handle(), depth_level); | ||||
|                 // Clear stencil attachment
 | ||||
|                 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, | ||||
|                                        0); | ||||
|             } | ||||
|         } else { | ||||
|             // Clear both depth and stencil attachment
 | ||||
|             glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, | ||||
|                                    0, 0); | ||||
|         } | ||||
|  |  | |||
|  | @ -10,8 +10,9 @@ | |||
| #include "video_core/renderer_opengl/gl_format_reinterpreter.h" | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| struct Material; | ||||
| class RendererBase; | ||||
| } | ||||
| } // namespace VideoCore
 | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
|  | @ -27,17 +28,19 @@ struct FormatTuple { | |||
| }; | ||||
| 
 | ||||
| struct HostTextureTag { | ||||
|     FormatTuple tuple{}; | ||||
|     VideoCore::TextureType type{}; | ||||
|     u32 width = 0; | ||||
|     u32 height = 0; | ||||
|     u32 levels = 1; | ||||
|     u32 res_scale = 1; | ||||
|     u32 width; | ||||
|     u32 height; | ||||
|     u32 levels; | ||||
|     u32 res_scale; | ||||
|     FormatTuple tuple; | ||||
|     VideoCore::TextureType type; | ||||
|     bool is_custom; | ||||
|     bool has_normal; | ||||
| 
 | ||||
|     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, | ||||
|                         other.res_scale); | ||||
|                         other.res_scale, other.is_custom, other.has_normal); | ||||
|     } | ||||
| 
 | ||||
|     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 { | ||||
|     std::array<OGLTexture, 2> textures; | ||||
|     std::array<GLuint, 2> handles; | ||||
|     std::array<OGLTexture, 3> textures; | ||||
|     std::array<GLuint, 3> handles; | ||||
|     FormatTuple tuple; | ||||
|     u32 width; | ||||
|     u32 height; | ||||
|  | @ -59,15 +64,10 @@ struct Allocation { | |||
|     operator bool() const noexcept { | ||||
|         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 Driver; | ||||
| struct CachedTextureCube; | ||||
| 
 | ||||
| /**
 | ||||
|  * Provides texture manipulation functions to the rasterizer cache | ||||
|  | @ -82,6 +82,9 @@ public: | |||
|     explicit TextureRuntime(const Driver& driver, VideoCore::RendererBase& renderer); | ||||
|     ~TextureRuntime(); | ||||
| 
 | ||||
|     /// Clears all cached runtime resources
 | ||||
|     void Reset(); | ||||
| 
 | ||||
|     /// Returns true if the provided pixel format cannot be used natively by the runtime.
 | ||||
|     bool NeedsConversion(VideoCore::PixelFormat pixel_format) const; | ||||
| 
 | ||||
|  | @ -90,12 +93,14 @@ public: | |||
| 
 | ||||
|     /// Returns the OpenGL format tuple associated with the provided pixel format
 | ||||
|     const FormatTuple& GetFormatTuple(VideoCore::PixelFormat pixel_format) const; | ||||
|     const FormatTuple& GetFormatTuple(VideoCore::CustomPixelFormat pixel_format); | ||||
| 
 | ||||
|     /// Takes back ownership of the allocation for recycling
 | ||||
|     void Recycle(const HostTextureTag tag, Allocation&& alloc); | ||||
| 
 | ||||
|     /// Allocates an OpenGL texture with the specified dimentions and format
 | ||||
|     Allocation Allocate(const VideoCore::SurfaceParams& params); | ||||
|     /// Allocates a texture with the specified dimentions and format
 | ||||
|     Allocation Allocate(const VideoCore::SurfaceParams& params, | ||||
|                         const VideoCore::Material* material = nullptr); | ||||
| 
 | ||||
|     /// Fills the rectangle of the texture with the clear value provided
 | ||||
|     bool ClearTexture(Surface& surface, const VideoCore::TextureClear& clear); | ||||
|  | @ -107,7 +112,7 @@ public: | |||
|     bool BlitTextures(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); | ||||
| 
 | ||||
|     /// 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
 | ||||
|     const ReinterpreterList& GetPossibleReinterpretations(VideoCore::PixelFormat dest_format) const; | ||||
|  | @ -123,7 +128,7 @@ private: | |||
|     BlitHelper blit_helper; | ||||
|     std::vector<u8> staging_buffer; | ||||
|     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::array<OGLFramebuffer, 3> draw_fbos; | ||||
|     std::array<OGLFramebuffer, 3> read_fbos; | ||||
|  | @ -140,9 +145,9 @@ public: | |||
|     Surface(Surface&& o) noexcept = default; | ||||
|     Surface& operator=(Surface&& o) noexcept = default; | ||||
| 
 | ||||
|     /// Returns the surface image handle
 | ||||
|     GLuint Handle(bool scaled = true) const noexcept { | ||||
|         return alloc.handles[static_cast<u32>(scaled)]; | ||||
|     /// Returns the surface image handle at the provided index.
 | ||||
|     GLuint Handle(u32 index = 1) const noexcept { | ||||
|         return alloc.handles[index]; | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the tuple of the surface allocation.
 | ||||
|  | @ -150,9 +155,20 @@ public: | |||
|         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
 | ||||
|     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
 | ||||
|     void Download(const VideoCore::BufferTextureCopy& download, | ||||
|                   const VideoCore::StagingData& staging); | ||||
|  | @ -160,6 +176,9 @@ public: | |||
|     /// Attaches a handle of surface to the specified framebuffer target
 | ||||
|     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
 | ||||
|     u32 GetInternalBytesPerPixel() const; | ||||
| 
 | ||||
|  | @ -171,6 +190,9 @@ private: | |||
|     bool DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download, | ||||
|                             const VideoCore::StagingData& staging); | ||||
| 
 | ||||
|     /// Returns the texture tag of the current allocation
 | ||||
|     HostTextureTag MakeTag() const noexcept; | ||||
| 
 | ||||
| private: | ||||
|     const Driver* driver; | ||||
|     TextureRuntime* runtime; | ||||
|  |  | |||
|  | @ -313,7 +313,8 @@ RendererOpenGL::RendererOpenGL(Core::System& system, Frontend::EmuWindow& window | |||
|     } | ||||
|     frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>(); | ||||
|     InitOpenGLObjects(); | ||||
|     rasterizer = std::make_unique<RasterizerOpenGL>(system.Memory(), *this, driver); | ||||
|     rasterizer = std::make_unique<RasterizerOpenGL>(system.Memory(), system.CustomTexManager(), | ||||
|                                                     *this, driver); | ||||
| } | ||||
| 
 | ||||
| RendererOpenGL::~RendererOpenGL() = default; | ||||
|  | @ -347,6 +348,8 @@ void RendererOpenGL::SwapBuffers() { | |||
| 
 | ||||
|     EndFrame(); | ||||
|     prev_state.Apply(); | ||||
| 
 | ||||
|     rasterizer->TickFrame(); | ||||
| } | ||||
| 
 | ||||
| void RendererOpenGL::RenderScreenshot() { | ||||
|  | @ -1169,9 +1172,6 @@ void RendererOpenGL::TryPresent(int timeout_ms, bool is_secondary) { | |||
|     glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); | ||||
| } | ||||
| 
 | ||||
| /// Updates the framerate
 | ||||
| void RendererOpenGL::UpdateFramerate() {} | ||||
| 
 | ||||
| void RendererOpenGL::PrepareVideoDumping() { | ||||
|     auto* mailbox = static_cast<OGLVideoDumpingMailbox*>(frame_dumper.mailbox.get()); | ||||
|     { | ||||
|  |  | |||
|  | @ -94,7 +94,6 @@ private: | |||
|                                        float h); | ||||
|     void DrawSingleScreenStereo(const ScreenInfo& screen_info_l, const ScreenInfo& screen_info_r, | ||||
|                                 float x, float y, float w, float h); | ||||
|     void UpdateFramerate(); | ||||
| 
 | ||||
|     // Loads framebuffer from emulated memory into the display information structure
 | ||||
|     void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue