Compare commits

...

13 Commits

Author SHA1 Message Date
iwubcode
5f87382168
Merge c97a947f67 into ed2fe134aa 2025-12-15 17:14:34 -06:00
Jordan Woyak
ed2fe134aa
Merge pull request #14219 from Sintendo/stringutils-drop-tabs2spaces
StringUtils: Drop TabsToSpaces
2025-12-15 14:59:20 -06:00
Jordan Woyak
206e1bae66
Merge pull request #14220 from Anarky/master
GameSettings: Enable MMU for Vexx (PAL)
2025-12-15 14:56:11 -06:00
Anarky
bd9d865f80 GameSettings: Enable MMU for Vexx (PAL)
Without it, the game crashes after completing the tutorial level.
2025-12-15 20:05:02 +01:00
Sintendo
558cee8dcf StringUtils: Drop TabsToSpaces
This function is unused.
2025-12-15 13:41:54 +01:00
iwubcode
c97a947f67 VideoCommon: move resource state processing to the resource base class 2025-11-23 11:08:50 -06:00
iwubcode
5c00f07074 VideoCommon: update resource manager with a material/shader/and texture(+sampler) resource to show the complexities that warrant the resource manager system 2025-11-23 11:08:50 -06:00
iwubcode
23c637c029 VideoCommon: add custom includer to custom shaders, this will allow us to ship built-in custom shaders in the future 2025-11-23 11:04:51 -06:00
iwubcode
93a6cc80b4 VideoCommon: add some helper functions for resource logic that generates invalid textures for when a texture isn't provided for a custom asset 2025-11-23 11:04:24 -06:00
iwubcode
8016e2cfbd VideoCommon: move ApplyDriverBugs for the normal pipeline out into a utility function, add a way to hash the pipeline (using the vertex declaration instead of the native vertex format) 2025-11-23 11:04:24 -06:00
iwubcode
989ecca235 VideoCommon: add a texture pool for resource management 2025-11-23 11:04:24 -06:00
iwubcode
2d21a99205 VideoCommon: separate the concept of a 'resource' from an 'asset'. A resource is potentially multiple assets that are chained together but represent one type of data to the rest of the system. An example is a 'material'. A 'material' is a collection of textures, a custom shader, and some metadata that all comes together to form what the concept of the material is. There will be a 'material' resource. For now, start small by introducing the interface and change our texture loading which used assets from the old resource manager, to an actual resource. 2025-11-23 11:04:24 -06:00
iwubcode
59d9c1772a VideoCommon: rename 'IsAnisostropicEnhancementSafe' to 'IsAnisotropicEnhancementSafe' in TextureCacheBase 2025-11-23 11:04:24 -06:00
34 changed files with 2116 additions and 301 deletions

View File

@ -0,0 +1,5 @@
# GJXP51 - Vexx (PAL)
[Core]
# Prevent crash after completing the tutorial level
MMU = True

View File

@ -16,7 +16,6 @@
#include <locale>
#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
#include <fmt/format.h>
@ -365,17 +364,6 @@ std::vector<std::string> SplitString(const std::string& str, const char delim)
return output;
}
std::string TabsToSpaces(int tab_size, std::string str)
{
const std::string spaces(tab_size, ' ');
size_t i = 0;
while (str.npos != (i = str.find('\t')))
str.replace(i, 1, spaces);
return str;
}
std::string ReplaceAll(std::string result, std::string_view src, std::string_view dest)
{
size_t pos = 0;

View File

@ -180,8 +180,6 @@ std::from_chars_result FromChars(std::string_view sv, std::floating_point auto&
}
} // namespace Common
std::string TabsToSpaces(int tab_size, std::string str);
std::vector<std::string> SplitString(const std::string& str, char delim);
// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"

View File

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

View File

@ -674,10 +674,11 @@
<ClInclude Include="VideoCommon\AbstractShader.h" />
<ClInclude Include="VideoCommon\AbstractStagingTexture.h" />
<ClInclude Include="VideoCommon\AbstractTexture.h" />
<ClInclude Include="VideoCommon\Assets\AssetListener.h" />
<ClInclude Include="VideoCommon\Assets\CustomAsset.h" />
<ClInclude Include="VideoCommon\Assets\CustomAssetCache.h" />
<ClInclude Include="VideoCommon\Assets\CustomAssetLibrary.h" />
<ClInclude Include="VideoCommon\Assets\CustomAssetLoader.h" />
<ClInclude Include="VideoCommon\Assets\CustomResourceManager.h" />
<ClInclude Include="VideoCommon\Assets\CustomTextureData.h" />
<ClInclude Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.h" />
<ClInclude Include="VideoCommon\Assets\MaterialAsset.h" />
@ -747,12 +748,21 @@
<ClInclude Include="VideoCommon\PerfQueryBase.h" />
<ClInclude Include="VideoCommon\PerformanceMetrics.h" />
<ClInclude Include="VideoCommon\PerformanceTracker.h" />
<ClInclude Include="VideoCommon\PipelineUtils.h" />
<ClInclude Include="VideoCommon\PixelEngine.h" />
<ClInclude Include="VideoCommon\PixelShaderGen.h" />
<ClInclude Include="VideoCommon\PixelShaderManager.h" />
<ClInclude Include="VideoCommon\PostProcessing.h" />
<ClInclude Include="VideoCommon\Present.h" />
<ClInclude Include="VideoCommon\RenderState.h" />
<ClInclude Include="VideoCommon\Resources\CustomResourceManager.h" />
<ClInclude Include="VideoCommon\Resources\InvalidTextures.h" />
<ClInclude Include="VideoCommon\Resources\MaterialResource.h" />
<ClInclude Include="VideoCommon\Resources\Resource.h" />
<ClInclude Include="VideoCommon\Resources\ShaderResource.h" />
<ClInclude Include="VideoCommon\Resources\TextureAndSampler.h" />
<ClInclude Include="VideoCommon\Resources\TextureDataResource.h" />
<ClInclude Include="VideoCommon\Resources\TexturePool.h" />
<ClInclude Include="VideoCommon\ShaderCache.h" />
<ClInclude Include="VideoCommon\ShaderCompileUtils.h" />
<ClInclude Include="VideoCommon\ShaderGenCommon.h" />
@ -1342,8 +1352,8 @@
<ClCompile Include="VideoCommon\AbstractStagingTexture.cpp" />
<ClCompile Include="VideoCommon\AbstractTexture.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomAsset.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomAssetCache.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomAssetLoader.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomResourceManager.cpp" />
<ClCompile Include="VideoCommon\Assets\CustomTextureData.cpp" />
<ClCompile Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.cpp" />
<ClCompile Include="VideoCommon\Assets\MaterialAsset.cpp" />
@ -1398,12 +1408,21 @@
<ClCompile Include="VideoCommon\PerfQueryBase.cpp" />
<ClCompile Include="VideoCommon\PerformanceMetrics.cpp" />
<ClCompile Include="VideoCommon\PerformanceTracker.cpp" />
<ClCompile Include="VideoCommon\PipelineUtils.cpp" />
<ClCompile Include="VideoCommon\PixelEngine.cpp" />
<ClCompile Include="VideoCommon\PixelShaderGen.cpp" />
<ClCompile Include="VideoCommon\PixelShaderManager.cpp" />
<ClCompile Include="VideoCommon\PostProcessing.cpp" />
<ClCompile Include="VideoCommon\Present.cpp" />
<ClCompile Include="VideoCommon\RenderState.cpp" />
<ClCompile Include="VideoCommon\Resources\CustomResourceManager.cpp" />
<ClCompile Include="VideoCommon\Resources\InvalidTextures.cpp" />
<ClCompile Include="VideoCommon\Resources\MaterialResource.cpp" />
<ClCompile Include="VideoCommon\Resources\Resource.cpp" />
<ClCompile Include="VideoCommon\Resources\ShaderResource.cpp" />
<ClCompile Include="VideoCommon\Resources\TextureAndSamplerResource.cpp" />
<ClCompile Include="VideoCommon\Resources\TextureDataResource.cpp" />
<ClCompile Include="VideoCommon\Resources\TexturePool.cpp" />
<ClCompile Include="VideoCommon\ShaderCache.cpp" />
<ClCompile Include="VideoCommon\ShaderCompileUtils.cpp" />
<ClCompile Include="VideoCommon\ShaderGenCommon.cpp" />

View File

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

View File

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

View File

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

View File

@ -8,19 +8,20 @@
#include <fmt/std.h>
#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<VideoCommon::ShaderIncluder>(
PathToString(pixel_shader->second.parent_path()), graphics_mod_builtin);
return LoadInfo{approx_mem_size};
}

View File

@ -4,6 +4,7 @@
#pragma once
#include <array>
#include <memory>
#include <span>
#include <string>
#include <string_view>
@ -13,6 +14,7 @@
#include <picojson.h>
#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<SamplerData> samplers;
std::unique_ptr<VideoCommon::ShaderIncluder> shader_includer;
};
class RasterSurfaceShaderAsset final : public CustomLoadableAsset<RasterSurfaceShaderData>

View File

@ -8,13 +8,14 @@ add_library(videocommon
AbstractStagingTexture.h
AbstractTexture.cpp
AbstractTexture.h
Assets/AssetListener.h
Assets/CustomAsset.cpp
Assets/CustomAsset.h
Assets/CustomAssetCache.cpp
Assets/CustomAssetCache.h
Assets/CustomAssetLibrary.h
Assets/CustomAssetLoader.cpp
Assets/CustomAssetLoader.h
Assets/CustomResourceManager.cpp
Assets/CustomResourceManager.h
Assets/CustomTextureData.cpp
Assets/CustomTextureData.h
Assets/DirectFilesystemAssetLibrary.cpp
@ -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

View File

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

View File

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

View File

@ -0,0 +1,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>);
// 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<void*>(&out), static_cast<const void*>(&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<u32>(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<bool>(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<u32>(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<u32>(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<XXH64_hash_t>(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

View File

@ -0,0 +1,22 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <xxh3.h>
#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

View File

@ -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<VideoCommon::CustomAssetLibrary> library)
{
auto& resource = m_texture_data_resources[asset_id];
if (resource == nullptr)
{
resource =
std::make_unique<TextureDataResource>(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<VideoCommon::CustomAssetLibrary> library)
{
auto& resource = m_material_resources[asset_id][PipelineToHash(pipeline_uid)];
if (resource == nullptr)
{
resource = std::make_unique<MaterialResource>(
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<VideoCommon::CustomAssetLibrary> library)
{
auto& resource = m_shader_resources[asset_id][shader_key];
if (resource == nullptr)
{
resource = std::make_unique<ShaderResource>(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<VideoCommon::CustomAssetLibrary> library)
{
auto& resource = m_texture_sampler_resources[asset_id];
if (resource == nullptr)
{
resource = std::make_unique<TextureAndSamplerResource>(
CreateResourceContext(asset_id, std::move(library)));
}
resource->Process();
return resource.get();
}
Resource::ResourceContext CustomResourceManager::CreateResourceContext(
const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<VideoCommon::CustomAssetLibrary> 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

View File

@ -0,0 +1,82 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <memory>
#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<VideoCommon::CustomAssetLibrary> library);
MaterialResource* GetMaterialFromAsset(const CustomAssetLibrary::AssetID& asset_id,
const GXPipelineUid& pipeline_uid,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
ShaderResource* GetShaderFromAsset(const CustomAssetLibrary::AssetID& asset_id,
std::size_t shader_key, const GXPipelineUid& pipeline_uid,
const std::string& preprocessor_settings,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
TextureAndSamplerResource*
GetTextureAndSamplerFromAsset(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
private:
Resource::ResourceContext
CreateResourceContext(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
CustomAssetCache m_asset_cache;
TexturePool m_texture_pool;
Common::AsyncWorkThreadSP m_worker_thread;
std::unique_ptr<AsyncShaderCompiler> m_async_shader_compiler;
using PipelineIdToMaterial = std::map<std::size_t, std::unique_ptr<MaterialResource>>;
std::map<CustomAssetLibrary::AssetID, PipelineIdToMaterial> m_material_resources;
using ShaderKeyToShader = std::map<std::size_t, std::unique_ptr<ShaderResource>>;
std::map<CustomAssetLibrary::AssetID, ShaderKeyToShader> m_shader_resources;
std::map<CustomAssetLibrary::AssetID, std::unique_ptr<TextureDataResource>>
m_texture_data_resources;
std::map<CustomAssetLibrary::AssetID, std::unique_ptr<TextureAndSamplerResource>>
m_texture_sampler_resources;
ShaderHostConfig m_host_config;
Common::EventHook m_xfb_event;
std::unique_ptr<AbstractTexture> m_invalid_transparent_texture;
std::unique_ptr<AbstractTexture> m_invalid_color_texture;
std::unique_ptr<AbstractTexture> m_invalid_cubemap_texture;
std::unique_ptr<AbstractTexture> m_invalid_array_texture;
};
} // namespace VideoCommon

View File

@ -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<AbstractTexture> 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<u8, 4> pixel{0, 0, 0, 0};
texture->Load(0, 1, 1, 1, pixel.data(), pixel.size());
return texture;
}
std::unique_ptr<AbstractTexture> 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<u8, 4> pixel{255, 0, 255, 255};
texture->Load(0, 1, 1, 1, pixel.data(), pixel.size());
return texture;
}
std::unique_ptr<AbstractTexture> 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<u8, 4> 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<AbstractTexture> 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<u8, 4> pixel{255, 0, 255, 255};
texture->Load(0, 1, 1, 1, pixel.data(), pixel.size());
return texture;
}
} // namespace VideoCommon

View File

@ -0,0 +1,16 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include "VideoCommon/AbstractTexture.h"
namespace VideoCommon
{
std::unique_ptr<AbstractTexture> CreateInvalidTransparentTexture();
std::unique_ptr<AbstractTexture> CreateInvalidColorTexture();
std::unique_ptr<AbstractTexture> CreateInvalidCubemapTexture();
std::unique_ptr<AbstractTexture> CreateInvalidArrayTexture();
} // namespace VideoCommon

View File

@ -0,0 +1,385 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Resources/MaterialResource.h"
#include <xxh3.h>
#include "Common/VariantUtil.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/Assets/CustomAssetCache.h"
#include "VideoCommon/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<MaterialAsset>(
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<Data>();
m_processing_load_data = false;
}
Resource::TaskComplete MaterialResource::CollectPrimaryData()
{
const auto material_data = m_material_asset->GetData();
if (!material_data) [[unlikely]]
{
return Resource::TaskComplete::No;
}
m_load_data->m_material_data = material_data;
// A shader asset is required to function
if (m_load_data->m_material_data->shader_asset == "")
{
return Resource::TaskComplete::Error;
}
CreateTextureData(m_load_data.get());
SetShaderKey(m_load_data.get(), &m_uid);
return Resource::TaskComplete::Yes;
}
Resource::TaskComplete MaterialResource::CollectDependencyData()
{
bool loaded = true;
{
auto* const shader_resource = m_resource_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<TextureAndSamplerResource::Data>& 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<MaterialResource::Data> material_resource_data,
std::shared_ptr<ShaderResource::Data> 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<MaterialResource::Data> m_material_resource_data;
std::shared_ptr<ShaderResource::Data> 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<WorkItem>(
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<u32>(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<TextureAndSamplerResource::Data>{});
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<XXH64_hash_t>(1));
UpdateHashWithPipeline(*uid, &shader_key_hash);
XXH3_64bits_update(&shader_key_hash, data->m_preprocessor_settings.c_str(),
data->m_preprocessor_settings.size());
data->m_shader_key = XXH3_64bits_digest(&shader_key_hash);
}
void MaterialResource::WriteUniforms(Data* data)
{
// Calculate the size in memory of the buffer
std::size_t max_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<u8>(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

View File

@ -0,0 +1,99 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <memory>
#include <string_view>
#include <variant>
#include <vector>
#include "Common/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<const u8> GetUniforms() const { return m_uniform_data; }
std::span<const TextureLikeReference> GetTextures() const { return m_texture_like_references; }
MaterialResource* GetNextMaterial() const { return m_next_material; }
private:
friend class MaterialResource;
std::unique_ptr<AbstractPipeline> m_pipeline = nullptr;
Common::UniqueBuffer<u8> m_uniform_data;
std::shared_ptr<MaterialData> m_material_data = nullptr;
ShaderResource* m_shader_resource = nullptr;
using TextureLikeResource = Resource*;
Common::SmallVector<TextureLikeResource, VideoCommon::MAX_PIXEL_SHADER_SAMPLERS>
m_texture_like_resources;
// Variant for future expansion...
using TextureLikeData = std::variant<std::shared_ptr<TextureAndSamplerResource::Data>>;
Common::SmallVector<TextureLikeData, VideoCommon::MAX_PIXEL_SHADER_SAMPLERS>
m_texture_like_data;
Common::SmallVector<TextureLikeReference, VideoCommon::MAX_PIXEL_SHADER_SAMPLERS>
m_texture_like_references;
MaterialResource* m_next_material = nullptr;
std::size_t m_shader_key;
std::string m_preprocessor_settings;
std::atomic_bool m_processing_finished;
};
const std::shared_ptr<Data>& GetData() const { return m_current_data; }
void MarkAsActive() override;
void MarkAsPending() override;
private:
void ResetData() override;
Resource::TaskComplete CollectPrimaryData() override;
Resource::TaskComplete CollectDependencyData() override;
Resource::TaskComplete ProcessData() override;
static void CreateTextureData(Data* data);
static void SetShaderKey(Data* data, GXPipelineUid* uid);
static void WriteUniforms(Data* data);
std::shared_ptr<Data> m_current_data;
std::shared_ptr<Data> m_load_data;
bool m_processing_load_data = false;
// Note: asset cache owns the asset, we access as a reference
MaterialAsset* m_material_asset = nullptr;
GXPipelineUid m_uid;
std::unique_ptr<NativeVertexFormat> m_uid_vertex_format_copy;
};
} // namespace VideoCommon

View File

@ -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<int>(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

View File

@ -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 <memory>
#include <unordered_set>
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<CustomAssetLibrary> 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<Resource*> m_references;
};
} // namespace VideoCommon

View File

@ -0,0 +1,311 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Resources/ShaderResource.h"
#include <string_view>
#include <fmt/format.h>
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/Assets/CustomAssetCache.h"
#include "VideoCommon/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<AbstractShader>
CompileGeometryShader(const GeometryShaderUid& uid, APIType api_type, ShaderHostConfig host_config)
{
const ShaderCode source_code =
GenerateGeometryShaderCode(api_type, host_config, uid.GetUidData());
return g_gfx->CreateShaderFromSource(ShaderStage::Geometry, source_code.GetBuffer(), nullptr,
fmt::format("Geometry shader: {}", *uid.GetUidData()));
}
std::unique_ptr<AbstractShader> CompilePixelShader(const PixelShaderUid& uid,
std::string_view preprocessor_settings,
APIType api_type,
const ShaderHostConfig& host_config,
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<AbstractShader> 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<RasterSurfaceShaderAsset>(
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<Data>();
m_processing_load_data = false;
}
Resource::TaskComplete ShaderResource::CollectPrimaryData()
{
const auto shader_data = m_shader_asset->GetData();
if (!shader_data) [[unlikely]]
{
return Resource::TaskComplete::No;
}
m_load_data->m_shader_data = shader_data;
return Resource::TaskComplete::Yes;
}
Resource::TaskComplete ShaderResource::ProcessData()
{
class WorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
{
public:
WorkItem(std::shared_ptr<ShaderResource::Data> 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<ShaderResource::Data> 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<WorkItem>(
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

View File

@ -0,0 +1,67 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include "VideoCommon/Resources/Resource.h"
#include "VideoCommon/Assets/ShaderAsset.h"
#include "VideoCommon/GXPipelineTypes.h"
#include "VideoCommon/ShaderGenCommon.h"
namespace VideoCommon
{
class ShaderResource final : public Resource
{
public:
ShaderResource(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<AbstractShader> m_vertex_shader;
std::unique_ptr<AbstractShader> m_pixel_shader;
std::unique_ptr<AbstractShader> m_geometry_shader;
std::shared_ptr<RasterSurfaceShaderData> m_shader_data;
bool m_needs_geometry_shader = false;
std::atomic_bool m_processing_finished;
};
// Changes the shader host config. Shaders should be reloaded afterwards.
void SetHostConfig(const ShaderHostConfig& host_config);
const std::shared_ptr<Data>& GetData() const { return m_current_data; }
void MarkAsActive() override;
void MarkAsPending() override;
private:
void ResetData() override;
Resource::TaskComplete CollectPrimaryData() override;
TaskComplete ProcessData() override;
// Note: asset cache owns the asset, we access as a reference
RasterSurfaceShaderAsset* m_shader_asset = nullptr;
std::shared_ptr<Data> m_current_data;
std::shared_ptr<Data> m_load_data;
bool m_processing_load_data = false;
ShaderHostConfig m_shader_host_config;
GXPipelineUid m_uid;
std::string m_preprocessor_settings;
};
} // namespace VideoCommon

View File

@ -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<TextureAndSamplerAsset>(
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::Data>& TextureAndSamplerResource::GetData() const
{
return m_current_data;
}
void TextureAndSamplerResource::ResetData()
{
m_load_data = std::make_shared<Data>();
}
Resource::TaskComplete TextureAndSamplerResource::CollectPrimaryData()
{
m_load_data->m_texture_and_sampler_data = m_texture_and_sampler_asset->GetData();
if (!m_load_data->m_texture_and_sampler_data)
return Resource::TaskComplete::No;
auto& texture_data = m_load_data->m_texture_and_sampler_data->texture_data;
if (texture_data.m_slices.empty())
return Resource::TaskComplete::Error;
if (texture_data.m_slices[0].m_levels.empty())
return Resource::TaskComplete::Error;
const auto& first_level = texture_data.m_slices[0].m_levels[0];
auto& config = m_load_data->m_config;
config.format = first_level.format;
config.flags = 0;
config.layers = 1;
config.levels = 1;
config.type = m_load_data->m_texture_and_sampler_data->type;
config.samples = 1;
config.width = first_level.width;
config.height = first_level.height;
return Resource::TaskComplete::Yes;
}
Resource::TaskComplete TextureAndSamplerResource::ProcessData()
{
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<u32>(slice.m_levels.size()); ++level_index)
{
auto& level = slice.m_levels[level_index];
m_load_data->m_texture->Load(level_index, level.width, level.height, level.row_length,
level.data.data(), level.data.size(),
static_cast<u32>(slice_index));
}
}
std::swap(m_current_data, m_load_data);
// Release old data back to the pool
if (m_load_data)
m_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

View File

@ -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<TextureAndSamplerData> m_texture_and_sampler_data;
std::unique_ptr<AbstractTexture> m_texture;
TextureConfig m_config;
};
const std::shared_ptr<Data>& GetData() const;
private:
void ResetData() override;
TaskComplete CollectPrimaryData() override;
TaskComplete ProcessData() override;
void OnUnloadRequested() override;
// Note: asset cache owns the asset, we access as a reference
TextureAndSamplerAsset* m_texture_and_sampler_asset = nullptr;
std::shared_ptr<Data> m_current_data;
std::shared_ptr<Data> m_load_data;
};
} // namespace VideoCommon

View File

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

View File

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

View File

@ -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<AbstractTexture> 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<AbstractTexture> 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<AbstractTexture> texture)
{
auto config = texture->GetConfig();
(void)m_cache.emplace(config, std::move(texture));
}
} // namespace VideoCommon

View File

@ -0,0 +1,25 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <unordered_map>
#include "VideoCommon/AbstractTexture.h"
#include "VideoCommon/TextureConfig.h"
namespace VideoCommon
{
class TexturePool
{
public:
void Reset();
std::unique_ptr<AbstractTexture> AllocateTexture(const TextureConfig& config);
void ReleaseTexture(std::unique_ptr<AbstractTexture> texture);
private:
std::unordered_multimap<TextureConfig, std::unique_ptr<AbstractTexture>> m_cache;
};
} // namespace VideoCommon

View File

@ -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"
@ -604,141 +605,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>);
// 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<void*>(&out), static_cast<const void*>(&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<u32>(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<bool>(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<u32>(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<u32>(prim);
}
return out;
}
std::optional<AbstractPipelineConfig>
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)

View File

@ -4,6 +4,7 @@
#include "VideoCommon/TextureCacheBase.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstring>
#include <memory>
@ -37,7 +38,6 @@
#include "VideoCommon/AbstractFramebuffer.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/AbstractStagingTexture.h"
#include "VideoCommon/Assets/CustomResourceManager.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/Assets/TextureAssetUtils.h"
#include "VideoCommon/BPMemory.h"
@ -48,6 +48,7 @@
#include "VideoCommon/OpcodeDecoding.h"
#include "VideoCommon/PixelShaderManager.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/Resources/CustomResourceManager.h"
#include "VideoCommon/ShaderCache.h"
#include "VideoCommon/Statistics.h"
#include "VideoCommon/TMEM.h"
@ -266,9 +267,9 @@ bool TextureCacheBase::DidLinkedAssetsChange(const TCacheEntry& entry)
if (!entry.hires_texture)
return false;
const auto [texture_data, load_time] = entry.hires_texture->LoadTexture();
const auto* resource = entry.hires_texture->LoadTexture();
return load_time > entry.last_load_time;
return resource->GetLoadTime() > entry.last_load_time;
}
RcTcacheEntry TextureCacheBase::ApplyPaletteToEntry(RcTcacheEntry& entry, const u8* palette,
@ -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()))

View File

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