mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-12-16 04:09:39 +00:00
386 lines
13 KiB
C++
386 lines
13 KiB
C++
// 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
|