// 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 #include #include #include #include #include #include #include #include #include #include #include #include namespace Nz { namespace { const UInt8 r_textureBlitShader[] = { #include }; const UInt8 r_basicMaterialShader[] = { #include }; const UInt8 r_fullscreenVertexShader[] = { #include }; const UInt8 r_phongMaterialShader[] = { #include }; const UInt8 r_physicallyBasedMaterialShader[] = { #include }; // Modules const UInt8 r_instanceDataModule[] = { #include }; const UInt8 r_lightDataModule[] = { #include }; const UInt8 r_lightShadowModule[] = { #include }; const UInt8 r_skeletalDataModule[] = { #include }; const UInt8 r_skinningDataModule[] = { #include }; const UInt8 r_skinningLinearModule[] = { #include }; const UInt8 r_viewerDataModule[] = { #include }; const UInt8 r_mathColorModule[] = { #include }; const UInt8 r_mathConstantsModule[] = { #include }; const UInt8 r_mathCookTorrancePBRModule[] = { #include }; // Passes const UInt8 r_gammaCorrectionPass[] = { #include }; } /*! * \ingroup graphics * \class Graphics * \brief Graphics class that represents the module initializer of Graphics */ Graphics::Graphics(Config config) : ModuleBase("Graphics", this), m_preferredDepthFormat(PixelFormat::Undefined), m_preferredDepthStencilFormat(PixelFormat::Undefined) { Renderer* renderer = Renderer::Instance(); const std::vector& renderDeviceInfo = renderer->QueryRenderDevices(); if (renderDeviceInfo.empty()) throw std::runtime_error("no render device available"); std::size_t bestRenderDeviceIndex = 0; for (std::size_t i = 0; i < renderDeviceInfo.size(); ++i) { const auto& deviceInfo = renderDeviceInfo[i]; if (config.useDedicatedRenderDevice && deviceInfo.type == RenderDeviceType::Dedicated) { bestRenderDeviceIndex = i; break; } else if (!config.useDedicatedRenderDevice && deviceInfo.type == RenderDeviceType::Integrated) { bestRenderDeviceIndex = i; break; } } RenderDeviceFeatures enabledFeatures; enabledFeatures.anisotropicFiltering = !config.forceDisableFeatures.anisotropicFiltering && renderDeviceInfo[bestRenderDeviceIndex].features.anisotropicFiltering; enabledFeatures.computeShaders = !config.forceDisableFeatures.computeShaders && renderDeviceInfo[bestRenderDeviceIndex].features.computeShaders; enabledFeatures.depthClamping = !config.forceDisableFeatures.depthClamping && renderDeviceInfo[bestRenderDeviceIndex].features.depthClamping; enabledFeatures.nonSolidFaceFilling = !config.forceDisableFeatures.nonSolidFaceFilling && renderDeviceInfo[bestRenderDeviceIndex].features.nonSolidFaceFilling; enabledFeatures.storageBuffers = !config.forceDisableFeatures.storageBuffers && renderDeviceInfo[bestRenderDeviceIndex].features.storageBuffers; enabledFeatures.textureReadWithoutFormat = !config.forceDisableFeatures.textureReadWithoutFormat && renderDeviceInfo[bestRenderDeviceIndex].features.textureReadWithoutFormat; enabledFeatures.textureReadWrite = !config.forceDisableFeatures.textureReadWrite && renderDeviceInfo[bestRenderDeviceIndex].features.textureReadWrite; enabledFeatures.textureWriteWithoutFormat = !config.forceDisableFeatures.textureWriteWithoutFormat && renderDeviceInfo[bestRenderDeviceIndex].features.textureWriteWithoutFormat; enabledFeatures.unrestrictedTextureViews = !config.forceDisableFeatures.unrestrictedTextureViews && renderDeviceInfo[bestRenderDeviceIndex].features.unrestrictedTextureViews; m_renderDevice = renderer->InstanciateRenderDevice(bestRenderDeviceIndex, enabledFeatures); if (!m_renderDevice) throw std::runtime_error("failed to instantiate render device"); m_renderPassCache.emplace(*m_renderDevice); m_samplerCache.emplace(m_renderDevice); SelectDepthStencilFormats(); BuildDefaultTextures(); RegisterShaderModules(); BuildBlitPipeline(); RegisterMaterialPasses(); MaterialPipeline::Initialize(); BuildDefaultMaterials(); RegisterPipelinePasses(); BuildDefaultPipelinePasses(); Font::SetDefaultAtlas(std::make_shared(*m_renderDevice)); m_materialInstanceLoader.RegisterLoader(Loaders::GetMaterialInstanceLoader_Texture()); // texture to material loader m_pipelinePassListLoader.RegisterLoader(Loaders::GetPipelinePassListLoader()); // texture to material loader } Graphics::~Graphics() { // Free of atlas if it is ours std::shared_ptr defaultAtlas = Font::GetDefaultAtlas(); if (defaultAtlas && defaultAtlas->GetStorage() == DataStorage::Hardware) { Font::SetDefaultAtlas(nullptr); // The default police can make live one hardware atlas after the free of a module (which could be problematic) // So, if the default police use a hardware atlas, we stole it. // I don't like this solution, but I don't have any better if (defaultAtlas.use_count() > 1) { // At least one police use the atlas const std::shared_ptr& defaultFont = Font::GetDefault(); defaultFont->SetAtlas(nullptr); if (defaultAtlas.use_count() >= 1) { // Still not the only one to own it ? Then crap. NazaraWarning("Default font atlas uses hardware storage and is still used"); } } } defaultAtlas.reset(); MaterialPipeline::Uninitialize(); m_renderPassCache.reset(); m_samplerCache.reset(); m_blitPipeline.reset(); m_blitPipelineLayout.reset(); m_defaultMaterials = DefaultMaterials{}; m_defaultTextures = DefaultTextures{}; } void Graphics::RegisterComponent(AppFilesystemComponent& component) { TextureParams defaultTexParams; defaultTexParams.renderDevice = m_renderDevice; component.SetDefaultResourceParameters(defaultTexParams); } void Graphics::BuildBlitPipeline() { RenderPipelineLayoutInfo layoutInfo; layoutInfo.bindings.assign({ { 0, 0, 1, ShaderBindingType::Sampler, nzsl::ShaderStageType::Fragment } }); m_blitPipelineLayout = m_renderDevice->InstantiateRenderPipelineLayout(std::move(layoutInfo)); if (!m_blitPipelineLayout) throw std::runtime_error("failed to instantiate fullscreen renderpipeline layout"); nzsl::Ast::ModulePtr blitShaderModule = m_shaderModuleResolver->Resolve("TextureBlit"); nzsl::ShaderWriter::States states; states.shaderModuleResolver = m_shaderModuleResolver; auto blitShader = m_renderDevice->InstantiateShaderModule(nzsl::ShaderStageType::Fragment | nzsl::ShaderStageType::Vertex, *blitShaderModule, states); if (!blitShader) throw std::runtime_error("failed to instantiate blit shader"); RenderPipelineInfo pipelineInfo; pipelineInfo.pipelineLayout = m_blitPipelineLayout; pipelineInfo.shaderModules.push_back(std::move(blitShader)); m_blitPipeline = m_renderDevice->InstantiateRenderPipeline(pipelineInfo); // Blending pipelineInfo.blending = true; pipelineInfo.blend.modeColor = BlendEquation::Add; pipelineInfo.blend.modeAlpha = BlendEquation::Add; pipelineInfo.blend.srcColor = BlendFunc::SrcAlpha; pipelineInfo.blend.dstColor = BlendFunc::InvSrcAlpha; pipelineInfo.blend.srcAlpha = BlendFunc::SrcAlpha; pipelineInfo.blend.dstAlpha = BlendFunc::InvSrcAlpha; m_blitPipelineTransparent = m_renderDevice->InstantiateRenderPipeline(std::move(pipelineInfo)); } void Graphics::BuildDefaultMaterials() { std::size_t depthPassIndex = m_materialPassRegistry.GetPassIndex("DepthPass"); std::size_t shadowPassIndex = m_materialPassRegistry.GetPassIndex("ShadowPass"); std::size_t distanceShadowPassIndex = m_materialPassRegistry.GetPassIndex("DistanceShadowPass"); std::size_t forwardPassIndex = m_materialPassRegistry.GetPassIndex("ForwardPass"); const auto& enabledFeatures = m_renderDevice->GetEnabledFeatures(); // BasicMaterial { MaterialSettings settings; PredefinedMaterials::AddBasicSettings(settings); MaterialPass forwardPass; forwardPass.states.depthBuffer = true; forwardPass.shaders.push_back(std::make_shared(nzsl::ShaderStageType::Fragment | nzsl::ShaderStageType::Vertex, "BasicMaterial")); settings.AddPass(forwardPassIndex, forwardPass); MaterialPass depthPass = forwardPass; depthPass.options[CRC32("DepthPass")] = true; settings.AddPass(depthPassIndex, depthPass); MaterialPass shadowPass = depthPass; shadowPass.options[CRC32("ShadowPass")] = true; shadowPass.states.frontFace = FrontFace::Clockwise; shadowPass.states.depthClamp = enabledFeatures.depthClamping; settings.AddPass(shadowPassIndex, shadowPass); MaterialPass distanceShadowPass = shadowPass; distanceShadowPass.options[CRC32("DistanceDepth")] = true; settings.AddPass(distanceShadowPassIndex, distanceShadowPass); m_defaultMaterials.materials[MaterialType::Basic].material = std::make_shared(std::move(settings), "BasicMaterial"); } // PbrMaterial { MaterialSettings settings; PredefinedMaterials::AddBasicSettings(settings); PredefinedMaterials::AddPbrSettings(settings); MaterialPass forwardPass; forwardPass.states.depthBuffer = true; forwardPass.shaders.push_back(std::make_shared(nzsl::ShaderStageType::Fragment | nzsl::ShaderStageType::Vertex, "PhysicallyBasedMaterial")); settings.AddPass(forwardPassIndex, forwardPass); MaterialPass depthPass = forwardPass; depthPass.options[CRC32("DepthPass")] = true; settings.AddPass(depthPassIndex, depthPass); MaterialPass shadowPass = depthPass; shadowPass.options[CRC32("ShadowPass")] = true; shadowPass.states.frontFace = FrontFace::Clockwise; shadowPass.states.depthClamp = enabledFeatures.depthClamping; settings.AddPass(shadowPassIndex, shadowPass); MaterialPass distanceShadowPass = shadowPass; distanceShadowPass.options[CRC32("DistanceDepth")] = true; settings.AddPass(distanceShadowPassIndex, distanceShadowPass); m_defaultMaterials.materials[MaterialType::PhysicallyBased].material = std::make_shared(std::move(settings), "PhysicallyBasedMaterial"); } // PhongMaterial { MaterialSettings settings; PredefinedMaterials::AddBasicSettings(settings); PredefinedMaterials::AddPhongSettings(settings); MaterialPass forwardPass; forwardPass.states.depthBuffer = true; forwardPass.shaders.push_back(std::make_shared(nzsl::ShaderStageType::Fragment | nzsl::ShaderStageType::Vertex, "PhongMaterial")); settings.AddPass(forwardPassIndex, forwardPass); MaterialPass depthPass = forwardPass; depthPass.options[CRC32("DepthPass")] = true; settings.AddPass(depthPassIndex, depthPass); MaterialPass shadowPass = depthPass; shadowPass.options[CRC32("ShadowPass")] = true; shadowPass.states.frontFace = FrontFace::Clockwise; shadowPass.states.depthClamp = enabledFeatures.depthClamping; settings.AddPass(shadowPassIndex, shadowPass); MaterialPass distanceShadowPass = shadowPass; distanceShadowPass.options[CRC32("DistanceDepth")] = true; settings.AddPass(distanceShadowPassIndex, distanceShadowPass); m_defaultMaterials.materials[MaterialType::Phong].material = std::make_shared(std::move(settings), "PhongMaterial"); } for (auto&& [materialType, materialData] : m_defaultMaterials.materials.iter_kv()) { materialData.presets[MaterialInstancePreset::Default] = materialData.material->GetDefaultInstance(); materialData.presets[MaterialInstancePreset::NoDepth] = materialData.material->Instantiate(); materialData.presets[MaterialInstancePreset::NoDepth]->DisablePass(depthPassIndex); materialData.presets[MaterialInstancePreset::NoDepth]->DisablePass(shadowPassIndex); materialData.presets[MaterialInstancePreset::NoDepth]->UpdatePassStates(forwardPassIndex, [](RenderStates& states) { states.depthBuffer = false; }); materialData.presets[MaterialInstancePreset::Transparent] = materialData.material->Instantiate(); materialData.presets[MaterialInstancePreset::Transparent]->DisablePass(depthPassIndex); materialData.presets[MaterialInstancePreset::Transparent]->DisablePass(shadowPassIndex); materialData.presets[MaterialInstancePreset::Transparent]->UpdatePassFlags(forwardPassIndex, MaterialPassFlag::SortByDistance); materialData.presets[MaterialInstancePreset::Transparent]->UpdatePassStates(forwardPassIndex, [](RenderStates& renderStates) { renderStates.depthBuffer = true; renderStates.depthWrite = false; renderStates.blending = true; renderStates.blend.modeColor = BlendEquation::Add; renderStates.blend.modeAlpha = BlendEquation::Add; renderStates.blend.srcColor = BlendFunc::SrcAlpha; renderStates.blend.dstColor = BlendFunc::InvSrcAlpha; renderStates.blend.srcAlpha = BlendFunc::One; renderStates.blend.dstAlpha = BlendFunc::One; }); } } void Graphics::BuildDefaultPipelinePasses() { m_defaultPipelinePasses = std::make_shared(); // Forward pass std::size_t forwardColorOutput = m_defaultPipelinePasses->AddAttachment({ "Forward output", PixelFormat::RGBA16F }); std::size_t forwardDepthOutput = m_defaultPipelinePasses->AddAttachment({ "Depth-stencil buffer", m_preferredDepthStencilFormat }); std::size_t forwardPass = m_defaultPipelinePasses->AddPass("ForwardPass", m_pipelinePassRegistry.GetPassIndex("Forward")); m_defaultPipelinePasses->SetPassOutput(forwardPass, 0, forwardColorOutput); m_defaultPipelinePasses->SetPassDepthStencilOutput(forwardPass, forwardDepthOutput); m_defaultPipelinePasses->EnablePassFlags(forwardPass, FramePipelinePassFlag::LightShadowing); // Gamma correction std::size_t gammaCorrectionOutput = m_defaultPipelinePasses->AddAttachment({ "Gamma-corrected output", PixelFormat::RGBA8 }); ParameterList gammaCorrectionParameters; gammaCorrectionParameters.SetParameter("Shader", "PostProcess.GammaCorrection"); std::size_t gammaCorrectionPass = m_defaultPipelinePasses->AddPass("Gamma correction", m_pipelinePassRegistry.GetPassIndex("PostProcess"), gammaCorrectionParameters); m_defaultPipelinePasses->SetPassInput(gammaCorrectionPass, 0, forwardColorOutput); m_defaultPipelinePasses->SetPassOutput(gammaCorrectionPass, 0, gammaCorrectionOutput); m_defaultPipelinePasses->SetFinalOutput(gammaCorrectionOutput); } void Graphics::BuildDefaultTextures() { // Depth textures (white but with a depth format) { TextureInfo texInfo; texInfo.width = texInfo.height = texInfo.depth = texInfo.levelCount = 1; texInfo.pixelFormat = m_preferredDepthFormat; std::array whitePixels = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; for (auto&& [imageType, texture] : m_defaultTextures.depthTextures.iter_kv()) { if (imageType == ImageType::E3D) continue; texInfo.type = imageType; texInfo.layerCount = (imageType == ImageType::Cubemap) ? 6 : 1; texture = m_renderDevice->InstantiateTexture(texInfo, whitePixels.data(), false); } } // White texture 2D { TextureInfo texInfo; texInfo.width = texInfo.height = texInfo.depth = texInfo.levelCount = 1; texInfo.pixelFormat = PixelFormat::L8; std::array whitePixels = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; for (auto&& [imageType, texture] : m_defaultTextures.whiteTextures.iter_kv()) { texInfo.type = imageType; texInfo.layerCount = (imageType == ImageType::Cubemap) ? 6 : 1; texture = m_renderDevice->InstantiateTexture(texInfo, whitePixels.data(), false); } } } template void Graphics::RegisterEmbedShaderModule(const UInt8(&content)[N]) { nzsl::Unserializer unserializer(content, N); m_shaderModuleResolver->RegisterModule(nzsl::Ast::UnserializeShader(unserializer)); } void Graphics::RegisterMaterialPasses() { m_materialPassRegistry.RegisterPass("ForwardPass"); m_materialPassRegistry.RegisterPass("DepthPass"); m_materialPassRegistry.RegisterPass("ShadowPass"); m_materialPassRegistry.RegisterPass("DistanceShadowPass"); } void Graphics::RegisterPipelinePasses() { m_pipelinePassRegistry.RegisterPass("Depth", {}, {}); m_pipelinePassRegistry.RegisterPass("DebugDraw", { "Input" }, { "Output" }); m_pipelinePassRegistry.RegisterPass("Forward", {}, { "Output" }); m_pipelinePassRegistry.RegisterPass("PostProcess", { "Input" }, { "Output" }); } void Graphics::RegisterShaderModules() { m_shaderModuleResolver = std::make_shared(); RegisterEmbedShaderModule(r_basicMaterialShader); RegisterEmbedShaderModule(r_fullscreenVertexShader); RegisterEmbedShaderModule(r_gammaCorrectionPass); RegisterEmbedShaderModule(r_instanceDataModule); RegisterEmbedShaderModule(r_lightDataModule); RegisterEmbedShaderModule(r_lightShadowModule); RegisterEmbedShaderModule(r_mathColorModule); RegisterEmbedShaderModule(r_mathConstantsModule); RegisterEmbedShaderModule(r_mathCookTorrancePBRModule); RegisterEmbedShaderModule(r_phongMaterialShader); RegisterEmbedShaderModule(r_physicallyBasedMaterialShader); RegisterEmbedShaderModule(r_skinningDataModule); RegisterEmbedShaderModule(r_skinningLinearModule); RegisterEmbedShaderModule(r_skeletalDataModule); RegisterEmbedShaderModule(r_textureBlitShader); RegisterEmbedShaderModule(r_viewerDataModule); #ifdef NAZARA_DEBUG // Override embed files with dev files in debug if (std::filesystem::path modulePath = "../../src/Nazara/Graphics/Resources/Shaders"; std::filesystem::is_directory(modulePath)) m_shaderModuleResolver->RegisterModuleDirectory(modulePath, true); #endif // Let application register their own shaders if (std::filesystem::path shaderPath = "Shaders"; std::filesystem::is_directory(shaderPath)) m_shaderModuleResolver->RegisterModuleDirectory(shaderPath); } void Graphics::SelectDepthStencilFormats() { for (PixelFormat depthStencilCandidate : { PixelFormat::Depth24, PixelFormat::Depth32F, PixelFormat::Depth16 }) { if (m_renderDevice->IsTextureFormatSupported(depthStencilCandidate, TextureUsage::DepthStencilAttachment)) { m_preferredDepthFormat = depthStencilCandidate; break; } } if (m_preferredDepthFormat == PixelFormat::Undefined) throw std::runtime_error("no supported depth format found"); for (PixelFormat depthStencilCandidate : { PixelFormat::Depth24Stencil8, PixelFormat::Depth32FStencil8, PixelFormat::Depth16Stencil8 }) { if (m_renderDevice->IsTextureFormatSupported(depthStencilCandidate, TextureUsage::DepthStencilAttachment)) { m_preferredDepthStencilFormat = depthStencilCandidate; break; } } if (m_preferredDepthStencilFormat == PixelFormat::Undefined) throw std::runtime_error("no supported depth-stencil format found"); } Graphics* Graphics::s_instance = nullptr; void Graphics::Config::Override(const CommandLineParameters& parameters) { if (parameters.HasFlag("use-dedicated-gpu")) useDedicatedRenderDevice = true; if (parameters.HasFlag("use-integrated-gpu")) useDedicatedRenderDevice = false; } }