mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-09-11 20:15:48 +00:00
VideoCommon: move material/texture/mesh logic to new ResourceManager system
This commit is contained in:
parent
a176d8ffd8
commit
013eeb44ef
24 changed files with 2255 additions and 689 deletions
22
Source/Core/VideoCommon/Assets/AssetListener.h
Normal file
22
Source/Core/VideoCommon/Assets/AssetListener.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// 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 AssetLoaded(bool has_error, bool triggered_by_reload) = 0;
|
||||||
|
virtual void AssetUnloaded() = 0;
|
||||||
|
};
|
||||||
|
} // namespace VideoCommon
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2025 Dolphin Emulator Project
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// 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/Logging/Log.h"
|
||||||
#include "Common/MemoryUtil.h"
|
#include "Common/MemoryUtil.h"
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
namespace VideoCommon
|
namespace VideoCommon
|
||||||
{
|
{
|
||||||
void CustomResourceManager::Initialize()
|
void CustomAssetCache::Initialize()
|
||||||
{
|
{
|
||||||
// Use half of available system memory but leave at least 2GiB unused for system stability.
|
// 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);
|
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.");
|
ERROR_LOG_FMT(VIDEO, "Not enough system memory for custom resources.");
|
||||||
|
|
||||||
m_asset_loader.Initialize();
|
m_asset_loader.Initialize();
|
||||||
|
|
||||||
m_xfb_event =
|
|
||||||
AfterFrameEvent::Register([this](Core::System&) { XFBTriggered(); }, "CustomResourceManager");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CustomResourceManager::Shutdown()
|
void CustomAssetCache::Shutdown()
|
||||||
{
|
{
|
||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
m_asset_loader.Shutdown();
|
m_asset_loader.Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CustomResourceManager::Reset()
|
void CustomAssetCache::Reset()
|
||||||
{
|
{
|
||||||
m_asset_loader.Reset(true);
|
m_asset_loader.Reset(true);
|
||||||
|
|
||||||
|
@ -48,66 +45,27 @@ void CustomResourceManager::Reset()
|
||||||
m_pending_assets = {};
|
m_pending_assets = {};
|
||||||
m_asset_handle_to_data.clear();
|
m_asset_handle_to_data.clear();
|
||||||
m_asset_id_to_handle.clear();
|
m_asset_id_to_handle.clear();
|
||||||
m_texture_data_asset_cache.clear();
|
|
||||||
m_dirty_assets.clear();
|
m_dirty_assets.clear();
|
||||||
m_ram_used = 0;
|
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);
|
std::lock_guard guard(m_dirty_mutex);
|
||||||
m_dirty_assets.insert(asset_id);
|
m_dirty_assets.insert(asset_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomResourceManager::TextureTimePair CustomResourceManager::GetTextureDataFromAsset(
|
void CustomAssetCache::MarkAssetPending(CustomAsset* asset)
|
||||||
const CustomAssetLibrary::AssetID& asset_id,
|
|
||||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
|
||||||
{
|
{
|
||||||
auto& resource = m_texture_data_asset_cache[asset_id];
|
m_pending_assets.MakeAssetHighestPriority(asset->GetHandle(), asset);
|
||||||
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 {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CustomResourceManager::LoadTextureDataAsset(
|
void CustomAssetCache::MarkAssetActive(CustomAsset* asset)
|
||||||
const CustomAssetLibrary::AssetID& asset_id,
|
|
||||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library, InternalTextureDataResource* resource)
|
|
||||||
{
|
{
|
||||||
if (!resource->asset)
|
m_active_assets.MakeAssetHighestPriority(asset->GetHandle(), 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CustomResourceManager::XFBTriggered()
|
void CustomAssetCache::Update()
|
||||||
{
|
{
|
||||||
ProcessDirtyAssets();
|
ProcessDirtyAssets();
|
||||||
ProcessLoadedAssets();
|
ProcessLoadedAssets();
|
||||||
|
@ -127,7 +85,7 @@ void CustomResourceManager::XFBTriggered()
|
||||||
m_asset_loader.ScheduleAssetsToLoad(m_pending_assets.Elements(), allowed_memory);
|
m_asset_loader.ScheduleAssetsToLoad(m_pending_assets.Elements(), allowed_memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CustomResourceManager::ProcessDirtyAssets()
|
void CustomAssetCache::ProcessDirtyAssets()
|
||||||
{
|
{
|
||||||
decltype(m_dirty_assets) dirty_assets;
|
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();
|
const auto load_results = m_asset_loader.TakeLoadResults();
|
||||||
|
|
||||||
|
@ -179,6 +137,8 @@ void CustomResourceManager::ProcessLoadedAssets()
|
||||||
|
|
||||||
m_pending_assets.RemoveAsset(handle);
|
m_pending_assets.RemoveAsset(handle);
|
||||||
|
|
||||||
|
const bool triggered_by_reload =
|
||||||
|
asset_data.load_request_time > VideoCommon::CustomAsset::TimeType{};
|
||||||
asset_data.load_request_time = {};
|
asset_data.load_request_time = {};
|
||||||
if (!load_successful)
|
if (!load_successful)
|
||||||
{
|
{
|
||||||
|
@ -189,10 +149,15 @@ void CustomResourceManager::ProcessLoadedAssets()
|
||||||
m_active_assets.InsertAsset(handle, asset_data.asset.get());
|
m_active_assets.InsertAsset(handle, asset_data.asset.get());
|
||||||
asset_data.load_status = AssetData::LoadStatus::LoadFinished;
|
asset_data.load_status = AssetData::LoadStatus::LoadFinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& listener : asset_data.listeners)
|
||||||
|
{
|
||||||
|
listener->AssetLoaded(!load_successful, triggered_by_reload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CustomResourceManager::RemoveAssetsUntilBelowMemoryLimit()
|
void CustomAssetCache::RemoveAssetsUntilBelowMemoryLimit()
|
||||||
{
|
{
|
||||||
const u64 threshold_ram = m_max_ram_available * 8 / 10;
|
const u64 threshold_ram = m_max_ram_available * 8 / 10;
|
||||||
|
|
||||||
|
@ -209,11 +174,11 @@ void CustomResourceManager::RemoveAssetsUntilBelowMemoryLimit()
|
||||||
|
|
||||||
AssetData& asset_data = m_asset_handle_to_data[asset->GetHandle()];
|
AssetData& asset_data = m_asset_handle_to_data[asset->GetHandle()];
|
||||||
|
|
||||||
// Remove the resource manager's cached entry with its asset data
|
for (const auto& listener : asset_data.listeners)
|
||||||
if (asset_data.type == AssetData::AssetType::TextureData)
|
|
||||||
{
|
{
|
||||||
m_texture_data_asset_cache.erase(asset->GetAssetId());
|
listener->AssetUnloaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the asset's copy
|
// Remove the asset's copy
|
||||||
const std::size_t bytes_unloaded = asset_data.asset->Unload();
|
const std::size_t bytes_unloaded = asset_data.asset->Unload();
|
||||||
m_ram_used -= bytes_unloaded;
|
m_ram_used -= bytes_unloaded;
|
|
@ -10,25 +10,73 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/HookableEvent.h"
|
|
||||||
|
|
||||||
|
#include "VideoCommon/Assets/AssetListener.h"
|
||||||
#include "VideoCommon/Assets/CustomAsset.h"
|
#include "VideoCommon/Assets/CustomAsset.h"
|
||||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
|
||||||
|
|
||||||
namespace VideoCommon
|
namespace VideoCommon
|
||||||
{
|
{
|
||||||
class TextureAsset;
|
// The asset cache manages custom resources (textures, shaders, meshes)
|
||||||
|
|
||||||
// The resource manager manages custom resources (textures, shaders, meshes)
|
|
||||||
// called assets. These assets are loaded using a priority system,
|
// called assets. These assets are loaded using a priority system,
|
||||||
// where assets requested more often gets loaded first. This system
|
// where assets requested more often gets loaded first. This system
|
||||||
// also tracks memory usage and if memory usage goes over a calculated limit,
|
// also tracks memory usage and if memory usage goes over a calculated limit,
|
||||||
// then assets will be purged with older assets being targeted first.
|
// then assets will be purged with older assets being targeted first.
|
||||||
class CustomResourceManager
|
class CustomAssetCache
|
||||||
{
|
{
|
||||||
public:
|
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>(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 Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
|
@ -37,81 +85,21 @@ public:
|
||||||
// Request that an asset be reloaded
|
// Request that an asset be reloaded
|
||||||
void MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id);
|
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
|
void Update();
|
||||||
// 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);
|
|
||||||
|
|
||||||
private:
|
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 ProcessDirtyAssets();
|
||||||
void ProcessLoadedAssets();
|
void ProcessLoadedAssets();
|
||||||
void RemoveAssetsUntilBelowMemoryLimit();
|
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.
|
// Maintains a priority-sorted list of assets.
|
||||||
// Used to figure out which assets to load or unload first.
|
// Used to figure out which assets to load or unload first.
|
||||||
// Most recently used assets get marked with highest priority.
|
// Most recently used assets get marked with highest priority.
|
||||||
|
@ -202,14 +190,10 @@ private:
|
||||||
// A calculated amount of memory to avoid exceeding.
|
// A calculated amount of memory to avoid exceeding.
|
||||||
u64 m_max_ram_available = 0;
|
u64 m_max_ram_available = 0;
|
||||||
|
|
||||||
std::map<CustomAssetLibrary::AssetID, InternalTextureDataResource> m_texture_data_asset_cache;
|
|
||||||
|
|
||||||
std::mutex m_dirty_mutex;
|
std::mutex m_dirty_mutex;
|
||||||
std::set<CustomAssetLibrary::AssetID> m_dirty_assets;
|
std::set<CustomAssetLibrary::AssetID> m_dirty_assets;
|
||||||
|
|
||||||
CustomAssetLoader m_asset_loader;
|
CustomAssetLoader m_asset_loader;
|
||||||
|
|
||||||
Common::EventHook m_xfb_event;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace VideoCommon
|
} // namespace VideoCommon
|
|
@ -1,401 +0,0 @@
|
||||||
// Copyright 2022 Dolphin Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h"
|
|
||||||
|
|
||||||
#include "VideoCommon/AbstractGfx.h"
|
|
||||||
#include "VideoCommon/VideoConfig.h"
|
|
||||||
|
|
||||||
CustomShaderCache::CustomShaderCache()
|
|
||||||
{
|
|
||||||
m_api_type = g_backend_info.api_type;
|
|
||||||
m_host_config.bits = ShaderHostConfig::GetCurrent().bits;
|
|
||||||
|
|
||||||
m_async_shader_compiler = g_gfx->CreateAsyncShaderCompiler();
|
|
||||||
m_async_shader_compiler->StartWorkerThreads(1); // TODO
|
|
||||||
|
|
||||||
m_async_uber_shader_compiler = g_gfx->CreateAsyncShaderCompiler();
|
|
||||||
m_async_uber_shader_compiler->StartWorkerThreads(1); // TODO
|
|
||||||
|
|
||||||
m_frame_end_handler = AfterFrameEvent::Register([this](Core::System&) { RetrieveAsyncShaders(); },
|
|
||||||
"RetrieveAsyncShaders");
|
|
||||||
|
|
||||||
m_mesh_cache.Initialize(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomShaderCache::~CustomShaderCache()
|
|
||||||
{
|
|
||||||
if (m_async_shader_compiler)
|
|
||||||
m_async_shader_compiler->StopWorkerThreads();
|
|
||||||
|
|
||||||
if (m_async_uber_shader_compiler)
|
|
||||||
m_async_uber_shader_compiler->StopWorkerThreads();
|
|
||||||
|
|
||||||
m_mesh_cache.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomShaderCache::RetrieveAsyncShaders()
|
|
||||||
{
|
|
||||||
m_async_shader_compiler->RetrieveWorkItems();
|
|
||||||
m_async_uber_shader_compiler->RetrieveWorkItems();
|
|
||||||
|
|
||||||
m_mesh_cache.RetrieveAsyncShaders();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomShaderCache::Reload()
|
|
||||||
{
|
|
||||||
while (m_async_shader_compiler->HasPendingWork() || m_async_shader_compiler->HasCompletedWork())
|
|
||||||
{
|
|
||||||
m_async_shader_compiler->RetrieveWorkItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (m_async_uber_shader_compiler->HasPendingWork() ||
|
|
||||||
m_async_uber_shader_compiler->HasCompletedWork())
|
|
||||||
{
|
|
||||||
m_async_uber_shader_compiler->RetrieveWorkItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ps_cache = {};
|
|
||||||
m_uber_ps_cache = {};
|
|
||||||
m_pipeline_cache = {};
|
|
||||||
m_uber_pipeline_cache = {};
|
|
||||||
|
|
||||||
m_mesh_cache.Reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<const AbstractPipeline*>
|
|
||||||
CustomShaderCache::GetPipelineAsync(const VideoCommon::GXPipelineUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders,
|
|
||||||
const AbstractPipelineConfig& pipeline_config)
|
|
||||||
{
|
|
||||||
if (auto holder = m_pipeline_cache.GetHolder(uid, custom_shaders))
|
|
||||||
{
|
|
||||||
if (holder->pending)
|
|
||||||
return std::nullopt;
|
|
||||||
return holder->value.get();
|
|
||||||
}
|
|
||||||
AsyncCreatePipeline(uid, custom_shaders, pipeline_config);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<const AbstractPipeline*>
|
|
||||||
CustomShaderCache::GetPipelineAsync(const VideoCommon::GXUberPipelineUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders,
|
|
||||||
const AbstractPipelineConfig& pipeline_config)
|
|
||||||
{
|
|
||||||
if (auto holder = m_uber_pipeline_cache.GetHolder(uid, custom_shaders))
|
|
||||||
{
|
|
||||||
if (holder->pending)
|
|
||||||
return std::nullopt;
|
|
||||||
return holder->value.get();
|
|
||||||
}
|
|
||||||
AsyncCreatePipeline(uid, custom_shaders, pipeline_config);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AbstractPipeline* CustomShaderCache::GetPipelineForUid(const VideoCommon::GXPipelineUid& uid)
|
|
||||||
{
|
|
||||||
return m_mesh_cache.GetPipelineForUid(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
const AbstractPipeline*
|
|
||||||
CustomShaderCache::GetUberPipelineForUid(const VideoCommon::GXUberPipelineUid& uid)
|
|
||||||
{
|
|
||||||
return m_mesh_cache.GetUberPipelineForUid(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<const AbstractPipeline*>
|
|
||||||
CustomShaderCache::GetPipelineForUidAsync(const VideoCommon::GXPipelineUid& uid)
|
|
||||||
{
|
|
||||||
return m_mesh_cache.GetPipelineForUidAsync(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomShaderCache::AsyncCreatePipeline(const VideoCommon::GXPipelineUid& uid,
|
|
||||||
|
|
||||||
const CustomShaderInstance& custom_shaders,
|
|
||||||
const AbstractPipelineConfig& pipeline_config)
|
|
||||||
{
|
|
||||||
class PipelineWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PipelineWorkItem(CustomShaderCache* shader_cache, const VideoCommon::GXPipelineUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders, PipelineIterator iterator,
|
|
||||||
const AbstractPipelineConfig& pipeline_config)
|
|
||||||
: m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator), m_config(pipeline_config),
|
|
||||||
m_custom_shaders(custom_shaders)
|
|
||||||
{
|
|
||||||
SetStagesReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetStagesReady()
|
|
||||||
{
|
|
||||||
m_stages_ready = true;
|
|
||||||
|
|
||||||
PixelShaderUid ps_uid = m_uid.ps_uid;
|
|
||||||
ClearUnusedPixelShaderUidBits(m_shader_cache->m_api_type, m_shader_cache->m_host_config,
|
|
||||||
&ps_uid);
|
|
||||||
|
|
||||||
if (auto holder = m_shader_cache->m_ps_cache.GetHolder(ps_uid, m_custom_shaders))
|
|
||||||
{
|
|
||||||
// If the pixel shader is no longer pending compilation
|
|
||||||
// and the shader compilation succeeded, set
|
|
||||||
// the pipeline to use the new pixel shader.
|
|
||||||
// Otherwise, use the existing shader.
|
|
||||||
if (!holder->pending && holder->value.get())
|
|
||||||
{
|
|
||||||
m_config.pixel_shader = holder->value.get();
|
|
||||||
}
|
|
||||||
m_stages_ready &= !holder->pending;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_stages_ready &= false;
|
|
||||||
m_shader_cache->QueuePixelShaderCompile(ps_uid, m_custom_shaders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Compile() override
|
|
||||||
{
|
|
||||||
if (m_stages_ready)
|
|
||||||
{
|
|
||||||
m_pipeline = g_gfx->CreatePipeline(m_config);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Retrieve() override
|
|
||||||
{
|
|
||||||
if (m_stages_ready)
|
|
||||||
{
|
|
||||||
m_shader_cache->NotifyPipelineFinished(m_iterator, std::move(m_pipeline));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Re-queue for next frame.
|
|
||||||
auto wi = m_shader_cache->m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(
|
|
||||||
m_shader_cache, m_uid, m_custom_shaders, m_iterator, m_config);
|
|
||||||
m_shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
CustomShaderCache* m_shader_cache;
|
|
||||||
std::unique_ptr<AbstractPipeline> m_pipeline;
|
|
||||||
VideoCommon::GXPipelineUid m_uid;
|
|
||||||
PipelineIterator m_iterator;
|
|
||||||
AbstractPipelineConfig m_config;
|
|
||||||
CustomShaderInstance m_custom_shaders;
|
|
||||||
bool m_stages_ready;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto list_iter = m_pipeline_cache.InsertElement(uid, custom_shaders);
|
|
||||||
auto work_item = m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(
|
|
||||||
this, uid, custom_shaders, list_iter, pipeline_config);
|
|
||||||
m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomShaderCache::AsyncCreatePipeline(const VideoCommon::GXUberPipelineUid& uid,
|
|
||||||
|
|
||||||
const CustomShaderInstance& custom_shaders,
|
|
||||||
const AbstractPipelineConfig& pipeline_config)
|
|
||||||
{
|
|
||||||
class PipelineWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PipelineWorkItem(CustomShaderCache* shader_cache, const VideoCommon::GXUberPipelineUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders, UberPipelineIterator iterator,
|
|
||||||
const AbstractPipelineConfig& pipeline_config)
|
|
||||||
: m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator), m_config(pipeline_config),
|
|
||||||
m_custom_shaders(custom_shaders)
|
|
||||||
{
|
|
||||||
SetStagesReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetStagesReady()
|
|
||||||
{
|
|
||||||
m_stages_ready = true;
|
|
||||||
|
|
||||||
UberShader::PixelShaderUid ps_uid = m_uid.ps_uid;
|
|
||||||
ClearUnusedPixelShaderUidBits(m_shader_cache->m_api_type, m_shader_cache->m_host_config,
|
|
||||||
&ps_uid);
|
|
||||||
|
|
||||||
if (auto holder = m_shader_cache->m_uber_ps_cache.GetHolder(ps_uid, m_custom_shaders))
|
|
||||||
{
|
|
||||||
if (!holder->pending && holder->value.get())
|
|
||||||
{
|
|
||||||
m_config.pixel_shader = holder->value.get();
|
|
||||||
}
|
|
||||||
m_stages_ready &= !holder->pending;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_stages_ready &= false;
|
|
||||||
m_shader_cache->QueuePixelShaderCompile(ps_uid, m_custom_shaders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Compile() override
|
|
||||||
{
|
|
||||||
if (m_stages_ready)
|
|
||||||
{
|
|
||||||
if (m_config.pixel_shader == nullptr || m_config.vertex_shader == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_pipeline = g_gfx->CreatePipeline(m_config);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Retrieve() override
|
|
||||||
{
|
|
||||||
if (m_stages_ready)
|
|
||||||
{
|
|
||||||
m_shader_cache->NotifyPipelineFinished(m_iterator, std::move(m_pipeline));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Re-queue for next frame.
|
|
||||||
auto wi = m_shader_cache->m_async_uber_shader_compiler->CreateWorkItem<PipelineWorkItem>(
|
|
||||||
m_shader_cache, m_uid, m_custom_shaders, m_iterator, m_config);
|
|
||||||
m_shader_cache->m_async_uber_shader_compiler->QueueWorkItem(std::move(wi), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
CustomShaderCache* m_shader_cache;
|
|
||||||
std::unique_ptr<AbstractPipeline> m_pipeline;
|
|
||||||
VideoCommon::GXUberPipelineUid m_uid;
|
|
||||||
UberPipelineIterator m_iterator;
|
|
||||||
AbstractPipelineConfig m_config;
|
|
||||||
CustomShaderInstance m_custom_shaders;
|
|
||||||
bool m_stages_ready;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto list_iter = m_uber_pipeline_cache.InsertElement(uid, custom_shaders);
|
|
||||||
auto work_item = m_async_uber_shader_compiler->CreateWorkItem<PipelineWorkItem>(
|
|
||||||
this, uid, custom_shaders, list_iter, pipeline_config);
|
|
||||||
m_async_uber_shader_compiler->QueueWorkItem(std::move(work_item), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomShaderCache::NotifyPipelineFinished(PipelineIterator iterator,
|
|
||||||
std::unique_ptr<AbstractPipeline> pipeline)
|
|
||||||
{
|
|
||||||
iterator->second.pending = false;
|
|
||||||
iterator->second.value = std::move(pipeline);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomShaderCache::NotifyPipelineFinished(UberPipelineIterator iterator,
|
|
||||||
std::unique_ptr<AbstractPipeline> pipeline)
|
|
||||||
{
|
|
||||||
iterator->second.pending = false;
|
|
||||||
iterator->second.value = std::move(pipeline);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomShaderCache::QueuePixelShaderCompile(const PixelShaderUid& uid,
|
|
||||||
|
|
||||||
const CustomShaderInstance& custom_shaders)
|
|
||||||
{
|
|
||||||
class PixelShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PixelShaderWorkItem(CustomShaderCache* shader_cache, const PixelShaderUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders, PixelShaderIterator iter)
|
|
||||||
: m_shader_cache(shader_cache), m_uid(uid), m_custom_shaders(custom_shaders), m_iter(iter)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Compile() override
|
|
||||||
{
|
|
||||||
m_shader = m_shader_cache->CompilePixelShader(m_uid, m_custom_shaders);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Retrieve() override
|
|
||||||
{
|
|
||||||
m_shader_cache->NotifyPixelShaderFinished(m_iter, std::move(m_shader));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
CustomShaderCache* m_shader_cache;
|
|
||||||
std::unique_ptr<AbstractShader> m_shader;
|
|
||||||
PixelShaderUid m_uid;
|
|
||||||
CustomShaderInstance m_custom_shaders;
|
|
||||||
PixelShaderIterator m_iter;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto list_iter = m_ps_cache.InsertElement(uid, custom_shaders);
|
|
||||||
auto work_item = m_async_shader_compiler->CreateWorkItem<PixelShaderWorkItem>(
|
|
||||||
this, uid, custom_shaders, list_iter);
|
|
||||||
m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomShaderCache::QueuePixelShaderCompile(const UberShader::PixelShaderUid& uid,
|
|
||||||
|
|
||||||
const CustomShaderInstance& custom_shaders)
|
|
||||||
{
|
|
||||||
class PixelShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PixelShaderWorkItem(CustomShaderCache* shader_cache, const UberShader::PixelShaderUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders, UberPixelShaderIterator iter)
|
|
||||||
: m_shader_cache(shader_cache), m_uid(uid), m_custom_shaders(custom_shaders), m_iter(iter)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Compile() override
|
|
||||||
{
|
|
||||||
m_shader = m_shader_cache->CompilePixelShader(m_uid, m_custom_shaders);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Retrieve() override
|
|
||||||
{
|
|
||||||
m_shader_cache->NotifyPixelShaderFinished(m_iter, std::move(m_shader));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
CustomShaderCache* m_shader_cache;
|
|
||||||
std::unique_ptr<AbstractShader> m_shader;
|
|
||||||
UberShader::PixelShaderUid m_uid;
|
|
||||||
CustomShaderInstance m_custom_shaders;
|
|
||||||
UberPixelShaderIterator m_iter;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto list_iter = m_uber_ps_cache.InsertElement(uid, custom_shaders);
|
|
||||||
auto work_item = m_async_uber_shader_compiler->CreateWorkItem<PixelShaderWorkItem>(
|
|
||||||
this, uid, custom_shaders, list_iter);
|
|
||||||
m_async_uber_shader_compiler->QueueWorkItem(std::move(work_item), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<AbstractShader>
|
|
||||||
CustomShaderCache::CompilePixelShader(const PixelShaderUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders) const
|
|
||||||
{
|
|
||||||
const ShaderCode source_code =
|
|
||||||
GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData(), {});
|
|
||||||
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
|
|
||||||
"Custom Pixel Shader");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<AbstractShader>
|
|
||||||
CustomShaderCache::CompilePixelShader(const UberShader::PixelShaderUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders) const
|
|
||||||
{
|
|
||||||
const ShaderCode source_code = GenPixelShader(m_api_type, m_host_config, uid.GetUidData());
|
|
||||||
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
|
|
||||||
"Custom Uber Pixel Shader");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomShaderCache::NotifyPixelShaderFinished(PixelShaderIterator iterator,
|
|
||||||
std::unique_ptr<AbstractShader> shader)
|
|
||||||
{
|
|
||||||
iterator->second.pending = false;
|
|
||||||
iterator->second.value = std::move(shader);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomShaderCache::NotifyPixelShaderFinished(UberPixelShaderIterator iterator,
|
|
||||||
std::unique_ptr<AbstractShader> shader)
|
|
||||||
{
|
|
||||||
iterator->second.pending = false;
|
|
||||||
iterator->second.value = std::move(shader);
|
|
||||||
}
|
|
|
@ -1,152 +0,0 @@
|
||||||
// Copyright 2022 Dolphin Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <list>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "VideoCommon/AbstractPipeline.h"
|
|
||||||
#include "VideoCommon/AbstractShader.h"
|
|
||||||
#include "VideoCommon/AsyncShaderCompiler.h"
|
|
||||||
#include "VideoCommon/GXPipelineTypes.h"
|
|
||||||
#include "VideoCommon/PixelShaderGen.h"
|
|
||||||
#include "VideoCommon/ShaderCache.h"
|
|
||||||
#include "VideoCommon/ShaderGenCommon.h"
|
|
||||||
#include "VideoCommon/UberShaderPixel.h"
|
|
||||||
#include "VideoCommon/VideoEvents.h"
|
|
||||||
|
|
||||||
struct CustomShaderInstance
|
|
||||||
{
|
|
||||||
CustomPixelShaderContents pixel_contents;
|
|
||||||
|
|
||||||
bool operator==(const CustomShaderInstance& other) const = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CustomShaderCache
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CustomShaderCache();
|
|
||||||
~CustomShaderCache();
|
|
||||||
CustomShaderCache(const CustomShaderCache&) = delete;
|
|
||||||
CustomShaderCache(CustomShaderCache&&) = delete;
|
|
||||||
CustomShaderCache& operator=(const CustomShaderCache&) = delete;
|
|
||||||
CustomShaderCache& operator=(CustomShaderCache&&) = delete;
|
|
||||||
|
|
||||||
// Changes the shader host config. Shaders should be reloaded afterwards.
|
|
||||||
void SetHostConfig(const ShaderHostConfig& host_config) { m_host_config.bits = host_config.bits; }
|
|
||||||
|
|
||||||
// Retrieves all pending shaders/pipelines from the async compiler.
|
|
||||||
void RetrieveAsyncShaders();
|
|
||||||
|
|
||||||
// Reloads/recreates all shaders and pipelines.
|
|
||||||
void Reload();
|
|
||||||
|
|
||||||
// The optional will be empty if this pipeline is now background compiling.
|
|
||||||
std::optional<const AbstractPipeline*>
|
|
||||||
GetPipelineAsync(const VideoCommon::GXPipelineUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders,
|
|
||||||
const AbstractPipelineConfig& pipeline_config);
|
|
||||||
std::optional<const AbstractPipeline*>
|
|
||||||
GetPipelineAsync(const VideoCommon::GXUberPipelineUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders,
|
|
||||||
const AbstractPipelineConfig& pipeline_config);
|
|
||||||
|
|
||||||
const AbstractPipeline* GetPipelineForUid(const VideoCommon::GXPipelineUid& uid);
|
|
||||||
const AbstractPipeline* GetUberPipelineForUid(const VideoCommon::GXUberPipelineUid& uid);
|
|
||||||
std::optional<const AbstractPipeline*>
|
|
||||||
GetPipelineForUidAsync(const VideoCommon::GXPipelineUid& uid);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Configuration bits.
|
|
||||||
APIType m_api_type = APIType::Nothing;
|
|
||||||
ShaderHostConfig m_host_config = {};
|
|
||||||
std::unique_ptr<VideoCommon::AsyncShaderCompiler> m_async_shader_compiler;
|
|
||||||
std::unique_ptr<VideoCommon::AsyncShaderCompiler> m_async_uber_shader_compiler;
|
|
||||||
|
|
||||||
void AsyncCreatePipeline(const VideoCommon::GXPipelineUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders,
|
|
||||||
const AbstractPipelineConfig& pipeline_config);
|
|
||||||
void AsyncCreatePipeline(const VideoCommon::GXUberPipelineUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders,
|
|
||||||
const AbstractPipelineConfig& pipeline_config);
|
|
||||||
|
|
||||||
// Shader/Pipeline cache helper
|
|
||||||
template <typename Uid, typename ValueType>
|
|
||||||
struct Cache
|
|
||||||
{
|
|
||||||
struct CacheHolder
|
|
||||||
{
|
|
||||||
std::unique_ptr<ValueType> value = nullptr;
|
|
||||||
bool pending = true;
|
|
||||||
};
|
|
||||||
using CacheElement = std::pair<CustomShaderInstance, CacheHolder>;
|
|
||||||
using CacheList = std::list<CacheElement>;
|
|
||||||
std::map<Uid, CacheList> uid_to_cachelist;
|
|
||||||
|
|
||||||
const CacheHolder* GetHolder(const Uid& uid, const CustomShaderInstance& custom_shaders) const
|
|
||||||
{
|
|
||||||
if (auto uuid_it = uid_to_cachelist.find(uid); uuid_it != uid_to_cachelist.end())
|
|
||||||
{
|
|
||||||
for (const auto& [custom_shader_val, holder] : uuid_it->second)
|
|
||||||
{
|
|
||||||
if (custom_shaders == custom_shader_val)
|
|
||||||
{
|
|
||||||
return &holder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
typename CacheList::iterator InsertElement(const Uid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders)
|
|
||||||
{
|
|
||||||
CacheList& cachelist = uid_to_cachelist[uid];
|
|
||||||
CacheElement e{custom_shaders, CacheHolder{}};
|
|
||||||
return cachelist.emplace(cachelist.begin(), std::move(e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Cache<PixelShaderUid, AbstractShader> m_ps_cache;
|
|
||||||
Cache<UberShader::PixelShaderUid, AbstractShader> m_uber_ps_cache;
|
|
||||||
Cache<VideoCommon::GXPipelineUid, AbstractPipeline> m_pipeline_cache;
|
|
||||||
Cache<VideoCommon::GXUberPipelineUid, AbstractPipeline> m_uber_pipeline_cache;
|
|
||||||
|
|
||||||
using PipelineIterator = Cache<VideoCommon::GXPipelineUid, AbstractPipeline>::CacheList::iterator;
|
|
||||||
using UberPipelineIterator =
|
|
||||||
Cache<VideoCommon::GXUberPipelineUid, AbstractPipeline>::CacheList::iterator;
|
|
||||||
using PixelShaderIterator = Cache<PixelShaderUid, AbstractShader>::CacheList::iterator;
|
|
||||||
using UberPixelShaderIterator =
|
|
||||||
Cache<UberShader::PixelShaderUid, AbstractShader>::CacheList::iterator;
|
|
||||||
|
|
||||||
void NotifyPipelineFinished(PipelineIterator iterator,
|
|
||||||
std::unique_ptr<AbstractPipeline> pipeline);
|
|
||||||
void NotifyPipelineFinished(UberPipelineIterator iterator,
|
|
||||||
std::unique_ptr<AbstractPipeline> pipeline);
|
|
||||||
|
|
||||||
std::unique_ptr<AbstractShader>
|
|
||||||
CompilePixelShader(const PixelShaderUid& uid, const CustomShaderInstance& custom_shaders) const;
|
|
||||||
void NotifyPixelShaderFinished(PixelShaderIterator iterator,
|
|
||||||
std::unique_ptr<AbstractShader> shader);
|
|
||||||
std::unique_ptr<AbstractShader>
|
|
||||||
CompilePixelShader(const UberShader::PixelShaderUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders) const;
|
|
||||||
void NotifyPixelShaderFinished(UberPixelShaderIterator iterator,
|
|
||||||
std::unique_ptr<AbstractShader> shader);
|
|
||||||
|
|
||||||
void QueuePixelShaderCompile(const PixelShaderUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders);
|
|
||||||
void QueuePixelShaderCompile(const UberShader::PixelShaderUid& uid,
|
|
||||||
const CustomShaderInstance& custom_shaders);
|
|
||||||
|
|
||||||
Common::EventHook m_frame_end_handler;
|
|
||||||
|
|
||||||
VideoCommon::ShaderCache m_mesh_cache;
|
|
||||||
};
|
|
255
Source/Core/VideoCommon/Resources/CustomResourceManager.cpp
Normal file
255
Source/Core/VideoCommon/Resources/CustomResourceManager.cpp
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/CustomResourceManager.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/ShaderCacheUtils.h"
|
||||||
|
#include "VideoCommon/VideoEvents.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
void CustomResourceManager::Initialize()
|
||||||
|
{
|
||||||
|
m_asset_cache.Initialize();
|
||||||
|
m_worker_thread.Reset("resource-worker");
|
||||||
|
m_host_config.bits = ShaderHostConfig::GetCurrent().bits;
|
||||||
|
|
||||||
|
m_xfb_event =
|
||||||
|
AfterFrameEvent::Register([this](Core::System&) { XFBTriggered(); }, "CustomResourceManager");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomResourceManager::Shutdown()
|
||||||
|
{
|
||||||
|
m_asset_cache.Shutdown();
|
||||||
|
m_worker_thread.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomResourceManager::Reset()
|
||||||
|
{
|
||||||
|
m_material_resources.clear();
|
||||||
|
m_mesh_resources.clear();
|
||||||
|
m_render_target_resources.clear();
|
||||||
|
m_shader_resources.clear();
|
||||||
|
m_texture_data_resources.clear();
|
||||||
|
m_texture_sampler_resources.clear();
|
||||||
|
|
||||||
|
m_asset_cache.Reset();
|
||||||
|
m_texture_pool.Reset();
|
||||||
|
m_worker_thread.Reset("resource-worker");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomResourceManager::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id)
|
||||||
|
{
|
||||||
|
m_asset_cache.MarkAssetDirty(asset_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomResourceManager::XFBTriggered()
|
||||||
|
{
|
||||||
|
m_asset_cache.Update();
|
||||||
|
|
||||||
|
/*for (auto& [id, texture_resource] : m_texture_sampler_resources)
|
||||||
|
{
|
||||||
|
// Hack to get access to resource internals
|
||||||
|
Resource* resource = &texture_resource;
|
||||||
|
|
||||||
|
// Unload our GPU data and
|
||||||
|
// tell texture and references to trigger a reload
|
||||||
|
// on next usage
|
||||||
|
resource->NotifyAssetUnloaded();
|
||||||
|
resource->NotifyAssetChanged(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [id, shader_resource] : m_shader_resources)
|
||||||
|
{
|
||||||
|
// Hack to get access to resource internals
|
||||||
|
Resource* resource = &shader_resource;
|
||||||
|
|
||||||
|
// Unload our GPU data and
|
||||||
|
// tell shader and references to trigger a reload
|
||||||
|
// on next usage
|
||||||
|
resource->NotifyAssetUnloaded();
|
||||||
|
resource->NotifyAssetChanged(false);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomResourceManager::SetHostConfig(const ShaderHostConfig& host_config)
|
||||||
|
{
|
||||||
|
for (auto& [id, shader_resources] : m_shader_resources)
|
||||||
|
{
|
||||||
|
for (auto& [key, shader_resource] : shader_resources)
|
||||||
|
{
|
||||||
|
shader_resource->SetHostConfig(host_config);
|
||||||
|
|
||||||
|
// Hack to get access to resource internals
|
||||||
|
Resource* resource = shader_resource.get();
|
||||||
|
|
||||||
|
// Tell shader and references to trigger a reload
|
||||||
|
// on next usage
|
||||||
|
resource->NotifyAssetChanged(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_host_config.bits = host_config.bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>(asset_id, library, &m_asset_cache, this,
|
||||||
|
&m_texture_pool, &m_worker_thread);
|
||||||
|
}
|
||||||
|
ProcessResource(it->second.get());
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialResource* CustomResourceManager::GetMaterialFromAsset(
|
||||||
|
const CustomAssetLibrary::AssetID& asset_id, const GXPipelineUid& pipeline_uid,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||||
|
{
|
||||||
|
const auto [it_container, added_container] =
|
||||||
|
m_material_resources.try_emplace(asset_id, PipelineIdToMaterial{});
|
||||||
|
const auto [it_uid, added_uid] =
|
||||||
|
it_container->second.try_emplace(PipelineToHash(pipeline_uid), nullptr);
|
||||||
|
if (added_uid)
|
||||||
|
{
|
||||||
|
it_uid->second = std::make_unique<MaterialResource>(
|
||||||
|
asset_id, library, &m_asset_cache, this, &m_texture_pool, &m_worker_thread, pipeline_uid);
|
||||||
|
}
|
||||||
|
ProcessResource(it_uid->second.get());
|
||||||
|
return it_uid->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderResource*
|
||||||
|
CustomResourceManager::GetShaderFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
std::size_t shader_key, const GXPipelineUid& pipeline_uid,
|
||||||
|
const std::string& preprocessor_settings,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||||
|
{
|
||||||
|
const auto [it_container, added_container] =
|
||||||
|
m_shader_resources.try_emplace(asset_id, ShaderKeyToShader{});
|
||||||
|
const auto [it_key, added_key] = it_container->second.try_emplace(shader_key, nullptr);
|
||||||
|
if (added_key)
|
||||||
|
{
|
||||||
|
it_key->second =
|
||||||
|
std::make_unique<ShaderResource>(asset_id, library, &m_asset_cache, this, &m_texture_pool,
|
||||||
|
&m_worker_thread, pipeline_uid, preprocessor_settings);
|
||||||
|
it_key->second->SetHostConfig(m_host_config);
|
||||||
|
}
|
||||||
|
ProcessResource(it_key->second.get());
|
||||||
|
return it_key->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAndSamplerResource* CustomResourceManager::GetTextureAndSamplerFromAsset(
|
||||||
|
const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||||
|
{
|
||||||
|
const auto [it, added] = m_texture_sampler_resources.try_emplace(asset_id, nullptr);
|
||||||
|
if (added)
|
||||||
|
{
|
||||||
|
it->second = std::make_unique<TextureAndSamplerResource>(
|
||||||
|
asset_id, library, &m_asset_cache, this, &m_texture_pool, &m_worker_thread);
|
||||||
|
}
|
||||||
|
ProcessResource(it->second.get());
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTargetResource* CustomResourceManager::GetRenderTargetFromAsset(
|
||||||
|
const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||||
|
{
|
||||||
|
const auto [it, added] = m_render_target_resources.try_emplace(asset_id, nullptr);
|
||||||
|
if (added)
|
||||||
|
{
|
||||||
|
it->second = std::make_unique<RenderTargetResource>(asset_id, library, &m_asset_cache, this,
|
||||||
|
&m_texture_pool, &m_worker_thread);
|
||||||
|
}
|
||||||
|
ProcessResource(it->second.get());
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshResource*
|
||||||
|
CustomResourceManager::GetMeshFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
const GXPipelineUid& pipeline_uid,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||||
|
{
|
||||||
|
const auto [it_container, added_container] =
|
||||||
|
m_mesh_resources.try_emplace(asset_id, PipelineIdToMesh{});
|
||||||
|
const auto [it_uid, added_uid] =
|
||||||
|
it_container->second.try_emplace(PipelineToHash(pipeline_uid), nullptr);
|
||||||
|
if (added_uid)
|
||||||
|
{
|
||||||
|
it_uid->second = std::make_unique<MeshResource>(
|
||||||
|
asset_id, library, &m_asset_cache, this, &m_texture_pool, &m_worker_thread, pipeline_uid);
|
||||||
|
}
|
||||||
|
ProcessResource(it_uid->second.get());
|
||||||
|
return it_uid->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
88
Source/Core/VideoCommon/Resources/CustomResourceManager.h
Normal file
88
Source/Core/VideoCommon/Resources/CustomResourceManager.h
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// 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/GraphicsModSystem/Types.h"
|
||||||
|
#include "VideoCommon/Resources/MaterialResource.h"
|
||||||
|
#include "VideoCommon/Resources/MeshResource.h"
|
||||||
|
#include "VideoCommon/Resources/RenderTargetResource.h"
|
||||||
|
#include "VideoCommon/Resources/ShaderResource.h"
|
||||||
|
#include "VideoCommon/Resources/TextureAndSamplerResource.h"
|
||||||
|
#include "VideoCommon/Resources/TextureDataResource.h"
|
||||||
|
#include "VideoCommon/Resources/TexturePool.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();
|
||||||
|
void SetHostConfig(const ShaderHostConfig& host_config);
|
||||||
|
|
||||||
|
TextureDataResource*
|
||||||
|
GetTextureDataFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||||
|
MaterialResource* GetMaterialFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
const GXPipelineUid& pipeline_uid,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||||
|
|
||||||
|
ShaderResource* GetShaderFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
std::size_t shader_key, const GXPipelineUid& pipeline_uid,
|
||||||
|
const std::string& preprocessor_settings,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||||
|
RenderTargetResource*
|
||||||
|
GetRenderTargetFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||||
|
TextureAndSamplerResource*
|
||||||
|
GetTextureAndSamplerFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||||
|
|
||||||
|
MeshResource* GetMeshFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||||
|
const GXPipelineUid& pipeline_uid,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ProcessResource(Resource* resource);
|
||||||
|
void ProcessResourceState(Resource* resource);
|
||||||
|
CustomAssetCache m_asset_cache;
|
||||||
|
TexturePool m_texture_pool;
|
||||||
|
Common::AsyncWorkThreadSP m_worker_thread;
|
||||||
|
|
||||||
|
using PipelineIdToMaterial = std::map<std::size_t, std::unique_ptr<MaterialResource>>;
|
||||||
|
std::map<CustomAssetLibrary::AssetID, PipelineIdToMaterial> m_material_resources;
|
||||||
|
|
||||||
|
using PipelineIdToMesh = std::map<std::size_t, std::unique_ptr<MeshResource>>;
|
||||||
|
std::map<CustomAssetLibrary::AssetID, PipelineIdToMesh> m_mesh_resources;
|
||||||
|
|
||||||
|
std::map<CustomAssetLibrary::AssetID, std::unique_ptr<RenderTargetResource>>
|
||||||
|
m_render_target_resources;
|
||||||
|
|
||||||
|
using ShaderKeyToShader = std::map<std::size_t, std::unique_ptr<ShaderResource>>;
|
||||||
|
std::map<CustomAssetLibrary::AssetID, ShaderKeyToShader> m_shader_resources;
|
||||||
|
|
||||||
|
std::map<CustomAssetLibrary::AssetID, std::unique_ptr<TextureDataResource>>
|
||||||
|
m_texture_data_resources;
|
||||||
|
|
||||||
|
std::map<CustomAssetLibrary::AssetID, std::unique_ptr<TextureAndSamplerResource>>
|
||||||
|
m_texture_sampler_resources;
|
||||||
|
|
||||||
|
ShaderHostConfig m_host_config;
|
||||||
|
|
||||||
|
Common::EventHook m_xfb_event;
|
||||||
|
};
|
||||||
|
} // namespace VideoCommon
|
378
Source/Core/VideoCommon/Resources/MaterialResource.cpp
Normal file
378
Source/Core/VideoCommon/Resources/MaterialResource.cpp
Normal file
|
@ -0,0 +1,378 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/MaterialResource.h"
|
||||||
|
|
||||||
|
#include <xxh3.h>
|
||||||
|
|
||||||
|
#include "Common/VariantUtil.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/AbstractGfx.h"
|
||||||
|
#include "VideoCommon/Assets/CustomAssetCache.h"
|
||||||
|
#include "VideoCommon/FramebufferManager.h"
|
||||||
|
#include "VideoCommon/Resources/CustomResourceManager.h"
|
||||||
|
#include "VideoCommon/ShaderCacheUtils.h"
|
||||||
|
#include "VideoCommon/VideoConfig.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// TODO: absorb this with TextureCacheBase
|
||||||
|
bool IsAnisostropicEnhancementSafe(const SamplerState::TM0& tm0)
|
||||||
|
{
|
||||||
|
return !(tm0.min_filter == FilterMode::Near && tm0.mag_filter == FilterMode::Near);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: absorb this with TextureCacheBase
|
||||||
|
SamplerState CalculateSamplerAnsiotropy(const SamplerState& initial_sampler)
|
||||||
|
{
|
||||||
|
SamplerState state = initial_sampler;
|
||||||
|
if (g_ActiveConfig.iMaxAnisotropy != AnisotropicFilteringMode::Default &&
|
||||||
|
IsAnisostropicEnhancementSafe(state.tm0))
|
||||||
|
{
|
||||||
|
state.tm0.anisotropic_filtering = Common::ToUnderlying(g_ActiveConfig.iMaxAnisotropy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.tm0.anisotropic_filtering != 0)
|
||||||
|
{
|
||||||
|
// https://www.opengl.org/registry/specs/EXT/texture_filter_anisotropic.txt
|
||||||
|
// For predictable results on all hardware/drivers, only use one of:
|
||||||
|
// GL_LINEAR + GL_LINEAR (No Mipmaps [Bilinear])
|
||||||
|
// GL_LINEAR + GL_LINEAR_MIPMAP_LINEAR (w/ Mipmaps [Trilinear])
|
||||||
|
// Letting the game set other combinations will have varying arbitrary results;
|
||||||
|
// possibly being interpreted as equal to bilinear/trilinear, implicitly
|
||||||
|
// disabling anisotropy, or changing the anisotropic algorithm employed.
|
||||||
|
state.tm0.min_filter = FilterMode::Linear;
|
||||||
|
state.tm0.mag_filter = FilterMode::Linear;
|
||||||
|
state.tm0.mipmap_filter = FilterMode::Linear;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
MaterialResource::MaterialResource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library,
|
||||||
|
CustomAssetCache* asset_cache,
|
||||||
|
CustomResourceManager* resource_manager,
|
||||||
|
TexturePool* texture_pool,
|
||||||
|
Common::AsyncWorkThreadSP* worker_queue,
|
||||||
|
const GXPipelineUid& pipeline_uid)
|
||||||
|
: Resource(std::move(primary_asset_id), std::move(asset_library), asset_cache, resource_manager,
|
||||||
|
texture_pool, worker_queue),
|
||||||
|
m_uid(pipeline_uid)
|
||||||
|
{
|
||||||
|
m_material_asset =
|
||||||
|
m_asset_cache->CreateAsset<RasterMaterialAsset>(m_primary_asset_id, m_asset_library, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaterialResource::ResetData()
|
||||||
|
{
|
||||||
|
if (m_current_data)
|
||||||
|
{
|
||||||
|
m_current_data->m_shader_resource->RemoveReference(this);
|
||||||
|
for (const auto& texture_resource : m_current_data->m_texture_like_resources)
|
||||||
|
{
|
||||||
|
std::visit(
|
||||||
|
overloaded{
|
||||||
|
[this](TextureAndSamplerResource* resource) { resource->RemoveReference(this); },
|
||||||
|
[this](RenderTargetResource* resource) { resource->RemoveReference(this); }},
|
||||||
|
texture_resource);
|
||||||
|
}
|
||||||
|
if (m_current_data->m_next_material)
|
||||||
|
m_current_data->m_next_material->RemoveReference(this);
|
||||||
|
}
|
||||||
|
m_load_data = std::make_shared<Data>();
|
||||||
|
m_processing_load_data = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete MaterialResource::CollectPrimaryData()
|
||||||
|
{
|
||||||
|
const auto material_data = m_material_asset->GetData();
|
||||||
|
if (!material_data) [[unlikely]]
|
||||||
|
{
|
||||||
|
return Resource::TaskComplete::No;
|
||||||
|
}
|
||||||
|
m_load_data->m_material_data = material_data;
|
||||||
|
|
||||||
|
// A shader asset is required to function
|
||||||
|
if (m_load_data->m_material_data->shader_asset == "")
|
||||||
|
{
|
||||||
|
return Resource::TaskComplete::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateTextureData(m_load_data.get());
|
||||||
|
SetShaderKey(m_load_data.get(), &m_uid);
|
||||||
|
|
||||||
|
return Resource::TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete MaterialResource::CollectDependencyData()
|
||||||
|
{
|
||||||
|
bool loaded = true;
|
||||||
|
{
|
||||||
|
auto* const shader_resource = m_resource_manager->GetShaderFromAsset(
|
||||||
|
m_load_data->m_material_data->shader_asset, m_load_data->m_shader_key, m_uid,
|
||||||
|
m_load_data->m_preprocessor_settings, m_asset_library);
|
||||||
|
shader_resource->AddReference(this);
|
||||||
|
m_load_data->m_shader_resource = shader_resource;
|
||||||
|
const auto data_processed = shader_resource->IsDataProcessed();
|
||||||
|
if (data_processed == TaskComplete::Error)
|
||||||
|
return TaskComplete::Error;
|
||||||
|
|
||||||
|
loaded &= data_processed == TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < m_load_data->m_material_data->pixel_textures.size(); i++)
|
||||||
|
{
|
||||||
|
const auto& texture_and_sampler = m_load_data->m_material_data->pixel_textures[i];
|
||||||
|
if (texture_and_sampler.asset == "")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (texture_and_sampler.is_render_target)
|
||||||
|
{
|
||||||
|
const auto render_target =
|
||||||
|
m_resource_manager->GetRenderTargetFromAsset(texture_and_sampler.asset, m_asset_library);
|
||||||
|
m_load_data->m_texture_like_resources[i] = render_target;
|
||||||
|
m_load_data->m_texture_like_data[i] = render_target->GetData();
|
||||||
|
render_target->AddReference(this);
|
||||||
|
|
||||||
|
const auto data_processed = render_target->IsDataProcessed();
|
||||||
|
if (data_processed == TaskComplete::Error)
|
||||||
|
return TaskComplete::Error;
|
||||||
|
|
||||||
|
loaded &= data_processed == TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto texture = m_resource_manager->GetTextureAndSamplerFromAsset(
|
||||||
|
texture_and_sampler.asset, m_asset_library);
|
||||||
|
m_load_data->m_texture_like_resources[i] = texture;
|
||||||
|
m_load_data->m_texture_like_data[i] = texture->GetData();
|
||||||
|
texture->AddReference(this);
|
||||||
|
|
||||||
|
const auto data_processed = texture->IsDataProcessed();
|
||||||
|
if (data_processed == TaskComplete::Error)
|
||||||
|
return TaskComplete::Error;
|
||||||
|
|
||||||
|
loaded &= data_processed == TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_load_data->m_material_data->next_material_asset != "")
|
||||||
|
{
|
||||||
|
m_load_data->m_next_material = m_resource_manager->GetMaterialFromAsset(
|
||||||
|
m_load_data->m_material_data->next_material_asset, m_uid, m_asset_library);
|
||||||
|
m_load_data->m_next_material->AddReference(this);
|
||||||
|
const auto data_processed = m_load_data->m_next_material->IsDataProcessed();
|
||||||
|
if (data_processed == TaskComplete::Error)
|
||||||
|
return TaskComplete::Error;
|
||||||
|
|
||||||
|
loaded &= data_processed == TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaded ? TaskComplete::Yes : TaskComplete::No;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete MaterialResource::ProcessData()
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < m_load_data->m_texture_like_data.size(); i++)
|
||||||
|
{
|
||||||
|
auto& texture_like_data = m_load_data->m_texture_like_data[i];
|
||||||
|
auto& texture_like_reference = m_load_data->m_texture_like_references[i];
|
||||||
|
|
||||||
|
if (!texture_like_reference.texture)
|
||||||
|
{
|
||||||
|
std::visit(overloaded{[&](const std::shared_ptr<TextureAndSamplerResource::Data>& data) {
|
||||||
|
texture_like_reference.texture = data->GetTexture();
|
||||||
|
texture_like_reference.sampler =
|
||||||
|
CalculateSamplerAnsiotropy(data->GetSampler());
|
||||||
|
;
|
||||||
|
},
|
||||||
|
[&](const std::shared_ptr<RenderTargetResource::Data>& data) {
|
||||||
|
texture_like_reference.texture = data->GetTexture();
|
||||||
|
texture_like_reference.sampler =
|
||||||
|
CalculateSamplerAnsiotropy(data->GetSampler());
|
||||||
|
}},
|
||||||
|
texture_like_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_processing_load_data)
|
||||||
|
{
|
||||||
|
auto work =
|
||||||
|
[load_data = m_load_data, shader_resource_data = m_load_data->m_shader_resource->GetData(),
|
||||||
|
efb_frame_buffer_state = g_framebuffer_manager->GetEFBFramebufferState(), uid = &m_uid]() {
|
||||||
|
// Sanity check
|
||||||
|
if (!shader_resource_data->IsCompiled())
|
||||||
|
{
|
||||||
|
load_data->m_processing_finished = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractPipelineConfig config;
|
||||||
|
config.vertex_shader = shader_resource_data->GetVertexShader();
|
||||||
|
config.pixel_shader = shader_resource_data->GetPixelShader();
|
||||||
|
config.geometry_shader = shader_resource_data->GetGeometryShader();
|
||||||
|
|
||||||
|
const auto actual_uid = ApplyDriverBugs(*uid);
|
||||||
|
|
||||||
|
if (load_data->m_material_data->blending_state)
|
||||||
|
config.blending_state = *load_data->m_material_data->blending_state;
|
||||||
|
else
|
||||||
|
config.blending_state = actual_uid.blending_state;
|
||||||
|
|
||||||
|
if (load_data->m_material_data->depth_state)
|
||||||
|
config.depth_state = *load_data->m_material_data->depth_state;
|
||||||
|
else
|
||||||
|
config.depth_state = actual_uid.depth_state;
|
||||||
|
|
||||||
|
config.framebuffer_state = std::move(efb_frame_buffer_state);
|
||||||
|
config.framebuffer_state.additional_color_attachment_count = 0;
|
||||||
|
|
||||||
|
config.rasterization_state = actual_uid.rasterization_state;
|
||||||
|
if (load_data->m_material_data->cull_mode)
|
||||||
|
config.rasterization_state.cullmode = *load_data->m_material_data->cull_mode;
|
||||||
|
|
||||||
|
config.vertex_format = actual_uid.vertex_format;
|
||||||
|
config.usage = AbstractPipelineUsage::GX;
|
||||||
|
|
||||||
|
load_data->m_pipeline = g_gfx->CreatePipeline(config);
|
||||||
|
|
||||||
|
if (load_data->m_pipeline)
|
||||||
|
{
|
||||||
|
WriteUniforms(load_data.get());
|
||||||
|
}
|
||||||
|
load_data->m_processing_finished = true;
|
||||||
|
};
|
||||||
|
m_worker_queue->Push(std::move(work));
|
||||||
|
m_processing_load_data = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_load_data->m_processing_finished)
|
||||||
|
return TaskComplete::No;
|
||||||
|
|
||||||
|
if (!m_load_data->m_pipeline)
|
||||||
|
{
|
||||||
|
return TaskComplete::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::swap(m_current_data, m_load_data);
|
||||||
|
return TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaterialResource::MarkAsActive()
|
||||||
|
{
|
||||||
|
if (!m_current_data) [[unlikely]]
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_asset_cache->MarkAssetActive(m_material_asset);
|
||||||
|
for (const auto& texture_resource : m_current_data->m_texture_like_resources)
|
||||||
|
{
|
||||||
|
std::visit(overloaded{[](TextureAndSamplerResource* resource) { resource->MarkAsActive(); },
|
||||||
|
[](RenderTargetResource* resource) { resource->MarkAsActive(); }},
|
||||||
|
texture_resource);
|
||||||
|
}
|
||||||
|
if (m_current_data->m_shader_resource)
|
||||||
|
m_current_data->m_shader_resource->MarkAsActive();
|
||||||
|
if (m_current_data->m_next_material)
|
||||||
|
m_current_data->m_next_material->MarkAsActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaterialResource::MarkAsPending()
|
||||||
|
{
|
||||||
|
m_asset_cache->MarkAssetPending(m_material_asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaterialResource::CreateTextureData(Data* data)
|
||||||
|
{
|
||||||
|
ShaderCode preprocessor_settings;
|
||||||
|
|
||||||
|
const auto& material_data = *data->m_material_data;
|
||||||
|
data->m_texture_like_data.clear();
|
||||||
|
data->m_texture_like_resources.clear();
|
||||||
|
data->m_texture_like_references.clear();
|
||||||
|
const u32 custom_sampler_index_offset = 8;
|
||||||
|
for (u32 i = 0; i < static_cast<u32>(material_data.pixel_textures.size()); i++)
|
||||||
|
{
|
||||||
|
const auto& texture_and_sampler = material_data.pixel_textures[i];
|
||||||
|
data->m_texture_like_references.push_back(TextureLikeReference{});
|
||||||
|
if (texture_and_sampler.is_render_target)
|
||||||
|
{
|
||||||
|
RenderTargetResource* value = nullptr;
|
||||||
|
data->m_texture_like_resources.push_back(value);
|
||||||
|
data->m_texture_like_data.push_back(std::shared_ptr<RenderTargetResource::Data>{});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TextureAndSamplerResource* value = nullptr;
|
||||||
|
data->m_texture_like_resources.push_back(value);
|
||||||
|
data->m_texture_like_data.push_back(std::shared_ptr<TextureAndSamplerResource::Data>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texture_and_sampler.asset == "")
|
||||||
|
{
|
||||||
|
preprocessor_settings.Write("#define HAS_SAMPLER_{} 0\n", i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto& texture_like_reference = data->m_texture_like_references[i];
|
||||||
|
|
||||||
|
texture_like_reference.sampler_origin = texture_and_sampler.sampler_origin;
|
||||||
|
texture_like_reference.sampler_index = i + custom_sampler_index_offset;
|
||||||
|
texture_like_reference.texture_hash = texture_and_sampler.texture_hash;
|
||||||
|
texture_like_reference.texture = nullptr;
|
||||||
|
|
||||||
|
preprocessor_settings.Write("#define HAS_SAMPLER_{} 1\n", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data->m_preprocessor_settings = preprocessor_settings.GetBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaterialResource::SetShaderKey(Data* data, GXPipelineUid* uid)
|
||||||
|
{
|
||||||
|
XXH3_state_t shader_key_hash;
|
||||||
|
XXH3_INITSTATE(&shader_key_hash);
|
||||||
|
XXH3_64bits_reset_withSeed(&shader_key_hash, static_cast<XXH64_hash_t>(1));
|
||||||
|
|
||||||
|
UpdateHashWithPipeline(*uid, &shader_key_hash);
|
||||||
|
XXH3_64bits_update(&shader_key_hash, data->m_preprocessor_settings.c_str(),
|
||||||
|
data->m_preprocessor_settings.size());
|
||||||
|
|
||||||
|
data->m_shader_key = XXH3_64bits_digest(&shader_key_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaterialResource::WriteUniforms(Data* data)
|
||||||
|
{
|
||||||
|
// Calculate the size in memory of the buffer
|
||||||
|
std::size_t max_pixeldata_size = 0;
|
||||||
|
for (const auto& property : data->m_material_data->pixel_properties)
|
||||||
|
{
|
||||||
|
max_pixeldata_size += VideoCommon::MaterialProperty2::GetMemorySize(property);
|
||||||
|
}
|
||||||
|
data->m_pixel_uniform_data.resize(max_pixeldata_size);
|
||||||
|
|
||||||
|
// Now write the memory
|
||||||
|
u8* pixel_data = data->m_pixel_uniform_data.data();
|
||||||
|
for (const auto& property : data->m_material_data->pixel_properties)
|
||||||
|
{
|
||||||
|
VideoCommon::MaterialProperty2::WriteToMemory(pixel_data, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the size in memory of the buffer
|
||||||
|
std::size_t max_vertexdata_size = 0;
|
||||||
|
for (const auto& property : data->m_material_data->vertex_properties)
|
||||||
|
{
|
||||||
|
max_vertexdata_size += VideoCommon::MaterialProperty2::GetMemorySize(property);
|
||||||
|
}
|
||||||
|
data->m_vertex_uniform_data.resize(max_vertexdata_size);
|
||||||
|
|
||||||
|
// Now write the memory
|
||||||
|
u8* vertex_data = data->m_vertex_uniform_data.data();
|
||||||
|
for (const auto& property : data->m_material_data->vertex_properties)
|
||||||
|
{
|
||||||
|
VideoCommon::MaterialProperty2::WriteToMemory(vertex_data, property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace VideoCommon
|
97
Source/Core/VideoCommon/Resources/MaterialResource.h
Normal file
97
Source/Core/VideoCommon/Resources/MaterialResource.h
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Common/SmallVector.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/AbstractPipeline.h"
|
||||||
|
#include "VideoCommon/Assets/MaterialAsset.h"
|
||||||
|
#include "VideoCommon/Assets/TextureSamplerValue.h"
|
||||||
|
#include "VideoCommon/Constants.h"
|
||||||
|
#include "VideoCommon/GXPipelineTypes.h"
|
||||||
|
#include "VideoCommon/RenderState.h"
|
||||||
|
#include "VideoCommon/Resources/RenderTargetResource.h"
|
||||||
|
#include "VideoCommon/Resources/Resource.h"
|
||||||
|
#include "VideoCommon/Resources/ShaderResource.h"
|
||||||
|
#include "VideoCommon/Resources/TextureAndSamplerResource.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
class MaterialResource final : public Resource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MaterialResource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library, CustomAssetCache* asset_cache,
|
||||||
|
CustomResourceManager* resource_manager, TexturePool* texture_pool,
|
||||||
|
Common::AsyncWorkThreadSP* worker_queue, const GXPipelineUid& pipeline_uid);
|
||||||
|
|
||||||
|
struct TextureLikeReference
|
||||||
|
{
|
||||||
|
SamplerState sampler;
|
||||||
|
u32 sampler_index;
|
||||||
|
TextureSamplerValue::SamplerOrigin sampler_origin;
|
||||||
|
std::string_view texture_hash;
|
||||||
|
AbstractTexture* texture;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AbstractPipeline* GetPipeline() const { return m_pipeline.get(); }
|
||||||
|
std::span<const u8> GetVertexUniforms() const { return m_vertex_uniform_data; }
|
||||||
|
std::span<const u8> GetPixelUniforms() const { return m_pixel_uniform_data; }
|
||||||
|
std::span<const TextureLikeReference> GetTextures() const { return m_texture_like_references; }
|
||||||
|
MaterialResource* GetNextMaterial() const { return m_next_material; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class MaterialResource;
|
||||||
|
std::unique_ptr<AbstractPipeline> m_pipeline = nullptr;
|
||||||
|
std::vector<u8> m_vertex_uniform_data;
|
||||||
|
std::vector<u8> m_pixel_uniform_data;
|
||||||
|
std::shared_ptr<RasterMaterialData> m_material_data = nullptr;
|
||||||
|
ShaderResource* m_shader_resource = nullptr;
|
||||||
|
using TextureLikeResource = std::variant<TextureAndSamplerResource*, RenderTargetResource*>;
|
||||||
|
Common::SmallVector<TextureLikeResource, VideoCommon::MAX_PIXEL_SHADER_SAMPLERS>
|
||||||
|
m_texture_like_resources;
|
||||||
|
using TextureLikeData = std::variant<std::shared_ptr<TextureAndSamplerResource::Data>,
|
||||||
|
std::shared_ptr<RenderTargetResource::Data>>;
|
||||||
|
Common::SmallVector<TextureLikeData, VideoCommon::MAX_PIXEL_SHADER_SAMPLERS>
|
||||||
|
m_texture_like_data;
|
||||||
|
Common::SmallVector<TextureLikeReference, VideoCommon::MAX_PIXEL_SHADER_SAMPLERS>
|
||||||
|
m_texture_like_references;
|
||||||
|
MaterialResource* m_next_material = nullptr;
|
||||||
|
std::size_t m_shader_key;
|
||||||
|
std::string m_preprocessor_settings;
|
||||||
|
std::atomic_bool m_processing_finished;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::shared_ptr<Data>& GetData() const { return m_current_data; }
|
||||||
|
void MarkAsActive() override;
|
||||||
|
void MarkAsPending() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ResetData() override;
|
||||||
|
Resource::TaskComplete CollectPrimaryData() override;
|
||||||
|
Resource::TaskComplete CollectDependencyData() override;
|
||||||
|
Resource::TaskComplete ProcessData() override;
|
||||||
|
|
||||||
|
static void CreateTextureData(Data* data);
|
||||||
|
static void SetShaderKey(Data* data, GXPipelineUid* uid);
|
||||||
|
static void WriteUniforms(Data* data);
|
||||||
|
|
||||||
|
std::shared_ptr<Data> m_current_data;
|
||||||
|
|
||||||
|
std::shared_ptr<Data> m_load_data;
|
||||||
|
bool m_processing_load_data = false;
|
||||||
|
|
||||||
|
RasterMaterialAsset* m_material_asset = nullptr;
|
||||||
|
GXPipelineUid m_uid;
|
||||||
|
};
|
||||||
|
} // namespace VideoCommon
|
253
Source/Core/VideoCommon/Resources/MeshResource.cpp
Normal file
253
Source/Core/VideoCommon/Resources/MeshResource.cpp
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/MeshResource.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/AbstractGfx.h"
|
||||||
|
#include "VideoCommon/Assets/CustomAssetCache.h"
|
||||||
|
#include "VideoCommon/NativeVertexFormat.h"
|
||||||
|
#include "VideoCommon/Resources/CustomResourceManager.h"
|
||||||
|
#include "VideoCommon/Resources/MaterialResource.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
GXPipelineUid CalculateUidForCustomMesh(const GXPipelineUid& original,
|
||||||
|
const MeshDataChunk& mesh_chunk,
|
||||||
|
NativeVertexFormat* vertex_format)
|
||||||
|
{
|
||||||
|
GXPipelineUid result;
|
||||||
|
memcpy(static_cast<void*>(&result), static_cast<const void*>(&original),
|
||||||
|
sizeof(result)); // copy padding
|
||||||
|
result.vertex_format = vertex_format;
|
||||||
|
vertex_shader_uid_data* const vs_uid_data = result.vs_uid.GetUidData();
|
||||||
|
vs_uid_data->components = mesh_chunk.components_available;
|
||||||
|
|
||||||
|
auto& tex_coords = vertex_format->GetVertexDeclaration().texcoords;
|
||||||
|
u32 texcoord_count = 0;
|
||||||
|
for (u32 i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
auto& texcoord = tex_coords[i];
|
||||||
|
if (texcoord.enable)
|
||||||
|
{
|
||||||
|
if ((vs_uid_data->components & (VB_HAS_UV0 << i)) != 0)
|
||||||
|
{
|
||||||
|
auto& texinfo = vs_uid_data->texMtxInfo[texcoord_count];
|
||||||
|
texinfo.texgentype = TexGenType::Passthrough;
|
||||||
|
texinfo.inputform = TexInputForm::ABC1;
|
||||||
|
texinfo.sourcerow = static_cast<SourceRow>(static_cast<u32>(SourceRow::Tex0) + i);
|
||||||
|
}
|
||||||
|
texcoord_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vs_uid_data->numTexGens = texcoord_count;
|
||||||
|
|
||||||
|
auto& colors = vertex_format->GetVertexDeclaration().colors;
|
||||||
|
u32 color_count = 0;
|
||||||
|
for (u32 i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
auto& color = colors[i];
|
||||||
|
if (color.enable)
|
||||||
|
{
|
||||||
|
color_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vs_uid_data->numColorChans = color_count;
|
||||||
|
|
||||||
|
vs_uid_data->dualTexTrans_enabled = false;
|
||||||
|
|
||||||
|
pixel_shader_uid_data* const ps_uid_data = result.ps_uid.GetUidData();
|
||||||
|
ps_uid_data->useDstAlpha = false;
|
||||||
|
|
||||||
|
ps_uid_data->genMode_numindstages = 0;
|
||||||
|
ps_uid_data->genMode_numtevstages = 0;
|
||||||
|
ps_uid_data->genMode_numtexgens = vs_uid_data->numTexGens;
|
||||||
|
ps_uid_data->bounding_box = false;
|
||||||
|
ps_uid_data->rgba6_format = false;
|
||||||
|
ps_uid_data->dither = false;
|
||||||
|
ps_uid_data->uint_output = false;
|
||||||
|
|
||||||
|
geometry_shader_uid_data* const gs_uid_data = result.gs_uid.GetUidData();
|
||||||
|
gs_uid_data->primitive_type = static_cast<u32>(mesh_chunk.primitive_type);
|
||||||
|
gs_uid_data->numTexGens = vs_uid_data->numTexGens;
|
||||||
|
|
||||||
|
result.rasterization_state.primitive = mesh_chunk.primitive_type;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomAssetLibrary::AssetID& GetMaterialAssetFromName(std::string_view name,
|
||||||
|
const MeshData& mesh_data)
|
||||||
|
{
|
||||||
|
if (const auto iter = mesh_data.m_mesh_material_to_material_asset_id.find(name);
|
||||||
|
iter != mesh_data.m_mesh_material_to_material_asset_id.end())
|
||||||
|
{
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
static CustomAssetLibrary::AssetID invalid = "";
|
||||||
|
return invalid;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
MeshResource::MeshChunk::MeshChunk(CustomAssetLibrary::AssetID asset_id,
|
||||||
|
const MeshDataChunk* data_chunk,
|
||||||
|
const GXPipelineUid& original_uid)
|
||||||
|
: m_asset_id(std::move(asset_id)), m_data_chunk(data_chunk),
|
||||||
|
m_native_vertex_format(g_gfx->CreateNativeVertexFormat(m_data_chunk->vertex_declaration)),
|
||||||
|
m_uid(CalculateUidForCustomMesh(original_uid, *data_chunk, m_native_vertex_format.get()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshResource::MeshChunk::~MeshChunk() = default;
|
||||||
|
|
||||||
|
void MeshResource::MeshChunk::UpdateMaterial(CustomResourceManager* resource_manager,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library)
|
||||||
|
{
|
||||||
|
m_material = resource_manager->GetMaterialFromAsset(m_asset_id, m_uid, std::move(asset_library));
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshResource::MeshResource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library,
|
||||||
|
CustomAssetCache* asset_cache, CustomResourceManager* resource_manager,
|
||||||
|
TexturePool* texture_pool, Common::AsyncWorkThreadSP* worker_queue,
|
||||||
|
const GXPipelineUid& pipeline_uid)
|
||||||
|
: Resource(std::move(primary_asset_id), std::move(asset_library), asset_cache, resource_manager,
|
||||||
|
texture_pool, worker_queue),
|
||||||
|
m_uid(pipeline_uid)
|
||||||
|
{
|
||||||
|
m_mesh_asset = m_asset_cache->CreateAsset<MeshAsset>(m_primary_asset_id, m_asset_library, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshResource::MarkAsActive()
|
||||||
|
{
|
||||||
|
if (!m_current_data) [[unlikely]]
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_asset_cache->MarkAssetActive(m_mesh_asset);
|
||||||
|
|
||||||
|
for (const auto& chunk : m_current_data->m_mesh_chunks)
|
||||||
|
{
|
||||||
|
chunk.m_material->MarkAsActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [draw_call, chunks] : m_current_data->m_gpu_skinned_mesh_chunks)
|
||||||
|
{
|
||||||
|
for (const auto& chunk : chunks)
|
||||||
|
{
|
||||||
|
chunk.m_material->MarkAsActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshResource::MarkAsPending()
|
||||||
|
{
|
||||||
|
m_asset_cache->MarkAssetPending(m_mesh_asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<const MeshResource::MeshChunk>
|
||||||
|
MeshResource::Data::GetMeshChunks(GraphicsModSystem::DrawCallID draw_call) const
|
||||||
|
{
|
||||||
|
if (const auto iter = m_gpu_skinned_mesh_chunks.find(draw_call);
|
||||||
|
iter != m_gpu_skinned_mesh_chunks.end())
|
||||||
|
{
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_mesh_chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshResource::Data::GenerateChunks(const GXPipelineUid& uid)
|
||||||
|
{
|
||||||
|
for (const auto& [draw_call_id, chunks] : m_mesh_data->m_gpu_skinning_chunks)
|
||||||
|
{
|
||||||
|
auto& skinned_mesh_chunks = m_gpu_skinned_mesh_chunks[draw_call_id];
|
||||||
|
for (const auto& chunk : chunks)
|
||||||
|
{
|
||||||
|
const auto& asset_id = GetMaterialAssetFromName(chunk.material_name, *m_mesh_data);
|
||||||
|
if (asset_id == "")
|
||||||
|
continue;
|
||||||
|
skinned_mesh_chunks.emplace_back(asset_id, &chunk, uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& chunk : m_mesh_data->m_mesh_chunks)
|
||||||
|
{
|
||||||
|
const auto& asset_id = GetMaterialAssetFromName(chunk.material_name, *m_mesh_data);
|
||||||
|
if (asset_id == "")
|
||||||
|
continue;
|
||||||
|
m_mesh_chunks.emplace_back(asset_id, &chunk, uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshResource::ResetData()
|
||||||
|
{
|
||||||
|
if (m_current_data)
|
||||||
|
{
|
||||||
|
for (auto& chunk : m_current_data->m_mesh_chunks)
|
||||||
|
{
|
||||||
|
chunk.m_material->RemoveReference(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [draw_call, chunks] : m_current_data->m_gpu_skinned_mesh_chunks)
|
||||||
|
{
|
||||||
|
for (auto& chunk : chunks)
|
||||||
|
{
|
||||||
|
chunk.m_material->RemoveReference(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_load_data = std::make_shared<Data>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete MeshResource::CollectPrimaryData()
|
||||||
|
{
|
||||||
|
const auto mesh_data = m_mesh_asset->GetData();
|
||||||
|
if (!mesh_data) [[unlikely]]
|
||||||
|
{
|
||||||
|
return TaskComplete::No;
|
||||||
|
}
|
||||||
|
m_load_data->m_mesh_data = mesh_data;
|
||||||
|
m_load_data->GenerateChunks(m_uid);
|
||||||
|
return TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete MeshResource::CollectDependencyData()
|
||||||
|
{
|
||||||
|
bool loaded = true;
|
||||||
|
for (auto& chunk : m_load_data->m_mesh_chunks)
|
||||||
|
{
|
||||||
|
chunk.UpdateMaterial(m_resource_manager, m_asset_library);
|
||||||
|
chunk.m_material->AddReference(this);
|
||||||
|
|
||||||
|
const auto data_processed = chunk.m_material->IsDataProcessed();
|
||||||
|
if (data_processed == TaskComplete::Error)
|
||||||
|
return TaskComplete::Error;
|
||||||
|
|
||||||
|
loaded &= data_processed == TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [draw_call, chunks] : m_load_data->m_gpu_skinned_mesh_chunks)
|
||||||
|
{
|
||||||
|
for (auto& chunk : chunks)
|
||||||
|
{
|
||||||
|
chunk.UpdateMaterial(m_resource_manager, m_asset_library);
|
||||||
|
chunk.m_material->AddReference(this);
|
||||||
|
|
||||||
|
const auto data_processed = chunk.m_material->IsDataProcessed();
|
||||||
|
if (data_processed == TaskComplete::Error)
|
||||||
|
return TaskComplete::Error;
|
||||||
|
|
||||||
|
loaded &= data_processed == TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaded ? TaskComplete::Yes : TaskComplete::No;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete MeshResource::ProcessData()
|
||||||
|
{
|
||||||
|
std::swap(m_current_data, m_load_data);
|
||||||
|
return TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
} // namespace VideoCommon
|
101
Source/Core/VideoCommon/Resources/MeshResource.h
Normal file
101
Source/Core/VideoCommon/Resources/MeshResource.h
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/Resource.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/Assets/MeshAsset.h"
|
||||||
|
|
||||||
|
class NativeVertexFormat;
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
class MaterialResource;
|
||||||
|
class MeshResource final : public Resource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MeshResource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library, CustomAssetCache* asset_cache,
|
||||||
|
CustomResourceManager* resource_manager, TexturePool* texture_pool,
|
||||||
|
Common::AsyncWorkThreadSP* worker_queue, const GXPipelineUid& pipeline_uid);
|
||||||
|
|
||||||
|
void MarkAsActive() override;
|
||||||
|
void MarkAsPending() override;
|
||||||
|
|
||||||
|
class MeshChunk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MeshChunk(CustomAssetLibrary::AssetID asset_id, const MeshDataChunk* data_chunk,
|
||||||
|
const GXPipelineUid& original_uid);
|
||||||
|
MeshChunk(const MeshChunk&) = delete;
|
||||||
|
MeshChunk(MeshChunk&&) = default;
|
||||||
|
MeshChunk& operator=(const MeshChunk&) = delete;
|
||||||
|
MeshChunk& operator=(MeshChunk&&) = default;
|
||||||
|
~MeshChunk();
|
||||||
|
|
||||||
|
std::span<const u8> GetVertexData() const
|
||||||
|
{
|
||||||
|
return std::span<const u8>(m_data_chunk->vertex_data.get(), m_data_chunk->num_vertices);
|
||||||
|
}
|
||||||
|
std::span<const u16> GetIndexData() const
|
||||||
|
{
|
||||||
|
return std::span<const u16>(m_data_chunk->indices.get(), m_data_chunk->num_indices);
|
||||||
|
}
|
||||||
|
u32 GetVertexStride() const { return m_data_chunk->vertex_stride; }
|
||||||
|
PrimitiveType GetPrimitiveType() const { return m_data_chunk->primitive_type; }
|
||||||
|
u32 GetComponentsAvailable() const { return m_data_chunk->components_available; }
|
||||||
|
Common::Matrix44 GetTransform() const { return m_data_chunk->transform; }
|
||||||
|
const MaterialResource* GetMaterial() const { return m_material; }
|
||||||
|
void SetMaterial(MaterialResource* material) { m_material = material; }
|
||||||
|
NativeVertexFormat* GetNativeVertexFormat() const { return m_native_vertex_format.get(); }
|
||||||
|
Common::Vec3 GetPivotPoint() const { return Common::Vec3{}; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class MeshResource;
|
||||||
|
void UpdateMaterial(CustomResourceManager* resource_manager,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library);
|
||||||
|
|
||||||
|
CustomAssetLibrary::AssetID m_asset_id;
|
||||||
|
MaterialResource* m_material = nullptr;
|
||||||
|
const MeshDataChunk* m_data_chunk = nullptr;
|
||||||
|
std::unique_ptr<NativeVertexFormat> m_native_vertex_format;
|
||||||
|
GXPipelineUid m_uid;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::span<const MeshChunk> GetMeshChunks(GraphicsModSystem::DrawCallID draw_call) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class MeshResource;
|
||||||
|
void GenerateChunks(const GXPipelineUid& uid);
|
||||||
|
|
||||||
|
std::map<std::string, CustomAssetLibrary::AssetID, std::less<>> m_name_to_material_id;
|
||||||
|
std::shared_ptr<MeshData> m_mesh_data = nullptr;
|
||||||
|
std::map<GraphicsModSystem::DrawCallID, std::vector<MeshChunk>> m_gpu_skinned_mesh_chunks;
|
||||||
|
std::vector<MeshChunk> m_mesh_chunks;
|
||||||
|
std::atomic_bool m_processing_finished;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::shared_ptr<Data>& GetData() const { return m_current_data; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ResetData() override;
|
||||||
|
TaskComplete CollectPrimaryData() override;
|
||||||
|
TaskComplete CollectDependencyData() override;
|
||||||
|
TaskComplete ProcessData() override;
|
||||||
|
|
||||||
|
std::shared_ptr<Data> m_current_data;
|
||||||
|
std::shared_ptr<Data> m_load_data;
|
||||||
|
|
||||||
|
MeshAsset* m_mesh_asset = nullptr;
|
||||||
|
|
||||||
|
GXPipelineUid m_uid;
|
||||||
|
};
|
||||||
|
} // namespace VideoCommon
|
92
Source/Core/VideoCommon/Resources/RenderTargetResource.cpp
Normal file
92
Source/Core/VideoCommon/Resources/RenderTargetResource.cpp
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/RenderTargetResource.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/Assets/CustomAssetCache.h"
|
||||||
|
#include "VideoCommon/Resources/TexturePool.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
RenderTargetResource::RenderTargetResource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library,
|
||||||
|
CustomAssetCache* asset_cache,
|
||||||
|
CustomResourceManager* resource_manager,
|
||||||
|
TexturePool* texture_pool,
|
||||||
|
Common::AsyncWorkThreadSP* worker_queue)
|
||||||
|
: Resource(std::move(primary_asset_id), std::move(asset_library), asset_cache, resource_manager,
|
||||||
|
texture_pool, worker_queue)
|
||||||
|
{
|
||||||
|
m_render_target_asset =
|
||||||
|
m_asset_cache->CreateAsset<RenderTargetAsset>(m_primary_asset_id, m_asset_library, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTargetResource::MarkAsActive()
|
||||||
|
{
|
||||||
|
if (!m_current_data)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_asset_cache->MarkAssetActive(m_render_target_asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTargetResource::MarkAsPending()
|
||||||
|
{
|
||||||
|
m_asset_cache->MarkAssetPending(m_render_target_asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::shared_ptr<RenderTargetResource::Data>& RenderTargetResource::GetData() const
|
||||||
|
{
|
||||||
|
return m_current_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTargetResource::ResetData()
|
||||||
|
{
|
||||||
|
m_load_data = std::make_shared<Data>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete RenderTargetResource::CollectPrimaryData()
|
||||||
|
{
|
||||||
|
m_load_data->m_render_target_data = m_render_target_asset->GetData();
|
||||||
|
m_load_data->m_load_time = m_render_target_asset->GetLastLoadedTime();
|
||||||
|
if (!m_load_data->m_render_target_data)
|
||||||
|
return Resource::TaskComplete::No;
|
||||||
|
|
||||||
|
auto& config = m_load_data->m_config;
|
||||||
|
config.format = m_load_data->m_render_target_data->texture_format;
|
||||||
|
config.flags = AbstractTextureFlag::AbstractTextureFlag_RenderTarget;
|
||||||
|
config.layers = 1;
|
||||||
|
config.levels = 1;
|
||||||
|
config.type = m_load_data->m_render_target_data->type;
|
||||||
|
config.samples = 1;
|
||||||
|
|
||||||
|
config.width = m_load_data->m_render_target_data->width;
|
||||||
|
config.height = m_load_data->m_render_target_data->height;
|
||||||
|
|
||||||
|
return Resource::TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete RenderTargetResource::ProcessData()
|
||||||
|
{
|
||||||
|
if (auto texture = m_texture_pool->AllocateTexture(m_load_data->m_config))
|
||||||
|
{
|
||||||
|
m_load_data->m_texture = std::move(*texture);
|
||||||
|
std::swap(m_current_data, m_load_data);
|
||||||
|
|
||||||
|
// Release old data back to the pool
|
||||||
|
if (m_load_data)
|
||||||
|
m_texture_pool->ReleaseTexture(std::move(m_load_data->m_texture));
|
||||||
|
|
||||||
|
return Resource::TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resource::TaskComplete::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTargetResource::OnUnloadRequested()
|
||||||
|
{
|
||||||
|
if (!m_current_data)
|
||||||
|
return;
|
||||||
|
m_texture_pool->ReleaseTexture(std::move(m_current_data->m_texture));
|
||||||
|
m_current_data = nullptr;
|
||||||
|
}
|
||||||
|
} // namespace VideoCommon
|
55
Source/Core/VideoCommon/Resources/RenderTargetResource.h
Normal file
55
Source/Core/VideoCommon/Resources/RenderTargetResource.h
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/Resource.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "VideoCommon/AbstractTexture.h"
|
||||||
|
#include "VideoCommon/Assets/RenderTargetAsset.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
class RenderTargetResource final : public Resource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RenderTargetResource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library,
|
||||||
|
CustomAssetCache* asset_cache, CustomResourceManager* resource_manager,
|
||||||
|
TexturePool* texture_pool, Common::AsyncWorkThreadSP* worker_queue);
|
||||||
|
|
||||||
|
void MarkAsActive() override;
|
||||||
|
void MarkAsPending() override;
|
||||||
|
|
||||||
|
class Data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AbstractTexture* GetTexture() const { return m_texture.get(); }
|
||||||
|
CustomAsset::TimeType GetLoadTime() const { return m_load_time; }
|
||||||
|
const SamplerState& GetSampler() const { return m_render_target_data->sampler; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class RenderTargetResource;
|
||||||
|
|
||||||
|
std::shared_ptr<RenderTargetData> m_render_target_data;
|
||||||
|
std::unique_ptr<AbstractTexture> m_texture;
|
||||||
|
TextureConfig m_config;
|
||||||
|
CustomAsset::TimeType m_load_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::shared_ptr<Data>& GetData() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ResetData() override;
|
||||||
|
TaskComplete CollectPrimaryData() override;
|
||||||
|
TaskComplete ProcessData() override;
|
||||||
|
|
||||||
|
void OnUnloadRequested() override;
|
||||||
|
|
||||||
|
RenderTargetAsset* m_render_target_asset = nullptr;
|
||||||
|
std::shared_ptr<Data> m_current_data;
|
||||||
|
std::shared_ptr<Data> m_load_data;
|
||||||
|
};
|
||||||
|
} // namespace VideoCommon
|
82
Source/Core/VideoCommon/Resources/Resource.cpp
Normal file
82
Source/Core/VideoCommon/Resources/Resource.cpp
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/Resource.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
Resource::Resource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library, CustomAssetCache* asset_cache,
|
||||||
|
CustomResourceManager* resource_manager, TexturePool* texture_pool,
|
||||||
|
Common::AsyncWorkThreadSP* worker_queue)
|
||||||
|
: m_primary_asset_id(std::move(primary_asset_id)), m_asset_library(std::move(asset_library)),
|
||||||
|
m_asset_cache(asset_cache), m_resource_manager(resource_manager),
|
||||||
|
m_texture_pool(texture_pool), m_worker_queue(worker_queue)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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::AssetLoaded(bool has_error, bool triggered_by_reload)
|
||||||
|
{
|
||||||
|
if (triggered_by_reload)
|
||||||
|
NotifyAssetChanged(has_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
83
Source/Core/VideoCommon/Resources/Resource.h
Normal file
83
Source/Core/VideoCommon/Resources/Resource.h
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common/WorkQueueThread.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/Assets/AssetListener.h"
|
||||||
|
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
class CustomAssetCache;
|
||||||
|
class CustomResourceManager;
|
||||||
|
class TexturePool;
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
Resource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library, CustomAssetCache* asset_cache,
|
||||||
|
CustomResourceManager* resource_manager, TexturePool* texture_pool,
|
||||||
|
Common::AsyncWorkThreadSP* worker_queue);
|
||||||
|
|
||||||
|
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* resource);
|
||||||
|
void RemoveReference(Resource* resource);
|
||||||
|
|
||||||
|
virtual void MarkAsActive() = 0;
|
||||||
|
virtual void MarkAsPending() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CustomAssetLibrary::AssetID m_primary_asset_id;
|
||||||
|
std::shared_ptr<CustomAssetLibrary> m_asset_library;
|
||||||
|
CustomAssetCache* m_asset_cache;
|
||||||
|
CustomResourceManager* m_resource_manager;
|
||||||
|
TexturePool* m_texture_pool;
|
||||||
|
Common::AsyncWorkThreadSP* m_worker_queue;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void NotifyAssetChanged(bool has_error);
|
||||||
|
void NotifyAssetUnloaded();
|
||||||
|
|
||||||
|
void AssetLoaded(bool has_error, bool triggered_by_reload) 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
|
247
Source/Core/VideoCommon/Resources/ShaderResource.cpp
Normal file
247
Source/Core/VideoCommon/Resources/ShaderResource.cpp
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/ShaderResource.h"
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "VideoCommon/AbstractGfx.h"
|
||||||
|
#include "VideoCommon/Assets/CustomAssetCache.h"
|
||||||
|
#include "VideoCommon/GeometryShaderGen.h"
|
||||||
|
#include "VideoCommon/PixelShaderGen.h"
|
||||||
|
#include "VideoCommon/ShaderCacheUtils.h"
|
||||||
|
#include "VideoCommon/VertexShaderGen.h"
|
||||||
|
#include "VideoCommon/VideoConfig.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::unique_ptr<AbstractShader>
|
||||||
|
CompileGeometryShader(const GeometryShaderUid& uid, APIType api_type, ShaderHostConfig host_config)
|
||||||
|
{
|
||||||
|
const ShaderCode source_code =
|
||||||
|
GenerateGeometryShaderCode(api_type, host_config, uid.GetUidData());
|
||||||
|
return g_gfx->CreateShaderFromSource(ShaderStage::Geometry, source_code.GetBuffer(),
|
||||||
|
fmt::format("Geometry shader: {}", *uid.GetUidData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AbstractShader> CompilePixelShader(const PixelShaderUid& uid,
|
||||||
|
std::string_view preprocessor_settings,
|
||||||
|
APIType api_type,
|
||||||
|
const ShaderHostConfig& host_config,
|
||||||
|
const RasterShaderData& shader_data)
|
||||||
|
{
|
||||||
|
ShaderCode shader_code;
|
||||||
|
|
||||||
|
// Write any preprocessor values that were passed in
|
||||||
|
shader_code.Write("{}", preprocessor_settings);
|
||||||
|
|
||||||
|
// TODO: in the future we could dynamically determine the amount of samplers
|
||||||
|
// available, for now just hardcode to start at 8 (the first non game
|
||||||
|
// sampler index available)
|
||||||
|
const std::size_t custom_sampler_index_offset = 8;
|
||||||
|
for (std::size_t i = 0; i < shader_data.m_pixel_samplers.size(); i++)
|
||||||
|
{
|
||||||
|
const auto& pixel_sampler = shader_data.m_pixel_samplers[i];
|
||||||
|
std::string_view sampler_type = "";
|
||||||
|
switch (pixel_sampler.type)
|
||||||
|
{
|
||||||
|
case AbstractTextureType::Texture_2D:
|
||||||
|
sampler_type = "sampler2D";
|
||||||
|
break;
|
||||||
|
case AbstractTextureType::Texture_2DArray:
|
||||||
|
sampler_type = "sampler2DArray";
|
||||||
|
break;
|
||||||
|
case AbstractTextureType::Texture_CubeMap:
|
||||||
|
sampler_type = "samplerCube";
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
shader_code.Write("SAMPLER_BINDING({}) uniform {} samp_{};\n", custom_sampler_index_offset + i,
|
||||||
|
sampler_type, pixel_sampler.name);
|
||||||
|
|
||||||
|
// Sampler usage is passed in from the material
|
||||||
|
// Write a new preprocessor value with the sampler name
|
||||||
|
// for easier code in the shader
|
||||||
|
shader_code.Write("#ifdef HAS_SAMPLER_{}\n", i);
|
||||||
|
shader_code.Write("#define HAS_{} 1\n", pixel_sampler.name);
|
||||||
|
shader_code.Write("#else\n");
|
||||||
|
shader_code.Write("#define HAS_{} 0\n", pixel_sampler.name);
|
||||||
|
shader_code.Write("#endif\n");
|
||||||
|
|
||||||
|
shader_code.Write("\n");
|
||||||
|
}
|
||||||
|
shader_code.Write("\n");
|
||||||
|
|
||||||
|
// Now write the custom shader
|
||||||
|
shader_code.Write("{}", ReplaceAll(shader_data.m_pixel_source, "\r\n", "\n"));
|
||||||
|
|
||||||
|
// Write out the uniform data
|
||||||
|
ShaderCode uniform_code;
|
||||||
|
for (const auto& [name, property] : shader_data.m_pixel_properties)
|
||||||
|
{
|
||||||
|
VideoCommon::ShaderProperty2::WriteAsShaderCode(uniform_code, name, property);
|
||||||
|
}
|
||||||
|
if (!shader_data.m_pixel_properties.empty())
|
||||||
|
uniform_code.Write("\n\n");
|
||||||
|
|
||||||
|
// Compile the shader
|
||||||
|
CustomPixelContents contents{.shader = shader_code.GetBuffer(),
|
||||||
|
.uniforms = uniform_code.GetBuffer()};
|
||||||
|
const ShaderCode source_code =
|
||||||
|
GeneratePixelShaderCode(api_type, host_config, uid.GetUidData(), contents);
|
||||||
|
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
|
||||||
|
"Custom Pixel Shader");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AbstractShader> CompileVertexShader(const VertexShaderUid& uid,
|
||||||
|
std::string_view preprocessor_settings,
|
||||||
|
APIType api_type,
|
||||||
|
const ShaderHostConfig& host_config,
|
||||||
|
const RasterShaderData& shader_data)
|
||||||
|
{
|
||||||
|
ShaderCode shader_code;
|
||||||
|
|
||||||
|
// Write any preprocessor values that were passed in
|
||||||
|
shader_code.Write("{}", preprocessor_settings);
|
||||||
|
|
||||||
|
// Now write the custom shader
|
||||||
|
shader_code.Write("{}", ReplaceAll(shader_data.m_vertex_source, "\r\n", "\n"));
|
||||||
|
|
||||||
|
// Write out the uniform data
|
||||||
|
ShaderCode uniform_code;
|
||||||
|
for (const auto& [name, property] : shader_data.m_vertex_properties)
|
||||||
|
{
|
||||||
|
VideoCommon::ShaderProperty2::WriteAsShaderCode(uniform_code, name, property);
|
||||||
|
}
|
||||||
|
if (!shader_data.m_vertex_properties.empty())
|
||||||
|
uniform_code.Write("\n\n");
|
||||||
|
|
||||||
|
// Compile the shader
|
||||||
|
CustomVertexContents contents{.shader = shader_code.GetBuffer(),
|
||||||
|
.uniforms = uniform_code.GetBuffer()};
|
||||||
|
const ShaderCode source_code =
|
||||||
|
GenerateVertexShaderCode(api_type, host_config, uid.GetUidData(), contents);
|
||||||
|
return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer(),
|
||||||
|
"Custom Vertex Shader");
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
ShaderResource::ShaderResource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library,
|
||||||
|
CustomAssetCache* asset_cache,
|
||||||
|
CustomResourceManager* resource_manager, TexturePool* texture_pool,
|
||||||
|
Common::AsyncWorkThreadSP* worker_queue,
|
||||||
|
const GXPipelineUid& pipeline_uid,
|
||||||
|
const std::string& preprocessor_setting)
|
||||||
|
: Resource(std::move(primary_asset_id), std::move(asset_library), asset_cache, resource_manager,
|
||||||
|
texture_pool, worker_queue),
|
||||||
|
m_uid(pipeline_uid), m_preprocessor_settings(preprocessor_setting)
|
||||||
|
{
|
||||||
|
m_shader_asset =
|
||||||
|
m_asset_cache->CreateAsset<RasterShaderAsset>(m_primary_asset_id, m_asset_library, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderResource::SetHostConfig(const ShaderHostConfig& host_config)
|
||||||
|
{
|
||||||
|
m_shader_host_config.bits = host_config.bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderResource::MarkAsPending()
|
||||||
|
{
|
||||||
|
m_asset_cache->MarkAssetPending(m_shader_asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderResource::MarkAsActive()
|
||||||
|
{
|
||||||
|
m_asset_cache->MarkAssetActive(m_shader_asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractShader* ShaderResource::Data::GetVertexShader() const
|
||||||
|
{
|
||||||
|
if (!m_vertex_shader)
|
||||||
|
return nullptr;
|
||||||
|
return m_vertex_shader.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractShader* ShaderResource::Data::GetPixelShader() const
|
||||||
|
{
|
||||||
|
if (!m_pixel_shader)
|
||||||
|
return nullptr;
|
||||||
|
return m_pixel_shader.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractShader* ShaderResource::Data::GetGeometryShader() const
|
||||||
|
{
|
||||||
|
if (!m_geometry_shader)
|
||||||
|
return nullptr;
|
||||||
|
return m_geometry_shader.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderResource::Data::IsCompiled() const
|
||||||
|
{
|
||||||
|
return m_vertex_shader && m_pixel_shader && (!m_needs_geometry_shader || m_geometry_shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderResource::ResetData()
|
||||||
|
{
|
||||||
|
m_load_data = std::make_shared<Data>();
|
||||||
|
m_processing_load_data = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete ShaderResource::CollectPrimaryData()
|
||||||
|
{
|
||||||
|
const auto shader_data = m_shader_asset->GetData();
|
||||||
|
if (!shader_data) [[unlikely]]
|
||||||
|
{
|
||||||
|
return Resource::TaskComplete::No;
|
||||||
|
}
|
||||||
|
m_load_data->m_shader_data = shader_data;
|
||||||
|
|
||||||
|
return Resource::TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete ShaderResource::ProcessData()
|
||||||
|
{
|
||||||
|
if (!m_processing_load_data)
|
||||||
|
{
|
||||||
|
std::string_view preprocessor_settings = m_preprocessor_settings;
|
||||||
|
auto work = [resource_data = m_load_data, uid = &m_uid, bits = m_shader_host_config.bits,
|
||||||
|
preprocessor_settings = preprocessor_settings] {
|
||||||
|
ShaderHostConfig shader_host_config;
|
||||||
|
shader_host_config.bits = bits;
|
||||||
|
auto actual_uid = ApplyDriverBugs(*uid);
|
||||||
|
|
||||||
|
ClearUnusedPixelShaderUidBits(g_backend_info.api_type, shader_host_config,
|
||||||
|
&actual_uid.ps_uid);
|
||||||
|
resource_data->m_needs_geometry_shader = shader_host_config.backend_geometry_shaders &&
|
||||||
|
!actual_uid.gs_uid.GetUidData()->IsPassthrough();
|
||||||
|
|
||||||
|
if (resource_data->m_needs_geometry_shader)
|
||||||
|
{
|
||||||
|
resource_data->m_geometry_shader =
|
||||||
|
CompileGeometryShader(actual_uid.gs_uid, g_backend_info.api_type, shader_host_config);
|
||||||
|
}
|
||||||
|
resource_data->m_pixel_shader =
|
||||||
|
CompilePixelShader(actual_uid.ps_uid, preprocessor_settings, g_backend_info.api_type,
|
||||||
|
shader_host_config, *resource_data->m_shader_data);
|
||||||
|
resource_data->m_vertex_shader =
|
||||||
|
CompileVertexShader(actual_uid.vs_uid, preprocessor_settings, g_backend_info.api_type,
|
||||||
|
shader_host_config, *resource_data->m_shader_data);
|
||||||
|
resource_data->m_processing_finished = true;
|
||||||
|
};
|
||||||
|
m_worker_queue->Push(std::move(work));
|
||||||
|
m_processing_load_data = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_load_data->m_processing_finished)
|
||||||
|
return Resource::TaskComplete::No;
|
||||||
|
|
||||||
|
if (!m_load_data->IsCompiled())
|
||||||
|
return Resource::TaskComplete::Error;
|
||||||
|
|
||||||
|
std::swap(m_current_data, m_load_data);
|
||||||
|
return Resource::TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
} // namespace VideoCommon
|
67
Source/Core/VideoCommon/Resources/ShaderResource.h
Normal file
67
Source/Core/VideoCommon/Resources/ShaderResource.h
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/Resource.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/Assets/ShaderAsset.h"
|
||||||
|
#include "VideoCommon/GXPipelineTypes.h"
|
||||||
|
#include "VideoCommon/ShaderGenCommon.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
class ShaderResource final : public Resource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ShaderResource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library, CustomAssetCache* asset_cache,
|
||||||
|
CustomResourceManager* resource_manager, TexturePool* texture_pool,
|
||||||
|
Common::AsyncWorkThreadSP* worker_queue, const GXPipelineUid& pipeline_uid,
|
||||||
|
const std::string& preprocessor_settings);
|
||||||
|
|
||||||
|
class Data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AbstractShader* GetVertexShader() const;
|
||||||
|
AbstractShader* GetPixelShader() const;
|
||||||
|
AbstractShader* GetGeometryShader() const;
|
||||||
|
|
||||||
|
bool IsCompiled() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class ShaderResource;
|
||||||
|
std::unique_ptr<AbstractShader> m_vertex_shader;
|
||||||
|
std::unique_ptr<AbstractShader> m_pixel_shader;
|
||||||
|
std::unique_ptr<AbstractShader> m_geometry_shader;
|
||||||
|
std::shared_ptr<RasterShaderData> m_shader_data;
|
||||||
|
bool m_needs_geometry_shader = false;
|
||||||
|
std::atomic_bool m_processing_finished;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Changes the shader host config. Shaders should be reloaded afterwards.
|
||||||
|
void SetHostConfig(const ShaderHostConfig& host_config);
|
||||||
|
const std::shared_ptr<Data>& GetData() const { return m_current_data; }
|
||||||
|
|
||||||
|
void MarkAsActive() override;
|
||||||
|
void MarkAsPending() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ResetData() override;
|
||||||
|
Resource::TaskComplete CollectPrimaryData() override;
|
||||||
|
TaskComplete ProcessData() override;
|
||||||
|
|
||||||
|
RasterShaderAsset* m_shader_asset = nullptr;
|
||||||
|
|
||||||
|
std::shared_ptr<Data> m_current_data;
|
||||||
|
std::shared_ptr<Data> m_load_data;
|
||||||
|
|
||||||
|
bool m_processing_load_data = false;
|
||||||
|
|
||||||
|
ShaderHostConfig m_shader_host_config;
|
||||||
|
GXPipelineUid m_uid;
|
||||||
|
std::string m_preprocessor_settings;
|
||||||
|
};
|
||||||
|
} // namespace VideoCommon
|
109
Source/Core/VideoCommon/Resources/TextureAndSamplerResource.cpp
Normal file
109
Source/Core/VideoCommon/Resources/TextureAndSamplerResource.cpp
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/TextureAndSamplerResource.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/Assets/CustomAssetCache.h"
|
||||||
|
#include "VideoCommon/Resources/TexturePool.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
TextureAndSamplerResource::TextureAndSamplerResource(
|
||||||
|
CustomAssetLibrary::AssetID primary_asset_id, std::shared_ptr<CustomAssetLibrary> asset_library,
|
||||||
|
CustomAssetCache* asset_cache, CustomResourceManager* resource_manager,
|
||||||
|
TexturePool* texture_pool, Common::AsyncWorkThreadSP* worker_queue)
|
||||||
|
: Resource(std::move(primary_asset_id), std::move(asset_library), asset_cache, resource_manager,
|
||||||
|
texture_pool, worker_queue)
|
||||||
|
{
|
||||||
|
m_texture_and_sampler_asset =
|
||||||
|
m_asset_cache->CreateAsset<TextureAndSamplerAsset>(m_primary_asset_id, m_asset_library, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAndSamplerResource::MarkAsActive()
|
||||||
|
{
|
||||||
|
m_asset_cache->MarkAssetActive(m_texture_and_sampler_asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAndSamplerResource::MarkAsPending()
|
||||||
|
{
|
||||||
|
m_asset_cache->MarkAssetPending(m_texture_and_sampler_asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::shared_ptr<TextureAndSamplerResource::Data>& TextureAndSamplerResource::GetData() const
|
||||||
|
{
|
||||||
|
return m_current_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAndSamplerResource::ResetData()
|
||||||
|
{
|
||||||
|
m_load_data = std::make_shared<Data>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete TextureAndSamplerResource::CollectPrimaryData()
|
||||||
|
{
|
||||||
|
m_load_data->m_texture_and_sampler_data = m_texture_and_sampler_asset->GetData();
|
||||||
|
if (!m_load_data->m_texture_and_sampler_data)
|
||||||
|
return Resource::TaskComplete::No;
|
||||||
|
|
||||||
|
auto& texture_data = m_load_data->m_texture_and_sampler_data->texture_data;
|
||||||
|
if (texture_data.m_slices.empty())
|
||||||
|
return Resource::TaskComplete::Error;
|
||||||
|
|
||||||
|
if (texture_data.m_slices[0].m_levels.empty())
|
||||||
|
return Resource::TaskComplete::Error;
|
||||||
|
|
||||||
|
const auto& first_level = texture_data.m_slices[0].m_levels[0];
|
||||||
|
|
||||||
|
auto& config = m_load_data->m_config;
|
||||||
|
config.format = first_level.format;
|
||||||
|
config.flags = 0;
|
||||||
|
config.layers = 1;
|
||||||
|
config.levels = 1;
|
||||||
|
config.type = m_load_data->m_texture_and_sampler_data->type;
|
||||||
|
config.samples = 1;
|
||||||
|
|
||||||
|
config.width = first_level.width;
|
||||||
|
config.height = first_level.height;
|
||||||
|
|
||||||
|
return Resource::TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete TextureAndSamplerResource::ProcessData()
|
||||||
|
{
|
||||||
|
if (auto texture = m_texture_pool->AllocateTexture(m_load_data->m_config))
|
||||||
|
{
|
||||||
|
m_load_data->m_texture = std::move(*texture);
|
||||||
|
|
||||||
|
auto& texture_data = m_load_data->m_texture_and_sampler_data->texture_data;
|
||||||
|
for (std::size_t slice_index = 0; slice_index < texture_data.m_slices.size(); slice_index++)
|
||||||
|
{
|
||||||
|
auto& slice = texture_data.m_slices[slice_index];
|
||||||
|
for (u32 level_index = 0; level_index < static_cast<u32>(slice.m_levels.size());
|
||||||
|
++level_index)
|
||||||
|
{
|
||||||
|
auto& level = slice.m_levels[level_index];
|
||||||
|
m_load_data->m_texture->Load(level_index, level.width, level.height, level.row_length,
|
||||||
|
level.data.data(), level.data.size(),
|
||||||
|
static_cast<u32>(slice_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::swap(m_current_data, m_load_data);
|
||||||
|
|
||||||
|
// Release old data back to the pool
|
||||||
|
if (m_load_data)
|
||||||
|
m_texture_pool->ReleaseTexture(std::move(m_load_data->m_texture));
|
||||||
|
|
||||||
|
return Resource::TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resource::TaskComplete::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAndSamplerResource::OnUnloadRequested()
|
||||||
|
{
|
||||||
|
if (!m_current_data)
|
||||||
|
return;
|
||||||
|
m_texture_pool->ReleaseTexture(std::move(m_current_data->m_texture));
|
||||||
|
m_current_data = nullptr;
|
||||||
|
}
|
||||||
|
} // namespace VideoCommon
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/Resource.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/AbstractTexture.h"
|
||||||
|
#include "VideoCommon/Assets/TextureAsset.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
class TextureAndSamplerResource final : public Resource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TextureAndSamplerResource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library,
|
||||||
|
CustomAssetCache* asset_cache, CustomResourceManager* resource_manager,
|
||||||
|
TexturePool* texture_pool, Common::AsyncWorkThreadSP* worker_queue);
|
||||||
|
void MarkAsActive() override;
|
||||||
|
void MarkAsPending() override;
|
||||||
|
|
||||||
|
class Data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AbstractTexture* GetTexture() const { return m_texture.get(); }
|
||||||
|
const SamplerState& GetSampler() const { return m_texture_and_sampler_data->sampler; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class TextureAndSamplerResource;
|
||||||
|
|
||||||
|
std::shared_ptr<TextureAndSamplerData> m_texture_and_sampler_data;
|
||||||
|
std::unique_ptr<AbstractTexture> m_texture;
|
||||||
|
TextureConfig m_config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::shared_ptr<Data>& GetData() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ResetData() override;
|
||||||
|
TaskComplete CollectPrimaryData() override;
|
||||||
|
TaskComplete ProcessData() override;
|
||||||
|
|
||||||
|
void OnUnloadRequested() override;
|
||||||
|
|
||||||
|
TextureAndSamplerAsset* m_texture_and_sampler_asset = nullptr;
|
||||||
|
std::shared_ptr<Data> m_current_data;
|
||||||
|
std::shared_ptr<Data> m_load_data;
|
||||||
|
};
|
||||||
|
} // namespace VideoCommon
|
59
Source/Core/VideoCommon/Resources/TextureDataResource.cpp
Normal file
59
Source/Core/VideoCommon/Resources/TextureDataResource.cpp
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// 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(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library,
|
||||||
|
CustomAssetCache* asset_cache,
|
||||||
|
CustomResourceManager* resource_manager,
|
||||||
|
TexturePool* texture_pool,
|
||||||
|
Common::AsyncWorkThreadSP* worker_queue)
|
||||||
|
: Resource(std::move(primary_asset_id), std::move(asset_library), asset_cache, resource_manager,
|
||||||
|
texture_pool, worker_queue)
|
||||||
|
{
|
||||||
|
m_texture_asset =
|
||||||
|
m_asset_cache->CreateAsset<TextureAsset>(m_primary_asset_id, m_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()
|
||||||
|
{
|
||||||
|
m_load_time = m_texture_asset->GetLastLoadedTime();
|
||||||
|
m_load_texture_data = m_texture_asset->GetData();
|
||||||
|
if (!m_load_texture_data)
|
||||||
|
return Resource::TaskComplete::No;
|
||||||
|
return Resource::TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource::TaskComplete TextureDataResource::ProcessData()
|
||||||
|
{
|
||||||
|
std::swap(m_current_texture_data, m_load_texture_data);
|
||||||
|
m_load_texture_data = nullptr;
|
||||||
|
m_current_time = m_load_time;
|
||||||
|
return Resource::TaskComplete::Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureDataResource::MarkAsActive()
|
||||||
|
{
|
||||||
|
m_asset_cache->MarkAssetActive(m_texture_asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureDataResource::MarkAsPending()
|
||||||
|
{
|
||||||
|
m_asset_cache->MarkAssetPending(m_texture_asset);
|
||||||
|
}
|
||||||
|
} // namespace VideoCommon
|
39
Source/Core/VideoCommon/Resources/TextureDataResource.h
Normal file
39
Source/Core/VideoCommon/Resources/TextureDataResource.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// 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:
|
||||||
|
TextureDataResource(CustomAssetLibrary::AssetID primary_asset_id,
|
||||||
|
std::shared_ptr<CustomAssetLibrary> asset_library,
|
||||||
|
CustomAssetCache* asset_cache, CustomResourceManager* resource_manager,
|
||||||
|
TexturePool* texture_pool, Common::AsyncWorkThreadSP* worker_queue);
|
||||||
|
|
||||||
|
std::shared_ptr<CustomTextureData> GetData() const;
|
||||||
|
CustomAsset::TimeType GetLoadTime() const;
|
||||||
|
|
||||||
|
void MarkAsActive() override;
|
||||||
|
void MarkAsPending() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TaskComplete CollectPrimaryData() override;
|
||||||
|
TaskComplete ProcessData() override;
|
||||||
|
|
||||||
|
TextureAsset* m_texture_asset = nullptr;
|
||||||
|
|
||||||
|
std::shared_ptr<CustomTextureData> m_load_texture_data;
|
||||||
|
CustomAsset::TimeType m_load_time = {};
|
||||||
|
|
||||||
|
std::shared_ptr<CustomTextureData> m_current_texture_data;
|
||||||
|
CustomAsset::TimeType m_current_time = {};
|
||||||
|
};
|
||||||
|
} // namespace VideoCommon
|
60
Source/Core/VideoCommon/Resources/TexturePool.cpp
Normal file
60
Source/Core/VideoCommon/Resources/TexturePool.cpp
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/Resources/TexturePool.h"
|
||||||
|
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/AbstractGfx.h"
|
||||||
|
#include "VideoCommon/AbstractTexture.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
TexturePool::TexturePool() = default;
|
||||||
|
TexturePool::~TexturePool() = default;
|
||||||
|
|
||||||
|
void TexturePool::Reset()
|
||||||
|
{
|
||||||
|
m_cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::unique_ptr<AbstractTexture>>
|
||||||
|
TexturePool::AllocateTexture(const TextureConfig& config)
|
||||||
|
{
|
||||||
|
Cache::iterator iter = FindMatchingTexture(config);
|
||||||
|
if (iter != m_cache.end())
|
||||||
|
{
|
||||||
|
auto entry = std::move(iter->second);
|
||||||
|
m_cache.erase(iter);
|
||||||
|
return std::move(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AbstractTexture> texture = g_gfx->CreateTexture(config);
|
||||||
|
if (!texture)
|
||||||
|
{
|
||||||
|
WARN_LOG_FMT(VIDEO, "Failed to allocate a {}x{}x{} texture", config.width, config.height,
|
||||||
|
config.layers);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
TexturePool::Cache::iterator TexturePool::FindMatchingTexture(const TextureConfig& config)
|
||||||
|
{
|
||||||
|
// Find a texture from the pool that does not have a frameCount of FRAMECOUNT_INVALID.
|
||||||
|
// This prevents a texture from being used twice in a single frame with different data,
|
||||||
|
// which potentially means that a driver has to maintain two copies of the texture anyway.
|
||||||
|
// Render-target textures are fine through, as they have to be generated in a seperated pass.
|
||||||
|
// As non-render-target textures are usually static, this should not matter much.
|
||||||
|
auto range = m_cache.equal_range(config);
|
||||||
|
auto matching_iter = std::find_if(range.first, range.second,
|
||||||
|
[](const auto& iter) { return iter.first.IsRenderTarget(); });
|
||||||
|
return matching_iter != range.second ? matching_iter : m_cache.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TexturePool::ReleaseTexture(std::unique_ptr<AbstractTexture> texture)
|
||||||
|
{
|
||||||
|
auto config = texture->GetConfig();
|
||||||
|
m_cache.emplace(config, std::move(texture));
|
||||||
|
}
|
||||||
|
} // namespace VideoCommon
|
33
Source/Core/VideoCommon/Resources/TexturePool.h
Normal file
33
Source/Core/VideoCommon/Resources/TexturePool.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "VideoCommon/TextureConfig.h"
|
||||||
|
|
||||||
|
class AbstractTexture;
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
class TexturePool
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TexturePool();
|
||||||
|
~TexturePool();
|
||||||
|
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
std::optional<std::unique_ptr<AbstractTexture>> AllocateTexture(const TextureConfig& config);
|
||||||
|
void ReleaseTexture(std::unique_ptr<AbstractTexture> texture);
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Cache = std::unordered_multimap<TextureConfig, std::unique_ptr<AbstractTexture>>;
|
||||||
|
Cache::iterator FindMatchingTexture(const TextureConfig& config);
|
||||||
|
|
||||||
|
Cache m_cache;
|
||||||
|
};
|
||||||
|
} // namespace VideoCommon
|
|
@ -41,7 +41,6 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "VideoCommon/AbstractGfx.h"
|
#include "VideoCommon/AbstractGfx.h"
|
||||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
|
||||||
#include "VideoCommon/AsyncRequests.h"
|
#include "VideoCommon/AsyncRequests.h"
|
||||||
#include "VideoCommon/BPStructs.h"
|
#include "VideoCommon/BPStructs.h"
|
||||||
#include "VideoCommon/BoundingBox.h"
|
#include "VideoCommon/BoundingBox.h"
|
||||||
|
@ -57,6 +56,7 @@
|
||||||
#include "VideoCommon/PixelEngine.h"
|
#include "VideoCommon/PixelEngine.h"
|
||||||
#include "VideoCommon/PixelShaderManager.h"
|
#include "VideoCommon/PixelShaderManager.h"
|
||||||
#include "VideoCommon/Present.h"
|
#include "VideoCommon/Present.h"
|
||||||
|
#include "VideoCommon/Resources/CustomResourceManager.h"
|
||||||
#include "VideoCommon/TMEM.h"
|
#include "VideoCommon/TMEM.h"
|
||||||
#include "VideoCommon/TextureCacheBase.h"
|
#include "VideoCommon/TextureCacheBase.h"
|
||||||
#include "VideoCommon/VertexLoaderManager.h"
|
#include "VideoCommon/VertexLoaderManager.h"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue