diff --git a/examples/PhysicsDemo/main.cpp b/examples/PhysicsDemo/main.cpp index 96dca0bef..ef823402c 100644 --- a/examples/PhysicsDemo/main.cpp +++ b/examples/PhysicsDemo/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,8 @@ int main() return __LINE__; } + Nz::RenderWindowImpl* windowImpl = window.GetImpl(); + std::shared_ptr spaceshipMesh = Nz::Mesh::LoadFromFile(resourceDir / "Spaceship/spaceship.obj", meshParams); if (!spaceshipMesh) { @@ -89,10 +92,6 @@ int main() Nz::Vector2ui windowSize = window.GetSize(); - Nz::ViewerInstance viewerInstance; - viewerInstance.UpdateTargetSize(Nz::Vector2f(window.GetSize())); - viewerInstance.UpdateProjViewMatrices(Nz::Matrix4f::Perspective(Nz::DegreeAnglef(70.f), float(windowSize.x) / windowSize.y, 0.1f, 1000.f), Nz::Matrix4f::Translate(Nz::Vector3f::Backward() * 1)); - Nz::VertexMapper vertexMapper(*spaceshipMesh->GetSubMesh(0), Nz::BufferAccess::ReadOnly); Nz::SparsePtr vertices = vertexMapper.GetComponentPtr(Nz::VertexComponent::Position); @@ -100,8 +99,13 @@ int main() entt::registry registry; Nz::Physics3DSystem physSytem(registry); + Nz::RenderSystem renderSystem(registry); + entt::entity viewer = registry.create(); + registry.emplace(viewer); + registry.emplace(viewer, windowImpl); + auto shipCollider = std::make_shared(vertices, vertexMapper.GetVertexCount(), 0.01f); std::shared_ptr colliderMat = std::make_shared(Nz::BasicMaterial::GetSettings()); @@ -122,41 +126,41 @@ int main() } entt::entity playerEntity = registry.create(); - Nz::Node headingNode; - { - auto& entityNode = registry.emplace(playerEntity); - entityNode.SetPosition(Nz::Vector3f(12.5f, 0.f, 25.f)); + entt::entity headingEntity = registry.create(); + { auto& entityGfx = registry.emplace(playerEntity); entityGfx.AttachRenderable(model); + auto& entityNode = registry.emplace(playerEntity); + entityNode.SetPosition(Nz::Vector3f(12.5f, 0.f, 25.f)); + auto& entityPhys = registry.emplace(playerEntity, physSytem.CreateRigidBody(shipCollider)); entityPhys.SetMass(50.f); entityPhys.SetAngularDamping(Nz::Vector3f::Zero()); - headingNode.SetParent(entityNode); + auto& headingNode = registry.emplace(headingEntity); headingNode.SetInheritRotation(false); - headingNode.SetRotation(entityNode.GetRotation()); + headingNode.SetParent(registry, playerEntity); } - Nz::Node cameraNode; - cameraNode.SetParent(headingNode); - cameraNode.SetPosition(Nz::Vector3f::Backward() * 2.5f + Nz::Vector3f::Up() * 1.f); + registry.get(viewer).SetParent(registry, headingEntity); + registry.get(viewer).SetPosition(Nz::Vector3f::Backward() * 2.5f + Nz::Vector3f::Up() * 1.f); - for (std::size_t x = 0; x < 1; ++x) + for (std::size_t x = 0; x < 5; ++x) { - for (std::size_t y = 0; y < 1; ++y) + for (std::size_t y = 0; y < 5; ++y) { - for (std::size_t z = 0; z < 10; ++z) + for (std::size_t z = 0; z < 5; ++z) { entt::entity entity = registry.create(); - auto& entityNode = registry.emplace(entity); - entityNode.SetPosition(Nz::Vector3f(x * 2.f, y * 1.5f, z * 2.f)); - auto& entityGfx = registry.emplace(entity); entityGfx.AttachRenderable(model); + auto& entityNode = registry.emplace(entity); + entityNode.SetPosition(Nz::Vector3f(x * 2.f, y * 1.5f, z * 2.f)); + auto& entityPhys = registry.emplace(entity, physSytem.CreateRigidBody(shipCollider)); entityPhys.SetMass(1.f); entityPhys.SetAngularDamping(Nz::Vector3f::Zero()); @@ -165,46 +169,6 @@ int main() } } - - Nz::RenderWindowImpl* windowImpl = window.GetImpl(); - std::shared_ptr commandPool = windowImpl->CreateCommandPool(Nz::QueueType::Graphics); - - Nz::CommandBufferPtr drawCommandBuffer; - auto RebuildCommandBuffer = [&] - { - Nz::Vector2ui windowSize = window.GetSize(); - drawCommandBuffer = commandPool->BuildCommandBuffer([&](Nz::CommandBufferBuilder& builder) - { - Nz::Recti renderRect(0, 0, window.GetSize().x, window.GetSize().y); - - Nz::CommandBufferBuilder::ClearValues clearValues[2]; - clearValues[0].color = Nz::Color(80, 80, 80); - clearValues[1].depth = 1.f; - clearValues[1].stencil = 0; - - builder.BeginDebugRegion("Main window rendering", Nz::Color::Green); - { - builder.BeginRenderPass(windowImpl->GetFramebuffer(), windowImpl->GetRenderPass(), renderRect, { clearValues[0], clearValues[1] }); - { - builder.SetScissor(Nz::Recti{ 0, 0, int(windowSize.x), int(windowSize.y) }); - builder.SetViewport(Nz::Recti{ 0, 0, int(windowSize.x), int(windowSize.y) }); - builder.BindShaderBinding(Nz::Graphics::ViewerBindingSet, viewerInstance.GetShaderBinding()); - - auto view = registry.view(); - for (auto [entity, gfxComponent] : view.each()) - { - const Nz::WorldInstance& worldInstance = gfxComponent.GetWorldInstance(); - for (const auto& renderable : gfxComponent.GetRenderables()) - renderable->Draw(builder, worldInstance); - } - } - builder.EndRenderPass(); - } - builder.EndDebugRegion(); - }); - }; - - Nz::Vector3f viewerPos = Nz::Vector3f::Zero(); Nz::EulerAnglesf camAngles(0.f, 0.f, 0.f); @@ -254,13 +218,19 @@ int main() { auto view = registry.view(); for (auto [entity, gfxComponent] : view.each()) + { gfxComponent.AttachRenderable(colliderModel); + registry.patch(entity); + } } else { auto view = registry.view(); for (auto [entity, gfxComponent] : view.each()) + { gfxComponent.DetachRenderable(colliderModel); + registry.patch(entity); + } } rebuildCommandBuffer = true; } @@ -278,15 +248,15 @@ int main() camQuat = camAngles; - headingNode.SetRotation(camQuat); + registry.get(headingEntity).SetRotation(camQuat); break; } case Nz::WindowEventType::Resized: { Nz::Vector2ui windowSize = window.GetSize(); - viewerInstance.UpdateProjectionMatrix(Nz::Matrix4f::Perspective(Nz::DegreeAnglef(70.f), float(windowSize.x) / windowSize.y, 0.1f, 1000.f)); - viewerInstance.UpdateTargetSize(Nz::Vector2f(windowSize)); + //viewerInstance.UpdateProjectionMatrix(Nz::Matrix4f::Perspective(Nz::DegreeAnglef(70.f), float(windowSize.x) / windowSize.y, 0.1f, 1000.f)); + //viewerInstance.UpdateTargetSize(Nz::Vector2f(windowSize)); break; } @@ -306,11 +276,11 @@ int main() Nz::RigidBody3DComponent& playerShipBody = registry.get(playerEntity); Nz::Quaternionf currentRotation = playerShipBody.GetRotation(); - Nz::Vector3f desiredHeading = headingNode.GetForward(); + Nz::Vector3f desiredHeading = registry.get(headingEntity).GetForward(); Nz::Vector3f currentHeading = currentRotation * Nz::Vector3f::Forward(); Nz::Vector3f headingError = currentHeading.CrossProduct(desiredHeading); - Nz::Vector3f desiredUp = headingNode.GetUp(); + Nz::Vector3f desiredUp = registry.get(headingEntity).GetUp(); Nz::Vector3f currentUp = currentRotation * Nz::Vector3f::Up(); Nz::Vector3f upError = currentUp.CrossProduct(desiredUp); @@ -342,49 +312,7 @@ int main() if (!frame) continue; - Nz::UploadPool& uploadPool = frame.GetUploadPool(); - - viewerInstance.UpdateViewMatrix(Nz::Matrix4f::ViewMatrix(cameraNode.GetPosition(Nz::CoordSys::Global), cameraNode.GetRotation(Nz::CoordSys::Global))); - - frame.Execute([&](Nz::CommandBufferBuilder& builder) - { - builder.BeginDebugRegion("UBO Update", Nz::Color::Yellow); - { - builder.PreTransferBarrier(); - - viewerInstance.UpdateBuffers(uploadPool, builder); - /* - modelInstance.UpdateBuffers(uploadPool, builder); - modelInstance2.UpdateBuffers(uploadPool, builder); - */ - - auto view = registry.view(); - for (auto [entity, gfxComponent, nodeComponent] : view.each()) - { - Nz::WorldInstance& worldInstance = gfxComponent.GetWorldInstance(); - worldInstance.UpdateWorldMatrix(nodeComponent.GetTransformMatrix()); - - worldInstance.UpdateBuffers(uploadPool, builder); - } - - if (material->Update(frame, builder)) - rebuildCommandBuffer = true; - - if (colliderMat->Update(frame, builder)) - rebuildCommandBuffer = true; - - builder.PostTransferBarrier(); - } - builder.EndDebugRegion(); - }, Nz::QueueType::Transfer); - - if (rebuildCommandBuffer || frame.IsFramebufferInvalidated()) - { - frame.PushForRelease(std::move(drawCommandBuffer)); - RebuildCommandBuffer(); - } - - frame.SubmitCommandBuffer(drawCommandBuffer.get(), Nz::QueueType::Graphics); + renderSystem.Render(registry, frame); frame.Present(); diff --git a/include/Nazara/Core/ECS.hpp b/include/Nazara/Core/ECS.hpp index 3ecf2aec9..60b9cd49b 100644 --- a/include/Nazara/Core/ECS.hpp +++ b/include/Nazara/Core/ECS.hpp @@ -13,6 +13,7 @@ namespace Nz { + class CameraComponent; class GraphicsComponent; class NodeComponent; class RigidBody3DComponent; diff --git a/include/Nazara/Core/ECS.inl b/include/Nazara/Core/ECS.inl index ed9d42ebc..79858ece3 100644 --- a/include/Nazara/Core/ECS.inl +++ b/include/Nazara/Core/ECS.inl @@ -8,6 +8,19 @@ namespace Nz { + namespace Detail + { + template + struct RegisterComponent + { + void operator()(entt::id_type& expectedId) + { + if (entt::type_seq() != expectedId++) + throw std::runtime_error(std::string(entt::type_name::value()) + " has wrong index, please initialize Nazara ECS before instancing your own components"); + } + }; + } + /*! * \ingroup core * \class Nz::ECS @@ -21,14 +34,8 @@ namespace Nz inline void ECS::RegisterComponents() { - if (entt::type_seq() != 0) - throw std::runtime_error("NodeComponent has wrong index, please initialize Nazara ECS before instancing your own components"); - - if (entt::type_seq() != 1) - throw std::runtime_error("GraphicsComponent has wrong index, please initialize Nazara ECS before instancing your own components"); - - if (entt::type_seq() != 2) - throw std::runtime_error("GraphicsComponent has wrong index, please initialize Nazara ECS before instancing your own components"); + entt::id_type expectedId = 0; + TypeListApply, Detail::RegisterComponent>(expectedId); } } diff --git a/include/Nazara/Graphics.hpp b/include/Nazara/Graphics.hpp index 70de1f7bd..a689b1b4c 100644 --- a/include/Nazara/Graphics.hpp +++ b/include/Nazara/Graphics.hpp @@ -29,14 +29,17 @@ #ifndef NAZARA_GLOBAL_GRAPHICS_HPP #define NAZARA_GLOBAL_GRAPHICS_HPP +#include #include #include #include #include #include +#include #include #include #include +#include #include #include #include diff --git a/include/Nazara/Graphics/AbstractViewer.hpp b/include/Nazara/Graphics/AbstractViewer.hpp new file mode 100644 index 000000000..2f52bc2df --- /dev/null +++ b/include/Nazara/Graphics/AbstractViewer.hpp @@ -0,0 +1,32 @@ +// Copyright (C) 2020 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 + +#pragma once + +#ifndef NAZARA_ABSTRACTVIEWER_HPP +#define NAZARA_ABSTRACTVIEWER_HPP + +#include +#include + +namespace Nz +{ + class RenderTarget; + class ViewerInstance; + + class NAZARA_GRAPHICS_API AbstractViewer + { + public: + AbstractViewer() = default; + ~AbstractViewer() = default; + + virtual const RenderTarget& GetRenderTarget() = 0; + virtual ViewerInstance& GetViewerInstance() = 0; + virtual const ViewerInstance& GetViewerInstance() const = 0; + }; +} + +#include + +#endif diff --git a/include/Nazara/Graphics/AbstractViewer.inl b/include/Nazara/Graphics/AbstractViewer.inl new file mode 100644 index 000000000..07337a668 --- /dev/null +++ b/include/Nazara/Graphics/AbstractViewer.inl @@ -0,0 +1,12 @@ +// Copyright (C) 2017 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 +#include + +namespace Nz +{ +} + +#include diff --git a/include/Nazara/Graphics/Components.hpp b/include/Nazara/Graphics/Components.hpp index b92d44c24..d181aac51 100644 --- a/include/Nazara/Graphics/Components.hpp +++ b/include/Nazara/Graphics/Components.hpp @@ -29,6 +29,7 @@ #ifndef NAZARA_GLOBAL_GRAPHICS_COMPONENTS_HPP #define NAZARA_GLOBAL_GRAPHICS_COMPONENTS_HPP +#include #include #endif // NAZARA_GLOBAL_GRAPHICS_COMPONENTS_HPP diff --git a/include/Nazara/Graphics/Components/CameraComponent.hpp b/include/Nazara/Graphics/Components/CameraComponent.hpp new file mode 100644 index 000000000..a18c1a17e --- /dev/null +++ b/include/Nazara/Graphics/Components/CameraComponent.hpp @@ -0,0 +1,48 @@ +// Copyright (C) 2021 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 + +#pragma once + +#ifndef NAZARA_CAMERACOMPONENT_HPP +#define NAZARA_CAMERACOMPONENT_HPP + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_GRAPHICS_API CameraComponent : public AbstractViewer + { + public: + inline CameraComponent(const RenderTarget* renderTarget, ProjectionType projectionType = ProjectionType::Perspective); + CameraComponent(const CameraComponent&) = default; + CameraComponent(CameraComponent&&) = default; + ~CameraComponent() = default; + + const RenderTarget& GetRenderTarget() override; + ViewerInstance& GetViewerInstance() override; + const ViewerInstance& GetViewerInstance() const override; + + inline void UpdateTarget(const RenderTarget* framebuffer); + inline void UpdateProjectionType(ProjectionType projectionType); + + CameraComponent& operator=(const CameraComponent&) = default; + CameraComponent& operator=(CameraComponent&&) = default; + + private: + inline void UpdateProjectionMatrix(); + + const RenderTarget* m_renderTarget; + ProjectionType m_projectionType; + ViewerInstance m_viewerInstance; + }; +} + +#include + +#endif diff --git a/include/Nazara/Graphics/Components/CameraComponent.inl b/include/Nazara/Graphics/Components/CameraComponent.inl new file mode 100644 index 000000000..1b1d0f578 --- /dev/null +++ b/include/Nazara/Graphics/Components/CameraComponent.inl @@ -0,0 +1,42 @@ +// Copyright (C) 2021 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Prerequisites.hpp + +#include +#include + +namespace Nz +{ + inline CameraComponent::CameraComponent(const RenderTarget* renderTarget, ProjectionType projectionType) : + m_projectionType(projectionType), + m_renderTarget(renderTarget) + { + UpdateProjectionMatrix(); + } + + inline void CameraComponent::UpdateTarget(const RenderTarget* renderTarget) + { + m_renderTarget = renderTarget; + } + + inline void CameraComponent::UpdateProjectionType(ProjectionType projectionType) + { + m_projectionType = projectionType; + UpdateProjectionMatrix(); + } + + inline void CameraComponent::UpdateProjectionMatrix() + { + //FIXME + switch (m_projectionType) + { + case ProjectionType::Orthographic: + m_viewerInstance.UpdateProjectionMatrix(Nz::Matrix4f::Ortho(0.f, 1920.f, 0.f, 1080.f)); + break; + + case ProjectionType::Perspective: + m_viewerInstance.UpdateProjectionMatrix(Nz::Matrix4f::Perspective(Nz::DegreeAnglef(70.f), 16.f / 9.f, 1.f, 1000.f)); + break; + } + } +} diff --git a/include/Nazara/Graphics/Components/GraphicsComponent.hpp b/include/Nazara/Graphics/Components/GraphicsComponent.hpp index 835528a87..f00c668fe 100644 --- a/include/Nazara/Graphics/Components/GraphicsComponent.hpp +++ b/include/Nazara/Graphics/Components/GraphicsComponent.hpp @@ -19,7 +19,7 @@ namespace Nz class NAZARA_GRAPHICS_API GraphicsComponent { public: - GraphicsComponent() = default; + GraphicsComponent(); GraphicsComponent(const GraphicsComponent&) = default; GraphicsComponent(GraphicsComponent&&) = default; ~GraphicsComponent() = default; @@ -34,9 +34,12 @@ namespace Nz GraphicsComponent& operator=(const GraphicsComponent&) = default; GraphicsComponent& operator=(GraphicsComponent&&) = default; + NazaraSignal(OnRenderableAttached, GraphicsComponent* /*graphicsComponent*/, const std::shared_ptr& /*renderable*/); + NazaraSignal(OnRenderableDetach, GraphicsComponent* /*graphicsComponent*/, const std::shared_ptr& /*renderable*/); + private: std::vector> m_renderables; - WorldInstance m_worldInstance; + std::unique_ptr m_worldInstance; }; } diff --git a/include/Nazara/Graphics/Components/GraphicsComponent.inl b/include/Nazara/Graphics/Components/GraphicsComponent.inl index f85cf7b3a..68887d605 100644 --- a/include/Nazara/Graphics/Components/GraphicsComponent.inl +++ b/include/Nazara/Graphics/Components/GraphicsComponent.inl @@ -2,22 +2,32 @@ // This file is part of the "Nazara Engine - Utility module" // For conditions of distribution and use, see copyright notice in Prerequisites.hpp -#include -#include -#include "GraphicsComponent.hpp" +#include +#include namespace Nz { + inline GraphicsComponent::GraphicsComponent() + { + m_worldInstance = std::make_unique(); //< FIXME + } + inline void GraphicsComponent::AttachRenderable(std::shared_ptr renderable) { m_renderables.push_back(std::move(renderable)); + + OnRenderableAttached(this, m_renderables.back()); } inline void GraphicsComponent::DetachRenderable(const std::shared_ptr& renderable) { auto it = std::find(m_renderables.begin(), m_renderables.end(), renderable); if (it != m_renderables.end()) + { + OnRenderableDetach(this, renderable); + m_renderables.erase(it); + } } inline const std::vector>& GraphicsComponent::GetRenderables() const @@ -27,11 +37,11 @@ namespace Nz inline WorldInstance& GraphicsComponent::GetWorldInstance() { - return m_worldInstance; + return *m_worldInstance; } inline const WorldInstance& GraphicsComponent::GetWorldInstance() const { - return m_worldInstance; + return *m_worldInstance; } } diff --git a/include/Nazara/Graphics/Enums.hpp b/include/Nazara/Graphics/Enums.hpp index fac37c150..2375755cb 100644 --- a/include/Nazara/Graphics/Enums.hpp +++ b/include/Nazara/Graphics/Enums.hpp @@ -16,6 +16,12 @@ namespace Nz Sphere, Volume }; + + enum class ProjectionType + { + Orthographic, + Perspective + }; } #endif // NAZARA_ENUMS_GRAPHICS_HPP diff --git a/include/Nazara/Graphics/ForwardFramePipeline.hpp b/include/Nazara/Graphics/ForwardFramePipeline.hpp new file mode 100644 index 000000000..04f3fa6ef --- /dev/null +++ b/include/Nazara/Graphics/ForwardFramePipeline.hpp @@ -0,0 +1,84 @@ +// Copyright (C) 2017 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 + +#pragma once + +#ifndef NAZARA_FORWARDFRAMEPIPELINE_HPP +#define NAZARA_FORWARDFRAMEPIPELINE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_GRAPHICS_API ForwardFramePipeline : public FramePipeline + { + public: + ForwardFramePipeline(); + ForwardFramePipeline(const ForwardFramePipeline&) = delete; + ForwardFramePipeline(ForwardFramePipeline&&) = delete; + ~ForwardFramePipeline() = default; + + void InvalidateViewer(AbstractViewer* viewerInstance) override; + void InvalidateWorldInstance(WorldInstance* worldInstance) override; + + void RegisterInstancedDrawable(WorldInstance* worldInstance, const InstancedRenderable* instancedRenderable) override; + void RegisterViewer(AbstractViewer* viewerInstance) override; + + void Render(RenderFrame& renderFrame) override; + + void UnregisterInstancedDrawable(WorldInstance* worldInstance, const InstancedRenderable* instancedRenderable) override; + void UnregisterViewer(AbstractViewer* viewerInstance) override; + + ForwardFramePipeline& operator=(const ForwardFramePipeline&) = delete; + ForwardFramePipeline& operator=(ForwardFramePipeline&&) = delete; + + private: + BakedFrameGraph BuildFrameGraph(); + void RegisterMaterial(Material* material); + void UnregisterMaterial(Material* material); + + struct MaterialData + { + std::size_t usedCount = 0; + + NazaraSlot(Material, OnMaterialInvalidated, onMaterialInvalided); + }; + + struct RenderableData + { + NazaraSlot(InstancedRenderable, OnMaterialInvalidated, onMaterialInvalidated); + }; + + struct ViewerData + { + std::size_t colorAttachment; + std::size_t depthStencilAttachment; + ShaderBindingPtr blitShaderBinding; + }; + + std::size_t m_forwardPass; + std::unordered_map m_viewers; + std::unordered_map m_materials; + std::unordered_map> m_renderables; + std::unordered_set m_invalidatedViewerInstances; + std::unordered_set m_invalidatedMaterials; + std::unordered_set m_invalidatedWorldInstances; + BakedFrameGraph m_bakedFrameGraph; + bool m_rebuildFrameGraph; + bool m_rebuildForwardPass; + }; +} + +#include + +#endif diff --git a/include/Nazara/Graphics/ForwardFramePipeline.inl b/include/Nazara/Graphics/ForwardFramePipeline.inl new file mode 100644 index 000000000..8f7914d07 --- /dev/null +++ b/include/Nazara/Graphics/ForwardFramePipeline.inl @@ -0,0 +1,12 @@ +// Copyright (C) 2017 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 +#include + +namespace Nz +{ +} + +#include diff --git a/include/Nazara/Graphics/FramePass.hpp b/include/Nazara/Graphics/FramePass.hpp index 99eafd2d0..35bcafae3 100644 --- a/include/Nazara/Graphics/FramePass.hpp +++ b/include/Nazara/Graphics/FramePass.hpp @@ -9,8 +9,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -30,7 +32,7 @@ namespace Nz class NAZARA_GRAPHICS_API FramePass { public: - using CommandCallback = std::function; + using CommandCallback = std::function; using ExecutionCallback = std::function; struct DepthStencilClear; struct Input; diff --git a/include/Nazara/Graphics/FramePipeline.hpp b/include/Nazara/Graphics/FramePipeline.hpp new file mode 100644 index 000000000..9272fcd79 --- /dev/null +++ b/include/Nazara/Graphics/FramePipeline.hpp @@ -0,0 +1,46 @@ +// Copyright (C) 2017 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 + +#pragma once + +#ifndef NAZARA_FRAMEPIPELINE_HPP +#define NAZARA_FRAMEPIPELINE_HPP + +#include +#include + +namespace Nz +{ + class AbstractViewer; + class InstancedRenderable; + class RenderFrame; + class WorldInstance; + + class NAZARA_GRAPHICS_API FramePipeline + { + public: + FramePipeline() = default; + FramePipeline(const FramePipeline&) = delete; + FramePipeline(FramePipeline&&) noexcept = default; + virtual ~FramePipeline(); + + virtual void InvalidateViewer(AbstractViewer* viewerInstance) = 0; + virtual void InvalidateWorldInstance(WorldInstance* worldInstance) = 0; + + virtual void RegisterInstancedDrawable(WorldInstance* worldInstance, const InstancedRenderable* instancedRenderable) = 0; + virtual void RegisterViewer(AbstractViewer* viewerInstance) = 0; + + virtual void Render(RenderFrame& renderFrame) = 0; + + virtual void UnregisterInstancedDrawable(WorldInstance* worldInstance, const InstancedRenderable* instancedRenderable) = 0; + virtual void UnregisterViewer(AbstractViewer* viewerInstance) = 0; + + FramePipeline& operator=(const FramePipeline&) = delete; + FramePipeline& operator=(FramePipeline&&) noexcept = default; + }; +} + +#include + +#endif diff --git a/include/Nazara/Graphics/FramePipeline.inl b/include/Nazara/Graphics/FramePipeline.inl new file mode 100644 index 000000000..12dcfaeb7 --- /dev/null +++ b/include/Nazara/Graphics/FramePipeline.inl @@ -0,0 +1,12 @@ +// Copyright (C) 2017 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 +#include + +namespace Nz +{ +} + +#include diff --git a/include/Nazara/Graphics/Graphics.hpp b/include/Nazara/Graphics/Graphics.hpp index 4509ae247..3399be2aa 100644 --- a/include/Nazara/Graphics/Graphics.hpp +++ b/include/Nazara/Graphics/Graphics.hpp @@ -31,6 +31,11 @@ namespace Nz Graphics(Config config); ~Graphics(); + inline const std::shared_ptr& GetBlitPipeline() const; + inline const std::shared_ptr& GetBlitPipelineLayout() const; + inline const std::shared_ptr& GetFullscreenVertexBuffer() const; + inline const std::shared_ptr& GetFullscreenVertexDeclaration() const; + inline PixelFormat GetPreferredDepthStencilFormat() const; inline const std::shared_ptr& GetReferencePipelineLayout() const; inline const std::shared_ptr& GetRenderDevice() const; inline TextureSamplerCache& GetSamplerCache(); @@ -48,9 +53,18 @@ namespace Nz static void FillWorldPipelineLayout(RenderPipelineLayoutInfo& layoutInfo, UInt32 set = WorldBindingSet); private: + void BuildBlitPipeline(); + void BuildFullscreenVertexBuffer(); + void SelectDepthStencilFormats(); + std::optional m_samplerCache; + std::shared_ptr m_fullscreenVertexBuffer; std::shared_ptr m_renderDevice; + std::shared_ptr m_blitPipeline; + std::shared_ptr m_blitPipelineLayout; std::shared_ptr m_referencePipelineLayout; + std::shared_ptr m_fullscreenVertexDeclaration; + PixelFormat m_preferredDepthStencilFormat; static Graphics* s_instance; }; diff --git a/include/Nazara/Graphics/Graphics.inl b/include/Nazara/Graphics/Graphics.inl index de301c273..80c3348a5 100644 --- a/include/Nazara/Graphics/Graphics.inl +++ b/include/Nazara/Graphics/Graphics.inl @@ -7,6 +7,31 @@ namespace Nz { + inline const std::shared_ptr& Graphics::GetBlitPipeline() const + { + return m_blitPipeline; + } + + inline const std::shared_ptr& Graphics::GetBlitPipelineLayout() const + { + return m_blitPipelineLayout; + } + + inline const std::shared_ptr& Graphics::GetFullscreenVertexBuffer() const + { + return m_fullscreenVertexBuffer; + } + + inline const std::shared_ptr& Graphics::GetFullscreenVertexDeclaration() const + { + return m_fullscreenVertexDeclaration; + } + + inline PixelFormat Graphics::GetPreferredDepthStencilFormat() const + { + return m_preferredDepthStencilFormat; + } + inline const std::shared_ptr& Graphics::GetReferencePipelineLayout() const { return m_referencePipelineLayout; diff --git a/include/Nazara/Graphics/InstancedRenderable.hpp b/include/Nazara/Graphics/InstancedRenderable.hpp index aa1966c16..d7968e1d4 100644 --- a/include/Nazara/Graphics/InstancedRenderable.hpp +++ b/include/Nazara/Graphics/InstancedRenderable.hpp @@ -8,11 +8,14 @@ #define NAZARA_INSTANCEDRENDERABLE_HPP #include +#include #include +#include namespace Nz { class CommandBufferBuilder; + class Material; class WorldInstance; class NAZARA_GRAPHICS_API InstancedRenderable @@ -23,10 +26,15 @@ namespace Nz InstancedRenderable(InstancedRenderable&&) noexcept = default; ~InstancedRenderable(); - virtual void Draw(CommandBufferBuilder& commandBuffer, const WorldInstance& instance) const = 0; + virtual void Draw(CommandBufferBuilder& commandBuffer) const = 0; + + virtual const std::shared_ptr& GetMaterial(std::size_t i) const = 0; + virtual std::size_t GetMaterialCount() const = 0; InstancedRenderable& operator=(const InstancedRenderable&) = delete; InstancedRenderable& operator=(InstancedRenderable&&) noexcept = default; + + NazaraSignal(OnMaterialInvalidated, InstancedRenderable* /*instancedRenderable*/, std::size_t /*materialIndex*/, const std::shared_ptr& /*newMaterial*/); }; } diff --git a/include/Nazara/Graphics/Material.hpp b/include/Nazara/Graphics/Material.hpp index dae8c1d17..3973f1e10 100644 --- a/include/Nazara/Graphics/Material.hpp +++ b/include/Nazara/Graphics/Material.hpp @@ -75,8 +75,8 @@ namespace Nz inline const std::shared_ptr& GetTexture(std::size_t textureIndex) const; inline const TextureSamplerInfo& GetTextureSampler(std::size_t textureIndex) const; inline const std::shared_ptr& GetUniformBuffer(std::size_t bufferIndex) const; - inline std::vector& GetUniformBufferData(std::size_t bufferIndex); inline const std::vector& GetUniformBufferConstData(std::size_t bufferIndex); + inline std::vector& GetUniformBufferData(std::size_t bufferIndex); inline bool HasTexture(std::size_t textureIndex) const; inline bool HasVertexColor() const; @@ -109,12 +109,14 @@ namespace Nz bool Update(RenderFrame& renderFrame, CommandBufferBuilder& builder); // Signals: + NazaraSignal(OnMaterialInvalidated, const Material* /*material*/); NazaraSignal(OnMaterialRelease, const Material* /*material*/); private: inline void InvalidatePipeline(); inline void InvalidateShaderBinding(); inline void InvalidateTextureSampler(std::size_t textureIndex); + inline void InvalidateUniformData(std::size_t uniformBufferIndex); inline void UpdatePipeline() const; void UpdateShaderBinding(); diff --git a/include/Nazara/Graphics/Material.inl b/include/Nazara/Graphics/Material.inl index c9687814d..9ef6f13cf 100644 --- a/include/Nazara/Graphics/Material.inl +++ b/include/Nazara/Graphics/Material.inl @@ -468,20 +468,21 @@ namespace Nz return m_uniformBuffers[bufferIndex].buffer; } - inline std::vector& Material::GetUniformBufferData(std::size_t bufferIndex) - { - NazaraAssert(bufferIndex < m_uniformBuffers.size(), "Invalid uniform buffer index"); - UniformBuffer& uboEntry = m_uniformBuffers[bufferIndex]; - uboEntry.dataInvalidated = true; - return uboEntry.data; - } - inline const std::vector& Material::GetUniformBufferConstData(std::size_t bufferIndex) { NazaraAssert(bufferIndex < m_uniformBuffers.size(), "Invalid uniform buffer index"); return m_uniformBuffers[bufferIndex].data; } + inline std::vector& Material::GetUniformBufferData(std::size_t bufferIndex) + { + NazaraAssert(bufferIndex < m_uniformBuffers.size(), "Invalid uniform buffer index"); + UniformBuffer& uboEntry = m_uniformBuffers[bufferIndex]; + InvalidateUniformData(bufferIndex); + + return uboEntry.data; + } + inline bool Material::HasTexture(std::size_t textureIndex) const { return GetTexture(textureIndex) != nullptr; @@ -740,11 +741,13 @@ namespace Nz inline void Material::InvalidatePipeline() { m_pipelineUpdated = false; + OnMaterialInvalidated(this); } inline void Material::InvalidateShaderBinding() { m_shaderBindingUpdated = false; + OnMaterialInvalidated(this); } inline void Material::InvalidateTextureSampler(std::size_t textureIndex) @@ -755,6 +758,15 @@ namespace Nz InvalidateShaderBinding(); } + inline void Material::InvalidateUniformData(std::size_t uniformBufferIndex) + { + assert(uniformBufferIndex < m_uniformBuffers.size()); + UniformBuffer& uboEntry = m_uniformBuffers[uniformBufferIndex]; + uboEntry.dataInvalidated = true; + + OnMaterialInvalidated(this); + } + inline void Material::UpdatePipeline() const { for (auto& shader : m_pipelineInfo.shaders) diff --git a/include/Nazara/Graphics/Model.hpp b/include/Nazara/Graphics/Model.hpp index 96e4fa0ca..b59d7aec1 100644 --- a/include/Nazara/Graphics/Model.hpp +++ b/include/Nazara/Graphics/Model.hpp @@ -28,11 +28,12 @@ namespace Nz Model(Model&&) noexcept = default; ~Model() = default; - void Draw(CommandBufferBuilder& commandBuffer, const WorldInstance& instance) const override; + void Draw(CommandBufferBuilder& commandBuffer) const override; const std::shared_ptr& GetIndexBuffer(std::size_t subMeshIndex) const; std::size_t GetIndexCount(std::size_t subMeshIndex) const; - const std::shared_ptr& GetMaterial(std::size_t subMeshIndex) const; + const std::shared_ptr& GetMaterial(std::size_t subMeshIndex) const override; + std::size_t GetMaterialCount() const override; const std::shared_ptr& GetRenderPipeline(std::size_t subMeshIndex) const; const std::shared_ptr& GetVertexBuffer(std::size_t subMeshIndex) const; inline std::size_t GetSubMeshCount() const; diff --git a/include/Nazara/Graphics/Model.inl b/include/Nazara/Graphics/Model.inl index de31598dc..d68b51d7e 100644 --- a/include/Nazara/Graphics/Model.inl +++ b/include/Nazara/Graphics/Model.inl @@ -16,6 +16,8 @@ namespace Nz inline void Model::SetMaterial(std::size_t subMeshIndex, std::shared_ptr material) { assert(subMeshIndex < m_subMeshes.size()); + + OnMaterialInvalidated(this, subMeshIndex, material); m_subMeshes[subMeshIndex].material = std::move(material); } } diff --git a/include/Nazara/Graphics/Systems.hpp b/include/Nazara/Graphics/Systems.hpp new file mode 100644 index 000000000..24869dc68 --- /dev/null +++ b/include/Nazara/Graphics/Systems.hpp @@ -0,0 +1,34 @@ +// This file was automatically generated + +/* + Nazara Engine - Graphics module + + Copyright (C) 2020 Jérôme "Lynix" Leclercq (Lynix680@gmail.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#pragma once + +#ifndef NAZARA_GLOBAL_GRAPHICS_SYSTEMS_HPP +#define NAZARA_GLOBAL_GRAPHICS_SYSTEMS_HPP + +#include + +#endif // NAZARA_GLOBAL_GRAPHICS_SYSTEMS_HPP diff --git a/include/Nazara/Graphics/Systems/RenderSystem.hpp b/include/Nazara/Graphics/Systems/RenderSystem.hpp new file mode 100644 index 000000000..877386666 --- /dev/null +++ b/include/Nazara/Graphics/Systems/RenderSystem.hpp @@ -0,0 +1,72 @@ +// Copyright (C) 2021 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_RENDERSYSTEM_HPP +#define NAZARA_RENDERSYSTEM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class CommandBufferBuilder; + class FramePipeline; + class RenderFrame; + class UploadPool; + + class NAZARA_GRAPHICS_API RenderSystem + { + public: + RenderSystem(entt::registry& registry); + RenderSystem(const RenderSystem&) = delete; + RenderSystem(RenderSystem&&) = delete; + ~RenderSystem(); + + void Render(entt::registry& registry, RenderFrame& renderFrame); + + RenderSystem& operator=(const RenderSystem&) = delete; + RenderSystem& operator=(RenderSystem&&) = delete; + + private: + void OnCameraDestroy(entt::registry& registry, entt::entity entity); + void OnGraphicsDestroy(entt::registry& registry, entt::entity entity); + void OnNodeDestroy(entt::registry& registry, entt::entity entity); + void UpdateInstances(entt::registry& registry); + + struct CameraEntity + { + NazaraSlot(Node, OnNodeInvalidation, onNodeInvalidation); + }; + + struct GraphicsEntity + { + NazaraSlot(GraphicsComponent, OnRenderableAttached, onRenderableAttached); + NazaraSlot(GraphicsComponent, OnRenderableDetach, onRenderableDetach); + NazaraSlot(Node, OnNodeInvalidation, onNodeInvalidation); + }; + + entt::connection m_cameraDestroyConnection; + entt::connection m_graphicsDestroyConnection; + entt::connection m_nodeDestroyConnection; + entt::observer m_cameraConstructObserver; + entt::observer m_graphicsConstructObserver; + std::set m_invalidatedCameraNode; + std::set m_invalidatedWorldNode; + std::unique_ptr m_pipeline; + std::unordered_map m_cameraEntities; + std::unordered_map m_graphicsEntities; + }; +} + +#include + +#endif diff --git a/include/Nazara/Graphics/Systems/RenderSystem.inl b/include/Nazara/Graphics/Systems/RenderSystem.inl new file mode 100644 index 000000000..fc775c0aa --- /dev/null +++ b/include/Nazara/Graphics/Systems/RenderSystem.inl @@ -0,0 +1,12 @@ +// Copyright (C) 2021 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Prerequisites.hpp + +#include +#include + +namespace Nz +{ +} + +#include diff --git a/include/Nazara/Renderer/RenderStates.inl b/include/Nazara/Renderer/RenderStates.inl index f688532fe..0e2ed11bd 100644 --- a/include/Nazara/Renderer/RenderStates.inl +++ b/include/Nazara/Renderer/RenderStates.inl @@ -103,7 +103,7 @@ namespace std NazaraRenderStateEnum(faceFilling); - if (pipelineInfo.blending) //< Remember, at this time we know lhs.blending == rhs.blending + if (pipelineInfo.blending) //< we don't care about blending state if blending isn't enabled { NazaraRenderStateEnum(blend.dstAlpha); NazaraRenderStateEnum(blend.dstColor); @@ -119,7 +119,7 @@ namespace std if (pipelineInfo.faceCulling) NazaraRenderStateEnum(cullingSide); - if (pipelineInfo.stencilTest) + if (pipelineInfo.stencilTest) //< we don't care about stencil state if stencil isn't enabled { NazaraRenderStateEnum(stencilBack.compare); NazaraRenderStateUInt32(stencilBack.compareMask); diff --git a/src/Nazara/Graphics/AbstractViewer.cpp b/src/Nazara/Graphics/AbstractViewer.cpp new file mode 100644 index 000000000..414dabbe7 --- /dev/null +++ b/src/Nazara/Graphics/AbstractViewer.cpp @@ -0,0 +1,10 @@ +// Copyright (C) 2017 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 +#include + +namespace Nz +{ +} diff --git a/src/Nazara/Graphics/Components/CameraComponent.cpp b/src/Nazara/Graphics/Components/CameraComponent.cpp new file mode 100644 index 000000000..05994af16 --- /dev/null +++ b/src/Nazara/Graphics/Components/CameraComponent.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2021 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Prerequisites.hpp + +#include +#include +#include + +namespace Nz +{ + const RenderTarget& CameraComponent::GetRenderTarget() + { + if (!m_renderTarget) + throw std::runtime_error("no rendertarget set"); + + return* m_renderTarget; + } + + ViewerInstance& CameraComponent::GetViewerInstance() + { + return m_viewerInstance; + } + + const ViewerInstance& CameraComponent::GetViewerInstance() const + { + return m_viewerInstance; + } +} diff --git a/src/Nazara/Graphics/ForwardFramePipeline.cpp b/src/Nazara/Graphics/ForwardFramePipeline.cpp new file mode 100644 index 000000000..717c9f98d --- /dev/null +++ b/src/Nazara/Graphics/ForwardFramePipeline.cpp @@ -0,0 +1,296 @@ +// Copyright (C) 2017 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + ForwardFramePipeline::ForwardFramePipeline() : + m_rebuildFrameGraph(true), + m_rebuildForwardPass(false) + { + } + + void ForwardFramePipeline::InvalidateViewer(AbstractViewer* viewerInstance) + { + m_invalidatedViewerInstances.insert(viewerInstance); + } + + void ForwardFramePipeline::InvalidateWorldInstance(WorldInstance* worldInstance) + { + m_invalidatedWorldInstances.insert(worldInstance); + } + + void ForwardFramePipeline::RegisterInstancedDrawable(WorldInstance* worldInstance, const InstancedRenderable* instancedRenderable) + { + auto& renderableMap = m_renderables[worldInstance]; + if (auto it = renderableMap.find(instancedRenderable); it == renderableMap.end()) + { + auto& renderableData = renderableMap.emplace(instancedRenderable, RenderableData{}).first->second; + renderableData.onMaterialInvalidated.Connect(instancedRenderable->OnMaterialInvalidated, [this](InstancedRenderable* instancedRenderable, std::size_t materialIndex, const std::shared_ptr& newMaterial) + { + if (newMaterial) + RegisterMaterial(newMaterial.get()); + + const auto& prevMaterial = instancedRenderable->GetMaterial(materialIndex); + if (prevMaterial) + UnregisterMaterial(prevMaterial.get()); + + m_rebuildForwardPass = true; + }); + + std::size_t matCount = instancedRenderable->GetMaterialCount(); + for (std::size_t i = 0; i < matCount; ++i) + { + if (Material* mat = instancedRenderable->GetMaterial(i).get()) + RegisterMaterial(mat); + } + + m_rebuildForwardPass = true; + } + } + + void ForwardFramePipeline::RegisterViewer(AbstractViewer* viewerInstance) + { + m_viewers.emplace(viewerInstance, ViewerData{}); + } + + void ForwardFramePipeline::Render(RenderFrame& renderFrame) + { + Graphics* graphics = Graphics::Instance(); + + if (m_rebuildFrameGraph) + { + renderFrame.PushForRelease(std::move(m_bakedFrameGraph)); + m_bakedFrameGraph = BuildFrameGraph(); + m_rebuildForwardPass = false; //< No need to rebuild forward pass twice + } + + // Update UBOs and materials + UploadPool& uploadPool = renderFrame.GetUploadPool(); + + renderFrame.Execute([&](CommandBufferBuilder& builder) + { + builder.BeginDebugRegion("UBO Update", Color::Yellow); + { + builder.PreTransferBarrier(); + + for (AbstractViewer* viewer : m_invalidatedViewerInstances) + viewer->GetViewerInstance().UpdateBuffers(uploadPool, builder); + + m_invalidatedViewerInstances.clear(); + + for (WorldInstance* worldInstance : m_invalidatedWorldInstances) + worldInstance->UpdateBuffers(uploadPool, builder); + + m_invalidatedWorldInstances.clear(); + + for (Material* material : m_invalidatedMaterials) + { + if (material->Update(renderFrame, builder)) + m_rebuildForwardPass = true; + } + m_invalidatedMaterials.clear(); + + builder.PostTransferBarrier(); + } + builder.EndDebugRegion(); + }, QueueType::Transfer); + + const Vector2ui& frameSize = renderFrame.GetSize(); + if (m_bakedFrameGraph.Resize(frameSize.x, frameSize.y)) + { + const std::shared_ptr& sampler = graphics->GetSamplerCache().Get({}); + for (auto&& [_, viewerData] : m_viewers) + { + if (viewerData.blitShaderBinding) + renderFrame.PushForRelease(std::move(viewerData.blitShaderBinding)); + + viewerData.blitShaderBinding = graphics->GetBlitPipelineLayout()->AllocateShaderBinding(0); + viewerData.blitShaderBinding->Update({ + { + 0, + ShaderBinding::TextureBinding { + m_bakedFrameGraph.GetAttachmentTexture(viewerData.colorAttachment).get(), + sampler.get() + } + } + }); + } + } + + m_bakedFrameGraph.Execute(renderFrame); + + for (auto&& [viewer, viewerData] : m_viewers) + { + const RenderTarget& renderTarget = viewer->GetRenderTarget(); + Recti renderRegion(0, 0, frameSize.x, frameSize.y); + const ShaderBindingPtr& blitShaderBinding = viewerData.blitShaderBinding; + const std::shared_ptr& sourceTexture = m_bakedFrameGraph.GetAttachmentTexture(viewerData.colorAttachment); + + renderFrame.Execute([&](CommandBufferBuilder& builder) + { + builder.TextureBarrier(PipelineStage::ColorOutput, PipelineStage::FragmentShader, MemoryAccess::ColorWrite, MemoryAccess::ShaderRead, TextureLayout::ColorOutput, TextureLayout::ColorInput, *sourceTexture); + + std::array clearValues; + clearValues[0].color = Color::Black; + clearValues[1].depth = 1.f; + clearValues[1].stencil = 0; + + builder.BeginDebugRegion("Main window rendering", Color::Green); + { + builder.BeginRenderPass(renderTarget.GetFramebuffer(renderFrame.GetFramebufferIndex()), renderTarget.GetRenderPass(), renderRegion, { clearValues[0], clearValues[1] }); + { + builder.SetScissor(renderRegion); + builder.SetViewport(renderRegion); + + builder.BindPipeline(*graphics->GetBlitPipeline()); + builder.BindVertexBuffer(0, graphics->GetFullscreenVertexBuffer().get()); + builder.BindShaderBinding(0, *blitShaderBinding); + + builder.Draw(3); + } + builder.EndRenderPass(); + } + builder.EndDebugRegion(); + + }, QueueType::Graphics); + } + } + + void ForwardFramePipeline::UnregisterInstancedDrawable(WorldInstance* worldInstance, const InstancedRenderable* instancedRenderable) + { + auto instanceIt = m_renderables.find(worldInstance); + if (instanceIt == m_renderables.end()) + return; + + auto& instancedRenderables = instanceIt->second; + + auto renderableIt = instancedRenderables.find(instancedRenderable); + if (renderableIt == instancedRenderables.end()) + return; + + if (instancedRenderables.size() > 1) + instancedRenderables.erase(renderableIt); + else + m_renderables.erase(worldInstance); + + std::size_t matCount = instancedRenderable->GetMaterialCount(); + for (std::size_t i = 0; i < matCount; ++i) + { + if (Material* mat = instancedRenderable->GetMaterial(i).get()) + UnregisterMaterial(mat); + } + + m_rebuildForwardPass = true; + } + + void ForwardFramePipeline::UnregisterViewer(AbstractViewer* viewerInstance) + { + m_viewers.erase(viewerInstance); + m_rebuildFrameGraph = true; + } + + BakedFrameGraph ForwardFramePipeline::BuildFrameGraph() + { + FrameGraph frameGraph; + + for (auto&& [viewer, viewerData] : m_viewers) + { + viewerData.colorAttachment = frameGraph.AddAttachment({ + "Color", + PixelFormat::RGBA8 + }); + + viewerData.depthStencilAttachment = frameGraph.AddAttachment({ + "Depth-stencil buffer", + Graphics::Instance()->GetPreferredDepthStencilFormat() + }); + } + + for (auto&& [viewer, viewerData] : m_viewers) + { + FramePass& framePass = frameGraph.AddPass("Forward pass"); + + framePass.AddOutput(viewerData.colorAttachment); + framePass.SetDepthStencilOutput(viewerData.depthStencilAttachment); + + framePass.SetClearColor(0, Color::Black); + framePass.SetDepthStencilClear(1.f, 0); + + framePass.SetExecutionCallback([this]() + { + if (m_rebuildForwardPass) + { + m_rebuildForwardPass = false; + return FramePassExecution::UpdateAndExecute; + } + else + return FramePassExecution::Execute; + }); + + framePass.SetCommandCallback([this, viewer = viewer](CommandBufferBuilder& builder, const Recti& renderRect) + { + builder.SetScissor(renderRect); + builder.SetViewport(renderRect); + builder.BindShaderBinding(Graphics::ViewerBindingSet, viewer->GetViewerInstance().GetShaderBinding()); + + for (const auto& [worldInstance, renderables] : m_renderables) + { + builder.BindShaderBinding(Graphics::WorldBindingSet, worldInstance->GetShaderBinding()); + + for (const auto& [renderable, renderableData] : renderables) + renderable->Draw(builder); + } + }); + } + + //FIXME: This doesn't handle multiple window viewers + for (auto&& [viewer, viewerData] : m_viewers) + frameGraph.SetBackbufferOutput(viewerData.colorAttachment); + + return frameGraph.Bake(); + } + + void ForwardFramePipeline::RegisterMaterial(Material* material) + { + auto it = m_materials.find(material); + if (it == m_materials.end()) + { + it = m_materials.emplace(material, MaterialData{}).first; + it->second.onMaterialInvalided.Connect(material->OnMaterialInvalidated, [this, material](const Material* /*material*/) + { + m_invalidatedMaterials.insert(material); + }); + + m_invalidatedMaterials.insert(material); + } + + it->second.usedCount++; + } + + void ForwardFramePipeline::UnregisterMaterial(Material* material) + { + auto it = m_materials.find(material); + assert(it != m_materials.end()); + + MaterialData& materialData = it->second; + assert(materialData.usedCount > 0); + if (--materialData.usedCount == 0) + m_materials.erase(material); + } +} diff --git a/src/Nazara/Graphics/FramePipeline.cpp b/src/Nazara/Graphics/FramePipeline.cpp new file mode 100644 index 000000000..f0826ce5b --- /dev/null +++ b/src/Nazara/Graphics/FramePipeline.cpp @@ -0,0 +1,11 @@ +// Copyright (C) 2017 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 +#include + +namespace Nz +{ + FramePipeline::~FramePipeline() = default; +} diff --git a/src/Nazara/Graphics/Graphics.cpp b/src/Nazara/Graphics/Graphics.cpp index badff0a2c..df031c8c5 100644 --- a/src/Nazara/Graphics/Graphics.cpp +++ b/src/Nazara/Graphics/Graphics.cpp @@ -11,13 +11,21 @@ namespace Nz { + namespace + { + const UInt8 r_blitShader[] = { + #include + }; + } + /*! * \ingroup graphics * \class Nz::Graphics * \brief Graphics class that represents the module initializer of Graphics */ Graphics::Graphics(Config config) : - ModuleBase("Graphics", this) + ModuleBase("Graphics", this), + m_preferredDepthStencilFormat(PixelFormat::Undefined) { ECS::RegisterComponents(); @@ -60,11 +68,20 @@ namespace Nz FillWorldPipelineLayout(referenceLayoutInfo); m_referencePipelineLayout = m_renderDevice->InstantiateRenderPipelineLayout(std::move(referenceLayoutInfo)); + + BuildFullscreenVertexBuffer(); + BuildBlitPipeline(); + SelectDepthStencilFormats(); } Graphics::~Graphics() { MaterialPipeline::Uninitialize(); + m_samplerCache.reset(); + m_fullscreenVertexBuffer.reset(); + m_fullscreenVertexDeclaration.reset(); + m_blitPipeline.reset(); + m_blitPipelineLayout.reset(); } void Graphics::FillViewerPipelineLayout(RenderPipelineLayoutInfo& layoutInfo, UInt32 set) @@ -85,5 +102,80 @@ namespace Nz }); } + void Graphics::BuildBlitPipeline() + { + RenderPipelineLayoutInfo layoutInfo; + layoutInfo.bindings.assign({ + { + 0, 0, + ShaderBindingType::Texture, + ShaderStageType::Fragment + } + }); + + m_blitPipelineLayout = m_renderDevice->InstantiateRenderPipelineLayout(std::move(layoutInfo)); + if (!m_blitPipelineLayout) + throw std::runtime_error("failed to instantiate fullscreen renderpipeline layout"); + + auto blitShader = m_renderDevice->InstantiateShaderModule(ShaderStageType::Fragment | ShaderStageType::Vertex, ShaderLanguage::NazaraShader, r_blitShader, sizeof(r_blitShader), {}); + if (!blitShader) + throw std::runtime_error("failed to instantiate blit shader"); + + RenderPipelineInfo pipelineInfo; + pipelineInfo.pipelineLayout = m_blitPipelineLayout; + pipelineInfo.shaderModules.push_back(std::move(blitShader)); + pipelineInfo.vertexBuffers.assign({ + { + 0, + m_fullscreenVertexDeclaration + } + }); + + m_blitPipeline = m_renderDevice->InstantiateRenderPipeline(std::move(pipelineInfo)); + } + + void Graphics::BuildFullscreenVertexBuffer() + { + m_fullscreenVertexDeclaration = VertexDeclaration::Get(VertexLayout::XY_UV); + std::array vertexData = { + { + { + Nz::Vector2f(-1.f, 1.f), + Nz::Vector2f(0.0f, 1.0f), + }, + { + Nz::Vector2f(-1.f, -3.f), + Nz::Vector2f(0.0f, -1.0f), + }, + { + Nz::Vector2f(3.f, 1.f), + Nz::Vector2f(2.0f, 1.0f), + } + } + }; + + m_fullscreenVertexBuffer = m_renderDevice->InstantiateBuffer(BufferType::Vertex); + if (!m_fullscreenVertexBuffer->Initialize(m_fullscreenVertexDeclaration->GetStride() * vertexData.size(), BufferUsage::DeviceLocal)) + throw std::runtime_error("failed to initialize fullscreen vertex buffer"); + + if (!m_fullscreenVertexBuffer->Fill(vertexData.data(), 0, m_fullscreenVertexDeclaration->GetStride() * vertexData.size())) + throw std::runtime_error("failed to fill fullscreen vertex buffer"); + } + + void Graphics::SelectDepthStencilFormats() + { + 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; } diff --git a/src/Nazara/Graphics/Model.cpp b/src/Nazara/Graphics/Model.cpp index de91798d1..d9648a6ee 100644 --- a/src/Nazara/Graphics/Model.cpp +++ b/src/Nazara/Graphics/Model.cpp @@ -29,10 +29,8 @@ namespace Nz } } - void Model::Draw(CommandBufferBuilder& commandBuffer, const WorldInstance& instance) const + void Model::Draw(CommandBufferBuilder& commandBuffer) const { - commandBuffer.BindShaderBinding(Graphics::WorldBindingSet, instance.GetShaderBinding()); - for (std::size_t i = 0; i < m_subMeshes.size(); ++i) { const auto& submeshData = m_subMeshes[i]; @@ -66,6 +64,11 @@ namespace Nz return subMeshData.material; } + std::size_t Model::GetMaterialCount() const + { + return m_subMeshes.size(); + } + const std::shared_ptr& Model::GetRenderPipeline(std::size_t subMeshIndex) const { assert(subMeshIndex < m_subMeshes.size()); diff --git a/src/Nazara/Graphics/Resources/Shaders/blit.nzsl b/src/Nazara/Graphics/Resources/Shaders/blit.nzsl new file mode 100644 index 000000000..4b14dbd9d --- /dev/null +++ b/src/Nazara/Graphics/Resources/Shaders/blit.nzsl @@ -0,0 +1,40 @@ +external +{ + [binding(0)] texture: sampler2D +} + +struct VertIn +{ + [location(0)] position: vec2, + [location(1)] uv: vec2 +} + +struct VertOut +{ + [builtin(position)] position: vec4, + [location(0)] uv: vec2 +} + +[entry(vert)] +fn main(vertIn: VertIn) -> VertOut +{ + let output: VertOut; + output.position = vec4(vertIn.position, 0.0, 1.0); + output.uv = vertIn.uv; + + return output; +} + +struct FragOut +{ + [location(0)] color: vec4 +} + +[entry(frag)] +fn main(input: VertOut) -> FragOut +{ + let output: FragOut; + output.color = texture.Sample(input.uv); + + return output; +} diff --git a/src/Nazara/Graphics/Resources/Shaders/blit.nzsl.h b/src/Nazara/Graphics/Resources/Shaders/blit.nzsl.h new file mode 100644 index 000000000..7071d3411 --- /dev/null +++ b/src/Nazara/Graphics/Resources/Shaders/blit.nzsl.h @@ -0,0 +1 @@ +101,120,116,101,114,110,97,108,10,123,10,32,32,32,32,91,98,105,110,100,105,110,103,40,48,41,93,32,116,101,120,116,117,114,101,58,32,115,97,109,112,108,101,114,50,68,60,102,51,50,62,10,125,10,10,115,116,114,117,99,116,32,86,101,114,116,73,110,10,123,10,32,32,32,32,91,108,111,99,97,116,105,111,110,40,48,41,93,32,112,111,115,105,116,105,111,110,58,32,118,101,99,50,60,102,51,50,62,44,10,32,32,32,32,91,108,111,99,97,116,105,111,110,40,49,41,93,32,117,118,58,32,118,101,99,50,60,102,51,50,62,10,125,10,10,115,116,114,117,99,116,32,86,101,114,116,79,117,116,10,123,10,32,32,32,32,91,98,117,105,108,116,105,110,40,112,111,115,105,116,105,111,110,41,93,32,112,111,115,105,116,105,111,110,58,32,118,101,99,52,60,102,51,50,62,44,10,32,32,32,32,91,108,111,99,97,116,105,111,110,40,48,41,93,32,117,118,58,32,118,101,99,50,60,102,51,50,62,10,125,10,10,91,101,110,116,114,121,40,118,101,114,116,41,93,10,102,110,32,109,97,105,110,40,118,101,114,116,73,110,58,32,86,101,114,116,73,110,41,32,45,62,32,86,101,114,116,79,117,116,10,123,10,32,32,32,32,108,101,116,32,111,117,116,112,117,116,58,32,86,101,114,116,79,117,116,59,10,32,32,32,32,111,117,116,112,117,116,46,112,111,115,105,116,105,111,110,32,61,32,118,101,99,52,60,102,51,50,62,40,118,101,114,116,73,110,46,112,111,115,105,116,105,111,110,44,32,48,46,48,44,32,49,46,48,41,59,10,32,32,32,32,111,117,116,112,117,116,46,117,118,32,61,32,118,101,114,116,73,110,46,117,118,59,10,10,32,32,32,32,114,101,116,117,114,110,32,111,117,116,112,117,116,59,10,125,10,10,115,116,114,117,99,116,32,70,114,97,103,79,117,116,10,123,10,32,32,32,32,91,108,111,99,97,116,105,111,110,40,48,41,93,32,99,111,108,111,114,58,32,118,101,99,52,60,102,51,50,62,10,125,10,10,91,101,110,116,114,121,40,102,114,97,103,41,93,10,102,110,32,109,97,105,110,40,105,110,112,117,116,58,32,86,101,114,116,79,117,116,41,32,45,62,32,70,114,97,103,79,117,116,10,123,10,32,32,32,32,108,101,116,32,111,117,116,112,117,116,58,32,70,114,97,103,79,117,116,59,10,32,32,32,32,111,117,116,112,117,116,46,99,111,108,111,114,32,61,32,116,101,120,116,117,114,101,46,83,97,109,112,108,101,40,105,110,112,117,116,46,117,118,41,59,10,10,32,32,32,32,114,101,116,117,114,110,32,111,117,116,112,117,116,59,10,125,10, \ No newline at end of file diff --git a/src/Nazara/Graphics/Systems/RenderSystem.cpp b/src/Nazara/Graphics/Systems/RenderSystem.cpp new file mode 100644 index 000000000..30c7b16a1 --- /dev/null +++ b/src/Nazara/Graphics/Systems/RenderSystem.cpp @@ -0,0 +1,149 @@ +// Copyright (C) 2021 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Prerequisites.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + RenderSystem::RenderSystem(entt::registry& registry) : + m_cameraConstructObserver(registry, entt::collector.group()), + m_graphicsConstructObserver(registry, entt::collector.group()) + { + m_cameraDestroyConnection = registry.on_destroy().connect<&RenderSystem::OnCameraDestroy>(this); + m_graphicsDestroyConnection = registry.on_destroy().connect<&RenderSystem::OnGraphicsDestroy>(this); + m_nodeDestroyConnection = registry.on_destroy().connect<&RenderSystem::OnNodeDestroy>(this); + + m_pipeline = std::make_unique(); + } + + RenderSystem::~RenderSystem() + { + m_cameraConstructObserver.disconnect(); + m_graphicsConstructObserver.disconnect(); + m_cameraDestroyConnection.release(); + m_graphicsDestroyConnection.release(); + m_nodeDestroyConnection.release(); + } + + void RenderSystem::Render(entt::registry& registry, RenderFrame& renderFrame) + { + m_cameraConstructObserver.each([&](entt::entity entity) + { + CameraComponent& entityCamera = registry.get(entity); + NodeComponent& entityNode = registry.get(entity); + + m_pipeline->RegisterViewer(&entityCamera); + + m_invalidatedCameraNode.insert(entity); + + assert(m_cameraEntities.find(entity) == m_cameraEntities.end()); + auto& cameraEntity = m_cameraEntities[entity]; + cameraEntity.onNodeInvalidation.Connect(entityNode.OnNodeInvalidation, [this, entity](const Node* node) + { + m_invalidatedCameraNode.insert(entity); + }); + }); + + m_graphicsConstructObserver.each([&](entt::entity entity) + { + GraphicsComponent& entityGfx = registry.get(entity); + NodeComponent& entityNode = registry.get(entity); + + WorldInstance& worldInstance = entityGfx.GetWorldInstance(); + for (const auto& renderable : entityGfx.GetRenderables()) + m_pipeline->RegisterInstancedDrawable(&worldInstance, renderable.get()); + + m_invalidatedWorldNode.insert(entity); + + assert(m_graphicsEntities.find(entity) == m_graphicsEntities.end()); + auto& graphicsEntity = m_graphicsEntities[entity]; + graphicsEntity.onNodeInvalidation.Connect(entityNode.OnNodeInvalidation, [this, entity](const Node* node) + { + m_invalidatedWorldNode.insert(entity); + }); + + graphicsEntity.onRenderableAttached.Connect(entityGfx.OnRenderableAttached, [this](GraphicsComponent* gfx, const std::shared_ptr& renderable) + { + WorldInstance& worldInstance = gfx->GetWorldInstance(); + m_pipeline->RegisterInstancedDrawable(&worldInstance, renderable.get()); + }); + + graphicsEntity.onRenderableDetach.Connect(entityGfx.OnRenderableDetach, [this](GraphicsComponent* gfx, const std::shared_ptr& renderable) + { + WorldInstance& worldInstance = gfx->GetWorldInstance(); + m_pipeline->UnregisterInstancedDrawable(&worldInstance, renderable.get()); + }); + }); + + UpdateInstances(registry); + + m_pipeline->Render(renderFrame); + } + + void RenderSystem::OnCameraDestroy(entt::registry& registry, entt::entity entity) + { + m_cameraEntities.erase(entity); + m_invalidatedCameraNode.erase(entity); + + CameraComponent& entityCamera = registry.get(entity); + m_pipeline->UnregisterViewer(&entityCamera); + } + + void RenderSystem::OnGraphicsDestroy(entt::registry& registry, entt::entity entity) + { + m_graphicsEntities.erase(entity); + m_invalidatedWorldNode.erase(entity); + + GraphicsComponent& entityGfx = registry.get(entity); + WorldInstance& worldInstance = entityGfx.GetWorldInstance(); + for (const auto& renderable : entityGfx.GetRenderables()) + m_pipeline->UnregisterInstancedDrawable(&worldInstance, renderable.get()); + } + + void RenderSystem::OnNodeDestroy(entt::registry& registry, entt::entity entity) + { + if (registry.try_get(entity)) + OnCameraDestroy(registry, entity); + + if (registry.try_get(entity)) + OnGraphicsDestroy(registry, entity); + } + + void RenderSystem::UpdateInstances(entt::registry& registry) + { + for (entt::entity entity : m_invalidatedCameraNode) + { + const NodeComponent& entityNode = registry.get(entity); + CameraComponent& entityCamera = registry.get(entity); + + ViewerInstance& viewerInstance = entityCamera.GetViewerInstance(); + viewerInstance.UpdateViewMatrix(Nz::Matrix4f::ViewMatrix(entityNode.GetPosition(CoordSys::Global), entityNode.GetRotation(CoordSys::Global))); + + m_pipeline->InvalidateViewer(&entityCamera); + } + m_invalidatedCameraNode.clear(); + + for (entt::entity entity : m_invalidatedWorldNode) + { + const NodeComponent& entityNode = registry.get(entity); + GraphicsComponent& entityGraphics = registry.get(entity); + + WorldInstance& worldInstance = entityGraphics.GetWorldInstance(); + worldInstance.UpdateWorldMatrix(entityNode.GetTransformMatrix()); + + m_pipeline->InvalidateWorldInstance(&worldInstance); + } + m_invalidatedWorldNode.clear(); + } +}