// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) // This file is part of the "Nazara Engine - Graphics module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include #include #include #include #include #include namespace Nz { bool MaterialInstanceParams::IsValid() const { return true; } MaterialInstance::MaterialInstance(std::shared_ptr parent) : m_parent(std::move(parent)), m_materialSettings(m_parent->GetSettings()) { const auto& settings = m_parent->GetSettings(); m_textureOverride.resize(settings.GetTexturePropertyCount()); for (std::size_t i = 0; i < m_textureOverride.size(); ++i) m_textureOverride[i].samplerInfo = settings.GetTextureProperty(i).defaultSamplerInfo; m_valueOverride.resize(settings.GetValuePropertyCount()); const auto& passSettings = settings.GetPasses(); m_passes.resize(passSettings.size()); for (std::size_t i = 0; i < m_passes.size(); ++i) { const auto& passSettingOpt = passSettings[i]; if (!passSettingOpt.has_value()) continue; const auto& passSetting = *passSettingOpt; auto& pass = m_passes[i]; pass.enabled = true; pass.flags = passSetting.flags; static_cast(pass.pipelineInfo) = passSetting.states; pass.shaders.reserve(passSetting.shaders.size()); for (const std::shared_ptr& uberShader : passSetting.shaders) { auto& shaderEntry = pass.shaders.emplace_back(); shaderEntry.shader = uberShader; shaderEntry.onShaderUpdated.Connect(shaderEntry.shader->OnShaderUpdated, [this, passIndex = i](UberShader*) { InvalidatePassPipeline(passIndex); }); auto& pipelineShaderEntry = pass.pipelineInfo.shaders.emplace_back(); pipelineShaderEntry.uberShader = uberShader; } } m_textureBinding.resize(m_parent->GetTextureCount()); m_uniformBuffers.resize(m_parent->GetUniformBlockCount()); for (std::size_t i = 0; i < m_uniformBuffers.size(); ++i) { const auto& uniformBlockData = m_parent->GetUniformBlockData(i); auto& uniformBuffer = m_uniformBuffers[i]; uniformBuffer.bufferView = uniformBlockData.bufferPool->Allocate(uniformBuffer.bufferIndex); uniformBuffer.values.resize(uniformBlockData.bufferPool->GetBufferSize()); } for (const auto& handler : m_materialSettings.GetPropertyHandlers()) handler->Update(*this); } MaterialInstance::MaterialInstance(const MaterialInstance& material, CopyToken) : m_parent(material.m_parent), m_optionValuesOverride(material.m_optionValuesOverride), m_valueOverride(material.m_valueOverride), m_textureBinding(material.m_textureBinding), m_textureOverride(material.m_textureOverride), m_materialSettings(material.m_materialSettings) { m_passes.resize(material.m_passes.size()); for (std::size_t i = 0; i < m_passes.size(); ++i) { m_passes[i].enabled = material.m_passes[i].enabled; m_passes[i].flags = material.m_passes[i].flags; m_passes[i].pipeline = material.m_passes[i].pipeline; m_passes[i].pipelineInfo = material.m_passes[i].pipelineInfo; m_passes[i].shaders.resize(material.m_passes[i].shaders.size()); for (std::size_t j = 0; j < m_passes[i].shaders.size(); ++j) { m_passes[i].shaders[j].shader = material.m_passes[i].shaders[j].shader; m_passes[i].shaders[j].onShaderUpdated.Connect(m_passes[i].shaders[j].shader->OnShaderUpdated, [this, passIndex = i](UberShader*) { InvalidatePassPipeline(passIndex); }); } } m_uniformBuffers.resize(m_parent->GetUniformBlockCount()); for (std::size_t i = 0; i < m_uniformBuffers.size(); ++i) { const auto& uniformBlockData = m_parent->GetUniformBlockData(i); auto& uniformBuffer = m_uniformBuffers[i]; uniformBuffer.bufferView = uniformBlockData.bufferPool->Allocate(uniformBuffer.bufferIndex); assert(material.m_uniformBuffers[i].values.size() == uniformBlockData.bufferPool->GetBufferSize()); uniformBuffer.values = material.m_uniformBuffers[i].values; } } MaterialInstance::~MaterialInstance() { for (std::size_t i = 0; i < m_uniformBuffers.size(); ++i) { auto& uniformBuffer = m_uniformBuffers[i]; m_parent->GetUniformBlockData(i).bufferPool->Free(uniformBuffer.bufferIndex); } } void MaterialInstance::DisablePass(std::string_view passName) { std::size_t passIndex = Graphics::Instance()->GetMaterialPassRegistry().GetPassIndex(passName); return DisablePass(passIndex); } void MaterialInstance::EnablePass(std::string_view passName, bool enable) { std::size_t passIndex = Graphics::Instance()->GetMaterialPassRegistry().GetPassIndex(passName); return EnablePass(passIndex, enable); } void MaterialInstance::FillShaderBinding(std::vector& bindings) const { // Textures const auto& defaultTextures = Graphics::Instance()->GetDefaultTextures(); for (std::size_t i = 0; i < m_textureBinding.size(); ++i) { const auto& textureSlot = m_parent->GetTextureData(i); const auto& textureBinding = m_textureBinding[i]; const std::shared_ptr& texture = (textureBinding.texture) ? textureBinding.texture : defaultTextures.whiteTextures[textureSlot.imageType]; const std::shared_ptr& sampler = (textureBinding.sampler) ? textureBinding.sampler : Graphics::Instance()->GetSamplerCache().Get({}); bindings.push_back({ textureSlot.bindingIndex, ShaderBinding::SampledTextureBinding { texture.get(), sampler.get() } }); } // UBO for (std::size_t i = 0; i < m_uniformBuffers.size(); ++i) { const auto& uboSlot = m_parent->GetUniformBlockData(i); const auto& uboInfo = m_uniformBuffers[i]; bindings.push_back({ uboSlot.bindingIndex, ShaderBinding::UniformBufferBinding { uboInfo.bufferView.GetBuffer(), uboInfo.bufferView.GetOffset(), uboInfo.bufferView.GetSize() } }); } } const std::shared_ptr& MaterialInstance::GetPipeline(std::size_t passIndex) const { if (passIndex >= m_passes.size() || !m_passes[passIndex].enabled) { static std::shared_ptr s_invalidPipeline; return s_invalidPipeline; } auto& pass = m_passes[passIndex]; if (!pass.pipeline) { pass.pipelineInfo.pipelineLayout = m_parent->GetRenderPipelineLayout(); // Options pass.pipelineInfo.optionValues.clear(); const MaterialPass* passSetting = m_materialSettings.GetPass(passIndex); assert(passSetting); // Pass options for (const auto& [hash, value] : passSetting->options) { if (m_optionValuesOverride.find(hash) != m_optionValuesOverride.end()) continue; auto& optionValue = pass.pipelineInfo.optionValues.emplace_back(); optionValue.hash = hash; optionValue.value = value; } // Custom options for (const auto& [hash, value] : m_optionValuesOverride) { auto& optionValue = pass.pipelineInfo.optionValues.emplace_back(); optionValue.hash = hash; optionValue.value = value; } // make option values consistent (required for hash/equality) std::sort(pass.pipelineInfo.optionValues.begin(), pass.pipelineInfo.optionValues.end(), [](const auto& lhs, const auto& rhs) { return lhs.hash < rhs.hash; }); m_passes[passIndex].pipeline = MaterialPipeline::Get(pass.pipelineInfo); } return m_passes[passIndex].pipeline; } bool MaterialInstance::HasPass(std::string_view passName) const { std::size_t passIndex = Graphics::Instance()->GetMaterialPassRegistry().GetPassIndex(passName); return HasPass(passIndex); } void MaterialInstance::OnTransfer(RenderFrame& renderFrame, CommandBufferBuilder& builder) { UploadPool& uploadPool = renderFrame.GetUploadPool(); for (UniformBuffer& uniformBuffer : m_uniformBuffers) { if (!uniformBuffer.dataInvalidated) continue; auto& allocation = uploadPool.Allocate(uniformBuffer.values.size()); std::memcpy(allocation.mappedPtr, uniformBuffer.values.data(), uniformBuffer.values.size()); builder.CopyBuffer(allocation, uniformBuffer.bufferView); uniformBuffer.dataInvalidated = false; } } void MaterialInstance::UpdatePassFlags(std::string_view passName, MaterialPassFlags materialFlags) { std::size_t passIndex = Graphics::Instance()->GetMaterialPassRegistry().GetPassIndex(passName); return UpdatePassFlags(passIndex, materialFlags); } void MaterialInstance::UpdatePassStates(std::string_view passName, FunctionRef stateUpdater) { std::size_t passIndex = Graphics::Instance()->GetMaterialPassRegistry().GetPassIndex(passName); return UpdatePassStates(passIndex, stateUpdater); } void MaterialInstance::UpdatePassesStates(std::initializer_list passesName, FunctionRef stateUpdater) { auto& materialPassRegistry = Graphics::Instance()->GetMaterialPassRegistry(); for (std::string_view passName : passesName) UpdatePassStates(materialPassRegistry.GetPassIndex(passName), stateUpdater); } void MaterialInstance::SetTextureProperty(std::size_t textureIndex, std::shared_ptr texture) { assert(textureIndex < m_textureOverride.size()); m_textureOverride[textureIndex].texture = std::move(texture); for (const auto& handler : m_materialSettings.GetPropertyHandlers()) { if (handler->NeedsUpdateOnTextureUpdate(textureIndex)) handler->Update(*this); } } void MaterialInstance::SetTextureProperty(std::size_t textureIndex, std::shared_ptr texture, const TextureSamplerInfo& samplerInfo) { assert(textureIndex < m_textureOverride.size()); m_textureOverride[textureIndex].samplerInfo = samplerInfo; m_textureOverride[textureIndex].texture = std::move(texture); for (const auto& handler : m_materialSettings.GetPropertyHandlers()) { if (handler->NeedsUpdateOnTextureUpdate(textureIndex)) handler->Update(*this); } } void MaterialInstance::SetTextureSamplerProperty(std::size_t textureIndex, const TextureSamplerInfo& samplerInfo) { assert(textureIndex < m_textureOverride.size()); m_textureOverride[textureIndex].samplerInfo = samplerInfo; for (const auto& handler : m_materialSettings.GetPropertyHandlers()) { if (handler->NeedsUpdateOnTextureUpdate(textureIndex)) handler->Update(*this); } } void MaterialInstance::SetValueProperty(std::size_t valueIndex, const MaterialSettings::Value& value) { assert(valueIndex < m_valueOverride.size()); m_valueOverride[valueIndex] = value; for (const auto& handler : m_materialSettings.GetPropertyHandlers()) { if (handler->NeedsUpdateOnValueUpdate(valueIndex)) handler->Update(*this); } } void MaterialInstance::UpdateOptionValue(UInt32 optionHash, const nzsl::Ast::ConstantSingleValue& value) { auto it = m_optionValuesOverride.find(optionHash); if (it == m_optionValuesOverride.end()) m_optionValuesOverride.emplace(optionHash, value); else if (it->second != value) it->second = value; else return; for (std::size_t i = 0; i < m_passes.size(); ++i) InvalidatePassPipeline(i); } void MaterialInstance::UpdateTextureBinding(std::size_t textureBinding, std::shared_ptr texture, std::shared_ptr textureSampler) { assert(textureBinding < m_textureBinding.size()); auto& binding = m_textureBinding[textureBinding]; binding.texture = std::move(texture); binding.sampler = std::move(textureSampler); InvalidateShaderBinding(); } void MaterialInstance::UpdateUniformBufferData(std::size_t uniformBufferIndex, std::size_t offset, std::size_t size, const void* data) { assert(uniformBufferIndex < m_uniformBuffers.size()); auto& uniformBlock = m_uniformBuffers[uniformBufferIndex]; uniformBlock.dataInvalidated = true; assert(offset + size <= uniformBlock.values.size()); std::memcpy(&uniformBlock.values[offset], data, size); OnTransferRequired(this); } std::shared_ptr MaterialInstance::LoadFromFile(const std::filesystem::path& filePath, const MaterialInstanceParams& params) { Graphics* graphics = Graphics::Instance(); NazaraAssert(graphics, "Utility module has not been initialized"); return graphics->GetMaterialInstanceLoader().LoadFromFile(filePath, params); } std::shared_ptr MaterialInstance::LoadFromMemory(const void* data, std::size_t size, const MaterialInstanceParams& params) { Graphics* graphics = Graphics::Instance(); NazaraAssert(graphics, "Utility module has not been initialized"); return graphics->GetMaterialInstanceLoader().LoadFromMemory(data, size, params); } std::shared_ptr MaterialInstance::LoadFromStream(Stream& stream, const MaterialInstanceParams& params) { Graphics* graphics = Graphics::Instance(); NazaraAssert(graphics, "Utility module has not been initialized"); return graphics->GetMaterialInstanceLoader().LoadFromStream(stream, params); } }