From 8a3a8547dc981fc85fdcfee659f3c201c45cc48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Wed, 2 Feb 2022 12:55:39 +0100 Subject: [PATCH] Add light support (WIP) --- examples/DeferredShading/main.cpp | 19 +- examples/PhysicsDemo/main.cpp | 39 +--- examples/WidgetDemo/main.cpp | 172 ++++++++++++++ examples/WidgetDemo/xmake.lua | 6 + include/Nazara/Graphics.hpp | 4 + include/Nazara/Graphics/Components.hpp | 1 + .../Graphics/Components/LightComponent.hpp | 64 ++++++ .../Graphics/Components/LightComponent.inl | 68 ++++++ include/Nazara/Graphics/DirectionalLight.hpp | 61 +++++ include/Nazara/Graphics/DirectionalLight.inl | 85 +++++++ include/Nazara/Graphics/ElementRenderer.hpp | 9 +- .../Nazara/Graphics/ForwardFramePipeline.hpp | 36 ++- .../Nazara/Graphics/ForwardFramePipeline.inl | 15 ++ include/Nazara/Graphics/FramePipeline.hpp | 3 + include/Nazara/Graphics/Light.hpp | 55 +++++ include/Nazara/Graphics/Light.inl | 29 +++ include/Nazara/Graphics/PointLight.hpp | 61 +++++ include/Nazara/Graphics/PointLight.inl | 91 ++++++++ include/Nazara/Graphics/RenderElement.hpp | 2 +- include/Nazara/Graphics/SpotLight.hpp | 77 +++++++ include/Nazara/Graphics/SpotLight.inl | 161 +++++++++++++ .../Nazara/Graphics/SpriteChainRenderer.hpp | 95 +++++--- include/Nazara/Graphics/SubmeshRenderer.hpp | 13 +- .../Nazara/Graphics/Systems/RenderSystem.hpp | 22 +- include/Nazara/Math/BoundingVolume.hpp | 2 + include/Nazara/Math/BoundingVolume.inl | 19 +- include/Nazara/Math/Box.inl | 9 +- include/Nazara/Renderer/RenderBufferView.hpp | 6 + include/Nazara/Renderer/RenderBufferView.inl | 22 ++ include/Nazara/Utility/Algorithm.hpp | 2 +- src/Nazara/Audio/Formats/libflacLoader.cpp | 2 +- .../Graphics/Components/LightComponent.cpp | 10 + src/Nazara/Graphics/DirectionalLight.cpp | 36 +++ src/Nazara/Graphics/ElementRenderer.cpp | 6 +- src/Nazara/Graphics/ForwardFramePipeline.cpp | 215 ++++++++++++++---- src/Nazara/Graphics/Light.cpp | 11 + src/Nazara/Graphics/PointLight.cpp | 36 +++ .../Resources/Shaders/phong_material.nzsl | 4 +- src/Nazara/Graphics/SpotLight.cpp | 39 ++++ src/Nazara/Graphics/SpriteChainRenderer.cpp | 176 +++++++------- src/Nazara/Graphics/SubmeshRenderer.cpp | 39 +++- src/Nazara/Graphics/Systems/RenderSystem.cpp | 127 +++++++++-- src/Nazara/Network/Win32/SocketImpl.cpp | 2 +- src/Nazara/Utility/AlgorithmUtility.cpp | 2 +- 44 files changed, 1700 insertions(+), 253 deletions(-) create mode 100644 examples/WidgetDemo/main.cpp create mode 100644 examples/WidgetDemo/xmake.lua create mode 100644 include/Nazara/Graphics/Components/LightComponent.hpp create mode 100644 include/Nazara/Graphics/Components/LightComponent.inl create mode 100644 include/Nazara/Graphics/DirectionalLight.hpp create mode 100644 include/Nazara/Graphics/DirectionalLight.inl create mode 100644 include/Nazara/Graphics/Light.hpp create mode 100644 include/Nazara/Graphics/Light.inl create mode 100644 include/Nazara/Graphics/PointLight.hpp create mode 100644 include/Nazara/Graphics/PointLight.inl create mode 100644 include/Nazara/Graphics/SpotLight.hpp create mode 100644 include/Nazara/Graphics/SpotLight.inl create mode 100644 src/Nazara/Graphics/Components/LightComponent.cpp create mode 100644 src/Nazara/Graphics/DirectionalLight.cpp create mode 100644 src/Nazara/Graphics/Light.cpp create mode 100644 src/Nazara/Graphics/PointLight.cpp create mode 100644 src/Nazara/Graphics/SpotLight.cpp diff --git a/examples/DeferredShading/main.cpp b/examples/DeferredShading/main.cpp index 8185568ab..8da726eda 100644 --- a/examples/DeferredShading/main.cpp +++ b/examples/DeferredShading/main.cpp @@ -854,12 +854,14 @@ int main() planeModel.BuildElement(forwardPassIndex, planeInstance, elements); std::vector> elementPointers; + std::vector renderStates(elements.size()); elementPointers.reserve(elements.size()); for (const auto& element : elements) elementPointers.emplace_back(element.get()); - submeshRenderer.Prepare(viewerInstance, *submeshRendererData, *currentFrame, {}, elementPointers.data(), elementPointers.size()); - submeshRenderer.Render(viewerInstance, *submeshRendererData, builder, elementPointers.data(), elementPointers.size()); + submeshRenderer.Prepare(viewerInstance, *submeshRendererData, *currentFrame, elementPointers.size(), elementPointers.data(), renderStates.data()); + submeshRenderer.PrepareEnd(*currentFrame, *spriteRendererData); + submeshRenderer.Render(viewerInstance, *submeshRendererData, builder, elementPointers.size(), elementPointers.data()); }); Nz::FramePass& lightingPass = graph.AddPass("Lighting pass"); @@ -918,12 +920,14 @@ int main() flareSprite.BuildElement(forwardPassIndex, flareInstance, elements); std::vector> elementPointers; + std::vector renderStates(elements.size()); + elementPointers.reserve(elements.size()); for (const auto& element : elements) elementPointers.emplace_back(element.get()); - spritechainRenderer.Prepare(viewerInstance, *spriteRendererData, *currentFrame, {}, elementPointers.data(), elementPointers.size()); - spritechainRenderer.Render(viewerInstance, *spriteRendererData, builder, elementPointers.data(), elementPointers.size()); + spritechainRenderer.Prepare(viewerInstance, *spriteRendererData, *currentFrame, elementPointers.size(), elementPointers.data(), renderStates.data()); + spritechainRenderer.Render(viewerInstance, *spriteRendererData, builder, elementPointers.size(), elementPointers.data()); }); forwardPass.SetExecutionCallback([&] { @@ -946,12 +950,15 @@ int main() flareSprite.BuildElement(forwardPassIndex, flareInstance, elements); std::vector> elementPointers; + std::vector renderStates(elements.size()); + elementPointers.reserve(elements.size()); for (const auto& element : elements) elementPointers.emplace_back(element.get()); - spritechainRenderer.Prepare(viewerInstance, *spriteRendererData, *currentFrame, {}, elementPointers.data(), elementPointers.size()); - spritechainRenderer.Render(viewerInstance, *spriteRendererData, builder, elementPointers.data(), elementPointers.size()); + spritechainRenderer.Prepare(viewerInstance, *spriteRendererData, *currentFrame, elementPointers.size(), elementPointers.data(), renderStates.data()); + spritechainRenderer.PrepareEnd(*currentFrame, *spriteRendererData); + spritechainRenderer.Render(viewerInstance, *spriteRendererData, builder, elementPointers.size(), elementPointers.data()); }); occluderPass.AddOutput(occluderTexture); diff --git a/examples/PhysicsDemo/main.cpp b/examples/PhysicsDemo/main.cpp index 39e999e2d..0313da7de 100644 --- a/examples/PhysicsDemo/main.cpp +++ b/examples/PhysicsDemo/main.cpp @@ -54,7 +54,7 @@ int main() Nz::MeshParams meshParams; meshParams.center = true; meshParams.matrix = Nz::Matrix4f::Rotate(Nz::EulerAnglesf(0.f, 90.f, 0.f)) * Nz::Matrix4f::Scale(Nz::Vector3f(0.002f)); - meshParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ_UV); + meshParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ_Normal_UV_Tangent); std::shared_ptr spaceshipMesh = Nz::Mesh::LoadFromFile(resourceDir / "Spaceship/spaceship.obj", meshParams); if (!spaceshipMesh) @@ -71,10 +71,10 @@ int main() std::shared_ptr depthPass = std::make_shared(Nz::DepthMaterial::GetSettings()); depthPass->EnableDepthBuffer(true); - depthPass->EnableDepthClamp(true); + //depthPass->EnableDepthClamp(true); depthPass->EnableFaceCulling(true); - std::shared_ptr materialPass = std::make_shared(Nz::BasicMaterial::GetSettings()); + std::shared_ptr materialPass = std::make_shared(Nz::PhongLightingMaterial::GetSettings()); materialPass->EnableDepthBuffer(true); materialPass->EnableDepthClamp(true); materialPass->EnableFaceCulling(true); @@ -91,7 +91,7 @@ int main() Nz::BasicMaterial basicMat(*materialPass); basicMat.EnableAlphaTest(false); - basicMat.SetAlphaMap(Nz::Texture::LoadFromFile(resourceDir / "alphatile.png", texParams)); + //basicMat.SetAlphaMap(Nz::Texture::LoadFromFile(resourceDir / "alphatile.png", texParams)); basicMat.SetDiffuseMap(Nz::Texture::LoadFromFile(resourceDir / "Spaceship/Texture/diffuse.png", texParams)); basicMat.SetDiffuseSampler(samplerInfo); @@ -143,37 +143,12 @@ int main() Nz::Physics3DSystem physSytem(registry); Nz::RenderSystem renderSystem(registry); - Nz::Canvas canvas2D(registry, window.GetEventHandler(), window.GetCursorController().CreateHandle(), 2); - canvas2D.Resize(Nz::Vector2f(window.GetSize())); - - Nz::LabelWidget* labelWidget = canvas2D.Add(); - labelWidget->SetPosition(0.f, 300.f, 0.f); - labelWidget->EnableBackground(true); - labelWidget->UpdateText(Nz::SimpleTextDrawer::Draw("Bonjour Paris !", 72)); - - Nz::ButtonWidget* buttonWidget = canvas2D.Add(); - buttonWidget->SetPosition(200.f, 400.f); - buttonWidget->UpdateText(Nz::SimpleTextDrawer::Draw("Press me senpai", 72)); - buttonWidget->Resize(buttonWidget->GetPreferredSize()); - buttonWidget->OnButtonTrigger.Connect([](const Nz::ButtonWidget*) - { - std::cout << "Coucou !" << std::endl; - }); - - entt::entity viewer2D = registry.create(); - { - registry.emplace(viewer2D); - auto& cameraComponent = registry.emplace(viewer2D, window.GetRenderTarget(), Nz::ProjectionType::Orthographic); - cameraComponent.UpdateClearColor(Nz::Color(0, 0, 0, 0)); - cameraComponent.UpdateRenderOrder(1); - cameraComponent.UpdateRenderMask(2); - } - entt::entity viewer = registry.create(); { registry.emplace(viewer); auto& cameraComponent = registry.emplace(viewer, window.GetRenderTarget()); cameraComponent.UpdateRenderMask(1); + //cameraComponent.UpdateClearColor(Nz::Color(127, 127, 127)); } auto shipCollider = std::make_shared(vertices, vertexMapper.GetVertexCount(), 0.01f); @@ -211,6 +186,9 @@ int main() entt::entity headingEntity = registry.create(); { + auto& entityLight = registry.emplace(playerEntity); + entityLight.AttachLight(std::make_shared(), 1); + auto& entityGfx = registry.emplace(playerEntity); entityGfx.AttachRenderable(model, 1); @@ -287,6 +265,7 @@ int main() case Nz::WindowEventType::KeyPressed: if (event.key.virtualKey == Nz::Keyboard::VKey::A) { + //canvas2D.Resize({ 1920.f, 1080.f }); basicMat.EnableAlphaTest(!basicMat.IsAlphaTestEnabled()); basicMatDepth.EnableAlphaTest(!basicMatDepth.IsAlphaTestEnabled()); } diff --git a/examples/WidgetDemo/main.cpp b/examples/WidgetDemo/main.cpp new file mode 100644 index 000000000..9f86592a8 --- /dev/null +++ b/examples/WidgetDemo/main.cpp @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +NAZARA_REQUEST_DEDICATED_GPU() + +int main() +{ + std::filesystem::path resourceDir = "resources"; + if (!std::filesystem::is_directory(resourceDir) && std::filesystem::is_directory(".." / resourceDir)) + resourceDir = ".." / resourceDir; + + Nz::Renderer::Config rendererConfig; + std::cout << "Run using Vulkan? (y/n)" << std::endl; + if (std::getchar() != 'n') + rendererConfig.preferredAPI = Nz::RenderAPI::Vulkan; + else + rendererConfig.preferredAPI = Nz::RenderAPI::OpenGL; + + std::cin.ignore(std::numeric_limits::max(), '\n'); + + Nz::Modules nazara(rendererConfig); + + Nz::RenderWindow window; + + std::shared_ptr device = Nz::Graphics::Instance()->GetRenderDevice(); + + std::string windowTitle = "Widget Test"; + if (!window.Create(device, Nz::VideoMode(1920, 1080, 32), windowTitle)) + { + std::cout << "Failed to create Window" << std::endl; + return __LINE__; + } + + entt::registry registry; + Nz::RenderSystem renderSystem(registry); + + Nz::Canvas canvas2D(registry, window.GetEventHandler(), window.GetCursorController().CreateHandle(), 0xFFFFFFFF); + canvas2D.Resize(Nz::Vector2f(window.GetSize())); + + Nz::LabelWidget* labelWidget = canvas2D.Add(); + labelWidget->SetPosition(0.f, 200.f, 0.f); + labelWidget->UpdateText(Nz::SimpleTextDrawer::Draw("Je suis un LabelWidget !", 72)); + + Nz::ButtonWidget* buttonWidget = canvas2D.Add(); + buttonWidget->SetPosition(200.f, 400.f); + buttonWidget->UpdateText(Nz::SimpleTextDrawer::Draw("Press me senpai", 72)); + buttonWidget->Resize(buttonWidget->GetPreferredSize()); + buttonWidget->OnButtonTrigger.Connect([=](const Nz::ButtonWidget*) + { + buttonWidget->Destroy(); + }); + + std::shared_ptr material = std::make_shared(); + + std::shared_ptr materialPass = std::make_shared(Nz::BasicMaterial::GetSettings()); + material->AddPass("ForwardPass", materialPass); + + Nz::TextureSamplerInfo samplerInfo; + samplerInfo.anisotropyLevel = 8; + + Nz::TextureParams texParams; + texParams.renderDevice = device; + texParams.loadFormat = Nz::PixelFormat::RGBA8_SRGB; + + Nz::BasicMaterial basicMat(*materialPass); + basicMat.SetDiffuseMap(Nz::Texture::LoadFromFile(resourceDir / "Spaceship/Texture/diffuse.png", texParams)); + basicMat.SetDiffuseSampler(samplerInfo); + + Nz::ImageWidget* imageWidget = canvas2D.Add(); + imageWidget->SetPosition(1200.f, 200.f); + imageWidget->SetMaterial(material); + imageWidget->Resize(imageWidget->GetPreferredSize() / 4.f); + + Nz::TextAreaWidget* textAreaWidget = canvas2D.Add(); + textAreaWidget->SetPosition(800.f, 500.f); + textAreaWidget->SetText("Je suis un TextAreaWidget !"); + textAreaWidget->Resize(Nz::Vector2f(400.f, textAreaWidget->GetPreferredHeight() * 5.f)); + textAreaWidget->SetBackgroundColor(Nz::Color::White); + textAreaWidget->SetTextColor(Nz::Color::Black); + textAreaWidget->EnableMultiline(true); + + Nz::CheckboxWidget* checkboxWidget = canvas2D.Add(); + //checkboxWidget->EnableTristate(true); + checkboxWidget->SetPosition(800.f, 800.f); + checkboxWidget->Resize({ 256.f, 256.f }); + checkboxWidget->SetState(true); + + /*Nz::TextAreaWidget* textAreaWidget2 = canvas2D.Add(); + textAreaWidget2->SetPosition(800.f, 700.f); + textAreaWidget2->SetText("Je suis un autre TextAreaWidget !"); + textAreaWidget2->Resize(Nz::Vector2f(500.f, textAreaWidget2->GetPreferredHeight())); + textAreaWidget2->SetBackgroundColor(Nz::Color::White); + textAreaWidget2->SetTextColor(Nz::Color::Black);*/ + + entt::entity viewer2D = registry.create(); + { + registry.emplace(viewer2D); + auto& cameraComponent = registry.emplace(viewer2D, window.GetRenderTarget(), Nz::ProjectionType::Orthographic); + cameraComponent.UpdateClearColor(Nz::Color(173, 216, 230, 255)); + } + + window.EnableEventPolling(true); + + Nz::Clock updateClock; + Nz::Clock secondClock; + unsigned int fps = 0; + + float elapsedTime = 0.f; + Nz::UInt64 time = Nz::GetElapsedMicroseconds(); + + while (window.IsOpen()) + { + Nz::UInt64 now = Nz::GetElapsedMicroseconds(); + elapsedTime = (now - time) / 1'000'000.f; + time = now; + + Nz::WindowEvent event; + while (window.PollEvent(&event)) + { + switch (event.type) + { + case Nz::WindowEventType::Quit: + window.Close(); + break; + + default: + break; + } + } + + Nz::RenderFrame frame = window.AcquireFrame(); + if (!frame) + continue; + + renderSystem.Render(registry, frame); + + frame.Present(); + + fps++; + + if (secondClock.GetMilliseconds() >= 1000) + { + window.SetTitle(windowTitle + " - " + Nz::NumberToString(fps) + " FPS" + " - " + Nz::NumberToString(registry.alive()) + " entities"); + + fps = 0; + + secondClock.Restart(); + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/WidgetDemo/xmake.lua b/examples/WidgetDemo/xmake.lua new file mode 100644 index 000000000..505302ef4 --- /dev/null +++ b/examples/WidgetDemo/xmake.lua @@ -0,0 +1,6 @@ +target("WidgetDemo") + set_group("Examples") + set_kind("binary") + add_deps("NazaraGraphics", "NazaraPhysics3D", "NazaraWidgets") + add_packages("entt") + add_files("main.cpp") diff --git a/include/Nazara/Graphics.hpp b/include/Nazara/Graphics.hpp index 8153980dc..409850c9f 100644 --- a/include/Nazara/Graphics.hpp +++ b/include/Nazara/Graphics.hpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +64,7 @@ #include #include #include +#include #include #include #include diff --git a/include/Nazara/Graphics/Components.hpp b/include/Nazara/Graphics/Components.hpp index fd126daa2..f0525b030 100644 --- a/include/Nazara/Graphics/Components.hpp +++ b/include/Nazara/Graphics/Components.hpp @@ -31,5 +31,6 @@ #include #include +#include #endif // NAZARA_GRAPHICS_COMPONENTS_HPP diff --git a/include/Nazara/Graphics/Components/LightComponent.hpp b/include/Nazara/Graphics/Components/LightComponent.hpp new file mode 100644 index 000000000..5101d746a --- /dev/null +++ b/include/Nazara/Graphics/Components/LightComponent.hpp @@ -0,0 +1,64 @@ +// 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 + +#pragma once + +#ifndef NAZARA_GRAPHICS_COMPONENTS_LIGHTCOMPONENT_HPP +#define NAZARA_GRAPHICS_COMPONENTS_LIGHTCOMPONENT_HPP + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_GRAPHICS_API LightComponent + { + public: + struct LightEntry; + + inline LightComponent(bool initialyVisible = true); + LightComponent(const LightComponent&) = default; + LightComponent(LightComponent&&) = default; + ~LightComponent() = default; + + inline void AttachLight(std::shared_ptr renderable, UInt32 renderMask); + + inline void Clear(); + + inline void DetachLight(const std::shared_ptr& renderable); + + inline const std::vector& GetLights() const; + + inline void Hide(); + + inline bool IsVisible() const; + + inline void Show(bool show = true); + + LightComponent& operator=(const LightComponent&) = default; + LightComponent& operator=(LightComponent&&) = default; + + NazaraSignal(OnLightAttached, LightComponent* /*graphicsComponent*/, const LightEntry& /*lightEntry*/); + NazaraSignal(OnLightDetach, LightComponent* /*graphicsComponent*/, const LightEntry& /*lightEntry*/); + NazaraSignal(OnVisibilityUpdate, LightComponent* /*graphicsComponent*/, bool /*newVisibilityState*/); + + struct LightEntry + { + std::shared_ptr light; + UInt32 renderMask; + }; + + private: + std::vector m_lightEntries; + bool m_isVisible; + }; +} + +#include + +#endif // NAZARA_GRAPHICS_COMPONENTS_LIGHTCOMPONENT_HPP diff --git a/include/Nazara/Graphics/Components/LightComponent.inl b/include/Nazara/Graphics/Components/LightComponent.inl new file mode 100644 index 000000000..61b2a9595 --- /dev/null +++ b/include/Nazara/Graphics/Components/LightComponent.inl @@ -0,0 +1,68 @@ +// 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 +#include + +namespace Nz +{ + inline LightComponent::LightComponent(bool initialyVisible) : + m_isVisible(initialyVisible) + { + } + + inline void LightComponent::AttachLight(std::shared_ptr light, UInt32 renderMask) + { + auto& entry = m_lightEntries.emplace_back(); + entry.light = std::move(light); + entry.renderMask = renderMask; + + OnLightAttached(this, m_lightEntries.back()); + } + + inline void LightComponent::Clear() + { + for (const auto& lightEntry : m_lightEntries) + OnLightDetach(this, lightEntry); + + m_lightEntries.clear(); + } + + inline void LightComponent::DetachLight(const std::shared_ptr& light) + { + auto it = std::find_if(m_lightEntries.begin(), m_lightEntries.end(), [&](const auto& lightEntry) { return lightEntry.light == light; }); + if (it != m_lightEntries.end()) + { + OnLightDetach(this, *it); + + m_lightEntries.erase(it); + } + } + + inline auto LightComponent::GetLights() const -> const std::vector& + { + return m_lightEntries; + } + + inline void LightComponent::Hide() + { + return Show(false); + } + + inline bool LightComponent::IsVisible() const + { + return m_isVisible; + } + + inline void LightComponent::Show(bool show) + { + if (m_isVisible != show) + { + OnVisibilityUpdate(this, show); + m_isVisible = show; + } + } +} + +#include diff --git a/include/Nazara/Graphics/DirectionalLight.hpp b/include/Nazara/Graphics/DirectionalLight.hpp new file mode 100644 index 000000000..aea839060 --- /dev/null +++ b/include/Nazara/Graphics/DirectionalLight.hpp @@ -0,0 +1,61 @@ +// 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 + +#pragma once + +#ifndef NAZARA_GRAPHICS_DIRECTIONALLIGHT_HPP +#define NAZARA_GRAPHICS_DIRECTIONALLIGHT_HPP + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_GRAPHICS_API DirectionalLight : public Light + { + public: + DirectionalLight(); + DirectionalLight(const DirectionalLight&) = delete; + DirectionalLight(DirectionalLight&&) noexcept = default; + ~DirectionalLight() = default; + + float ComputeContributionScore(const BoundingVolumef& boundingVolume) const override; + + void FillLightData(void* data) override; + + inline float GetAmbientFactor() const; + inline float GetDiffuseFactor() const; + inline Color GetColor() const; + inline const Vector3f& GetDirection() const; + inline const Quaternionf& GetRotation() const; + + inline void UpdateAmbientFactor(float factor); + inline void UpdateColor(Color color); + inline void UpdateDiffuseFactor(float factor); + inline void UpdateDirection(const Vector3f& direction); + inline void UpdateRotation(const Quaternionf& rotation); + + void UpdateTransform(const Vector3f& position, const Quaternionf& rotation, const Vector3f& scale) override; + + DirectionalLight& operator=(const DirectionalLight&) = delete; + DirectionalLight& operator=(DirectionalLight&&) noexcept = default; + + private: + inline void UpdateBoundingVolume(); + + Color m_color; + Quaternionf m_rotation; + Vector3f m_direction; + float m_ambientFactor; + float m_diffuseFactor; + }; +} + +#include + +#endif // NAZARA_GRAPHICS_DIRECTIONALLIGHT_HPP diff --git a/include/Nazara/Graphics/DirectionalLight.inl b/include/Nazara/Graphics/DirectionalLight.inl new file mode 100644 index 000000000..d0a28a6ac --- /dev/null +++ b/include/Nazara/Graphics/DirectionalLight.inl @@ -0,0 +1,85 @@ +// 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 +#include +#include +#include + +namespace Nz +{ + inline DirectionalLight::DirectionalLight() : + m_color(Color::White), + m_ambientFactor(0.2f), + m_diffuseFactor(1.f) + { + UpdateRotation(Quaternionf::Identity()); + } + + inline float DirectionalLight::GetAmbientFactor() const + { + return m_ambientFactor; + } + + inline Color DirectionalLight::GetColor() const + { + return m_color; + } + + inline const Vector3f& DirectionalLight::GetDirection() const + { + return m_direction; + } + + inline const Quaternionf& DirectionalLight::GetRotation() const + { + return m_rotation; + } + + inline float DirectionalLight::GetDiffuseFactor() const + { + return m_diffuseFactor; + } + + inline void DirectionalLight::UpdateAmbientFactor(float factor) + { + m_ambientFactor = factor; + + OnLightDataInvalided(this); + } + + inline void DirectionalLight::UpdateColor(Color color) + { + m_color = color; + + OnLightDataInvalided(this); + } + + inline void DirectionalLight::UpdateDiffuseFactor(float factor) + { + m_diffuseFactor = factor; + + OnLightDataInvalided(this); + } + + inline void DirectionalLight::UpdateDirection(const Vector3f& direction) + { + UpdateRotation(Quaternionf::RotationBetween(Vector3f::Forward(), direction)); + } + + inline void DirectionalLight::UpdateRotation(const Quaternionf& rotation) + { + m_rotation = rotation; + m_direction = rotation * Vector3f::Forward(); + + UpdateBoundingVolume(); + } + + inline void DirectionalLight::UpdateBoundingVolume() + { + Light::UpdateBoundingVolume(BoundingVolumef::Infinite()); //< will trigger OnLightDataInvalided + } +} + +#include diff --git a/include/Nazara/Graphics/ElementRenderer.hpp b/include/Nazara/Graphics/ElementRenderer.hpp index d9e974f2d..cc0bc674e 100644 --- a/include/Nazara/Graphics/ElementRenderer.hpp +++ b/include/Nazara/Graphics/ElementRenderer.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -18,7 +19,6 @@ namespace Nz { class CommandBufferBuilder; - class RenderBuffer; class RenderElement; class RenderFrame; class ViewerInstance; @@ -33,13 +33,14 @@ namespace Nz virtual ~ElementRenderer(); virtual std::unique_ptr InstanciateData() = 0; - virtual void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, const RenderStates& renderStates, const Pointer* elements, std::size_t elementCount); - virtual void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, const Pointer* elements, std::size_t elementCount) = 0; + virtual void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, const RenderStates* renderStates); + virtual void PrepareEnd(RenderFrame& currentFrame, ElementRendererData& rendererData); + virtual void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t elementCount, const Pointer* elements) = 0; virtual void Reset(ElementRendererData& rendererData, RenderFrame& currentFrame); struct RenderStates { - std::optional lightData; + RenderBufferView lightData; }; }; diff --git a/include/Nazara/Graphics/ForwardFramePipeline.hpp b/include/Nazara/Graphics/ForwardFramePipeline.hpp index 43399deda..f7419679a 100644 --- a/include/Nazara/Graphics/ForwardFramePipeline.hpp +++ b/include/Nazara/Graphics/ForwardFramePipeline.hpp @@ -13,11 +13,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -25,6 +27,7 @@ namespace Nz { + class PointLight; class RenderFrame; class RenderTarget; @@ -40,16 +43,20 @@ namespace Nz void InvalidateWorldInstance(WorldInstance* worldInstance) override; void RegisterInstancedDrawable(WorldInstancePtr worldInstance, const InstancedRenderable* instancedRenderable, UInt32 renderMask) override; + void RegisterLight(std::shared_ptr light, UInt32 renderMask) override; void RegisterViewer(AbstractViewer* viewerInstance, Int32 renderOrder) override; void Render(RenderFrame& renderFrame) override; void UnregisterInstancedDrawable(const WorldInstancePtr& worldInstance, const InstancedRenderable* instancedRenderable) override; + void UnregisterLight(Light* light) override; void UnregisterViewer(AbstractViewer* viewerInstance) override; ForwardFramePipeline& operator=(const ForwardFramePipeline&) = delete; ForwardFramePipeline& operator=(ForwardFramePipeline&&) = delete; + static constexpr std::size_t MaxLightCountPerDraw = 3; + private: BakedFrameGraph BuildFrameGraph(); @@ -59,6 +66,28 @@ namespace Nz struct ViewerData; + struct LightData + { + std::shared_ptr light; + UInt32 renderMask; + + NazaraSlot(Light, OnLightDataInvalided, onLightInvalidated); + }; + + using LightKey = std::array; + + struct LightKeyHasher + { + inline std::size_t operator()(const LightKey& lightKey) const; + }; + + struct LightDataUbo + { + std::shared_ptr renderBuffer; + std::size_t offset = 0; + UploadPool::Allocation* allocation = nullptr; + }; + struct MaterialData { std::size_t usedCount = 0; @@ -92,6 +121,8 @@ namespace Nz std::size_t colorAttachment; std::size_t depthStencilAttachment; std::size_t visibilityHash = 0; + std::unordered_map lightPerRenderElement; + std::unordered_map lightBufferPerLights; std::vector> depthPrepassRenderElements; std::vector> forwardRenderElements; std::vector> elementRendererData; @@ -108,8 +139,8 @@ namespace Nz std::size_t m_depthPassIndex; std::size_t m_forwardPassIndex; - std::shared_ptr m_lightDataBuffer; std::unordered_map m_viewers; + std::unordered_map m_lights; std::unordered_map m_materials; std::unordered_map> m_renderables; std::unordered_map m_renderTargets; @@ -118,6 +149,9 @@ namespace Nz std::unordered_set m_invalidatedWorldInstances; std::unordered_set m_removedWorldInstances; std::vector> m_elementRenderers; + std::vector m_renderStates; + std::vector m_visibleLights; + std::vector m_lightDataBuffers; std::vector m_visibleRenderables; BakedFrameGraph m_bakedFrameGraph; RenderFrame* m_currentRenderFrame; diff --git a/include/Nazara/Graphics/ForwardFramePipeline.inl b/include/Nazara/Graphics/ForwardFramePipeline.inl index 834d564f6..8662726b6 100644 --- a/include/Nazara/Graphics/ForwardFramePipeline.inl +++ b/include/Nazara/Graphics/ForwardFramePipeline.inl @@ -3,10 +3,25 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include #include namespace Nz { + inline std::size_t ForwardFramePipeline::LightKeyHasher::operator()(const LightKey& lightKey) const + { + std::size_t lightHash = 5; + auto CombineHash = [](std::size_t currentHash, std::size_t newHash) + { + return currentHash * 23 + newHash; + }; + + std::hash lightPtrHasher; + for (std::size_t i = 0; i < lightKey.size(); ++i) + lightHash = CombineHash(lightHash, lightPtrHasher(lightKey[i])); + + return lightHash; + } } #include diff --git a/include/Nazara/Graphics/FramePipeline.hpp b/include/Nazara/Graphics/FramePipeline.hpp index d00260b9d..d9fd43a9b 100644 --- a/include/Nazara/Graphics/FramePipeline.hpp +++ b/include/Nazara/Graphics/FramePipeline.hpp @@ -15,6 +15,7 @@ namespace Nz { class AbstractViewer; class InstancedRenderable; + class Light; class RenderFrame; class NAZARA_GRAPHICS_API FramePipeline @@ -29,11 +30,13 @@ namespace Nz virtual void InvalidateWorldInstance(WorldInstance* worldInstance) = 0; virtual void RegisterInstancedDrawable(WorldInstancePtr worldInstance, const InstancedRenderable* instancedRenderable, UInt32 renderMask) = 0; + virtual void RegisterLight(std::shared_ptr light, UInt32 renderMask) = 0; virtual void RegisterViewer(AbstractViewer* viewerInstance, Int32 renderOrder) = 0; virtual void Render(RenderFrame& renderFrame) = 0; virtual void UnregisterInstancedDrawable(const WorldInstancePtr& worldInstance, const InstancedRenderable* instancedRenderable) = 0; + virtual void UnregisterLight(Light* light) = 0; virtual void UnregisterViewer(AbstractViewer* viewerInstance) = 0; FramePipeline& operator=(const FramePipeline&) = delete; diff --git a/include/Nazara/Graphics/Light.hpp b/include/Nazara/Graphics/Light.hpp new file mode 100644 index 000000000..7f05cb507 --- /dev/null +++ b/include/Nazara/Graphics/Light.hpp @@ -0,0 +1,55 @@ +// 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 + +#pragma once + +#ifndef NAZARA_GRAPHICS_LIGHT_HPP +#define NAZARA_GRAPHICS_LIGHT_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class CommandBufferBuilder; + class RenderBuffer; + class RenderFrame; + + class NAZARA_GRAPHICS_API Light + { + public: + inline Light(); + Light(const Light&) = delete; + Light(Light&&) noexcept = default; + virtual ~Light(); + + virtual float ComputeContributionScore(const BoundingVolumef& boundingVolume) const = 0; + + virtual void FillLightData(void* data) = 0; + + inline const BoundingVolumef& GetBoundingVolume() const; + + virtual void UpdateTransform(const Vector3f& position, const Quaternionf& rotation, const Vector3f& scale) = 0; + + Light& operator=(const Light&) = delete; + Light& operator=(Light&&) noexcept = default; + + NazaraSignal(OnLightDataInvalided, Light* /*emitter*/); + + protected: + inline void UpdateBoundingVolume(const BoundingVolumef& boundingVolume); + + private: + BoundingVolumef m_boundingVolume; + }; +} + +#include + +#endif // NAZARA_GRAPHICS_LIGHT_HPP diff --git a/include/Nazara/Graphics/Light.inl b/include/Nazara/Graphics/Light.inl new file mode 100644 index 000000000..2680e37a8 --- /dev/null +++ b/include/Nazara/Graphics/Light.inl @@ -0,0 +1,29 @@ +// 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 +#include +#include + +namespace Nz +{ + inline Light::Light() : + m_boundingVolume(BoundingVolumef::Null()) + { + } + + inline const BoundingVolumef& Light::GetBoundingVolume() const + { + return m_boundingVolume; + } + + inline void Light::UpdateBoundingVolume(const BoundingVolumef& boundingVolume) + { + m_boundingVolume = boundingVolume; + + OnLightDataInvalided(this); + } +} + +#include diff --git a/include/Nazara/Graphics/PointLight.hpp b/include/Nazara/Graphics/PointLight.hpp new file mode 100644 index 000000000..08ae44ce5 --- /dev/null +++ b/include/Nazara/Graphics/PointLight.hpp @@ -0,0 +1,61 @@ +// 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 + +#pragma once + +#ifndef NAZARA_GRAPHICS_POINTLIGHT_HPP +#define NAZARA_GRAPHICS_POINTLIGHT_HPP + +#include +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_GRAPHICS_API PointLight : public Light + { + public: + PointLight(); + PointLight(const PointLight&) = delete; + PointLight(PointLight&&) noexcept = default; + ~PointLight() = default; + + float ComputeContributionScore(const BoundingVolumef& boundingVolume) const override; + + void FillLightData(void* data) override; + + inline float GetAmbientFactor() const; + inline float GetDiffuseFactor() const; + inline Color GetColor() const; + inline const Vector3f& GetPosition() const; + inline float GetRadius() const; + + inline void UpdateAmbientFactor(float factor); + inline void UpdateColor(Color color); + inline void UpdateDiffuseFactor(float factor); + inline void UpdatePosition(const Vector3f& position); + inline void UpdateRadius(float radius); + + void UpdateTransform(const Vector3f& position, const Quaternionf& rotation, const Vector3f& scale) override; + + PointLight& operator=(const PointLight&) = delete; + PointLight& operator=(PointLight&&) noexcept = default; + + private: + inline void UpdateBoundingVolume(); + + Color m_color; + Vector3f m_position; + float m_ambientFactor; + float m_diffuseFactor; + float m_invRadius; + float m_radius; + }; +} + +#include + +#endif // NAZARA_GRAPHICS_POINTLIGHT_HPP diff --git a/include/Nazara/Graphics/PointLight.inl b/include/Nazara/Graphics/PointLight.inl new file mode 100644 index 000000000..a054667ff --- /dev/null +++ b/include/Nazara/Graphics/PointLight.inl @@ -0,0 +1,91 @@ +// 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 +#include +#include + +namespace Nz +{ + inline PointLight::PointLight() : + m_color(Color::White), + m_position(Vector3f::Zero()), + m_ambientFactor(0.2f), + m_diffuseFactor(1.f) + { + UpdateRadius(5.f); + } + + inline float PointLight::GetAmbientFactor() const + { + return m_ambientFactor; + } + + inline Color PointLight::GetColor() const + { + return m_color; + } + + inline const Vector3f& PointLight::GetPosition() const + { + return m_position; + } + + inline float PointLight::GetDiffuseFactor() const + { + return m_diffuseFactor; + } + + inline float PointLight::GetRadius() const + { + return m_radius; + } + + inline void PointLight::UpdateAmbientFactor(float factor) + { + m_ambientFactor = factor; + + OnLightDataInvalided(this); + } + + inline void PointLight::UpdateColor(Color color) + { + m_color = color; + + OnLightDataInvalided(this); + } + + inline void PointLight::UpdateDiffuseFactor(float factor) + { + m_diffuseFactor = factor; + + OnLightDataInvalided(this); + } + + inline void PointLight::UpdatePosition(const Vector3f& position) + { + m_position = position; + + UpdateBoundingVolume(); + } + + inline void PointLight::UpdateRadius(float radius) + { + m_radius = radius; + m_invRadius = 1.f / m_radius; + + UpdateBoundingVolume(); + } + + inline void PointLight::UpdateBoundingVolume() + { + Vector3f extent = Vector3f(m_radius, m_radius, m_radius) * Sqrt3; + BoundingVolumef boundingVolume(Boxf(-extent, extent)); + boundingVolume.Update(m_position); + + Light::UpdateBoundingVolume(boundingVolume); //< will trigger OnLightDataInvalided + } +} + +#include diff --git a/include/Nazara/Graphics/RenderElement.hpp b/include/Nazara/Graphics/RenderElement.hpp index 604af6b6c..4edf6bdcf 100644 --- a/include/Nazara/Graphics/RenderElement.hpp +++ b/include/Nazara/Graphics/RenderElement.hpp @@ -25,7 +25,7 @@ namespace Nz inline RenderElement(UInt8 elementType); virtual ~RenderElement(); - virtual UInt64 ComputeSortingScore(const Nz::Frustumf& frustum, const RenderQueueRegistry& registry) const = 0; + virtual UInt64 ComputeSortingScore(const Frustumf& frustum, const RenderQueueRegistry& registry) const = 0; inline UInt8 GetElementType() const; diff --git a/include/Nazara/Graphics/SpotLight.hpp b/include/Nazara/Graphics/SpotLight.hpp new file mode 100644 index 000000000..cba896c4c --- /dev/null +++ b/include/Nazara/Graphics/SpotLight.hpp @@ -0,0 +1,77 @@ +// 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 + +#pragma once + +#ifndef NAZARA_GRAPHICS_SPOTLIGHT_HPP +#define NAZARA_GRAPHICS_SPOTLIGHT_HPP + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_GRAPHICS_API SpotLight : public Light + { + public: + SpotLight(); + SpotLight(const SpotLight&) = delete; + SpotLight(SpotLight&&) noexcept = default; + ~SpotLight() = default; + + float ComputeContributionScore(const BoundingVolumef& boundingVolume) const override; + + void FillLightData(void* data) override; + + inline float GetAmbientFactor() const; + inline float GetDiffuseFactor() const; + inline Color GetColor() const; + inline const Vector3f& GetDirection() const; + inline RadianAnglef GetInnerAngle() const; + inline RadianAnglef GetOuterAngle() const; + inline const Vector3f& GetPosition() const; + inline const Quaternionf& GetRotation() const; + inline float GetRadius() const; + + inline void UpdateAmbientFactor(float factor); + inline void UpdateColor(Color color); + inline void UpdateDiffuseFactor(float factor); + inline void UpdateDirection(const Vector3f& direction); + inline void UpdateInnerAngle(RadianAnglef innerAngle); + inline void UpdateOuterAngle(RadianAnglef outerAngle); + inline void UpdatePosition(const Vector3f& position); + inline void UpdateRadius(float radius); + inline void UpdateRotation(const Quaternionf& rotation); + + void UpdateTransform(const Vector3f& position, const Quaternionf& rotation, const Vector3f& scale) override; + + SpotLight& operator=(const SpotLight&) = delete; + SpotLight& operator=(SpotLight&&) noexcept = default; + + private: + inline void UpdateBoundingVolume(); + + Color m_color; + Quaternionf m_rotation; + RadianAnglef m_innerAngle; + RadianAnglef m_outerAngle; + Vector3f m_direction; + Vector3f m_position; + float m_ambientFactor; + float m_diffuseFactor; + float m_invRadius; + float m_radius; + float m_innerAngleCos; + float m_outerAngleCos; + float m_outerAngleTan; + }; +} + +#include + +#endif // NAZARA_GRAPHICS_SPOTLIGHT_HPP diff --git a/include/Nazara/Graphics/SpotLight.inl b/include/Nazara/Graphics/SpotLight.inl new file mode 100644 index 000000000..c96120d3a --- /dev/null +++ b/include/Nazara/Graphics/SpotLight.inl @@ -0,0 +1,161 @@ +// 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 +#include +#include + +namespace Nz +{ + inline SpotLight::SpotLight() : + m_color(Color::White), + m_position(Vector3f::Zero()), + m_ambientFactor(0.2f), + m_diffuseFactor(1.f) + { + UpdateInnerAngle(DegreeAnglef(30.f)); + UpdateOuterAngle(DegreeAnglef(45.f)); + UpdateRadius(5.f); + UpdateRotation(Quaternionf::Identity()); + } + + inline float SpotLight::GetAmbientFactor() const + { + return m_ambientFactor; + } + + inline Color SpotLight::GetColor() const + { + return m_color; + } + + inline const Vector3f& SpotLight::GetDirection() const + { + return m_direction; + } + + inline RadianAnglef SpotLight::GetInnerAngle() const + { + return m_innerAngle; + } + + inline RadianAnglef SpotLight::GetOuterAngle() const + { + return m_outerAngle; + } + + inline const Vector3f& SpotLight::GetPosition() const + { + return m_position; + } + + inline const Quaternionf& SpotLight::GetRotation() const + { + return m_rotation; + } + + inline float SpotLight::GetDiffuseFactor() const + { + return m_diffuseFactor; + } + + inline float SpotLight::GetRadius() const + { + return m_radius; + } + + inline void SpotLight::UpdateAmbientFactor(float factor) + { + m_ambientFactor = factor; + + OnLightDataInvalided(this); + } + + inline void SpotLight::UpdateColor(Color color) + { + m_color = color; + + OnLightDataInvalided(this); + } + + inline void SpotLight::UpdateDiffuseFactor(float factor) + { + m_diffuseFactor = factor; + + OnLightDataInvalided(this); + } + + inline void SpotLight::UpdateDirection(const Vector3f& direction) + { + UpdateRotation(Quaternionf::RotationBetween(Vector3f::Forward(), direction)); + } + + inline void SpotLight::UpdateInnerAngle(RadianAnglef innerAngle) + { + m_innerAngle = innerAngle; + m_innerAngleCos = m_innerAngle.GetCos(); + + UpdateBoundingVolume(); + } + + inline void SpotLight::UpdateOuterAngle(RadianAnglef outerAngle) + { + m_outerAngle = outerAngle; + m_outerAngleCos = m_outerAngle.GetCos(); + m_outerAngleTan = m_outerAngle.GetTan(); + + UpdateBoundingVolume(); + } + + inline void SpotLight::UpdatePosition(const Vector3f& position) + { + m_position = position; + + UpdateBoundingVolume(); + } + + inline void SpotLight::UpdateRadius(float radius) + { + m_radius = radius; + m_invRadius = 1.f / m_radius; + + UpdateBoundingVolume(); + } + + inline void SpotLight::UpdateRotation(const Quaternionf& rotation) + { + m_rotation = rotation; + m_direction = rotation * Vector3f::Forward(); + + UpdateBoundingVolume(); + } + + inline void SpotLight::UpdateBoundingVolume() + { + // We make a box center in the origin + Boxf box(Vector3f::Zero()); + + // We compute the other points + Vector3f base(Vector3f::Forward() * m_radius); + + // Now we need the radius of the projected circle depending on the distance + // Tangent = Opposite/Adjacent <=> Opposite = Adjacent * Tangent + float radius = m_radius * m_outerAngleTan; + Vector3f lExtend = Vector3f::Left() * radius; + Vector3f uExtend = Vector3f::Up() * radius; + + // And we add the four extremities of our pyramid + box.ExtendTo(base + lExtend + uExtend); + box.ExtendTo(base + lExtend - uExtend); + box.ExtendTo(base - lExtend + uExtend); + box.ExtendTo(base - lExtend - uExtend); + + BoundingVolumef boundingVolume(box); + boundingVolume.Update(Matrix4f::Transform(m_position, m_rotation)); + + Light::UpdateBoundingVolume(boundingVolume); //< will trigger OnLightDataInvalided + } +} + +#include diff --git a/include/Nazara/Graphics/SpriteChainRenderer.hpp b/include/Nazara/Graphics/SpriteChainRenderer.hpp index 0c1f20b04..3193e2501 100644 --- a/include/Nazara/Graphics/SpriteChainRenderer.hpp +++ b/include/Nazara/Graphics/SpriteChainRenderer.hpp @@ -18,44 +18,15 @@ namespace Nz { + class MaterialPass; class RenderDevice; class RenderPipeline; class RenderSpriteChain; class ShaderBinding; - - class NAZARA_GRAPHICS_API SpriteChainRenderer : public ElementRenderer - { - public: - SpriteChainRenderer(RenderDevice& device, std::size_t maxVertexBufferSize = 32 * 1024); - ~SpriteChainRenderer() = default; - - std::unique_ptr InstanciateData() override; - void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, const RenderStates& renderStates, const Pointer* elements, std::size_t elementCount) override; - void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, const Pointer* elements, std::size_t elementCount) override; - void Reset(ElementRendererData& rendererData, RenderFrame& currentFrame) override; - - private: - struct BufferCopy - { - RenderBuffer* targetBuffer; - UploadPool::Allocation* allocation; - std::size_t size; - }; - - struct VertexBufferPool - { - std::vector> vertexBuffers; - }; - - std::shared_ptr m_indexBuffer; - std::shared_ptr m_vertexBufferPool; - std::size_t m_maxVertexBufferSize; - std::size_t m_maxVertexCount; - std::vector m_pendingCopies; - std::vector m_bindingCache; - RenderDevice& m_device; - }; - + class Texture; + class VertexDeclaration; + class WorldInstance; + struct SpriteChainRendererData : public ElementRendererData { struct DrawCall @@ -79,6 +50,62 @@ namespace Nz std::vector> vertexBuffers; std::vector shaderBindings; }; + + class NAZARA_GRAPHICS_API SpriteChainRenderer final : public ElementRenderer + { + public: + SpriteChainRenderer(RenderDevice& device, std::size_t maxVertexBufferSize = 32 * 1024); + ~SpriteChainRenderer() = default; + + std::unique_ptr InstanciateData() override; + void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, const RenderStates* renderStates) override; + void PrepareEnd(RenderFrame& currentFrame, ElementRendererData& rendererData) override; + void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t elementCount, const Pointer* elements) override; + void Reset(ElementRendererData& rendererData, RenderFrame& currentFrame) override; + + private: + void Flush(); + void FlushDrawCall(); + void FlushDrawData(); + + struct BufferCopy + { + RenderBuffer* targetBuffer; + UploadPool::Allocation* allocation; + std::size_t size; + }; + + struct PendingData + { + std::size_t firstQuadIndex = 0; + SpriteChainRendererData::DrawCall* currentDrawCall = nullptr; + UploadPool::Allocation* currentAllocation = nullptr; + UInt8* currentAllocationMemPtr = nullptr; + const VertexDeclaration* currentVertexDeclaration = nullptr; + RenderBuffer* currentVertexBuffer = nullptr; + const MaterialPass* currentMaterialPass = nullptr; + const RenderPipeline* currentPipeline = nullptr; + const ShaderBinding* currentShaderBinding = nullptr; + const Texture* currentTextureOverlay = nullptr; + const WorldInstance* currentWorldInstance = nullptr; + RenderBufferView currentLightData; + Recti currentScissorBox = Recti(-1, -1, -1, -1); + }; + + struct VertexBufferPool + { + std::vector> vertexBuffers; + }; + + std::shared_ptr m_indexBuffer; + std::shared_ptr m_vertexBufferPool; + std::size_t m_maxVertexBufferSize; + std::size_t m_maxVertexCount; + std::vector m_pendingCopies; + std::vector m_bindingCache; + PendingData m_pendingData; + RenderDevice& m_device; + }; } #include diff --git a/include/Nazara/Graphics/SubmeshRenderer.hpp b/include/Nazara/Graphics/SubmeshRenderer.hpp index fd124bc9c..6fa71df76 100644 --- a/include/Nazara/Graphics/SubmeshRenderer.hpp +++ b/include/Nazara/Graphics/SubmeshRenderer.hpp @@ -15,6 +15,7 @@ namespace Nz { class RenderPipeline; + class RenderSubmesh; class ShaderBinding; class NAZARA_GRAPHICS_API SubmeshRenderer : public ElementRenderer @@ -24,8 +25,8 @@ namespace Nz ~SubmeshRenderer() = default; std::unique_ptr InstanciateData() override; - void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, const RenderStates& renderStates, const Pointer* elements, std::size_t elementCount) override; - void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, const Pointer* elements, std::size_t elementCount) override; + void Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, const RenderStates* renderStates) override; + void Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t elementCount, const Pointer* elements) override; void Reset(ElementRendererData& rendererData, RenderFrame& currentFrame) override; private: @@ -40,10 +41,18 @@ namespace Nz const RenderBuffer* vertexBuffer; const RenderPipeline* renderPipeline; const ShaderBinding* shaderBinding; + std::size_t firstIndex; std::size_t indexCount; Recti scissorBox; }; + struct DrawCallIndices + { + std::size_t start; + std::size_t count; + }; + + std::unordered_map drawCallPerElement; std::vector drawCalls; std::vector shaderBindings; }; diff --git a/include/Nazara/Graphics/Systems/RenderSystem.hpp b/include/Nazara/Graphics/Systems/RenderSystem.hpp index af2a22e61..d90ae1cd4 100644 --- a/include/Nazara/Graphics/Systems/RenderSystem.hpp +++ b/include/Nazara/Graphics/Systems/RenderSystem.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,7 @@ namespace Nz private: void OnCameraDestroy(entt::registry& registry, entt::entity entity); void OnGraphicsDestroy(entt::registry& registry, entt::entity entity); + void OnLightDestroy(entt::registry& registry, entt::entity entity); void OnNodeDestroy(entt::registry& registry, entt::entity entity); void UpdateInstances(entt::registry& registry); void UpdateVisibility(entt::registry& registry); @@ -56,18 +58,32 @@ namespace Nz NazaraSlot(Node, OnNodeInvalidation, onNodeInvalidation); }; + struct LightEntity + { + NazaraSlot(LightComponent, OnLightAttached, onLightAttached); + NazaraSlot(LightComponent, OnLightDetach, onLightDetach); + NazaraSlot(LightComponent, OnVisibilityUpdate, onVisibilityUpdate); + NazaraSlot(Node, OnNodeInvalidation, onNodeInvalidation); + }; + entt::connection m_cameraDestroyConnection; entt::connection m_graphicsDestroyConnection; + entt::connection m_lightDestroyConnection; entt::connection m_nodeDestroyConnection; entt::observer m_cameraConstructObserver; entt::observer m_graphicsConstructObserver; + entt::observer m_lightConstructObserver; std::set m_invalidatedCameraNode; - std::set m_invalidatedWorldNode; + std::set m_invalidatedGfxWorldNode; + std::set m_invalidatedLightWorldNode; std::unique_ptr m_pipeline; std::unordered_map m_cameraEntities; std::unordered_map m_graphicsEntities; - std::unordered_set m_newlyHiddenEntities; - std::unordered_set m_newlyVisibleEntities; + std::unordered_map m_lightEntities; + std::unordered_set m_newlyHiddenGfxEntities; + std::unordered_set m_newlyVisibleGfxEntities; + std::unordered_set m_newlyHiddenLightEntities; + std::unordered_set m_newlyVisibleLightEntities; }; } diff --git a/include/Nazara/Math/BoundingVolume.hpp b/include/Nazara/Math/BoundingVolume.hpp index db391be1c..6271f36a2 100644 --- a/include/Nazara/Math/BoundingVolume.hpp +++ b/include/Nazara/Math/BoundingVolume.hpp @@ -34,6 +34,8 @@ namespace Nz BoundingVolume& ExtendTo(const BoundingVolume& volume); + bool Intersect(const Box& box) const; + bool IsFinite() const; bool IsInfinite() const; bool IsNull() const; diff --git a/include/Nazara/Math/BoundingVolume.inl b/include/Nazara/Math/BoundingVolume.inl index 2dd73fc46..c96bb50dd 100644 --- a/include/Nazara/Math/BoundingVolume.inl +++ b/include/Nazara/Math/BoundingVolume.inl @@ -169,11 +169,28 @@ namespace Nz return *this; } + template + bool BoundingVolume::Intersect(const Box& box) const + { + switch (extend) + { + case Extend::Infinite: + return true; + + case Extend::Finite: + return aabb.Intersect(box); + + case Extend::Null: + return false; + } + + return false; + } + /*! * \brief Checks whether the volume is finite * \return true if extend is Extend::Finite */ - template bool BoundingVolume::IsFinite() const { diff --git a/include/Nazara/Math/Box.inl b/include/Nazara/Math/Box.inl index e54774c7a..b308b56a1 100644 --- a/include/Nazara/Math/Box.inl +++ b/include/Nazara/Math/Box.inl @@ -459,17 +459,12 @@ namespace Nz { T left = std::max(x, box.x); T right = std::min(x + width, box.x + box.width); - if (left >= right) - return false; - T top = std::max(y, box.y); T bottom = std::min(y + height, box.y + box.height); - if (top >= bottom) - return false; - T up = std::max(z, box.z); T down = std::min(z + depth, box.z + box.depth); - if (up >= down) + + if (left >= right || top >= bottom || up >= down) return false; if (intersection) diff --git a/include/Nazara/Renderer/RenderBufferView.hpp b/include/Nazara/Renderer/RenderBufferView.hpp index c01c2960c..61b3b329a 100644 --- a/include/Nazara/Renderer/RenderBufferView.hpp +++ b/include/Nazara/Renderer/RenderBufferView.hpp @@ -16,6 +16,7 @@ namespace Nz class RenderBufferView { public: + inline RenderBufferView(); inline RenderBufferView(RenderBuffer* buffer); inline RenderBufferView(RenderBuffer* buffer, UInt64 offset, UInt64 size); RenderBufferView(const RenderBufferView&) = default; @@ -26,6 +27,11 @@ namespace Nz inline UInt64 GetOffset() const; inline UInt64 GetSize() const; + inline explicit operator bool() const; + + inline bool operator==(const RenderBufferView& rhs) const; + inline bool operator!=(const RenderBufferView& rhs) const; + RenderBufferView& operator=(const RenderBufferView&) = default; RenderBufferView& operator=(RenderBufferView&&) = default; diff --git a/include/Nazara/Renderer/RenderBufferView.inl b/include/Nazara/Renderer/RenderBufferView.inl index 5e368bad9..d41794511 100644 --- a/include/Nazara/Renderer/RenderBufferView.inl +++ b/include/Nazara/Renderer/RenderBufferView.inl @@ -8,6 +8,13 @@ namespace Nz { + inline RenderBufferView::RenderBufferView() : + m_offset(0), + m_size(0), + m_buffer(nullptr) + { + } + inline RenderBufferView::RenderBufferView(RenderBuffer* buffer) : RenderBufferView(buffer, 0, buffer->GetSize()) { @@ -34,6 +41,21 @@ namespace Nz { return m_size; } + + inline RenderBufferView::operator bool() const + { + return m_buffer != nullptr; + } + + inline bool RenderBufferView::operator==(const RenderBufferView& rhs) const + { + return m_buffer == rhs.m_buffer && m_offset == rhs.m_offset && m_size == rhs.m_size; + } + + inline bool RenderBufferView::operator!=(const RenderBufferView& rhs) const + { + return !operator==(rhs); + } } #include diff --git a/include/Nazara/Utility/Algorithm.hpp b/include/Nazara/Utility/Algorithm.hpp index 4b5f6becc..3b4ad1750 100644 --- a/include/Nazara/Utility/Algorithm.hpp +++ b/include/Nazara/Utility/Algorithm.hpp @@ -44,7 +44,7 @@ namespace Nz NAZARA_UTILITY_API Boxf ComputeAABB(SparsePtr positionPtr, std::size_t vertexCount); NAZARA_UTILITY_API void ComputeBoxIndexVertexCount(const Vector3ui& subdivision, std::size_t* indexCount, std::size_t* vertexCount); - NAZARA_UTILITY_API UInt64 ComputeCacheMissCount(IndexIterator indices, std::size_t indexCount); + NAZARA_UTILITY_API UInt64 ComputeCacheMissCount(IndexIterator indices, UInt32 indexCount); NAZARA_UTILITY_API void ComputeConeIndexVertexCount(unsigned int subdivision, std::size_t* indexCount, std::size_t* vertexCount); NAZARA_UTILITY_API void ComputeCubicSphereIndexVertexCount(unsigned int subdivision, std::size_t* indexCount, std::size_t* vertexCount); NAZARA_UTILITY_API void ComputeIcoSphereIndexVertexCount(unsigned int recursionLevel, std::size_t* indexCount, std::size_t* vertexCount); diff --git a/src/Nazara/Audio/Formats/libflacLoader.cpp b/src/Nazara/Audio/Formats/libflacLoader.cpp index 52f2ad1ff..ab2a5b0d4 100644 --- a/src/Nazara/Audio/Formats/libflacLoader.cpp +++ b/src/Nazara/Audio/Formats/libflacLoader.cpp @@ -224,7 +224,7 @@ namespace Nz UInt32 channelCount = 0; UInt64 frameCount = 0; UInt64 sampleCount = 0; - UInt64 sampleRate = 0; + UInt32 sampleRate = 0; ud.metadataCallback = [&](const FLAC__StreamDecoder* /*decoder*/, const FLAC__StreamMetadata* meta) { diff --git a/src/Nazara/Graphics/Components/LightComponent.cpp b/src/Nazara/Graphics/Components/LightComponent.cpp new file mode 100644 index 000000000..2871a1535 --- /dev/null +++ b/src/Nazara/Graphics/Components/LightComponent.cpp @@ -0,0 +1,10 @@ +// 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 +#include + +namespace Nz +{ +} diff --git a/src/Nazara/Graphics/DirectionalLight.cpp b/src/Nazara/Graphics/DirectionalLight.cpp new file mode 100644 index 000000000..b1f1b8e06 --- /dev/null +++ b/src/Nazara/Graphics/DirectionalLight.cpp @@ -0,0 +1,36 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + float DirectionalLight::ComputeContributionScore(const BoundingVolumef& /*boundingVolume*/) const + { + return -std::numeric_limits::infinity(); + } + + void DirectionalLight::FillLightData(void* data) + { + auto lightOffset = PredefinedLightData::GetOffsets(); + + AccessByOffset(data, lightOffset.lightMemberOffsets.type) = UnderlyingCast(BasicLightType::Directional); + AccessByOffset(data, lightOffset.lightMemberOffsets.color) = Vector4f(m_color.r / 255.f, m_color.g / 255.f, m_color.b / 255.f, m_color.a / 255.f); + AccessByOffset(data, lightOffset.lightMemberOffsets.factor) = Vector2f(m_ambientFactor, m_diffuseFactor); + AccessByOffset(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_direction.x, m_direction.y, m_direction.z, 0.f); + AccessByOffset(data, lightOffset.lightMemberOffsets.shadowMappingFlag) = 0; + } + + void DirectionalLight::UpdateTransform(const Vector3f& /*position*/, const Quaternionf& rotation, const Vector3f& /*scale*/) + { + UpdateRotation(rotation); + } +} diff --git a/src/Nazara/Graphics/ElementRenderer.cpp b/src/Nazara/Graphics/ElementRenderer.cpp index b43681d03..ce189341c 100644 --- a/src/Nazara/Graphics/ElementRenderer.cpp +++ b/src/Nazara/Graphics/ElementRenderer.cpp @@ -9,7 +9,11 @@ namespace Nz { ElementRenderer::~ElementRenderer() = default; - void ElementRenderer::Prepare(const ViewerInstance& /*viewerInstance*/, ElementRendererData& /*rendererData*/, RenderFrame& /*currentFrame*/, const RenderStates& /*renderStates*/, const Pointer* /*elements*/, std::size_t /*elementCount*/) + void ElementRenderer::Prepare(const ViewerInstance& /*viewerInstance*/, ElementRendererData& /*rendererData*/, RenderFrame& /*currentFrame*/, std::size_t /*elementCount*/, const Pointer* /*elements*/, const RenderStates* /*renderStates*/) + { + } + + void ElementRenderer::PrepareEnd(RenderFrame& /*currentFrame*/, ElementRendererData& /*rendererData*/) { } diff --git a/src/Nazara/Graphics/ForwardFramePipeline.cpp b/src/Nazara/Graphics/ForwardFramePipeline.cpp index bc187cdc8..6f4fecfbd 100644 --- a/src/Nazara/Graphics/ForwardFramePipeline.cpp +++ b/src/Nazara/Graphics/ForwardFramePipeline.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -37,37 +38,6 @@ namespace Nz m_elementRenderers.resize(BasicRenderElementCount); m_elementRenderers[UnderlyingCast(BasicRenderElement::SpriteChain)] = std::make_unique(*Graphics::Instance()->GetRenderDevice()); m_elementRenderers[UnderlyingCast(BasicRenderElement::Submesh)] = std::make_unique(); - - auto lightOffset = PredefinedLightData::GetOffsets(); - - m_lightDataBuffer = Graphics::Instance()->GetRenderDevice()->InstantiateBuffer(BufferType::Uniform, lightOffset.totalSize, BufferUsage::DeviceLocal | BufferUsage::Write); - - std::vector staticLightData(lightOffset.totalSize); - /*AccessByOffset(staticLightData.data(), lightOffset.lightCountOffset) = 1; - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.type) = 0; - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.color) = Vector4f(1.f, 1.f, 1.f, 1.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.factor) = Vector2f(0.2f, 1.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.parameter1) = Vector4f(0.f, 0.f, -1.f, 1.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.shadowMappingFlag) = 0;*/ - - AccessByOffset(staticLightData.data(), lightOffset.lightCountOffset) = 1; - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.type) = 1; - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.color) = Vector4f(1.f, 1.f, 1.f, 1.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.factor) = Vector2f(0.2f, 1.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.parameter1) = Vector4f(0.f, 0.f, 0.f, 1.f / 3.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.shadowMappingFlag) = 0; - - /*AccessByOffset(staticLightData.data(), lightOffset.lightCountOffset) = 1; - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.type) = 2; - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.color) = Vector4f(1.f, 1.f, 1.f, 1.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.factor) = Vector2f(0.2f, 1.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.parameter1) = Vector4f(0.f, 0.f, 0.f, 1.f / 3.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.parameter2) = Vector4f(0.f, 0.f, -1.f, 0.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.parameter3) = Vector4f(DegreeAnglef(15.f).GetCos(), DegreeAnglef(20.f).GetCos(), 0.f, 0.f); - AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.shadowMappingFlag) = 0;*/ - - if (!m_lightDataBuffer->Fill(staticLightData.data(), 0, staticLightData.size())) - throw std::runtime_error("failed to fill light data buffer"); } void ForwardFramePipeline::InvalidateViewer(AbstractViewer* viewerInstance) @@ -159,6 +129,21 @@ namespace Nz } } + void ForwardFramePipeline::RegisterLight(std::shared_ptr light, UInt32 renderMask) + { + auto& lightData = m_lights[light.get()]; + lightData.light = std::move(light); + lightData.renderMask = renderMask; + lightData.onLightInvalidated.Connect(lightData.light->OnLightDataInvalided, [this](Light*) + { + for (auto&& [viewer, viewerData] : m_viewers) + { + viewerData.rebuildForwardPass = true; + viewerData.prepare = true; + } + }); + } + void ForwardFramePipeline::RegisterViewer(AbstractViewer* viewerInstance, Int32 renderOrder) { auto& viewerData = m_viewers.emplace(viewerInstance, ViewerData{}).first->second; @@ -226,6 +211,9 @@ namespace Nz return currentHash * 23 + newHash; }; + PredefinedLightData lightOffsets = PredefinedLightData::GetOffsets(); + std::size_t lightUboAlignedSize = Align(lightOffsets.totalSize, graphics->GetRenderDevice()->GetDeviceInfo().limits.minUniformBufferOffsetAlignment); + // Render queues handling for (auto&& [viewer, data] : m_viewers) { @@ -307,12 +295,104 @@ namespace Nz { renderFrame.PushForRelease(std::move(viewerData.forwardRenderElements)); viewerData.forwardRenderElements.clear(); - - for (const auto& renderableData : m_visibleRenderables) - renderableData.instancedRenderable->BuildElement(m_forwardPassIndex, *renderableData.worldInstance, viewerData.forwardRenderElements); - viewerData.forwardRegistry.Clear(); viewerData.forwardRenderQueue.Clear(); + viewerData.lightBufferPerLights.clear(); + viewerData.lightPerRenderElement.clear(); + + for (auto& lightDataUbo : m_lightDataBuffers) + { + lightDataUbo.allocation = nullptr; + lightDataUbo.offset = 0; + } + + for (const auto& renderableData : m_visibleRenderables) + { + BoundingVolumef renderableBoundingVolume(renderableData.instancedRenderable->GetAABB()); + renderableBoundingVolume.Update(renderableData.worldInstance->GetWorldMatrix()); + + // Select lights (TODO: Cull lights in frustum) + m_visibleLights.clear(); + for (auto&& [light, lightData] : m_lights) + { + const BoundingVolumef& boundingVolume = light->GetBoundingVolume(); + if ((renderMask & lightData.renderMask) && boundingVolume.Intersect(renderableBoundingVolume.aabb)) + m_visibleLights.push_back(light); + } + + // Sort lights + std::sort(m_visibleLights.begin(), m_visibleLights.end(), [&](Light* lhs, Light* rhs) + { + return lhs->ComputeContributionScore(renderableBoundingVolume) < rhs->ComputeContributionScore(renderableBoundingVolume); + }); + + std::size_t lightCount = std::min(m_visibleLights.size(), MaxLightCountPerDraw); + + LightKey lightKey; + lightKey.fill(nullptr); + for (std::size_t i = 0; i < lightCount; ++i) + lightKey[i] = m_visibleLights[i]; + + RenderBufferView lightUboView; + + auto it = viewerData.lightBufferPerLights.find(lightKey); + if (it == viewerData.lightBufferPerLights.end()) + { + // Prepare light ubo upload + + // Find light ubo + LightDataUbo* targetLightData = nullptr; + for (auto& lightUboData : m_lightDataBuffers) + { + if (lightUboData.offset + lightUboAlignedSize <= lightUboData.renderBuffer->GetSize()) + { + targetLightData = &lightUboData; + break; + } + } + + if (!targetLightData) + { + // Allocate a new light UBO + auto& lightUboData = m_lightDataBuffers.emplace_back(); + lightUboData.renderBuffer = graphics->GetRenderDevice()->InstantiateBuffer(BufferType::Uniform, 256 * lightUboAlignedSize, BufferUsage::DeviceLocal | BufferUsage::Dynamic | BufferUsage::Write); + + targetLightData = &lightUboData; + } + + assert(targetLightData); + if (!targetLightData->allocation) + targetLightData->allocation = &uploadPool.Allocate(targetLightData->renderBuffer->GetSize()); + + void* lightDataPtr = static_cast(targetLightData->allocation->mappedPtr) + targetLightData->offset; + AccessByOffset(lightDataPtr, lightOffsets.lightCountOffset) = SafeCast(lightCount); + + UInt8* lightPtr = static_cast(lightDataPtr) + lightOffsets.lightsOffset; + for (std::size_t i = 0; i < lightCount; ++i) + { + m_visibleLights[i]->FillLightData(lightPtr); + lightPtr += lightOffsets.lightSize; + } + + // Associate render element with light ubo + lightUboView = RenderBufferView(targetLightData->renderBuffer.get(), targetLightData->offset, lightUboAlignedSize); + + targetLightData->offset += lightUboAlignedSize; + + viewerData.lightBufferPerLights.emplace(lightKey, lightUboView); + } + else + lightUboView = it->second; + + std::size_t previousCount = viewerData.forwardRenderElements.size(); + renderableData.instancedRenderable->BuildElement(m_forwardPassIndex, *renderableData.worldInstance, viewerData.forwardRenderElements); + for (std::size_t i = previousCount; i < viewerData.forwardRenderElements.size(); ++i) + { + const RenderElement* element = viewerData.forwardRenderElements[i].get(); + viewerData.lightPerRenderElement.emplace(element, lightUboView); + } + } + for (const auto& renderElement : viewerData.forwardRenderElements) { renderElement->Register(viewerData.forwardRegistry); @@ -320,6 +400,25 @@ namespace Nz } viewerData.forwardRegistry.Finalize(); + + renderFrame.Execute([&](CommandBufferBuilder& builder) + { + builder.BeginDebugRegion("Light UBO Update", Color::Yellow); + { + builder.PreTransferBarrier(); + + for (auto& lightUboData : m_lightDataBuffers) + { + if (!lightUboData.allocation) + continue; + + builder.CopyBuffer(*lightUboData.allocation, RenderBufferView(lightUboData.renderBuffer.get(), 0, lightUboData.offset)); + } + + builder.PostTransferBarrier(); + } + builder.EndDebugRegion(); + }, QueueType::Transfer); } viewerData.forwardRenderQueue.Sort([&](const RenderElement* element) @@ -353,20 +452,41 @@ namespace Nz const auto& viewerInstance = viewer->GetViewerInstance(); - ElementRenderer::RenderStates renderStates; - renderStates.lightData = m_lightDataBuffer; - ProcessRenderQueue(viewerData.depthPrepassRenderQueue, [&](std::size_t elementType, const Pointer* elements, std::size_t elementCount) { ElementRenderer& elementRenderer = *m_elementRenderers[elementType]; - elementRenderer.Prepare(viewerInstance, *rendererData[elementType], renderFrame, renderStates, elements, elementCount); + + m_renderStates.clear(); + m_renderStates.resize(elementCount); + + elementRenderer.Prepare(viewerInstance, *rendererData[elementType], renderFrame, elementCount, elements, m_renderStates.data()); }); + for (std::size_t i = 0; i < m_elementRenderers.size(); ++i) + m_elementRenderers[i]->PrepareEnd(renderFrame, *rendererData[i]); + + auto& lightPerRenderElement = viewerData.lightPerRenderElement; ProcessRenderQueue(viewerData.forwardRenderQueue, [&](std::size_t elementType, const Pointer* elements, std::size_t elementCount) { ElementRenderer& elementRenderer = *m_elementRenderers[elementType]; - elementRenderer.Prepare(viewerInstance, *rendererData[elementType], renderFrame, renderStates, elements, elementCount); + + m_renderStates.clear(); + + m_renderStates.reserve(elementCount); + for (std::size_t i = 0; i < elementCount; ++i) + { + auto it = lightPerRenderElement.find(elements[i]); + assert(it != lightPerRenderElement.end()); + + auto& renderStates = m_renderStates.emplace_back(); + renderStates.lightData = it->second; + } + + elementRenderer.Prepare(viewerInstance, *rendererData[elementType], renderFrame, elementCount, elements, m_renderStates.data()); }); + + for (std::size_t i = 0; i < m_elementRenderers.size(); ++i) + m_elementRenderers[i]->PrepareEnd(renderFrame, *rendererData[i]); } if (m_bakedFrameGraph.Resize(renderFrame)) @@ -493,6 +613,17 @@ namespace Nz } } + void ForwardFramePipeline::UnregisterLight(Light* light) + { + m_lights.erase(light); + + for (auto&& [viewer, viewerData] : m_viewers) + { + viewerData.rebuildForwardPass = true; + viewerData.prepare = true; + } + } + void ForwardFramePipeline::UnregisterViewer(AbstractViewer* viewerInstance) { m_viewers.erase(viewerInstance); @@ -544,7 +675,7 @@ namespace Nz ProcessRenderQueue(viewerData.depthPrepassRenderQueue, [&](std::size_t elementType, const Pointer* elements, std::size_t elementCount) { ElementRenderer& elementRenderer = *m_elementRenderers[elementType]; - elementRenderer.Render(viewerInstance, *viewerData.elementRendererData[elementType], builder, elements, elementCount); + elementRenderer.Render(viewerInstance, *viewerData.elementRendererData[elementType], builder, elementCount, elements); }); }); @@ -576,7 +707,7 @@ namespace Nz ProcessRenderQueue(viewerData.forwardRenderQueue, [&](std::size_t elementType, const Pointer* elements, std::size_t elementCount) { ElementRenderer& elementRenderer = *m_elementRenderers[elementType]; - elementRenderer.Render(viewerInstance , *viewerData.elementRendererData[elementType], builder, elements, elementCount); + elementRenderer.Render(viewerInstance, *viewerData.elementRendererData[elementType], builder, elementCount, elements); }); }); } diff --git a/src/Nazara/Graphics/Light.cpp b/src/Nazara/Graphics/Light.cpp new file mode 100644 index 000000000..dc94e501e --- /dev/null +++ b/src/Nazara/Graphics/Light.cpp @@ -0,0 +1,11 @@ +// 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 +#include + +namespace Nz +{ + Light::~Light() = default; +} diff --git a/src/Nazara/Graphics/PointLight.cpp b/src/Nazara/Graphics/PointLight.cpp new file mode 100644 index 000000000..cbf81387b --- /dev/null +++ b/src/Nazara/Graphics/PointLight.cpp @@ -0,0 +1,36 @@ +// 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 +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + float PointLight::ComputeContributionScore(const BoundingVolumef& boundingVolume) const + { + // TODO: take luminosity/radius into account + return Vector3f::SquaredDistance(m_position, boundingVolume.aabb.GetCenter()); + } + + void PointLight::FillLightData(void* data) + { + auto lightOffset = PredefinedLightData::GetOffsets(); + + AccessByOffset(data, lightOffset.lightMemberOffsets.type) = UnderlyingCast(BasicLightType::Point); + AccessByOffset(data, lightOffset.lightMemberOffsets.color) = Vector4f(m_color.r / 255.f, m_color.g / 255.f, m_color.b / 255.f, m_color.a / 255.f); + AccessByOffset(data, lightOffset.lightMemberOffsets.factor) = Vector2f(m_ambientFactor, m_diffuseFactor); + AccessByOffset(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_position.x, m_position.y, m_position.z, m_invRadius); + AccessByOffset(data, lightOffset.lightMemberOffsets.shadowMappingFlag) = 0; + } + + void PointLight::UpdateTransform(const Vector3f& position, const Quaternionf& /*rotation*/, const Vector3f& /*scale*/) + { + UpdatePosition(position); + } +} diff --git a/src/Nazara/Graphics/Resources/Shaders/phong_material.nzsl b/src/Nazara/Graphics/Resources/Shaders/phong_material.nzsl index ce623ab78..984088ed4 100644 --- a/src/Nazara/Graphics/Resources/Shaders/phong_material.nzsl +++ b/src/Nazara/Graphics/Resources/Shaders/phong_material.nzsl @@ -72,7 +72,7 @@ struct Light struct LightData { lights: array[Light, MaxLightCount], - lightCount: u32 + lightCount: u32, } [layout(std140)] @@ -250,7 +250,7 @@ fn main(input: VertToFrag) -> FragOut else { let output: FragOut; - output.RenderTarget0 = diffuseColor.w; + output.RenderTarget0 = diffuseColor; return output; } } diff --git a/src/Nazara/Graphics/SpotLight.cpp b/src/Nazara/Graphics/SpotLight.cpp new file mode 100644 index 000000000..72ecebdc3 --- /dev/null +++ b/src/Nazara/Graphics/SpotLight.cpp @@ -0,0 +1,39 @@ +// 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 +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + float SpotLight::ComputeContributionScore(const BoundingVolumef& boundingVolume) const + { + // TODO: take luminosity/radius/direction into account + return Vector3f::SquaredDistance(m_position, boundingVolume.aabb.GetCenter()); + } + + void SpotLight::FillLightData(void* data) + { + auto lightOffset = PredefinedLightData::GetOffsets(); + + AccessByOffset(data, lightOffset.lightMemberOffsets.type) = UnderlyingCast(BasicLightType::Spot); + AccessByOffset(data, lightOffset.lightMemberOffsets.color) = Vector4f(m_color.r / 255.f, m_color.g / 255.f, m_color.b / 255.f, m_color.a / 255.f); + AccessByOffset(data, lightOffset.lightMemberOffsets.factor) = Vector2f(m_ambientFactor, m_diffuseFactor); + AccessByOffset(data, lightOffset.lightMemberOffsets.parameter1) = Vector4f(m_position.x, m_position.y, m_position.z, m_invRadius); + AccessByOffset(data, lightOffset.lightMemberOffsets.parameter2) = Vector4f(m_direction.x, m_direction.y, m_direction.z, 0.f); + AccessByOffset(data, lightOffset.lightMemberOffsets.parameter3) = Vector4f(m_innerAngleCos, m_outerAngleCos, 0.f, 0.f); + AccessByOffset(data, lightOffset.lightMemberOffsets.shadowMappingFlag) = 0; + } + + void SpotLight::UpdateTransform(const Vector3f& position, const Quaternionf& rotation, const Vector3f& /*scale*/) + { + UpdatePosition(position); + UpdateRotation(rotation); + } +} diff --git a/src/Nazara/Graphics/SpriteChainRenderer.cpp b/src/Nazara/Graphics/SpriteChainRenderer.cpp index 69d64cfc3..60dba6ec4 100644 --- a/src/Nazara/Graphics/SpriteChainRenderer.cpp +++ b/src/Nazara/Graphics/SpriteChainRenderer.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -49,7 +50,7 @@ namespace Nz return std::make_unique(); } - void SpriteChainRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, const RenderStates& renderStates, const Pointer* elements, std::size_t elementCount) + void SpriteChainRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& currentFrame, std::size_t elementCount, const Pointer* elements, const RenderStates* renderStates) { Graphics* graphics = Graphics::Instance(); @@ -57,103 +58,65 @@ namespace Nz Recti invalidScissorBox(-1, -1, -1, -1); - std::size_t firstQuadIndex = 0; - SpriteChainRendererData::DrawCall* currentDrawCall = nullptr; - UploadPool::Allocation* currentAllocation = nullptr; - UInt8* currentAllocationMemPtr = nullptr; - const VertexDeclaration* currentVertexDeclaration = nullptr; - RenderBuffer* currentVertexBuffer = nullptr; - const MaterialPass* currentMaterialPass = nullptr; - const RenderPipeline* currentPipeline = nullptr; - const ShaderBinding* currentShaderBinding = nullptr; - const Texture* currentTextureOverlay = nullptr; - const WorldInstance* currentWorldInstance = nullptr; - Recti currentScissorBox = invalidScissorBox; - - auto FlushDrawCall = [&]() - { - currentDrawCall = nullptr; - }; - - auto FlushDrawData = [&]() - { - FlushDrawCall(); - - currentShaderBinding = nullptr; - }; - - auto Flush = [&]() - { - // changing vertex buffer always mean we have to switch draw calls - FlushDrawCall(); - - if (currentAllocation) - { - std::size_t size = currentAllocationMemPtr - static_cast(currentAllocation->mappedPtr); - - m_pendingCopies.emplace_back(BufferCopy{ - currentVertexBuffer, - currentAllocation, - size - }); - - firstQuadIndex = 0; - currentAllocation = nullptr; - currentVertexBuffer = nullptr; - } - }; + const auto& defaultSampler = graphics->GetSamplerCache().Get({}); std::size_t oldDrawCallCount = data.drawCalls.size(); - const auto& defaultSampler = graphics->GetSamplerCache().Get({}); for (std::size_t i = 0; i < elementCount; ++i) { assert(elements[i]->GetElementType() == UnderlyingCast(BasicRenderElement::SpriteChain)); const RenderSpriteChain& spriteChain = static_cast(*elements[i]); + const RenderStates& renderState = renderStates[i]; const VertexDeclaration* vertexDeclaration = spriteChain.GetVertexDeclaration(); std::size_t stride = vertexDeclaration->GetStride(); - if (currentVertexDeclaration != vertexDeclaration) + if (m_pendingData.currentVertexDeclaration != vertexDeclaration) { // TODO: It's be possible to use another vertex declaration with the same vertex buffer but currently very complicated // Wait until buffer rewrite Flush(); - currentVertexDeclaration = vertexDeclaration; + m_pendingData.currentVertexDeclaration = vertexDeclaration; } - if (const RenderPipeline* pipeline = &spriteChain.GetRenderPipeline(); currentPipeline != pipeline) + if (const RenderPipeline* pipeline = &spriteChain.GetRenderPipeline(); m_pendingData.currentPipeline != pipeline) { FlushDrawCall(); - currentPipeline = pipeline; + m_pendingData.currentPipeline = pipeline; } - if (const MaterialPass* materialPass = &spriteChain.GetMaterialPass(); currentMaterialPass != materialPass) + if (const MaterialPass* materialPass = &spriteChain.GetMaterialPass(); m_pendingData.currentMaterialPass != materialPass) { FlushDrawData(); - currentMaterialPass = materialPass; + m_pendingData.currentMaterialPass = materialPass; } - if (const WorldInstance* worldInstance = &spriteChain.GetWorldInstance(); currentWorldInstance != worldInstance) + if (const WorldInstance* worldInstance = &spriteChain.GetWorldInstance(); m_pendingData.currentWorldInstance != worldInstance) { // TODO: Flushing draw calls on instance binding means we can have e.g. 1000 sprites rendered using a draw call for each one // which is far from being efficient, using some bindless could help (or at least instancing?) FlushDrawData(); - currentWorldInstance = worldInstance; + m_pendingData.currentWorldInstance = worldInstance; } - if (const Texture* textureOverlay = spriteChain.GetTextureOverlay(); currentTextureOverlay != textureOverlay) + if (const Texture* textureOverlay = spriteChain.GetTextureOverlay(); m_pendingData.currentTextureOverlay != textureOverlay) { FlushDrawData(); - currentTextureOverlay = textureOverlay; + m_pendingData.currentTextureOverlay = textureOverlay; + } + + if (m_pendingData.currentLightData != renderState.lightData) + { + FlushDrawData(); + m_pendingData.currentLightData = renderState.lightData; } const Recti& scissorBox = spriteChain.GetScissorBox(); const Recti& targetScissorBox = (scissorBox.width >= 0) ? scissorBox : invalidScissorBox; - if (currentScissorBox != targetScissorBox) + if (m_pendingData.currentScissorBox != targetScissorBox) { - FlushDrawData(); - currentScissorBox = targetScissorBox; + FlushDrawCall(); + m_pendingData.currentScissorBox = targetScissorBox; } std::size_t remainingQuads = spriteChain.GetSpriteCount(); @@ -161,10 +124,10 @@ namespace Nz while (remainingQuads > 0) { - if (!currentAllocation) + if (!m_pendingData.currentAllocation) { - currentAllocation = ¤tFrame.GetUploadPool().Allocate(m_maxVertexBufferSize); - currentAllocationMemPtr = static_cast(currentAllocation->mappedPtr); + m_pendingData.currentAllocation = ¤tFrame.GetUploadPool().Allocate(m_maxVertexBufferSize); + m_pendingData.currentAllocationMemPtr = static_cast(m_pendingData.currentAllocation->mappedPtr); std::shared_ptr vertexBuffer; @@ -177,12 +140,12 @@ namespace Nz else vertexBuffer = m_device.InstantiateBuffer(BufferType::Vertex, m_maxVertexBufferSize, BufferUsage::DeviceLocal | BufferUsage::Dynamic | BufferUsage::Write); - currentVertexBuffer = vertexBuffer.get(); + m_pendingData.currentVertexBuffer = vertexBuffer.get(); data.vertexBuffers.emplace_back(std::move(vertexBuffer)); } - if (!currentShaderBinding) + if (!m_pendingData.currentShaderBinding) { m_bindingCache.clear(); @@ -193,7 +156,7 @@ namespace Nz const auto& matSettings = materialPass.GetSettings(); if (std::size_t bindingIndex = matSettings->GetPredefinedBinding(PredefinedShaderBinding::InstanceDataUbo); bindingIndex != MaterialSettings::InvalidIndex) { - const auto& instanceBuffer = currentWorldInstance->GetInstanceBuffer(); + const auto& instanceBuffer = m_pendingData.currentWorldInstance->GetInstanceBuffer(); auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; @@ -203,13 +166,13 @@ namespace Nz }; } - if (std::size_t bindingIndex = matSettings->GetPredefinedBinding(PredefinedShaderBinding::LightDataUbo); bindingIndex != MaterialSettings::InvalidIndex) + if (std::size_t bindingIndex = matSettings->GetPredefinedBinding(PredefinedShaderBinding::LightDataUbo); bindingIndex != MaterialSettings::InvalidIndex && m_pendingData.currentLightData) { auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::UniformBufferBinding{ - renderStates.lightData.get(), - 0, renderStates.lightData->GetSize() + m_pendingData.currentLightData.GetBuffer(), + m_pendingData.currentLightData.GetOffset(), m_pendingData.currentLightData.GetSize() }; } @@ -230,33 +193,33 @@ namespace Nz auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::TextureBinding{ - currentTextureOverlay, defaultSampler.get() + m_pendingData.currentTextureOverlay, defaultSampler.get() }; } - ShaderBindingPtr drawDataBinding = currentPipeline->GetPipelineInfo().pipelineLayout->AllocateShaderBinding(0); + ShaderBindingPtr drawDataBinding = m_pendingData.currentPipeline->GetPipelineInfo().pipelineLayout->AllocateShaderBinding(0); drawDataBinding->Update(m_bindingCache.data(), m_bindingCache.size()); - currentShaderBinding = drawDataBinding.get(); + m_pendingData.currentShaderBinding = drawDataBinding.get(); data.shaderBindings.emplace_back(std::move(drawDataBinding)); } - if (!currentDrawCall) + if (!m_pendingData.currentDrawCall) { data.drawCalls.push_back(SpriteChainRendererData::DrawCall{ - currentVertexBuffer, - currentPipeline, - currentShaderBinding, - 6 * firstQuadIndex, + m_pendingData.currentVertexBuffer, + m_pendingData.currentPipeline, + m_pendingData.currentShaderBinding, + 6 * m_pendingData.firstQuadIndex, 0, - currentScissorBox + m_pendingData.currentScissorBox }); - currentDrawCall = &data.drawCalls.back(); + m_pendingData.currentDrawCall = &data.drawCalls.back(); } - std::size_t remainingSpace = m_maxVertexBufferSize - (currentAllocationMemPtr - static_cast(currentAllocation->mappedPtr)); + std::size_t remainingSpace = m_maxVertexBufferSize - (m_pendingData.currentAllocationMemPtr - static_cast(m_pendingData.currentAllocation->mappedPtr)); std::size_t maxQuads = remainingSpace / (4 * stride); if (maxQuads == 0) { @@ -267,12 +230,12 @@ namespace Nz std::size_t copiedQuadCount = std::min(maxQuads, remainingQuads); std::size_t copiedSize = 4 * copiedQuadCount * stride; - std::memcpy(currentAllocationMemPtr, spriteData, copiedSize); - currentAllocationMemPtr += copiedSize; + std::memcpy(m_pendingData.currentAllocationMemPtr, spriteData, copiedSize); + m_pendingData.currentAllocationMemPtr += copiedSize; spriteData += copiedSize; - firstQuadIndex += copiedQuadCount; - currentDrawCall->quadCount += copiedQuadCount; + m_pendingData.firstQuadIndex += copiedQuadCount; + m_pendingData.currentDrawCall->quadCount += copiedQuadCount; remainingQuads -= copiedQuadCount; // If there's still data to copy, it means buffer is full, flush it @@ -281,12 +244,16 @@ namespace Nz } } - //TODO: Add Finish()/PrepareEnd() call to allow to reuse buffers/draw calls for multiple Prepare calls - Flush(); + FlushDrawCall(); const RenderSpriteChain* firstSpriteChain = static_cast(elements[0]); std::size_t drawCallCount = data.drawCalls.size() - oldDrawCallCount; data.drawCallPerElement[firstSpriteChain] = SpriteChainRendererData::DrawCallIndices{ oldDrawCallCount, drawCallCount }; + } + + void SpriteChainRenderer::PrepareEnd(RenderFrame& currentFrame, ElementRendererData& /*rendererData*/) + { + Flush(); if (!m_pendingCopies.empty()) { @@ -300,9 +267,11 @@ namespace Nz m_pendingCopies.clear(); } + + m_pendingData = PendingData{}; } - void SpriteChainRenderer::Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, const Pointer* elements, std::size_t /*elementCount*/) + void SpriteChainRenderer::Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t /*elementCount*/, const Pointer* elements) { auto& data = static_cast(rendererData); @@ -374,4 +343,37 @@ namespace Nz data.drawCalls.clear(); } + + void SpriteChainRenderer::Flush() + { + // changing vertex buffer always mean we have to switch draw calls + FlushDrawCall(); + + if (m_pendingData.currentAllocation) + { + std::size_t size = m_pendingData.currentAllocationMemPtr - static_cast(m_pendingData.currentAllocation->mappedPtr); + + m_pendingCopies.emplace_back(BufferCopy{ + m_pendingData.currentVertexBuffer, + m_pendingData.currentAllocation, + size + }); + + m_pendingData.firstQuadIndex = 0; + m_pendingData.currentAllocation = nullptr; + m_pendingData.currentVertexBuffer = nullptr; + } + } + + void SpriteChainRenderer::FlushDrawCall() + { + m_pendingData.currentDrawCall = nullptr; + } + + void SpriteChainRenderer::FlushDrawData() + { + FlushDrawCall(); + + m_pendingData.currentShaderBinding = nullptr; + } } diff --git a/src/Nazara/Graphics/SubmeshRenderer.cpp b/src/Nazara/Graphics/SubmeshRenderer.cpp index 93e5913f2..6b048d88b 100644 --- a/src/Nazara/Graphics/SubmeshRenderer.cpp +++ b/src/Nazara/Graphics/SubmeshRenderer.cpp @@ -17,7 +17,7 @@ namespace Nz return std::make_unique(); } - void SubmeshRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& /*currentFrame*/, const RenderStates& renderStates, const Pointer* elements, std::size_t elementCount) + void SubmeshRenderer::Prepare(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, RenderFrame& /*currentFrame*/, std::size_t elementCount, const Pointer* elements, const RenderStates* renderStates) { Graphics* graphics = Graphics::Instance(); @@ -32,6 +32,7 @@ namespace Nz const ShaderBinding* currentShaderBinding = nullptr; const WorldInstance* currentWorldInstance = nullptr; Recti currentScissorBox = invalidScissorBox; + RenderBufferView currentLightData; auto FlushDrawCall = [&]() { @@ -48,10 +49,13 @@ namespace Nz const auto& whiteTexture = Graphics::Instance()->GetDefaultTextures().whiteTextures[UnderlyingCast(ImageType::E2D)]; const auto& defaultSampler = graphics->GetSamplerCache().Get({}); + std::size_t oldDrawCallCount = data.drawCalls.size(); + for (std::size_t i = 0; i < elementCount; ++i) { assert(elements[i]->GetElementType() == UnderlyingCast(BasicRenderElement::Submesh)); const RenderSubmesh& submesh = static_cast(*elements[i]); + const RenderStates& renderState = renderStates[i]; if (const RenderPipeline* pipeline = submesh.GetRenderPipeline(); currentPipeline != pipeline) { @@ -85,6 +89,12 @@ namespace Nz currentWorldInstance = worldInstance; } + if (currentLightData != renderState.lightData) + { + FlushDrawData(); + currentLightData = renderState.lightData; + } + const Recti& scissorBox = submesh.GetScissorBox(); const Recti& targetScissorBox = (scissorBox.width >= 0) ? scissorBox : invalidScissorBox; if (currentScissorBox != targetScissorBox) @@ -114,13 +124,13 @@ namespace Nz }; } - if (std::size_t bindingIndex = matSettings->GetPredefinedBinding(PredefinedShaderBinding::LightDataUbo); bindingIndex != MaterialSettings::InvalidIndex) + if (std::size_t bindingIndex = matSettings->GetPredefinedBinding(PredefinedShaderBinding::LightDataUbo); bindingIndex != MaterialSettings::InvalidIndex && currentLightData) { auto& bindingEntry = m_bindingCache.emplace_back(); bindingEntry.bindingIndex = bindingIndex; bindingEntry.content = ShaderBinding::UniformBufferBinding{ - renderStates.lightData.get(), - 0, renderStates.lightData->GetSize() + currentLightData.GetBuffer(), + currentLightData.GetOffset(), currentLightData.GetSize() }; } @@ -154,6 +164,7 @@ namespace Nz } auto& drawCall = data.drawCalls.emplace_back(); + drawCall.firstIndex = 0; drawCall.indexBuffer = currentIndexBuffer; drawCall.indexCount = submesh.GetIndexCount(); drawCall.renderPipeline = currentPipeline; @@ -161,9 +172,13 @@ namespace Nz drawCall.shaderBinding = currentShaderBinding; drawCall.vertexBuffer = currentVertexBuffer; } + + const RenderSubmesh* firstSubmesh = static_cast(elements[0]); + std::size_t drawCallCount = data.drawCalls.size() - oldDrawCallCount; + data.drawCallPerElement[firstSubmesh] = SubmeshRendererData::DrawCallIndices{ oldDrawCallCount, drawCallCount }; } - void SubmeshRenderer::Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, const Pointer* /*elements*/, std::size_t /*elementCount*/) + void SubmeshRenderer::Render(const ViewerInstance& viewerInstance, ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, std::size_t /*elementCount*/, const Pointer* elements) { auto& data = static_cast(rendererData); @@ -176,8 +191,16 @@ namespace Nz const ShaderBinding* currentShaderBinding = nullptr; Recti currentScissorBox(-1, -1, -1, -1); - for (const auto& drawData : data.drawCalls) + const RenderSubmesh* firstSubmesh = static_cast(elements[0]); + auto it = data.drawCallPerElement.find(firstSubmesh); + assert(it != data.drawCallPerElement.end()); + + const auto& indices = it->second; + + for (std::size_t i = 0; i < indices.count; ++i) { + const auto& drawData = data.drawCalls[indices.start + i]; + if (currentPipeline != drawData.renderPipeline) { commandBuffer.BindPipeline(*drawData.renderPipeline); @@ -210,9 +233,9 @@ namespace Nz } if (currentIndexBuffer) - commandBuffer.DrawIndexed(SafeCast(drawData.indexCount)); + commandBuffer.DrawIndexed(SafeCast(drawData.indexCount), 1U, SafeCast(drawData.firstIndex)); else - commandBuffer.Draw(SafeCast(drawData.indexCount)); + commandBuffer.Draw(SafeCast(drawData.indexCount), 1U, SafeCast(drawData.firstIndex)); } } diff --git a/src/Nazara/Graphics/Systems/RenderSystem.cpp b/src/Nazara/Graphics/Systems/RenderSystem.cpp index ecbaee8cf..47adc692e 100644 --- a/src/Nazara/Graphics/Systems/RenderSystem.cpp +++ b/src/Nazara/Graphics/Systems/RenderSystem.cpp @@ -18,10 +18,12 @@ namespace Nz { RenderSystem::RenderSystem(entt::registry& registry) : m_cameraConstructObserver(registry, entt::collector.group()), - m_graphicsConstructObserver(registry, entt::collector.group()) + m_graphicsConstructObserver(registry, entt::collector.group()), + m_lightConstructObserver(registry, entt::collector.group()) { m_cameraDestroyConnection = registry.on_destroy().connect<&RenderSystem::OnCameraDestroy>(this); m_graphicsDestroyConnection = registry.on_destroy().connect<&RenderSystem::OnGraphicsDestroy>(this); + m_lightDestroyConnection = registry.on_destroy().connect<&RenderSystem::OnLightDestroy>(this); m_nodeDestroyConnection = registry.on_destroy().connect<&RenderSystem::OnNodeDestroy>(this); m_pipeline = std::make_unique(); @@ -31,8 +33,10 @@ namespace Nz { m_cameraConstructObserver.disconnect(); m_graphicsConstructObserver.disconnect(); + m_lightConstructObserver.disconnect(); m_cameraDestroyConnection.release(); m_graphicsDestroyConnection.release(); + m_lightDestroyConnection.release(); m_nodeDestroyConnection.release(); } @@ -67,13 +71,13 @@ namespace Nz m_pipeline->RegisterInstancedDrawable(worldInstance, renderableEntry.renderable.get(), renderableEntry.renderMask); } - m_invalidatedWorldNode.insert(entity); + m_invalidatedGfxWorldNode.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); + m_invalidatedGfxWorldNode.insert(entity); }); graphicsEntity.onRenderableAttached.Connect(entityGfx.OnRenderableAttached, [this](GraphicsComponent* gfx, const GraphicsComponent::Renderable& renderableEntry) @@ -98,13 +102,64 @@ namespace Nz { if (isVisible) { - m_newlyHiddenEntities.erase(entity); - m_newlyVisibleEntities.insert(entity); + m_newlyHiddenGfxEntities.erase(entity); + m_newlyVisibleGfxEntities.insert(entity); } else { - m_newlyHiddenEntities.insert(entity); - m_newlyVisibleEntities.erase(entity); + m_newlyHiddenGfxEntities.insert(entity); + m_newlyVisibleGfxEntities.erase(entity); + } + }); + }); + + m_lightConstructObserver.each([&](entt::entity entity) + { + LightComponent& entityLight = registry.get(entity); + NodeComponent& entityNode = registry.get(entity); + + if (entityLight.IsVisible()) + { + for (const auto& lightEntry : entityLight.GetLights()) + m_pipeline->RegisterLight(lightEntry.light, lightEntry.renderMask); + } + + m_invalidatedLightWorldNode.insert(entity); + + assert(m_lightEntities.find(entity) == m_lightEntities.end()); + auto& lightEntity = m_lightEntities[entity]; + lightEntity.onNodeInvalidation.Connect(entityNode.OnNodeInvalidation, [this, entity](const Node* /*node*/) + { + m_invalidatedLightWorldNode.insert(entity); + }); + + lightEntity.onLightAttached.Connect(entityLight.OnLightAttached, [this](LightComponent* light, const LightComponent::LightEntry& lightEntry) + { + if (!light->IsVisible()) + return; + + m_pipeline->RegisterLight(lightEntry.light, lightEntry.renderMask); + }); + + lightEntity.onLightDetach.Connect(entityLight.OnLightDetach, [this](LightComponent* light, const LightComponent::LightEntry& lightEntry) + { + if (!light->IsVisible()) + return; + + m_pipeline->UnregisterLight(lightEntry.light.get()); + }); + + lightEntity.onVisibilityUpdate.Connect(entityLight.OnVisibilityUpdate, [this, entity](LightComponent* /*light*/, bool isVisible) + { + if (isVisible) + { + m_newlyHiddenLightEntities.erase(entity); + m_newlyVisibleLightEntities.insert(entity); + } + else + { + m_newlyHiddenLightEntities.insert(entity); + m_newlyVisibleLightEntities.erase(entity); } }); }); @@ -127,9 +182,22 @@ namespace Nz void RenderSystem::OnGraphicsDestroy(entt::registry& registry, entt::entity entity) { m_graphicsEntities.erase(entity); - m_invalidatedWorldNode.erase(entity); - m_newlyHiddenEntities.erase(entity); - m_newlyVisibleEntities.erase(entity); + m_invalidatedGfxWorldNode.erase(entity); + m_newlyHiddenGfxEntities.erase(entity); + m_newlyVisibleGfxEntities.erase(entity); + + GraphicsComponent& entityGfx = registry.get(entity); + const WorldInstancePtr& worldInstance = entityGfx.GetWorldInstance(); + for (const auto& renderableEntry : entityGfx.GetRenderables()) + m_pipeline->UnregisterInstancedDrawable(worldInstance, renderableEntry.renderable.get()); + } + + void RenderSystem::OnLightDestroy(entt::registry& registry, entt::entity entity) + { + m_lightEntities.erase(entity); + m_invalidatedLightWorldNode.erase(entity); + m_newlyHiddenLightEntities.erase(entity); + m_newlyVisibleLightEntities.erase(entity); GraphicsComponent& entityGfx = registry.get(entity); const WorldInstancePtr& worldInstance = entityGfx.GetWorldInstance(); @@ -139,14 +207,19 @@ namespace Nz void RenderSystem::OnNodeDestroy(entt::registry& registry, entt::entity entity) { - m_newlyHiddenEntities.erase(entity); - m_newlyVisibleEntities.erase(entity); + m_newlyHiddenGfxEntities.erase(entity); + m_newlyVisibleGfxEntities.erase(entity); + m_newlyHiddenLightEntities.erase(entity); + m_newlyVisibleLightEntities.erase(entity); if (registry.try_get(entity)) OnCameraDestroy(registry, entity); if (registry.try_get(entity)) OnGraphicsDestroy(registry, entity); + + if (registry.try_get(entity)) + OnLightDestroy(registry, entity); } void RenderSystem::UpdateInstances(entt::registry& registry) @@ -166,7 +239,7 @@ namespace Nz } m_invalidatedCameraNode.clear(); - for (entt::entity entity : m_invalidatedWorldNode) + for (entt::entity entity : m_invalidatedGfxWorldNode) { const NodeComponent& entityNode = registry.get(entity); GraphicsComponent& entityGraphics = registry.get(entity); @@ -176,13 +249,27 @@ namespace Nz m_pipeline->InvalidateWorldInstance(worldInstance.get()); } - m_invalidatedWorldNode.clear(); + m_invalidatedGfxWorldNode.clear(); + + for (entt::entity entity : m_invalidatedLightWorldNode) + { + const NodeComponent& entityNode = registry.get(entity); + LightComponent& entityLight = registry.get(entity); + + const Vector3f& position = entityNode.GetPosition(CoordSys::Global); + const Quaternionf& rotation = entityNode.GetRotation(CoordSys::Global); + const Vector3f& scale = entityNode.GetScale(CoordSys::Global); + + for (const auto& lightEntry : entityLight.GetLights()) + lightEntry.light->UpdateTransform(position, rotation, scale); + } + m_invalidatedLightWorldNode.clear(); } void RenderSystem::UpdateVisibility(entt::registry& registry) { - // Unregister drawables for hidden entities - for (entt::entity entity : m_newlyHiddenEntities) + // Unregister drawable for hidden entities + for (entt::entity entity : m_newlyHiddenGfxEntities) { GraphicsComponent& entityGfx = registry.get(entity); @@ -190,10 +277,10 @@ namespace Nz for (const auto& renderableEntry : entityGfx.GetRenderables()) m_pipeline->UnregisterInstancedDrawable(worldInstance, renderableEntry.renderable.get()); } - m_newlyHiddenEntities.clear(); + m_newlyHiddenGfxEntities.clear(); - // Register drawables for newly visible entities - for (entt::entity entity : m_newlyVisibleEntities) + // Register drawable for newly visible entities + for (entt::entity entity : m_newlyVisibleGfxEntities) { GraphicsComponent& entityGfx = registry.get(entity); @@ -201,6 +288,6 @@ namespace Nz for (const auto& renderableEntry : entityGfx.GetRenderables()) m_pipeline->RegisterInstancedDrawable(worldInstance, renderableEntry.renderable.get(), renderableEntry.renderMask); } - m_newlyVisibleEntities.clear(); + m_newlyVisibleGfxEntities.clear(); } } diff --git a/src/Nazara/Network/Win32/SocketImpl.cpp b/src/Nazara/Network/Win32/SocketImpl.cpp index c14f6d988..694e65ce9 100644 --- a/src/Nazara/Network/Win32/SocketImpl.cpp +++ b/src/Nazara/Network/Win32/SocketImpl.cpp @@ -42,7 +42,7 @@ namespace Nz NazaraAssert(handle != InvalidHandle, "Invalid handle"); IpAddressImpl::SockAddrBuffer nameBuffer; - std::fill(nameBuffer.begin(), nameBuffer.end(), 0); + nameBuffer.fill(0); int bufferLength = static_cast(nameBuffer.size()); diff --git a/src/Nazara/Utility/AlgorithmUtility.cpp b/src/Nazara/Utility/AlgorithmUtility.cpp index fb958082e..85d891001 100644 --- a/src/Nazara/Utility/AlgorithmUtility.cpp +++ b/src/Nazara/Utility/AlgorithmUtility.cpp @@ -665,7 +665,7 @@ namespace Nz *vertexCount = xVertexCount*2 + yVertexCount*2 + zVertexCount*2; } - UInt64 ComputeCacheMissCount(IndexIterator indices, std::size_t indexCount) + UInt64 ComputeCacheMissCount(IndexIterator indices, UInt32 indexCount) { VertexCache cache(indices, indexCount); return cache.GetMissCount();