307 lines
9.7 KiB
C++
307 lines
9.7 KiB
C++
// Copyright (C) 2015 Jérôme Leclercq
|
|
// 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/DeferredGeometryPass.hpp>
|
|
#include <Nazara/Core/Error.hpp>
|
|
#include <Nazara/Core/ErrorFlags.hpp>
|
|
#include <Nazara/Graphics/AbstractViewer.hpp>
|
|
#include <Nazara/Graphics/DeferredRenderTechnique.hpp>
|
|
#include <Nazara/Graphics/Material.hpp>
|
|
#include <Nazara/Renderer/Renderer.hpp>
|
|
#include <Nazara/Renderer/RenderTexture.hpp>
|
|
#include <Nazara/Utility/BufferMapper.hpp>
|
|
#include <Nazara/Utility/StaticMesh.hpp>
|
|
#include <Nazara/Utility/VertexStruct.hpp>
|
|
#include <memory>
|
|
#include <Nazara/Graphics/Debug.hpp>
|
|
|
|
namespace Nz
|
|
{
|
|
/*!
|
|
* \ingroup graphics
|
|
* \class Nz::DeferredGeometryPass
|
|
* \brief Graphics class that represents the pass for geometries in deferred rendering
|
|
*/
|
|
|
|
/*!
|
|
* \brief Constructs a DeferredGeometryPass object by default
|
|
*/
|
|
|
|
DeferredGeometryPass::DeferredGeometryPass()
|
|
{
|
|
m_clearShader = ShaderLibrary::Get("DeferredGBufferClear");
|
|
m_clearStates.parameters[RendererParameter_DepthBuffer] = true;
|
|
m_clearStates.parameters[RendererParameter_FaceCulling] = true;
|
|
m_clearStates.parameters[RendererParameter_StencilTest] = true;
|
|
m_clearStates.depthFunc = RendererComparison_Always;
|
|
m_clearStates.frontFace.stencilCompare = RendererComparison_Always;
|
|
m_clearStates.frontFace.stencilPass = StencilOperation_Zero;
|
|
}
|
|
|
|
DeferredGeometryPass::~DeferredGeometryPass() = default;
|
|
|
|
/*!
|
|
* \brief Processes the work on the data while working with textures
|
|
* \return false
|
|
*
|
|
* \param sceneData Data for the scene
|
|
* \param firstWorkTexture Index of the first texture to work with
|
|
* \param firstWorkTexture Index of the second texture to work with
|
|
*/
|
|
|
|
bool DeferredGeometryPass::Process(const SceneData& sceneData, unsigned int firstWorkTexture, unsigned int secondWorkTexture) const
|
|
{
|
|
NazaraAssert(sceneData.viewer, "Invalid viewer");
|
|
NazaraUnused(firstWorkTexture);
|
|
NazaraUnused(secondWorkTexture);
|
|
|
|
bool instancingEnabled = m_deferredTechnique->IsInstancingEnabled();
|
|
|
|
m_GBufferRTT->SetColorTargets({0, 1, 2}); // G-Buffer
|
|
Renderer::SetTarget(m_GBufferRTT);
|
|
Renderer::SetViewport(Recti(0, 0, m_dimensions.x, m_dimensions.y));
|
|
|
|
Renderer::SetRenderStates(m_clearStates);
|
|
Renderer::SetShader(m_clearShader);
|
|
Renderer::DrawFullscreenQuad();
|
|
|
|
|
|
Renderer::SetMatrix(MatrixType_Projection, sceneData.viewer->GetProjectionMatrix());
|
|
Renderer::SetMatrix(MatrixType_View, sceneData.viewer->GetViewMatrix());
|
|
|
|
const Shader* lastShader = nullptr;
|
|
const ShaderUniforms* shaderUniforms = nullptr;
|
|
|
|
for (auto& pair : m_renderQueue->layers)
|
|
{
|
|
DeferredRenderQueue::Layer& layer = pair.second;
|
|
|
|
for (auto& matIt : layer.opaqueModels)
|
|
{
|
|
auto& matEntry = matIt.second;
|
|
|
|
if (matEntry.enabled)
|
|
{
|
|
DeferredRenderQueue::MeshInstanceContainer& meshInstances = matEntry.meshMap;
|
|
|
|
if (!meshInstances.empty())
|
|
{
|
|
const Material* material = matIt.first;
|
|
|
|
bool useInstancing = instancingEnabled && matEntry.instancingEnabled;
|
|
|
|
// We begin by getting the program for materials
|
|
UInt32 flags = ShaderFlags_Deferred;
|
|
if (useInstancing)
|
|
flags |= ShaderFlags_Instancing;
|
|
|
|
const Shader* shader = material->Apply(flags);
|
|
|
|
// The uniforms are conserved in our program, there's no point to send them back if they don't change
|
|
if (shader != lastShader)
|
|
{
|
|
// Index of uniforms in the shader
|
|
shaderUniforms = GetShaderUniforms(shader);
|
|
|
|
// Ambient color for the scene
|
|
shader->SendColor(shaderUniforms->sceneAmbient, sceneData.ambientColor);
|
|
// Position of the camera
|
|
shader->SendVector(shaderUniforms->eyePosition, sceneData.viewer->GetEyePosition());
|
|
|
|
lastShader = shader;
|
|
}
|
|
|
|
// Meshes
|
|
for (auto& meshIt : meshInstances)
|
|
{
|
|
const MeshData& meshData = meshIt.first;
|
|
auto& meshEntry = meshIt.second;
|
|
|
|
std::vector<Matrix4f>& instances = meshEntry.instances;
|
|
if (!instances.empty())
|
|
{
|
|
const IndexBuffer* indexBuffer = meshData.indexBuffer;
|
|
const VertexBuffer* vertexBuffer = meshData.vertexBuffer;
|
|
|
|
// Handle draw call before rendering loop
|
|
Renderer::DrawCall drawFunc;
|
|
Renderer::DrawCallInstanced instancedDrawFunc;
|
|
unsigned int indexCount;
|
|
|
|
if (indexBuffer)
|
|
{
|
|
drawFunc = Renderer::DrawIndexedPrimitives;
|
|
instancedDrawFunc = Renderer::DrawIndexedPrimitivesInstanced;
|
|
indexCount = indexBuffer->GetIndexCount();
|
|
}
|
|
else
|
|
{
|
|
drawFunc = Renderer::DrawPrimitives;
|
|
instancedDrawFunc = Renderer::DrawPrimitivesInstanced;
|
|
indexCount = vertexBuffer->GetVertexCount();
|
|
}
|
|
|
|
Renderer::SetIndexBuffer(indexBuffer);
|
|
Renderer::SetVertexBuffer(vertexBuffer);
|
|
|
|
if (useInstancing)
|
|
{
|
|
// We get the buffer for instance of Renderer and we configure it to work with matrices
|
|
VertexBuffer* instanceBuffer = Renderer::GetInstanceBuffer();
|
|
instanceBuffer->SetVertexDeclaration(VertexDeclaration::Get(VertexLayout_Matrix4));
|
|
|
|
const Matrix4f* instanceMatrices = &instances[0];
|
|
unsigned int instanceCount = instances.size();
|
|
unsigned int maxInstanceCount = instanceBuffer->GetVertexCount(); // The number of matrices that can be hold in the buffer
|
|
|
|
while (instanceCount > 0)
|
|
{
|
|
// We compute the number of instances that we will be able to show this time (Depending on the instance buffer size)
|
|
unsigned int renderedInstanceCount = std::min(instanceCount, maxInstanceCount);
|
|
instanceCount -= renderedInstanceCount;
|
|
|
|
// We fill the instancing buffer with our world matrices
|
|
instanceBuffer->Fill(instanceMatrices, 0, renderedInstanceCount, true);
|
|
instanceMatrices += renderedInstanceCount;
|
|
|
|
// And we show
|
|
instancedDrawFunc(renderedInstanceCount, meshData.primitiveMode, 0, indexCount);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Without instancing, we must do one draw call for each instance
|
|
// This may be faster than instancing under a threshold
|
|
// Due to the time to modify the instancing buffer
|
|
for (const Matrix4f& matrix : instances)
|
|
{
|
|
Renderer::SetMatrix(MatrixType_World, matrix);
|
|
drawFunc(meshData.primitiveMode, 0, indexCount);
|
|
}
|
|
}
|
|
|
|
instances.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Abd we set it back data to zero
|
|
matEntry.enabled = false;
|
|
matEntry.instancingEnabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false; // We only fill the G-Buffer, the work texture are unchanged
|
|
}
|
|
|
|
/*!
|
|
* \brief Resizes the texture sizes
|
|
* \return true If successful
|
|
*
|
|
* \param dimensions Dimensions for the compute texture
|
|
*/
|
|
|
|
bool DeferredGeometryPass::Resize(const Vector2ui& dimensions)
|
|
{
|
|
DeferredRenderPass::Resize(dimensions);
|
|
|
|
/*
|
|
G-Buffer:
|
|
Texture0: Diffuse Color + Flags
|
|
Texture1: Encoded normal
|
|
Texture2: Specular value + Shininess
|
|
Texture3: N/A
|
|
*/
|
|
|
|
try
|
|
{
|
|
ErrorFlags errFlags(ErrorFlag_ThrowException);
|
|
|
|
unsigned int width = dimensions.x;
|
|
unsigned int height = dimensions.y;
|
|
|
|
m_depthStencilBuffer->Create(PixelFormatType_Depth24Stencil8, width, height);
|
|
|
|
m_GBuffer[0]->Create(ImageType_2D, PixelFormatType_RGBA8, width, height); // Texture 0 : Diffuse Color + Specular
|
|
m_GBuffer[1]->Create(ImageType_2D, PixelFormatType_RG16F, width, height); // Texture 1 : Encoded normal
|
|
m_GBuffer[2]->Create(ImageType_2D, PixelFormatType_RGBA8, width, height); // Texture 2 : Depth (24bits) + Shininess
|
|
|
|
m_GBufferRTT->Create(true);
|
|
|
|
m_GBufferRTT->AttachTexture(AttachmentPoint_Color, 0, m_GBuffer[0]);
|
|
m_GBufferRTT->AttachTexture(AttachmentPoint_Color, 1, m_GBuffer[1]);
|
|
m_GBufferRTT->AttachTexture(AttachmentPoint_Color, 2, m_GBuffer[2]);
|
|
|
|
// Texture 3 : Emission map ?
|
|
|
|
m_GBufferRTT->AttachBuffer(AttachmentPoint_DepthStencil, 0, m_depthStencilBuffer);
|
|
|
|
m_GBufferRTT->Unlock();
|
|
|
|
m_workRTT->Create(true);
|
|
|
|
for (unsigned int i = 0; i < 2; ++i)
|
|
{
|
|
m_workTextures[i]->Create(ImageType_2D, PixelFormatType_RGBA8, width, height);
|
|
m_workRTT->AttachTexture(AttachmentPoint_Color, i, m_workTextures[i]);
|
|
}
|
|
|
|
m_workRTT->AttachBuffer(AttachmentPoint_DepthStencil, 0, m_depthStencilBuffer);
|
|
|
|
m_workRTT->Unlock();
|
|
|
|
if (!m_workRTT->IsComplete() || !m_GBufferRTT->IsComplete())
|
|
{
|
|
NazaraError("Incomplete RTT");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
NazaraError("Failed to create G-Buffer RTT: " + String(e.what()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Gets the uniforms of a shader
|
|
* \return Uniforms of the shader
|
|
*
|
|
* \param shader Shader to get uniforms from
|
|
*/
|
|
|
|
const DeferredGeometryPass::ShaderUniforms* DeferredGeometryPass::GetShaderUniforms(const Shader* shader) const
|
|
{
|
|
auto it = m_shaderUniforms.find(shader);
|
|
if (it == m_shaderUniforms.end())
|
|
{
|
|
ShaderUniforms uniforms;
|
|
uniforms.shaderReleaseSlot.Connect(shader->OnShaderRelease, this, &DeferredGeometryPass::OnShaderInvalidated);
|
|
uniforms.shaderUniformInvalidatedSlot.Connect(shader->OnShaderUniformInvalidated, this, &DeferredGeometryPass::OnShaderInvalidated);
|
|
|
|
uniforms.eyePosition = shader->GetUniformLocation("EyePosition");
|
|
uniforms.sceneAmbient = shader->GetUniformLocation("SceneAmbient");
|
|
uniforms.textureOverlay = shader->GetUniformLocation("TextureOverlay");
|
|
|
|
it = m_shaderUniforms.emplace(shader, std::move(uniforms)).first;
|
|
}
|
|
|
|
return &it->second;
|
|
}
|
|
|
|
/*!
|
|
* \brief Handle the invalidation of a shader
|
|
*
|
|
* \param shader Shader being invalidated
|
|
*/
|
|
|
|
void DeferredGeometryPass::OnShaderInvalidated(const Shader* shader) const
|
|
{
|
|
m_shaderUniforms.erase(shader);
|
|
}
|
|
}
|