437 lines
15 KiB
C++
437 lines
15 KiB
C++
// Copyright (C) 2022 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 <Nazara/Graphics/Graphics.hpp>
|
|
#include <Nazara/Graphics/GuillotineTextureAtlas.hpp>
|
|
#include <Nazara/Graphics/MaterialInstance.hpp>
|
|
#include <Nazara/Graphics/MaterialPipeline.hpp>
|
|
#include <Nazara/Graphics/PredefinedMaterials.hpp>
|
|
#include <Nazara/Graphics/PredefinedShaderStructs.hpp>
|
|
#include <Nazara/Graphics/Formats/TextureLoader.hpp>
|
|
#include <Nazara/Utility/Font.hpp>
|
|
#include <NZSL/Ast/AstSerializer.hpp>
|
|
#include <NZSL/Ast/Module.hpp>
|
|
#include <array>
|
|
#include <stdexcept>
|
|
#include <Nazara/Graphics/Debug.hpp>
|
|
|
|
namespace Nz
|
|
{
|
|
namespace
|
|
{
|
|
const UInt8 r_textureBlitShader[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/TextureBlit.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_basicMaterialShader[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/BasicMaterial.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_fullscreenVertexShader[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/FullscreenVertex.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_phongMaterialShader[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/PhongMaterial.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_physicallyBasedMaterialShader[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/PhysicallyBasedMaterial.nzslb.h>
|
|
};
|
|
|
|
// Modules
|
|
const UInt8 r_instanceDataModule[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/Modules/Engine/InstanceData.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_lightDataModule[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/Modules/Engine/LightData.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_skeletalDataModule[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/Modules/Engine/SkeletalData.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_skinningDataModule[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/Modules/Engine/SkinningData.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_skinningLinearModule[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/Modules/Engine/SkinningLinear.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_viewerDataModule[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/Modules/Engine/ViewerData.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_mathConstantsModule[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/Modules/Math/Constants.nzslb.h>
|
|
};
|
|
|
|
const UInt8 r_mathCookTorrancePBRModule[] = {
|
|
#include <Nazara/Graphics/Resources/Shaders/Modules/Math/CookTorrancePBR.nzslb.h>
|
|
};
|
|
}
|
|
|
|
/*!
|
|
* \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>& 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.depthClamping = !config.forceDisableFeatures.depthClamping && renderDeviceInfo[bestRenderDeviceIndex].features.depthClamping;
|
|
enabledFeatures.nonSolidFaceFilling = !config.forceDisableFeatures.nonSolidFaceFilling && renderDeviceInfo[bestRenderDeviceIndex].features.nonSolidFaceFilling;
|
|
|
|
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);
|
|
|
|
BuildDefaultTextures();
|
|
RegisterShaderModules();
|
|
BuildBlitPipeline();
|
|
RegisterMaterialPasses();
|
|
SelectDepthStencilFormats();
|
|
|
|
MaterialPipeline::Initialize();
|
|
BuildDefaultMaterials();
|
|
|
|
Font::SetDefaultAtlas(std::make_shared<GuillotineTextureAtlas>(*m_renderDevice));
|
|
|
|
m_materialInstanceLoader.RegisterLoader(Loaders::GetMaterialInstanceLoader_Texture()); // texture to material loader
|
|
}
|
|
|
|
Graphics::~Graphics()
|
|
{
|
|
// Free of atlas if it is ours
|
|
std::shared_ptr<AbstractAtlas> 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.unique())
|
|
{
|
|
// Still at least one police use the atlas
|
|
const std::shared_ptr<Font>& defaultFont = Font::GetDefault();
|
|
defaultFont->SetAtlas(nullptr);
|
|
|
|
if (!defaultAtlas.unique())
|
|
{
|
|
// 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::BuildBlitPipeline()
|
|
{
|
|
RenderPipelineLayoutInfo layoutInfo;
|
|
layoutInfo.bindings.assign({
|
|
{
|
|
0, 0, 1,
|
|
ShaderBindingType::Texture,
|
|
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 forwardPassIndex = m_materialPassRegistry.GetPassIndex("ForwardPass");
|
|
|
|
// BasicMaterial
|
|
{
|
|
MaterialSettings settings;
|
|
PredefinedMaterials::AddBasicSettings(settings);
|
|
|
|
MaterialPass forwardPass;
|
|
forwardPass.states.depthBuffer = true;
|
|
forwardPass.shaders.push_back(std::make_shared<UberShader>(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.states.faceCulling = FaceCulling::Front;
|
|
settings.AddPass(shadowPassIndex, shadowPass);
|
|
|
|
m_defaultMaterials.basicMaterial = std::make_shared<Material>(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<UberShader>(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.states.faceCulling = FaceCulling::Front;
|
|
settings.AddPass(shadowPassIndex, shadowPass);
|
|
|
|
m_defaultMaterials.pbrMaterial = std::make_shared<Material>(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<UberShader>(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.states.faceCulling = FaceCulling::Front;
|
|
shadowPass.states.depthBias = true;
|
|
shadowPass.states.depthBiasConstantFactor = 0.005f;
|
|
shadowPass.states.depthBiasSlopeFactor = 0.05f;
|
|
settings.AddPass(shadowPassIndex, shadowPass);
|
|
|
|
m_defaultMaterials.phongMaterial = std::make_shared<Material>(std::move(settings), "PhongMaterial");
|
|
}
|
|
|
|
m_defaultMaterials.basicDefault = m_defaultMaterials.basicMaterial->GetDefaultInstance();
|
|
|
|
m_defaultMaterials.basicNoDepth = m_defaultMaterials.basicMaterial->Instantiate();
|
|
m_defaultMaterials.basicNoDepth->DisablePass(depthPassIndex);
|
|
m_defaultMaterials.basicNoDepth->DisablePass(shadowPassIndex);
|
|
m_defaultMaterials.basicNoDepth->UpdatePassStates(forwardPassIndex, [](RenderStates& states)
|
|
{
|
|
states.depthBuffer = false;
|
|
});
|
|
|
|
m_defaultMaterials.basicTransparent = m_defaultMaterials.basicMaterial->Instantiate();
|
|
m_defaultMaterials.basicTransparent->DisablePass(depthPassIndex);
|
|
m_defaultMaterials.basicTransparent->DisablePass(shadowPassIndex);
|
|
m_defaultMaterials.basicTransparent->UpdatePassStates(forwardPassIndex, [](RenderStates& renderStates)
|
|
{
|
|
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::BuildDefaultTextures()
|
|
{
|
|
// Depth textures (white but with a depth format)
|
|
{
|
|
PixelFormat depthFormat = PixelFormat::Undefined;
|
|
for (PixelFormat depthStencilCandidate : { PixelFormat::Depth16, PixelFormat::Depth24, PixelFormat::Depth32F })
|
|
{
|
|
if (m_renderDevice->IsTextureFormatSupported(depthStencilCandidate, TextureUsage::ShaderSampling))
|
|
{
|
|
depthFormat = depthStencilCandidate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (depthFormat == PixelFormat::Undefined)
|
|
throw std::runtime_error("couldn't find a sampling-compatible depth pixel format");
|
|
|
|
TextureInfo texInfo;
|
|
texInfo.width = texInfo.height = texInfo.depth = texInfo.mipmapLevel = 1;
|
|
texInfo.pixelFormat = depthFormat;
|
|
|
|
std::array<UInt8, 6> whitePixels = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
|
|
|
for (std::size_t i = 0; i < ImageTypeCount; ++i)
|
|
{
|
|
texInfo.type = static_cast<ImageType>(i);
|
|
if (texInfo.type == ImageType::E3D)
|
|
continue;
|
|
|
|
m_defaultTextures.depthTextures[i] = m_renderDevice->InstantiateTexture(texInfo);
|
|
m_defaultTextures.depthTextures[i]->Update(whitePixels.data());
|
|
}
|
|
}
|
|
|
|
// White texture 2D
|
|
{
|
|
TextureInfo texInfo;
|
|
texInfo.width = texInfo.height = texInfo.depth = texInfo.mipmapLevel = 1;
|
|
texInfo.pixelFormat = PixelFormat::L8;
|
|
|
|
std::array<UInt8, 6> whitePixels = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
|
|
|
for (std::size_t i = 0; i < ImageTypeCount; ++i)
|
|
{
|
|
texInfo.type = static_cast<ImageType>(i);
|
|
|
|
m_defaultTextures.whiteTextures[i] = m_renderDevice->InstantiateTexture(texInfo);
|
|
m_defaultTextures.whiteTextures[i]->Update(whitePixels.data());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Graphics::RegisterMaterialPasses()
|
|
{
|
|
m_materialPassRegistry.RegisterPass("ForwardPass");
|
|
m_materialPassRegistry.RegisterPass("DepthPass");
|
|
m_materialPassRegistry.RegisterPass("ShadowPass");
|
|
}
|
|
|
|
void Graphics::RegisterShaderModules()
|
|
{
|
|
m_shaderModuleResolver = std::make_shared<nzsl::FilesystemModuleResolver>();
|
|
RegisterEmbedShaderModule(r_basicMaterialShader);
|
|
RegisterEmbedShaderModule(r_fullscreenVertexShader);
|
|
RegisterEmbedShaderModule(r_instanceDataModule);
|
|
RegisterEmbedShaderModule(r_lightDataModule);
|
|
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);
|
|
}
|
|
|
|
template<std::size_t N>
|
|
void Graphics::RegisterEmbedShaderModule(const UInt8(&content)[N])
|
|
{
|
|
nzsl::Unserializer unserializer(content, N);
|
|
m_shaderModuleResolver->RegisterModule(nzsl::Ast::UnserializeShader(unserializer));
|
|
}
|
|
|
|
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;
|
|
}
|