Compare commits
	
		
			3 Commits
		
	
	
		
			3dsx-args
			...
			file-watch
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | aa79505ddd | ||
|  | 2b7faf60a3 | ||
|  | fd32a82b4e | 
| @@ -89,6 +89,8 @@ add_library(citra_common STATIC | ||||
|     expected.h | ||||
|     file_util.cpp | ||||
|     file_util.h | ||||
|     file_watcher.cpp | ||||
|     file_watcher.h | ||||
|     hash.h | ||||
|     linear_disk_cache.h | ||||
|     literals.h | ||||
|   | ||||
| @@ -534,7 +534,6 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, | ||||
| } | ||||
|  | ||||
| void GetAllFilesFromNestedEntries(FSTEntry& directory, std::vector<FSTEntry>& output) { | ||||
|     std::vector<FSTEntry> files; | ||||
|     for (auto& entry : directory.children) { | ||||
|         if (entry.isDirectory) { | ||||
|             GetAllFilesFromNestedEntries(entry, output); | ||||
|   | ||||
							
								
								
									
										148
									
								
								src/common/file_watcher.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								src/common/file_watcher.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <windows.h> | ||||
| #include <thread> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/file_watcher.h" | ||||
|  | ||||
| namespace Common { | ||||
|  | ||||
| static FileAction Win32ActionToFileAction(DWORD action) { | ||||
|     switch (action) { | ||||
|     case FILE_ACTION_ADDED: | ||||
|         return FileAction::Added; | ||||
|     case FILE_ACTION_REMOVED: | ||||
|         return FileAction::Removed; | ||||
|     case FILE_ACTION_MODIFIED: | ||||
|         return FileAction::Modified; | ||||
|     case FILE_ACTION_RENAMED_OLD_NAME: | ||||
|         return FileAction::RenamedOldName; | ||||
|     case FILE_ACTION_RENAMED_NEW_NAME: | ||||
|         return FileAction::RenamedNewName; | ||||
|     default: | ||||
|         UNREACHABLE_MSG("Unknown action {}", action); | ||||
|         return FileAction::Invalid; | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct FileWatcher::Impl { | ||||
|     explicit Impl(const std::string& path, FileWatcher::Callback&& callback_) | ||||
|         : callback{callback_} { | ||||
|         // Create file handle for the directory we are watching. | ||||
|         dir_handle = | ||||
|             CreateFile(path.c_str(), FILE_LIST_DIRECTORY | GENERIC_READ, | ||||
|                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, | ||||
|                         FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); | ||||
|         ASSERT_MSG(dir_handle != INVALID_HANDLE_VALUE, "Unable to create watch file"); | ||||
|  | ||||
|         // Create an event that will terminate the thread when fired. | ||||
|         termination_event = CreateEvent(NULL, TRUE, FALSE, NULL); | ||||
|         ASSERT_MSG(termination_event != INVALID_HANDLE_VALUE, "Unable to create watch event"); | ||||
|  | ||||
|         // Create an event that will wake up the watcher thread on filesystem changes. | ||||
|         overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); | ||||
|         ASSERT_MSG(overlapped.hEvent != INVALID_HANDLE_VALUE, "Unable to create watch event"); | ||||
|  | ||||
|         // Create the watcher thread. | ||||
|         watch_thread = std::thread([this] { WatcherThread(); }); | ||||
|     } | ||||
|  | ||||
|     ~Impl() { | ||||
|         // Signal watcher thread to terminate. | ||||
|         SetEvent(termination_event); | ||||
|  | ||||
|         // Wait for said termination. | ||||
|         if (watch_thread.joinable()) { | ||||
|             watch_thread.join(); | ||||
|         } | ||||
|  | ||||
|         // Close used handles. | ||||
|         CancelIo(dir_handle); | ||||
|         GetOverlappedResult(dir_handle, &overlapped, &num_bytes_read, TRUE); | ||||
|         CloseHandle(termination_event); | ||||
|         CloseHandle(overlapped.hEvent); | ||||
|     } | ||||
|  | ||||
|     void WatcherThread() { | ||||
|         const std::array wait_handles{overlapped.hEvent, termination_event}; | ||||
|         while (is_running) { | ||||
|             bool result = | ||||
|                 ReadDirectoryChangesW(dir_handle, buffer.data(), buffer.size(), TRUE, | ||||
|                                       FILE_NOTIFY_CHANGE_FILE_NAME | | ||||
|                                       FILE_NOTIFY_CHANGE_DIR_NAME | | ||||
|                                       FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, &overlapped, NULL); | ||||
|             ASSERT_MSG(result, "Unable to read directory changes: {}", GetLastErrorMsg()); | ||||
|  | ||||
|             // Sleep until we receive a file changed notification or a termination event. | ||||
|             switch ( | ||||
|                 WaitForMultipleObjects(wait_handles.size(), wait_handles.data(), FALSE, INFINITE)) { | ||||
|             case WAIT_OBJECT_0: { | ||||
|                 // Retrieve asynchronously the data from ReadDirectoryChangesW. | ||||
|                 result = GetOverlappedResult(dir_handle, &overlapped, &num_bytes_read, TRUE); | ||||
|                 ASSERT_MSG(result, "Unable to retrieve overlapped result: {}", GetLastErrorMsg()); | ||||
|  | ||||
|                 // Notify about file changes. | ||||
|                 NotifyFileChanges(); | ||||
|                 break; | ||||
|             } | ||||
|             case WAIT_OBJECT_0 + 1: | ||||
|                 is_running = false; | ||||
|                 break; | ||||
|             case WAIT_FAILED: | ||||
|                 UNREACHABLE_MSG("Failed waiting for file watcher events: {}", GetLastErrorMsg()); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void NotifyFileChanges() { | ||||
|         // If no data was read we have nothing to do. | ||||
|         if (num_bytes_read == 0) [[unlikely]] { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         u32 next_entry_offset{}; | ||||
|         while (true) { | ||||
|             // Retrieve file notify information. | ||||
|             auto fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer.data() + next_entry_offset); | ||||
|  | ||||
|             // Call the callback function informing about the change. | ||||
|             if (fni->Action != 0) { | ||||
|                 std::string file_name(fni->FileNameLength / sizeof(WCHAR), ' '); | ||||
|                 WideCharToMultiByte(CP_UTF8, 0, fni->FileName, | ||||
|                                     fni->FileNameLength / sizeof(WCHAR), file_name.data(), file_name.size(), NULL, NULL); | ||||
|                 const FileAction action = Win32ActionToFileAction(fni->Action); | ||||
|                 callback(file_name, action); | ||||
|             } | ||||
|  | ||||
|             // If this was the last action, break. | ||||
|             if (fni->NextEntryOffset == 0) { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             // Move to next fni structure. | ||||
|             next_entry_offset += fni->NextEntryOffset; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     static constexpr size_t DirectoryWatcherBufferSize = 4096; | ||||
|     FileWatcher::Callback callback; | ||||
|     HANDLE dir_handle{}; | ||||
|     HANDLE termination_event{}; | ||||
|     OVERLAPPED overlapped{}; | ||||
|     std::array<u8, DirectoryWatcherBufferSize> buffer{}; | ||||
|     std::atomic_bool is_running{true}; | ||||
|     DWORD num_bytes_read{}; | ||||
|     std::thread watch_thread; | ||||
| }; | ||||
|  | ||||
| FileWatcher::FileWatcher(const std::string& log_dir, Callback&& callback) | ||||
|     : impl{std::make_unique<Impl>(log_dir, std::move(callback))} {} | ||||
|  | ||||
| FileWatcher::~FileWatcher() = default; | ||||
|  | ||||
| } // namespace Common | ||||
							
								
								
									
										33
									
								
								src/common/file_watcher.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/common/file_watcher.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <functional> | ||||
| #include <string> | ||||
|  | ||||
| namespace Common { | ||||
|  | ||||
| enum class FileAction : u8 { | ||||
|     Added, | ||||
|     Removed, | ||||
|     Modified, | ||||
|     RenamedOldName, | ||||
|     RenamedNewName, | ||||
|     Invalid = std::numeric_limits<u8>::max(), | ||||
| }; | ||||
|  | ||||
| class FileWatcher { | ||||
|     using Callback = std::function<void(const std::string&, FileAction)>; | ||||
|  | ||||
| public: | ||||
|     explicit FileWatcher(const std::string& log_dir, Callback&& callback); | ||||
|     ~FileWatcher(); | ||||
|  | ||||
| private: | ||||
|     struct Impl; | ||||
|     std::unique_ptr<Impl> impl; | ||||
| }; | ||||
|  | ||||
| } // namespace Common | ||||
| @@ -1,9 +1,10 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma clang optimize off | ||||
| #include <json.hpp> | ||||
| #include "common/file_util.h" | ||||
| #include "common/file_watcher.h" | ||||
| #include "common/memory_detect.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "common/settings.h" | ||||
| @@ -16,6 +17,7 @@ | ||||
| #include "video_core/custom_textures/custom_tex_manager.h" | ||||
| #include "video_core/rasterizer_cache/surface_params.h" | ||||
| #include "video_core/rasterizer_cache/utils.h" | ||||
| #include "video_core/renderer_base.h" | ||||
|  | ||||
| namespace VideoCore { | ||||
|  | ||||
| @@ -63,17 +65,17 @@ void CustomTexManager::TickFrame() { | ||||
|         return; | ||||
|     } | ||||
|     std::size_t num_uploads = 0; | ||||
|     for (auto it = async_uploads.begin(); it != async_uploads.end();) { | ||||
|     for (auto it = async_actions.begin(); it != async_actions.end();) { | ||||
|         if (num_uploads >= MAX_UPLOADS_PER_TICK) { | ||||
|             return; | ||||
|         } | ||||
|         switch (it->material->state) { | ||||
|         case DecodeState::Decoded: | ||||
|             it->func(); | ||||
|             it->func(it->material); | ||||
|             num_uploads++; | ||||
|             [[fallthrough]]; | ||||
|         case DecodeState::Failed: | ||||
|             it = async_uploads.erase(it); | ||||
|             it = async_actions.erase(it); | ||||
|             continue; | ||||
|         default: | ||||
|             it++; | ||||
| @@ -102,7 +104,7 @@ void CustomTexManager::FindCustomTextures() { | ||||
|         if (file.isDirectory) { | ||||
|             continue; | ||||
|         } | ||||
|         custom_textures.push_back(std::make_unique<CustomTexture>(image_interface)); | ||||
|         custom_textures.emplace_back(std::make_unique<CustomTexture>(image_interface)); | ||||
|         CustomTexture* const texture{custom_textures.back().get()}; | ||||
|         if (!ParseFilename(file, texture)) { | ||||
|             continue; | ||||
| @@ -292,16 +294,17 @@ Material* CustomTexManager::GetMaterial(u64 data_hash) { | ||||
|     return it->second.get(); | ||||
| } | ||||
|  | ||||
| bool CustomTexManager::Decode(Material* material, std::function<bool()>&& upload) { | ||||
| bool CustomTexManager::Decode(Material* material, AsyncFunc&& upload) { | ||||
|     if (!async_custom_loading) { | ||||
|         material->LoadFromDisk(flip_png_files); | ||||
|         return upload(); | ||||
|         return upload(material); | ||||
|     } | ||||
|     if (material->IsUnloaded()) { | ||||
|         material->state = DecodeState::Pending; | ||||
|         workers->QueueWork([material, this] { material->LoadFromDisk(flip_png_files); }); | ||||
|     } | ||||
|     async_uploads.push_back({ | ||||
|     std::scoped_lock lock{async_actions_mutex}; | ||||
|     async_actions.push_back({ | ||||
|         .material = material, | ||||
|         .func = std::move(upload), | ||||
|     }); | ||||
| @@ -374,6 +377,14 @@ std::vector<FileUtil::FSTEntry> CustomTexManager::GetTextures(u64 title_id) { | ||||
|         FileUtil::CreateFullPath(load_path); | ||||
|     } | ||||
|  | ||||
|     const auto callback = [this](const std::string& file, Common::FileAction action) { | ||||
|         OnFileAction(file, action); | ||||
|     }; | ||||
|  | ||||
|     // Create a file watcher to monitor any changes to the textures directory for hot-reloading. | ||||
|     file_watcher = std::make_unique<Common::FileWatcher>(load_path, callback); | ||||
|  | ||||
|     // Retrieve all texture files. | ||||
|     FileUtil::FSTEntry texture_dir; | ||||
|     std::vector<FileUtil::FSTEntry> textures; | ||||
|     FileUtil::ScanDirectoryTree(load_path, texture_dir, 64); | ||||
| @@ -386,4 +397,24 @@ void CustomTexManager::CreateWorkers() { | ||||
|     workers = std::make_unique<Common::ThreadWorker>(num_workers, "Custom textures"); | ||||
| } | ||||
|  | ||||
| void CustomTexManager::OnFileAction(const std::string& file, Common::FileAction action) { | ||||
|     const auto invalidate = [this](const Material* material) -> bool { | ||||
|         for (const SurfaceParams* params : material->loaded_to) { | ||||
|             system.Renderer().Rasterizer()->InvalidateRegion(params->addr, params->size); | ||||
|         } | ||||
|         return true; | ||||
|     }; | ||||
|  | ||||
|     const std::string filename{FileUtil::GetFilename(file)}; | ||||
|     const auto& hashes = path_to_hash_map[filename]; | ||||
|     std::scoped_lock lock{async_actions_mutex}; | ||||
|  | ||||
|     for (const Hash hash : hashes) { | ||||
|         async_actions.push_back({ | ||||
|             .material = material_map[hash].get(), | ||||
|             .func = std::move(invalidate), | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| } // namespace VideoCore | ||||
|   | ||||
| @@ -12,6 +12,11 @@ | ||||
| #include "video_core/custom_textures/material.h" | ||||
| #include "video_core/rasterizer_interface.h" | ||||
|  | ||||
| namespace Common { | ||||
| class FileWatcher; | ||||
| enum class FileAction : u8; | ||||
| } // namespace Common | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
| @@ -24,12 +29,10 @@ namespace VideoCore { | ||||
|  | ||||
| class SurfaceParams; | ||||
|  | ||||
| struct AsyncUpload { | ||||
|     const Material* material; | ||||
|     std::function<bool()> func; | ||||
| }; | ||||
|  | ||||
| class CustomTexManager { | ||||
|     using Hash = u64; | ||||
|     using AsyncFunc = std::function<bool(const Material*)>; | ||||
|  | ||||
| public: | ||||
|     explicit CustomTexManager(Core::System& system); | ||||
|     ~CustomTexManager(); | ||||
| @@ -57,7 +60,7 @@ public: | ||||
|     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); | ||||
|     bool Decode(Material* material, AsyncFunc&& func); | ||||
|  | ||||
|     /// True when mipmap uploads should be skipped (legacy packs only) | ||||
|     bool SkipMipmaps() const noexcept { | ||||
| @@ -79,15 +82,25 @@ private: | ||||
|     /// Creates the thread workers. | ||||
|     void CreateWorkers(); | ||||
|  | ||||
|     /// Callback for when a custom texture file is modified. | ||||
|     void OnFileAction(const std::string& file, Common::FileAction action); | ||||
|  | ||||
| private: | ||||
|     struct AsyncAction { | ||||
|         const Material* material; | ||||
|         AsyncFunc func; | ||||
|     }; | ||||
|  | ||||
|     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, std::vector<u64>> path_to_hash_map; | ||||
|     std::unordered_set<Hash> dumped_textures; | ||||
|     std::unordered_map<Hash, std::unique_ptr<Material>> material_map; | ||||
|     std::unordered_map<std::string, std::vector<Hash>> path_to_hash_map; | ||||
|     std::vector<std::unique_ptr<CustomTexture>> custom_textures; | ||||
|     std::list<AsyncUpload> async_uploads; | ||||
|     std::mutex async_actions_mutex; | ||||
|     std::list<AsyncAction> async_actions; | ||||
|     std::unique_ptr<Common::ThreadWorker> workers; | ||||
|     std::unique_ptr<Common::FileWatcher> file_watcher; | ||||
|     bool textures_loaded{false}; | ||||
|     bool async_custom_loading{true}; | ||||
|     bool skip_mipmap{false}; | ||||
|   | ||||
| @@ -18,6 +18,8 @@ class ImageInterface; | ||||
|  | ||||
| namespace VideoCore { | ||||
|  | ||||
| class SurfaceParams; | ||||
|  | ||||
| enum class MapType : u32 { | ||||
|     Color = 0, | ||||
|     Normal = 1, | ||||
| @@ -72,6 +74,7 @@ struct Material { | ||||
|     u64 hash; | ||||
|     CustomPixelFormat format; | ||||
|     std::array<CustomTexture*, MAX_MAPS> textures; | ||||
|     mutable std::vector<SurfaceParams*> loaded_to; | ||||
|     std::atomic<DecodeState> state{}; | ||||
|  | ||||
|     void LoadFromDisk(bool flip_png) noexcept; | ||||
|   | ||||
| @@ -653,7 +653,6 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color | ||||
|         static_cast<u32>(std::clamp(viewport_rect.bottom, 0, framebuffer_height)), | ||||
|     }; | ||||
|  | ||||
|     // get color and depth surfaces | ||||
|     SurfaceParams color_params; | ||||
|     color_params.is_tiled = true; | ||||
|     color_params.res_scale = resolution_scale_factor; | ||||
| @@ -672,14 +671,6 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color | ||||
|     auto color_vp_interval = color_params.GetSubRectInterval(viewport_clamped); | ||||
|     auto depth_vp_interval = depth_params.GetSubRectInterval(viewport_clamped); | ||||
|  | ||||
|     // Make sure that framebuffers don't overlap if both color and depth are being used | ||||
|     if (using_color_fb && using_depth_fb && | ||||
|         boost::icl::length(color_vp_interval & depth_vp_interval)) { | ||||
|         LOG_CRITICAL(HW_GPU, "Color and depth framebuffer memory regions overlap; " | ||||
|                              "overlapping framebuffers not supported!"); | ||||
|         using_depth_fb = false; | ||||
|     } | ||||
|  | ||||
|     Common::Rectangle<u32> color_rect{}; | ||||
|     SurfaceId color_id{}; | ||||
|     u32 color_level{}; | ||||
| @@ -713,11 +704,13 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color | ||||
|  | ||||
|     if (color_id) { | ||||
|         color_level = color_surface->LevelOf(color_params.addr); | ||||
|         color_surface->flags |= SurfaceFlagBits::RenderTarget; | ||||
|         ValidateSurface(color_id, boost::icl::first(color_vp_interval), | ||||
|                         boost::icl::length(color_vp_interval)); | ||||
|     } | ||||
|     if (depth_id) { | ||||
|         depth_level = depth_surface->LevelOf(depth_params.addr); | ||||
|         depth_surface->flags |= SurfaceFlagBits::RenderTarget; | ||||
|         ValidateSurface(depth_id, boost::icl::first(depth_vp_interval), | ||||
|                         boost::icl::length(depth_vp_interval)); | ||||
|     } | ||||
| @@ -991,7 +984,9 @@ void RasterizerCache<T>::UploadSurface(Surface& surface, SurfaceInterval interva | ||||
|     DecodeTexture(load_info, load_info.addr, load_info.end, upload_data, staging.mapped, | ||||
|                   runtime.NeedsConversion(surface.pixel_format)); | ||||
|  | ||||
|     if (dump_textures && False(surface.flags & SurfaceFlagBits::Custom)) { | ||||
|     const bool should_dump = False(surface.flags & SurfaceFlagBits::Custom) && | ||||
|                              False(surface.flags & SurfaceFlagBits::RenderTarget); | ||||
|     if (dump_textures && should_dump) { | ||||
|         const u64 hash = ComputeHash(load_info, upload_data); | ||||
|         const u32 level = surface.LevelOf(load_info.addr); | ||||
|         custom_tex_manager.DumpTexture(load_info, level, upload_data, hash); | ||||
| @@ -1048,7 +1043,7 @@ bool RasterizerCache<T>::UploadCustomSurface(SurfaceId surface_id, SurfaceInterv | ||||
|  | ||||
|     surface.flags |= SurfaceFlagBits::Custom; | ||||
|  | ||||
|     const auto upload = [this, level, surface_id, material]() -> bool { | ||||
|     const auto upload = [this, level, surface_id](const Material* material) -> bool { | ||||
|         ASSERT_MSG(True(slot_surfaces[surface_id].flags & SurfaceFlagBits::Custom), | ||||
|                    "Surface is not suitable for custom upload, aborting!"); | ||||
|         if (!slot_surfaces[surface_id].IsCustom()) { | ||||
|   | ||||
| @@ -11,7 +11,18 @@ namespace VideoCore { | ||||
|  | ||||
| SurfaceBase::SurfaceBase(const SurfaceParams& params) : SurfaceParams{params} {} | ||||
|  | ||||
| SurfaceBase::~SurfaceBase() = default; | ||||
| SurfaceBase::SurfaceBase(const SurfaceParams& params, const Material* mat) | ||||
|     : SurfaceParams{params}, material{mat} { | ||||
|     custom_format = material->format; | ||||
|     material->loaded_to.push_back(this); | ||||
| } | ||||
|  | ||||
| SurfaceBase::~SurfaceBase() { | ||||
|     if (!material) { | ||||
|         return; | ||||
|     } | ||||
|     std::erase_if(material->loaded_to, [this](SurfaceParams* params) { return params == this; }); | ||||
| } | ||||
|  | ||||
| bool SurfaceBase::CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const { | ||||
|     if (type == SurfaceType::Fill && IsRegionValid(fill_interval) && | ||||
|   | ||||
| @@ -15,17 +15,19 @@ using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterva | ||||
| struct Material; | ||||
|  | ||||
| enum class SurfaceFlagBits : u32 { | ||||
|     Registered = 1 << 0, ///< Surface is registed in the rasterizer cache. | ||||
|     Picked = 1 << 1,     ///< Surface has been picked when searching for a match. | ||||
|     Tracked = 1 << 2,    ///< Surface is part of a texture cube and should be tracked. | ||||
|     Custom = 1 << 3,     ///< Surface texture has been replaced with a custom texture. | ||||
|     ShadowMap = 1 << 4,  ///< Surface is used during shadow rendering. | ||||
|     Registered = 1 << 0,   ///< Surface is registed in the rasterizer cache. | ||||
|     Picked = 1 << 1,       ///< Surface has been picked when searching for a match. | ||||
|     Tracked = 1 << 2,      ///< Surface is part of a texture cube and should be tracked. | ||||
|     Custom = 1 << 3,       ///< Surface texture has been replaced with a custom texture. | ||||
|     ShadowMap = 1 << 4,    ///< Surface is used during shadow rendering. | ||||
|     RenderTarget = 1 << 5, ///< Surface was a render target. | ||||
| }; | ||||
| DECLARE_ENUM_FLAG_OPERATORS(SurfaceFlagBits); | ||||
|  | ||||
| class SurfaceBase : public SurfaceParams { | ||||
| public: | ||||
|     SurfaceBase(const SurfaceParams& params); | ||||
|     explicit SurfaceBase(const SurfaceParams& params); | ||||
|     explicit SurfaceBase(const SurfaceParams& params, const Material* mat); | ||||
|     ~SurfaceBase(); | ||||
|  | ||||
|     /// Returns true when this surface can be used to fill the fill_interval of dest_surface | ||||
|   | ||||
| @@ -333,7 +333,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param | ||||
|  | ||||
| Surface::Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface, | ||||
|                  const VideoCore::Material* mat) | ||||
|     : SurfaceBase{surface}, tuple{runtime.GetFormatTuple(mat->format)} { | ||||
|     : SurfaceBase{surface, mat}, tuple{runtime.GetFormatTuple(mat->format)} { | ||||
|     if (mat && !driver->IsCustomFormatSupported(mat->format)) { | ||||
|         return; | ||||
|     } | ||||
| @@ -342,9 +342,6 @@ Surface::Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface, | ||||
|     const GLenum target = | ||||
|         texture_type == VideoCore::TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; | ||||
|  | ||||
|     custom_format = mat->format; | ||||
|     material = mat; | ||||
|  | ||||
|     textures[0] = MakeHandle(target, mat->width, mat->height, levels, tuple, DebugName(false)); | ||||
|     if (res_scale != 1) { | ||||
|         textures[1] = MakeHandle(target, mat->width, mat->height, levels, DEFAULT_TUPLE, | ||||
|   | ||||
| @@ -3,10 +3,10 @@ | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/file_watcher.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "common/settings.h" | ||||
| #include "common/texture.h" | ||||
| #include "core/core.h" | ||||
| #include "core/frontend/emu_window.h" | ||||
| #include "core/hw/gpu.h" | ||||
| @@ -19,7 +19,6 @@ | ||||
| #include "video_core/host_shaders/vulkan_present_frag_spv.h" | ||||
| #include "video_core/host_shaders/vulkan_present_interlaced_frag_spv.h" | ||||
| #include "video_core/host_shaders/vulkan_present_vert_spv.h" | ||||
| #include "vulkan/vulkan_format_traits.hpp" | ||||
|  | ||||
| #include <vk_mem_alloc.h> | ||||
|  | ||||
|   | ||||
| @@ -750,7 +750,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param | ||||
|  | ||||
| Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface, | ||||
|                  const VideoCore::Material* mat) | ||||
|     : SurfaceBase{surface}, runtime{&runtime_}, instance{&runtime_.GetInstance()}, | ||||
|     : SurfaceBase{surface, mat}, runtime{&runtime_}, instance{&runtime_.GetInstance()}, | ||||
|       scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(mat->format)} { | ||||
|     if (!traits.transfer_support) { | ||||
|         return; | ||||
| @@ -791,9 +791,6 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface | ||||
|                                vk::PipelineStageFlagBits::eTopOfPipe, | ||||
|                                vk::DependencyFlagBits::eByRegion, {}, {}, barriers); | ||||
|     }); | ||||
|  | ||||
|     custom_format = mat->format; | ||||
|     material = mat; | ||||
| } | ||||
|  | ||||
| Surface::~Surface() { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user