mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-04-24 14:24:54 +00:00
Merge d0d0fa18d9
into 879a8889aa
This commit is contained in:
commit
96a809a9bd
29 changed files with 940 additions and 151 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -84,6 +84,9 @@
|
|||
[submodule "Externals/Vulkan-Headers"]
|
||||
path = Externals/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "Externals/watcher/watcher"]
|
||||
path = Externals/watcher/watcher
|
||||
url = https://github.com/e-dant/watcher.git
|
||||
[submodule "Externals/SFML/SFML"]
|
||||
path = Externals/SFML/SFML
|
||||
url = https://github.com/SFML/SFML.git
|
||||
|
|
|
@ -784,6 +784,8 @@ if (USE_RETRO_ACHIEVEMENTS)
|
|||
add_subdirectory(Externals/rcheevos)
|
||||
endif()
|
||||
|
||||
add_subdirectory(Externals/watcher)
|
||||
|
||||
########################################
|
||||
# Pre-build events: Define configuration variables and write SCM info header
|
||||
#
|
||||
|
|
4
Externals/watcher/CMakeLists.txt
vendored
Normal file
4
Externals/watcher/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
add_library(watcher INTERFACE IMPORTED GLOBAL)
|
||||
set_target_properties(watcher PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/watcher/include
|
||||
)
|
1
Externals/watcher/watcher
vendored
Submodule
1
Externals/watcher/watcher
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 0d6b9b409ccaed6313437ea3dc8b2fc078f3d25b
|
|
@ -64,6 +64,8 @@ add_library(common
|
|||
FatFsUtil.h
|
||||
FileSearch.cpp
|
||||
FileSearch.h
|
||||
FilesystemWatcher.cpp
|
||||
FilesystemWatcher.h
|
||||
FileUtil.cpp
|
||||
FileUtil.h
|
||||
FixedSizeQueue.h
|
||||
|
@ -183,6 +185,7 @@ PRIVATE
|
|||
FatFs
|
||||
Iconv::Iconv
|
||||
spng::spng
|
||||
watcher
|
||||
${VTUNE_LIBRARIES}
|
||||
)
|
||||
|
||||
|
|
62
Source/Core/Common/FilesystemWatcher.cpp
Normal file
62
Source/Core/Common/FilesystemWatcher.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/FilesystemWatcher.h"
|
||||
|
||||
#include <wtr/watcher.hpp>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
namespace Common
|
||||
{
|
||||
FilesystemWatcher::FilesystemWatcher() = default;
|
||||
FilesystemWatcher::~FilesystemWatcher() = default;
|
||||
|
||||
void FilesystemWatcher::Watch(const std::string& path)
|
||||
{
|
||||
const auto [iter, inserted] = m_watched_paths.try_emplace(path, nullptr);
|
||||
if (inserted)
|
||||
{
|
||||
iter->second = std::make_unique<wtr::watch>(path, [this](wtr::event e) {
|
||||
if (e.path_type == wtr::event::path_type::watcher)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.effect_type == wtr::event::effect_type::create)
|
||||
{
|
||||
const auto path = WithUnifiedPathSeparators(PathToString(e.path_name));
|
||||
PathAdded(path);
|
||||
}
|
||||
else if (e.effect_type == wtr::event::effect_type::modify)
|
||||
{
|
||||
const auto path = WithUnifiedPathSeparators(PathToString(e.path_name));
|
||||
PathModified(path);
|
||||
}
|
||||
else if (e.effect_type == wtr::event::effect_type::rename)
|
||||
{
|
||||
if (!e.associated)
|
||||
{
|
||||
WARN_LOG_FMT(COMMON, "Rename on path seen without association!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old_path = WithUnifiedPathSeparators(PathToString(e.path_name));
|
||||
const auto new_path = WithUnifiedPathSeparators(PathToString(e.associated->path_name));
|
||||
PathRenamed(old_path, new_path);
|
||||
}
|
||||
else if (e.effect_type == wtr::event::effect_type::destroy)
|
||||
{
|
||||
const auto path = WithUnifiedPathSeparators(PathToString(e.path_name));
|
||||
PathDeleted(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void FilesystemWatcher::Unwatch(const std::string& path)
|
||||
{
|
||||
m_watched_paths.erase(path);
|
||||
}
|
||||
} // namespace Common
|
47
Source/Core/Common/FilesystemWatcher.h
Normal file
47
Source/Core/Common/FilesystemWatcher.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace wtr
|
||||
{
|
||||
inline namespace watcher
|
||||
{
|
||||
class watch;
|
||||
}
|
||||
} // namespace wtr
|
||||
|
||||
namespace Common
|
||||
{
|
||||
// A class that can watch a path and receive callbacks
|
||||
// when files or directories underneath that path receive events
|
||||
class FilesystemWatcher
|
||||
{
|
||||
public:
|
||||
FilesystemWatcher();
|
||||
virtual ~FilesystemWatcher();
|
||||
|
||||
void Watch(const std::string& path);
|
||||
void Unwatch(const std::string& path);
|
||||
|
||||
private:
|
||||
// A new file or folder was added to one of the watched paths
|
||||
virtual void PathAdded(std::string_view path) {}
|
||||
|
||||
// A file or folder was modified in one of the watched paths
|
||||
virtual void PathModified(std::string_view path) {}
|
||||
|
||||
// A file or folder was renamed in one of the watched paths
|
||||
virtual void PathRenamed(std::string_view old_path, std::string_view new_path) {}
|
||||
|
||||
// A file or folder was deleted in one of the watched paths
|
||||
virtual void PathDeleted(std::string_view path) {}
|
||||
|
||||
std::map<std::string, std::unique_ptr<wtr::watch>> m_watched_paths;
|
||||
};
|
||||
} // namespace Common
|
|
@ -34,6 +34,7 @@
|
|||
#include "IOS/USB/Emulated/Skylanders/Skylander.h"
|
||||
#include "IOS/USB/USBScanner.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/CommandProcessor.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
#include "VideoCommon/GeometryShaderManager.h"
|
||||
|
@ -97,6 +98,7 @@ struct System::Impl
|
|||
Interpreter m_interpreter;
|
||||
JitInterface m_jit_interface;
|
||||
VideoCommon::CustomAssetLoader m_custom_asset_loader;
|
||||
VideoCommon::CustomResourceManager m_custom_resource_manager;
|
||||
FifoPlayer m_fifo_player;
|
||||
FifoRecorder m_fifo_recorder;
|
||||
Movie::MovieManager m_movie;
|
||||
|
@ -339,4 +341,9 @@ VideoCommon::CustomAssetLoader& System::GetCustomAssetLoader() const
|
|||
{
|
||||
return m_impl->m_custom_asset_loader;
|
||||
}
|
||||
|
||||
VideoCommon::CustomResourceManager& System::GetCustomResourceManager() const
|
||||
{
|
||||
return m_impl->m_custom_resource_manager;
|
||||
}
|
||||
} // namespace Core
|
||||
|
|
|
@ -109,7 +109,8 @@ class SystemTimersManager;
|
|||
namespace VideoCommon
|
||||
{
|
||||
class CustomAssetLoader;
|
||||
}
|
||||
class CustomResourceManager;
|
||||
} // namespace VideoCommon
|
||||
namespace VideoInterface
|
||||
{
|
||||
class VideoInterfaceManager;
|
||||
|
@ -198,6 +199,7 @@ public:
|
|||
XFStateManager& GetXFStateManager() const;
|
||||
VideoInterface::VideoInterfaceManager& GetVideoInterface() const;
|
||||
VideoCommon::CustomAssetLoader& GetCustomAssetLoader() const;
|
||||
VideoCommon::CustomResourceManager& GetCustomResourceManager() const;
|
||||
|
||||
private:
|
||||
System();
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
<ClInclude Include="Common\Event.h" />
|
||||
<ClInclude Include="Common\FatFsUtil.h" />
|
||||
<ClInclude Include="Common\FileSearch.h" />
|
||||
<ClInclude Include="Common\FilesystemWatcher.h" />
|
||||
<ClInclude Include="Common\FileUtil.h" />
|
||||
<ClInclude Include="Common\FixedSizeQueue.h" />
|
||||
<ClInclude Include="Common\Flag.h" />
|
||||
|
@ -663,12 +664,16 @@
|
|||
<ClInclude Include="VideoCommon\Assets\CustomAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAssetLoader.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAssetLoader2.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomResourceManager.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomTextureData.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\MaterialAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\MeshAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\ShaderAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\TextureAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\Types.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\WatchableFilesystemAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\AsyncRequests.h" />
|
||||
<ClInclude Include="VideoCommon\AsyncShaderCompiler.h" />
|
||||
<ClInclude Include="VideoCommon\BoundingBox.h" />
|
||||
|
@ -805,6 +810,7 @@
|
|||
<ClCompile Include="Common\ENet.cpp" />
|
||||
<ClCompile Include="Common\FatFsUtil.cpp" />
|
||||
<ClCompile Include="Common\FileSearch.cpp" />
|
||||
<ClCompile Include="Common\FilesystemWatcher.cpp" />
|
||||
<ClCompile Include="Common\FileUtil.cpp" />
|
||||
<ClCompile Include="Common\FloatUtils.cpp" />
|
||||
<ClCompile Include="Common\GekkoDisassembler.cpp" />
|
||||
|
@ -1311,6 +1317,8 @@
|
|||
<ClCompile Include="VideoCommon\Assets\CustomAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomAssetLibrary.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomAssetLoader.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomAssetLoader2.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomResourceManager.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomTextureData.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\MaterialAsset.cpp" />
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
namespace VideoCommon
|
||||
{
|
||||
CustomAsset::CustomAsset(std::shared_ptr<CustomAssetLibrary> library,
|
||||
const CustomAssetLibrary::AssetID& asset_id)
|
||||
: m_owning_library(std::move(library)), m_asset_id(asset_id)
|
||||
const CustomAssetLibrary::AssetID& asset_id, u64 session_id)
|
||||
: m_owning_library(std::move(library)), m_asset_id(asset_id), m_session_id(session_id)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,11 @@ const CustomAssetLibrary::TimeType& CustomAsset::GetLastLoadedTime() const
|
|||
return m_last_loaded_time;
|
||||
}
|
||||
|
||||
std::size_t CustomAsset::GetSessionId() const
|
||||
{
|
||||
return m_session_id;
|
||||
}
|
||||
|
||||
const CustomAssetLibrary::AssetID& CustomAsset::GetAssetId() const
|
||||
{
|
||||
return m_asset_id;
|
||||
|
|
|
@ -18,7 +18,7 @@ class CustomAsset
|
|||
{
|
||||
public:
|
||||
CustomAsset(std::shared_ptr<CustomAssetLibrary> library,
|
||||
const CustomAssetLibrary::AssetID& asset_id);
|
||||
const CustomAssetLibrary::AssetID& asset_id, u64 session_id);
|
||||
virtual ~CustomAsset() = default;
|
||||
CustomAsset(const CustomAsset&) = delete;
|
||||
CustomAsset(CustomAsset&&) = delete;
|
||||
|
@ -39,6 +39,9 @@ public:
|
|||
// Returns an id that uniquely identifies this asset
|
||||
const CustomAssetLibrary::AssetID& GetAssetId() const;
|
||||
|
||||
// Returns an id that is unique to this session
|
||||
std::size_t GetSessionId() const;
|
||||
|
||||
// A rough estimate of how much space this asset
|
||||
// will take in memroy
|
||||
std::size_t GetByteSizeInMemory() const;
|
||||
|
@ -49,6 +52,7 @@ protected:
|
|||
private:
|
||||
virtual CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) = 0;
|
||||
CustomAssetLibrary::AssetID m_asset_id;
|
||||
std::size_t m_session_id;
|
||||
|
||||
mutable std::mutex m_info_lock;
|
||||
std::size_t m_bytes_loaded = 0;
|
||||
|
|
|
@ -67,19 +67,20 @@ private:
|
|||
if (shared)
|
||||
return shared;
|
||||
}
|
||||
std::shared_ptr<AssetType> ptr(new AssetType(std::move(library), asset_id), [&](AssetType* a) {
|
||||
{
|
||||
std::lock_guard lk(m_asset_load_lock);
|
||||
m_total_bytes_loaded -= a->GetByteSizeInMemory();
|
||||
m_assets_to_monitor.erase(a->GetAssetId());
|
||||
if (m_max_memory_available >= m_total_bytes_loaded && m_memory_exceeded)
|
||||
{
|
||||
INFO_LOG_FMT(VIDEO, "Asset memory went below limit, new assets can begin loading.");
|
||||
m_memory_exceeded = false;
|
||||
}
|
||||
}
|
||||
delete a;
|
||||
});
|
||||
std::shared_ptr<AssetType> ptr(
|
||||
new AssetType(std::move(library), asset_id, asset_map.size()), [&](AssetType* a) {
|
||||
{
|
||||
std::lock_guard lk(m_asset_load_lock);
|
||||
m_total_bytes_loaded -= a->GetByteSizeInMemory();
|
||||
m_assets_to_monitor.erase(a->GetAssetId());
|
||||
if (m_max_memory_available >= m_total_bytes_loaded && m_memory_exceeded)
|
||||
{
|
||||
INFO_LOG_FMT(VIDEO, "Asset memory went below limit, new assets can begin loading.");
|
||||
m_memory_exceeded = false;
|
||||
}
|
||||
}
|
||||
delete a;
|
||||
});
|
||||
it->second = ptr;
|
||||
m_asset_load_thread.Push(it->second);
|
||||
return ptr;
|
||||
|
|
175
Source/Core/VideoCommon/Assets/CustomAssetLoader2.cpp
Normal file
175
Source/Core/VideoCommon/Assets/CustomAssetLoader2.cpp
Normal file
|
@ -0,0 +1,175 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLoader2.h"
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Thread.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
void CustomAssetLoader2::Initialize()
|
||||
{
|
||||
ResizeWorkerThreads(2);
|
||||
}
|
||||
|
||||
void CustomAssetLoader2 ::Shutdown()
|
||||
{
|
||||
Reset(false);
|
||||
}
|
||||
|
||||
void CustomAssetLoader2::Reset(bool restart_worker_threads)
|
||||
{
|
||||
const std::size_t worker_thread_count = m_worker_threads.size();
|
||||
StopWorkerThreads();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_pending_work_lock);
|
||||
m_pending_assets.clear();
|
||||
m_max_memory_allowed = 0;
|
||||
m_current_asset_memory = 0;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_completed_work_lock);
|
||||
m_completed_asset_session_ids.clear();
|
||||
m_completed_asset_memory = 0;
|
||||
}
|
||||
|
||||
if (restart_worker_threads)
|
||||
{
|
||||
StartWorkerThreads(static_cast<u32>(worker_thread_count));
|
||||
}
|
||||
}
|
||||
|
||||
bool CustomAssetLoader2::StartWorkerThreads(u32 num_worker_threads)
|
||||
{
|
||||
if (num_worker_threads == 0)
|
||||
return true;
|
||||
|
||||
for (u32 i = 0; i < num_worker_threads; i++)
|
||||
{
|
||||
m_worker_thread_start_result.store(false);
|
||||
|
||||
void* thread_param = nullptr;
|
||||
std::thread thr(&CustomAssetLoader2::WorkerThreadEntryPoint, this, thread_param);
|
||||
m_init_event.Wait();
|
||||
|
||||
if (!m_worker_thread_start_result.load())
|
||||
{
|
||||
WARN_LOG_FMT(VIDEO, "Failed to start asset load worker thread.");
|
||||
thr.join();
|
||||
break;
|
||||
}
|
||||
|
||||
m_worker_threads.push_back(std::move(thr));
|
||||
}
|
||||
|
||||
return HasWorkerThreads();
|
||||
}
|
||||
|
||||
bool CustomAssetLoader2::ResizeWorkerThreads(u32 num_worker_threads)
|
||||
{
|
||||
if (m_worker_threads.size() == num_worker_threads)
|
||||
return true;
|
||||
|
||||
StopWorkerThreads();
|
||||
return StartWorkerThreads(num_worker_threads);
|
||||
}
|
||||
|
||||
bool CustomAssetLoader2::HasWorkerThreads() const
|
||||
{
|
||||
return !m_worker_threads.empty();
|
||||
}
|
||||
|
||||
void CustomAssetLoader2::StopWorkerThreads()
|
||||
{
|
||||
if (!HasWorkerThreads())
|
||||
return;
|
||||
|
||||
// Signal worker threads to stop, and wake all of them.
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_pending_work_lock);
|
||||
m_exit_flag.Set();
|
||||
m_worker_thread_wake.notify_all();
|
||||
}
|
||||
|
||||
// Wait for worker threads to exit.
|
||||
for (std::thread& thr : m_worker_threads)
|
||||
thr.join();
|
||||
m_worker_threads.clear();
|
||||
m_exit_flag.Clear();
|
||||
}
|
||||
|
||||
void CustomAssetLoader2::WorkerThreadEntryPoint(void* param)
|
||||
{
|
||||
Common::SetCurrentThreadName("Asset Loader Worker");
|
||||
|
||||
m_worker_thread_start_result.store(true);
|
||||
m_init_event.Set();
|
||||
|
||||
WorkerThreadRun();
|
||||
}
|
||||
|
||||
void CustomAssetLoader2::WorkerThreadRun()
|
||||
{
|
||||
std::unique_lock<std::mutex> pending_lock(m_pending_work_lock);
|
||||
while (!m_exit_flag.IsSet())
|
||||
{
|
||||
m_worker_thread_wake.wait(pending_lock);
|
||||
|
||||
while (!m_pending_assets.empty() && !m_exit_flag.IsSet())
|
||||
{
|
||||
auto pending_iter = m_pending_assets.begin();
|
||||
const auto item = *pending_iter;
|
||||
m_pending_assets.erase(pending_iter);
|
||||
|
||||
if ((m_current_asset_memory + m_completed_asset_memory) > m_max_memory_allowed)
|
||||
break;
|
||||
|
||||
pending_lock.unlock();
|
||||
if (item->Load())
|
||||
{
|
||||
std::lock_guard<std::mutex> completed_guard(m_completed_work_lock);
|
||||
m_completed_asset_memory += item->GetByteSizeInMemory();
|
||||
m_completed_asset_session_ids.push_back(item->GetSessionId());
|
||||
}
|
||||
|
||||
pending_lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::size_t>
|
||||
CustomAssetLoader2::LoadAssets(const std::list<CustomAsset*>& pending_assets,
|
||||
u64 current_loaded_memory, u64 max_memory_allowed)
|
||||
{
|
||||
u64 total_memory = current_loaded_memory;
|
||||
std::vector<std::size_t> completed_asset_session_ids;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_completed_work_lock);
|
||||
m_completed_asset_session_ids.swap(completed_asset_session_ids);
|
||||
total_memory += m_completed_asset_memory;
|
||||
m_completed_asset_memory = 0;
|
||||
}
|
||||
|
||||
if (pending_assets.empty())
|
||||
return completed_asset_session_ids;
|
||||
|
||||
if (total_memory > max_memory_allowed)
|
||||
return completed_asset_session_ids;
|
||||
|
||||
// There's new assets to process, notify worker threads
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_pending_work_lock);
|
||||
m_pending_assets = pending_assets;
|
||||
m_current_asset_memory = total_memory;
|
||||
m_max_memory_allowed = max_memory_allowed;
|
||||
if (m_current_asset_memory < m_max_memory_allowed)
|
||||
m_worker_thread_wake.notify_all();
|
||||
}
|
||||
|
||||
return completed_asset_session_ids;
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
65
Source/Core/VideoCommon/Assets/CustomAssetLoader2.h
Normal file
65
Source/Core/VideoCommon/Assets/CustomAssetLoader2.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Event.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class CustomAssetLoader2
|
||||
{
|
||||
public:
|
||||
CustomAssetLoader2() = default;
|
||||
~CustomAssetLoader2() = default;
|
||||
CustomAssetLoader2(const CustomAssetLoader2&) = delete;
|
||||
CustomAssetLoader2(CustomAssetLoader2&&) = delete;
|
||||
CustomAssetLoader2& operator=(const CustomAssetLoader2&) = delete;
|
||||
CustomAssetLoader2& operator=(CustomAssetLoader2&&) = delete;
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
// Returns a vector of asset session ids that were loaded in the last frame
|
||||
std::vector<std::size_t> LoadAssets(const std::list<CustomAsset*>& pending_assets,
|
||||
u64 current_loaded_memory, u64 max_memory_allowed);
|
||||
|
||||
void Reset(bool restart_worker_threads = true);
|
||||
|
||||
private:
|
||||
bool StartWorkerThreads(u32 num_worker_threads);
|
||||
bool ResizeWorkerThreads(u32 num_worker_threads);
|
||||
bool HasWorkerThreads() const;
|
||||
void StopWorkerThreads();
|
||||
|
||||
void WorkerThreadEntryPoint(void* param);
|
||||
void WorkerThreadRun();
|
||||
|
||||
Common::Flag m_exit_flag;
|
||||
Common::Event m_init_event;
|
||||
|
||||
std::vector<std::thread> m_worker_threads;
|
||||
std::atomic_bool m_worker_thread_start_result{false};
|
||||
|
||||
std::list<CustomAsset*> m_pending_assets;
|
||||
std::atomic<u64> m_current_asset_memory = 0;
|
||||
u64 m_max_memory_allowed = 0;
|
||||
std::mutex m_pending_work_lock;
|
||||
|
||||
std::condition_variable m_worker_thread_wake;
|
||||
|
||||
std::vector<std::size_t> m_completed_asset_session_ids;
|
||||
std::atomic<u64> m_completed_asset_memory = 0;
|
||||
std::mutex m_completed_work_lock;
|
||||
};
|
||||
} // namespace VideoCommon
|
202
Source/Core/VideoCommon/Assets/CustomResourceManager.cpp
Normal file
202
Source/Core/VideoCommon/Assets/CustomResourceManager.cpp
Normal file
|
@ -0,0 +1,202 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/MemoryUtil.h"
|
||||
#include "Common/VariantUtil.h"
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
void CustomResourceManager::Initialize()
|
||||
{
|
||||
m_asset_loader.Initialize();
|
||||
|
||||
const size_t sys_mem = Common::MemPhysical();
|
||||
const size_t 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
|
||||
m_max_ram_available =
|
||||
(sys_mem / 2 < recommended_min_mem) ? (sys_mem / 2) : (sys_mem - recommended_min_mem);
|
||||
|
||||
m_xfb_event = AfterFrameEvent::Register([this](Core::System&) { XFBTriggered(""); },
|
||||
"CustomResourceManager");
|
||||
}
|
||||
|
||||
void CustomResourceManager::Shutdown()
|
||||
{
|
||||
Reset();
|
||||
|
||||
m_asset_loader.Shutdown();
|
||||
}
|
||||
|
||||
void CustomResourceManager::Reset()
|
||||
{
|
||||
m_asset_loader.Reset(true);
|
||||
|
||||
m_loaded_assets = {};
|
||||
m_pending_assets = {};
|
||||
m_session_id_to_asset_data.clear();
|
||||
m_asset_id_to_session_id.clear();
|
||||
m_ram_used = 0;
|
||||
}
|
||||
|
||||
void CustomResourceManager::ReloadAsset(const CustomAssetLibrary::AssetID& asset_id)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_reload_mutex);
|
||||
m_assets_to_reload.insert(asset_id);
|
||||
}
|
||||
|
||||
CustomTextureData* CustomResourceManager::GetTextureDataFromAsset(
|
||||
const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
const auto [it, inserted] =
|
||||
m_texture_data_asset_cache.try_emplace(asset_id, InternalTextureDataResource{});
|
||||
if (it->second.asset_data &&
|
||||
it->second.asset_data->load_type == AssetData::LoadType::LoadFinalyzed)
|
||||
{
|
||||
m_loaded_assets.put(it->second.asset->GetSessionId(), it->second.asset);
|
||||
return &it->second.texture_data->m_texture;
|
||||
}
|
||||
|
||||
LoadTextureDataAsset(asset_id, std::move(library), &it->second);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CustomResourceManager::LoadTextureDataAsset(
|
||||
const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||
InternalTextureDataResource* internal_texture_data)
|
||||
{
|
||||
if (!internal_texture_data->asset)
|
||||
{
|
||||
internal_texture_data->asset =
|
||||
CreateAsset<GameTextureAsset>(asset_id, AssetData::AssetType::TextureData, library);
|
||||
internal_texture_data->asset_data =
|
||||
&m_session_id_to_asset_data[internal_texture_data->asset->GetSessionId()];
|
||||
}
|
||||
|
||||
auto texture_data = internal_texture_data->asset->GetData();
|
||||
if (!texture_data ||
|
||||
internal_texture_data->asset_data->load_type == AssetData::LoadType::PendingReload)
|
||||
{
|
||||
// Tell the system we are still interested in loading this asset
|
||||
const auto session_id = internal_texture_data->asset->GetSessionId();
|
||||
m_pending_assets.put(session_id, m_session_id_to_asset_data[session_id].asset.get());
|
||||
}
|
||||
else if (internal_texture_data->asset_data->load_type == AssetData::LoadType::LoadFinished)
|
||||
{
|
||||
internal_texture_data->texture_data = std::move(texture_data);
|
||||
internal_texture_data->asset_data->load_type = AssetData::LoadType::LoadFinalyzed;
|
||||
}
|
||||
}
|
||||
|
||||
void CustomResourceManager::XFBTriggered(std::string_view)
|
||||
{
|
||||
std::set<std::size_t> session_ids_reloaded_this_frame;
|
||||
|
||||
// Look for any assets requested to be reloaded
|
||||
{
|
||||
decltype(m_assets_to_reload) assets_to_reload;
|
||||
|
||||
if (m_reload_mutex.try_lock())
|
||||
{
|
||||
std::swap(assets_to_reload, m_assets_to_reload);
|
||||
m_reload_mutex.unlock();
|
||||
}
|
||||
|
||||
for (const auto& asset_id : assets_to_reload)
|
||||
{
|
||||
if (const auto it = m_asset_id_to_session_id.find(asset_id);
|
||||
it != m_asset_id_to_session_id.end())
|
||||
{
|
||||
const auto session_id = it->second;
|
||||
session_ids_reloaded_this_frame.insert(session_id);
|
||||
AssetData& asset_data = m_session_id_to_asset_data[session_id];
|
||||
asset_data.load_type = AssetData::LoadType::PendingReload;
|
||||
asset_data.has_errors = false;
|
||||
for (const auto owner_session_id : asset_data.asset_owners)
|
||||
{
|
||||
AssetData& owner_asset_data = m_session_id_to_asset_data[owner_session_id];
|
||||
if (owner_asset_data.load_type == AssetData::LoadType::LoadFinalyzed)
|
||||
{
|
||||
owner_asset_data.load_type = AssetData::LoadType::DependenciesChanged;
|
||||
}
|
||||
}
|
||||
m_pending_assets.put(it->second, asset_data.asset.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_ram_used > m_max_ram_available)
|
||||
{
|
||||
const u64 threshold_ram = 0.8f * m_max_ram_available;
|
||||
u64 ram_used = m_ram_used;
|
||||
|
||||
// Clear out least recently used resources until
|
||||
// we get safely in our threshold
|
||||
while (ram_used > threshold_ram && m_loaded_assets.size() > 0)
|
||||
{
|
||||
const auto asset = m_loaded_assets.pop();
|
||||
ram_used -= asset->GetByteSizeInMemory();
|
||||
|
||||
AssetData& asset_data = m_session_id_to_asset_data[asset->GetSessionId()];
|
||||
|
||||
if (asset_data.type == AssetData::AssetType::TextureData)
|
||||
{
|
||||
m_texture_data_asset_cache.erase(asset->GetAssetId());
|
||||
}
|
||||
asset_data.asset.reset();
|
||||
asset_data.load_type = AssetData::LoadType::Unloaded;
|
||||
}
|
||||
|
||||
// Recalculate to ensure accuracy
|
||||
m_ram_used = 0;
|
||||
for (const auto asset : m_loaded_assets.elements())
|
||||
{
|
||||
m_ram_used += asset->GetByteSizeInMemory();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pending_assets.empty())
|
||||
return;
|
||||
|
||||
const auto asset_session_ids_loaded =
|
||||
m_asset_loader.LoadAssets(m_pending_assets.elements(), m_ram_used, m_max_ram_available);
|
||||
for (const std::size_t session_id : asset_session_ids_loaded)
|
||||
{
|
||||
// While unlikely, if we loaded an asset in the previous frame but it was reloaded
|
||||
// this frame, we should ignore this load and wait on the reload
|
||||
if (session_ids_reloaded_this_frame.count(session_id) > 0) [[unlikely]]
|
||||
continue;
|
||||
|
||||
m_pending_assets.erase(session_id);
|
||||
|
||||
AssetData& asset_data = m_session_id_to_asset_data[session_id];
|
||||
m_loaded_assets.put(session_id, asset_data.asset.get());
|
||||
asset_data.load_type = AssetData::LoadType::LoadFinished;
|
||||
m_ram_used += asset_data.asset->GetByteSizeInMemory();
|
||||
|
||||
for (const auto owner_session_id : asset_data.asset_owners)
|
||||
{
|
||||
AssetData& owner_asset_data = m_session_id_to_asset_data[owner_session_id];
|
||||
if (owner_asset_data.load_type == AssetData::LoadType::LoadFinalyzed)
|
||||
{
|
||||
owner_asset_data.load_type = AssetData::LoadType::DependenciesChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
182
Source/Core/VideoCommon/Assets/CustomResourceManager.h
Normal file
182
Source/Core/VideoCommon/Assets/CustomResourceManager.h
Normal file
|
@ -0,0 +1,182 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/HookableEvent.h"
|
||||
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader2.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class GameTextureAsset;
|
||||
|
||||
class CustomResourceManager
|
||||
{
|
||||
public:
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void Reset();
|
||||
|
||||
// Requests that an asset that exists be reloaded
|
||||
void ReloadAsset(const CustomAssetLibrary::AssetID& asset_id);
|
||||
|
||||
void XFBTriggered(std::string_view texture_hash);
|
||||
|
||||
CustomTextureData*
|
||||
GetTextureDataFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||
|
||||
private:
|
||||
struct AssetData
|
||||
{
|
||||
std::unique_ptr<CustomAsset> asset;
|
||||
CustomAssetLibrary::TimeType load_request_time = {};
|
||||
std::set<std::size_t> asset_owners;
|
||||
|
||||
enum class AssetType
|
||||
{
|
||||
TextureData
|
||||
};
|
||||
AssetType type;
|
||||
|
||||
enum class LoadType
|
||||
{
|
||||
PendingReload,
|
||||
LoadFinished,
|
||||
LoadFinalyzed,
|
||||
DependenciesChanged,
|
||||
Unloaded
|
||||
};
|
||||
LoadType load_type = LoadType::PendingReload;
|
||||
bool has_errors = false;
|
||||
};
|
||||
|
||||
struct InternalTextureDataResource
|
||||
{
|
||||
AssetData* asset_data = nullptr;
|
||||
VideoCommon::GameTextureAsset* asset = nullptr;
|
||||
std::shared_ptr<TextureData> texture_data;
|
||||
};
|
||||
|
||||
void LoadTextureDataAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||
InternalTextureDataResource* internal_texture_data);
|
||||
|
||||
template <typename T>
|
||||
T* CreateAsset(const CustomAssetLibrary::AssetID& asset_id, AssetData::AssetType asset_type,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
const auto [it, added] =
|
||||
m_asset_id_to_session_id.try_emplace(asset_id, m_session_id_to_asset_data.size());
|
||||
|
||||
if (added)
|
||||
{
|
||||
AssetData asset_data;
|
||||
asset_data.asset = std::make_unique<T>(library, asset_id, it->second);
|
||||
asset_data.type = asset_type;
|
||||
asset_data.has_errors = false;
|
||||
asset_data.load_type = AssetData::LoadType::PendingReload;
|
||||
asset_data.load_request_time = {};
|
||||
|
||||
m_session_id_to_asset_data.insert_or_assign(it->second, std::move(asset_data));
|
||||
|
||||
// Synchronize the priority cache session id
|
||||
m_pending_assets.prepare();
|
||||
m_loaded_assets.prepare();
|
||||
}
|
||||
auto& asset_data_from_session = m_session_id_to_asset_data[it->second];
|
||||
|
||||
// Asset got unloaded, rebuild it with the same metadata
|
||||
if (!asset_data_from_session.asset)
|
||||
{
|
||||
asset_data_from_session.asset = std::make_unique<T>(library, asset_id, it->second);
|
||||
asset_data_from_session.has_errors = false;
|
||||
asset_data_from_session.load_type = AssetData::LoadType::PendingReload;
|
||||
}
|
||||
|
||||
return static_cast<T*>(asset_data_from_session.asset.get());
|
||||
}
|
||||
|
||||
class LeastRecentlyUsedCache
|
||||
{
|
||||
public:
|
||||
const std::list<CustomAsset*>& elements() const { return m_asset_cache; }
|
||||
|
||||
void put(u64 asset_session_id, CustomAsset* asset)
|
||||
{
|
||||
erase(asset_session_id);
|
||||
m_asset_cache.push_front(asset);
|
||||
m_iterator_lookup[m_asset_cache.front()->GetSessionId()] = m_asset_cache.begin();
|
||||
}
|
||||
|
||||
CustomAsset* pop()
|
||||
{
|
||||
if (m_asset_cache.empty()) [[unlikely]]
|
||||
return nullptr;
|
||||
const auto ret = m_asset_cache.back();
|
||||
if (ret != nullptr)
|
||||
{
|
||||
m_iterator_lookup[ret->GetSessionId()].reset();
|
||||
}
|
||||
m_asset_cache.pop_back();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void prepare() { m_iterator_lookup.push_back(std::nullopt); }
|
||||
|
||||
void erase(u64 asset_session_id)
|
||||
{
|
||||
if (const auto iter = m_iterator_lookup[asset_session_id])
|
||||
{
|
||||
m_asset_cache.erase(*iter);
|
||||
m_iterator_lookup[asset_session_id].reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool empty() const { return m_asset_cache.empty(); }
|
||||
|
||||
std::size_t size() const { return m_asset_cache.size(); }
|
||||
|
||||
private:
|
||||
std::list<CustomAsset*> m_asset_cache;
|
||||
|
||||
// Note: this vector is expected to be kept in sync with
|
||||
// the total amount of (unique) assets ever seen
|
||||
std::vector<std::optional<decltype(m_asset_cache)::iterator>> m_iterator_lookup;
|
||||
};
|
||||
|
||||
LeastRecentlyUsedCache m_loaded_assets;
|
||||
LeastRecentlyUsedCache m_pending_assets;
|
||||
|
||||
std::map<std::size_t, AssetData> m_session_id_to_asset_data;
|
||||
std::map<CustomAssetLibrary::AssetID, std::size_t> m_asset_id_to_session_id;
|
||||
|
||||
u64 m_ram_used = 0;
|
||||
u64 m_max_ram_available = 0;
|
||||
|
||||
std::map<CustomAssetLibrary::AssetID, InternalTextureDataResource> m_texture_data_asset_cache;
|
||||
|
||||
std::mutex m_reload_mutex;
|
||||
std::set<CustomAssetLibrary::AssetID> m_assets_to_reload;
|
||||
|
||||
CustomAssetLoader2 m_asset_loader;
|
||||
|
||||
Common::EventHook m_xfb_event;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
|
@ -13,6 +13,8 @@
|
|||
#include "Common/JsonUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/System.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/Assets/MaterialAsset.h"
|
||||
#include "VideoCommon/Assets/MeshAsset.h"
|
||||
#include "VideoCommon/Assets/ShaderAsset.h"
|
||||
|
@ -53,7 +55,7 @@ std::size_t GetAssetSize(const CustomTextureData& data)
|
|||
CustomAssetLibrary::TimeType
|
||||
DirectFilesystemAssetLibrary::GetLastAssetWriteTime(const AssetID& asset_id) const
|
||||
{
|
||||
std::lock_guard lk(m_lock);
|
||||
std::lock_guard lk(m_asset_map_lock);
|
||||
if (auto iter = m_assetid_to_asset_map_path.find(asset_id);
|
||||
iter != m_assetid_to_asset_map_path.end())
|
||||
{
|
||||
|
@ -434,10 +436,42 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
|
|||
}
|
||||
|
||||
void DirectFilesystemAssetLibrary::SetAssetIDMapData(const AssetID& asset_id,
|
||||
AssetMap asset_path_map)
|
||||
VideoCommon::Assets::AssetMap asset_path_map)
|
||||
{
|
||||
std::lock_guard lk(m_lock);
|
||||
m_assetid_to_asset_map_path[asset_id] = std::move(asset_path_map);
|
||||
VideoCommon::Assets::AssetMap previous_asset_map;
|
||||
{
|
||||
std::lock_guard lk(m_asset_map_lock);
|
||||
previous_asset_map = m_assetid_to_asset_map_path[asset_id];
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lk(m_path_map_lock);
|
||||
for (const auto& [name, path] : previous_asset_map)
|
||||
{
|
||||
m_path_to_asset_id.erase(PathToString(path));
|
||||
}
|
||||
|
||||
for (const auto& [name, path] : asset_path_map)
|
||||
{
|
||||
m_path_to_asset_id[PathToString(path)] = asset_id;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lk(m_asset_map_lock);
|
||||
m_assetid_to_asset_map_path[asset_id] = std::move(asset_path_map);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectFilesystemAssetLibrary::PathModified(std::string_view path)
|
||||
{
|
||||
std::lock_guard lk(m_path_map_lock);
|
||||
if (const auto iter = m_path_to_asset_id.find(path); iter != m_path_to_asset_id.end())
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& resource_manager = system.GetCustomResourceManager();
|
||||
resource_manager.ReloadAsset(iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_path,
|
||||
|
@ -492,10 +526,10 @@ bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_p
|
|||
return true;
|
||||
}
|
||||
|
||||
DirectFilesystemAssetLibrary::AssetMap
|
||||
VideoCommon::Assets::AssetMap
|
||||
DirectFilesystemAssetLibrary::GetAssetMapForID(const AssetID& asset_id) const
|
||||
{
|
||||
std::lock_guard lk(m_lock);
|
||||
std::lock_guard lk(m_asset_map_lock);
|
||||
if (auto iter = m_assetid_to_asset_map_path.find(asset_id);
|
||||
iter != m_assetid_to_asset_map_path.end())
|
||||
{
|
||||
|
|
|
@ -8,18 +8,17 @@
|
|||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
#include "VideoCommon/Assets/Types.h"
|
||||
#include "VideoCommon/Assets/WatchableFilesystemAssetLibrary.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
// This class implements 'CustomAssetLibrary' and loads any assets
|
||||
// directly from the filesystem
|
||||
class DirectFilesystemAssetLibrary final : public CustomAssetLibrary
|
||||
class DirectFilesystemAssetLibrary final : public WatchableFilesystemAssetLibrary
|
||||
{
|
||||
public:
|
||||
using AssetMap = std::map<std::string, std::filesystem::path>;
|
||||
|
||||
LoadInfo LoadTexture(const AssetID& asset_id, TextureData* data) override;
|
||||
LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) override;
|
||||
LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* data) override;
|
||||
|
@ -31,16 +30,21 @@ public:
|
|||
// Assigns the asset id to a map of files, how this map is read is dependent on the data
|
||||
// For instance, a raw texture would expect the map to have a single entry and load that
|
||||
// file as the asset. But a model file data might have its data spread across multiple files
|
||||
void SetAssetIDMapData(const AssetID& asset_id, AssetMap asset_path_map);
|
||||
void SetAssetIDMapData(const AssetID& asset_id, Assets::AssetMap asset_path_map);
|
||||
|
||||
private:
|
||||
void PathModified(std::string_view path) override;
|
||||
|
||||
// Loads additional mip levels into the texture structure until _mip<N> texture is not found
|
||||
bool LoadMips(const std::filesystem::path& asset_path, CustomTextureData::ArraySlice* data);
|
||||
|
||||
// Gets the asset map given an asset id
|
||||
AssetMap GetAssetMapForID(const AssetID& asset_id) const;
|
||||
Assets::AssetMap GetAssetMapForID(const AssetID& asset_id) const;
|
||||
|
||||
mutable std::mutex m_lock;
|
||||
std::map<AssetID, std::map<std::string, std::filesystem::path>> m_assetid_to_asset_map_path;
|
||||
mutable std::mutex m_asset_map_lock;
|
||||
std::map<AssetID, Assets::AssetMap> m_assetid_to_asset_map_path;
|
||||
|
||||
mutable std::mutex m_path_map_lock;
|
||||
std::map<std::string, AssetID, std::less<>> m_path_to_asset_id;
|
||||
};
|
||||
} // namespace VideoCommon
|
||||
|
|
13
Source/Core/VideoCommon/Assets/Types.h
Normal file
13
Source/Core/VideoCommon/Assets/Types.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace VideoCommon::Assets
|
||||
{
|
||||
using AssetMap = std::map<std::string, std::filesystem::path>;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/FilesystemWatcher.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class WatchableFilesystemAssetLibrary : public CustomAssetLibrary, public Common::FilesystemWatcher
|
||||
{
|
||||
};
|
||||
} // namespace VideoCommon
|
|
@ -14,6 +14,10 @@ add_library(videocommon
|
|||
Assets/CustomAssetLibrary.h
|
||||
Assets/CustomAssetLoader.cpp
|
||||
Assets/CustomAssetLoader.h
|
||||
Assets/CustomAssetLoader2.cpp
|
||||
Assets/CustomAssetLoader2.h
|
||||
Assets/CustomResourceManager.cpp
|
||||
Assets/CustomResourceManager.h
|
||||
Assets/CustomTextureData.cpp
|
||||
Assets/CustomTextureData.h
|
||||
Assets/DirectFilesystemAssetLibrary.cpp
|
||||
|
@ -26,6 +30,8 @@ add_library(videocommon
|
|||
Assets/ShaderAsset.h
|
||||
Assets/TextureAsset.cpp
|
||||
Assets/TextureAsset.h
|
||||
Assets/Types.h
|
||||
Assets/WatchableFilesystemAssetLibrary.h
|
||||
AsyncRequests.cpp
|
||||
AsyncRequests.h
|
||||
AsyncShaderCompiler.cpp
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
#include <picojson.h>
|
||||
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/Types.h"
|
||||
|
||||
struct GraphicsModAssetConfig
|
||||
{
|
||||
VideoCommon::CustomAssetLibrary::AssetID m_asset_id;
|
||||
VideoCommon::DirectFilesystemAssetLibrary::AssetMap m_map;
|
||||
VideoCommon::Assets::AssetMap m_map;
|
||||
|
||||
void SerializeToConfig(picojson::object& json_obj) const;
|
||||
bool DeserializeFromConfig(const picojson::object& obj);
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
#include "Core/ConfigManager.h"
|
||||
#include "Core/System.h"
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
@ -95,10 +95,11 @@ void HiresTexture::Update()
|
|||
GetTextureDirectoriesWithGameId(File::GetUserPath(D_HIRESTEXTURES_IDX), game_id);
|
||||
const std::vector<std::string> extensions{".png", ".dds"};
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
|
||||
for (const auto& texture_directory : texture_directories)
|
||||
{
|
||||
// Watch this directory for any texture reloads
|
||||
s_file_library->Watch(texture_directory);
|
||||
|
||||
const auto texture_paths =
|
||||
Common::DoFileSearch({texture_directory}, extensions, /*recursive*/ true);
|
||||
|
||||
|
@ -130,10 +131,10 @@ void HiresTexture::Update()
|
|||
|
||||
if (g_ActiveConfig.bCacheHiresTextures)
|
||||
{
|
||||
auto hires_texture = std::make_shared<HiresTexture>(
|
||||
has_arbitrary_mipmaps,
|
||||
system.GetCustomAssetLoader().LoadGameTexture(filename, s_file_library));
|
||||
s_hires_texture_cache.try_emplace(filename, std::move(hires_texture));
|
||||
auto hires_texture =
|
||||
std::make_shared<HiresTexture>(has_arbitrary_mipmaps, std::move(filename));
|
||||
static_cast<void>(hires_texture->LoadTexture());
|
||||
s_hires_texture_cache.try_emplace(hires_texture->GetId(), hires_texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +168,7 @@ void HiresTexture::Clear()
|
|||
|
||||
std::shared_ptr<HiresTexture> HiresTexture::Search(const TextureInfo& texture_info)
|
||||
{
|
||||
const auto [base_filename, has_arb_mipmaps] = GetNameArbPair(texture_info);
|
||||
auto [base_filename, has_arb_mipmaps] = GetNameArbPair(texture_info);
|
||||
if (base_filename == "")
|
||||
return nullptr;
|
||||
|
||||
|
@ -177,24 +178,27 @@ std::shared_ptr<HiresTexture> HiresTexture::Search(const TextureInfo& texture_in
|
|||
}
|
||||
else
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto hires_texture = std::make_shared<HiresTexture>(
|
||||
has_arb_mipmaps,
|
||||
system.GetCustomAssetLoader().LoadGameTexture(base_filename, s_file_library));
|
||||
auto hires_texture = std::make_shared<HiresTexture>(has_arb_mipmaps, std::move(base_filename));
|
||||
if (g_ActiveConfig.bCacheHiresTextures)
|
||||
{
|
||||
s_hires_texture_cache.try_emplace(base_filename, hires_texture);
|
||||
s_hires_texture_cache.try_emplace(hires_texture->GetId(), hires_texture);
|
||||
}
|
||||
return hires_texture;
|
||||
}
|
||||
}
|
||||
|
||||
HiresTexture::HiresTexture(bool has_arbitrary_mipmaps,
|
||||
std::shared_ptr<VideoCommon::GameTextureAsset> asset)
|
||||
: m_has_arbitrary_mipmaps(has_arbitrary_mipmaps), m_game_texture(std::move(asset))
|
||||
HiresTexture::HiresTexture(bool has_arbitrary_mipmaps, std::string id)
|
||||
: m_has_arbitrary_mipmaps(has_arbitrary_mipmaps), m_id(std::move(id))
|
||||
{
|
||||
}
|
||||
|
||||
VideoCommon::CustomTextureData* HiresTexture::LoadTexture() const
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& custom_resource_manager = system.GetCustomResourceManager();
|
||||
return custom_resource_manager.GetTextureDataFromAsset(m_id, s_file_library);
|
||||
}
|
||||
|
||||
std::set<std::string> GetTextureDirectoriesWithGameId(const std::string& root_directory,
|
||||
const std::string& game_id)
|
||||
{
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
|
||||
|
@ -27,12 +26,13 @@ public:
|
|||
static void Shutdown();
|
||||
static std::shared_ptr<HiresTexture> Search(const TextureInfo& texture_info);
|
||||
|
||||
HiresTexture(bool has_arbitrary_mipmaps, std::shared_ptr<VideoCommon::GameTextureAsset> asset);
|
||||
HiresTexture(bool has_arbitrary_mipmaps, std::string id);
|
||||
|
||||
bool HasArbitraryMipmaps() const { return m_has_arbitrary_mipmaps; }
|
||||
const std::shared_ptr<VideoCommon::GameTextureAsset>& GetAsset() const { return m_game_texture; }
|
||||
VideoCommon::CustomTextureData* LoadTexture() const;
|
||||
const std::string& GetId() const { return m_id; }
|
||||
|
||||
private:
|
||||
bool m_has_arbitrary_mipmaps = false;
|
||||
std::shared_ptr<VideoCommon::GameTextureAsset> m_game_texture;
|
||||
std::string m_id;
|
||||
};
|
||||
|
|
|
@ -37,13 +37,13 @@
|
|||
#include "VideoCommon/AbstractFramebuffer.h"
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/AbstractStagingTexture.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
||||
#include "VideoCommon/HiresTextures.h"
|
||||
#include "VideoCommon/OpcodeDecoding.h"
|
||||
#include "VideoCommon/PixelShaderManager.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
|
@ -262,25 +262,10 @@ void TextureCacheBase::SetBackupConfig(const VideoConfig& config)
|
|||
|
||||
bool TextureCacheBase::DidLinkedAssetsChange(const TCacheEntry& entry)
|
||||
{
|
||||
for (const auto& cached_asset : entry.linked_game_texture_assets)
|
||||
{
|
||||
if (cached_asset.m_asset)
|
||||
{
|
||||
if (cached_asset.m_asset->GetLastLoadedTime() > cached_asset.m_cached_write_time)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!entry.hires_texture)
|
||||
return false;
|
||||
|
||||
for (const auto& cached_asset : entry.linked_asset_dependencies)
|
||||
{
|
||||
if (cached_asset.m_asset)
|
||||
{
|
||||
if (cached_asset.m_asset->GetLastLoadedTime() > cached_asset.m_cached_write_time)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return entry.last_custom_texture_data != entry.hires_texture->LoadTexture();
|
||||
}
|
||||
|
||||
RcTcacheEntry TextureCacheBase::ApplyPaletteToEntry(RcTcacheEntry& entry, const u8* palette,
|
||||
|
@ -1566,80 +1551,42 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp
|
|||
InvalidateTexture(oldest_entry);
|
||||
}
|
||||
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> cached_game_assets;
|
||||
std::vector<std::shared_ptr<VideoCommon::TextureData>> data_for_assets;
|
||||
std::shared_ptr<HiresTexture> hires_texture;
|
||||
bool has_arbitrary_mipmaps = false;
|
||||
bool skip_texture_dump = false;
|
||||
std::shared_ptr<HiresTexture> hires_texture;
|
||||
VideoCommon::CustomTextureData* custom_texture_data = nullptr;
|
||||
if (g_ActiveConfig.bHiresTextures)
|
||||
{
|
||||
hires_texture = HiresTexture::Search(texture_info);
|
||||
if (hires_texture)
|
||||
{
|
||||
auto asset = hires_texture->GetAsset();
|
||||
const auto loaded_time = asset->GetLastLoadedTime();
|
||||
cached_game_assets.push_back(
|
||||
VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>{std::move(asset), loaded_time});
|
||||
has_arbitrary_mipmaps = hires_texture->HasArbitraryMipmaps();
|
||||
custom_texture_data = hires_texture->LoadTexture();
|
||||
skip_texture_dump = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::CustomAsset>> additional_dependencies;
|
||||
|
||||
std::string texture_name = "";
|
||||
|
||||
if (g_ActiveConfig.bGraphicMods)
|
||||
{
|
||||
u32 height = texture_info.GetRawHeight();
|
||||
u32 width = texture_info.GetRawWidth();
|
||||
if (hires_texture)
|
||||
{
|
||||
auto asset = hires_texture->GetAsset();
|
||||
if (asset)
|
||||
{
|
||||
auto data = asset->GetData();
|
||||
if (data)
|
||||
{
|
||||
if (!data->m_texture.m_slices.empty())
|
||||
{
|
||||
if (!data->m_texture.m_slices[0].m_levels.empty())
|
||||
{
|
||||
height = data->m_texture.m_slices[0].m_levels[0].height;
|
||||
width = data->m_texture.m_slices[0].m_levels[0].width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
texture_name = texture_info.CalculateTextureName().GetFullName();
|
||||
GraphicsModActionData::TextureCreate texture_create{
|
||||
texture_name, width, height, &cached_game_assets, &additional_dependencies};
|
||||
GraphicsModActionData::TextureCreate texture_create{texture_name, width, height, nullptr,
|
||||
nullptr};
|
||||
for (const auto& action : g_graphics_mod_manager->GetTextureCreateActions(texture_name))
|
||||
{
|
||||
action->OnTextureCreate(&texture_create);
|
||||
}
|
||||
}
|
||||
|
||||
data_for_assets.reserve(cached_game_assets.size());
|
||||
for (auto& cached_asset : cached_game_assets)
|
||||
{
|
||||
auto data = cached_asset.m_asset->GetData();
|
||||
if (data)
|
||||
{
|
||||
if (cached_asset.m_asset->Validate(texture_info.GetRawWidth(), texture_info.GetRawHeight()))
|
||||
{
|
||||
data_for_assets.push_back(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto entry =
|
||||
CreateTextureEntry(TextureCreationInfo{base_hash, full_hash, bytes_per_block, palette_size},
|
||||
texture_info, textureCacheSafetyColorSampleSize,
|
||||
std::move(data_for_assets), has_arbitrary_mipmaps, skip_texture_dump);
|
||||
entry->linked_game_texture_assets = std::move(cached_game_assets);
|
||||
entry->linked_asset_dependencies = std::move(additional_dependencies);
|
||||
texture_info, textureCacheSafetyColorSampleSize, custom_texture_data,
|
||||
has_arbitrary_mipmaps, skip_texture_dump);
|
||||
entry->hires_texture = std::move(hires_texture);
|
||||
entry->last_custom_texture_data = custom_texture_data;
|
||||
entry->texture_info_name = std::move(texture_name);
|
||||
return entry;
|
||||
}
|
||||
|
@ -1649,8 +1596,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp
|
|||
// expected because each texture is loaded into a texture array
|
||||
RcTcacheEntry TextureCacheBase::CreateTextureEntry(
|
||||
const TextureCreationInfo& creation_info, const TextureInfo& texture_info,
|
||||
const int safety_color_sample_size,
|
||||
std::vector<std::shared_ptr<VideoCommon::TextureData>> assets_data,
|
||||
const int safety_color_sample_size, VideoCommon::CustomTextureData* custom_texture_data,
|
||||
const bool custom_arbitrary_mipmaps, bool skip_texture_dump)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
|
@ -1660,33 +1606,22 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry(
|
|||
#endif
|
||||
|
||||
RcTcacheEntry entry;
|
||||
if (!assets_data.empty())
|
||||
if (custom_texture_data)
|
||||
{
|
||||
const auto calculate_max_levels = [&]() {
|
||||
const auto max_element = std::ranges::max_element(
|
||||
assets_data, {}, [](const auto& v) { return v->m_texture.m_slices[0].m_levels.size(); });
|
||||
return (*max_element)->m_texture.m_slices[0].m_levels.size();
|
||||
};
|
||||
const u32 texLevels = no_mips ? 1 : (u32)calculate_max_levels();
|
||||
const auto& first_level = assets_data[0]->m_texture.m_slices[0].m_levels[0];
|
||||
const TextureConfig config(first_level.width, first_level.height, texLevels,
|
||||
static_cast<u32>(assets_data.size()), 1, first_level.format, 0,
|
||||
AbstractTextureType::Texture_2DArray);
|
||||
const u32 texLevels = no_mips ? 1 : (u32)custom_texture_data->m_slices[0].m_levels.size();
|
||||
const auto& first_level = custom_texture_data->m_slices[0].m_levels[0];
|
||||
const TextureConfig config(first_level.width, first_level.height, texLevels, 1, 1,
|
||||
first_level.format, 0, AbstractTextureType::Texture_2DArray);
|
||||
entry = AllocateCacheEntry(config);
|
||||
if (!entry) [[unlikely]]
|
||||
return entry;
|
||||
for (u32 data_index = 0; data_index < static_cast<u32>(assets_data.size()); data_index++)
|
||||
const auto& slice = custom_texture_data->m_slices[0];
|
||||
for (u32 level_index = 0;
|
||||
level_index < std::min(texLevels, static_cast<u32>(slice.m_levels.size())); ++level_index)
|
||||
{
|
||||
const auto& asset = assets_data[data_index];
|
||||
const auto& slice = asset->m_texture.m_slices[0];
|
||||
for (u32 level_index = 0;
|
||||
level_index < std::min(texLevels, static_cast<u32>(slice.m_levels.size()));
|
||||
++level_index)
|
||||
{
|
||||
const auto& level = slice.m_levels[level_index];
|
||||
entry->texture->Load(level_index, level.width, level.height, level.row_length,
|
||||
level.data.data(), level.data.size(), data_index);
|
||||
}
|
||||
const auto& level = slice.m_levels[level_index];
|
||||
entry->texture->Load(level_index, level.width, level.height, level.row_length,
|
||||
level.data.data(), level.data.size());
|
||||
}
|
||||
|
||||
entry->has_arbitrary_mips = custom_arbitrary_mipmaps;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/HiresTextures.h"
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
#include "VideoCommon/TextureDecoder.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
|
@ -167,8 +168,8 @@ struct TCacheEntry
|
|||
|
||||
std::string texture_info_name = "";
|
||||
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> linked_game_texture_assets;
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::CustomAsset>> linked_asset_dependencies;
|
||||
VideoCommon::CustomTextureData* last_custom_texture_data;
|
||||
std::shared_ptr<HiresTexture> hires_texture;
|
||||
|
||||
explicit TCacheEntry(std::unique_ptr<AbstractTexture> tex,
|
||||
std::unique_ptr<AbstractFramebuffer> fb);
|
||||
|
@ -351,11 +352,10 @@ private:
|
|||
|
||||
void SetBackupConfig(const VideoConfig& config);
|
||||
|
||||
RcTcacheEntry
|
||||
CreateTextureEntry(const TextureCreationInfo& creation_info, const TextureInfo& texture_info,
|
||||
int safety_color_sample_size,
|
||||
std::vector<std::shared_ptr<VideoCommon::TextureData>> assets_data,
|
||||
bool custom_arbitrary_mipmaps, bool skip_texture_dump);
|
||||
RcTcacheEntry CreateTextureEntry(const TextureCreationInfo& creation_info,
|
||||
const TextureInfo& texture_info, int safety_color_sample_size,
|
||||
VideoCommon::CustomTextureData* custom_texture_data,
|
||||
bool custom_arbitrary_mipmaps, bool skip_texture_dump);
|
||||
|
||||
RcTcacheEntry GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride);
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#endif
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/AsyncRequests.h"
|
||||
#include "VideoCommon/BPStructs.h"
|
||||
#include "VideoCommon/BoundingBox.h"
|
||||
|
@ -341,12 +342,16 @@ bool VideoBackendBase::InitializeShared(std::unique_ptr<AbstractGfx> gfx,
|
|||
}
|
||||
|
||||
g_shader_cache->InitializeShaderCache();
|
||||
system.GetCustomResourceManager().Initialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoBackendBase::ShutdownShared()
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.GetCustomResourceManager().Shutdown();
|
||||
|
||||
g_frame_dumper.reset();
|
||||
g_presenter.reset();
|
||||
|
||||
|
@ -369,7 +374,6 @@ void VideoBackendBase::ShutdownShared()
|
|||
|
||||
m_initialized = false;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
VertexLoaderManager::Clear();
|
||||
system.GetFifo().Shutdown();
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<AdditionalIncludeDirectories>$(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ExternalsDir)Vulkan-Headers\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ExternalsDir)VulkanMemoryAllocator\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ExternalsDir)watcher\watcher\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ExternalsDir)WIL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<!--WIL doesn't have it's own vcxproj/exports, and no externals reference WIL, so this is fine to define only for Dolphin-->
|
||||
<PreprocessorDefinitions>WIL_SUPPRESS_EXCEPTIONS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
|
|
Loading…
Add table
Reference in a new issue