VideoCommon: separate the concept of a 'resource' from an 'asset'. A resource is potentially multiple assets that are chained together but represent one type of data to the rest of the system. An example is a 'material'. A 'material' is a collection of textures, a custom shader, and some metadata that all comes together to form what the concept of the material is. There will be a 'material' resource. For now, start small by introducing the interface and change our texture loading which used assets from the old resource manager, to an actual resource.

This commit is contained in:
iwubcode
2025-10-29 01:21:30 -05:00
parent 59d9c1772a
commit 2d21a99205
17 changed files with 559 additions and 153 deletions

View File

@@ -33,12 +33,12 @@
#include "IOS/USB/Emulated/Infinity.h"
#include "IOS/USB/Emulated/Skylanders/Skylander.h"
#include "IOS/USB/USBScanner.h"
#include "VideoCommon/Assets/CustomResourceManager.h"
#include "VideoCommon/CommandProcessor.h"
#include "VideoCommon/Fifo.h"
#include "VideoCommon/GeometryShaderManager.h"
#include "VideoCommon/PixelEngine.h"
#include "VideoCommon/PixelShaderManager.h"
#include "VideoCommon/Resources/CustomResourceManager.h"
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/XFStateManager.h"

View File

@@ -673,10 +673,11 @@
<ClInclude Include="VideoCommon\AbstractShader.h" />
<ClInclude Include="VideoCommon\AbstractStagingTexture.h" />
<ClInclude Include="VideoCommon\AbstractTexture.h" />
<ClInclude Include="VideoCommon\Assets\AssetListener.h" />
<ClInclude Include="VideoCommon\Assets\CustomAsset.h" />
<ClInclude Include="VideoCommon\Assets\CustomAssetCache.h" />
<ClInclude Include="VideoCommon\Assets\CustomAssetLibrary.h" />
<ClInclude Include="VideoCommon\Assets\CustomAssetLoader.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" />
@@ -752,6 +753,9 @@
<ClInclude Include="VideoCommon\PostProcessing.h" />
<ClInclude Include="VideoCommon\Present.h" />
<ClInclude Include="VideoCommon\RenderState.h" />
<ClInclude Include="VideoCommon\Resources\CustomResourceManager.h" />
<ClInclude Include="VideoCommon\Resources\Resource.h" />
<ClInclude Include="VideoCommon\Resources\TextureDataResource.h" />
<ClInclude Include="VideoCommon\ShaderCache.h" />
<ClInclude Include="VideoCommon\ShaderCompileUtils.h" />
<ClInclude Include="VideoCommon\ShaderGenCommon.h" />
@@ -1340,8 +1344,8 @@
<ClCompile Include="VideoCommon\AbstractStagingTexture.cpp" />
<ClCompile Include="VideoCommon\AbstractTexture.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomAsset.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomAssetCache.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomAssetLoader.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" />
@@ -1402,6 +1406,9 @@
<ClCompile Include="VideoCommon\PostProcessing.cpp" />
<ClCompile Include="VideoCommon\Present.cpp" />
<ClCompile Include="VideoCommon\RenderState.cpp" />
<ClCompile Include="VideoCommon\Resources\CustomResourceManager.cpp" />
<ClCompile Include="VideoCommon\Resources\Resource.cpp" />
<ClCompile Include="VideoCommon\Resources\TextureDataResource.cpp" />
<ClCompile Include="VideoCommon\ShaderCache.cpp" />
<ClCompile Include="VideoCommon\ShaderCompileUtils.cpp" />
<ClCompile Include="VideoCommon\ShaderGenCommon.cpp" />

View File

@@ -0,0 +1,23 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
namespace VideoCommon
{
class AssetListener
{
public:
AssetListener() = default;
virtual ~AssetListener() = default;
AssetListener(const AssetListener&) = default;
AssetListener(AssetListener&&) = default;
AssetListener& operator=(const AssetListener&) = default;
AssetListener& operator=(AssetListener&&) = default;
virtual void NotifyAssetLoadSuccess() = 0;
virtual void NotifyAssetLoadFailed() = 0;
virtual void AssetUnloaded() = 0;
};
} // namespace VideoCommon

View File

@@ -1,7 +1,7 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Assets/CustomResourceManager.h"
#include "VideoCommon/Assets/CustomAssetCache.h"
#include "Common/Logging/Log.h"
#include "Common/MemoryUtil.h"
@@ -14,7 +14,7 @@
namespace VideoCommon
{
void CustomResourceManager::Initialize()
void CustomAssetCache::Initialize()
{
// Use half of available system memory but leave at least 2GiB unused for system stability.
constexpr size_t must_keep_unused = 2 * size_t(1024 * 1024 * 1024);
@@ -28,19 +28,16 @@ void CustomResourceManager::Initialize()
ERROR_LOG_FMT(VIDEO, "Not enough system memory for custom resources.");
m_asset_loader.Initialize();
m_xfb_event =
GetVideoEvents().after_frame_event.Register([this](Core::System&) { XFBTriggered(); });
}
void CustomResourceManager::Shutdown()
void CustomAssetCache::Shutdown()
{
Reset();
m_asset_loader.Shutdown();
}
void CustomResourceManager::Reset()
void CustomAssetCache::Reset()
{
m_asset_loader.Reset(true);
@@ -48,66 +45,27 @@ void CustomResourceManager::Reset()
m_pending_assets = {};
m_asset_handle_to_data.clear();
m_asset_id_to_handle.clear();
m_texture_data_asset_cache.clear();
m_dirty_assets.clear();
m_ram_used = 0;
}
void CustomResourceManager::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id)
void CustomAssetCache::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id)
{
std::lock_guard guard(m_dirty_mutex);
m_dirty_assets.insert(asset_id);
}
CustomResourceManager::TextureTimePair CustomResourceManager::GetTextureDataFromAsset(
const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
void CustomAssetCache::MarkAssetPending(CustomAsset* asset)
{
auto& resource = m_texture_data_asset_cache[asset_id];
if (resource.asset_data != nullptr &&
resource.asset_data->load_status == AssetData::LoadStatus::ResourceDataAvailable)
{
m_active_assets.MakeAssetHighestPriority(resource.asset->GetHandle(), resource.asset);
return {resource.texture_data, resource.asset->GetLastLoadedTime()};
}
// If there is an error, don't try and load again until the error is fixed
if (resource.asset_data != nullptr && resource.asset_data->has_load_error)
return {};
LoadTextureDataAsset(asset_id, std::move(library), &resource);
m_active_assets.MakeAssetHighestPriority(resource.asset->GetHandle(), resource.asset);
return {};
m_pending_assets.MakeAssetHighestPriority(asset->GetHandle(), asset);
}
void CustomResourceManager::LoadTextureDataAsset(
const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library, InternalTextureDataResource* resource)
void CustomAssetCache::MarkAssetActive(CustomAsset* asset)
{
if (!resource->asset)
{
resource->asset =
CreateAsset<TextureAsset>(asset_id, AssetData::AssetType::TextureData, std::move(library));
resource->asset_data = &m_asset_handle_to_data[resource->asset->GetHandle()];
}
auto texture_data = resource->asset->GetData();
if (!texture_data || resource->asset_data->load_status == AssetData::LoadStatus::PendingReload)
{
// Tell the system we are still interested in loading this asset
const auto asset_handle = resource->asset->GetHandle();
m_pending_assets.MakeAssetHighestPriority(asset_handle,
m_asset_handle_to_data[asset_handle].asset.get());
}
else if (resource->asset_data->load_status == AssetData::LoadStatus::LoadFinished)
{
resource->texture_data = std::move(texture_data);
resource->asset_data->load_status = AssetData::LoadStatus::ResourceDataAvailable;
}
m_active_assets.MakeAssetHighestPriority(asset->GetHandle(), asset);
}
void CustomResourceManager::XFBTriggered()
void CustomAssetCache::Update()
{
ProcessDirtyAssets();
ProcessLoadedAssets();
@@ -127,7 +85,7 @@ void CustomResourceManager::XFBTriggered()
m_asset_loader.ScheduleAssetsToLoad(m_pending_assets.Elements(), allowed_memory);
}
void CustomResourceManager::ProcessDirtyAssets()
void CustomAssetCache::ProcessDirtyAssets()
{
decltype(m_dirty_assets) dirty_assets;
@@ -154,7 +112,7 @@ void CustomResourceManager::ProcessDirtyAssets()
}
}
void CustomResourceManager::ProcessLoadedAssets()
void CustomAssetCache::ProcessLoadedAssets()
{
const auto load_results = m_asset_loader.TakeLoadResults();
@@ -189,10 +147,18 @@ void CustomResourceManager::ProcessLoadedAssets()
m_active_assets.InsertAsset(handle, asset_data.asset.get());
asset_data.load_status = AssetData::LoadStatus::LoadFinished;
}
for (const auto& listener : asset_data.listeners)
{
if (load_successful)
listener->NotifyAssetLoadSuccess();
else
listener->NotifyAssetLoadFailed();
}
}
}
void CustomResourceManager::RemoveAssetsUntilBelowMemoryLimit()
void CustomAssetCache::RemoveAssetsUntilBelowMemoryLimit()
{
const u64 threshold_ram = m_max_ram_available * 8 / 10;
@@ -209,11 +175,11 @@ void CustomResourceManager::RemoveAssetsUntilBelowMemoryLimit()
AssetData& asset_data = m_asset_handle_to_data[asset->GetHandle()];
// Remove the resource manager's cached entry with its asset data
if (asset_data.type == AssetData::AssetType::TextureData)
for (const auto& listener : asset_data.listeners)
{
m_texture_data_asset_cache.erase(asset->GetAssetId());
listener->AssetUnloaded();
}
// Remove the asset's copy
const std::size_t bytes_unloaded = asset_data.asset->Unload();
m_ram_used -= bytes_unloaded;

View File

@@ -10,25 +10,73 @@
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/HookableEvent.h"
#include "VideoCommon/Assets/AssetListener.h"
#include "VideoCommon/Assets/CustomAsset.h"
#include "VideoCommon/Assets/CustomAssetLibrary.h"
#include "VideoCommon/Assets/CustomAssetLoader.h"
#include "VideoCommon/Assets/CustomTextureData.h"
namespace VideoCommon
{
class TextureAsset;
// The resource manager manages custom resources (textures, shaders, meshes)
// called assets. These assets are loaded using a priority system,
// The asset cache manages custom assets (textures, shaders, meshes).
// These assets are loaded using a priority system,
// where assets requested more often gets loaded first. This system
// also tracks memory usage and if memory usage goes over a calculated limit,
// then assets will be purged with older assets being targeted first.
class CustomResourceManager
class CustomAssetCache
{
public:
// A generic interface to describe an asset
// and load state
struct AssetData
{
std::unique_ptr<CustomAsset> asset;
std::vector<AssetListener*> listeners;
CustomAsset::TimeType load_request_time = {};
bool has_load_error = false;
enum class LoadStatus
{
PendingReload,
LoadFinished,
Unloaded,
};
LoadStatus load_status = LoadStatus::PendingReload;
};
template <typename T>
T* CreateAsset(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library, AssetListener* listener)
{
const auto [it, added] =
m_asset_id_to_handle.try_emplace(asset_id, m_asset_handle_to_data.size());
if (added)
{
AssetData asset_data;
asset_data.asset = std::make_unique<T>(std::move(library), asset_id, it->second);
asset_data.load_request_time = {};
asset_data.has_load_error = false;
m_asset_handle_to_data.insert_or_assign(it->second, std::move(asset_data));
}
auto& asset_data_from_handle = m_asset_handle_to_data[it->second];
asset_data_from_handle.listeners.push_back(listener);
asset_data_from_handle.load_status = AssetData::LoadStatus::PendingReload;
return static_cast<T*>(asset_data_from_handle.asset.get());
}
AssetData* GetAssetData(const CustomAssetLibrary::AssetID& asset_id)
{
const auto it_handle = m_asset_id_to_handle.find(asset_id);
if (it_handle == m_asset_id_to_handle.end())
return nullptr;
return &m_asset_handle_to_data[it_handle->second];
}
void Initialize();
void Shutdown();
@@ -37,81 +85,21 @@ public:
// Request that an asset be reloaded
void MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id);
void XFBTriggered();
// Notify the system that we are interested in this asset and
// are waiting for it to be loaded
void MarkAssetPending(CustomAsset* asset);
using TextureTimePair = std::pair<std::shared_ptr<CustomTextureData>, CustomAsset::TimeType>;
// Notify the system we are interested in this asset and
// it has seen activity
void MarkAssetActive(CustomAsset* asset);
// Returns a pair with the custom texture data and the time it was last loaded
// Callees are not expected to hold onto the shared_ptr as that will prevent
// the resource manager from being able to properly release data
TextureTimePair GetTextureDataFromAsset(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
void Update();
private:
// A generic interface to describe an assets' type
// and load state
struct AssetData
{
std::unique_ptr<CustomAsset> asset;
CustomAsset::TimeType load_request_time = {};
bool has_load_error = false;
enum class AssetType
{
TextureData
};
AssetType type;
enum class LoadStatus
{
PendingReload,
LoadFinished,
ResourceDataAvailable,
Unloaded,
};
LoadStatus load_status = LoadStatus::PendingReload;
};
// A structure to represent some raw texture data
// (this data hasn't hit the GPU yet, used for custom textures)
struct InternalTextureDataResource
{
AssetData* asset_data = nullptr;
VideoCommon::TextureAsset* asset = nullptr;
std::shared_ptr<CustomTextureData> texture_data;
};
void LoadTextureDataAsset(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
InternalTextureDataResource* resource);
void ProcessDirtyAssets();
void ProcessLoadedAssets();
void RemoveAssetsUntilBelowMemoryLimit();
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_handle.try_emplace(asset_id, m_asset_handle_to_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.load_request_time = {};
asset_data.has_load_error = false;
m_asset_handle_to_data.insert_or_assign(it->second, std::move(asset_data));
}
auto& asset_data_from_handle = m_asset_handle_to_data[it->second];
asset_data_from_handle.load_status = AssetData::LoadStatus::PendingReload;
return static_cast<T*>(asset_data_from_handle.asset.get());
}
// Maintains a priority-sorted list of assets.
// Used to figure out which assets to load or unload first.
// Most recently used assets get marked with highest priority.
@@ -202,14 +190,10 @@ private:
// A calculated amount of memory to avoid exceeding.
u64 m_max_ram_available = 0;
std::map<CustomAssetLibrary::AssetID, InternalTextureDataResource> m_texture_data_asset_cache;
std::mutex m_dirty_mutex;
std::set<CustomAssetLibrary::AssetID> m_dirty_assets;
CustomAssetLoader m_asset_loader;
Common::EventHook m_xfb_event;
};
} // namespace VideoCommon

View File

@@ -14,13 +14,13 @@
#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"
#include "VideoCommon/Assets/TextureAsset.h"
#include "VideoCommon/Assets/TextureAssetUtils.h"
#include "VideoCommon/RenderState.h"
#include "VideoCommon/Resources/CustomResourceManager.h"
namespace VideoCommon
{

View File

@@ -8,13 +8,14 @@ add_library(videocommon
AbstractStagingTexture.h
AbstractTexture.cpp
AbstractTexture.h
Assets/AssetListener.h
Assets/CustomAsset.cpp
Assets/CustomAsset.h
Assets/CustomAssetCache.cpp
Assets/CustomAssetCache.h
Assets/CustomAssetLibrary.h
Assets/CustomAssetLoader.cpp
Assets/CustomAssetLoader.h
Assets/CustomResourceManager.cpp
Assets/CustomResourceManager.h
Assets/CustomTextureData.cpp
Assets/CustomTextureData.h
Assets/DirectFilesystemAssetLibrary.cpp
@@ -145,6 +146,12 @@ add_library(videocommon
Present.h
RenderState.cpp
RenderState.h
Resources/CustomResourceManager.cpp
Resources/CustomResourceManager.h
Resources/Resource.cpp
Resources/Resource.h
Resources/TextureDataResource.cpp
Resources/TextureDataResource.h
ShaderCache.cpp
ShaderCache.h
ShaderCompileUtils.cpp

View File

@@ -27,6 +27,7 @@
#include "VideoCommon/Assets/CustomAsset.h"
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/Resources/CustomResourceManager.h"
#include "VideoCommon/VideoConfig.h"
constexpr std::string_view s_format_prefix{"tex1_"};
@@ -191,7 +192,7 @@ HiresTexture::HiresTexture(bool has_arbitrary_mipmaps, std::string id)
{
}
VideoCommon::CustomResourceManager::TextureTimePair HiresTexture::LoadTexture() const
VideoCommon::TextureDataResource* HiresTexture::LoadTexture() const
{
auto& system = Core::System::GetInstance();
auto& custom_resource_manager = system.GetCustomResourceManager();

View File

@@ -8,12 +8,13 @@
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "VideoCommon/Assets/CustomResourceManager.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/TextureConfig.h"
#include "VideoCommon/TextureInfo.h"
namespace VideoCommon
{
class TextureDataResource;
}
enum class TextureFormat;
std::set<std::string> GetTextureDirectoriesWithGameId(const std::string& root_directory,
@@ -30,7 +31,7 @@ public:
HiresTexture(bool has_arbitrary_mipmaps, std::string id);
bool HasArbitraryMipmaps() const { return m_has_arbitrary_mipmaps; }
VideoCommon::CustomResourceManager::TextureTimePair LoadTexture() const;
VideoCommon::TextureDataResource* LoadTexture() const;
const std::string& GetId() const { return m_id; }
private:

View File

@@ -0,0 +1,124 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Resources/CustomResourceManager.h"
#include "VideoCommon/VideoEvents.h"
namespace VideoCommon
{
void CustomResourceManager::Initialize()
{
m_asset_cache.Initialize();
m_xfb_event =
AfterFrameEvent::Register([this](Core::System&) { XFBTriggered(); }, "CustomResourceManager");
}
void CustomResourceManager::Shutdown()
{
m_asset_cache.Shutdown();
Reset();
}
void CustomResourceManager::Reset()
{
m_texture_data_resources.clear();
m_asset_cache.Reset();
}
void CustomResourceManager::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id)
{
m_asset_cache.MarkAssetDirty(asset_id);
}
void CustomResourceManager::XFBTriggered()
{
m_asset_cache.Update();
}
TextureDataResource* CustomResourceManager::GetTextureDataFromAsset(
const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
{
const auto [it, added] = m_texture_data_resources.try_emplace(asset_id, nullptr);
if (added)
{
it->second = std::make_unique<TextureDataResource>(CreateResourceContext(asset_id, library));
}
ProcessResource(it->second.get());
return it->second.get();
}
Resource::ResourceContext CustomResourceManager::CreateResourceContext(
const CustomAssetLibrary::AssetID& asset_id,
const std::shared_ptr<VideoCommon::CustomAssetLibrary>& library)
{
return Resource::ResourceContext{asset_id, library, &m_asset_cache, this};
}
void CustomResourceManager::ProcessResource(Resource* resource)
{
resource->MarkAsActive();
const auto data_processed = resource->IsDataProcessed();
if (data_processed == Resource::TaskComplete::Yes ||
data_processed == Resource::TaskComplete::Error)
{
resource->MarkAsActive();
if (data_processed == Resource::TaskComplete::Error)
return;
}
// Early out if we're already at our end state
if (resource->GetState() == Resource::State::DataAvailable)
return;
ProcessResourceState(resource);
}
void CustomResourceManager::ProcessResourceState(Resource* resource)
{
Resource::State next_state = resource->GetState();
Resource::TaskComplete task_complete = Resource::TaskComplete::No;
switch (resource->GetState())
{
case Resource::State::ReloadData:
resource->ResetData();
task_complete = Resource::TaskComplete::Yes;
next_state = Resource::State::CollectingPrimaryData;
break;
case Resource::State::CollectingPrimaryData:
task_complete = resource->CollectPrimaryData();
next_state = Resource::State::CollectingDependencyData;
if (task_complete == Resource::TaskComplete::No)
resource->MarkAsPending();
break;
case Resource::State::CollectingDependencyData:
task_complete = resource->CollectDependencyData();
next_state = Resource::State::ProcessingData;
break;
case Resource::State::ProcessingData:
task_complete = resource->ProcessData();
next_state = Resource::State::DataAvailable;
};
if (task_complete == Resource::TaskComplete::Yes)
{
resource->m_state = next_state;
if (next_state == Resource::State::DataAvailable)
{
resource->m_data_processed = task_complete;
}
else
{
ProcessResourceState(resource);
}
}
else if (task_complete == Resource::TaskComplete::Error)
{
resource->m_data_processed = task_complete;
}
}
} // namespace VideoCommon

View File

@@ -0,0 +1,46 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <memory>
#include "Common/HookableEvent.h"
#include "VideoCommon/Assets/CustomAssetCache.h"
#include "VideoCommon/Resources/TextureDataResource.h"
namespace VideoCommon
{
class CustomResourceManager
{
public:
void Initialize();
void Shutdown();
void Reset();
// Request that an asset be reloaded
void MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id);
void XFBTriggered();
TextureDataResource*
GetTextureDataFromAsset(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
private:
Resource::ResourceContext
CreateResourceContext(const CustomAssetLibrary::AssetID& asset_id,
const std::shared_ptr<VideoCommon::CustomAssetLibrary>& library);
void ProcessResource(Resource* resource);
void ProcessResourceState(Resource* resource);
CustomAssetCache m_asset_cache;
std::map<CustomAssetLibrary::AssetID, std::unique_ptr<TextureDataResource>>
m_texture_data_resources;
Common::EventHook m_xfb_event;
};
} // namespace VideoCommon

View File

@@ -0,0 +1,81 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Resources/Resource.h"
namespace VideoCommon
{
Resource::Resource(ResourceContext resource_context)
: m_resource_context(std::move(resource_context))
{
}
void Resource::NotifyAssetChanged(bool has_error)
{
m_data_processed = has_error ? TaskComplete::Error : TaskComplete::No;
m_state = State::ReloadData;
for (Resource* reference : m_references)
{
reference->NotifyAssetChanged(has_error);
}
}
void Resource::NotifyAssetUnloaded()
{
OnUnloadRequested();
for (Resource* reference : m_references)
{
reference->NotifyAssetUnloaded();
}
}
void Resource::AddReference(Resource* reference)
{
m_references.insert(reference);
}
void Resource::RemoveReference(Resource* reference)
{
m_references.erase(reference);
}
void Resource::NotifyAssetLoadSuccess()
{
NotifyAssetChanged(false);
}
void Resource::NotifyAssetLoadFailed()
{
NotifyAssetChanged(true);
}
void Resource::AssetUnloaded()
{
NotifyAssetUnloaded();
}
void Resource::OnUnloadRequested()
{
}
void Resource::ResetData()
{
}
Resource::TaskComplete Resource::CollectPrimaryData()
{
return TaskComplete::Yes;
}
Resource::TaskComplete Resource::CollectDependencyData()
{
return TaskComplete::Yes;
}
Resource::TaskComplete Resource::ProcessData()
{
return TaskComplete::Yes;
}
} // namespace VideoCommon

View File

@@ -0,0 +1,82 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "VideoCommon/Assets/AssetListener.h"
#include "VideoCommon/Assets/CustomAssetLibrary.h"
#include <memory>
#include <unordered_set>
class AbstractTexture;
namespace VideoCommon
{
class CustomAssetCache;
class CustomResourceManager;
// A resource is an abstract object that maintains
// relationships between assets (ex: a material that references a texture),
// as well as a standard way of calculating the final data (ex: a material's AbstractPipeline)
class Resource : public AssetListener
{
public:
// Everything the resource needs to manage itself
struct ResourceContext
{
CustomAssetLibrary::AssetID primary_asset_id;
std::shared_ptr<CustomAssetLibrary> asset_library;
CustomAssetCache* asset_cache;
CustomResourceManager* resource_manager;
};
explicit Resource(ResourceContext resource_context);
enum class TaskComplete
{
Yes,
No,
Error,
};
enum class State
{
ReloadData,
CollectingPrimaryData,
CollectingDependencyData,
ProcessingData,
DataAvailable,
};
TaskComplete IsDataProcessed() const { return m_data_processed; }
State GetState() const { return m_state; }
void AddReference(Resource* reference);
void RemoveReference(Resource* reference);
virtual void MarkAsActive() = 0;
virtual void MarkAsPending() = 0;
protected:
ResourceContext m_resource_context;
private:
void NotifyAssetChanged(bool has_error);
void NotifyAssetUnloaded();
void NotifyAssetLoadSuccess() final;
void NotifyAssetLoadFailed() final;
void AssetUnloaded() final;
virtual void OnUnloadRequested();
friend class CustomResourceManager;
virtual void ResetData();
virtual TaskComplete CollectPrimaryData();
virtual TaskComplete CollectDependencyData();
virtual TaskComplete ProcessData();
TaskComplete m_data_processed = TaskComplete::No;
State m_state = State::ReloadData;
std::unordered_set<Resource*> m_references;
};
} // namespace VideoCommon

View File

@@ -0,0 +1,48 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Resources/TextureDataResource.h"
#include "VideoCommon/Assets/CustomAssetCache.h"
namespace VideoCommon
{
TextureDataResource::TextureDataResource(Resource::ResourceContext resource_context)
: Resource(std::move(resource_context))
{
m_texture_asset = m_resource_context.asset_cache->CreateAsset<TextureAsset>(
m_resource_context.primary_asset_id, m_resource_context.asset_library, this);
}
std::shared_ptr<CustomTextureData> TextureDataResource::GetData() const
{
return m_current_texture_data;
}
CustomAsset::TimeType TextureDataResource::GetLoadTime() const
{
return m_current_time;
}
Resource::TaskComplete TextureDataResource::CollectPrimaryData()
{
const auto last_load_time = m_texture_asset->GetLastLoadedTime();
const auto asset = m_texture_asset->GetData();
if (!asset)
return Resource::TaskComplete::No;
m_current_texture_data = asset;
m_current_time = last_load_time;
return Resource::TaskComplete::Yes;
}
void TextureDataResource::MarkAsActive()
{
m_resource_context.asset_cache->MarkAssetActive(m_texture_asset);
}
void TextureDataResource::MarkAsPending()
{
m_resource_context.asset_cache->MarkAssetPending(m_texture_asset);
}
} // namespace VideoCommon

View File

@@ -0,0 +1,33 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "VideoCommon/Resources/Resource.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/Assets/TextureAsset.h"
namespace VideoCommon
{
class TextureDataResource final : public Resource
{
public:
explicit TextureDataResource(Resource::ResourceContext resource_context);
std::shared_ptr<CustomTextureData> GetData() const;
CustomAsset::TimeType GetLoadTime() const;
void MarkAsActive() override;
void MarkAsPending() override;
private:
TaskComplete CollectPrimaryData() override;
// Note: asset cache owns the asset, we access as a reference
TextureAsset* m_texture_asset = nullptr;
std::shared_ptr<CustomTextureData> m_current_texture_data;
CustomAsset::TimeType m_current_time = {};
};
} // namespace VideoCommon

View File

@@ -4,6 +4,7 @@
#include "VideoCommon/TextureCacheBase.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstring>
#include <memory>
@@ -37,7 +38,6 @@
#include "VideoCommon/AbstractFramebuffer.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/AbstractStagingTexture.h"
#include "VideoCommon/Assets/CustomResourceManager.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/Assets/TextureAssetUtils.h"
#include "VideoCommon/BPMemory.h"
@@ -48,6 +48,7 @@
#include "VideoCommon/OpcodeDecoding.h"
#include "VideoCommon/PixelShaderManager.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/Resources/CustomResourceManager.h"
#include "VideoCommon/ShaderCache.h"
#include "VideoCommon/Statistics.h"
#include "VideoCommon/TMEM.h"
@@ -266,9 +267,9 @@ bool TextureCacheBase::DidLinkedAssetsChange(const TCacheEntry& entry)
if (!entry.hires_texture)
return false;
const auto [texture_data, load_time] = entry.hires_texture->LoadTexture();
const auto* resource = entry.hires_texture->LoadTexture();
return load_time > entry.last_load_time;
return resource->GetLoadTime() > entry.last_load_time;
}
RcTcacheEntry TextureCacheBase::ApplyPaletteToEntry(RcTcacheEntry& entry, const u8* palette,
@@ -1569,7 +1570,9 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp
if (hires_texture)
{
has_arbitrary_mipmaps = hires_texture->HasArbitraryMipmaps();
std::tie(custom_texture_data, load_time) = hires_texture->LoadTexture();
const auto resource = hires_texture->LoadTexture();
load_time = resource->GetLoadTime();
custom_texture_data = resource->GetData();
if (custom_texture_data && !VideoCommon::ValidateTextureData(
hires_texture->GetId(), *custom_texture_data,
texture_info.GetRawWidth(), texture_info.GetRawHeight()))

View File

@@ -43,7 +43,6 @@
#endif
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/Assets/CustomResourceManager.h"
#include "VideoCommon/AsyncRequests.h"
#include "VideoCommon/BPStructs.h"
#include "VideoCommon/BoundingBox.h"
@@ -59,6 +58,7 @@
#include "VideoCommon/PixelEngine.h"
#include "VideoCommon/PixelShaderManager.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/Resources/CustomResourceManager.h"
#include "VideoCommon/TMEM.h"
#include "VideoCommon/TextureCacheBase.h"
#include "VideoCommon/VertexLoaderManager.h"