VideoCommon: update resource manager with a material/shader/and texture(+sampler) resource to show the complexities that warrant the resource manager system

This commit is contained in:
iwubcode
2025-11-02 15:14:14 -06:00
parent 23c637c029
commit 5c00f07074
11 changed files with 1199 additions and 15 deletions

View File

@@ -756,7 +756,10 @@
<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" />
@@ -1412,7 +1415,10 @@
<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" />

View File

@@ -152,8 +152,14 @@ add_library(videocommon
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

View File

@@ -3,6 +3,11 @@
#include "VideoCommon/Resources/CustomResourceManager.h"
#include "Common/Logging/Log.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/PipelineUtils.h"
#include "VideoCommon/Resources/InvalidTextures.h"
#include "VideoCommon/VideoEvents.h"
namespace VideoCommon
@@ -10,22 +15,46 @@ 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 =
AfterFrameEvent::Register([this](Core::System&) { XFBTriggered(); }, "CustomResourceManager");
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)
@@ -38,24 +67,99 @@ 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)
{
const auto [it, added] = m_texture_data_resources.try_emplace(asset_id, nullptr);
if (added)
auto& resource = m_texture_data_resources[asset_id];
if (resource == nullptr)
{
it->second = std::make_unique<TextureDataResource>(CreateResourceContext(asset_id, library));
resource =
std::make_unique<TextureDataResource>(CreateResourceContext(asset_id, std::move(library)));
}
ProcessResource(it->second.get());
return it->second.get();
ProcessResource(resource.get());
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);
}
ProcessResource(resource.get());
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);
}
ProcessResource(resource.get());
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)));
}
ProcessResource(resource.get());
return resource.get();
}
Resource::ResourceContext CustomResourceManager::CreateResourceContext(
const CustomAssetLibrary::AssetID& asset_id,
const std::shared_ptr<VideoCommon::CustomAssetLibrary>& library)
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
{
return Resource::ResourceContext{asset_id, library, &m_asset_cache, this};
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()};
}
void CustomResourceManager::ProcessResource(Resource* resource)
@@ -71,18 +175,15 @@ void CustomResourceManager::ProcessResource(Resource* resource)
return;
}
// Early out if we're already at our end state
if (resource->GetState() == Resource::State::DataAvailable)
return;
ProcessResourceState(resource);
}
void CustomResourceManager::ProcessResourceState(Resource* resource)
{
Resource::State next_state = resource->GetState();
const auto current_state = resource->GetState();
Resource::State next_state = current_state;
Resource::TaskComplete task_complete = Resource::TaskComplete::No;
switch (resource->GetState())
switch (current_state)
{
case Resource::State::ReloadData:
resource->ResetData();
@@ -102,6 +203,14 @@ void CustomResourceManager::ProcessResourceState(Resource* resource)
case Resource::State::ProcessingData:
task_complete = resource->ProcessData();
next_state = Resource::State::DataAvailable;
break;
case Resource::State::DataAvailable:
// Early out, we're already at our end state
return;
default:
ERROR_LOG_FMT(VIDEO, "Unknown resource state '{}' for resource '{}'",
static_cast<int>(current_state), resource->m_resource_context.primary_asset_id);
return;
};
if (task_complete == Resource::TaskComplete::Yes)

View File

@@ -7,9 +7,16 @@
#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
{
@@ -25,22 +32,53 @@ public:
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,
const std::shared_ptr<VideoCommon::CustomAssetLibrary>& library);
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
void ProcessResource(Resource* resource);
void ProcessResourceState(Resource* resource);
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,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

@@ -3,6 +3,8 @@
#pragma once
#include "Common/WorkQueueThread.h"
#include "VideoCommon/Assets/AssetListener.h"
#include "VideoCommon/Assets/CustomAssetLibrary.h"
@@ -12,8 +14,10 @@
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),
@@ -28,6 +32,13 @@ public:
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);

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