diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 9737ceeac8..8d59805dee 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -756,7 +756,10 @@ + + + @@ -1412,7 +1415,10 @@ + + + diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index d3b68ba95c..69a0968e4d 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -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 diff --git a/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp b/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp index 35eeb0393a..8148ccd899 100644 --- a/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp +++ b/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp @@ -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 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(CreateResourceContext(asset_id, library)); + resource = + std::make_unique(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 library) +{ + auto& resource = m_material_resources[asset_id][PipelineToHash(pipeline_uid)]; + if (resource == nullptr) + { + resource = std::make_unique( + CreateResourceContext(asset_id, std::move(library)), pipeline_uid); + } + 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 library) +{ + auto& resource = m_shader_resources[asset_id][shader_key]; + if (resource == nullptr) + { + resource = std::make_unique(CreateResourceContext(asset_id, std::move(library)), + pipeline_uid, preprocessor_settings, m_host_config); + } + ProcessResource(resource.get()); + return resource.get(); +} + +TextureAndSamplerResource* CustomResourceManager::GetTextureAndSamplerFromAsset( + const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library) +{ + auto& resource = m_texture_sampler_resources[asset_id]; + if (resource == nullptr) + { + resource = std::make_unique( + CreateResourceContext(asset_id, std::move(library))); + } + ProcessResource(resource.get()); + return resource.get(); } Resource::ResourceContext CustomResourceManager::CreateResourceContext( const CustomAssetLibrary::AssetID& asset_id, - const std::shared_ptr& library) + std::shared_ptr 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(current_state), resource->m_resource_context.primary_asset_id); + return; }; if (task_complete == Resource::TaskComplete::Yes) diff --git a/Source/Core/VideoCommon/Resources/CustomResourceManager.h b/Source/Core/VideoCommon/Resources/CustomResourceManager.h index 512bd77712..538bec13f1 100644 --- a/Source/Core/VideoCommon/Resources/CustomResourceManager.h +++ b/Source/Core/VideoCommon/Resources/CustomResourceManager.h @@ -7,9 +7,16 @@ #include #include "Common/HookableEvent.h" +#include "Common/WorkQueueThread.h" +#include "VideoCommon/AbstractTexture.h" #include "VideoCommon/Assets/CustomAssetCache.h" +#include "VideoCommon/AsyncShaderCompiler.h" +#include "VideoCommon/Resources/MaterialResource.h" +#include "VideoCommon/Resources/ShaderResource.h" +#include "VideoCommon/Resources/TextureAndSamplerResource.h" #include "VideoCommon/Resources/TextureDataResource.h" +#include "VideoCommon/Resources/TexturePool.h" namespace VideoCommon { @@ -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 library); + MaterialResource* GetMaterialFromAsset(const CustomAssetLibrary::AssetID& asset_id, + const GXPipelineUid& pipeline_uid, + std::shared_ptr library); + + ShaderResource* GetShaderFromAsset(const CustomAssetLibrary::AssetID& asset_id, + std::size_t shader_key, const GXPipelineUid& pipeline_uid, + const std::string& preprocessor_settings, + std::shared_ptr library); + TextureAndSamplerResource* + GetTextureAndSamplerFromAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library); private: Resource::ResourceContext CreateResourceContext(const CustomAssetLibrary::AssetID& asset_id, - const std::shared_ptr& library); + std::shared_ptr 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 m_async_shader_compiler; + + using PipelineIdToMaterial = std::map>; + std::map m_material_resources; + + using ShaderKeyToShader = std::map>; + std::map m_shader_resources; std::map> m_texture_data_resources; + std::map> + m_texture_sampler_resources; + + ShaderHostConfig m_host_config; + Common::EventHook m_xfb_event; + + std::unique_ptr m_invalid_transparent_texture; + std::unique_ptr m_invalid_color_texture; + std::unique_ptr m_invalid_cubemap_texture; + std::unique_ptr m_invalid_array_texture; }; } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/MaterialResource.cpp b/Source/Core/VideoCommon/Resources/MaterialResource.cpp new file mode 100644 index 0000000000..657647ffb9 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/MaterialResource.cpp @@ -0,0 +1,385 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Resources/MaterialResource.h" + +#include + +#include "Common/VariantUtil.h" + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/Assets/CustomAssetCache.h" +#include "VideoCommon/AsyncShaderCompiler.h" +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/PipelineUtils.h" +#include "VideoCommon/Resources/CustomResourceManager.h" +#include "VideoCommon/VideoConfig.h" + +namespace +{ +// TODO: absorb this with TextureCacheBase +bool IsAnisotropicEnhancementSafe(const SamplerState::TM0& tm0) +{ + return !(tm0.min_filter == FilterMode::Near && tm0.mag_filter == FilterMode::Near); +} + +// TODO: absorb this with TextureCacheBase +SamplerState CalculateSamplerAnisotropy(const SamplerState& initial_sampler) +{ + SamplerState state = initial_sampler; + if (g_ActiveConfig.iMaxAnisotropy != AnisotropicFilteringMode::Default && + IsAnisotropicEnhancementSafe(state.tm0)) + { + state.tm0.anisotropic_filtering = Common::ToUnderlying(g_ActiveConfig.iMaxAnisotropy); + } + + if (state.tm0.anisotropic_filtering != 0) + { + // https://www.opengl.org/registry/specs/EXT/texture_filter_anisotropic.txt + // For predictable results on all hardware/drivers, only use one of: + // GL_LINEAR + GL_LINEAR (No Mipmaps [Bilinear]) + // GL_LINEAR + GL_LINEAR_MIPMAP_LINEAR (w/ Mipmaps [Trilinear]) + // Letting the game set other combinations will have varying arbitrary results; + // possibly being interpreted as equal to bilinear/trilinear, implicitly + // disabling anisotropy, or changing the anisotropic algorithm employed. + state.tm0.min_filter = FilterMode::Linear; + state.tm0.mag_filter = FilterMode::Linear; + state.tm0.mipmap_filter = FilterMode::Linear; + } + return state; +} +} // namespace + +namespace VideoCommon +{ +MaterialResource::MaterialResource(Resource::ResourceContext resource_context, + const GXPipelineUid& pipeline_uid) + : Resource(std::move(resource_context)), m_uid(pipeline_uid) +{ + m_material_asset = m_resource_context.asset_cache->CreateAsset( + m_resource_context.primary_asset_id, m_resource_context.asset_library, this); + m_uid_vertex_format_copy = + g_gfx->CreateNativeVertexFormat(m_uid.vertex_format->GetVertexDeclaration()); + m_uid.vertex_format = m_uid_vertex_format_copy.get(); +} + +void MaterialResource::ResetData() +{ + if (m_current_data) + { + m_current_data->m_shader_resource->RemoveReference(this); + for (const auto& texture_like_resource : m_current_data->m_texture_like_resources) + { + if (texture_like_resource) + texture_like_resource->RemoveReference(this); + } + if (m_current_data->m_next_material) + m_current_data->m_next_material->RemoveReference(this); + } + m_load_data = std::make_shared(); + m_processing_load_data = false; +} + +Resource::TaskComplete MaterialResource::CollectPrimaryData() +{ + const auto material_data = m_material_asset->GetData(); + if (!material_data) [[unlikely]] + { + return Resource::TaskComplete::No; + } + m_load_data->m_material_data = material_data; + + // A shader asset is required to function + if (m_load_data->m_material_data->shader_asset == "") + { + return Resource::TaskComplete::Error; + } + + CreateTextureData(m_load_data.get()); + SetShaderKey(m_load_data.get(), &m_uid); + + return Resource::TaskComplete::Yes; +} + +Resource::TaskComplete MaterialResource::CollectDependencyData() +{ + bool loaded = true; + { + auto* const shader_resource = m_resource_context.resource_manager->GetShaderFromAsset( + m_load_data->m_material_data->shader_asset, m_load_data->m_shader_key, m_uid, + m_load_data->m_preprocessor_settings, m_resource_context.asset_library); + shader_resource->AddReference(this); + m_load_data->m_shader_resource = shader_resource; + const auto data_processed = shader_resource->IsDataProcessed(); + if (data_processed == TaskComplete::Error) + return TaskComplete::Error; + + loaded &= data_processed == TaskComplete::Yes; + } + + for (std::size_t i = 0; i < m_load_data->m_material_data->textures.size(); i++) + { + const auto& texture_and_sampler = m_load_data->m_material_data->textures[i]; + if (texture_and_sampler.asset == "") + continue; + + const auto texture = m_resource_context.resource_manager->GetTextureAndSamplerFromAsset( + texture_and_sampler.asset, m_resource_context.asset_library); + m_load_data->m_texture_like_resources[i] = texture; + m_load_data->m_texture_like_data[i] = texture->GetData(); + texture->AddReference(this); + + const auto data_processed = texture->IsDataProcessed(); + if (data_processed == TaskComplete::Error) + return TaskComplete::Error; + + loaded &= data_processed == TaskComplete::Yes; + } + + if (m_load_data->m_material_data->next_material_asset != "") + { + m_load_data->m_next_material = m_resource_context.resource_manager->GetMaterialFromAsset( + m_load_data->m_material_data->next_material_asset, m_uid, m_resource_context.asset_library); + m_load_data->m_next_material->AddReference(this); + const auto data_processed = m_load_data->m_next_material->IsDataProcessed(); + if (data_processed == TaskComplete::Error) + return TaskComplete::Error; + + loaded &= data_processed == TaskComplete::Yes; + } + + return loaded ? TaskComplete::Yes : TaskComplete::No; +} + +Resource::TaskComplete MaterialResource::ProcessData() +{ + auto shader_data = m_load_data->m_shader_resource->GetData(); + + if (!shader_data) [[unlikely]] + return Resource::TaskComplete::Error; + + for (std::size_t i = 0; i < m_load_data->m_texture_like_data.size(); i++) + { + auto& texture_like_reference = m_load_data->m_texture_like_references[i]; + const auto& texture_and_sampler = m_load_data->m_material_data->textures[i]; + + // If the texture doesn't exist, use one of the placeholders + if (texture_and_sampler.asset == "") + { + const auto texture_type = shader_data->GetTextureType(i); + if (texture_type == AbstractTextureType::Texture_2D) + texture_like_reference.texture = m_resource_context.invalid_color_texture; + else if (texture_type == AbstractTextureType::Texture_2DArray) + texture_like_reference.texture = m_resource_context.invalid_array_texture; + else if (texture_type == AbstractTextureType::Texture_CubeMap) + texture_like_reference.texture = m_resource_context.invalid_cubemap_texture; + + if (texture_like_reference.texture == nullptr) + { + PanicAlertFmt("Invalid texture (texture_type={}) is not found during material " + "resource processing (asset_id={})", + texture_type, m_resource_context.primary_asset_id); + } + + continue; + } + + auto& texture_like_data = m_load_data->m_texture_like_data[i]; + + std::visit(overloaded{[&](const std::shared_ptr& data) { + texture_like_reference.texture = data->GetTexture(); + texture_like_reference.sampler = CalculateSamplerAnisotropy(data->GetSampler()); + ; + }}, + texture_like_data); + } + + class WorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem + { + public: + WorkItem(std::shared_ptr material_resource_data, + std::shared_ptr shader_resource_data, + VideoCommon::GXPipelineUid* uid, FramebufferState frame_buffer_state) + : m_material_resource_data(std::move(material_resource_data)), + m_shader_resource_data(std::move(shader_resource_data)), m_uid(uid), + m_frame_buffer_state(frame_buffer_state) + { + } + + bool Compile() override + { + // Sanity check + if (!m_shader_resource_data->IsCompiled()) + { + m_material_resource_data->m_processing_finished = true; + return false; + } + + AbstractPipelineConfig config; + config.vertex_shader = m_shader_resource_data->GetVertexShader(); + config.pixel_shader = m_shader_resource_data->GetPixelShader(); + config.geometry_shader = m_shader_resource_data->GetGeometryShader(); + + const auto actual_uid = ApplyDriverBugs(*m_uid); + + if (m_material_resource_data->m_material_data->blending_state) + config.blending_state = *m_material_resource_data->m_material_data->blending_state; + else + config.blending_state = actual_uid.blending_state; + + if (m_material_resource_data->m_material_data->depth_state) + config.depth_state = *m_material_resource_data->m_material_data->depth_state; + else + config.depth_state = actual_uid.depth_state; + + config.framebuffer_state = std::move(m_frame_buffer_state); + config.framebuffer_state.additional_color_attachment_count = 0; + + config.rasterization_state = actual_uid.rasterization_state; + if (m_material_resource_data->m_material_data->cull_mode) + { + config.rasterization_state.cull_mode = + *m_material_resource_data->m_material_data->cull_mode; + } + + config.vertex_format = actual_uid.vertex_format; + config.usage = AbstractPipelineUsage::GX; + + m_material_resource_data->m_pipeline = g_gfx->CreatePipeline(config); + + if (m_material_resource_data->m_pipeline) + { + WriteUniforms(m_material_resource_data.get()); + } + m_material_resource_data->m_processing_finished = true; + return true; + } + void Retrieve() override {} + + private: + std::shared_ptr m_material_resource_data; + std::shared_ptr m_shader_resource_data; + VideoCommon::GXPipelineUid* m_uid; + FramebufferState m_frame_buffer_state; + }; + + if (!m_processing_load_data) + { + auto wi = m_resource_context.shader_compiler->CreateWorkItem( + m_load_data, std::move(shader_data), &m_uid, + g_framebuffer_manager->GetEFBFramebufferState()); + + // We don't need priority, that is already handled by the resource system + m_resource_context.shader_compiler->QueueWorkItem(std::move(wi), 0); + m_processing_load_data = true; + } + + if (!m_load_data->m_processing_finished) + return TaskComplete::No; + + if (!m_load_data->m_pipeline) + { + return TaskComplete::Error; + } + + std::swap(m_current_data, m_load_data); + return TaskComplete::Yes; +} + +void MaterialResource::MarkAsActive() +{ + if (!m_current_data) [[unlikely]] + return; + + m_resource_context.asset_cache->MarkAssetActive(m_material_asset); + for (const auto& texture_like_resource : m_current_data->m_texture_like_resources) + { + if (texture_like_resource) + texture_like_resource->MarkAsActive(); + } + if (m_current_data->m_shader_resource) + m_current_data->m_shader_resource->MarkAsActive(); + if (m_current_data->m_next_material) + m_current_data->m_next_material->MarkAsActive(); +} + +void MaterialResource::MarkAsPending() +{ + m_resource_context.asset_cache->MarkAssetPending(m_material_asset); +} + +void MaterialResource::CreateTextureData(Data* data) +{ + ShaderCode preprocessor_settings; + + const auto& material_data = *data->m_material_data; + data->m_texture_like_data.clear(); + data->m_texture_like_resources.clear(); + data->m_texture_like_references.clear(); + const u32 custom_sampler_index_offset = 8; + for (u32 i = 0; i < static_cast(material_data.textures.size()); i++) + { + const auto& texture_and_sampler = material_data.textures[i]; + data->m_texture_like_references.push_back(TextureLikeReference{}); + + TextureAndSamplerResource* value = nullptr; + data->m_texture_like_resources.push_back(value); + data->m_texture_like_data.push_back(std::shared_ptr{}); + + auto& texture_like_reference = data->m_texture_like_references[i]; + + if (texture_and_sampler.asset == "") + { + preprocessor_settings.Write("#define HAS_SAMPLER_{} 0\n", i); + + // For an invalid asset, force the sampler to use the default sampler + texture_like_reference.sampler_origin = + VideoCommon::TextureSamplerValue::SamplerOrigin::Asset; + texture_like_reference.texture_hash = ""; + } + else + { + preprocessor_settings.Write("#define HAS_SAMPLER_{} 1\n", i); + + texture_like_reference.sampler_origin = texture_and_sampler.sampler_origin; + texture_like_reference.texture_hash = texture_and_sampler.texture_hash; + } + + texture_like_reference.sampler_index = i + custom_sampler_index_offset; + texture_like_reference.texture = nullptr; + } + + data->m_preprocessor_settings = preprocessor_settings.GetBuffer(); +} + +void MaterialResource::SetShaderKey(Data* data, GXPipelineUid* uid) +{ + XXH3_state_t shader_key_hash; + XXH3_INITSTATE(&shader_key_hash); + XXH3_64bits_reset_withSeed(&shader_key_hash, static_cast(1)); + + UpdateHashWithPipeline(*uid, &shader_key_hash); + XXH3_64bits_update(&shader_key_hash, data->m_preprocessor_settings.c_str(), + data->m_preprocessor_settings.size()); + + data->m_shader_key = XXH3_64bits_digest(&shader_key_hash); +} + +void MaterialResource::WriteUniforms(Data* data) +{ + // Calculate the size in memory of the buffer + std::size_t max_uniformdata_size = 0; + for (const auto& property : data->m_material_data->properties) + { + max_uniformdata_size += VideoCommon::MaterialProperty::GetMemorySize(property); + } + data->m_uniform_data = Common::UniqueBuffer(max_uniformdata_size); + + // Now write the memory + u8* uniform_data = data->m_uniform_data.data(); + for (const auto& property : data->m_material_data->properties) + { + VideoCommon::MaterialProperty::WriteToMemory(uniform_data, property); + } +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/MaterialResource.h b/Source/Core/VideoCommon/Resources/MaterialResource.h new file mode 100644 index 0000000000..4842f000ea --- /dev/null +++ b/Source/Core/VideoCommon/Resources/MaterialResource.h @@ -0,0 +1,99 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common/Buffer.h" +#include "Common/SmallVector.h" + +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/Assets/MaterialAsset.h" +#include "VideoCommon/Assets/TextureSamplerValue.h" +#include "VideoCommon/Constants.h" +#include "VideoCommon/GXPipelineTypes.h" +#include "VideoCommon/RenderState.h" +#include "VideoCommon/Resources/Resource.h" +#include "VideoCommon/Resources/ShaderResource.h" +#include "VideoCommon/Resources/TextureAndSamplerResource.h" + +namespace VideoCommon +{ +class MaterialResource final : public Resource +{ +public: + MaterialResource(Resource::ResourceContext resource_context, const GXPipelineUid& pipeline_uid); + + struct TextureLikeReference + { + SamplerState sampler; + u32 sampler_index; + TextureSamplerValue::SamplerOrigin sampler_origin; + std::string_view texture_hash; + AbstractTexture* texture; + }; + + class Data + { + public: + AbstractPipeline* GetPipeline() const { return m_pipeline.get(); } + std::span GetUniforms() const { return m_uniform_data; } + std::span GetTextures() const { return m_texture_like_references; } + MaterialResource* GetNextMaterial() const { return m_next_material; } + + private: + friend class MaterialResource; + std::unique_ptr m_pipeline = nullptr; + Common::UniqueBuffer m_uniform_data; + std::shared_ptr m_material_data = nullptr; + ShaderResource* m_shader_resource = nullptr; + + using TextureLikeResource = Resource*; + Common::SmallVector + m_texture_like_resources; + + // Variant for future expansion... + using TextureLikeData = std::variant>; + Common::SmallVector + m_texture_like_data; + + Common::SmallVector + m_texture_like_references; + + MaterialResource* m_next_material = nullptr; + std::size_t m_shader_key; + std::string m_preprocessor_settings; + std::atomic_bool m_processing_finished; + }; + + const std::shared_ptr& GetData() const { return m_current_data; } + void MarkAsActive() override; + void MarkAsPending() override; + +private: + void ResetData() override; + Resource::TaskComplete CollectPrimaryData() override; + Resource::TaskComplete CollectDependencyData() override; + Resource::TaskComplete ProcessData() override; + + static void CreateTextureData(Data* data); + static void SetShaderKey(Data* data, GXPipelineUid* uid); + static void WriteUniforms(Data* data); + + std::shared_ptr m_current_data; + + std::shared_ptr m_load_data; + bool m_processing_load_data = false; + + // Note: asset cache owns the asset, we access as a reference + MaterialAsset* m_material_asset = nullptr; + + GXPipelineUid m_uid; + std::unique_ptr m_uid_vertex_format_copy; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/Resource.h b/Source/Core/VideoCommon/Resources/Resource.h index f9656fe2fa..4bcec910c7 100644 --- a/Source/Core/VideoCommon/Resources/Resource.h +++ b/Source/Core/VideoCommon/Resources/Resource.h @@ -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 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); diff --git a/Source/Core/VideoCommon/Resources/ShaderResource.cpp b/Source/Core/VideoCommon/Resources/ShaderResource.cpp new file mode 100644 index 0000000000..e1aecd355c --- /dev/null +++ b/Source/Core/VideoCommon/Resources/ShaderResource.cpp @@ -0,0 +1,311 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Resources/ShaderResource.h" + +#include + +#include + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/Assets/CustomAssetCache.h" +#include "VideoCommon/AsyncShaderCompiler.h" +#include "VideoCommon/GeometryShaderGen.h" +#include "VideoCommon/PipelineUtils.h" +#include "VideoCommon/PixelShaderGen.h" +#include "VideoCommon/VertexShaderGen.h" +#include "VideoCommon/VideoConfig.h" + +namespace VideoCommon +{ +namespace +{ +std::unique_ptr +CompileGeometryShader(const GeometryShaderUid& uid, APIType api_type, ShaderHostConfig host_config) +{ + const ShaderCode source_code = + GenerateGeometryShaderCode(api_type, host_config, uid.GetUidData()); + return g_gfx->CreateShaderFromSource(ShaderStage::Geometry, source_code.GetBuffer(), nullptr, + fmt::format("Geometry shader: {}", *uid.GetUidData())); +} + +std::unique_ptr CompilePixelShader(const PixelShaderUid& uid, + std::string_view preprocessor_settings, + APIType api_type, + const ShaderHostConfig& host_config, + RasterSurfaceShaderData* shader_data) +{ + ShaderCode shader_code; + + // Write any preprocessor values that were passed in + shader_code.Write("{}", preprocessor_settings); + + // TODO: in the future we could dynamically determine the amount of samplers + // available, for now just hardcode to start at 8 (the first non game + // sampler index available) + const std::size_t custom_sampler_index_offset = 8; + for (std::size_t i = 0; i < shader_data->samplers.size(); i++) + { + const auto& sampler = shader_data->samplers[i]; + std::string_view sampler_type; + switch (sampler.type) + { + case AbstractTextureType::Texture_2D: + sampler_type = "sampler2D"; + break; + case AbstractTextureType::Texture_2DArray: + sampler_type = "sampler2DArray"; + break; + case AbstractTextureType::Texture_CubeMap: + sampler_type = "samplerCube"; + break; + }; + shader_code.Write("SAMPLER_BINDING({}) uniform {} samp_{};\n", custom_sampler_index_offset + i, + sampler_type, sampler.name); + + // Sampler usage is passed in from the material + // Write a new preprocessor value with the sampler name + // for easier code in the shader + shader_code.Write("#if HAS_SAMPLER_{} == 1\n", i); + shader_code.Write("#define HAS_{} 1\n", sampler.name); + shader_code.Write("#endif\n"); + + shader_code.Write("\n"); + } + shader_code.Write("\n"); + + // Now write the custom shader + shader_code.Write("{}", ReplaceAll(shader_data->pixel_source, "\r\n", "\n")); + + // Write out the uniform data + ShaderCode uniform_code; + for (const auto& property : shader_data->uniform_properties) + { + VideoCommon::ShaderProperty::WriteAsShaderCode(uniform_code, property); + } + if (!shader_data->uniform_properties.empty()) + uniform_code.Write("\n\n"); + + // Compile the shader + CustomPixelContents contents{.shader = shader_code.GetBuffer(), + .uniforms = uniform_code.GetBuffer()}; + const ShaderCode source_code = + GeneratePixelShaderCode(api_type, host_config, uid.GetUidData(), contents); + ShaderIncluder* shader_includer = + shader_data->shader_includer ? &*shader_data->shader_includer : nullptr; + return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(), shader_includer, + "Custom Pixel Shader"); +} + +std::unique_ptr CompileVertexShader(const VertexShaderUid& uid, + std::string_view preprocessor_settings, + APIType api_type, + const ShaderHostConfig& host_config, + const RasterSurfaceShaderData& shader_data) +{ + ShaderCode shader_code; + + // Write any preprocessor values that were passed in + shader_code.Write("{}", preprocessor_settings); + + // TODO: in the future we could dynamically determine the amount of samplers + // available, for now just hardcode to start at 8 (the first non game + // sampler index available) + const std::size_t custom_sampler_index_offset = 8; + for (std::size_t i = 0; i < shader_data.samplers.size(); i++) + { + const auto& sampler = shader_data.samplers[i]; + std::string_view sampler_type = ""; + switch (sampler.type) + { + case AbstractTextureType::Texture_2D: + sampler_type = "sampler2D"; + break; + case AbstractTextureType::Texture_2DArray: + sampler_type = "sampler2DArray"; + break; + case AbstractTextureType::Texture_CubeMap: + sampler_type = "samplerCube"; + break; + }; + shader_code.Write("SAMPLER_BINDING({}) uniform {} samp_{};\n", custom_sampler_index_offset + i, + sampler_type, sampler.name); + + // Sampler usage is passed in from the material + // Write a new preprocessor value with the sampler name + // for easier code in the shader + shader_code.Write("#if HAS_SAMPLER_{} == 1\n", i); + shader_code.Write("#define HAS_{} 1\n", sampler.name); + shader_code.Write("#endif\n"); + + shader_code.Write("\n"); + } + shader_code.Write("\n"); + + // Now write the custom shader + shader_code.Write("{}", ReplaceAll(shader_data.vertex_source, "\r\n", "\n")); + + // Write out the uniform data + ShaderCode uniform_code; + for (const auto& property : shader_data.uniform_properties) + { + VideoCommon::ShaderProperty::WriteAsShaderCode(uniform_code, property); + } + if (!shader_data.uniform_properties.empty()) + uniform_code.Write("\n\n"); + + // Compile the shader + CustomVertexContents contents{.shader = shader_code.GetBuffer(), + .uniforms = uniform_code.GetBuffer()}; + const ShaderCode source_code = + GenerateVertexShaderCode(api_type, host_config, uid.GetUidData(), contents); + return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer(), nullptr, + "Custom Vertex Shader"); +} +} // namespace +ShaderResource::ShaderResource(Resource::ResourceContext resource_context, + const GXPipelineUid& pipeline_uid, + const std::string& preprocessor_setting, + const ShaderHostConfig& shader_host_config) + : Resource(std::move(resource_context)), m_uid(pipeline_uid), + m_preprocessor_settings(preprocessor_setting), + m_shader_host_config{.bits = shader_host_config.bits} +{ + m_shader_asset = m_resource_context.asset_cache->CreateAsset( + m_resource_context.primary_asset_id, m_resource_context.asset_library, this); +} + +void ShaderResource::SetHostConfig(const ShaderHostConfig& host_config) +{ + m_shader_host_config.bits = host_config.bits; +} + +void ShaderResource::MarkAsPending() +{ + m_resource_context.asset_cache->MarkAssetPending(m_shader_asset); +} + +void ShaderResource::MarkAsActive() +{ + m_resource_context.asset_cache->MarkAssetActive(m_shader_asset); +} + +AbstractShader* ShaderResource::Data::GetVertexShader() const +{ + if (!m_vertex_shader) + return nullptr; + return m_vertex_shader.get(); +} + +AbstractShader* ShaderResource::Data::GetPixelShader() const +{ + if (!m_pixel_shader) + return nullptr; + return m_pixel_shader.get(); +} + +AbstractShader* ShaderResource::Data::GetGeometryShader() const +{ + if (!m_geometry_shader) + return nullptr; + return m_geometry_shader.get(); +} + +bool ShaderResource::Data::IsCompiled() const +{ + return m_vertex_shader && m_pixel_shader && (!m_needs_geometry_shader || m_geometry_shader); +} + +AbstractTextureType ShaderResource::Data::GetTextureType(std::size_t index) +{ + // If the data doesn't exist, just pick one... + if (!m_shader_data || index >= m_shader_data->samplers.size()) [[unlikely]] + return AbstractTextureType::Texture_2D; + + return m_shader_data->samplers[index].type; +} + +void ShaderResource::ResetData() +{ + m_load_data = std::make_shared(); + m_processing_load_data = false; +} + +Resource::TaskComplete ShaderResource::CollectPrimaryData() +{ + const auto shader_data = m_shader_asset->GetData(); + if (!shader_data) [[unlikely]] + { + return Resource::TaskComplete::No; + } + m_load_data->m_shader_data = shader_data; + + return Resource::TaskComplete::Yes; +} + +Resource::TaskComplete ShaderResource::ProcessData() +{ + class WorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem + { + public: + WorkItem(std::shared_ptr resource_data, VideoCommon::GXPipelineUid* uid, + u32 shader_bits, std::string_view preprocessor_settings) + : m_resource_data(std::move(resource_data)), m_uid(uid), m_shader_bits(shader_bits), + m_preprocessor_settings(preprocessor_settings) + { + } + + bool Compile() override + { + const ShaderHostConfig shader_host_config{.bits = m_shader_bits}; + auto actual_uid = ApplyDriverBugs(*m_uid); + + ClearUnusedPixelShaderUidBits(g_backend_info.api_type, shader_host_config, + &actual_uid.ps_uid); + m_resource_data->m_needs_geometry_shader = shader_host_config.backend_geometry_shaders && + !actual_uid.gs_uid.GetUidData()->IsPassthrough(); + + if (m_resource_data->m_needs_geometry_shader) + { + m_resource_data->m_geometry_shader = + CompileGeometryShader(actual_uid.gs_uid, g_backend_info.api_type, shader_host_config); + } + m_resource_data->m_pixel_shader = + CompilePixelShader(actual_uid.ps_uid, m_preprocessor_settings, g_backend_info.api_type, + shader_host_config, m_resource_data->m_shader_data.get()); + m_resource_data->m_vertex_shader = + CompileVertexShader(actual_uid.vs_uid, m_preprocessor_settings, g_backend_info.api_type, + shader_host_config, *m_resource_data->m_shader_data); + m_resource_data->m_processing_finished = true; + return true; + } + void Retrieve() override {} + + private: + std::shared_ptr m_resource_data; + VideoCommon::GXPipelineUid* m_uid; + u32 m_shader_bits; + std::string_view m_preprocessor_settings; + }; + + if (!m_processing_load_data) + { + std::string_view preprocessor_settings = m_preprocessor_settings; + auto wi = m_resource_context.shader_compiler->CreateWorkItem( + m_load_data, &m_uid, m_shader_host_config.bits, preprocessor_settings); + + // We don't need priority, that is already handled by the resource system + m_resource_context.shader_compiler->QueueWorkItem(std::move(wi), 0); + m_processing_load_data = true; + } + + if (!m_load_data->m_processing_finished) + return Resource::TaskComplete::No; + + if (!m_load_data->IsCompiled()) + return Resource::TaskComplete::Error; + + std::swap(m_current_data, m_load_data); + return Resource::TaskComplete::Yes; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/ShaderResource.h b/Source/Core/VideoCommon/Resources/ShaderResource.h new file mode 100644 index 0000000000..0f91cd3714 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/ShaderResource.h @@ -0,0 +1,67 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/Resources/Resource.h" + +#include "VideoCommon/Assets/ShaderAsset.h" +#include "VideoCommon/GXPipelineTypes.h" +#include "VideoCommon/ShaderGenCommon.h" + +namespace VideoCommon +{ +class ShaderResource final : public Resource +{ +public: + ShaderResource(Resource::ResourceContext resource_context, const GXPipelineUid& pipeline_uid, + const std::string& preprocessor_settings, + const ShaderHostConfig& shader_host_config); + + class Data + { + public: + AbstractShader* GetVertexShader() const; + AbstractShader* GetPixelShader() const; + AbstractShader* GetGeometryShader() const; + + bool IsCompiled() const; + AbstractTextureType GetTextureType(std::size_t index); + + private: + friend class ShaderResource; + std::unique_ptr m_vertex_shader; + std::unique_ptr m_pixel_shader; + std::unique_ptr m_geometry_shader; + std::shared_ptr m_shader_data; + bool m_needs_geometry_shader = false; + std::atomic_bool m_processing_finished; + }; + + // Changes the shader host config. Shaders should be reloaded afterwards. + void SetHostConfig(const ShaderHostConfig& host_config); + const std::shared_ptr& GetData() const { return m_current_data; } + + void MarkAsActive() override; + void MarkAsPending() override; + +private: + void ResetData() override; + Resource::TaskComplete CollectPrimaryData() override; + TaskComplete ProcessData() override; + + // Note: asset cache owns the asset, we access as a reference + RasterSurfaceShaderAsset* m_shader_asset = nullptr; + + std::shared_ptr m_current_data; + std::shared_ptr m_load_data; + + bool m_processing_load_data = false; + + ShaderHostConfig m_shader_host_config; + GXPipelineUid m_uid; + std::string m_preprocessor_settings; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.cpp b/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.cpp new file mode 100644 index 0000000000..735a64a357 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.cpp @@ -0,0 +1,103 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Resources/TextureAndSamplerResource.h" + +#include "VideoCommon/Assets/CustomAssetCache.h" +#include "VideoCommon/Resources/TexturePool.h" + +namespace VideoCommon +{ +TextureAndSamplerResource::TextureAndSamplerResource(Resource::ResourceContext resource_context) + : Resource(std::move(resource_context)) +{ + m_texture_and_sampler_asset = m_resource_context.asset_cache->CreateAsset( + m_resource_context.primary_asset_id, m_resource_context.asset_library, this); +} + +void TextureAndSamplerResource::MarkAsActive() +{ + m_resource_context.asset_cache->MarkAssetActive(m_texture_and_sampler_asset); +} + +void TextureAndSamplerResource::MarkAsPending() +{ + m_resource_context.asset_cache->MarkAssetPending(m_texture_and_sampler_asset); +} + +const std::shared_ptr& TextureAndSamplerResource::GetData() const +{ + return m_current_data; +} + +void TextureAndSamplerResource::ResetData() +{ + m_load_data = std::make_shared(); +} + +Resource::TaskComplete TextureAndSamplerResource::CollectPrimaryData() +{ + m_load_data->m_texture_and_sampler_data = m_texture_and_sampler_asset->GetData(); + if (!m_load_data->m_texture_and_sampler_data) + return Resource::TaskComplete::No; + + auto& texture_data = m_load_data->m_texture_and_sampler_data->texture_data; + if (texture_data.m_slices.empty()) + return Resource::TaskComplete::Error; + + if (texture_data.m_slices[0].m_levels.empty()) + return Resource::TaskComplete::Error; + + const auto& first_level = texture_data.m_slices[0].m_levels[0]; + + auto& config = m_load_data->m_config; + config.format = first_level.format; + config.flags = 0; + config.layers = 1; + config.levels = 1; + config.type = m_load_data->m_texture_and_sampler_data->type; + config.samples = 1; + + config.width = first_level.width; + config.height = first_level.height; + + return Resource::TaskComplete::Yes; +} + +Resource::TaskComplete TextureAndSamplerResource::ProcessData() +{ + auto texture = m_resource_context.texture_pool->AllocateTexture(m_load_data->m_config); + if (!texture) [[unlikely]] + return Resource::TaskComplete::Error; + + m_load_data->m_texture = std::move(texture); + + auto& texture_data = m_load_data->m_texture_and_sampler_data->texture_data; + for (std::size_t slice_index = 0; slice_index < texture_data.m_slices.size(); slice_index++) + { + auto& slice = texture_data.m_slices[slice_index]; + for (u32 level_index = 0; level_index < static_cast(slice.m_levels.size()); ++level_index) + { + auto& level = slice.m_levels[level_index]; + m_load_data->m_texture->Load(level_index, level.width, level.height, level.row_length, + level.data.data(), level.data.size(), + static_cast(slice_index)); + } + } + std::swap(m_current_data, m_load_data); + + // Release old data back to the pool + if (m_load_data) + m_resource_context.texture_pool->ReleaseTexture(std::move(m_load_data->m_texture)); + + return Resource::TaskComplete::Yes; +} + +void TextureAndSamplerResource::OnUnloadRequested() +{ + if (!m_current_data) + return; + m_resource_context.texture_pool->ReleaseTexture(std::move(m_current_data->m_texture)); + m_current_data = nullptr; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.h b/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.h new file mode 100644 index 0000000000..3641b8eb91 --- /dev/null +++ b/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.h @@ -0,0 +1,49 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "VideoCommon/Resources/Resource.h" + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/Assets/TextureAsset.h" + +namespace VideoCommon +{ +class TextureAndSamplerResource final : public Resource +{ +public: + explicit TextureAndSamplerResource(Resource::ResourceContext resource_context); + void MarkAsActive() override; + void MarkAsPending() override; + + class Data + { + public: + AbstractTexture* GetTexture() const { return m_texture.get(); } + const SamplerState& GetSampler() const { return m_texture_and_sampler_data->sampler; } + + private: + friend class TextureAndSamplerResource; + + std::shared_ptr m_texture_and_sampler_data; + std::unique_ptr m_texture; + TextureConfig m_config; + }; + + const std::shared_ptr& GetData() const; + +private: + void ResetData() override; + TaskComplete CollectPrimaryData() override; + TaskComplete ProcessData() override; + + void OnUnloadRequested() override; + + // Note: asset cache owns the asset, we access as a reference + TextureAndSamplerAsset* m_texture_and_sampler_asset = nullptr; + + std::shared_ptr m_current_data; + std::shared_ptr m_load_data; +}; +} // namespace VideoCommon