diff --git a/Source/Core/VideoCommon/Assets/AssetListener.h b/Source/Core/VideoCommon/Assets/AssetListener.h new file mode 100644 index 0000000000..3fae6fbef0 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/AssetListener.h @@ -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 diff --git a/Source/Core/VideoCommon/Assets/CustomResourceManager.cpp b/Source/Core/VideoCommon/Assets/CustomAssetCache.cpp similarity index 61% rename from Source/Core/VideoCommon/Assets/CustomResourceManager.cpp rename to Source/Core/VideoCommon/Assets/CustomAssetCache.cpp index f2af4ccbea..d833c30ab1 100644 --- a/Source/Core/VideoCommon/Assets/CustomResourceManager.cpp +++ b/Source/Core/VideoCommon/Assets/CustomAssetCache.cpp @@ -1,7 +1,7 @@ // Copyright 2025 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "VideoCommon/Assets/CustomResourceManager.h" +#include "VideoCommon/Assets/CustomAssetCache.h" #include "Common/Logging/Log.h" #include "Common/MemoryUtil.h" @@ -14,7 +14,7 @@ namespace VideoCommon { -void CustomResourceManager::Initialize() +void CustomAssetCache::Initialize() { // Use half of available system memory but leave at least 2GiB unused for system stability. constexpr size_t must_keep_unused = 2 * size_t(1024 * 1024 * 1024); @@ -28,19 +28,16 @@ void CustomResourceManager::Initialize() ERROR_LOG_FMT(VIDEO, "Not enough system memory for custom resources."); m_asset_loader.Initialize(); - - m_xfb_event = - AfterFrameEvent::Register([this](Core::System&) { XFBTriggered(); }, "CustomResourceManager"); } -void CustomResourceManager::Shutdown() +void CustomAssetCache::Shutdown() { Reset(); m_asset_loader.Shutdown(); } -void CustomResourceManager::Reset() +void CustomAssetCache::Reset() { m_asset_loader.Reset(true); @@ -48,66 +45,27 @@ void CustomResourceManager::Reset() m_pending_assets = {}; m_asset_handle_to_data.clear(); m_asset_id_to_handle.clear(); - m_texture_data_asset_cache.clear(); m_dirty_assets.clear(); m_ram_used = 0; } -void CustomResourceManager::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id) +void CustomAssetCache::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id) { std::lock_guard guard(m_dirty_mutex); m_dirty_assets.insert(asset_id); } -CustomResourceManager::TextureTimePair CustomResourceManager::GetTextureDataFromAsset( - const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr library) +void CustomAssetCache::MarkAssetPending(CustomAsset* asset) { - auto& resource = m_texture_data_asset_cache[asset_id]; - if (resource.asset_data != nullptr && - resource.asset_data->load_status == AssetData::LoadStatus::ResourceDataAvailable) - { - m_active_assets.MakeAssetHighestPriority(resource.asset->GetHandle(), resource.asset); - return {resource.texture_data, resource.asset->GetLastLoadedTime()}; - } - - // If there is an error, don't try and load again until the error is fixed - if (resource.asset_data != nullptr && resource.asset_data->has_load_error) - return {}; - - LoadTextureDataAsset(asset_id, std::move(library), &resource); - m_active_assets.MakeAssetHighestPriority(resource.asset->GetHandle(), resource.asset); - - return {}; + m_pending_assets.MakeAssetHighestPriority(asset->GetHandle(), asset); } -void CustomResourceManager::LoadTextureDataAsset( - const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr library, InternalTextureDataResource* resource) +void CustomAssetCache::MarkAssetActive(CustomAsset* asset) { - if (!resource->asset) - { - resource->asset = - CreateAsset(asset_id, AssetData::AssetType::TextureData, std::move(library)); - resource->asset_data = &m_asset_handle_to_data[resource->asset->GetHandle()]; - } - - auto texture_data = resource->asset->GetData(); - if (!texture_data || resource->asset_data->load_status == AssetData::LoadStatus::PendingReload) - { - // Tell the system we are still interested in loading this asset - const auto asset_handle = resource->asset->GetHandle(); - m_pending_assets.MakeAssetHighestPriority(asset_handle, - m_asset_handle_to_data[asset_handle].asset.get()); - } - else if (resource->asset_data->load_status == AssetData::LoadStatus::LoadFinished) - { - resource->texture_data = std::move(texture_data); - resource->asset_data->load_status = AssetData::LoadStatus::ResourceDataAvailable; - } + m_active_assets.MakeAssetHighestPriority(asset->GetHandle(), asset); } -void CustomResourceManager::XFBTriggered() +void CustomAssetCache::Update() { ProcessDirtyAssets(); ProcessLoadedAssets(); @@ -127,7 +85,7 @@ void CustomResourceManager::XFBTriggered() m_asset_loader.ScheduleAssetsToLoad(m_pending_assets.Elements(), allowed_memory); } -void CustomResourceManager::ProcessDirtyAssets() +void CustomAssetCache::ProcessDirtyAssets() { decltype(m_dirty_assets) dirty_assets; @@ -154,7 +112,7 @@ void CustomResourceManager::ProcessDirtyAssets() } } -void CustomResourceManager::ProcessLoadedAssets() +void CustomAssetCache::ProcessLoadedAssets() { const auto load_results = m_asset_loader.TakeLoadResults(); @@ -179,6 +137,8 @@ void CustomResourceManager::ProcessLoadedAssets() m_pending_assets.RemoveAsset(handle); + const bool triggered_by_reload = + asset_data.load_request_time > VideoCommon::CustomAsset::TimeType{}; asset_data.load_request_time = {}; if (!load_successful) { @@ -189,10 +149,15 @@ void CustomResourceManager::ProcessLoadedAssets() m_active_assets.InsertAsset(handle, asset_data.asset.get()); asset_data.load_status = AssetData::LoadStatus::LoadFinished; } + + for (const auto& listener : asset_data.listeners) + { + listener->AssetLoaded(!load_successful, triggered_by_reload); + } } } -void CustomResourceManager::RemoveAssetsUntilBelowMemoryLimit() +void CustomAssetCache::RemoveAssetsUntilBelowMemoryLimit() { 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()]; - // Remove the resource manager's cached entry with its asset data - if (asset_data.type == AssetData::AssetType::TextureData) + for (const auto& listener : asset_data.listeners) { - m_texture_data_asset_cache.erase(asset->GetAssetId()); + listener->AssetUnloaded(); } + // Remove the asset's copy const std::size_t bytes_unloaded = asset_data.asset->Unload(); m_ram_used -= bytes_unloaded; diff --git a/Source/Core/VideoCommon/Assets/CustomResourceManager.h b/Source/Core/VideoCommon/Assets/CustomAssetCache.h similarity index 74% rename from Source/Core/VideoCommon/Assets/CustomResourceManager.h rename to Source/Core/VideoCommon/Assets/CustomAssetCache.h index 6a2b5cbbe3..0b48affbcf 100644 --- a/Source/Core/VideoCommon/Assets/CustomResourceManager.h +++ b/Source/Core/VideoCommon/Assets/CustomAssetCache.h @@ -10,25 +10,73 @@ #include #include "Common/CommonTypes.h" -#include "Common/HookableEvent.h" +#include "VideoCommon/Assets/AssetListener.h" #include "VideoCommon/Assets/CustomAsset.h" #include "VideoCommon/Assets/CustomAssetLibrary.h" #include "VideoCommon/Assets/CustomAssetLoader.h" -#include "VideoCommon/Assets/CustomTextureData.h" namespace VideoCommon { -class TextureAsset; - -// The resource manager manages custom resources (textures, shaders, meshes) +// The asset cache manages custom resources (textures, shaders, meshes) // called assets. These assets are loaded using a priority system, // where assets requested more often gets loaded first. This system // also tracks memory usage and if memory usage goes over a calculated limit, // then assets will be purged with older assets being targeted first. -class CustomResourceManager +class CustomAssetCache { public: + // A generic interface to describe an asset + // and load state + struct AssetData + { + std::unique_ptr asset; + + std::vector listeners; + + CustomAsset::TimeType load_request_time = {}; + bool has_load_error = false; + + enum class LoadStatus + { + PendingReload, + LoadFinished, + Unloaded, + }; + LoadStatus load_status = LoadStatus::PendingReload; + }; + + template + T* CreateAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr 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(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(asset_data_from_handle.asset.get()); + } + + AssetData* GetAssetData(const CustomAssetLibrary::AssetID& asset_id) + { + const auto it_handle = m_asset_id_to_handle.find(asset_id); + if (it_handle == m_asset_id_to_handle.end()) + return nullptr; + return &m_asset_handle_to_data[it_handle->second]; + } + void Initialize(); void Shutdown(); @@ -37,81 +85,21 @@ public: // Request that an asset be reloaded void MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id); - void XFBTriggered(); + // Notify the system that we are interested in this asset and + // are waiting for it to be loaded + void MarkAssetPending(CustomAsset* asset); - using TextureTimePair = std::pair, CustomAsset::TimeType>; + // Notify the system we are interested in this asset and + // it has seen activity + void MarkAssetActive(CustomAsset* asset); - // Returns a pair with the custom texture data and the time it was last loaded - // Callees are not expected to hold onto the shared_ptr as that will prevent - // the resource manager from being able to properly release data - TextureTimePair GetTextureDataFromAsset(const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr library); + void Update(); private: - // A generic interface to describe an assets' type - // and load state - struct AssetData - { - std::unique_ptr 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 texture_data; - }; - - void LoadTextureDataAsset(const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr library, - InternalTextureDataResource* resource); - void ProcessDirtyAssets(); void ProcessLoadedAssets(); void RemoveAssetsUntilBelowMemoryLimit(); - template - T* CreateAsset(const CustomAssetLibrary::AssetID& asset_id, AssetData::AssetType asset_type, - std::shared_ptr 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(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(asset_data_from_handle.asset.get()); - } - // Maintains a priority-sorted list of assets. // Used to figure out which assets to load or unload first. // Most recently used assets get marked with highest priority. @@ -202,14 +190,10 @@ private: // A calculated amount of memory to avoid exceeding. u64 m_max_ram_available = 0; - std::map m_texture_data_asset_cache; - std::mutex m_dirty_mutex; std::set m_dirty_assets; CustomAssetLoader m_asset_loader; - - Common::EventHook m_xfb_event; }; } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp deleted file mode 100644 index 8d12c4389d..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp +++ /dev/null @@ -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 -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 -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 -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( - 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 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( - 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( - 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 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( - 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 pipeline) -{ - iterator->second.pending = false; - iterator->second.value = std::move(pipeline); -} - -void CustomShaderCache::NotifyPipelineFinished(UberPipelineIterator iterator, - std::unique_ptr 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 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( - 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 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( - this, uid, custom_shaders, list_iter); - m_async_uber_shader_compiler->QueueWorkItem(std::move(work_item), 0); -} - -std::unique_ptr -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 -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 shader) -{ - iterator->second.pending = false; - iterator->second.value = std::move(shader); -} - -void CustomShaderCache::NotifyPixelShaderFinished(UberPixelShaderIterator iterator, - std::unique_ptr shader) -{ - iterator->second.pending = false; - iterator->second.value = std::move(shader); -} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h deleted file mode 100644 index b5fc89d749..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#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 - GetPipelineAsync(const VideoCommon::GXPipelineUid& uid, - const CustomShaderInstance& custom_shaders, - const AbstractPipelineConfig& pipeline_config); - std::optional - 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 - GetPipelineForUidAsync(const VideoCommon::GXPipelineUid& uid); - -private: - // Configuration bits. - APIType m_api_type = APIType::Nothing; - ShaderHostConfig m_host_config = {}; - std::unique_ptr m_async_shader_compiler; - std::unique_ptr 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 - struct Cache - { - struct CacheHolder - { - std::unique_ptr value = nullptr; - bool pending = true; - }; - using CacheElement = std::pair; - using CacheList = std::list; - std::map 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 m_ps_cache; - Cache m_uber_ps_cache; - Cache m_pipeline_cache; - Cache m_uber_pipeline_cache; - - using PipelineIterator = Cache::CacheList::iterator; - using UberPipelineIterator = - Cache::CacheList::iterator; - using PixelShaderIterator = Cache::CacheList::iterator; - using UberPixelShaderIterator = - Cache::CacheList::iterator; - - void NotifyPipelineFinished(PipelineIterator iterator, - std::unique_ptr pipeline); - void NotifyPipelineFinished(UberPipelineIterator iterator, - std::unique_ptr pipeline); - - std::unique_ptr - CompilePixelShader(const PixelShaderUid& uid, const CustomShaderInstance& custom_shaders) const; - void NotifyPixelShaderFinished(PixelShaderIterator iterator, - std::unique_ptr shader); - std::unique_ptr - CompilePixelShader(const UberShader::PixelShaderUid& uid, - const CustomShaderInstance& custom_shaders) const; - void NotifyPixelShaderFinished(UberPixelShaderIterator iterator, - std::unique_ptr 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; -}; diff --git a/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp b/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp new file mode 100644 index 0000000000..0aa5efab41 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp @@ -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 library) +{ + const auto [it, added] = m_texture_data_resources.try_emplace(asset_id, nullptr); + if (added) + { + it->second = std::make_unique(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 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( + 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 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(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 library) +{ + const auto [it, added] = m_texture_sampler_resources.try_emplace(asset_id, nullptr); + if (added) + { + it->second = std::make_unique( + 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 library) +{ + const auto [it, added] = m_render_target_resources.try_emplace(asset_id, nullptr); + if (added) + { + it->second = std::make_unique(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 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( + 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 diff --git a/Source/Core/VideoCommon/Resources/CustomResourceManager.h b/Source/Core/VideoCommon/Resources/CustomResourceManager.h new file mode 100644 index 0000000000..971f734999 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/CustomResourceManager.h @@ -0,0 +1,88 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#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 library); + MaterialResource* GetMaterialFromAsset(const CustomAssetLibrary::AssetID& asset_id, + const GXPipelineUid& pipeline_uid, + std::shared_ptr 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 library); + RenderTargetResource* + GetRenderTargetFromAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library); + TextureAndSamplerResource* + GetTextureAndSamplerFromAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library); + + MeshResource* GetMeshFromAsset(const CustomAssetLibrary::AssetID& asset_id, + const GXPipelineUid& pipeline_uid, + std::shared_ptr 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::map m_material_resources; + + using PipelineIdToMesh = std::map>; + std::map m_mesh_resources; + + std::map> + m_render_target_resources; + + using ShaderKeyToShader = std::map>; + std::map m_shader_resources; + + std::map> + m_texture_data_resources; + + std::map> + m_texture_sampler_resources; + + ShaderHostConfig m_host_config; + + Common::EventHook m_xfb_event; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/MaterialResource.cpp b/Source/Core/VideoCommon/Resources/MaterialResource.cpp new file mode 100644 index 0000000000..f1ab047b50 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/MaterialResource.cpp @@ -0,0 +1,378 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Resources/MaterialResource.h" + +#include + +#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 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(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(); + 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& data) { + texture_like_reference.texture = data->GetTexture(); + texture_like_reference.sampler = + CalculateSamplerAnsiotropy(data->GetSampler()); + ; + }, + [&](const std::shared_ptr& 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(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{}); + } + else + { + TextureAndSamplerResource* value = nullptr; + data->m_texture_like_resources.push_back(value); + data->m_texture_like_data.push_back(std::shared_ptr{}); + } + + 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(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 diff --git a/Source/Core/VideoCommon/Resources/MaterialResource.h b/Source/Core/VideoCommon/Resources/MaterialResource.h new file mode 100644 index 0000000000..76c450db13 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/MaterialResource.h @@ -0,0 +1,97 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#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 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 GetVertexUniforms() const { return m_vertex_uniform_data; } + std::span GetPixelUniforms() const { return m_pixel_uniform_data; } + std::span GetTextures() const { return m_texture_like_references; } + MaterialResource* GetNextMaterial() const { return m_next_material; } + + private: + friend class MaterialResource; + std::unique_ptr m_pipeline = nullptr; + std::vector m_vertex_uniform_data; + std::vector m_pixel_uniform_data; + std::shared_ptr m_material_data = nullptr; + ShaderResource* m_shader_resource = nullptr; + using TextureLikeResource = std::variant; + Common::SmallVector + m_texture_like_resources; + using TextureLikeData = std::variant, + std::shared_ptr>; + Common::SmallVector + m_texture_like_data; + Common::SmallVector + 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& 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 m_current_data; + + std::shared_ptr m_load_data; + bool m_processing_load_data = false; + + RasterMaterialAsset* m_material_asset = nullptr; + GXPipelineUid m_uid; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/MeshResource.cpp b/Source/Core/VideoCommon/Resources/MeshResource.cpp new file mode 100644 index 0000000000..abf640789a --- /dev/null +++ b/Source/Core/VideoCommon/Resources/MeshResource.cpp @@ -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(&result), static_cast(&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(static_cast(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(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 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 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(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 +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(); +} + +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 diff --git a/Source/Core/VideoCommon/Resources/MeshResource.h b/Source/Core/VideoCommon/Resources/MeshResource.h new file mode 100644 index 0000000000..ed2c094214 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/MeshResource.h @@ -0,0 +1,101 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#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 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 GetVertexData() const + { + return std::span(m_data_chunk->vertex_data.get(), m_data_chunk->num_vertices); + } + std::span GetIndexData() const + { + return std::span(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 asset_library); + + CustomAssetLibrary::AssetID m_asset_id; + MaterialResource* m_material = nullptr; + const MeshDataChunk* m_data_chunk = nullptr; + std::unique_ptr m_native_vertex_format; + GXPipelineUid m_uid; + }; + + class Data + { + public: + std::span GetMeshChunks(GraphicsModSystem::DrawCallID draw_call) const; + + private: + friend class MeshResource; + void GenerateChunks(const GXPipelineUid& uid); + + std::map> m_name_to_material_id; + std::shared_ptr m_mesh_data = nullptr; + std::map> m_gpu_skinned_mesh_chunks; + std::vector m_mesh_chunks; + std::atomic_bool m_processing_finished; + }; + + const std::shared_ptr& GetData() const { return m_current_data; } + +private: + void ResetData() override; + TaskComplete CollectPrimaryData() override; + TaskComplete CollectDependencyData() override; + TaskComplete ProcessData() override; + + std::shared_ptr m_current_data; + std::shared_ptr m_load_data; + + MeshAsset* m_mesh_asset = nullptr; + + GXPipelineUid m_uid; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/RenderTargetResource.cpp b/Source/Core/VideoCommon/Resources/RenderTargetResource.cpp new file mode 100644 index 0000000000..e31f88d985 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/RenderTargetResource.cpp @@ -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 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(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::GetData() const +{ + return m_current_data; +} + +void RenderTargetResource::ResetData() +{ + m_load_data = std::make_shared(); +} + +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 diff --git a/Source/Core/VideoCommon/Resources/RenderTargetResource.h b/Source/Core/VideoCommon/Resources/RenderTargetResource.h new file mode 100644 index 0000000000..aabde0a2fe --- /dev/null +++ b/Source/Core/VideoCommon/Resources/RenderTargetResource.h @@ -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 + +#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 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 m_render_target_data; + std::unique_ptr m_texture; + TextureConfig m_config; + CustomAsset::TimeType m_load_time; + }; + + const std::shared_ptr& GetData() const; + +private: + void ResetData() override; + TaskComplete CollectPrimaryData() override; + TaskComplete ProcessData() override; + + void OnUnloadRequested() override; + + RenderTargetAsset* m_render_target_asset = nullptr; + std::shared_ptr m_current_data; + std::shared_ptr m_load_data; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/Resource.cpp b/Source/Core/VideoCommon/Resources/Resource.cpp new file mode 100644 index 0000000000..520b383155 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/Resource.cpp @@ -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 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 diff --git a/Source/Core/VideoCommon/Resources/Resource.h b/Source/Core/VideoCommon/Resources/Resource.h new file mode 100644 index 0000000000..94d8af7d56 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/Resource.h @@ -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 +#include + +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 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 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 m_references; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/ShaderResource.cpp b/Source/Core/VideoCommon/Resources/ShaderResource.cpp new file mode 100644 index 0000000000..8f18213acf --- /dev/null +++ b/Source/Core/VideoCommon/Resources/ShaderResource.cpp @@ -0,0 +1,247 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Resources/ShaderResource.h" + +#include + +#include + +#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 +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 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 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 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(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(); + 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 diff --git a/Source/Core/VideoCommon/Resources/ShaderResource.h b/Source/Core/VideoCommon/Resources/ShaderResource.h new file mode 100644 index 0000000000..d79daa7b33 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/ShaderResource.h @@ -0,0 +1,67 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 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 m_vertex_shader; + std::unique_ptr m_pixel_shader; + std::unique_ptr m_geometry_shader; + std::shared_ptr 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& 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 m_current_data; + std::shared_ptr m_load_data; + + bool m_processing_load_data = false; + + ShaderHostConfig m_shader_host_config; + GXPipelineUid m_uid; + std::string m_preprocessor_settings; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.cpp b/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.cpp new file mode 100644 index 0000000000..4e12a0c1c9 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.cpp @@ -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 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(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::GetData() const +{ + return m_current_data; +} + +void TextureAndSamplerResource::ResetData() +{ + m_load_data = std::make_shared(); +} + +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(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(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 diff --git a/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.h b/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.h new file mode 100644 index 0000000000..369154665a --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.h @@ -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 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 m_texture_and_sampler_data; + std::unique_ptr m_texture; + TextureConfig m_config; + }; + + const std::shared_ptr& 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 m_current_data; + std::shared_ptr m_load_data; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/TextureDataResource.cpp b/Source/Core/VideoCommon/Resources/TextureDataResource.cpp new file mode 100644 index 0000000000..43f2609d56 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TextureDataResource.cpp @@ -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 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(m_primary_asset_id, m_asset_library, this); +} + +std::shared_ptr 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 diff --git a/Source/Core/VideoCommon/Resources/TextureDataResource.h b/Source/Core/VideoCommon/Resources/TextureDataResource.h new file mode 100644 index 0000000000..5f5b755847 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TextureDataResource.h @@ -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 asset_library, + CustomAssetCache* asset_cache, CustomResourceManager* resource_manager, + TexturePool* texture_pool, Common::AsyncWorkThreadSP* worker_queue); + + std::shared_ptr 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 m_load_texture_data; + CustomAsset::TimeType m_load_time = {}; + + std::shared_ptr m_current_texture_data; + CustomAsset::TimeType m_current_time = {}; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/TexturePool.cpp b/Source/Core/VideoCommon/Resources/TexturePool.cpp new file mode 100644 index 0000000000..313478eab2 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TexturePool.cpp @@ -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> +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 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 texture) +{ + auto config = texture->GetConfig(); + m_cache.emplace(config, std::move(texture)); +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/TexturePool.h b/Source/Core/VideoCommon/Resources/TexturePool.h new file mode 100644 index 0000000000..f9518fd1a6 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TexturePool.h @@ -0,0 +1,33 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "VideoCommon/TextureConfig.h" + +class AbstractTexture; + +namespace VideoCommon +{ +class TexturePool +{ +public: + TexturePool(); + ~TexturePool(); + + void Reset(); + + std::optional> AllocateTexture(const TextureConfig& config); + void ReleaseTexture(std::unique_ptr texture); + +private: + using Cache = std::unordered_multimap>; + Cache::iterator FindMatchingTexture(const TextureConfig& config); + + Cache m_cache; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/VideoBackendBase.cpp b/Source/Core/VideoCommon/VideoBackendBase.cpp index 2613c3bb63..2003e4861d 100644 --- a/Source/Core/VideoCommon/VideoBackendBase.cpp +++ b/Source/Core/VideoCommon/VideoBackendBase.cpp @@ -41,7 +41,6 @@ #endif #include "VideoCommon/AbstractGfx.h" -#include "VideoCommon/Assets/CustomResourceManager.h" #include "VideoCommon/AsyncRequests.h" #include "VideoCommon/BPStructs.h" #include "VideoCommon/BoundingBox.h" @@ -57,6 +56,7 @@ #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" #include "VideoCommon/Present.h" +#include "VideoCommon/Resources/CustomResourceManager.h" #include "VideoCommon/TMEM.h" #include "VideoCommon/TextureCacheBase.h" #include "VideoCommon/VertexLoaderManager.h"