diff --git a/Source/Core/Core/System.cpp b/Source/Core/Core/System.cpp index 22681cac43..d22c058258 100644 --- a/Source/Core/Core/System.cpp +++ b/Source/Core/Core/System.cpp @@ -33,12 +33,12 @@ #include "IOS/USB/Emulated/Infinity.h" #include "IOS/USB/Emulated/Skylanders/Skylander.h" #include "IOS/USB/USBScanner.h" -#include "VideoCommon/Assets/CustomResourceManager.h" #include "VideoCommon/CommandProcessor.h" #include "VideoCommon/Fifo.h" #include "VideoCommon/GeometryShaderManager.h" #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" +#include "VideoCommon/Resources/CustomResourceManager.h" #include "VideoCommon/VertexShaderManager.h" #include "VideoCommon/XFStateManager.h" diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 17572da524..128e875ae3 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -674,10 +674,11 @@ + + - @@ -747,12 +748,21 @@ + + + + + + + + + @@ -1342,8 +1352,8 @@ + - @@ -1398,12 +1408,21 @@ + + + + + + + + + diff --git a/Source/Core/VideoCommon/Assets/AssetListener.h b/Source/Core/VideoCommon/Assets/AssetListener.h new file mode 100644 index 0000000000..d543a2bb14 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/AssetListener.h @@ -0,0 +1,23 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace VideoCommon +{ +class AssetListener +{ +public: + AssetListener() = default; + virtual ~AssetListener() = default; + + AssetListener(const AssetListener&) = default; + AssetListener(AssetListener&&) = default; + AssetListener& operator=(const AssetListener&) = default; + AssetListener& operator=(AssetListener&&) = default; + + virtual void NotifyAssetLoadSuccess() = 0; + virtual void NotifyAssetLoadFailed() = 0; + virtual void AssetUnloaded() = 0; +}; +} // namespace VideoCommon 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 6f8d3557bb..606f28d5fe 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 = - GetVideoEvents().after_frame_event.Register([this](Core::System&) { XFBTriggered(); }); } -void CustomResourceManager::Shutdown() +void CustomAssetCache::Shutdown() { Reset(); m_asset_loader.Shutdown(); } -void CustomResourceManager::Reset() +void CustomAssetCache::Reset() { m_asset_loader.Reset(true); @@ -48,66 +45,27 @@ void CustomResourceManager::Reset() m_pending_assets = {}; m_asset_handle_to_data.clear(); m_asset_id_to_handle.clear(); - m_texture_data_asset_cache.clear(); m_dirty_assets.clear(); m_ram_used = 0; } -void CustomResourceManager::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id) +void CustomAssetCache::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id) { std::lock_guard guard(m_dirty_mutex); m_dirty_assets.insert(asset_id); } -CustomResourceManager::TextureTimePair CustomResourceManager::GetTextureDataFromAsset( - const CustomAssetLibrary::AssetID& asset_id, - std::shared_ptr 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(); @@ -189,10 +147,18 @@ void CustomResourceManager::ProcessLoadedAssets() m_active_assets.InsertAsset(handle, asset_data.asset.get()); asset_data.load_status = AssetData::LoadStatus::LoadFinished; } + + for (const auto& listener : asset_data.listeners) + { + if (load_successful) + listener->NotifyAssetLoadSuccess(); + else + listener->NotifyAssetLoadFailed(); + } } } -void CustomResourceManager::RemoveAssetsUntilBelowMemoryLimit() +void CustomAssetCache::RemoveAssetsUntilBelowMemoryLimit() { const u64 threshold_ram = m_max_ram_available * 8 / 10; @@ -209,11 +175,11 @@ void CustomResourceManager::RemoveAssetsUntilBelowMemoryLimit() AssetData& asset_data = m_asset_handle_to_data[asset->GetHandle()]; - // Remove the resource manager's cached entry with its asset data - if (asset_data.type == AssetData::AssetType::TextureData) + for (const auto& listener : asset_data.listeners) { - m_texture_data_asset_cache.erase(asset->GetAssetId()); + listener->AssetUnloaded(); } + // Remove the asset's copy const std::size_t bytes_unloaded = asset_data.asset->Unload(); m_ram_used -= bytes_unloaded; diff --git a/Source/Core/VideoCommon/Assets/CustomResourceManager.h b/Source/Core/VideoCommon/Assets/CustomAssetCache.h similarity index 72% rename from Source/Core/VideoCommon/Assets/CustomResourceManager.h rename to Source/Core/VideoCommon/Assets/CustomAssetCache.h index 6a2b5cbbe3..169c0faaf8 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) -// called assets. These assets are loaded using a priority system, +// The asset cache manages custom assets (textures, shaders, meshes). +// These assets are loaded using a priority system, // where assets requested more often gets loaded first. This system // also tracks memory usage and if memory usage goes over a calculated limit, // then assets will be purged with older assets being targeted first. -class CustomResourceManager +class CustomAssetCache { public: + // A generic interface to describe an asset + // and load state + struct AssetData + { + std::unique_ptr 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(std::move(library), asset_id, it->second); + asset_data.load_request_time = {}; + asset_data.has_load_error = false; + + m_asset_handle_to_data.insert_or_assign(it->second, std::move(asset_data)); + } + + auto& asset_data_from_handle = m_asset_handle_to_data[it->second]; + asset_data_from_handle.listeners.push_back(listener); + asset_data_from_handle.load_status = AssetData::LoadStatus::PendingReload; + + return static_cast(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/Assets/DirectFilesystemAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp index 5e400f3de5..1945ca0aa7 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp @@ -8,19 +8,20 @@ #include +#include "Common/CommonPaths.h" #include "Common/FileUtil.h" #include "Common/IOFile.h" #include "Common/JsonUtil.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" #include "Core/System.h" -#include "VideoCommon/Assets/CustomResourceManager.h" #include "VideoCommon/Assets/MaterialAsset.h" #include "VideoCommon/Assets/MeshAsset.h" #include "VideoCommon/Assets/ShaderAsset.h" #include "VideoCommon/Assets/TextureAsset.h" #include "VideoCommon/Assets/TextureAssetUtils.h" #include "VideoCommon/RenderState.h" +#include "VideoCommon/Resources/CustomResourceManager.h" namespace VideoCommon { @@ -150,6 +151,12 @@ DirectFilesystemAssetLibrary::LoadRasterSurfaceShader(const AssetID& asset_id, if (!RasterSurfaceShaderData::FromJson(asset_id, root_obj, data)) return {}; + const std::string graphics_mod_builtin = + File::GetSysDirectory() + GRAPHICSMOD_DIR + "/Builtin" + "/Shaders"; + + data->shader_includer = std::make_unique( + PathToString(pixel_shader->second.parent_path()), graphics_mod_builtin); + return LoadInfo{approx_mem_size}; } diff --git a/Source/Core/VideoCommon/Assets/ShaderAsset.h b/Source/Core/VideoCommon/Assets/ShaderAsset.h index 81631fa0b3..0204f65bb3 100644 --- a/Source/Core/VideoCommon/Assets/ShaderAsset.h +++ b/Source/Core/VideoCommon/Assets/ShaderAsset.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/ShaderCompileUtils.h" #include "VideoCommon/TextureConfig.h" class ShaderCode; @@ -45,6 +47,13 @@ struct ShaderProperty struct RasterSurfaceShaderData { + RasterSurfaceShaderData() = default; + RasterSurfaceShaderData(const RasterSurfaceShaderData&) = delete; + RasterSurfaceShaderData(RasterSurfaceShaderData&&) = default; + ~RasterSurfaceShaderData() = default; + RasterSurfaceShaderData& operator=(const RasterSurfaceShaderData&) = delete; + RasterSurfaceShaderData& operator=(RasterSurfaceShaderData&&) = default; + static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, RasterSurfaceShaderData* data); static void ToJson(picojson::object& obj, const RasterSurfaceShaderData& data); @@ -65,6 +74,8 @@ struct RasterSurfaceShaderData bool operator==(const SamplerData&) const = default; }; std::vector samplers; + + std::unique_ptr shader_includer; }; class RasterSurfaceShaderAsset final : public CustomLoadableAsset diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 73b9221c4b..69a0968e4d 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -8,13 +8,14 @@ add_library(videocommon AbstractStagingTexture.h AbstractTexture.cpp AbstractTexture.h + Assets/AssetListener.h Assets/CustomAsset.cpp Assets/CustomAsset.h + Assets/CustomAssetCache.cpp + Assets/CustomAssetCache.h Assets/CustomAssetLibrary.h Assets/CustomAssetLoader.cpp Assets/CustomAssetLoader.h - Assets/CustomResourceManager.cpp - Assets/CustomResourceManager.h Assets/CustomTextureData.cpp Assets/CustomTextureData.h Assets/DirectFilesystemAssetLibrary.cpp @@ -133,6 +134,8 @@ add_library(videocommon PerformanceMetrics.h PerformanceTracker.cpp PerformanceTracker.h + PipelineUtils.cpp + PipelineUtils.h PixelEngine.cpp PixelEngine.h PixelShaderGen.cpp @@ -145,6 +148,22 @@ add_library(videocommon Present.h RenderState.cpp RenderState.h + Resources/CustomResourceManager.cpp + Resources/CustomResourceManager.h + Resources/InvalidTextures.cpp + Resources/InvalidTextures.h + Resources/MaterialResource.cpp + Resources/MaterialResource.h + Resources/Resource.cpp + Resources/Resource.h + Resources/ShaderResource.cpp + Resources/ShaderResource.h + Resources/TextureAndSamplerResource.cpp + Resources/TextureAndSamplerResource.h + Resources/TextureDataResource.cpp + Resources/TextureDataResource.h + Resources/TexturePool.cpp + Resources/TexturePool.h ShaderCache.cpp ShaderCache.h ShaderCompileUtils.cpp diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp index 8acc3aff03..12f59f5ff5 100644 --- a/Source/Core/VideoCommon/HiresTextures.cpp +++ b/Source/Core/VideoCommon/HiresTextures.cpp @@ -27,6 +27,7 @@ #include "VideoCommon/Assets/CustomAsset.h" #include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" #include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/Resources/CustomResourceManager.h" #include "VideoCommon/VideoConfig.h" constexpr std::string_view s_format_prefix{"tex1_"}; @@ -191,7 +192,7 @@ HiresTexture::HiresTexture(bool has_arbitrary_mipmaps, std::string id) { } -VideoCommon::CustomResourceManager::TextureTimePair HiresTexture::LoadTexture() const +VideoCommon::TextureDataResource* HiresTexture::LoadTexture() const { auto& system = Core::System::GetInstance(); auto& custom_resource_manager = system.GetCustomResourceManager(); diff --git a/Source/Core/VideoCommon/HiresTextures.h b/Source/Core/VideoCommon/HiresTextures.h index e99a3fc966..f577aebd78 100644 --- a/Source/Core/VideoCommon/HiresTextures.h +++ b/Source/Core/VideoCommon/HiresTextures.h @@ -8,12 +8,13 @@ #include #include -#include "Common/CommonTypes.h" -#include "VideoCommon/Assets/CustomResourceManager.h" -#include "VideoCommon/Assets/CustomTextureData.h" -#include "VideoCommon/TextureConfig.h" #include "VideoCommon/TextureInfo.h" +namespace VideoCommon +{ +class TextureDataResource; +} + enum class TextureFormat; std::set GetTextureDirectoriesWithGameId(const std::string& root_directory, @@ -30,7 +31,7 @@ public: HiresTexture(bool has_arbitrary_mipmaps, std::string id); bool HasArbitraryMipmaps() const { return m_has_arbitrary_mipmaps; } - VideoCommon::CustomResourceManager::TextureTimePair LoadTexture() const; + VideoCommon::TextureDataResource* LoadTexture() const; const std::string& GetId() const { return m_id; } private: diff --git a/Source/Core/VideoCommon/PipelineUtils.cpp b/Source/Core/VideoCommon/PipelineUtils.cpp new file mode 100644 index 0000000000..a63f7f00e8 --- /dev/null +++ b/Source/Core/VideoCommon/PipelineUtils.cpp @@ -0,0 +1,172 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/PipelineUtils.h" + +#include "Common/Assert.h" +#include "Common/Logging/Log.h" + +#include "VideoCommon/BPMemory.h" +#include "VideoCommon/ConstantManager.h" +#include "VideoCommon/DriverDetails.h" +#include "VideoCommon/GeometryShaderGen.h" +#include "VideoCommon/PixelShaderGen.h" +#include "VideoCommon/RenderState.h" +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/VertexShaderGen.h" +#include "VideoCommon/VideoConfig.h" + +namespace VideoCommon +{ +/// Edits the UID based on driver bugs and other special configurations +GXPipelineUid ApplyDriverBugs(const GXPipelineUid& in) +{ + GXPipelineUid out; + // TODO: static_assert(std::is_trivially_copyable_v); + // GXPipelineUid is not trivially copyable because RasterizationState and BlendingState aren't + // either, but we can pretend it is for now. This will be improved after PR #10848 is finished. + memcpy(static_cast(&out), static_cast(&in), sizeof(out)); // copy padding + pixel_shader_uid_data* ps = out.ps_uid.GetUidData(); + BlendingState& blend = out.blending_state; + + if (ps->ztest == EmulatedZ::ForcedEarly && !out.depth_state.update_enable) + { + // No need to force early depth test if you're not writing z + ps->ztest = EmulatedZ::Early; + } + + // If framebuffer fetch is available, we can emulate logic ops in the fragment shader + // and don't need the below blend approximation + if (blend.logic_op_enable && !g_backend_info.bSupportsLogicOp && + !g_backend_info.bSupportsFramebufferFetch) + { + if (!blend.LogicOpApproximationIsExact()) + WARN_LOG_FMT(VIDEO, + "Approximating logic op with blending, this will produce incorrect rendering."); + if (blend.LogicOpApproximationWantsShaderHelp()) + { + ps->emulate_logic_op_with_blend = true; + ps->logic_op_mode = static_cast(blend.logic_mode.Value()); + } + blend.ApproximateLogicOpWithBlending(); + } + + const bool benefits_from_ps_dual_source_off = + (!g_backend_info.bSupportsDualSourceBlend && g_backend_info.bSupportsFramebufferFetch) || + DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DUAL_SOURCE_BLENDING); + if (benefits_from_ps_dual_source_off && !blend.RequiresDualSrc()) + { + // Only use dual-source blending when required on drivers that don't support it very well. + ps->no_dual_src = true; + blend.use_dual_src = false; + } + + if (g_backend_info.bSupportsFramebufferFetch) + { + bool fbfetch_blend = false; + if ((DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DISCARD_WITH_EARLY_Z) || + !g_backend_info.bSupportsEarlyZ) && + ps->ztest == EmulatedZ::ForcedEarly) + { + ps->ztest = EmulatedZ::EarlyWithFBFetch; + fbfetch_blend |= static_cast(out.blending_state.blend_enable); + ps->no_dual_src = true; + } + fbfetch_blend |= blend.logic_op_enable && !g_backend_info.bSupportsLogicOp; + fbfetch_blend |= blend.use_dual_src && !g_backend_info.bSupportsDualSourceBlend; + if (fbfetch_blend) + { + ps->no_dual_src = true; + if (blend.logic_op_enable) + { + ps->logic_op_enable = true; + ps->logic_op_mode = static_cast(blend.logic_mode.Value()); + blend.logic_op_enable = false; + } + if (blend.blend_enable) + { + ps->blend_enable = true; + ps->blend_src_factor = blend.src_factor; + ps->blend_src_factor_alpha = blend.src_factor_alpha; + ps->blend_dst_factor = blend.dst_factor; + ps->blend_dst_factor_alpha = blend.dst_factor_alpha; + ps->blend_subtract = blend.subtract; + ps->blend_subtract_alpha = blend.subtract_alpha; + blend.blend_enable = false; + } + } + } + + // force dual src off if we can't support it + if (!g_backend_info.bSupportsDualSourceBlend) + { + ps->no_dual_src = true; + blend.use_dual_src = false; + } + + if (ps->ztest == EmulatedZ::ForcedEarly && !g_backend_info.bSupportsEarlyZ) + { + // These things should be false + ASSERT(!ps->zfreeze); + // ZCOMPLOC HACK: + // The only way to emulate alpha test + early-z is to force early-z in the shader. + // As this isn't available on all drivers and as we can't emulate this feature otherwise, + // we are only able to choose which one we want to respect more. + // Tests seem to have proven that writing depth even when the alpha test fails is more + // important that a reliable alpha test, so we just force the alpha test to always succeed. + // At least this seems to be less buggy. + ps->ztest = EmulatedZ::EarlyWithZComplocHack; + } + + if (g_ActiveConfig.UseVSForLinePointExpand() && + (out.rasterization_state.primitive == PrimitiveType::Points || + out.rasterization_state.primitive == PrimitiveType::Lines)) + { + // All primitives are expanded to triangles in the vertex shader + vertex_shader_uid_data* vs = out.vs_uid.GetUidData(); + const PortableVertexDeclaration& decl = out.vertex_format->GetVertexDeclaration(); + vs->position_has_3_elems = decl.position.components >= 3; + vs->texcoord_elem_count = 0; + for (int i = 0; i < 8; i++) + { + if (decl.texcoords[i].enable) + { + ASSERT(decl.texcoords[i].components <= 3); + vs->texcoord_elem_count |= decl.texcoords[i].components << (i * 2); + } + } + out.vertex_format = nullptr; + if (out.rasterization_state.primitive == PrimitiveType::Points) + vs->vs_expand = VSExpand::Point; + else + vs->vs_expand = VSExpand::Line; + PrimitiveType prim = g_backend_info.bSupportsPrimitiveRestart ? PrimitiveType::TriangleStrip : + PrimitiveType::Triangles; + out.rasterization_state.primitive = prim; + out.gs_uid.GetUidData()->primitive_type = static_cast(prim); + } + + return out; +} + +std::size_t PipelineToHash(const GXPipelineUid& in) +{ + XXH3_state_t pipeline_hash_state; + XXH3_INITSTATE(&pipeline_hash_state); + XXH3_64bits_reset_withSeed(&pipeline_hash_state, static_cast(1)); + UpdateHashWithPipeline(in, &pipeline_hash_state); + return XXH3_64bits_digest(&pipeline_hash_state); +} + +void UpdateHashWithPipeline(const GXPipelineUid& in, XXH3_state_t* hash_state) +{ + XXH3_64bits_update(hash_state, &in.vertex_format->GetVertexDeclaration(), + sizeof(PortableVertexDeclaration)); + XXH3_64bits_update(hash_state, &in.blending_state, sizeof(BlendingState)); + XXH3_64bits_update(hash_state, &in.depth_state, sizeof(DepthState)); + XXH3_64bits_update(hash_state, &in.rasterization_state, sizeof(RasterizationState)); + XXH3_64bits_update(hash_state, &in.gs_uid, sizeof(GeometryShaderUid)); + XXH3_64bits_update(hash_state, &in.ps_uid, sizeof(PixelShaderUid)); + XXH3_64bits_update(hash_state, &in.vs_uid, sizeof(VertexShaderUid)); +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/PipelineUtils.h b/Source/Core/VideoCommon/PipelineUtils.h new file mode 100644 index 0000000000..4a9b7b1696 --- /dev/null +++ b/Source/Core/VideoCommon/PipelineUtils.h @@ -0,0 +1,22 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/GXPipelineTypes.h" + +namespace VideoCommon +{ +GXPipelineUid ApplyDriverBugs(const GXPipelineUid& in); + +// Returns a hash of the pipeline, hashing the +// vertex declarations instead of the native vertex format +// object +std::size_t PipelineToHash(const GXPipelineUid& in); + +// Updates an existing hash with the hash of the pipeline +void UpdateHashWithPipeline(const GXPipelineUid& in, XXH3_state_t* hash_state); + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp b/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp new file mode 100644 index 0000000000..56556285d8 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp @@ -0,0 +1,162 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Resources/CustomResourceManager.h" + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/PipelineUtils.h" +#include "VideoCommon/Resources/InvalidTextures.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_async_shader_compiler = g_gfx->CreateAsyncShaderCompiler(); + + m_async_shader_compiler->StartWorkerThreads(1); // TODO expose to config + + m_xfb_event = + GetVideoEvents().after_frame_event.Register([this](Core::System&) { XFBTriggered(); }); + + m_invalid_array_texture = CreateInvalidArrayTexture(); + m_invalid_color_texture = CreateInvalidColorTexture(); + m_invalid_cubemap_texture = CreateInvalidCubemapTexture(); + m_invalid_transparent_texture = CreateInvalidTransparentTexture(); +} + +void CustomResourceManager::Shutdown() +{ + if (m_async_shader_compiler) + m_async_shader_compiler->StopWorkerThreads(); + + m_asset_cache.Shutdown(); + m_worker_thread.Shutdown(); + Reset(); +} + +void CustomResourceManager::Reset() +{ + m_material_resources.clear(); + m_shader_resources.clear(); + m_texture_data_resources.clear(); + m_texture_sampler_resources.clear(); + + m_invalid_transparent_texture.reset(); + m_invalid_color_texture.reset(); + m_invalid_cubemap_texture.reset(); + m_invalid_array_texture.reset(); + + 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(); +} + +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) +{ + auto& resource = m_texture_data_resources[asset_id]; + if (resource == nullptr) + { + resource = + std::make_unique(CreateResourceContext(asset_id, std::move(library))); + } + resource->Process(); + return resource.get(); +} + +MaterialResource* CustomResourceManager::GetMaterialFromAsset( + const CustomAssetLibrary::AssetID& asset_id, const GXPipelineUid& pipeline_uid, + std::shared_ptr library) +{ + auto& resource = m_material_resources[asset_id][PipelineToHash(pipeline_uid)]; + if (resource == nullptr) + { + resource = std::make_unique( + CreateResourceContext(asset_id, std::move(library)), pipeline_uid); + } + resource->Process(); + return resource.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) +{ + auto& resource = m_shader_resources[asset_id][shader_key]; + if (resource == nullptr) + { + resource = std::make_unique(CreateResourceContext(asset_id, std::move(library)), + pipeline_uid, preprocessor_settings, m_host_config); + } + resource->Process(); + return resource.get(); +} + +TextureAndSamplerResource* CustomResourceManager::GetTextureAndSamplerFromAsset( + const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library) +{ + auto& resource = m_texture_sampler_resources[asset_id]; + if (resource == nullptr) + { + resource = std::make_unique( + CreateResourceContext(asset_id, std::move(library))); + } + resource->Process(); + return resource.get(); +} + +Resource::ResourceContext CustomResourceManager::CreateResourceContext( + const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library) +{ + return Resource::ResourceContext{asset_id, + std::move(library), + &m_asset_cache, + this, + &m_texture_pool, + &m_worker_thread, + m_async_shader_compiler.get(), + m_invalid_array_texture.get(), + m_invalid_color_texture.get(), + m_invalid_cubemap_texture.get(), + m_invalid_transparent_texture.get()}; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/CustomResourceManager.h b/Source/Core/VideoCommon/Resources/CustomResourceManager.h new file mode 100644 index 0000000000..04eaa46942 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/CustomResourceManager.h @@ -0,0 +1,82 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/HookableEvent.h" +#include "Common/WorkQueueThread.h" + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/Assets/CustomAssetCache.h" +#include "VideoCommon/AsyncShaderCompiler.h" +#include "VideoCommon/Resources/MaterialResource.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); + TextureAndSamplerResource* + GetTextureAndSamplerFromAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library); + +private: + Resource::ResourceContext + CreateResourceContext(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library); + CustomAssetCache m_asset_cache; + TexturePool m_texture_pool; + Common::AsyncWorkThreadSP m_worker_thread; + std::unique_ptr m_async_shader_compiler; + + using PipelineIdToMaterial = std::map>; + std::map m_material_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; + + std::unique_ptr m_invalid_transparent_texture; + std::unique_ptr m_invalid_color_texture; + std::unique_ptr m_invalid_cubemap_texture; + std::unique_ptr m_invalid_array_texture; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/InvalidTextures.cpp b/Source/Core/VideoCommon/Resources/InvalidTextures.cpp new file mode 100644 index 0000000000..b27630660e --- /dev/null +++ b/Source/Core/VideoCommon/Resources/InvalidTextures.cpp @@ -0,0 +1,58 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Resources/InvalidTextures.h" + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/TextureConfig.h" + +namespace VideoCommon +{ +std::unique_ptr CreateInvalidTransparentTexture() +{ + const TextureConfig tex_config{ + 1, 1, 1, 1, 1, AbstractTextureFormat::RGBA8, 0, AbstractTextureType::Texture_2D}; + auto texture = g_gfx->CreateTexture(tex_config, "Invalid Transparent Texture"); + const std::array pixel{0, 0, 0, 0}; + texture->Load(0, 1, 1, 1, pixel.data(), pixel.size()); + return texture; +} + +std::unique_ptr CreateInvalidColorTexture() +{ + const TextureConfig tex_config{ + 1, 1, 1, 1, 1, AbstractTextureFormat::RGBA8, 0, AbstractTextureType::Texture_2D}; + auto texture = g_gfx->CreateTexture(tex_config, "Invalid Color Texture"); + const std::array pixel{255, 0, 255, 255}; + texture->Load(0, 1, 1, 1, pixel.data(), pixel.size()); + return texture; +} + +std::unique_ptr CreateInvalidCubemapTexture() +{ +#ifdef __APPLE__ + // TODO: figure out cube map oddness on Apple (specifically Metal) + return nullptr; +#else + const TextureConfig tex_config{ + 1, 1, 1, 6, 1, AbstractTextureFormat::RGBA8, 0, AbstractTextureType::Texture_CubeMap}; + auto texture = g_gfx->CreateTexture(tex_config, "Invalid Cubemap Texture"); + const std::array pixel{255, 0, 255, 255}; + for (u32 i = 0; i < tex_config.layers; i++) + { + texture->Load(0, tex_config.width, tex_config.height, 1, pixel.data(), pixel.size(), i); + } + return texture; +#endif +} + +std::unique_ptr CreateInvalidArrayTexture() +{ + const TextureConfig tex_config{ + 1, 1, 1, 1, 1, AbstractTextureFormat::RGBA8, 0, AbstractTextureType::Texture_2DArray}; + auto texture = g_gfx->CreateTexture(tex_config, "Invalid Array Texture"); + const std::array pixel{255, 0, 255, 255}; + texture->Load(0, 1, 1, 1, pixel.data(), pixel.size()); + return texture; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/InvalidTextures.h b/Source/Core/VideoCommon/Resources/InvalidTextures.h new file mode 100644 index 0000000000..532f7e0102 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/InvalidTextures.h @@ -0,0 +1,16 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/AbstractTexture.h" + +namespace VideoCommon +{ +std::unique_ptr CreateInvalidTransparentTexture(); +std::unique_ptr CreateInvalidColorTexture(); +std::unique_ptr CreateInvalidCubemapTexture(); +std::unique_ptr CreateInvalidArrayTexture(); +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/MaterialResource.cpp b/Source/Core/VideoCommon/Resources/MaterialResource.cpp new file mode 100644 index 0000000000..657647ffb9 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/MaterialResource.cpp @@ -0,0 +1,385 @@ +// 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/AsyncShaderCompiler.h" +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/PipelineUtils.h" +#include "VideoCommon/Resources/CustomResourceManager.h" +#include "VideoCommon/VideoConfig.h" + +namespace +{ +// TODO: absorb this with TextureCacheBase +bool IsAnisotropicEnhancementSafe(const SamplerState::TM0& tm0) +{ + return !(tm0.min_filter == FilterMode::Near && tm0.mag_filter == FilterMode::Near); +} + +// TODO: absorb this with TextureCacheBase +SamplerState CalculateSamplerAnisotropy(const SamplerState& initial_sampler) +{ + SamplerState state = initial_sampler; + if (g_ActiveConfig.iMaxAnisotropy != AnisotropicFilteringMode::Default && + IsAnisotropicEnhancementSafe(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(Resource::ResourceContext resource_context, + const GXPipelineUid& pipeline_uid) + : Resource(std::move(resource_context)), m_uid(pipeline_uid) +{ + m_material_asset = m_resource_context.asset_cache->CreateAsset( + m_resource_context.primary_asset_id, m_resource_context.asset_library, this); + m_uid_vertex_format_copy = + g_gfx->CreateNativeVertexFormat(m_uid.vertex_format->GetVertexDeclaration()); + m_uid.vertex_format = m_uid_vertex_format_copy.get(); +} + +void MaterialResource::ResetData() +{ + if (m_current_data) + { + m_current_data->m_shader_resource->RemoveReference(this); + for (const auto& texture_like_resource : m_current_data->m_texture_like_resources) + { + if (texture_like_resource) + texture_like_resource->RemoveReference(this); + } + 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_context.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_resource_context.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->textures.size(); i++) + { + const auto& texture_and_sampler = m_load_data->m_material_data->textures[i]; + if (texture_and_sampler.asset == "") + continue; + + const auto texture = m_resource_context.resource_manager->GetTextureAndSamplerFromAsset( + texture_and_sampler.asset, m_resource_context.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_context.resource_manager->GetMaterialFromAsset( + m_load_data->m_material_data->next_material_asset, m_uid, m_resource_context.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() +{ + auto shader_data = m_load_data->m_shader_resource->GetData(); + + if (!shader_data) [[unlikely]] + return Resource::TaskComplete::Error; + + for (std::size_t i = 0; i < m_load_data->m_texture_like_data.size(); i++) + { + auto& texture_like_reference = m_load_data->m_texture_like_references[i]; + const auto& texture_and_sampler = m_load_data->m_material_data->textures[i]; + + // If the texture doesn't exist, use one of the placeholders + if (texture_and_sampler.asset == "") + { + const auto texture_type = shader_data->GetTextureType(i); + if (texture_type == AbstractTextureType::Texture_2D) + texture_like_reference.texture = m_resource_context.invalid_color_texture; + else if (texture_type == AbstractTextureType::Texture_2DArray) + texture_like_reference.texture = m_resource_context.invalid_array_texture; + else if (texture_type == AbstractTextureType::Texture_CubeMap) + texture_like_reference.texture = m_resource_context.invalid_cubemap_texture; + + if (texture_like_reference.texture == nullptr) + { + PanicAlertFmt("Invalid texture (texture_type={}) is not found during material " + "resource processing (asset_id={})", + texture_type, m_resource_context.primary_asset_id); + } + + continue; + } + + auto& texture_like_data = m_load_data->m_texture_like_data[i]; + + std::visit(overloaded{[&](const std::shared_ptr& data) { + texture_like_reference.texture = data->GetTexture(); + texture_like_reference.sampler = CalculateSamplerAnisotropy(data->GetSampler()); + ; + }}, + texture_like_data); + } + + class WorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem + { + public: + WorkItem(std::shared_ptr material_resource_data, + std::shared_ptr shader_resource_data, + VideoCommon::GXPipelineUid* uid, FramebufferState frame_buffer_state) + : m_material_resource_data(std::move(material_resource_data)), + m_shader_resource_data(std::move(shader_resource_data)), m_uid(uid), + m_frame_buffer_state(frame_buffer_state) + { + } + + bool Compile() override + { + // Sanity check + if (!m_shader_resource_data->IsCompiled()) + { + m_material_resource_data->m_processing_finished = true; + return false; + } + + AbstractPipelineConfig config; + config.vertex_shader = m_shader_resource_data->GetVertexShader(); + config.pixel_shader = m_shader_resource_data->GetPixelShader(); + config.geometry_shader = m_shader_resource_data->GetGeometryShader(); + + const auto actual_uid = ApplyDriverBugs(*m_uid); + + if (m_material_resource_data->m_material_data->blending_state) + config.blending_state = *m_material_resource_data->m_material_data->blending_state; + else + config.blending_state = actual_uid.blending_state; + + if (m_material_resource_data->m_material_data->depth_state) + config.depth_state = *m_material_resource_data->m_material_data->depth_state; + else + config.depth_state = actual_uid.depth_state; + + config.framebuffer_state = std::move(m_frame_buffer_state); + config.framebuffer_state.additional_color_attachment_count = 0; + + config.rasterization_state = actual_uid.rasterization_state; + if (m_material_resource_data->m_material_data->cull_mode) + { + config.rasterization_state.cull_mode = + *m_material_resource_data->m_material_data->cull_mode; + } + + config.vertex_format = actual_uid.vertex_format; + config.usage = AbstractPipelineUsage::GX; + + m_material_resource_data->m_pipeline = g_gfx->CreatePipeline(config); + + if (m_material_resource_data->m_pipeline) + { + WriteUniforms(m_material_resource_data.get()); + } + m_material_resource_data->m_processing_finished = true; + return true; + } + void Retrieve() override {} + + private: + std::shared_ptr m_material_resource_data; + std::shared_ptr m_shader_resource_data; + VideoCommon::GXPipelineUid* m_uid; + FramebufferState m_frame_buffer_state; + }; + + if (!m_processing_load_data) + { + auto wi = m_resource_context.shader_compiler->CreateWorkItem( + m_load_data, std::move(shader_data), &m_uid, + g_framebuffer_manager->GetEFBFramebufferState()); + + // We don't need priority, that is already handled by the resource system + m_resource_context.shader_compiler->QueueWorkItem(std::move(wi), 0); + 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_resource_context.asset_cache->MarkAssetActive(m_material_asset); + for (const auto& texture_like_resource : m_current_data->m_texture_like_resources) + { + if (texture_like_resource) + texture_like_resource->MarkAsActive(); + } + 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_resource_context.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.textures.size()); i++) + { + const auto& texture_and_sampler = material_data.textures[i]; + data->m_texture_like_references.push_back(TextureLikeReference{}); + + TextureAndSamplerResource* value = nullptr; + data->m_texture_like_resources.push_back(value); + data->m_texture_like_data.push_back(std::shared_ptr{}); + + auto& texture_like_reference = data->m_texture_like_references[i]; + + if (texture_and_sampler.asset == "") + { + preprocessor_settings.Write("#define HAS_SAMPLER_{} 0\n", i); + + // For an invalid asset, force the sampler to use the default sampler + texture_like_reference.sampler_origin = + VideoCommon::TextureSamplerValue::SamplerOrigin::Asset; + texture_like_reference.texture_hash = ""; + } + else + { + preprocessor_settings.Write("#define HAS_SAMPLER_{} 1\n", i); + + texture_like_reference.sampler_origin = texture_and_sampler.sampler_origin; + texture_like_reference.texture_hash = texture_and_sampler.texture_hash; + } + + texture_like_reference.sampler_index = i + custom_sampler_index_offset; + texture_like_reference.texture = nullptr; + } + + 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_uniformdata_size = 0; + for (const auto& property : data->m_material_data->properties) + { + max_uniformdata_size += VideoCommon::MaterialProperty::GetMemorySize(property); + } + data->m_uniform_data = Common::UniqueBuffer(max_uniformdata_size); + + // Now write the memory + u8* uniform_data = data->m_uniform_data.data(); + for (const auto& property : data->m_material_data->properties) + { + VideoCommon::MaterialProperty::WriteToMemory(uniform_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..4842f000ea --- /dev/null +++ b/Source/Core/VideoCommon/Resources/MaterialResource.h @@ -0,0 +1,99 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common/Buffer.h" +#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/Resource.h" +#include "VideoCommon/Resources/ShaderResource.h" +#include "VideoCommon/Resources/TextureAndSamplerResource.h" + +namespace VideoCommon +{ +class MaterialResource final : public Resource +{ +public: + MaterialResource(Resource::ResourceContext resource_context, 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 GetUniforms() const { return m_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; + Common::UniqueBuffer m_uniform_data; + std::shared_ptr m_material_data = nullptr; + ShaderResource* m_shader_resource = nullptr; + + using TextureLikeResource = Resource*; + Common::SmallVector + m_texture_like_resources; + + // Variant for future expansion... + using TextureLikeData = std::variant>; + 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; + + // Note: asset cache owns the asset, we access as a reference + MaterialAsset* m_material_asset = nullptr; + + GXPipelineUid m_uid; + std::unique_ptr m_uid_vertex_format_copy; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/Resource.cpp b/Source/Core/VideoCommon/Resources/Resource.cpp new file mode 100644 index 0000000000..b4376314db --- /dev/null +++ b/Source/Core/VideoCommon/Resources/Resource.cpp @@ -0,0 +1,151 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Resources/Resource.h" + +#include "Common/Logging/Log.h" + +namespace VideoCommon +{ +Resource::Resource(ResourceContext resource_context) + : m_resource_context(std::move(resource_context)) +{ +} + +void Resource::Process() +{ + MarkAsActive(); + + if (m_data_processed == TaskComplete::Error) + return; + + ProcessCurrentTask(); +} + +void Resource::ProcessCurrentTask() +{ + Task next_task = m_current_task; + TaskComplete task_complete = TaskComplete::No; + switch (m_current_task) + { + case Task::ReloadData: + ResetData(); + task_complete = TaskComplete::Yes; + next_task = Task::CollectPrimaryData; + break; + case Task::CollectPrimaryData: + task_complete = CollectPrimaryData(); + next_task = Task::CollectDependencyData; + if (task_complete == TaskComplete::No) + MarkAsPending(); + break; + case Task::CollectDependencyData: + task_complete = CollectDependencyData(); + next_task = Task::ProcessData; + break; + case Task::ProcessData: + task_complete = ProcessData(); + next_task = Task::DataAvailable; + break; + case Task::DataAvailable: + // Early out, we're already at our end state + return; + default: + ERROR_LOG_FMT(VIDEO, "Unknown task '{}' for resource '{}'", static_cast(m_current_task), + m_resource_context.primary_asset_id); + return; + }; + + if (task_complete == Resource::TaskComplete::Yes) + { + m_current_task = next_task; + if (next_task == Resource::Task::DataAvailable) + { + m_data_processed = task_complete; + } + else + { + // No need to wait for the next resource request + // process the new task immediately + ProcessCurrentTask(); + } + } + else if (task_complete == Resource::TaskComplete::Error) + { + // If we failed our task due to an error, + // we can't service this resource request, + // wait for a reload and mark the whole + // data processing as an error + m_data_processed = task_complete; + } +} + +void Resource::NotifyAssetChanged(bool has_error) +{ + m_data_processed = has_error ? TaskComplete::Error : TaskComplete::No; + m_current_task = Task::ReloadData; + + for (Resource* reference : m_references) + { + reference->NotifyAssetChanged(has_error); + } +} + +void Resource::NotifyAssetUnloaded() +{ + OnUnloadRequested(); + + for (Resource* reference : m_references) + { + reference->NotifyAssetUnloaded(); + } +} + +void Resource::AddReference(Resource* reference) +{ + m_references.insert(reference); +} + +void Resource::RemoveReference(Resource* reference) +{ + m_references.erase(reference); +} + +void Resource::NotifyAssetLoadSuccess() +{ + NotifyAssetChanged(false); +} + +void Resource::NotifyAssetLoadFailed() +{ + NotifyAssetChanged(true); +} + +void Resource::AssetUnloaded() +{ + NotifyAssetUnloaded(); +} + +void Resource::OnUnloadRequested() +{ +} + +void Resource::ResetData() +{ +} + +Resource::TaskComplete Resource::CollectPrimaryData() +{ + return TaskComplete::Yes; +} + +Resource::TaskComplete Resource::CollectDependencyData() +{ + return TaskComplete::Yes; +} + +Resource::TaskComplete Resource::ProcessData() +{ + return TaskComplete::Yes; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/Resource.h b/Source/Core/VideoCommon/Resources/Resource.h new file mode 100644 index 0000000000..d53460004f --- /dev/null +++ b/Source/Core/VideoCommon/Resources/Resource.h @@ -0,0 +1,94 @@ +// 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 + +class AbstractTexture; +namespace VideoCommon +{ +class AsyncShaderCompiler; +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: + // Everything the resource needs to manage itself + struct ResourceContext + { + CustomAssetLibrary::AssetID primary_asset_id; + std::shared_ptr asset_library; + CustomAssetCache* asset_cache; + CustomResourceManager* resource_manager; + TexturePool* texture_pool; + Common::AsyncWorkThreadSP* worker_queue; + AsyncShaderCompiler* shader_compiler; + AbstractTexture* invalid_array_texture; + AbstractTexture* invalid_color_texture; + AbstractTexture* invalid_cubemap_texture; + AbstractTexture* invalid_transparent_texture; + }; + explicit Resource(ResourceContext resource_context); + + enum class TaskComplete + { + Yes, + No, + Error, + }; + + void Process(); + TaskComplete IsDataProcessed() const { return m_data_processed; } + + void AddReference(Resource* reference); + void RemoveReference(Resource* reference); + + virtual void MarkAsActive() = 0; + virtual void MarkAsPending() = 0; + +protected: + ResourceContext m_resource_context; + +private: + void ProcessCurrentTask(); + void NotifyAssetChanged(bool has_error); + void NotifyAssetUnloaded(); + + void NotifyAssetLoadSuccess() final; + void NotifyAssetLoadFailed() final; + void AssetUnloaded() final; + virtual void OnUnloadRequested(); + + friend class CustomResourceManager; + virtual void ResetData(); + virtual TaskComplete CollectPrimaryData(); + virtual TaskComplete CollectDependencyData(); + virtual TaskComplete ProcessData(); + + TaskComplete m_data_processed = TaskComplete::No; + + enum class Task + { + ReloadData, + CollectPrimaryData, + CollectDependencyData, + ProcessData, + DataAvailable, + }; + Task m_current_task = Task::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..e1aecd355c --- /dev/null +++ b/Source/Core/VideoCommon/Resources/ShaderResource.cpp @@ -0,0 +1,311 @@ +// 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/AsyncShaderCompiler.h" +#include "VideoCommon/GeometryShaderGen.h" +#include "VideoCommon/PipelineUtils.h" +#include "VideoCommon/PixelShaderGen.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(), nullptr, + 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, + RasterSurfaceShaderData* 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->samplers.size(); i++) + { + const auto& sampler = shader_data->samplers[i]; + std::string_view sampler_type; + switch (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, 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("#if HAS_SAMPLER_{} == 1\n", i); + shader_code.Write("#define HAS_{} 1\n", 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->pixel_source, "\r\n", "\n")); + + // Write out the uniform data + ShaderCode uniform_code; + for (const auto& property : shader_data->uniform_properties) + { + VideoCommon::ShaderProperty::WriteAsShaderCode(uniform_code, property); + } + if (!shader_data->uniform_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); + ShaderIncluder* shader_includer = + shader_data->shader_includer ? &*shader_data->shader_includer : nullptr; + return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(), shader_includer, + "Custom Pixel Shader"); +} + +std::unique_ptr CompileVertexShader(const VertexShaderUid& uid, + std::string_view preprocessor_settings, + APIType api_type, + const ShaderHostConfig& host_config, + const RasterSurfaceShaderData& 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.samplers.size(); i++) + { + const auto& sampler = shader_data.samplers[i]; + std::string_view sampler_type = ""; + switch (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, 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("#if HAS_SAMPLER_{} == 1\n", i); + shader_code.Write("#define HAS_{} 1\n", 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.vertex_source, "\r\n", "\n")); + + // Write out the uniform data + ShaderCode uniform_code; + for (const auto& property : shader_data.uniform_properties) + { + VideoCommon::ShaderProperty::WriteAsShaderCode(uniform_code, property); + } + if (!shader_data.uniform_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(), nullptr, + "Custom Vertex Shader"); +} +} // namespace +ShaderResource::ShaderResource(Resource::ResourceContext resource_context, + const GXPipelineUid& pipeline_uid, + const std::string& preprocessor_setting, + const ShaderHostConfig& shader_host_config) + : Resource(std::move(resource_context)), m_uid(pipeline_uid), + m_preprocessor_settings(preprocessor_setting), + m_shader_host_config{.bits = shader_host_config.bits} +{ + m_shader_asset = m_resource_context.asset_cache->CreateAsset( + m_resource_context.primary_asset_id, m_resource_context.asset_library, this); +} + +void ShaderResource::SetHostConfig(const ShaderHostConfig& host_config) +{ + m_shader_host_config.bits = host_config.bits; +} + +void ShaderResource::MarkAsPending() +{ + m_resource_context.asset_cache->MarkAssetPending(m_shader_asset); +} + +void ShaderResource::MarkAsActive() +{ + m_resource_context.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); +} + +AbstractTextureType ShaderResource::Data::GetTextureType(std::size_t index) +{ + // If the data doesn't exist, just pick one... + if (!m_shader_data || index >= m_shader_data->samplers.size()) [[unlikely]] + return AbstractTextureType::Texture_2D; + + return m_shader_data->samplers[index].type; +} + +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() +{ + class WorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem + { + public: + WorkItem(std::shared_ptr resource_data, VideoCommon::GXPipelineUid* uid, + u32 shader_bits, std::string_view preprocessor_settings) + : m_resource_data(std::move(resource_data)), m_uid(uid), m_shader_bits(shader_bits), + m_preprocessor_settings(preprocessor_settings) + { + } + + bool Compile() override + { + const ShaderHostConfig shader_host_config{.bits = m_shader_bits}; + auto actual_uid = ApplyDriverBugs(*m_uid); + + ClearUnusedPixelShaderUidBits(g_backend_info.api_type, shader_host_config, + &actual_uid.ps_uid); + m_resource_data->m_needs_geometry_shader = shader_host_config.backend_geometry_shaders && + !actual_uid.gs_uid.GetUidData()->IsPassthrough(); + + if (m_resource_data->m_needs_geometry_shader) + { + m_resource_data->m_geometry_shader = + CompileGeometryShader(actual_uid.gs_uid, g_backend_info.api_type, shader_host_config); + } + m_resource_data->m_pixel_shader = + CompilePixelShader(actual_uid.ps_uid, m_preprocessor_settings, g_backend_info.api_type, + shader_host_config, m_resource_data->m_shader_data.get()); + m_resource_data->m_vertex_shader = + CompileVertexShader(actual_uid.vs_uid, m_preprocessor_settings, g_backend_info.api_type, + shader_host_config, *m_resource_data->m_shader_data); + m_resource_data->m_processing_finished = true; + return true; + } + void Retrieve() override {} + + private: + std::shared_ptr m_resource_data; + VideoCommon::GXPipelineUid* m_uid; + u32 m_shader_bits; + std::string_view m_preprocessor_settings; + }; + + if (!m_processing_load_data) + { + std::string_view preprocessor_settings = m_preprocessor_settings; + auto wi = m_resource_context.shader_compiler->CreateWorkItem( + m_load_data, &m_uid, m_shader_host_config.bits, preprocessor_settings); + + // We don't need priority, that is already handled by the resource system + m_resource_context.shader_compiler->QueueWorkItem(std::move(wi), 0); + 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..0f91cd3714 --- /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(Resource::ResourceContext resource_context, const GXPipelineUid& pipeline_uid, + const std::string& preprocessor_settings, + const ShaderHostConfig& shader_host_config); + + class Data + { + public: + AbstractShader* GetVertexShader() const; + AbstractShader* GetPixelShader() const; + AbstractShader* GetGeometryShader() const; + + bool IsCompiled() const; + AbstractTextureType GetTextureType(std::size_t index); + + 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; + + // Note: asset cache owns the asset, we access as a reference + RasterSurfaceShaderAsset* 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..735a64a357 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.cpp @@ -0,0 +1,103 @@ +// 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(Resource::ResourceContext resource_context) + : Resource(std::move(resource_context)) +{ + m_texture_and_sampler_asset = m_resource_context.asset_cache->CreateAsset( + m_resource_context.primary_asset_id, m_resource_context.asset_library, this); +} + +void TextureAndSamplerResource::MarkAsActive() +{ + m_resource_context.asset_cache->MarkAssetActive(m_texture_and_sampler_asset); +} + +void TextureAndSamplerResource::MarkAsPending() +{ + m_resource_context.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() +{ + auto texture = m_resource_context.texture_pool->AllocateTexture(m_load_data->m_config); + if (!texture) [[unlikely]] + return Resource::TaskComplete::Error; + + 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_resource_context.texture_pool->ReleaseTexture(std::move(m_load_data->m_texture)); + + return Resource::TaskComplete::Yes; +} + +void TextureAndSamplerResource::OnUnloadRequested() +{ + if (!m_current_data) + return; + m_resource_context.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..3641b8eb91 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.h @@ -0,0 +1,49 @@ +// 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: + explicit TextureAndSamplerResource(Resource::ResourceContext resource_context); + 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; + + // Note: asset cache owns the asset, we access as a reference + 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..d72ae8d39d --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TextureDataResource.cpp @@ -0,0 +1,48 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Resources/TextureDataResource.h" + +#include "VideoCommon/Assets/CustomAssetCache.h" + +namespace VideoCommon +{ +TextureDataResource::TextureDataResource(Resource::ResourceContext resource_context) + : Resource(std::move(resource_context)) +{ + m_texture_asset = m_resource_context.asset_cache->CreateAsset( + m_resource_context.primary_asset_id, m_resource_context.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() +{ + const auto last_load_time = m_texture_asset->GetLastLoadedTime(); + const auto asset = m_texture_asset->GetData(); + if (!asset) + return Resource::TaskComplete::No; + + m_current_texture_data = asset; + m_current_time = last_load_time; + return Resource::TaskComplete::Yes; +} + +void TextureDataResource::MarkAsActive() +{ + m_resource_context.asset_cache->MarkAssetActive(m_texture_asset); +} + +void TextureDataResource::MarkAsPending() +{ + m_resource_context.asset_cache->MarkAssetPending(m_texture_asset); +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/TextureDataResource.h b/Source/Core/VideoCommon/Resources/TextureDataResource.h new file mode 100644 index 0000000000..edb529d407 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TextureDataResource.h @@ -0,0 +1,33 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "VideoCommon/Resources/Resource.h" + +#include "VideoCommon/Assets/CustomTextureData.h" +#include "VideoCommon/Assets/TextureAsset.h" + +namespace VideoCommon +{ +class TextureDataResource final : public Resource +{ +public: + explicit TextureDataResource(Resource::ResourceContext resource_context); + + std::shared_ptr GetData() const; + CustomAsset::TimeType GetLoadTime() const; + + void MarkAsActive() override; + void MarkAsPending() override; + +private: + TaskComplete CollectPrimaryData() override; + + // Note: asset cache owns the asset, we access as a reference + TextureAsset* m_texture_asset = nullptr; + + std::shared_ptr 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..ce76dc5b24 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TexturePool.cpp @@ -0,0 +1,43 @@ +// 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 +{ +void TexturePool::Reset() +{ + m_cache.clear(); +} + +std::unique_ptr TexturePool::AllocateTexture(const TextureConfig& config) +{ + const auto iter = m_cache.find(config); + if (iter != m_cache.end()) + { + auto entry = std::move(iter->second); + m_cache.erase(iter); + return entry; + } + + std::unique_ptr texture = g_gfx->CreateTexture(config); + if (!texture) + { + ERROR_LOG_FMT(VIDEO, "Failed to allocate a {}x{}x{} texture", config.width, config.height, + config.layers); + return {}; + } + return texture; +} + +void TexturePool::ReleaseTexture(std::unique_ptr texture) +{ + auto config = texture->GetConfig(); + (void)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..6040a3ae76 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TexturePool.h @@ -0,0 +1,25 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/TextureConfig.h" + +namespace VideoCommon +{ +class TexturePool +{ +public: + void Reset(); + + std::unique_ptr AllocateTexture(const TextureConfig& config); + void ReleaseTexture(std::unique_ptr texture); + +private: + std::unordered_multimap> m_cache; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index 03dc21c937..22a5437046 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -15,6 +15,7 @@ #include "VideoCommon/DriverDetails.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/FramebufferShaderGen.h" +#include "VideoCommon/PipelineUtils.h" #include "VideoCommon/Present.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/VertexLoaderManager.h" @@ -607,141 +608,10 @@ AbstractPipelineConfig ShaderCache::GetGXPipelineConfig( return config; } -/// Edits the UID based on driver bugs and other special configurations -static GXPipelineUid ApplyDriverBugs(const GXPipelineUid& in) -{ - GXPipelineUid out; - // TODO: static_assert(std::is_trivially_copyable_v); - // GXPipelineUid is not trivially copyable because RasterizationState and BlendingState aren't - // either, but we can pretend it is for now. This will be improved after PR #10848 is finished. - memcpy(static_cast(&out), static_cast(&in), sizeof(out)); // copy padding - pixel_shader_uid_data* ps = out.ps_uid.GetUidData(); - BlendingState& blend = out.blending_state; - - if (ps->ztest == EmulatedZ::ForcedEarly && !out.depth_state.update_enable) - { - // No need to force early depth test if you're not writing z - ps->ztest = EmulatedZ::Early; - } - - // If framebuffer fetch is available, we can emulate logic ops in the fragment shader - // and don't need the below blend approximation - if (blend.logic_op_enable && !g_backend_info.bSupportsLogicOp && - !g_backend_info.bSupportsFramebufferFetch) - { - if (!blend.LogicOpApproximationIsExact()) - WARN_LOG_FMT(VIDEO, - "Approximating logic op with blending, this will produce incorrect rendering."); - if (blend.LogicOpApproximationWantsShaderHelp()) - { - ps->emulate_logic_op_with_blend = true; - ps->logic_op_mode = static_cast(blend.logic_mode.Value()); - } - blend.ApproximateLogicOpWithBlending(); - } - - const bool benefits_from_ps_dual_source_off = - (!g_backend_info.bSupportsDualSourceBlend && g_backend_info.bSupportsFramebufferFetch) || - DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DUAL_SOURCE_BLENDING); - if (benefits_from_ps_dual_source_off && !blend.RequiresDualSrc()) - { - // Only use dual-source blending when required on drivers that don't support it very well. - ps->no_dual_src = true; - blend.use_dual_src = false; - } - - if (g_backend_info.bSupportsFramebufferFetch) - { - bool fbfetch_blend = false; - if ((DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DISCARD_WITH_EARLY_Z) || - !g_backend_info.bSupportsEarlyZ) && - ps->ztest == EmulatedZ::ForcedEarly) - { - ps->ztest = EmulatedZ::EarlyWithFBFetch; - fbfetch_blend |= static_cast(out.blending_state.blend_enable); - ps->no_dual_src = true; - } - fbfetch_blend |= blend.logic_op_enable && !g_backend_info.bSupportsLogicOp; - fbfetch_blend |= blend.use_dual_src && !g_backend_info.bSupportsDualSourceBlend; - if (fbfetch_blend) - { - ps->no_dual_src = true; - if (blend.logic_op_enable) - { - ps->logic_op_enable = true; - ps->logic_op_mode = static_cast(blend.logic_mode.Value()); - blend.logic_op_enable = false; - } - if (blend.blend_enable) - { - ps->blend_enable = true; - ps->blend_src_factor = blend.src_factor; - ps->blend_src_factor_alpha = blend.src_factor_alpha; - ps->blend_dst_factor = blend.dst_factor; - ps->blend_dst_factor_alpha = blend.dst_factor_alpha; - ps->blend_subtract = blend.subtract; - ps->blend_subtract_alpha = blend.subtract_alpha; - blend.blend_enable = false; - } - } - } - - // force dual src off if we can't support it - if (!g_backend_info.bSupportsDualSourceBlend) - { - ps->no_dual_src = true; - blend.use_dual_src = false; - } - - if (ps->ztest == EmulatedZ::ForcedEarly && !g_backend_info.bSupportsEarlyZ) - { - // These things should be false - ASSERT(!ps->zfreeze); - // ZCOMPLOC HACK: - // The only way to emulate alpha test + early-z is to force early-z in the shader. - // As this isn't available on all drivers and as we can't emulate this feature otherwise, - // we are only able to choose which one we want to respect more. - // Tests seem to have proven that writing depth even when the alpha test fails is more - // important that a reliable alpha test, so we just force the alpha test to always succeed. - // At least this seems to be less buggy. - ps->ztest = EmulatedZ::EarlyWithZComplocHack; - } - - if (g_ActiveConfig.UseVSForLinePointExpand() && - (out.rasterization_state.primitive == PrimitiveType::Points || - out.rasterization_state.primitive == PrimitiveType::Lines)) - { - // All primitives are expanded to triangles in the vertex shader - vertex_shader_uid_data* vs = out.vs_uid.GetUidData(); - const PortableVertexDeclaration& decl = out.vertex_format->GetVertexDeclaration(); - vs->position_has_3_elems = decl.position.components >= 3; - vs->texcoord_elem_count = 0; - for (int i = 0; i < 8; i++) - { - if (decl.texcoords[i].enable) - { - ASSERT(decl.texcoords[i].components <= 3); - vs->texcoord_elem_count |= decl.texcoords[i].components << (i * 2); - } - } - out.vertex_format = nullptr; - if (out.rasterization_state.primitive == PrimitiveType::Points) - vs->vs_expand = VSExpand::Point; - else - vs->vs_expand = VSExpand::Line; - PrimitiveType prim = g_backend_info.bSupportsPrimitiveRestart ? PrimitiveType::TriangleStrip : - PrimitiveType::Triangles; - out.rasterization_state.primitive = prim; - out.gs_uid.GetUidData()->primitive_type = static_cast(prim); - } - - return out; -} - std::optional ShaderCache::GetGXPipelineConfig(const GXPipelineUid& config_in) { - GXPipelineUid config = ApplyDriverBugs(config_in); + GXPipelineUid config = VideoCommon::ApplyDriverBugs(config_in); const AbstractShader* vs; auto vs_iter = m_vs_cache.shader_map.find(config.vs_uid); if (vs_iter != m_vs_cache.shader_map.end() && !vs_iter->second.pending) diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index f057c4f23d..6ae8b4f96f 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -4,6 +4,7 @@ #include "VideoCommon/TextureCacheBase.h" #include +#include #include #include #include @@ -37,7 +38,6 @@ #include "VideoCommon/AbstractFramebuffer.h" #include "VideoCommon/AbstractGfx.h" #include "VideoCommon/AbstractStagingTexture.h" -#include "VideoCommon/Assets/CustomResourceManager.h" #include "VideoCommon/Assets/CustomTextureData.h" #include "VideoCommon/Assets/TextureAssetUtils.h" #include "VideoCommon/BPMemory.h" @@ -48,6 +48,7 @@ #include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PixelShaderManager.h" #include "VideoCommon/Present.h" +#include "VideoCommon/Resources/CustomResourceManager.h" #include "VideoCommon/ShaderCache.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TMEM.h" @@ -266,9 +267,9 @@ bool TextureCacheBase::DidLinkedAssetsChange(const TCacheEntry& entry) if (!entry.hires_texture) return false; - const auto [texture_data, load_time] = entry.hires_texture->LoadTexture(); + const auto* resource = entry.hires_texture->LoadTexture(); - return load_time > entry.last_load_time; + return resource->GetLoadTime() > entry.last_load_time; } RcTcacheEntry TextureCacheBase::ApplyPaletteToEntry(RcTcacheEntry& entry, const u8* palette, @@ -994,7 +995,7 @@ RcTcacheEntry TextureCacheBase::DoPartialTextureUpdates(RcTcacheEntry& entry_to_ // then applying anisotropic filtering is equivalent to forced filtering. Point // mode textures are usually some sort of 2D UI billboard which will end up // misaligned from the correct pixels when filtered anisotropically. -static bool IsAnisostropicEnhancementSafe(const TexMode0& tm0) +static bool IsAnisotropicEnhancementSafe(const TexMode0& tm0) { return !(tm0.min_filter == FilterMode::Near && tm0.mag_filter == FilterMode::Near); } @@ -1028,7 +1029,7 @@ SamplerState TextureCacheBase::GetSamplerState(u32 index, float custom_tex_scale // Anisotropic filtering option. if (g_ActiveConfig.iMaxAnisotropy != AnisotropicFilteringMode::Default && - IsAnisostropicEnhancementSafe(tm0)) + IsAnisotropicEnhancementSafe(tm0)) { state.tm0.anisotropic_filtering = Common::ToUnderlying(g_ActiveConfig.iMaxAnisotropy); } @@ -1569,7 +1570,9 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp if (hires_texture) { has_arbitrary_mipmaps = hires_texture->HasArbitraryMipmaps(); - std::tie(custom_texture_data, load_time) = hires_texture->LoadTexture(); + const auto resource = hires_texture->LoadTexture(); + load_time = resource->GetLoadTime(); + custom_texture_data = resource->GetData(); if (custom_texture_data && !VideoCommon::ValidateTextureData( hires_texture->GetId(), *custom_texture_data, texture_info.GetRawWidth(), texture_info.GetRawHeight())) diff --git a/Source/Core/VideoCommon/VideoBackendBase.cpp b/Source/Core/VideoCommon/VideoBackendBase.cpp index 23d9d6e7f4..7eeefb7187 100644 --- a/Source/Core/VideoCommon/VideoBackendBase.cpp +++ b/Source/Core/VideoCommon/VideoBackendBase.cpp @@ -43,7 +43,6 @@ #endif #include "VideoCommon/AbstractGfx.h" -#include "VideoCommon/Assets/CustomResourceManager.h" #include "VideoCommon/AsyncRequests.h" #include "VideoCommon/BPStructs.h" #include "VideoCommon/BoundingBox.h" @@ -59,6 +58,7 @@ #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" #include "VideoCommon/Present.h" +#include "VideoCommon/Resources/CustomResourceManager.h" #include "VideoCommon/TMEM.h" #include "VideoCommon/TextureCacheBase.h" #include "VideoCommon/VertexLoaderManager.h"