diff --git a/include/Nazara/Graphics/BasicMaterial.hpp b/include/Nazara/Graphics/BasicMaterial.hpp new file mode 100644 index 000000000..a623d973a --- /dev/null +++ b/include/Nazara/Graphics/BasicMaterial.hpp @@ -0,0 +1,72 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_BASIC_MATERIAL_HPP +#define NAZARA_BASIC_MATERIAL_HPP + +#include +#include + +namespace Nz +{ + class NAZARA_GRAPHICS_API BasicMaterial + { + friend class MaterialPipeline; + + public: + BasicMaterial(Material* material); + + inline const TextureRef& GetAlphaMap() const; + float GetAlphaThreshold() const; + Color GetDiffuseColor() const; + inline const TextureRef& GetDiffuseMap() const; + + inline bool HasAlphaMap() const; + inline bool HasAlphaThreshold() const; + inline bool HasDiffuseColor() const; + inline bool HasDiffuseMap() const; + + inline bool SetAlphaMap(const String& textureName); + inline void SetAlphaMap(TextureRef alphaMap); + void SetAlphaThreshold(float alphaThreshold); + void SetDiffuseColor(const Color& diffuse); + inline bool SetDiffuseMap(const String& textureName); + inline void SetDiffuseMap(TextureRef diffuseMap); + + static const std::shared_ptr& GetSettings(); + + private: + struct UniformOffsets + { + std::size_t alphaThreshold; + std::size_t diffuseColor; + }; + + struct TextureIndexes + { + std::size_t alpha; + std::size_t diffuse; + }; + + static bool Initialize(); + static void Uninitialize(); + + MaterialRef m_material; + std::size_t m_uniformBlockIndex; + TextureIndexes m_textureIndexes; + UniformOffsets m_uniformOffsets; + + static std::shared_ptr s_materialSettings; + static std::size_t s_uniformBlockIndex; + static RenderPipelineLayoutRef s_renderPipelineLayout; + static TextureIndexes s_textureIndexes; + static UniformOffsets s_uniformOffsets; + }; +} + +#include + +#endif // NAZARA_BASIC_MATERIAL_HPP diff --git a/include/Nazara/Graphics/BasicMaterial.inl b/include/Nazara/Graphics/BasicMaterial.inl new file mode 100644 index 000000000..79e92e8a2 --- /dev/null +++ b/include/Nazara/Graphics/BasicMaterial.inl @@ -0,0 +1,91 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +namespace Nz +{ + inline const TextureRef& BasicMaterial::GetAlphaMap() const + { + NazaraAssert(HasAlphaMap(), "Material has no alpha map slot"); + return m_material->GetTexture(m_textureIndexes.alpha); + } + + inline const TextureRef& BasicMaterial::GetDiffuseMap() const + { + NazaraAssert(HasDiffuseMap(), "Material has no alpha map slot"); + return m_material->GetTexture(m_textureIndexes.diffuse); + } + + inline bool BasicMaterial::HasAlphaMap() const + { + return m_textureIndexes.alpha != MaterialSettings::InvalidIndex; + } + + inline bool BasicMaterial::HasAlphaThreshold() const + { + return m_uniformOffsets.alphaThreshold != MaterialSettings::InvalidIndex; + } + + inline bool BasicMaterial::HasDiffuseColor() const + { + return m_uniformOffsets.diffuseColor != MaterialSettings::InvalidIndex; + } + + inline bool BasicMaterial::HasDiffuseMap() const + { + return m_textureIndexes.diffuse != MaterialSettings::InvalidIndex; + } + + inline bool BasicMaterial::SetAlphaMap(const String& textureName) + { + TextureRef texture = TextureLibrary::Query(textureName); + if (!texture) + { + texture = TextureManager::Get(textureName); + if (!texture) + { + NazaraError("Failed to get alpha map \"" + textureName + "\""); + return false; + } + } + + SetAlphaMap(std::move(texture)); + return true; + } + + inline void BasicMaterial::SetAlphaMap(TextureRef alphaMap) + { + NazaraAssert(HasAlphaMap(), "Material has no alpha map slot"); + m_material->SetTexture(m_textureIndexes.alpha, std::move(alphaMap)); + } + + inline bool BasicMaterial::SetDiffuseMap(const String& textureName) + { + TextureRef texture = TextureLibrary::Query(textureName); + if (!texture) + { + texture = TextureManager::Get(textureName); + if (!texture) + { + NazaraError("Failed to get diffuse map \"" + textureName + "\""); + return false; + } + } + + SetDiffuseMap(std::move(texture)); + return true; + } + + inline void BasicMaterial::SetDiffuseMap(TextureRef diffuseMap) + { + NazaraAssert(HasDiffuseMap(), "Material has no diffuse map slot"); + m_material->SetTexture(m_textureIndexes.diffuse, std::move(diffuseMap)); + } +} + +#include diff --git a/include/Nazara/Graphics/CullingList.hpp b/include/Nazara/Graphics/CullingList.hpp new file mode 100644 index 000000000..7eff97852 --- /dev/null +++ b/include/Nazara/Graphics/CullingList.hpp @@ -0,0 +1,215 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_CULLINGLIST_HPP +#define NAZARA_CULLINGLIST_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + template + class CullingList + { + public: + template class Entry; + class BoxEntry; + class NoTestEntry; + class SphereEntry; + class VolumeEntry; + + template friend class Entry; + friend BoxEntry; + friend NoTestEntry; + friend SphereEntry; + friend VolumeEntry; + + using ResultContainer = std::vector; + + CullingList() = default; + CullingList(const CullingList& renderable) = delete; + CullingList(CullingList&& renderable) = delete; + ~CullingList(); + + std::size_t Cull(const Frustumf& frustum, bool* forceInvalidation = nullptr); + + std::size_t FillWithAllEntries(bool* forceInvalidation = nullptr); + + const ResultContainer& GetFullyVisibleResults() const; + const ResultContainer& GetPartiallyVisibleResults() const; + + BoxEntry RegisterBoxTest(const T* renderable); + NoTestEntry RegisterNoTest(const T* renderable); + SphereEntry RegisterSphereTest(const T* renderable); + VolumeEntry RegisterVolumeTest(const T* renderable); + + CullingList& operator=(const CullingList& renderable) = delete; + CullingList& operator=(CullingList&& renderable) = delete; + + NazaraSignal(OnCullingListRelease, CullingList* /*cullingList*/); + + private: + inline void NotifyBoxUpdate(std::size_t index, const Boxf& boundingVolume); + inline void NotifyForceInvalidation(CullTest type, std::size_t index); + inline void NotifyMovement(CullTest type, std::size_t index, void* oldPtr, void* newPtr); + inline void NotifyRelease(CullTest type, std::size_t index); + inline void NotifySphereUpdate(std::size_t index, const Spheref& sphere); + inline void NotifyVolumeUpdate(std::size_t index, const BoundingVolumef& boundingVolume); + + struct BoxVisibilityEntry + { + Boxf box; + BoxEntry* entry; + const T* renderable; + bool forceInvalidation; + }; + + struct NoTestVisibilityEntry + { + NoTestEntry* entry; + const T* renderable; + bool forceInvalidation; + }; + + struct SphereVisibilityEntry + { + Spheref sphere; + SphereEntry* entry; + const T* renderable; + bool forceInvalidation; + }; + + struct VolumeVisibilityEntry + { + BoundingVolumef volume; + VolumeEntry* entry; + const T* renderable; + bool forceInvalidation; + }; + + std::vector m_boxTestList; + std::vector m_noTestList; + std::vector m_sphereTestList; + std::vector m_volumeTestList; + ResultContainer m_fullyVisibleResults; + ResultContainer m_partiallyVisibleResults; + }; + + template + template + class CullingList::Entry + { + public: + Entry(); + Entry(const Entry&) = delete; + Entry(Entry&& entry); + ~Entry(); + + void ForceInvalidation(); + + CullingList* GetParent() const; + + void UpdateIndex(std::size_t index); + + Entry& operator=(const Entry&) = delete; + Entry& operator=(Entry&& entry); + + protected: + Entry(CullingList* parent, std::size_t index); + + std::size_t m_index; + CullingList* m_parent; + }; + + template + class CullingList::BoxEntry : public CullingList::template Entry + { + friend CullingList; + + using ParentType = Entry; + + public: + BoxEntry(); + BoxEntry(BoxEntry&&) = default; + ~BoxEntry() = default; + + void UpdateBox(const Boxf& box); + + BoxEntry& operator=(BoxEntry&&) = default; + + private: + BoxEntry(CullingList* parent, std::size_t index); + }; + + template + class CullingList::NoTestEntry : public CullingList::template Entry + { + friend CullingList; + + using ParentType = Entry; + + public: + NoTestEntry(); + NoTestEntry(NoTestEntry&&) = default; + ~NoTestEntry() = default; + + NoTestEntry& operator=(NoTestEntry&&) = default; + + private: + NoTestEntry(CullingList* parent, std::size_t index); + }; + + template + class CullingList::SphereEntry : public CullingList::template Entry + { + friend CullingList; + + using ParentType = Entry; + + public: + SphereEntry(); + SphereEntry(SphereEntry&&) = default; + ~SphereEntry() = default; + + void UpdateSphere(const Spheref& sphere); + + SphereEntry& operator=(SphereEntry&&) = default; + + private: + SphereEntry(CullingList* parent, std::size_t index); + }; + + template + class CullingList::VolumeEntry : public CullingList::template Entry + { + friend CullingList; + + using ParentType = Entry; + + public: + VolumeEntry(); + VolumeEntry(VolumeEntry&&) = default; + ~VolumeEntry() = default; + + void UpdateVolume(const BoundingVolumef& sphere); + + VolumeEntry& operator=(VolumeEntry&&) = default; + + private: + VolumeEntry(CullingList* parent, std::size_t index); + }; +} + +#include + +#endif // NAZARA_CULLINGLIST_HPP diff --git a/include/Nazara/Graphics/CullingList.inl b/include/Nazara/Graphics/CullingList.inl new file mode 100644 index 000000000..aa94bc995 --- /dev/null +++ b/include/Nazara/Graphics/CullingList.inl @@ -0,0 +1,498 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + template + CullingList::~CullingList() + { + OnCullingListRelease(this); + } + + template + std::size_t CullingList::Cull(const Frustumf& frustum, bool* forceInvalidation) + { + m_fullyVisibleResults.clear(); + m_partiallyVisibleResults.clear(); + + bool forcedInvalidation = false; + + std::size_t fullyVisibleHash = 5U; + std::size_t partiallyVisibleHash = 5U; + + auto CombineHash = [](std::size_t currentHash, std::size_t newHash) + { + return currentHash * 23 + newHash; + }; + + for (BoxVisibilityEntry& entry : m_boxTestList) + { + switch (frustum.Intersect(entry.box)) + { + case IntersectionSide_Inside: + m_fullyVisibleResults.push_back(entry.renderable); + fullyVisibleHash = CombineHash(fullyVisibleHash, std::hash()(entry.renderable)); + + forcedInvalidation = forcedInvalidation | entry.forceInvalidation; + entry.forceInvalidation = false; + break; + + case IntersectionSide_Intersecting: + m_partiallyVisibleResults.push_back(entry.renderable); + partiallyVisibleHash = CombineHash(partiallyVisibleHash, std::hash()(entry.renderable)); + + forcedInvalidation = forcedInvalidation | entry.forceInvalidation; + entry.forceInvalidation = false; + break; + + case IntersectionSide_Outside: + break; + } + } + + for (NoTestVisibilityEntry& entry : m_noTestList) + { + m_fullyVisibleResults.push_back(entry.renderable); + CombineHash(fullyVisibleHash, std::hash()(entry.renderable)); + + if (entry.forceInvalidation) + { + forcedInvalidation = true; + entry.forceInvalidation = false; + } + } + + for (SphereVisibilityEntry& entry : m_sphereTestList) + { + switch (frustum.Intersect(entry.sphere)) + { + case IntersectionSide_Inside: + m_fullyVisibleResults.push_back(entry.renderable); + fullyVisibleHash = CombineHash(fullyVisibleHash, std::hash()(entry.renderable)); + + forcedInvalidation = forcedInvalidation | entry.forceInvalidation; + entry.forceInvalidation = false; + break; + + case IntersectionSide_Intersecting: + m_partiallyVisibleResults.push_back(entry.renderable); + partiallyVisibleHash = CombineHash(partiallyVisibleHash, std::hash()(entry.renderable)); + + forcedInvalidation = forcedInvalidation | entry.forceInvalidation; + entry.forceInvalidation = false; + break; + + case IntersectionSide_Outside: + break; + } + } + + for (VolumeVisibilityEntry& entry : m_volumeTestList) + { + switch (frustum.Intersect(entry.volume)) + { + case IntersectionSide_Inside: + m_fullyVisibleResults.push_back(entry.renderable); + fullyVisibleHash = CombineHash(fullyVisibleHash, std::hash()(entry.renderable)); + + forcedInvalidation = forcedInvalidation | entry.forceInvalidation; + entry.forceInvalidation = false; + break; + + case IntersectionSide_Intersecting: + m_partiallyVisibleResults.push_back(entry.renderable); + partiallyVisibleHash = CombineHash(partiallyVisibleHash, std::hash()(entry.renderable)); + + forcedInvalidation = forcedInvalidation | entry.forceInvalidation; + entry.forceInvalidation = false; + break; + + case IntersectionSide_Outside: + break; + } + } + + if (forceInvalidation) + *forceInvalidation = forcedInvalidation; + + return 5 + partiallyVisibleHash * 17 + fullyVisibleHash; + } + + template + std::size_t CullingList::FillWithAllEntries(bool* forceInvalidation) + { + m_fullyVisibleResults.clear(); + m_partiallyVisibleResults.clear(); + + bool forcedInvalidation = false; + + std::size_t visibleHash = 5U; + + auto FillWithList = [&](auto& testList) + { + for (auto& entry : testList) + { + m_fullyVisibleResults.push_back(entry.renderable); + visibleHash = visibleHash * 23 + std::hash()(entry.renderable); + + forcedInvalidation = forcedInvalidation | entry.forceInvalidation; + entry.forceInvalidation = false; + } + }; + + FillWithList(m_boxTestList); + FillWithList(m_noTestList); + FillWithList(m_sphereTestList); + FillWithList(m_volumeTestList); + + if (forceInvalidation) + *forceInvalidation = forcedInvalidation; + + return visibleHash; + } + + template + auto CullingList::GetFullyVisibleResults() const -> const ResultContainer& + { + return m_fullyVisibleResults; + } + + template + auto CullingList::GetPartiallyVisibleResults() const -> const ResultContainer& + { + return m_partiallyVisibleResults; + } + + template + auto CullingList::RegisterBoxTest(const T* renderable) -> BoxEntry + { + BoxEntry newEntry(this, m_boxTestList.size()); + m_boxTestList.emplace_back(BoxVisibilityEntry{ Nz::Boxf(), &newEntry, renderable, false }); //< Address of entry will be updated when moving + + return newEntry; + } + + template + auto CullingList::RegisterNoTest(const T* renderable) -> NoTestEntry + { + NoTestEntry newEntry(this, m_volumeTestList.size()); + m_noTestList.emplace_back(NoTestVisibilityEntry{&newEntry, renderable, false}); //< Address of entry will be updated when moving + + return newEntry; + } + + template + auto CullingList::RegisterSphereTest(const T* renderable) -> SphereEntry + { + SphereEntry newEntry(this, m_sphereTestList.size()); + m_sphereTestList.emplace_back(SphereVisibilityEntry{Nz::Spheref(), &newEntry, renderable, false}); //< Address of entry will be updated when moving + + return newEntry; + } + + template + auto CullingList::RegisterVolumeTest(const T* renderable) -> VolumeEntry + { + VolumeEntry newEntry(this, m_volumeTestList.size()); + m_volumeTestList.emplace_back(VolumeVisibilityEntry{Nz::BoundingVolumef(), &newEntry, renderable, false}); //< Address of entry will be updated when moving + + return newEntry; + } + + template + inline void CullingList::NotifyBoxUpdate(std::size_t index, const Boxf& box) + { + m_boxTestList[index].box = box; + } + + template + void CullingList::NotifyForceInvalidation(CullTest type, std::size_t index) + { + switch (type) + { + case CullTest::Box: + { + m_boxTestList[index].forceInvalidation = true; + break; + } + + case CullTest::NoTest: + { + m_noTestList[index].forceInvalidation = true; + break; + } + + case CullTest::Sphere: + { + m_sphereTestList[index].forceInvalidation = true; + break; + } + + case CullTest::Volume: + { + m_volumeTestList[index].forceInvalidation = true; + break; + } + + default: + NazaraInternalError("Unhandled culltype"); + break; + } + } + + template + void CullingList::NotifyMovement(CullTest type, std::size_t index, void* oldPtr, void* newPtr) + { + NazaraUnused(oldPtr); + + switch (type) + { + case CullTest::Box: + { + BoxVisibilityEntry& entry = m_boxTestList[index]; + NazaraAssert(entry.entry == oldPtr, "Invalid box entry"); + + entry.entry = static_cast(newPtr); + break; + } + + case CullTest::NoTest: + { + NoTestVisibilityEntry& entry = m_noTestList[index]; + NazaraAssert(entry.entry == oldPtr, "Invalid entry"); + + entry.entry = static_cast(newPtr); + break; + } + + case CullTest::Sphere: + { + SphereVisibilityEntry& entry = m_sphereTestList[index]; + NazaraAssert(entry.entry == oldPtr, "Invalid sphere entry"); + + entry.entry = static_cast(newPtr); + break; + } + + case CullTest::Volume: + { + VolumeVisibilityEntry& entry = m_volumeTestList[index]; + NazaraAssert(entry.entry == oldPtr, "Invalid volume entry"); + + entry.entry = static_cast(newPtr); + break; + } + + default: + NazaraInternalError("Unhandled culltype"); + break; + } + } + + template + void CullingList::NotifyRelease(CullTest type, std::size_t index) + { + switch (type) + { + case CullTest::Box: + { + m_boxTestList[index] = std::move(m_boxTestList.back()); + m_boxTestList[index].entry->UpdateIndex(index); + m_boxTestList.pop_back(); + break; + } + + case CullTest::NoTest: + { + m_noTestList[index] = std::move(m_noTestList.back()); + m_noTestList[index].entry->UpdateIndex(index); + m_noTestList.pop_back(); + break; + } + + case CullTest::Sphere: + { + m_sphereTestList[index] = std::move(m_sphereTestList.back()); + m_sphereTestList[index].entry->UpdateIndex(index); + m_sphereTestList.pop_back(); + break; + } + + case CullTest::Volume: + { + m_volumeTestList[index] = std::move(m_volumeTestList.back()); + m_volumeTestList[index].entry->UpdateIndex(index); + m_volumeTestList.pop_back(); + break; + } + + default: + NazaraInternalError("Unhandled culltype"); + break; + } + } + + template + void CullingList::NotifySphereUpdate(std::size_t index, const Spheref& sphere) + { + m_sphereTestList[index].sphere = sphere; + } + + template + void CullingList::NotifyVolumeUpdate(std::size_t index, const BoundingVolumef& boundingVolume) + { + m_volumeTestList[index].volume = boundingVolume; + } + + ////////////////////////////////////////////////////////////////////////// + + template + template + CullingList::Entry::Entry() : + m_parent(nullptr) + { + } + + template + template + CullingList::Entry::Entry(CullingList* parent, std::size_t index) : + m_index(index), + m_parent(parent) + { + } + + template + template + CullingList::Entry::Entry(Entry&& entry) : + m_index(entry.m_index), + m_parent(entry.m_parent) + { + if (m_parent) + m_parent->NotifyMovement(Type, m_index, &entry, this); + + entry.m_parent = nullptr; + } + + template + template + CullingList::Entry::~Entry() + { + if (m_parent) + m_parent->NotifyRelease(Type, m_index); + } + + template + template + void CullingList::Entry::ForceInvalidation() + { + m_parent->NotifyForceInvalidation(Type, m_index); + } + + template + template + CullingList* CullingList::Entry::GetParent() const + { + return m_parent; + } + + template + template + void CullingList::Entry::UpdateIndex(std::size_t index) + { + m_index = index; + } + + template + template + typename CullingList::template Entry& CullingList::Entry::operator=(Entry&& entry) + { + m_index = entry.m_index; + m_parent = entry.m_parent; + if (m_parent) + m_parent->NotifyMovement(Type, m_index, &entry, this); + + entry.m_parent = nullptr; + + return *this; + } + + ////////////////////////////////////////////////////////////////////////// + + template + CullingList::BoxEntry::BoxEntry() : + ParentType() + { + } + + template + CullingList::BoxEntry::BoxEntry(CullingList* parent, std::size_t index) : + ParentType(parent, index) + { + } + + template + void CullingList::BoxEntry::UpdateBox(const Boxf& box) + { + this->m_parent->NotifyBoxUpdate(this->m_index, box); + } + + ////////////////////////////////////////////////////////////////////////// + + template + CullingList::NoTestEntry::NoTestEntry() : + ParentType() + { + } + + template + CullingList::NoTestEntry::NoTestEntry(CullingList* parent, std::size_t index) : + ParentType(parent, index) + { + } + + ////////////////////////////////////////////////////////////////////////// + + template + CullingList::SphereEntry::SphereEntry() : + ParentType() + { + } + + template + CullingList::SphereEntry::SphereEntry(CullingList* parent, std::size_t index) : + ParentType(parent, index) + { + } + + template + void CullingList::SphereEntry::UpdateSphere(const Spheref& sphere) + { + this->m_parent->NotifySphereUpdate(this->m_index, sphere); + } + + ////////////////////////////////////////////////////////////////////////// + + template + CullingList::VolumeEntry::VolumeEntry() : + ParentType() + { + } + + template + CullingList::VolumeEntry::VolumeEntry(CullingList* parent, std::size_t index) : + ParentType(parent, index) + { + } + + template + void CullingList::VolumeEntry::UpdateVolume(const BoundingVolumef& volume) + { + this->m_parent->NotifyVolumeUpdate(this->m_index, volume); + } +} + +#include diff --git a/include/Nazara/Graphics/Enums.hpp b/include/Nazara/Graphics/Enums.hpp index f847d5fe4..ca7288e87 100644 --- a/include/Nazara/Graphics/Enums.hpp +++ b/include/Nazara/Graphics/Enums.hpp @@ -9,6 +9,24 @@ namespace Nz { + enum class CullTest + { + Box, + NoTest, + Sphere, + Volume + }; + + enum PredefinedShaderBinding + { + PredefinedShaderBinding_TexOverlay, + PredefinedShaderBinding_UboInstanceData, + PredefinedShaderBinding_UboLighData, + PredefinedShaderBinding_UboViewerData, + + PredefinedShaderBinding_Max = PredefinedShaderBinding_UboViewerData + }; + } #endif // NAZARA_ENUMS_GRAPHICS_HPP diff --git a/include/Nazara/Graphics/Graphics.hpp b/include/Nazara/Graphics/Graphics.hpp index 71f7f533a..7cf692bc4 100644 --- a/include/Nazara/Graphics/Graphics.hpp +++ b/include/Nazara/Graphics/Graphics.hpp @@ -10,9 +10,12 @@ #include #include #include +#include namespace Nz { + class RenderDevice; + class NAZARA_GRAPHICS_API Graphics : public ModuleBase { friend ModuleBase; @@ -20,14 +23,25 @@ namespace Nz public: using Dependencies = TypeList; - struct Config {}; + struct Config; - Graphics(Config /*config*/); - ~Graphics(); + Graphics(Config config); + ~Graphics() = default; + + inline RenderDevice& GetRenderDevice(); + + struct Config + { + bool useDedicatedRenderDevice = true; + }; private: + std::shared_ptr m_renderDevice; + static Graphics* s_instance; }; } +#include + #endif diff --git a/include/Nazara/Graphics/Graphics.inl b/include/Nazara/Graphics/Graphics.inl new file mode 100644 index 000000000..ba9766953 --- /dev/null +++ b/include/Nazara/Graphics/Graphics.inl @@ -0,0 +1,16 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + inline RenderDevice& Graphics::GetRenderDevice() + { + return *m_renderDevice; + } +} + +#include diff --git a/include/Nazara/Graphics/Material.hpp b/include/Nazara/Graphics/Material.hpp new file mode 100644 index 000000000..31e732125 --- /dev/null +++ b/include/Nazara/Graphics/Material.hpp @@ -0,0 +1,150 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_BASE_MATERIAL_HPP +#define NAZARA_BASE_MATERIAL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + struct NAZARA_GRAPHICS_API MaterialParams : ResourceParameters + { + bool loadAlphaMap = true; + bool loadDiffuseMap = true; + bool loadEmissiveMap = true; + bool loadHeightMap = true; + bool loadNormalMap = true; + bool loadSpecularMap = true; + std::string shaderName = "Basic"; + + bool IsValid() const; + }; + + class NAZARA_GRAPHICS_API Material : public RefCounted, public Resource + { + public: + Material(std::shared_ptr settings); + inline Material(const Material& material); + inline ~Material(); + + void Apply(const MaterialPipeline::Instance& instance) const; + + void BuildFromParameters(const ParameterList& matData, const MaterialParams& matParams = MaterialParams()); + + inline void Configure(const MaterialPipeline* pipeline); + inline void Configure(const MaterialPipelineInfo& pipelineInfo); + inline bool Configure(const String& pipelineName); + + inline void EnableAlphaTest(bool alphaTest); + inline void EnableBlending(bool blending); + inline void EnableColorWrite(bool colorWrite); + inline void EnableDepthBuffer(bool depthBuffer); + inline void EnableDepthSorting(bool depthSorting); + inline void EnableDepthWrite(bool depthWrite); + inline void EnableFaceCulling(bool faceCulling); + inline void EnableReflectionMapping(bool reflection); + inline void EnableScissorTest(bool scissorTest); + inline void EnableShadowCasting(bool castShadows); + inline void EnableShadowReceive(bool receiveShadows); + inline void EnableStencilTest(bool stencilTest); + inline void EnableVertexColor(bool vertexColor); + + inline void EnsurePipelineUpdate() const; + + inline RendererComparison GetDepthFunc() const; + inline BlendFunc GetDstBlend() const; + inline FaceSide GetFaceCulling() const; + inline FaceFilling GetFaceFilling() const; + inline float GetLineWidth() const; + inline const MaterialPipeline* GetPipeline() const; + inline const MaterialPipelineInfo& GetPipelineInfo() const; + inline float GetPointSize() const; + inline const std::shared_ptr& GetSettings() const; + inline const std::shared_ptr& GetShader() const; + inline BlendFunc GetSrcBlend() const; + inline UniformBuffer* GetSharedUniformBuffer(std::size_t bufferIndex) const; + inline const std::shared_ptr& GetTexture(std::size_t textureIndex) const; + inline const std::shared_ptr& GetTextureSampler(std::size_t textureIndex) const; + inline UniformBufferRef& GetUniformBuffer(std::size_t bufferIndex); + inline const UniformBufferRef& GetUniformBuffer(std::size_t bufferIndex) const; + + inline bool HasDepthMaterial() const; + inline bool HasTexture(std::size_t textureIndex) const; + inline bool HasVertexColor() const; + + inline bool IsAlphaTestEnabled() const; + inline bool IsBlendingEnabled() const; + inline bool IsColorWriteEnabled() const; + inline bool IsDepthBufferEnabled() const; + inline bool IsDepthSortingEnabled() const; + inline bool IsDepthWriteEnabled() const; + inline bool IsFaceCullingEnabled() const; + inline bool IsReflectionMappingEnabled() const; + inline bool IsScissorTestEnabled() const; + inline bool IsStencilTestEnabled() const; + inline bool IsShadowCastingEnabled() const; + inline bool IsShadowReceiveEnabled() const; + + void SaveToParameters(ParameterList* matData); + + inline void SetDepthFunc(RendererComparison depthFunc); + inline void SetDstBlend(BlendFunc func); + inline void SetFaceCulling(FaceSide faceSide); + inline void SetFaceFilling(FaceFilling filling); + inline void SetLineWidth(float lineWidth); + inline void SetPointSize(float pointSize); + inline void SetShader(std::shared_ptr shader); + inline void SetUniformBuffer(std::size_t bufferIndex, UniformBuffer* uniformBuffer); + inline void SetSrcBlend(BlendFunc func); + inline void SetTexture(std::size_t textureIndex, Texture* texture); + inline void SetTextureSampler(std::size_t textureIndex, const TextureSampler& sampler); + + inline Material& operator=(const Material& material); + + // Signals: + NazaraSignal(OnMaterialRelease, const Material* /*material*/); + + private: + inline void InvalidatePipeline(); + inline void UpdatePipeline() const; + + struct MaterialTexture + { + std::shared_ptr sampler; + std::shared_ptr texture; + }; + + std::shared_ptr m_settings; + std::vector m_textures; + std::vector m_uniformBuffers; + mutable const MaterialPipeline* m_pipeline; + MaterialPipelineInfo m_pipelineInfo; + mutable bool m_pipelineUpdated; + bool m_shadowCastingEnabled; + }; +} + +#include + +#endif // NAZARA_BASE_MATERIAL_HPP diff --git a/include/Nazara/Graphics/Material.inl b/include/Nazara/Graphics/Material.inl new file mode 100644 index 000000000..d60bde1c1 --- /dev/null +++ b/include/Nazara/Graphics/Material.inl @@ -0,0 +1,981 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +namespace Nz +{ + /*! + * \brief Constructs a Material object by assignation + * + * \param material Material to copy into this + */ + inline Material::Material(const Material& material) : + RefCounted(), + Resource(material) + { + operator=(material); + } + + /*! + * \brief Destructs the object and calls OnMaterialRelease + * + * \see OnMaterialRelease + */ + inline Material::~Material() + { + OnMaterialRelease(this); + } + + /*! + * \brief Reset material pipeline state + * + * Sets the material pipeline + * + * \remark pipeline must be valid + * + * \see Configure + */ + inline void Material::Configure(const MaterialPipeline* pipeline) + { + NazaraAssert(pipeline, "Invalid material pipeline"); + + m_pipeline = pipeline; + m_pipelineInfo = m_pipeline->GetInfo(); + m_pipelineUpdated = true; + } + + /*! + * \brief Reset material pipeline state + * + * Sets the material pipeline using pipeline info + * + * \remark pipeline must be valid + * + * \see Configure + */ + inline void Material::Configure(const MaterialPipelineInfo& pipelineInfo) + { + m_pipelineInfo = pipelineInfo; + + InvalidatePipeline(); + } + + /*! + * \brief Reset material pipeline state + * + * Sets the material pipeline using a name to lookup in the MaterialPipelineLibrary + * + * \return True if the material pipeline was found in the library + * + * \see Configure + */ + inline bool Material::Configure(const String& pipelineName) + { + MaterialPipelineRef pipeline = MaterialPipelineLibrary::Query(pipelineName); + if (!pipeline) + { + NazaraError("Failed to get pipeline \"" + pipelineName + "\""); + return false; + } + + Configure(std::move(pipeline)); + return true; + } + + /*! + * \brief Enable/Disable alpha test for this material + * + * When enabled, all objects using this material will be rendered using alpha testing, + * rejecting pixels if their alpha component is under a defined threshold. + * This allows some kind of transparency with a much cheaper cost as it doesn't prevent any optimization (as deferred rendering or batching). + * + * \param alphaTest Defines if this material will use alpha testing + * + * \remark Invalidates the pipeline + * + * \see IsAlphaTestEnabled + * \see SetAlphaThreshold + */ + inline void Material::EnableAlphaTest(bool alphaTest) + { + m_pipelineInfo.alphaTest = alphaTest; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable blending for this material + * + * When enabled, all objects using this material will be rendered using blending, obeying the dstBlend and srcBlend parameters + * This is useful with translucent objects, but will reduces performance as it prevents some optimizations (as deferred rendering) + * + * \param blending Defines if this material will use blending + * + * \remark Invalidates the pipeline + * + * \see IsBlendingEnabled + * \see SetDstBlend + * \see SetSrcBlend + */ + inline void Material::EnableBlending(bool blending) + { + m_pipelineInfo.blending = blending; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable color writing for this material + * + * \param colorWrite Defines if this material will use color writing + * + * \remark Invalidates the pipeline + * + * \see IsColorWritingEnabled + */ + inline void Material::EnableColorWrite(bool colorWrite) + { + m_pipelineInfo.colorWrite = colorWrite; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable depth buffer for this material + * + * When enabled, all objects using this material will be rendered using a depth buffer, if the RenderTarget has one. + * This will enable Depth Test, preventing further fragments to render on top of closer ones. + * + * This parameter is required for depth writing. + * + * In order to enable depth writing without enabling depth test, set the depth comparison function to RendererComparison_Never + * + * \param depthBuffer Defines if this material will use depth buffer + * + * \remark Invalidates the pipeline + * + * \see EnableDepthWrite + * \see IsDepthBufferEnabled + * \see SetDepthFunc + */ + inline void Material::EnableDepthBuffer(bool depthBuffer) + { + m_pipelineInfo.depthBuffer = depthBuffer; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable depth sorting for this material + * + * When enabled, all objects using this material will be rendered far from near + * This is useful with translucent objects, but will reduces performance as it breaks batching + * + * \param depthSorting Defines if this material will use depth sorting + * + * \remark Depth sorting may not be perfect (may be object-sorting instead of triangle-sorting) + * \remark Invalidates the pipeline + * + * \see IsDepthSortingEnabled + */ + inline void Material::EnableDepthSorting(bool depthSorting) + { + m_pipelineInfo.depthSorting = depthSorting; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable depth writing for this material + * + * When enabled, and if depth buffer is enabled and present, all fragments generated with this material will write + * to the depth buffer if they pass depth test. + * + * This is usually disabled with translucent objects, as depth test is wanted to prevent them from rendering on top of opaque objects but + * not depth writing (which could make other translucent fragments to fail depth test) + * + * \param depthBuffer Defines if this material will use depth write + * + * \remark Invalidates the pipeline + * + * \see EnableDepthBuffer + * \see IsDepthWriteEnabled + */ + inline void Material::EnableDepthWrite(bool depthWrite) + { + m_pipelineInfo.depthWrite = depthWrite; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable face culling for this material + * + * When enabled, the material prevents front and/or back faces from rendering. + * This is commonly used as an optimization to prevent processing of hidden faces by the rendering device. + * + * Use SetFaceCulling to control which side will be eliminated. + * + * \param faceCulling Defines if this material will use face culling + * + * \remark Invalidates the pipeline + * + * \see IsFaceCullingEnabled + * \see SetFaceCulling + */ + inline void Material::EnableFaceCulling(bool faceCulling) + { + m_pipelineInfo.faceCulling = faceCulling; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable reflection mapping for this material + * + * When enabled, the material will render reflections from the object environment according to the reflection mode. + * Whether or not this is expensive depends of the reflection mode and size. + * + * Please note this is only a hint for the render technique, and reflections can be forcefully enabled or disabled depending on the material shader. + * + * Use SetReflectionMode and SetReflectionSize to control reflection quality. + * + * \param reflection Defines if this material should use reflection mapping + * + * \remark May invalidates the pipeline + * + * \see IsReflectionMappingEnabled + * \see SetReflectionMode + * \see SetReflectionSize + */ + inline void Material::EnableReflectionMapping(bool reflection) + { + m_pipelineInfo.reflectionMapping = reflection; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable scissor test for this material + * + * When enabled, the material prevents fragments out of the scissor box to be rendered. + * This can be useful with GUI, where widgets must not be rendered outside of their parent rendering area. + * + * \param scissorTest Defines if this material will use scissor test + * + * \remark Invalidates the pipeline + * + * \see IsScissorTestEnabled + */ + inline void Material::EnableScissorTest(bool scissorTest) + { + m_pipelineInfo.scissorTest = scissorTest; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable shadow casting for this material + * + * When enabled, all objects using this material will be allowed to cast shadows upon any objects using a material with shadow receiving enabled. + * The depth material replaces this one when rendering shadows. + * + * \param castShadows Defines if this material will be allowed to cast shadows + * + * \remark Does not invalidate the pipeline + * + * \see EnableShadowReceive + * \see IsShadowCastingEnabled + * \see SetDepthMaterial + */ + inline void Material::EnableShadowCasting(bool castShadows) + { + // Has no influence on pipeline + m_shadowCastingEnabled = castShadows; + } + + /*! + * \brief Enable/Disable shadow receiving for this material + * + * When enabled, all objects using this material will be allowed to be casted shadows upon themselves + * Disabling this can be helpful to prevent some rendering artifacts (especially with translucent objects) + * + * \param receiveShadows Defines if this material will be able to receive shadows + * + * \remark Invalidates the pipeline + * + * \see IsShadowReceiveEnabled + */ + inline void Material::EnableShadowReceive(bool receiveShadows) + { + m_pipelineInfo.shadowReceive = receiveShadows; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable stencil test for this material + * + * When enabled, all fragments must pass the stencil test to be rendered. + * + * \param scissorTest Defines if this material will use stencil test + * + * \remark Invalidates the pipeline + * + * \see IsStencilTestEnabled + */ + inline void Material::EnableStencilTest(bool stencilTest) + { + m_pipelineInfo.stencilTest = stencilTest; + + InvalidatePipeline(); + } + + /*! + * \brief Enable/Disable vertex coloring on this material + * + * This is a temporary option, until the new material pipeline system is ready, allowing to enable vertex coloring. + * This option only works with meshes using vertex colors. + * + * \param vertexColor Defines if this material will use vertex color or not + * + * \remark Invalidates the pipeline + * + * \see HasVertexColor + */ + inline void Material::EnableVertexColor(bool vertexColor) + { + m_pipelineInfo.hasVertexColor = vertexColor; + + InvalidatePipeline(); + } + + /*! + * \brief Ensures the pipeline gets updated + * + * When the pipeline gets invalidated, it's not updated until required (per example by calling GetPipeline). + * Using this function forces the pipeline update, making GetPipeline thread-safe as long as the pipeline does not get invalidated. + * + * \see GetPipeline + */ + inline void Material::EnsurePipelineUpdate() const + { + if (!m_pipelineUpdated) + UpdatePipeline(); + } + + /*! + * \brief Gets the function to compare depth + * + * \return Function comparing the depth of two materials + * + * \see EnableDepthTest + * \see SetAmbientColor + */ + inline RendererComparison Material::GetDepthFunc() const + { + return m_pipelineInfo.depthCompare; + } + + /*! + * \brief Gets the depth material + * + * \return Constant reference to the depth material + * + * \see EnableShadowCasting + */ + inline const MaterialRef& Material::GetDepthMaterial() const + { + return m_depthMaterial; + } + + /*! + * \brief Gets the dst in blend + * + * \return Function for dst blending + * + * \see SetDstBlend + */ + inline BlendFunc Material::GetDstBlend() const + { + return m_pipelineInfo.dstBlend; + } + + /*! + * \brief Gets the face culling + * + * \return Current face culling side + * + * \see SetFaceCulling + */ + inline FaceSide Material::GetFaceCulling() const + { + return m_pipelineInfo.cullingSide; + } + + /*! + * \brief Gets the face filling + * \return Current face filling + */ + inline FaceFilling Material::GetFaceFilling() const + { + return m_pipelineInfo.faceFilling; + } + + /*! + * \brief Gets the line width of this material + * \return Line width + */ + inline float Material::GetLineWidth() const + { + return m_pipelineInfo.lineWidth; + } + + /*! + * \brief Gets the render states + * \return Constant reference to the render states + */ + inline const MaterialPipeline* Material::GetPipeline() const + { + EnsurePipelineUpdate(); + + return m_pipeline; + } + + /*! + * \brief Gets the pipeline informations + * \return Constant reference to the pipeline info + */ + inline const MaterialPipelineInfo& Material::GetPipelineInfo() const + { + return m_pipelineInfo; + } + + /*! + * \brief Gets the point size of this material + * \return Point size + */ + inline float Material::GetPointSize() const + { + return m_pipelineInfo.pointSize; + } + + /*! + * \brief Gets the reflection mode of the material + * + * \return Current reflection mode + * + * \see SetReflectionMode + */ + inline ReflectionMode Material::GetReflectionMode() const + { + return m_reflectionMode; + } + + inline const std::shared_ptr& Material::GetSettings() const + { + return m_settings; + } + + /*! + * \brief Gets the über-shader used by this material + * \return Constant pointer to the über-shader used + */ + inline const UberShader* Material::GetShader() const + { + return m_pipelineInfo.uberShader; + } + + /*! + * \brief Gets the src in blend + * \return Function for src blending + */ + inline BlendFunc Material::GetSrcBlend() const + { + return m_pipelineInfo.srcBlend; + } + + inline const TextureRef& Material::GetTexture(std::size_t textureIndex) const + { + NazaraAssert(textureIndex < m_textures.size(), "Invalid texture index"); + return m_textures[textureIndex].texture; + } + + inline TextureSampler& Material::GetTextureSampler(std::size_t textureIndex) + { + NazaraAssert(textureIndex < m_textures.size(), "Invalid texture index"); + return m_textures[textureIndex].sampler; + } + + inline const TextureSampler& Material::GetTextureSampler(std::size_t textureIndex) const + { + NazaraAssert(textureIndex < m_textures.size(), "Invalid texture index"); + return m_textures[textureIndex].sampler; + } + + inline UniformBufferRef& Material::GetUniformBuffer(std::size_t bufferIndex) + { + NazaraAssert(bufferIndex < m_uniformBuffers.size(), "Invalid uniform buffer index"); + return m_uniformBuffers[bufferIndex]; + } + + inline const UniformBufferRef& Material::GetUniformBuffer(std::size_t bufferIndex) const + { + NazaraAssert(bufferIndex < m_uniformBuffers.size(), "Invalid uniform buffer index"); + return m_uniformBuffers[bufferIndex]; + } + + inline bool Material::HasDepthMaterial() const + { + return m_depthMaterial.IsValid(); + } + + inline bool Material::HasTexture(std::size_t textureIndex) const + { + Texture* texture = GetTexture(textureIndex); + return texture && texture->IsValid(); + } + + /*! + * \brief Checks whether this material uses vertex coloring + * \return true If it is the case + */ + inline bool Material::HasVertexColor() const + { + return m_pipelineInfo.hasVertexColor; + } + + /*! + * \brief Checks whether this material has alpha test enabled + * \return true If it is the case + */ + inline bool Material::IsAlphaTestEnabled() const + { + return m_pipelineInfo.alphaTest; + } + + /*! + * \brief Checks whether this material has blending enabled + * \return true If it is the case + */ + inline bool Material::IsBlendingEnabled() const + { + return m_pipelineInfo.blending; + } + + /*! + * \brief Checks whether this material has color write enabled + * \return true If it is the case + */ + inline bool Material::IsColorWriteEnabled() const + { + return m_pipelineInfo.colorWrite; + } + + /*! + * \brief Checks whether this material has depth buffer enabled + * \return true If it is the case + */ + inline bool Material::IsDepthBufferEnabled() const + { + return m_pipelineInfo.depthBuffer; + } + + /*! + * \brief Checks whether this material has depth sorting enabled + * \return true If it is the case + */ + inline bool Material::IsDepthSortingEnabled() const + { + return m_pipelineInfo.depthSorting; + } + + /*! + * \brief Checks whether this material has depth writing enabled + * \return true If it is the case + */ + inline bool Material::IsDepthWriteEnabled() const + { + return m_pipelineInfo.depthWrite; + } + + /*! + * \brief Checks whether this material has face culling enabled + * \return true If it is the case + */ + inline bool Material::IsFaceCullingEnabled() const + { + return m_pipelineInfo.faceCulling; + } + + /*! + * \brief Checks whether this material has reflection mapping enabled + * \return true If it is the case + * + * \see EnableReflectionMapping + */ + inline bool Material::IsReflectionMappingEnabled() const + { + return m_pipelineInfo.reflectionMapping; + } + + /*! + * \brief Checks whether this material has scissor test enabled + * \return true If it is the case + */ + inline bool Material::IsScissorTestEnabled() const + { + return m_pipelineInfo.scissorTest; + } + + /*! + * \brief Checks whether this material has stencil test enabled + * \return true If it is the case + */ + inline bool Material::IsStencilTestEnabled() const + { + return m_pipelineInfo.stencilTest; + } + + /*! + * \brief Checks whether this material cast shadow + * \return true If it is the case + */ + inline bool Material::IsShadowCastingEnabled() const + { + return m_shadowCastingEnabled; + } + + /*! + * \brief Checks whether this material receive shadow + * \return true If it is the case + */ + inline bool Material::IsShadowReceiveEnabled() const + { + return m_pipelineInfo.shadowReceive; + } + + /*! + * \brief Sets the depth functor + * + * \param depthFunc + * + * \remark Invalidates the pipeline + */ + inline void Material::SetDepthFunc(RendererComparison depthFunc) + { + m_pipelineInfo.depthFunc = depthFunc; + + InvalidatePipeline(); + } + + /*! + * \brief Sets the depth material + * \return true If successful + * + * \param depthMaterial Material for depth + */ + inline void Material::SetDepthMaterial(MaterialRef depthMaterial) + { + m_depthMaterial = std::move(depthMaterial); + } + + /*! + * \brief Sets the dst in blend + * + * \param func Function for dst blending + * + * \remark Invalidates the pipeline + */ + inline void Material::SetDstBlend(BlendFunc func) + { + m_pipelineInfo.dstBlend = func; + + InvalidatePipeline(); + } + + /*! + * \brief Sets the face culling + * + * \param faceSide Face to cull + * + * \remark Invalidates the pipeline + */ + inline void Material::SetFaceCulling(FaceSide faceSide) + { + m_pipelineInfo.cullingSide = faceSide; + + InvalidatePipeline(); + } + + /*! + * \brief Sets the face filling + * + * \param filling Face to fill + * + * \remark Invalidates the pipeline + */ + inline void Material::SetFaceFilling(FaceFilling filling) + { + m_pipelineInfo.faceFilling = filling; + + InvalidatePipeline(); + } + + /*! + * \brief Sets the line width for this material + * + * This parameter is used when rendering lines, to define the width (in pixels) the line will take on the framebuffer + * + * \param lineWidth Width of the line + * + * \remark Invalidates the pipeline + * + * \see GetLineWidth + */ + inline void Material::SetLineWidth(float lineWidth) + { + m_pipelineInfo.lineWidth = lineWidth; + + InvalidatePipeline(); + } + + /*! + * \brief Sets the point size for this material + * + * This parameter is used when rendering points, to define the size (in pixels) the point will take on the framebuffer + * + * \param pointSize Size of the point + * + * \remark Invalidates the pipeline + * + * \see GetPointSize + */ + inline void Material::SetPointSize(float pointSize) + { + m_pipelineInfo.pointSize = pointSize; + + InvalidatePipeline(); + } + + /*! + * \brief Changes reflection mode of the material + * + * When reflections are enabled, the material will render reflections from the object environment according to the reflection mode. + * This function does change the reflection mode used by the material. + * + * Skyboxes reflections are the cheapest but are static and thus can't reflect other objects. + * Probes reflections are cheap, depending on probes reflection mode, but require regular probe finding from objects using it. + * Real-time reflections are expensive but provide the most accurate reflection map (and can reflect other objects around). + * + * \param reflectionMode The new reflection mode this material should use + * + * \remark May invalidates the pipeline + * + * \see EnableReflectionMapping + * \see IsReflectionMappingEnabled + * \see SetReflectionSize + */ + inline void Material::SetReflectionMode(ReflectionMode reflectionMode) + { + if (m_reflectionMode != reflectionMode) + { + OnMaterialReflectionModeChange(this, reflectionMode); + + m_reflectionMode = reflectionMode; + } + } + + /*! + * \brief Sets the shader with a constant reference to a ubershader + * + * \param uberShader Uber shader to apply + * + * \remark Invalidates the pipeline + * + * \see GetShader + */ + inline void Material::SetShader(UberShaderConstRef uberShader) + { + m_pipelineInfo.uberShader = std::move(uberShader); + + InvalidatePipeline(); + } + + /*! + * \brief Sets the shader by name + * \return true If successful + * + * \param uberShaderName Named shader + */ + inline bool Material::SetShader(const String& uberShaderName) + { + UberShaderConstRef uberShader = UberShaderLibrary::Get(uberShaderName); + if (!uberShader) + return false; + + SetShader(std::move(uberShader)); + return true; + } + + inline void Material::SetUniformBuffer(std::size_t bufferIndex, UniformBuffer* uniformBuffer) + { + NazaraAssert(bufferIndex < m_uniformBuffers.size(), "Invalid shared uniform buffer index"); + m_uniformBuffers[bufferIndex] = uniformBuffer; + } + + inline void Material::SetTexture(std::size_t textureIndex, Texture* texture) + { + NazaraAssert(textureIndex < m_textures.size(), "Invalid texture index"); + m_textures[textureIndex].texture = texture; + + if (texture) + m_pipelineInfo.textures |= UInt64(1) << UInt64(textureIndex); + else + m_pipelineInfo.textures &= ~(UInt64(1) << UInt64(textureIndex)); + + InvalidatePipeline(); + } + + inline void Material::SetTextureSampler(std::size_t textureIndex, const TextureSampler& sampler) + { + NazaraAssert(textureIndex < m_textures.size(), "Invalid texture index"); + m_textures[textureIndex].sampler = sampler; + } + + /*! + * \brief Sets the src in blend + * + * \param func Function for src blending + * + * \remark Invalidates the pipeline + * + * \see GetSrcBlend + */ + inline void Material::SetSrcBlend(BlendFunc func) + { + m_pipelineInfo.srcBlend = func; + + InvalidatePipeline(); + } + + /*! + * \brief Sets the current material with the content of the other one + * \return A reference to this + * + * \param material The other Material + */ + inline Material& Material::operator=(const Material& material) + { + Resource::operator=(material); + + m_settings = material.m_settings; + m_textures = material.m_textures; + m_depthMaterial = material.m_depthMaterial; + m_pipeline = material.m_pipeline; + m_pipelineInfo = material.m_pipelineInfo; + m_pipelineUpdated = material.m_pipelineUpdated; + m_shadowCastingEnabled = material.m_shadowCastingEnabled; + m_reflectionSize = material.m_reflectionSize; + + m_pipelineInfo.settings = m_settings; + + for (std::size_t i = 0; i < m_uniformBuffers.size(); ++i) + { + const UniformBuffer* sourceBuffer = material.GetUniformBuffer(i); + UniformBuffer* targetBuffer = m_uniformBuffers[i] = UniformBuffer::New(sourceBuffer->GetEndOffset() - sourceBuffer->GetStartOffset(), DataStorage_Hardware, BufferUsage_Dynamic); + if (!targetBuffer->CopyContent(sourceBuffer)) + NazaraError("Failed to copy uniform buffer content"); + } + + SetReflectionMode(material.GetReflectionMode()); + return *this; + } + + /*! + * \brief Gets the default material + * + * \return Reference to the default material + * + * \remark This material should NOT be modified as it would affect all objects using it + */ + inline MaterialRef Material::GetDefault() + { + return s_defaultMaterial; + } + + inline int Material::GetTextureUnit(TextureMap textureMap) + { + return s_textureUnits[textureMap]; + } + + /*! + * \brief Loads the material from file + * \return true if loading is successful + * + * \param filePath Path to the file + * \param params Parameters for the material + */ + inline MaterialRef Material::LoadFromFile(const String& filePath, const MaterialParams& params) + { + return MaterialLoader::LoadFromFile(filePath, params); + } + + /*! + * \brief Loads the material from memory + * \return true if loading is successful + * + * \param data Raw memory + * \param size Size of the memory + * \param params Parameters for the material + */ + inline MaterialRef Material::LoadFromMemory(const void* data, std::size_t size, const MaterialParams& params) + { + return MaterialLoader::LoadFromMemory(data, size, params); + } + + /*! + * \brief Loads the material from stream + * \return true if loading is successful + * + * \param stream Stream to the material + * \param params Parameters for the material + */ + inline MaterialRef Material::LoadFromStream(Stream& stream, const MaterialParams& params) + { + return MaterialLoader::LoadFromStream(stream, params); + } + + inline void Material::InvalidatePipeline() + { + m_pipelineUpdated = false; + } + + inline void Material::UpdatePipeline() const + { + m_pipeline = MaterialPipeline::GetPipeline(m_pipelineInfo); + m_pipelineUpdated = true; + } + + /*! + * \brief Creates a new material from the arguments + * \return A reference to the newly created material + * + * \param args Arguments for the material + */ + template + MaterialRef Material::New(Args&&... args) + { + std::unique_ptr object(new Material(std::forward(args)...)); + object->SetPersistent(false); + + return object.release(); + } +} + +#include diff --git a/include/Nazara/Graphics/MaterialPipeline.hpp b/include/Nazara/Graphics/MaterialPipeline.hpp new file mode 100644 index 000000000..6147e50b9 --- /dev/null +++ b/include/Nazara/Graphics/MaterialPipeline.hpp @@ -0,0 +1,87 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_MATERIALPIPELINE_HPP +#define NAZARA_MATERIALPIPELINE_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class Shader; + + struct MaterialPipelineInfo : RenderStates + { + bool alphaTest = false; + bool depthSorting = false; + bool hasVertexColor = false; + bool reflectionMapping = false; + bool shadowReceive = true; + Nz::UInt64 textures = 0; + + std::shared_ptr pipelineLayout; + std::shared_ptr settings; + std::shared_ptr shader; + }; + + inline bool operator==(const MaterialPipelineInfo& lhs, const MaterialPipelineInfo& rhs); + inline bool operator!=(const MaterialPipelineInfo& lhs, const MaterialPipelineInfo& rhs); + + + class NAZARA_GRAPHICS_API MaterialPipeline + { + public: + struct Instance; + + MaterialPipeline(const MaterialPipeline&) = delete; + MaterialPipeline(MaterialPipeline&&) = delete; + ~MaterialPipeline() = default; + + inline const Instance& Apply(UInt32 flags = ShaderFlags_None) const; + + MaterialPipeline& operator=(const MaterialPipeline&) = delete; + MaterialPipeline& operator=(MaterialPipeline&&) = delete; + + inline const MaterialPipelineInfo& GetInfo() const; + inline const Instance& GetInstance(UInt32 flags = ShaderFlags_None) const; + + static MaterialPipelineRef GetPipeline(const MaterialPipelineInfo& pipelineInfo); + + struct Instance + { + RenderPipeline renderPipeline; + //Shader::LayoutBindings bindings; + //UberShaderInstance* uberInstance = nullptr; + }; + + private: + inline MaterialPipeline(const MaterialPipelineInfo& pipelineInfo); + + void GenerateRenderPipeline(UInt32 flags) const; + + static bool Initialize(); + template static MaterialPipelineRef New(Args&&... args); + static void Uninitialize(); + + MaterialPipelineInfo m_pipelineInfo; + mutable std::array m_instances; + + using PipelineCache = std::unordered_map; + static PipelineCache s_pipelineCache; + + static MaterialPipelineLibrary::LibraryMap s_library; + }; +} + +#include + +#endif // NAZARA_MATERIALPIPELINE_HPP diff --git a/include/Nazara/Graphics/MaterialPipeline.inl b/include/Nazara/Graphics/MaterialPipeline.inl new file mode 100644 index 000000000..f771f8a52 --- /dev/null +++ b/include/Nazara/Graphics/MaterialPipeline.inl @@ -0,0 +1,138 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +namespace Nz +{ + inline MaterialPipeline::MaterialPipeline(const MaterialPipelineInfo& pipelineInfo) : + m_pipelineInfo(pipelineInfo) + { + } + + /*! + * \brief Enable pipeline states for rendering + * + * \param flags Shader flags + */ + inline const MaterialPipeline::Instance& MaterialPipeline::Apply(UInt32 flags) const + { + const Instance& instance = GetInstance(flags); + instance.uberInstance->Activate(); + + Renderer::SetRenderStates(m_pipelineInfo); + + return instance; + } + + /*! + * \brief Retrieve a MaterialPipelineInfo object describing this pipeline + * + * \return Pipeline informations + */ + const MaterialPipelineInfo& MaterialPipeline::GetInfo() const + { + return m_pipelineInfo; + } + + /*! + * \brief Retrieve (and generate if required) a pipeline instance using shader flags without applying it + * + * \param flags Shader flags + * + * \return Pipeline instance + */ + inline const MaterialPipeline::Instance& MaterialPipeline::GetInstance(UInt32 flags) const + { + const Instance& instance = m_instances[flags]; + if (!instance.uberInstance) + GenerateRenderPipeline(flags); + + return instance; + } + + bool operator==(const MaterialPipelineInfo& lhs, const MaterialPipelineInfo& rhs) + { + if (!operator==(static_cast(lhs), static_cast(rhs))) + return false; + + #define NazaraPipelineMember(field) if (lhs.field != rhs.field) return false + #define NazaraPipelineBoolMember NazaraPipelineMember + + NazaraPipelineBoolMember(depthSorting); + NazaraPipelineBoolMember(hasVertexColor); + NazaraPipelineBoolMember(reflectionMapping); + NazaraPipelineBoolMember(shadowReceive); + + NazaraPipelineMember(pipelineLayout); + NazaraPipelineMember(settings); + NazaraPipelineMember(shader); + + #undef NazaraPipelineMember + #undef NazaraPipelineBoolMember + + return true; + } + + bool operator!=(const MaterialPipelineInfo& lhs, const MaterialPipelineInfo& rhs) + { + return !operator==(lhs, rhs); + } + + /*! + * \brief Creates a new MaterialPipeline from the arguments + * \return A reference to the newly created material pipeline + * + * \param args Arguments for the material pipeline + */ + template + MaterialPipelineRef MaterialPipeline::New(Args&&... args) + { + std::unique_ptr object(new MaterialPipeline(std::forward(args)...)); + object->SetPersistent(false); + return object.release(); + } +} + +namespace std +{ + template<> + struct hash + { + size_t operator()(const Nz::MaterialPipelineInfo& pipelineInfo) const + { + hash parentHash; + + std::size_t seed = parentHash(pipelineInfo); + + Nz::UInt16 parameterHash = 0; + Nz::UInt16 parameterIndex = 0; + + #define NazaraPipelineMember(member) Nz::HashCombine(seed, pipelineInfo.member) + #define NazaraPipelineBoolMember(member) parameterHash |= ((pipelineInfo.member) ? 1U : 0U) << (parameterIndex++) + + NazaraPipelineBoolMember(alphaTest); + NazaraPipelineBoolMember(depthSorting); + NazaraPipelineBoolMember(hasVertexColor); + NazaraPipelineBoolMember(reflectionMapping); + NazaraPipelineBoolMember(shadowReceive); + + NazaraPipelineMember(pipelineLayout.get()); //< Hash pointer + NazaraPipelineMember(settings.get()); //< Hash pointer + NazaraPipelineMember(shader.get()); + + #undef NazaraPipelineMember + #undef NazaraPipelineBoolMember + + Nz::HashCombine(seed, parameterHash); + + return seed; + } + }; +} + +#include diff --git a/include/Nazara/Graphics/MaterialSettings.hpp b/include/Nazara/Graphics/MaterialSettings.hpp new file mode 100644 index 000000000..069224bc4 --- /dev/null +++ b/include/Nazara/Graphics/MaterialSettings.hpp @@ -0,0 +1,92 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_MATERIALSETTINGS_HPP +#define NAZARA_MATERIALSETTINGS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class MaterialSettings + { + public: + using PredefinedBinding = std::array; + + struct SharedUniformBlock; + struct Texture; + struct UniformBlock; + + inline MaterialSettings(); + inline MaterialSettings(std::vector textures, std::vector uniformBlocks, std::vector sharedUniformBlocks, const PredefinedBinding& predefinedBinding); + MaterialSettings(const MaterialSettings&) = default; + MaterialSettings(MaterialSettings&&) = delete; + ~MaterialSettings() = default; + + inline std::size_t GetPredefinedBindingIndex(PredefinedShaderBinding binding) const; + inline const std::shared_ptr& GetRenderPipelineLayout() const; + inline const std::vector& GetSharedUniformBlocks() const; + inline std::size_t GetSharedUniformBlockVariableOffset(std::size_t uniformBlockIndex, const std::string_view& name) const; + inline std::size_t GetSharedUniformBlockIndex(const std::string_view& name) const; + inline const std::vector& GetTextures() const; + inline std::size_t GetTextureIndex(const std::string_view& name) const; + inline const std::vector& GetUniformBlocks() const; + inline std::size_t GetUniformBlockIndex(const std::string_view& name) const; + inline std::size_t GetUniformBlockVariableOffset(std::size_t uniformBlockIndex, const std::string_view& name) const; + + MaterialSettings& operator=(const MaterialSettings&) = delete; + MaterialSettings& operator=(MaterialSettings&&) = delete; + + static constexpr std::size_t InvalidIndex = std::numeric_limits::max(); + + struct UniformVariable + { + std::string name; + std::size_t offset; + }; + + struct SharedUniformBlock + { + std::string name; + std::string bindingPoint; + std::vector uniforms; + }; + + struct Texture + { + std::string bindingPoint; + std::string name; + ImageType type; + }; + + struct UniformBlock + { + std::size_t blockSize; + std::string name; + std::string bindingPoint; + std::vector uniforms; + std::vector defaultValues; + }; + + private: + std::shared_ptr m_pipelineLayout; + std::vector m_sharedUniformBlocks; + std::vector m_textures; + std::vector m_uniformBlocks; + PredefinedBinding m_predefinedBinding; + }; +} + +#include + +#endif // NAZARA_MATERIALPIPELINESETTINGS_HPP diff --git a/include/Nazara/Graphics/MaterialSettings.inl b/include/Nazara/Graphics/MaterialSettings.inl new file mode 100644 index 000000000..ca410d0fc --- /dev/null +++ b/include/Nazara/Graphics/MaterialSettings.inl @@ -0,0 +1,147 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +namespace Nz +{ + inline MaterialSettings::MaterialSettings() : + MaterialSettings({}, {}, {}, { InvalidIndex }) + { + } + + inline MaterialSettings::MaterialSettings(std::vector textures, std::vector uniformBlocks, std::vector sharedUniformBlocks, const PredefinedBinding& predefinedBindings) : + m_sharedUniformBlocks(std::move(sharedUniformBlocks)), + m_textures(std::move(textures)), + m_uniformBlocks(std::move(uniformBlocks)), + m_predefinedBinding(predefinedBindings) + { + RenderPipelineLayoutInfo info; + + unsigned int bindingIndex = 0; + + for (const Texture& textureInfo : m_textures) + { + info.bindings.push_back({ + textureInfo.bindingPoint, + ShaderBindingType::Texture, + ShaderStageType_All, + bindingIndex++ + }); + } + + for (const UniformBlock& ubo : m_uniformBlocks) + { + info.bindings.push_back({ + ubo.bindingPoint, + ShaderBindingType::UniformBuffer, + ShaderStageType_All, + bindingIndex++ + }); + } + + for (const SharedUniformBlock& ubo : m_sharedUniformBlocks) + { + info.bindings.push_back({ + ubo.bindingPoint, + ShaderBindingType::UniformBuffer, + ShaderStageType_All, + bindingIndex++ + }); + } + + m_pipelineLayout = Graphics::Instance()->GetRenderDevice().InstantiateRenderPipelineLayout(std::move(info)); + } + + inline std::size_t MaterialSettings::GetPredefinedBindingIndex(PredefinedShaderBinding binding) const + { + return m_predefinedBinding[static_cast(binding)]; + } + + inline const std::shared_ptr& MaterialSettings::GetRenderPipelineLayout() const + { + return m_pipelineLayout; + } + + inline auto MaterialSettings::GetSharedUniformBlocks() const -> const std::vector& + { + return m_sharedUniformBlocks; + } + + inline std::size_t MaterialSettings::GetSharedUniformBlockIndex(const std::string_view& name) const + { + for (std::size_t i = 0; i < m_sharedUniformBlocks.size(); ++i) + { + if (m_sharedUniformBlocks[i].name == name) + return i; + } + + return InvalidIndex; + } + + inline auto MaterialSettings::GetTextures() const -> const std::vector& + { + return m_textures; + } + + inline std::size_t MaterialSettings::GetSharedUniformBlockVariableOffset(std::size_t uniformBlockIndex, const std::string_view& name) const + { + assert(uniformBlockIndex < m_sharedUniformBlocks.size()); + + const std::vector& variables = m_sharedUniformBlocks[uniformBlockIndex].uniforms; + for (std::size_t i = 0; i < variables.size(); ++i) + { + if (variables[i].name == name) + return i; + } + + return InvalidIndex; + } + + inline std::size_t MaterialSettings::GetTextureIndex(const std::string_view& name) const + { + for (std::size_t i = 0; i < m_textures.size(); ++i) + { + if (m_textures[i].name == name) + return i; + } + + return InvalidIndex; + } + + inline auto MaterialSettings::GetUniformBlocks() const -> const std::vector& + { + return m_uniformBlocks; + } + + inline std::size_t MaterialSettings::GetUniformBlockIndex(const std::string_view& name) const + { + for (std::size_t i = 0; i < m_uniformBlocks.size(); ++i) + { + if (m_uniformBlocks[i].name == name) + return i; + } + + return InvalidIndex; + } + + inline std::size_t MaterialSettings::GetUniformBlockVariableOffset(std::size_t uniformBlockIndex, const std::string_view& name) const + { + assert(uniformBlockIndex < m_uniformBlocks.size()); + + const std::vector& variables = m_uniformBlocks[uniformBlockIndex].uniforms; + for (std::size_t i = 0; i < variables.size(); ++i) + { + if (variables[i].name == name) + return i; + } + + return InvalidIndex; + } +} + +#include diff --git a/include/Nazara/Graphics/PhongLightingMaterial.hpp b/include/Nazara/Graphics/PhongLightingMaterial.hpp new file mode 100644 index 000000000..277cdc873 --- /dev/null +++ b/include/Nazara/Graphics/PhongLightingMaterial.hpp @@ -0,0 +1,110 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_PHONG_LIGHTING_MATERIAL_HPP +#define NAZARA_PHONG_LIGHTING_MATERIAL_HPP + +#include +#include + +namespace Nz +{ + class NAZARA_GRAPHICS_API PhongLightingMaterial + { + friend class MaterialPipeline; + + public: + PhongLightingMaterial(Material* material); + + inline const TextureRef& GetAlphaMap() const; + float GetAlphaThreshold() const; + Color GetAmbientColor() const; + Color GetDiffuseColor() const; + inline const TextureRef& GetDiffuseMap() const; + inline TextureSampler& GetDiffuseSampler(); + inline const TextureSampler& GetDiffuseSampler() const; + inline const TextureRef& GetEmissiveMap() const; + inline const TextureRef& GetHeightMap() const; + inline const TextureRef& GetNormalMap() const; + float GetShininess() const; + Color GetSpecularColor() const; + inline const TextureRef& GetSpecularMap() const; + inline TextureSampler& GetSpecularSampler(); + inline const TextureSampler& GetSpecularSampler() const; + + inline bool HasAlphaMap() const; + inline bool HasAlphaThreshold() const; + inline bool HasAmbientColor() const; + inline bool HasDiffuseColor() const; + inline bool HasDiffuseMap() const; + inline bool HasEmissiveMap() const; + inline bool HasHeightMap() const; + inline bool HasNormalMap() const; + inline bool HasShininess() const; + inline bool HasSpecularColor() const; + inline bool HasSpecularMap() const; + + inline bool SetAlphaMap(const String& textureName); + inline void SetAlphaMap(TextureRef alphaMap); + void SetAlphaThreshold(float alphaThreshold); + void SetAmbientColor(const Color& ambient); + void SetDiffuseColor(const Color& diffuse); + inline bool SetDiffuseMap(const String& textureName); + inline void SetDiffuseMap(TextureRef diffuseMap); + inline void SetDiffuseSampler(const TextureSampler& sampler); + inline bool SetEmissiveMap(const String& textureName); + inline void SetEmissiveMap(TextureRef textureName); + inline bool SetHeightMap(const String& textureName); + inline void SetHeightMap(TextureRef textureName); + inline bool SetNormalMap(const String& textureName); + inline void SetNormalMap(TextureRef textureName); + void SetShininess(float shininess); + void SetSpecularColor(const Color& specular); + inline bool SetSpecularMap(const String& textureName); + inline void SetSpecularMap(TextureRef specularMap); + inline void SetSpecularSampler(const TextureSampler& sampler); + + static const std::shared_ptr& GetSettings(); + + private: + struct PhongUniformOffsets + { + std::size_t alphaThreshold; + std::size_t shininess; + std::size_t ambientColor; + std::size_t diffuseColor; + std::size_t specularColor; + }; + + struct TextureIndexes + { + std::size_t alpha; + std::size_t diffuse; + std::size_t emissive; + std::size_t height; + std::size_t normal; + std::size_t specular; + }; + + static bool Initialize(); + static void Uninitialize(); + + MaterialRef m_material; + std::size_t m_phongUniformIndex; + TextureIndexes m_textureIndexes; + PhongUniformOffsets m_phongUniformOffsets; + + static std::shared_ptr s_materialSettings; + static std::size_t s_phongUniformBlockIndex; + static RenderPipelineLayoutRef s_renderPipelineLayout; + static TextureIndexes s_textureIndexes; + static PhongUniformOffsets s_phongUniformOffsets; + }; +} + +#include + +#endif // NAZARA_PHONG_LIGHTING_MATERIAL_HPP diff --git a/include/Nazara/Graphics/PhongLightingMaterial.inl b/include/Nazara/Graphics/PhongLightingMaterial.inl new file mode 100644 index 000000000..1fc14ed20 --- /dev/null +++ b/include/Nazara/Graphics/PhongLightingMaterial.inl @@ -0,0 +1,156 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +namespace Nz +{ + inline const TextureRef& PhongLightingMaterial::GetAlphaMap() const + { + NazaraAssert(HasAlphaMap(), "Material has no alpha map slot"); + return m_material->GetTexture(m_textureIndexes.alpha); + } + + inline const TextureRef& PhongLightingMaterial::GetDiffuseMap() const + { + NazaraAssert(HasDiffuseMap(), "Material has no alpha map slot"); + return m_material->GetTexture(m_textureIndexes.diffuse); + } + + inline const TextureRef& PhongLightingMaterial::GetEmissiveMap() const + { + NazaraAssert(HasEmissiveMap(), "Material has no alpha map slot"); + return m_material->GetTexture(m_textureIndexes.emissive); + } + + inline const TextureRef& PhongLightingMaterial::GetHeightMap() const + { + NazaraAssert(HasHeightMap(), "Material has no alpha map slot"); + return m_material->GetTexture(m_textureIndexes.height); + } + + inline const TextureRef& PhongLightingMaterial::GetNormalMap() const + { + NazaraAssert(HasNormalMap(), "Material has no alpha map slot"); + return m_material->GetTexture(m_textureIndexes.normal); + } + + inline const TextureRef& PhongLightingMaterial::GetSpecularMap() const + { + NazaraAssert(HasSpecularMap(), "Material has no alpha map slot"); + return m_material->GetTexture(m_textureIndexes.specular); + } + + inline bool PhongLightingMaterial::HasAlphaMap() const + { + return m_textureIndexes.alpha != MaterialSettings::InvalidIndex; + } + + inline bool PhongLightingMaterial::HasAlphaThreshold() const + { + return m_phongUniformOffsets.alphaThreshold != MaterialSettings::InvalidIndex; + } + + inline bool PhongLightingMaterial::HasAmbientColor() const + { + return m_phongUniformOffsets.ambientColor != MaterialSettings::InvalidIndex; + } + + inline bool PhongLightingMaterial::HasDiffuseColor() const + { + return m_phongUniformOffsets.diffuseColor != MaterialSettings::InvalidIndex; + } + + inline bool PhongLightingMaterial::HasDiffuseMap() const + { + return m_textureIndexes.diffuse != MaterialSettings::InvalidIndex; + } + + inline bool PhongLightingMaterial::HasEmissiveMap() const + { + return m_textureIndexes.emissive != MaterialSettings::InvalidIndex; + } + + inline bool PhongLightingMaterial::HasHeightMap() const + { + return m_textureIndexes.height != MaterialSettings::InvalidIndex; + } + + inline bool PhongLightingMaterial::HasNormalMap() const + { + return m_textureIndexes.normal != MaterialSettings::InvalidIndex; + } + + inline bool PhongLightingMaterial::HasShininess() const + { + return m_phongUniformOffsets.shininess != MaterialSettings::InvalidIndex; + } + + inline bool PhongLightingMaterial::HasSpecularColor() const + { + return m_phongUniformOffsets.specularColor != MaterialSettings::InvalidIndex; + } + + inline bool PhongLightingMaterial::HasSpecularMap() const + { + return m_textureIndexes.specular != MaterialSettings::InvalidIndex; + } + + inline void PhongLightingMaterial::SetAlphaMap(TextureRef alphaMap) + { + NazaraAssert(HasAlphaMap(), "Material has no alpha map slot"); + m_material->SetTexture(m_textureIndexes.alpha, std::move(alphaMap)); + } + + inline bool PhongLightingMaterial::SetDiffuseMap(const String& textureName) + { + TextureRef texture = TextureLibrary::Query(textureName); + if (!texture) + { + texture = TextureManager::Get(textureName); + if (!texture) + { + NazaraError("Failed to get diffuse map \"" + textureName + "\""); + return false; + } + } + + SetDiffuseMap(std::move(texture)); + return true; + } + + inline void PhongLightingMaterial::SetDiffuseMap(TextureRef diffuseMap) + { + NazaraAssert(HasDiffuseMap(), "Material has no diffuse map slot"); + m_material->SetTexture(m_textureIndexes.diffuse, std::move(diffuseMap)); + } + + inline bool PhongLightingMaterial::SetNormalMap(const String& textureName) + { + TextureRef texture = TextureLibrary::Query(textureName); + if (!texture) + { + texture = TextureManager::Get(textureName); + if (!texture) + { + NazaraError("Failed to get normal map \"" + textureName + "\""); + return false; + } + } + + SetNormalMap(std::move(texture)); + return true; + } + + inline void PhongLightingMaterial::SetNormalMap(TextureRef normalMap) + { + NazaraAssert(HasNormalMap(), "Material has no normal map slot"); + m_material->SetTexture(m_textureIndexes.normal, std::move(normalMap)); + } +} + +#include diff --git a/include/Nazara/Graphics/PredefinedShaderStructs.hpp b/include/Nazara/Graphics/PredefinedShaderStructs.hpp new file mode 100644 index 000000000..d196b5e0a --- /dev/null +++ b/include/Nazara/Graphics/PredefinedShaderStructs.hpp @@ -0,0 +1,68 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_PREDEFINEDSHADERSTRUCTS_HPP +#define NAZARA_PREDEFINEDSHADERSTRUCTS_HPP + +#include +#include +#include + +namespace Nz +{ + struct NAZARA_GRAPHICS_API PredefinedLightData + { + struct InnerStruct + { + std::size_t type; + std::size_t color; + std::size_t factor; + std::size_t parameter1; + std::size_t parameter2; + std::size_t parameter3; + std::size_t shadowMappingFlag; + std::size_t totalSize; + }; + + InnerStruct innerOffsets; + std::array lightArray; + std::size_t lightArraySize; + + static PredefinedLightData GetOffset(); + static MaterialSettings::SharedUniformBlock GetUniformBlock(); + }; + + struct NAZARA_GRAPHICS_API PredefinedInstanceData + { + std::size_t invWorldMatrixOffset; + std::size_t totalSize; + std::size_t worldMatrixOffset; + + static PredefinedInstanceData GetOffset(); + static MaterialSettings::SharedUniformBlock GetUniformBlock(); + }; + + struct NAZARA_GRAPHICS_API PredefinedViewerData + { + std::size_t eyePositionOffset; + std::size_t invProjMatrixOffset; + std::size_t invTargetSizeOffset; + std::size_t invViewMatrixOffset; + std::size_t invViewProjMatrixOffset; + std::size_t projMatrixOffset; + std::size_t targetSizeOffset; + std::size_t totalSize; + std::size_t viewMatrixOffset; + std::size_t viewProjMatrixOffset; + + static PredefinedViewerData GetOffset(); + static MaterialSettings::SharedUniformBlock GetUniformBlock(); + }; +} + +#include + +#endif // NAZARA_PREDEFINEDSHADERSTRUCTS_HPP diff --git a/include/Nazara/Graphics/PredefinedShaderStructs.inl b/include/Nazara/Graphics/PredefinedShaderStructs.inl new file mode 100644 index 000000000..e3daa8694 --- /dev/null +++ b/include/Nazara/Graphics/PredefinedShaderStructs.inl @@ -0,0 +1,9 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include + +namespace Nz +{ +} diff --git a/include/Nazara/Graphics/RenderQueue.hpp b/include/Nazara/Graphics/RenderQueue.hpp new file mode 100644 index 000000000..0d1f1fb81 --- /dev/null +++ b/include/Nazara/Graphics/RenderQueue.hpp @@ -0,0 +1,101 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_RENDERQUEUE_HPP +#define NAZARA_RENDERQUEUE_HPP + +#include +#include +#include + +namespace Nz +{ + class RenderQueueInternal + { + public: + using Index = Nz::UInt64; + + RenderQueueInternal() = default; + RenderQueueInternal(const RenderQueueInternal&) = default; + RenderQueueInternal(RenderQueueInternal&&) = default; + ~RenderQueueInternal() = default; + + RenderQueueInternal& operator=(const RenderQueueInternal&) = default; + RenderQueueInternal& operator=(RenderQueueInternal&&) = default; + + protected: + using RenderDataPair = std::pair; + + void Sort(); + + std::vector m_orderedRenderQueue; + }; + + template + class RenderQueue : public RenderQueueInternal + { + public: + class const_iterator; + friend const_iterator; + using size_type = std::size_t; + + RenderQueue() = default; + RenderQueue(const RenderQueue&) = default; + RenderQueue(RenderQueue&&) noexcept = default; + ~RenderQueue() = default; + + void Clear(); + + void Insert(RenderData&& data); + + template void Sort(IndexFunc&& func); + + // STL API + inline const_iterator begin() const; + inline bool empty() const; + inline const_iterator end() const; + inline size_type size() const; + + RenderQueue& operator=(const RenderQueue&) = default; + RenderQueue& operator=(RenderQueue&&) noexcept = default; + + private: + const RenderData& GetData(std::size_t i) const; + + std::vector m_data; + }; + + template + class RenderQueue::const_iterator : public std::iterator + { + friend RenderQueue; + + public: + const_iterator(const const_iterator& it); + + const RenderData& operator*() const; + + const_iterator& operator=(const const_iterator& it); + const_iterator& operator++(); + const_iterator operator++(int); + + bool operator==(const const_iterator& rhs) const; + bool operator!=(const const_iterator& rhs) const; + + void swap(const_iterator& rhs); + + private: + const_iterator(const RenderQueue* queue, std::size_t nextId); + + std::size_t m_nextDataId; + const RenderQueue* m_queue; + }; + +} + +#include + +#endif // NAZARA_RENDERQUEUE_HPP diff --git a/include/Nazara/Graphics/RenderQueue.inl b/include/Nazara/Graphics/RenderQueue.inl new file mode 100644 index 000000000..47ec03c98 --- /dev/null +++ b/include/Nazara/Graphics/RenderQueue.inl @@ -0,0 +1,136 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + template + void RenderQueue::Clear() + { + m_orderedRenderQueue.clear(); + m_data.clear(); + } + + template + void RenderQueue::Insert(RenderData&& data) + { + m_data.emplace_back(std::move(data)); + } + + template + template + void RenderQueue::Sort(IndexFunc&& func) + { + m_orderedRenderQueue.clear(); + m_orderedRenderQueue.reserve(m_data.size()); + + std::size_t dataIndex = 0; + for (const RenderData& renderData : m_data) + m_orderedRenderQueue.emplace_back(func(renderData), dataIndex++); + + RenderQueueInternal::Sort(); + } + + template + typename RenderQueue::const_iterator RenderQueue::begin() const + { + return const_iterator(this, 0); + } + + template + bool RenderQueue::empty() const + { + return m_orderedRenderQueue.empty(); + } + + template + typename RenderQueue::const_iterator RenderQueue::end() const + { + return const_iterator(this, m_orderedRenderQueue.size()); + } + + template + typename RenderQueue::size_type RenderQueue::size() const + { + return m_orderedRenderQueue.size(); + } + + template + const RenderData& RenderQueue::GetData(std::size_t i) const + { + NazaraAssert(i < m_orderedRenderQueue.size(), "Cannot dereference post-end iterator"); + + return m_data[m_orderedRenderQueue[i].second]; + } + + + template + RenderQueue::const_iterator::const_iterator(const RenderQueue* queue, std::size_t nextId) : + m_nextDataId(nextId), + m_queue(queue) + { + } + + template + RenderQueue::const_iterator::const_iterator(const const_iterator& it) : + m_nextDataId(it.m_nextDataId), + m_queue(it.m_queue) + { + } + + template + const RenderData& RenderQueue::const_iterator::operator*() const + { + return m_queue->GetData(m_nextDataId); + } + + template + typename RenderQueue::const_iterator& RenderQueue::const_iterator::operator=(const const_iterator& it) + { + m_nextDataId = it.m_nextDataId; + m_queue = it.m_queue; + + return *this; + } + + template + typename RenderQueue::const_iterator& RenderQueue::const_iterator::operator++() + { + ++m_nextDataId; + + return *this; + } + + template + typename RenderQueue::const_iterator RenderQueue::const_iterator::operator++(int) + { + return iterator(m_queue, m_nextDataId++); + } + + template + bool RenderQueue::const_iterator::operator==(const typename RenderQueue::const_iterator& rhs) const + { + NazaraAssert(m_queue == rhs.m_queue, "Cannot compare iterator coming from different queues"); + + return m_nextDataId == rhs.m_nextDataId; + } + + template + bool RenderQueue::const_iterator::operator!=(const typename RenderQueue::const_iterator& rhs) const + { + return !operator==(rhs); + } + + template + void RenderQueue::const_iterator::swap(typename RenderQueue::const_iterator& rhs) + { + NazaraAssert(m_queue == rhs.m_queue, "Cannot swap iterator coming from different queues"); + + using std::swap; + + swap(m_nextDataId, rhs.m_nextDataId); + } +} diff --git a/include/Nazara/Renderer/RenderPipeline.hpp b/include/Nazara/Renderer/RenderPipeline.hpp index 6e938eba7..82f08e7c4 100644 --- a/include/Nazara/Renderer/RenderPipeline.hpp +++ b/include/Nazara/Renderer/RenderPipeline.hpp @@ -10,7 +10,6 @@ #include #include #include -//#include namespace Nz { diff --git a/src/Nazara/Graphics/BasicMaterial.cpp b/src/Nazara/Graphics/BasicMaterial.cpp new file mode 100644 index 000000000..6843562b5 --- /dev/null +++ b/src/Nazara/Graphics/BasicMaterial.cpp @@ -0,0 +1,203 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + namespace + { + constexpr std::size_t AlphaMapBinding = 0; + constexpr std::size_t DiffuseMapBinding = 1; + constexpr std::size_t TextureOverlayBinding = 2; + } + + BasicMaterial::BasicMaterial(Material* material) : + m_material(material) + { + NazaraAssert(material, "Invalid material"); + + // Most common case: don't fetch texture indexes as a little optimization + const std::shared_ptr& materialSettings = material->GetSettings(); + if (materialSettings == s_materialSettings) + { + m_textureIndexes = s_textureIndexes; + m_uniformBlockIndex = s_uniformBlockIndex; + m_uniformOffsets = s_uniformOffsets; + } + else + { + m_textureIndexes.alpha = materialSettings->GetTextureIndex("Alpha"); + m_textureIndexes.diffuse = materialSettings->GetTextureIndex("Diffuse"); + + m_uniformBlockIndex = materialSettings->GetUniformBlockIndex("BasicSettings"); + + m_uniformOffsets.alphaThreshold = materialSettings->GetUniformBlockVariableOffset(m_uniformBlockIndex, "AlphaThreshold"); + m_uniformOffsets.diffuseColor = materialSettings->GetUniformBlockVariableOffset(m_uniformBlockIndex, "DiffuseColor"); + } + } + + float BasicMaterial::GetAlphaThreshold() const + { + NazaraAssert(HasAlphaThreshold(), "Material has no alpha threshold uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_uniformBlockIndex), BufferAccess_ReadOnly); + return *AccessByOffset(mapper.GetPointer(), m_uniformOffsets.alphaThreshold); + } + + Color BasicMaterial::GetDiffuseColor() const + { + NazaraAssert(HasDiffuseColor(), "Material has no diffuse color uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_uniformBlockIndex), BufferAccess_ReadOnly); + + const float* colorPtr = AccessByOffset(mapper.GetPointer(), m_uniformOffsets.diffuseColor); + return Color(colorPtr[0] * 255, colorPtr[1] * 255, colorPtr[2] * 255, colorPtr[3] * 255); //< TODO: Make color able to use float + } + + void BasicMaterial::SetAlphaThreshold(float alphaThreshold) + { + NazaraAssert(HasAlphaThreshold(), "Material has no alpha threshold uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_uniformBlockIndex), BufferAccess_WriteOnly); + *AccessByOffset(mapper.GetPointer(), m_uniformOffsets.alphaThreshold) = alphaThreshold; + } + + void BasicMaterial::SetDiffuseColor(const Color& diffuse) + { + NazaraAssert(HasDiffuseColor(), "Material has no diffuse color uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_uniformBlockIndex), BufferAccess_WriteOnly); + float* colorPtr = AccessByOffset(mapper.GetPointer(), m_uniformOffsets.diffuseColor); + colorPtr[0] = diffuse.r / 255.f; + colorPtr[1] = diffuse.g / 255.f; + colorPtr[2] = diffuse.b / 255.f; + colorPtr[3] = diffuse.a / 255.f; + } + + const std::shared_ptr& BasicMaterial::GetSettings() + { + return s_materialSettings; + } + + bool BasicMaterial::Initialize() + { + RenderPipelineLayoutInfo info; + info.bindings.assign({ + { + "MaterialAlphaMap", + ShaderBindingType_Texture, + ShaderStageType_Fragment, + AlphaMapBinding + }, + { + "MaterialDiffuseMap", + ShaderBindingType_Texture, + ShaderStageType_Fragment, + DiffuseMapBinding + }, + { + "TextureOverlay", + ShaderBindingType_Texture, + ShaderStageType_Fragment, + TextureOverlayBinding + } + }); + + s_renderPipelineLayout = RenderPipelineLayout::New(); + s_renderPipelineLayout->Create(info); + + FieldOffsets fieldOffsets(StructLayout_Std140); + + s_uniformOffsets.diffuseColor = fieldOffsets.AddField(StructFieldType_Float4); + s_uniformOffsets.alphaThreshold = fieldOffsets.AddField(StructFieldType_Float1); + + MaterialSettings::PredefinedBinding predefinedBinding; + predefinedBinding.fill(MaterialSettings::InvalidIndex); + + std::vector variables; + variables.assign({ + { + "AlphaThreshold", + s_uniformOffsets.alphaThreshold + }, + { + "DiffuseColor", + s_uniformOffsets.diffuseColor + } + }); + + static_assert(sizeof(Vector4f) == 4 * sizeof(float), "Vector4f is expected to be exactly 4 floats wide"); + + std::vector defaultValues(fieldOffsets.GetSize()); + *AccessByOffset(defaultValues.data(), s_uniformOffsets.diffuseColor) = Vector4f(1.f, 1.f, 1.f, 1.f); + *AccessByOffset(defaultValues.data(), s_uniformOffsets.alphaThreshold) = 0.2f; + + std::vector uniformBlocks; + s_uniformBlockIndex = uniformBlocks.size(); + uniformBlocks.assign({ + { + "BasicSettings", + fieldOffsets.GetSize(), + "MaterialBasicSettings", + std::move(variables), + std::move(defaultValues) + } + }); + + std::vector sharedUniformBlock; + + predefinedBinding[PredefinedShaderBinding_UboInstanceData] = sharedUniformBlock.size(); + sharedUniformBlock.push_back(PredefinedInstanceData::GetUniformBlock()); + predefinedBinding[PredefinedShaderBinding_UboViewerData] = sharedUniformBlock.size(); + sharedUniformBlock.push_back(PredefinedViewerData::GetUniformBlock()); + + std::vector textures; + s_textureIndexes.alpha = textures.size(); + textures.push_back({ + "Alpha", + ImageType_2D, + "MaterialAlphaMap" + }); + + s_textureIndexes.diffuse = textures.size(); + textures.push_back({ + "Diffuse", + ImageType_2D, + "MaterialDiffuseMap" + }); + + predefinedBinding[PredefinedShaderBinding_TexOverlay] = textures.size(); + textures.push_back({ + "Overlay", + ImageType_2D, + "TextureOverlay" + }); + + s_materialSettings = std::make_shared(std::move(textures), std::move(uniformBlocks), std::move(sharedUniformBlock), predefinedBinding); + + return true; + } + + void BasicMaterial::Uninitialize() + { + s_renderPipelineLayout.Reset(); + s_materialSettings.reset(); + } + + std::shared_ptr BasicMaterial::s_materialSettings; + std::size_t BasicMaterial::s_uniformBlockIndex; + RenderPipelineLayoutRef BasicMaterial::s_renderPipelineLayout; + BasicMaterial::TextureIndexes BasicMaterial::s_textureIndexes; + BasicMaterial::UniformOffsets BasicMaterial::s_uniformOffsets; +} diff --git a/src/Nazara/Graphics/BasicRenderQueue.cpp b/src/Nazara/Graphics/BasicRenderQueue.cpp new file mode 100644 index 000000000..88ce8ddbd --- /dev/null +++ b/src/Nazara/Graphics/BasicRenderQueue.cpp @@ -0,0 +1,956 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include + +///TODO: Replace sinus/cosinus by a lookup table (which will lead to a speed up about 10x) + +namespace Nz +{ + /*! + * \ingroup graphics + * \class Nz::BasicRenderQueue + * \brief Graphics class that represents a simple rendering queue + */ + + /*! + * \brief Adds multiple billboards to the queue + * + * \param renderOrder Order of rendering + * \param material Material of the billboards + * \param count Number of billboards + * \param positionPtr Position of the billboards + * \param sizePtr Sizes of the billboards + * \param sinCosPtr Rotation of the billboards if null, Vector2f(0.f, 1.f) is used + * \param colorPtr Color of the billboards if null, Color::White is used + * + * \remark Produces a NazaraAssert if material is invalid + */ + + void BasicRenderQueue::AddBillboards(int renderOrder, const Material* material, std::size_t billboardCount, const Recti& scissorRect, SparsePtr positionPtr, SparsePtr sizePtr, SparsePtr sinCosPtr, SparsePtr colorPtr) + { + NazaraAssert(material, "Invalid material"); + + RegisterLayer(renderOrder); + + Vector2f defaultSinCos(0.f, 1.f); // sin(0) = 0, cos(0) = 1 + + if (!sinCosPtr) + sinCosPtr.Reset(&defaultSinCos, 0); // The trick here is to put the stride to zero, which leads the pointer to be immobile + + if (!colorPtr) + colorPtr.Reset(&Color::White, 0); // Same + + if (material->IsDepthSortingEnabled()) + { + for (std::size_t i = 0; i < billboardCount; ++i) + { + depthSortedBillboards.Insert({ + renderOrder, + material, + scissorRect, + { + *colorPtr++, + *positionPtr++, + *sizePtr++, + *sinCosPtr++ + } + }); + } + } + else + { + std::size_t billboardIndex = m_billboards.size(); + m_billboards.resize(billboardIndex + billboardCount); + BillboardData* data = &m_billboards[billboardIndex]; + + for (std::size_t i = 0; i < billboardCount; ++i) + { + data->center = *positionPtr++; + data->color = *colorPtr++; + data->sinCos = *sinCosPtr++; + data->size = *sizePtr++; + data++; + } + + billboards.Insert({ + renderOrder, + material, + scissorRect, + billboardCount, + billboardIndex + }); + } + } + + /*! + * \brief Adds multiple billboards to the queue + * + * \param renderOrder Order of rendering + * \param material Material of the billboards + * \param count Number of billboards + * \param positionPtr Position of the billboards + * \param sizePtr Sizes of the billboards + * \param sinCosPtr Rotation of the billboards if null, Vector2f(0.f, 1.f) is used + * \param alphaPtr Alpha parameters of the billboards if null, 1.f is used + * + * \remark Produces a NazaraAssert if material is invalid + */ + + void BasicRenderQueue::AddBillboards(int renderOrder, const Material* material, std::size_t billboardCount, const Recti& scissorRect, SparsePtr positionPtr, SparsePtr sizePtr, SparsePtr sinCosPtr, SparsePtr alphaPtr) + { + NazaraAssert(material, "Invalid material"); + + RegisterLayer(renderOrder); + + Vector2f defaultSinCos(0.f, 1.f); // sin(0) = 0, cos(0) = 1 + + if (!sinCosPtr) + sinCosPtr.Reset(&defaultSinCos, 0); // The trick here is to put the stride to zero, which leads the pointer to be immobile + + float defaultAlpha = 1.f; + + if (!alphaPtr) + alphaPtr.Reset(&defaultAlpha, 0); // Same + + if (material->IsDepthSortingEnabled()) + { + for (std::size_t i = 0; i < billboardCount; ++i) + { + depthSortedBillboards.Insert({ + renderOrder, + material, + scissorRect, + { + ComputeColor(*alphaPtr++), + *positionPtr++, + *sizePtr++, + *sinCosPtr++ + } + }); + } + } + else + { + std::size_t billboardIndex = m_billboards.size(); + m_billboards.resize(billboardIndex + billboardCount); + BillboardData* data = &m_billboards[billboardIndex]; + + for (std::size_t i = 0; i < billboardCount; ++i) + { + data->center = *positionPtr++; + data->color = ComputeColor(*alphaPtr++); + data->sinCos = *sinCosPtr++; + data->size = *sizePtr++; + data++; + } + + billboards.Insert({ + renderOrder, + material, + scissorRect, + billboardCount, + billboardIndex + }); + } + } + + /*! + * \brief Adds multiple billboards to the queue + * + * \param renderOrder Order of rendering + * \param material Material of the billboards + * \param count Number of billboards + * \param positionPtr Position of the billboards + * \param sizePtr Sizes of the billboards + * \param anglePtr Rotation of the billboards if null, 0.f is used + * \param colorPtr Color of the billboards if null, Color::White is used + * + * \remark Produces a NazaraAssert if material is invalid + */ + + void BasicRenderQueue::AddBillboards(int renderOrder, const Material* material, std::size_t billboardCount, const Recti& scissorRect, SparsePtr positionPtr, SparsePtr sizePtr, SparsePtr anglePtr, SparsePtr colorPtr) + { + NazaraAssert(material, "Invalid material"); + + RegisterLayer(renderOrder); + + float defaultRotation = 0.f; + + if (!anglePtr) + anglePtr.Reset(&defaultRotation, 0); // The trick here is to put the stride to zero, which leads the pointer to be immobile + + if (!colorPtr) + colorPtr.Reset(&Color::White, 0); // Same + + if (material->IsDepthSortingEnabled()) + { + for (std::size_t i = 0; i < billboardCount; ++i) + { + depthSortedBillboards.Insert({ + renderOrder, + material, + scissorRect, + { + *colorPtr++, + *positionPtr++, + *sizePtr++, + ComputeSinCos(*anglePtr++) + } + }); + } + } + else + { + std::size_t billboardIndex = m_billboards.size(); + m_billboards.resize(billboardIndex + billboardCount); + BillboardData* data = &m_billboards[billboardIndex]; + + for (std::size_t i = 0; i < billboardCount; ++i) + { + data->center = *positionPtr++; + data->color = *colorPtr++; + data->sinCos = ComputeSinCos(*anglePtr++); + data->size = *sizePtr++; + data++; + } + + billboards.Insert({ + renderOrder, + material, + scissorRect, + billboardCount, + billboardIndex + }); + } + } + + /*! + * \brief Adds multiple billboards to the queue + * + * \param renderOrder Order of rendering + * \param material Material of the billboards + * \param count Number of billboards + * \param positionPtr Position of the billboards + * \param sizePtr Sizes of the billboards + * \param anglePtr Rotation of the billboards if null, 0.f is used + * \param alphaPtr Alpha parameters of the billboards if null, 1.f is used + * + * \remark Produces a NazaraAssert if material is invalid + */ + + void BasicRenderQueue::AddBillboards(int renderOrder, const Material* material, std::size_t billboardCount, const Recti& scissorRect, SparsePtr positionPtr, SparsePtr sizePtr, SparsePtr anglePtr, SparsePtr alphaPtr) + { + NazaraAssert(material, "Invalid material"); + + RegisterLayer(renderOrder); + + float defaultRotation = 0.f; + + if (!anglePtr) + anglePtr.Reset(&defaultRotation, 0); // The trick here is to put the stride to zero, which leads the pointer to be immobile + + float defaultAlpha = 1.f; + + if (!alphaPtr) + alphaPtr.Reset(&defaultAlpha, 0); // Same + + if (material->IsDepthSortingEnabled()) + { + for (std::size_t i = 0; i < billboardCount; ++i) + { + depthSortedBillboards.Insert({ + renderOrder, + material, + scissorRect, + { + ComputeColor(*alphaPtr++), + *positionPtr++, + *sizePtr++, + ComputeSinCos(*anglePtr++) + } + }); + } + } + else + { + std::size_t billboardIndex = m_billboards.size(); + m_billboards.resize(billboardIndex + billboardCount); + BillboardData* data = &m_billboards[billboardIndex]; + + for (std::size_t i = 0; i < billboardCount; ++i) + { + data->center = *positionPtr++; + data->color = ComputeColor(*alphaPtr++); + data->sinCos = ComputeSinCos(*anglePtr++); + data->size = *sizePtr++; + data++; + } + + billboards.Insert({ + renderOrder, + material, + scissorRect, + billboardCount, + billboardIndex + }); + } + } + + /*! + * \brief Adds multiple billboards to the queue + * + * \param renderOrder Order of rendering + * \param material Material of the billboards + * \param count Number of billboards + * \param positionPtr Position of the billboards + * \param sizePtr Size of the billboards + * \param sinCosPtr Rotation of the billboards if null, Vector2f(0.f, 1.f) is used + * \param colorPtr Color of the billboards if null, Color::White is used + * + * \remark Produces a NazaraAssert if material is invalid + */ + + void BasicRenderQueue::AddBillboards(int renderOrder, const Material* material, std::size_t billboardCount, const Recti& scissorRect, SparsePtr positionPtr, SparsePtr sizePtr, SparsePtr sinCosPtr, SparsePtr colorPtr) + { + NazaraAssert(material, "Invalid material"); + + RegisterLayer(renderOrder); + + Vector2f defaultSinCos(0.f, 1.f); // sin(0) = 0, cos(0) = 1 + + if (!sinCosPtr) + sinCosPtr.Reset(&defaultSinCos, 0); // The trick here is to put the stride to zero, which leads the pointer to be immobile + + if (!colorPtr) + colorPtr.Reset(&Color::White, 0); // Same + + if (material->IsDepthSortingEnabled()) + { + for (std::size_t i = 0; i < billboardCount; ++i) + { + depthSortedBillboards.Insert({ + renderOrder, + material, + scissorRect, + { + *colorPtr++, + *positionPtr++, + ComputeSize(*sizePtr++), + *sinCosPtr++ + } + }); + } + } + else + { + std::size_t billboardIndex = m_billboards.size(); + m_billboards.resize(billboardIndex + billboardCount); + BillboardData* data = &m_billboards[billboardIndex]; + + for (std::size_t i = 0; i < billboardCount; ++i) + { + data->center = *positionPtr++; + data->color = *colorPtr++; + data->sinCos = *sinCosPtr++; + data->size = ComputeSize(*sizePtr++); + data++; + } + + billboards.Insert({ + renderOrder, + material, + scissorRect, + billboardCount, + billboardIndex + }); + } + } + + /*! + * \brief Adds multiple billboards to the queue + * + * \param renderOrder Order of rendering + * \param material Material of the billboards + * \param count Number of billboards + * \param positionPtr Position of the billboards + * \param sizePtr Size of the billboards + * \param sinCosPtr Rotation of the billboards if null, Vector2f(0.f, 1.f) is used + * \param alphaPtr Alpha parameters of the billboards if null, 1.f is used + * + * \remark Produces a NazaraAssert if material is invalid + */ + + void BasicRenderQueue::AddBillboards(int renderOrder, const Material* material, std::size_t billboardCount, const Recti& scissorRect, SparsePtr positionPtr, SparsePtr sizePtr, SparsePtr sinCosPtr, SparsePtr alphaPtr) + { + NazaraAssert(material, "Invalid material"); + + RegisterLayer(renderOrder); + + Vector2f defaultSinCos(0.f, 1.f); // sin(0) = 0, cos(0) = 1 + + if (!sinCosPtr) + sinCosPtr.Reset(&defaultSinCos, 0); // The trick here is to put the stride to zero, which leads the pointer to be immobile + + float defaultAlpha = 1.f; + + if (!alphaPtr) + alphaPtr.Reset(&defaultAlpha, 0); // Same + + if (material->IsDepthSortingEnabled()) + { + for (std::size_t i = 0; i < billboardCount; ++i) + { + depthSortedBillboards.Insert({ + renderOrder, + material, + scissorRect, + { + ComputeColor(*alphaPtr++), + *positionPtr++, + ComputeSize(*sizePtr++), + *sinCosPtr++ + } + }); + } + } + else + { + std::size_t billboardIndex = m_billboards.size(); + m_billboards.resize(billboardIndex + billboardCount); + BillboardData* data = &m_billboards[billboardIndex]; + + for (std::size_t i = 0; i < billboardCount; ++i) + { + data->center = *positionPtr++; + data->color = ComputeColor(*alphaPtr++); + data->sinCos = *sinCosPtr++; + data->size = ComputeSize(*sizePtr++); + data++; + } + + billboards.Insert({ + renderOrder, + material, + scissorRect, + billboardCount, + billboardIndex + }); + } + } + + /*! + * \brief Adds multiple billboards to the queue + * + * \param renderOrder Order of rendering + * \param material Material of the billboards + * \param count Number of billboards + * \param positionPtr Position of the billboards + * \param sizePtr Size of the billboards + * \param anglePtr Rotation of the billboards if null, 0.f is used + * \param colorPtr Color of the billboards if null, Color::White is used + * + * \remark Produces a NazaraAssert if material is invalid + */ + + void BasicRenderQueue::AddBillboards(int renderOrder, const Material* material, std::size_t billboardCount, const Recti& scissorRect, SparsePtr positionPtr, SparsePtr sizePtr, SparsePtr anglePtr, SparsePtr colorPtr) + { + NazaraAssert(material, "Invalid material"); + + RegisterLayer(renderOrder); + + float defaultRotation = 0.f; + + if (!anglePtr) + anglePtr.Reset(&defaultRotation, 0); // The trick here is to put the stride to zero, which leads the pointer to be immobile + + if (!colorPtr) + colorPtr.Reset(&Color::White, 0); // Same + + if (material->IsDepthSortingEnabled()) + { + for (std::size_t i = 0; i < billboardCount; ++i) + { + depthSortedBillboards.Insert({ + renderOrder, + material, + scissorRect, + { + *colorPtr++, + *positionPtr++, + ComputeSize(*sizePtr++), + ComputeSinCos(*anglePtr++) + } + }); + } + } + else + { + std::size_t billboardIndex = m_billboards.size(); + m_billboards.resize(billboardIndex + billboardCount); + BillboardData* data = &m_billboards[billboardIndex]; + + for (std::size_t i = 0; i < billboardCount; ++i) + { + data->center = *positionPtr++; + data->color = *colorPtr++; + data->sinCos = ComputeSinCos(*anglePtr++); + data->size = ComputeSize(*sizePtr++); + data++; + } + + billboards.Insert({ + renderOrder, + material, + scissorRect, + billboardCount, + billboardIndex + }); + } + } + + /*! + * \brief Adds multiple billboards to the queue + * + * \param renderOrder Order of rendering + * \param material Material of the billboards + * \param count Number of billboards + * \param positionPtr Position of the billboards + * \param sizePtr Size of the billboards + * \param anglePtr Rotation of the billboards if null, 0.f is used + * \param alphaPtr Alpha parameters of the billboards if null, 1.f is used + * + * \remark Produces a NazaraAssert if material is invalid + */ + + void BasicRenderQueue::AddBillboards(int renderOrder, const Material* material, std::size_t billboardCount, const Recti& scissorRect, SparsePtr positionPtr, SparsePtr sizePtr, SparsePtr anglePtr, SparsePtr alphaPtr) + { + NazaraAssert(material, "Invalid material"); + + RegisterLayer(renderOrder); + + float defaultRotation = 0.f; + + if (!anglePtr) + anglePtr.Reset(&defaultRotation, 0); // The trick here is to put the stride to zero, which leads the pointer to be immobile + + float defaultAlpha = 1.f; + + if (!alphaPtr) + alphaPtr.Reset(&defaultAlpha, 0); // Same + + if (material->IsDepthSortingEnabled()) + { + for (std::size_t i = 0; i < billboardCount; ++i) + { + depthSortedBillboards.Insert({ + renderOrder, + material, + scissorRect, + { + ComputeColor(*alphaPtr++), + *positionPtr++, + ComputeSize(*sizePtr++), + ComputeSinCos(*anglePtr++) + } + }); + } + } + else + { + std::size_t billboardIndex = m_billboards.size(); + m_billboards.resize(billboardIndex + billboardCount); + BillboardData* data = &m_billboards[billboardIndex]; + + for (std::size_t i = 0; i < billboardCount; ++i) + { + data->center = *positionPtr++; + data->color = ComputeColor(*alphaPtr++); + data->sinCos = ComputeSinCos(*anglePtr++); + data->size = ComputeSize(*sizePtr++); + data++; + } + + billboards.Insert({ + renderOrder, + material, + scissorRect, + billboardCount, + billboardIndex + }); + } + } + + /*! + * \brief Adds drawable to the queue + * + * \param renderOrder Order of rendering + * \param drawable Drawable user defined + * + * \remark Produces a NazaraError if drawable is invalid + */ + void BasicRenderQueue::AddDrawable(int renderOrder, const Drawable* drawable) + { + NazaraAssert(drawable, "Invalid material"); + + RegisterLayer(renderOrder); + + customDrawables.Insert({ + renderOrder, + drawable + }); + } + + /*! + * \brief Adds mesh to the queue + * + * \param renderOrder Order of rendering + * \param material Material of the mesh + * \param meshData Data of the mesh + * \param meshAABB Box of the mesh + * \param transformMatrix Matrix of the mesh + * + * \remark Produces a NazaraAssert if material is invalid + */ + void BasicRenderQueue::AddMesh(int renderOrder, const Material* material, const MeshData& meshData, const Boxf& meshAABB, const Nz::Matrix4f& transformMatrix, std::size_t instanceIndex, const Recti& scissorRect) + { + NazaraAssert(material, "Invalid material"); + + RegisterLayer(renderOrder); + + Spheref obbSphere(transformMatrix.GetTranslation() + meshAABB.GetCenter(), meshAABB.GetSquaredRadius()); + + if (material->IsDepthSortingEnabled()) + { + depthSortedModels.Insert({ + renderOrder, + instanceIndex, + meshData, + material, + scissorRect, + obbSphere + }); + } + else + { + models.Insert({ + renderOrder, + instanceIndex, + meshData, + material, + scissorRect, + obbSphere + }); + } + } + + /*! + * \brief Adds sprites to the queue + * + * \param renderOrder Order of rendering + * \param material Material of the sprites + * \param vertices Buffer of data for the sprites + * \param spriteCount Number of sprites + * \param overlay Texture of the sprites + * + * \remark Produces a NazaraAssert if material is invalid + */ + void BasicRenderQueue::AddSprites(int renderOrder, const Material* material, const VertexStruct_XYZ_Color_UV* vertices, std::size_t spriteCount, const Recti& scissorRect, const Texture* overlay /*= nullptr*/) + { + NazaraAssert(material, "Invalid material"); + + RegisterLayer(renderOrder); + + if (material->IsDepthSortingEnabled()) + { + depthSortedSprites.Insert({ + renderOrder, + spriteCount, + material, + overlay, + vertices, + scissorRect + }); + } + else + { + basicSprites.Insert({ + renderOrder, + spriteCount, + material, + overlay, + vertices, + scissorRect + }); + } + } + + /*! + * \brief Clears the queue + * + * \param fully Should everything be cleared or we can keep layers + */ + + void BasicRenderQueue::Clear(bool fully) + { + AbstractRenderQueue::Clear(fully); + + basicSprites.Clear(); + billboards.Clear(); + depthSortedBillboards.Clear(); + depthSortedModels.Clear(); + depthSortedSprites.Clear(); + models.Clear(); + + m_pipelineCache.clear(); + m_materialCache.clear(); + m_overlayCache.clear(); + m_shaderCache.clear(); + m_textureCache.clear(); + m_vertexBufferCache.clear(); + + m_billboards.clear(); + m_renderLayers.clear(); + } + + /*! + * \brief Sorts the object according to the viewer position, furthest to nearest + * + * \param viewer Viewer of the scene + */ + void BasicRenderQueue::Sort(const AbstractViewer* viewer) + { + m_layerCache.clear(); + for (int layer : m_renderLayers) + m_layerCache.emplace(layer, m_layerCache.size()); + + auto GetOrInsert = [](auto& container, auto&& value) + { + auto it = container.find(value); + if (it == container.end()) + it = container.emplace(value, container.size()).first; + + return it->second; + }; + + basicSprites.Sort([&](const SpriteChain& vertices) + { + // RQ index: + // - Layer (16bits) + // - Pipeline (8bits) + // - Material (8bits) + // - Shader? (8bits) + // - Textures (8bits) + // - Overlay (8bits) + // - Scissor (4bits) + // - ??? (4bits) + + UInt64 layerIndex = m_layerCache[vertices.layerIndex]; + UInt64 pipelineIndex = GetOrInsert(m_pipelineCache, vertices.material->GetPipeline()); + UInt64 materialIndex = GetOrInsert(m_materialCache, vertices.material); + UInt64 shaderIndex = GetOrInsert(m_shaderCache, vertices.material->GetShader()); + UInt64 textureIndex = 0;/* GetOrInsert(m_textureCache, vertices.material->GetDiffuseMap());*/ + UInt64 overlayIndex = GetOrInsert(m_overlayCache, vertices.overlay); + UInt64 scissorIndex = 0; //< TODO + + UInt64 index = (layerIndex & 0xFFFF) << 48 | + (pipelineIndex & 0xFF) << 40 | + (materialIndex & 0xFF) << 32 | + (shaderIndex & 0xFF) << 24 | + (textureIndex & 0xFF) << 16 | + (overlayIndex & 0xFF) << 8 | + (scissorIndex & 0x0F) << 4; + + return index; + }); + + billboards.Sort([&](const BillboardChain& billboard) + { + // RQ index: + // - Layer (16bits) + // - Pipeline (8bits) + // - Material (8bits) + // - Shader? (8bits) + // - Textures (8bits) + // - Scissor (4bits) + // - ??? (12bits) + + UInt64 layerIndex = m_layerCache[billboard.layerIndex]; + UInt64 pipelineIndex = GetOrInsert(m_pipelineCache, billboard.material->GetPipeline()); + UInt64 materialIndex = GetOrInsert(m_materialCache, billboard.material); + UInt64 shaderIndex = GetOrInsert(m_shaderCache, billboard.material->GetShader()); + UInt64 textureIndex = 0; /*GetOrInsert(m_textureCache, billboard.material->GetDiffuseMap())*/; + UInt64 unknownIndex = 0; //< ??? + UInt64 scissorIndex = 0; //< TODO + + UInt64 index = (layerIndex & 0xFFFF) << 48 | + (pipelineIndex & 0xFF) << 40 | + (materialIndex & 0xFF) << 32 | + (shaderIndex & 0xFF) << 24 | + (textureIndex & 0xFF) << 16 | + (scissorIndex & 0x0F) << 12 | + (unknownIndex & 0xFF) << 0; + + return index; + }); + + customDrawables.Sort([&](const CustomDrawable& drawable) + { + // RQ index: + // - Layer (16bits) + + UInt64 layerIndex = m_layerCache[drawable.layerIndex]; + + UInt64 index = (layerIndex & 0xFFFF) << 48; + + return index; + + }); + + models.Sort([&](const Model& renderData) + { + // RQ index: + // - Layer (16bits) + // - Pipeline (8bits) + // - Material (8bits) + // - Shader? (8bits) + // - Textures (8bits) + // - Buffers (8bits) + // - Scissor (4bits) + // - ??? (4bits) + + UInt64 layerIndex = m_layerCache[renderData.layerIndex]; + UInt64 pipelineIndex = GetOrInsert(m_pipelineCache, renderData.material->GetPipeline()); + UInt64 materialIndex = GetOrInsert(m_materialCache, renderData.material); + UInt64 shaderIndex = GetOrInsert(m_shaderCache, renderData.material->GetShader()); + UInt64 textureIndex = 0;/* GetOrInsert(m_textureCache, renderData.material->GetDiffuseMap()) */; + UInt64 bufferIndex = GetOrInsert(m_vertexBufferCache, renderData.meshData.vertexBuffer); + UInt64 scissorIndex = 0; //< TODO + UInt64 depthIndex = 0; //< TODO + + UInt64 index = (layerIndex & 0xFFFF) << 48 | + (pipelineIndex & 0xFF) << 40 | + (materialIndex & 0xFF) << 32 | + (shaderIndex & 0xFF) << 24 | + (textureIndex & 0xFF) << 16 | + (bufferIndex & 0xFF) << 8 | + (scissorIndex & 0x0F) << 4; + + return index; + }); + + static_assert(std::numeric_limits::is_iec559, "The following sorting functions relies on IEEE 754 floatings-points"); + +#if defined(arm) && \ + ((defined(__MAVERICK__) && defined(NAZARA_BIG_ENDIAN)) || \ + (!defined(__SOFTFP__) && !defined(__VFP_FP__) && !defined(__MAVERICK__))) + #error The following code relies on native-endian IEEE-754 representation, which your platform does not guarantee +#endif + + Planef nearPlane = viewer->GetFrustum().GetPlane(FrustumPlane_Near); + + depthSortedBillboards.Sort([&](const Billboard& billboard) + { + // RQ index: + // - Layer (16bits) + // - Depth (32bits) + // - ?? (16bits) + + // Reinterpret depth as UInt32 (this will work as long as they're all either positive or negative, + // a negative distance may happen with billboard behind the camera which we don't care about since they'll not be rendered) + float depth = nearPlane.Distance(billboard.data.center); + + UInt64 layerIndex = m_layerCache[billboard.layerIndex]; + UInt64 depthIndex = ~reinterpret_cast(depth); + + UInt64 index = (layerIndex & 0xFFFF) << 48 | + (depthIndex & 0xFFFFFFFF) << 16; + + return index; + }); + + if (viewer->GetProjectionType() == ProjectionType_Orthogonal) + { + depthSortedModels.Sort([&](const Model& model) + { + // RQ index: + // - Layer (16bits) + // - Depth (32bits) + // - ?? (16bits) + + float depth = nearPlane.Distance(model.obbSphere.GetPosition()); + + UInt64 layerIndex = m_layerCache[model.layerIndex]; + UInt64 depthIndex = ~reinterpret_cast(depth); + + UInt64 index = (layerIndex & 0xFFFF) << 48 | + (depthIndex & 0xFFFFFFFF) << 16; + + return index; + }); + + depthSortedSprites.Sort([&](const SpriteChain& spriteChain) + { + // RQ index: + // - Layer (16bits) + // - Depth (32bits) + // - ?? (16bits) + + float depth = nearPlane.Distance(spriteChain.vertices[0].position); + + UInt64 layerIndex = m_layerCache[spriteChain.layerIndex]; + UInt64 depthIndex = ~reinterpret_cast(depth); + + UInt64 index = (layerIndex & 0xFFFF) << 48 | + (depthIndex & 0xFFFFFFFF) << 16; + + return index; + }); + } + else + { + Vector3f viewerPos = viewer->GetEyePosition(); + + depthSortedModels.Sort([&](const Model& model) + { + // RQ index: + // - Layer (16bits) + // - Depth (32bits) + // - ?? (16bits) + + float depth = viewerPos.SquaredDistance(model.obbSphere.GetPosition()); + + UInt64 layerIndex = m_layerCache[model.layerIndex]; + UInt64 depthIndex = ~reinterpret_cast(depth); + + UInt64 index = (layerIndex & 0x0F) << 48 | + (depthIndex & 0xFFFFFFFF) << 16; + + return index; + }); + + depthSortedSprites.Sort([&](const SpriteChain& sprites) + { + // RQ index: + // - Layer (16bits) + // - Depth (32bits) + // - ?? (16bits) + + float depth = viewerPos.SquaredDistance(sprites.vertices[0].position); + + UInt64 layerIndex = m_layerCache[sprites.layerIndex]; + UInt64 depthIndex = ~reinterpret_cast(depth); + + UInt64 index = (layerIndex & 0xFFFF) << 48 | + (depthIndex & 0xFFFFFFFF) << 16; + + return index; + }); + } + } +} diff --git a/src/Nazara/Graphics/Graphics.cpp b/src/Nazara/Graphics/Graphics.cpp index b2ac1d928..0ae24e7c6 100644 --- a/src/Nazara/Graphics/Graphics.cpp +++ b/src/Nazara/Graphics/Graphics.cpp @@ -3,6 +3,7 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include #include namespace Nz @@ -13,13 +14,29 @@ namespace Nz * \brief Audio class that represents the module initializer of Graphics */ - Graphics::Graphics(Config /*config*/) : + Graphics::Graphics(Config config) : ModuleBase("Graphics", this) { - } + Renderer* renderer = Renderer::Instance(); + RendererImpl* rendererImpl = renderer->GetRendererImpl(); //< FIXME + std::vector renderDeviceInfo = rendererImpl->QueryRenderDevices(); + if (renderDeviceInfo.empty()) + throw std::runtime_error("no render device available"); - Graphics::~Graphics() - { + std::size_t bestRenderDeviceIndex = 0; + for (std::size_t i = 0; i < renderDeviceInfo.size(); ++i) + { + const auto& deviceInfo = renderDeviceInfo[i]; + if (config.useDedicatedRenderDevice && deviceInfo.type == RenderDeviceType::Dedicated) + { + bestRenderDeviceIndex = i; + break; + } + } + + m_renderDevice = rendererImpl->InstanciateRenderDevice(bestRenderDeviceIndex); + if (!m_renderDevice) + throw std::runtime_error("failed to instantiate render device"); } Graphics* Graphics::s_instance = nullptr; diff --git a/src/Nazara/Graphics/Material.cpp b/src/Nazara/Graphics/Material.cpp new file mode 100644 index 000000000..8b8fafaf5 --- /dev/null +++ b/src/Nazara/Graphics/Material.cpp @@ -0,0 +1,487 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + /*! + * \ingroup graphics + * \class Nz::Material + * \brief Graphics class that represents a material + */ + + /*! + * \brief Checks whether the parameters for the material are correct + * \return true If parameters are valid + */ + bool MaterialParams::IsValid() const + { + if (!UberShaderLibrary::Has(shaderName)) + return false; + + return true; + } + + /*! + * \brief Constructs a Material object with default states + * + * \see Reset + */ + Material::Material(std::shared_ptr settings) : + m_settings(std::move(settings)), + m_reflectionMode(ReflectionMode_Skybox), + m_pipelineUpdated(false), + m_shadowCastingEnabled(true), + m_reflectionSize(256) + { + m_pipelineInfo.settings = m_settings; + SetShader("Basic"); + + m_textures.resize(m_settings->GetTextures().size()); + + m_uniformBuffers.reserve(m_settings->GetUniformBlocks().size()); + for (const auto& uniformBufferInfo : m_settings->GetUniformBlocks()) + { + //TODO: Use pools + UniformBufferRef ubo = UniformBuffer::New(static_cast(uniformBufferInfo.blockSize), DataStorage_Hardware, BufferUsage_Dynamic); + ubo->Fill(uniformBufferInfo.defaultValues.data(), 0, uniformBufferInfo.defaultValues.size()); + + m_uniformBuffers.emplace_back(std::move(ubo)); + } + } + + /*! + * \brief Applies shader to the material + * + * \param instance Pipeline instance to update + * \param textureUnit Unit for the texture GL_TEXTURE"i" + * \param lastUsedUnit Optional argument to get the last texture unit + */ + void Material::Apply(const MaterialPipeline::Instance& instance) const + { + const Shader* shader = instance.renderPipeline.GetInfo().shader; + + unsigned int bindingIndex = 0; + + for (const MaterialTexture& textureData : m_textures) + { + auto it = instance.bindings.find(bindingIndex++); + assert(it != instance.bindings.end()); + + unsigned int textureIndex = it->second; + + Renderer::SetTexture(textureIndex, textureData.texture); + Renderer::SetTextureSampler(textureIndex, textureData.sampler); + } + + for (const UniformBufferRef& ubo : m_uniformBuffers) + { + auto it = instance.bindings.find(bindingIndex++); + assert(it != instance.bindings.end()); + + unsigned int uniformBufferIndex = it->second; + + Renderer::SetUniformBuffer(uniformBufferIndex, ubo); + } + + /*if (instance.uniforms[MaterialUniform_AlphaThreshold] != -1) + shader->SendFloat(instance.uniforms[MaterialUniform_AlphaThreshold], m_alphaThreshold); + + if (instance.uniforms[MaterialUniform_Ambient] != -1) + shader->SendColor(instance.uniforms[MaterialUniform_Ambient], m_ambientColor); + + if (instance.uniforms[MaterialUniform_Diffuse] != -1) + shader->SendColor(instance.uniforms[MaterialUniform_Diffuse], m_diffuseColor); + + if (instance.uniforms[MaterialUniform_Shininess] != -1) + shader->SendFloat(instance.uniforms[MaterialUniform_Shininess], m_shininess); + + if (instance.uniforms[MaterialUniform_Specular] != -1) + shader->SendColor(instance.uniforms[MaterialUniform_Specular], m_specularColor);*/ + + /*if (m_alphaMap && instance.uniforms[MaterialUniform_AlphaMap] != -1) + { + unsigned int textureUnit = s_textureUnits[TextureMap_Alpha]; + + Renderer::SetTexture(textureUnit, m_alphaMap); + Renderer::SetTextureSampler(textureUnit, m_diffuseSampler); + } + + if (m_diffuseMap && instance.uniforms[MaterialUniform_DiffuseMap] != -1) + { + unsigned int textureUnit = s_textureUnits[TextureMap_Diffuse]; + + Renderer::SetTexture(textureUnit, m_diffuseMap); + Renderer::SetTextureSampler(textureUnit, m_diffuseSampler); + } + + if (m_emissiveMap && instance.uniforms[MaterialUniform_EmissiveMap] != -1) + { + unsigned int textureUnit = s_textureUnits[TextureMap_Emissive]; + + Renderer::SetTexture(textureUnit, m_emissiveMap); + Renderer::SetTextureSampler(textureUnit, m_diffuseSampler); + } + + if (m_heightMap && instance.uniforms[MaterialUniform_HeightMap] != -1) + { + unsigned int textureUnit = s_textureUnits[TextureMap_Height]; + + Renderer::SetTexture(textureUnit, m_heightMap); + Renderer::SetTextureSampler(textureUnit, m_diffuseSampler); + } + + if (m_normalMap && instance.uniforms[MaterialUniform_NormalMap] != -1) + { + unsigned int textureUnit = s_textureUnits[TextureMap_Normal]; + + Renderer::SetTexture(textureUnit, m_normalMap); + Renderer::SetTextureSampler(textureUnit, m_diffuseSampler); + } + + if (m_specularMap && instance.uniforms[MaterialUniform_SpecularMap] != -1) + { + unsigned int textureUnit = s_textureUnits[TextureMap_Specular]; + + Renderer::SetTexture(textureUnit, m_specularMap); + Renderer::SetTextureSampler(textureUnit, m_specularSampler); + }*/ + } + + /*! + * \brief Builds the material from a parameter list + * + * \param matData Data information for the material + * \param matParams Additional parameters for the material + */ + void Material::BuildFromParameters(const ParameterList& matData, const MaterialParams& matParams) + { + Color color; + bool isEnabled; + double dValue; + long long iValue; + String path; + + ErrorFlags errFlags(ErrorFlag_Silent | ErrorFlag_ThrowExceptionDisabled, true); + + /*if (matData.GetDoubleParameter(MaterialData::AlphaThreshold, &dValue)) + SetAlphaThreshold(float(dValue));*/ + + if (matData.GetBooleanParameter(MaterialData::AlphaTest, &isEnabled)) + EnableAlphaTest(isEnabled); + + /*if (matData.GetColorParameter(MaterialData::AmbientColor, &color)) + SetAmbientColor(color);*/ + + if (matData.GetIntegerParameter(MaterialData::CullingSide, &iValue)) + SetFaceCulling(static_cast(iValue)); + + if (matData.GetIntegerParameter(MaterialData::DepthFunc, &iValue)) + SetDepthFunc(static_cast(iValue)); + + if (matData.GetBooleanParameter(MaterialData::DepthSorting, &isEnabled)) + EnableDepthSorting(isEnabled); + + /*if (matData.GetColorParameter(MaterialData::DiffuseColor, &color)) + SetDiffuseColor(color);*/ + + if (matData.GetIntegerParameter(MaterialData::DstBlend, &iValue)) + SetDstBlend(static_cast(iValue)); + + if (matData.GetIntegerParameter(MaterialData::FaceFilling, &iValue)) + SetFaceFilling(static_cast(iValue)); + + if (matData.GetDoubleParameter(MaterialData::LineWidth, &dValue)) + SetLineWidth(float(dValue)); + + if (matData.GetDoubleParameter(MaterialData::PointSize, &dValue)) + SetPointSize(float(dValue)); + + /*if (matData.GetColorParameter(MaterialData::SpecularColor, &color)) + SetSpecularColor(color); + + if (matData.GetDoubleParameter(MaterialData::Shininess, &dValue)) + SetShininess(float(dValue));*/ + + if (matData.GetIntegerParameter(MaterialData::SrcBlend, &iValue)) + SetSrcBlend(static_cast(iValue)); + + // RendererParameter + if (matData.GetBooleanParameter(MaterialData::Blending, &isEnabled)) + EnableBlending(isEnabled); + + if (matData.GetBooleanParameter(MaterialData::ColorWrite, &isEnabled)) + EnableColorWrite(isEnabled); + + if (matData.GetBooleanParameter(MaterialData::DepthBuffer, &isEnabled)) + EnableDepthBuffer(isEnabled); + + if (matData.GetBooleanParameter(MaterialData::DepthWrite, &isEnabled)) + EnableDepthWrite(isEnabled); + + if (matData.GetBooleanParameter(MaterialData::FaceCulling, &isEnabled)) + EnableFaceCulling(isEnabled); + + if (matData.GetBooleanParameter(MaterialData::ScissorTest, &isEnabled)) + EnableScissorTest(isEnabled); + + if (matData.GetBooleanParameter(MaterialData::StencilTest, &isEnabled)) + EnableStencilTest(isEnabled); + + if (matData.GetBooleanParameter(MaterialData::VertexColor, &isEnabled)) + EnableVertexColor(isEnabled); + + // Samplers + /*if (matData.GetIntegerParameter(MaterialData::DiffuseAnisotropyLevel, &iValue)) + m_diffuseSampler.SetAnisotropyLevel(static_cast(iValue)); + + if (matData.GetIntegerParameter(MaterialData::DiffuseFilter, &iValue)) + m_diffuseSampler.SetFilterMode(static_cast(iValue)); + + if (matData.GetIntegerParameter(MaterialData::DiffuseWrap, &iValue)) + m_diffuseSampler.SetWrapMode(static_cast(iValue)); + + if (matData.GetIntegerParameter(MaterialData::SpecularAnisotropyLevel, &iValue)) + m_specularSampler.SetAnisotropyLevel(static_cast(iValue)); + + if (matData.GetIntegerParameter(MaterialData::SpecularFilter, &iValue)) + m_specularSampler.SetFilterMode(static_cast(iValue)); + + if (matData.GetIntegerParameter(MaterialData::SpecularWrap, &iValue)) + m_specularSampler.SetWrapMode(static_cast(iValue));*/ + + // Stencil + if (matData.GetIntegerParameter(MaterialData::StencilCompare, &iValue)) + m_pipelineInfo.stencilCompare.front = static_cast(iValue); + + if (matData.GetIntegerParameter(MaterialData::StencilFail, &iValue)) + m_pipelineInfo.stencilFail.front = static_cast(iValue); + + if (matData.GetIntegerParameter(MaterialData::StencilPass, &iValue)) + m_pipelineInfo.stencilPass.front = static_cast(iValue); + + if (matData.GetIntegerParameter(MaterialData::StencilZFail, &iValue)) + m_pipelineInfo.stencilDepthFail.front = static_cast(iValue); + + if (matData.GetIntegerParameter(MaterialData::StencilMask, &iValue)) + m_pipelineInfo.stencilWriteMask.front = static_cast(iValue); + + if (matData.GetIntegerParameter(MaterialData::StencilReference, &iValue)) + m_pipelineInfo.stencilReference.front = static_cast(iValue); + + // Stencil (back) + if (matData.GetIntegerParameter(MaterialData::BackFaceStencilCompare, &iValue)) + m_pipelineInfo.stencilCompare.back = static_cast(iValue); + + if (matData.GetIntegerParameter(MaterialData::BackFaceStencilFail, &iValue)) + m_pipelineInfo.stencilFail.back = static_cast(iValue); + + if (matData.GetIntegerParameter(MaterialData::BackFaceStencilPass, &iValue)) + m_pipelineInfo.stencilPass.back = static_cast(iValue); + + if (matData.GetIntegerParameter(MaterialData::BackFaceStencilZFail, &iValue)) + m_pipelineInfo.stencilDepthFail.back = static_cast(iValue); + + if (matData.GetIntegerParameter(MaterialData::BackFaceStencilMask, &iValue)) + m_pipelineInfo.stencilWriteMask.back = static_cast(iValue); + + if (matData.GetIntegerParameter(MaterialData::BackFaceStencilReference, &iValue)) + m_pipelineInfo.stencilReference.back = static_cast(iValue); + + InvalidatePipeline(); + + // Textures + /*if (matParams.loadAlphaMap && matData.GetStringParameter(MaterialData::AlphaTexturePath, &path)) + SetAlphaMap(path); + + if (matParams.loadDiffuseMap && matData.GetStringParameter(MaterialData::DiffuseTexturePath, &path)) + SetDiffuseMap(path); + + if (matParams.loadEmissiveMap && matData.GetStringParameter(MaterialData::EmissiveTexturePath, &path)) + SetEmissiveMap(path); + + if (matParams.loadHeightMap && matData.GetStringParameter(MaterialData::HeightTexturePath, &path)) + SetHeightMap(path); + + if (matParams.loadNormalMap && matData.GetStringParameter(MaterialData::NormalTexturePath, &path)) + SetNormalMap(path); + + if (matParams.loadSpecularMap && matData.GetStringParameter(MaterialData::SpecularTexturePath, &path)) + SetSpecularMap(path);*/ + + SetShader(matParams.shaderName); + } + + /*! + * \brief Builds a ParameterList with material data + * + * \param matData Destination parameter list which will receive material data + */ + void Material::SaveToParameters(ParameterList* matData) + { + NazaraAssert(matData, "Invalid ParameterList"); + + matData->SetParameter(MaterialData::AlphaTest, IsAlphaTestEnabled()); + //matData->SetParameter(MaterialData::AlphaThreshold, GetAlphaThreshold()); + //matData->SetParameter(MaterialData::AmbientColor, GetAmbientColor()); + matData->SetParameter(MaterialData::CullingSide, static_cast(GetFaceCulling())); + matData->SetParameter(MaterialData::DepthFunc, static_cast(GetDepthFunc())); + matData->SetParameter(MaterialData::DepthSorting, IsDepthSortingEnabled()); + //matData->SetParameter(MaterialData::DiffuseColor, GetDiffuseColor()); + matData->SetParameter(MaterialData::DstBlend, static_cast(GetDstBlend())); + matData->SetParameter(MaterialData::FaceFilling, static_cast(GetFaceFilling())); + matData->SetParameter(MaterialData::LineWidth, GetLineWidth()); + matData->SetParameter(MaterialData::PointSize, GetPointSize()); + //matData->SetParameter(MaterialData::Shininess, GetShininess()); + //matData->SetParameter(MaterialData::SpecularColor, GetSpecularColor()); + matData->SetParameter(MaterialData::SrcBlend, static_cast(GetSrcBlend())); + + // RendererParameter + matData->SetParameter(MaterialData::Blending, IsBlendingEnabled()); + matData->SetParameter(MaterialData::ColorWrite, IsColorWriteEnabled()); + matData->SetParameter(MaterialData::DepthBuffer, IsDepthBufferEnabled()); + matData->SetParameter(MaterialData::DepthWrite, IsDepthWriteEnabled()); + matData->SetParameter(MaterialData::FaceCulling, IsFaceCullingEnabled()); + matData->SetParameter(MaterialData::ScissorTest, IsScissorTestEnabled()); + matData->SetParameter(MaterialData::StencilTest, IsStencilTestEnabled()); + matData->SetParameter(MaterialData::VertexColor, HasVertexColor()); + + // Samplers + /*matData->SetParameter(MaterialData::DiffuseAnisotropyLevel, static_cast(GetDiffuseSampler().GetAnisotropicLevel())); + matData->SetParameter(MaterialData::DiffuseFilter, static_cast(GetDiffuseSampler().GetFilterMode())); + matData->SetParameter(MaterialData::DiffuseWrap, static_cast(GetDiffuseSampler().GetWrapMode())); + + matData->SetParameter(MaterialData::SpecularAnisotropyLevel, static_cast(GetSpecularSampler().GetAnisotropicLevel())); + matData->SetParameter(MaterialData::SpecularFilter, static_cast(GetSpecularSampler().GetFilterMode())); + matData->SetParameter(MaterialData::SpecularWrap, static_cast(GetSpecularSampler().GetWrapMode()));*/ + + // Stencil + matData->SetParameter(MaterialData::StencilCompare, static_cast(GetPipelineInfo().stencilCompare.front)); + matData->SetParameter(MaterialData::StencilFail, static_cast(GetPipelineInfo().stencilFail.front)); + matData->SetParameter(MaterialData::StencilPass, static_cast(GetPipelineInfo().stencilPass.front)); + matData->SetParameter(MaterialData::StencilZFail, static_cast(GetPipelineInfo().stencilDepthFail.front)); + matData->SetParameter(MaterialData::StencilMask, static_cast(GetPipelineInfo().stencilWriteMask.front)); + matData->SetParameter(MaterialData::StencilReference, static_cast(GetPipelineInfo().stencilReference.front)); + + // Stencil (back) + matData->SetParameter(MaterialData::BackFaceStencilCompare, static_cast(GetPipelineInfo().stencilCompare.back)); + matData->SetParameter(MaterialData::BackFaceStencilFail, static_cast(GetPipelineInfo().stencilFail.back)); + matData->SetParameter(MaterialData::BackFaceStencilPass, static_cast(GetPipelineInfo().stencilPass.back)); + matData->SetParameter(MaterialData::BackFaceStencilZFail, static_cast(GetPipelineInfo().stencilDepthFail.back)); + matData->SetParameter(MaterialData::BackFaceStencilMask, static_cast(GetPipelineInfo().stencilWriteMask.back)); + matData->SetParameter(MaterialData::BackFaceStencilReference, static_cast(GetPipelineInfo().stencilReference.back)); + + // Textures + /*if (HasAlphaMap()) + { + const String& path = GetAlphaMap()->GetFilePath(); + if (!path.IsEmpty()) + matData->SetParameter(MaterialData::AlphaTexturePath, path); + } + + if (HasDiffuseMap()) + { + const String& path = GetDiffuseMap()->GetFilePath(); + if (!path.IsEmpty()) + matData->SetParameter(MaterialData::DiffuseTexturePath, path); + } + + if (HasEmissiveMap()) + { + const String& path = GetEmissiveMap()->GetFilePath(); + if (!path.IsEmpty()) + matData->SetParameter(MaterialData::EmissiveTexturePath, path); + } + + if (HasHeightMap()) + { + const String& path = GetHeightMap()->GetFilePath(); + if (!path.IsEmpty()) + matData->SetParameter(MaterialData::HeightTexturePath, path); + } + + if (HasNormalMap()) + { + const String& path = GetNormalMap()->GetFilePath(); + if (!path.IsEmpty()) + matData->SetParameter(MaterialData::NormalTexturePath, path); + } + + if (HasSpecularMap()) + { + const String& path = GetSpecularMap()->GetFilePath(); + if (!path.IsEmpty()) + matData->SetParameter(MaterialData::SpecularTexturePath, path); + }*/ + } + + /*! + * \brief Initializes the material librairies + * \return true If successful + * + * \remark Produces a NazaraError if the material library failed to be initialized + */ + bool Material::Initialize() + { + if (!MaterialLibrary::Initialize()) + { + NazaraError("Failed to initialise library"); + return false; + } + + if (!MaterialManager::Initialize()) + { + NazaraError("Failed to initialise manager"); + return false; + } + + s_defaultMaterial = New(BasicMaterial::GetSettings()); + s_defaultMaterial->EnableFaceCulling(false); + s_defaultMaterial->SetFaceFilling(FaceFilling_Line); + MaterialLibrary::Register("Default", s_defaultMaterial); + + unsigned int textureUnit = 0; + + s_textureUnits[TextureMap_Diffuse] = textureUnit++; + s_textureUnits[TextureMap_Alpha] = textureUnit++; + s_textureUnits[TextureMap_Specular] = textureUnit++; + s_textureUnits[TextureMap_Normal] = textureUnit++; + s_textureUnits[TextureMap_Emissive] = textureUnit++; + s_textureUnits[TextureMap_Overlay] = textureUnit++; + s_textureUnits[TextureMap_ReflectionCube] = textureUnit++; + s_textureUnits[TextureMap_Height] = textureUnit++; + s_textureUnits[TextureMap_Shadow2D_1] = textureUnit++; + s_textureUnits[TextureMap_ShadowCube_1] = textureUnit++; + s_textureUnits[TextureMap_Shadow2D_2] = textureUnit++; + s_textureUnits[TextureMap_ShadowCube_2] = textureUnit++; + s_textureUnits[TextureMap_Shadow2D_3] = textureUnit++; + s_textureUnits[TextureMap_ShadowCube_3] = textureUnit++; + + return true; + } + + /*! + * \brief Uninitializes the material librairies + */ + void Material::Uninitialize() + { + s_defaultMaterial.Reset(); + + MaterialManager::Uninitialize(); + MaterialLibrary::Uninitialize(); + } + + std::array Material::s_textureUnits; + MaterialLibrary::LibraryMap Material::s_library; + MaterialLoader::LoaderList Material::s_loaders; + MaterialManager::ManagerMap Material::s_managerMap; + MaterialManager::ManagerParams Material::s_managerParameters; + MaterialRef Material::s_defaultMaterial = nullptr; +} diff --git a/src/Nazara/Graphics/MaterialPipeline.cpp b/src/Nazara/Graphics/MaterialPipeline.cpp new file mode 100644 index 000000000..7cd4b808c --- /dev/null +++ b/src/Nazara/Graphics/MaterialPipeline.cpp @@ -0,0 +1,235 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + namespace + { + const UInt8 r_basicFragmentShader[] = { + #include + }; + + const UInt8 r_basicVertexShader[] = { + #include + }; + + const UInt8 r_phongLightingFragmentShader[] = { + #include + }; + + const UInt8 r_phongLightingVertexShader[] = { + #include + }; + + void OverrideShader(const String& path, String* source) + { + ErrorFlags errFlags(ErrorFlag_Silent | ErrorFlag_ThrowExceptionDisabled); + + File shaderFile(path, Nz::OpenMode_ReadOnly | Nz::OpenMode_Text); + if (shaderFile.IsOpen()) + { + StringStream shaderSource; + + while (!shaderFile.EndOfFile()) + { + shaderSource << shaderFile.ReadLine(); + shaderSource << '\n'; + } + + *source = shaderSource; + + NazaraNotice(path + " will be used to override built-in shader"); + } + } + } + + /*! + * \ingroup graphics + * \class Nz::MaterialPipeline + * + * \brief Graphics class used to contains all rendering states that are not allowed to change individually on rendering devices + */ + + /*! + * \brief Returns a reference to a MaterialPipeline built with MaterialPipelineInfo + * + * This function is using a cache, calling it multiples times with the same MaterialPipelineInfo will returns references to a single MaterialPipeline + * + * \param pipelineInfo Pipeline informations used to build/retrieve a MaterialPipeline object + */ + MaterialPipelineRef MaterialPipeline::GetPipeline(const MaterialPipelineInfo& pipelineInfo) + { + auto it = s_pipelineCache.find(pipelineInfo); + if (it == s_pipelineCache.end()) + it = s_pipelineCache.insert(it, PipelineCache::value_type(pipelineInfo, New(pipelineInfo))); + + return it->second; + } + + void MaterialPipeline::GenerateRenderPipeline(UInt32 flags) const + { + NazaraAssert(m_pipelineInfo.settings, "Material pipeline has no settings"); + NazaraAssert(m_pipelineInfo.uberShader, "Material pipeline has no uber shader"); + + const auto& textures = m_pipelineInfo.settings->GetTextures(); + + ParameterList list; + for (std::size_t i = 0, texCount = textures.size(); i < texCount; ++i) + { + const auto& texture = textures[i]; + String parameterName = "HAS_" + texture.name.ToUpper() + "_TEXTURE"; + + list.SetParameter(parameterName, (m_pipelineInfo.textures & (1 << i)) != 0); + } + + list.SetParameter("ALPHA_TEST", m_pipelineInfo.alphaTest); + list.SetParameter("REFLECTION_MAPPING", m_pipelineInfo.reflectionMapping); + list.SetParameter("SHADOW_MAPPING", m_pipelineInfo.shadowReceive); + list.SetParameter("TEXTURE_MAPPING", m_pipelineInfo.textures != 0 || + m_pipelineInfo.reflectionMapping || flags & ShaderFlags_TextureOverlay); + list.SetParameter("TRANSFORM", true); + + list.SetParameter("FLAG_BILLBOARD", static_cast((flags & ShaderFlags_Billboard) != 0)); + list.SetParameter("FLAG_DEFERRED", static_cast((flags & ShaderFlags_Deferred) != 0)); + list.SetParameter("FLAG_INSTANCING", static_cast((flags & ShaderFlags_Instancing) != 0)); + list.SetParameter("FLAG_TEXTUREOVERLAY", static_cast((flags & ShaderFlags_TextureOverlay) != 0)); + list.SetParameter("FLAG_VERTEXCOLOR", m_pipelineInfo.hasVertexColor || static_cast((flags & ShaderFlags_VertexColor) != 0)); + + Instance& instance = m_instances[flags]; + instance.uberInstance = m_pipelineInfo.uberShader->Get(list); + + RenderPipelineInfo renderPipelineInfo; + static_cast(renderPipelineInfo).operator=(m_pipelineInfo); // Not my proudest line + + renderPipelineInfo.shader = instance.uberInstance->GetShader(); + + instance.renderPipeline.Create(renderPipelineInfo); + + // Send texture units (those never changes) + const RenderPipelineLayout* pipelineLayout = m_pipelineInfo.pipelineLayout; + if (!pipelineLayout) + pipelineLayout = m_pipelineInfo.settings->GetRenderPipelineLayout(); + + instance.bindings = renderPipelineInfo.shader->ApplyLayout(pipelineLayout); + + renderPipelineInfo.shader->SendInteger(renderPipelineInfo.shader->GetUniformLocation("ReflectionMap"), Material::GetTextureUnit(TextureMap_ReflectionCube)); + + renderPipelineInfo.shader->SendInteger(renderPipelineInfo.shader->GetUniformLocation("DirectionalSpotLightShadowMap[0]"), Material::GetTextureUnit(TextureMap_Shadow2D_1)); + renderPipelineInfo.shader->SendInteger(renderPipelineInfo.shader->GetUniformLocation("DirectionalSpotLightShadowMap[1]"), Material::GetTextureUnit(TextureMap_Shadow2D_2)); + renderPipelineInfo.shader->SendInteger(renderPipelineInfo.shader->GetUniformLocation("DirectionalSpotLightShadowMap[2]"), Material::GetTextureUnit(TextureMap_Shadow2D_3)); + + renderPipelineInfo.shader->SendInteger(renderPipelineInfo.shader->GetUniformLocation("PointLightShadowMap[0]"), Material::GetTextureUnit(TextureMap_ShadowCube_1)); + renderPipelineInfo.shader->SendInteger(renderPipelineInfo.shader->GetUniformLocation("PointLightShadowMap[1]"), Material::GetTextureUnit(TextureMap_ShadowCube_2)); + renderPipelineInfo.shader->SendInteger(renderPipelineInfo.shader->GetUniformLocation("PointLightShadowMap[2]"), Material::GetTextureUnit(TextureMap_ShadowCube_3)); + } + + bool MaterialPipeline::Initialize() + { + // Basic shader + { + UberShaderPreprocessorRef uberShader = UberShaderPreprocessor::New(); + + String fragmentShader(reinterpret_cast(r_basicFragmentShader), sizeof(r_basicFragmentShader)); + String vertexShader(reinterpret_cast(r_basicVertexShader), sizeof(r_basicVertexShader)); + + #ifdef NAZARA_DEBUG + OverrideShader("Shaders/Basic/core.frag", &fragmentShader); + OverrideShader("Shaders/Basic/core.vert", &vertexShader); + #endif + + uberShader->SetShader(ShaderStageType_Fragment, fragmentShader, "FLAG_TEXTUREOVERLAY AUTO_TEXCOORDS HAS_ALPHA_TEXTURE HAS_DIFFUSE_TEXTURE TEXTURE_MAPPING"); + uberShader->SetShader(ShaderStageType_Vertex, vertexShader, "FLAG_BILLBOARD FLAG_INSTANCING FLAG_VERTEXCOLOR TEXTURE_MAPPING TRANSFORM UNIFORM_VERTEX_DEPTH"); + + UberShaderLibrary::Register("Basic", uberShader); + } + + if (!BasicMaterial::Initialize()) + { + NazaraError("Failed to initialize phong lighting materials"); + return false; + } + + // PhongLighting shader + { + UberShaderPreprocessorRef uberShader = UberShaderPreprocessor::New(); + + String fragmentShader(reinterpret_cast(r_phongLightingFragmentShader), sizeof(r_phongLightingFragmentShader)); + String vertexShader(reinterpret_cast(r_phongLightingVertexShader), sizeof(r_phongLightingVertexShader)); + + #ifdef NAZARA_DEBUG + OverrideShader("Shaders/PhongLighting/core.frag", &fragmentShader); + OverrideShader("Shaders/PhongLighting/core.vert", &vertexShader); + #endif + + uberShader->SetShader(ShaderStageType_Fragment, fragmentShader, "FLAG_DEFERRED FLAG_TEXTUREOVERLAY ALPHA_TEST AUTO_TEXCOORDS HAS_ALPHA_TEXTURE HAS_DIFFUSE_TEXTURE HAS_EMISSIVE_TEXTURE HAS_NORMAL_TEXTURE HAS_HEIGHT_TEXTURE HAS_SPECULAR_TEXTURE REFLECTION_MAPPING SHADOW_MAPPING"); + uberShader->SetShader(ShaderStageType_Vertex, vertexShader, "FLAG_BILLBOARD FLAG_DEFERRED FLAG_INSTANCING FLAG_VERTEXCOLOR HAS_NORMAL_TEXTURE SHADOW_MAPPING TEXTURE_MAPPING TRANSFORM UNIFORM_VERTEX_DEPTH"); + + UberShaderLibrary::Register("PhongLighting", uberShader); + } + + if (!PhongLightingMaterial::Initialize()) + { + NazaraError("Failed to initialize phong lighting materials"); + return false; + } + + // Once the base shaders are registered, we can now set some default materials + MaterialPipelineInfo pipelineInfo; + pipelineInfo.settings = BasicMaterial::GetSettings(); + pipelineInfo.uberShader = UberShaderLibrary::Get("Basic"); + + // Basic 2D - No depth write/face culling with scissoring + pipelineInfo.depthWrite = false; + pipelineInfo.faceCulling = false; + pipelineInfo.scissorTest = true; + + MaterialPipelineLibrary::Register("Basic2D", GetPipeline(pipelineInfo)); + + // Translucent 2D - Alpha blending with no depth write/face culling and scissoring + pipelineInfo.blending = true; + pipelineInfo.depthWrite = false; + pipelineInfo.faceCulling = false; + pipelineInfo.depthSorting = false; + pipelineInfo.scissorTest = true; + pipelineInfo.dstBlend = BlendFunc_InvSrcAlpha; + pipelineInfo.srcBlend = BlendFunc_SrcAlpha; + + MaterialPipelineLibrary::Register("Translucent2D", GetPipeline(pipelineInfo)); + + // Translucent 3D - Alpha blending with depth buffer and no depth write/face culling + pipelineInfo.blending = true; + pipelineInfo.depthBuffer = true; + pipelineInfo.depthWrite = false; + pipelineInfo.faceCulling = false; + pipelineInfo.depthSorting = true; + pipelineInfo.scissorTest = false; + pipelineInfo.dstBlend = BlendFunc_InvSrcAlpha; + pipelineInfo.srcBlend = BlendFunc_SrcAlpha; + + MaterialPipelineLibrary::Register("Translucent3D", GetPipeline(pipelineInfo)); + + return true; + } + + void MaterialPipeline::Uninitialize() + { + s_pipelineCache.clear(); + UberShaderLibrary::Unregister("PhongLighting"); + PhongLightingMaterial::Uninitialize(); + UberShaderLibrary::Unregister("Basic"); + BasicMaterial::Uninitialize(); + MaterialPipelineLibrary::Uninitialize(); + } + + MaterialPipelineLibrary::LibraryMap MaterialPipeline::s_library; + MaterialPipeline::PipelineCache MaterialPipeline::s_pipelineCache; +} diff --git a/src/Nazara/Graphics/PhongLightingMaterial.cpp b/src/Nazara/Graphics/PhongLightingMaterial.cpp new file mode 100644 index 000000000..53b5a61c1 --- /dev/null +++ b/src/Nazara/Graphics/PhongLightingMaterial.cpp @@ -0,0 +1,331 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + namespace + { + constexpr std::size_t AlphaMapBinding = 0; + constexpr std::size_t DiffuseMapBinding = 1; + constexpr std::size_t EmissiveMapBinding = 2; + constexpr std::size_t HeightMapBinding = 3; + constexpr std::size_t NormalMapBinding = 4; + constexpr std::size_t SpecularMapBinding = 5; + constexpr std::size_t TextureOverlayBinding = 6; + } + + PhongLightingMaterial::PhongLightingMaterial(Material* material) : + m_material(material) + { + NazaraAssert(material, "Invalid material"); + + // Most common case: don't fetch texture indexes as a little optimization + const std::shared_ptr& materialSettings = material->GetSettings(); + if (materialSettings == s_materialSettings) + { + m_textureIndexes = s_textureIndexes; + m_phongUniformIndex = s_phongUniformBlockIndex; + m_phongUniformOffsets = s_phongUniformOffsets; + } + else + { + m_textureIndexes.alpha = materialSettings->GetTextureIndex("Alpha"); + m_textureIndexes.diffuse = materialSettings->GetTextureIndex("Diffuse"); + m_textureIndexes.emissive = materialSettings->GetTextureIndex("Emissive"); + m_textureIndexes.height = materialSettings->GetTextureIndex("Height"); + m_textureIndexes.normal = materialSettings->GetTextureIndex("Normal"); + m_textureIndexes.specular = materialSettings->GetTextureIndex("Specular"); + + m_phongUniformIndex = materialSettings->GetUniformBlockIndex("PhongSettings"); + + m_phongUniformOffsets.alphaThreshold = materialSettings->GetUniformBlockVariableOffset(m_phongUniformIndex, "AlphaThreshold"); + m_phongUniformOffsets.ambientColor = materialSettings->GetUniformBlockVariableOffset(m_phongUniformIndex, "AmbientColor"); + m_phongUniformOffsets.diffuseColor = materialSettings->GetUniformBlockVariableOffset(m_phongUniformIndex, "DiffuseColor"); + m_phongUniformOffsets.shininess = materialSettings->GetUniformBlockVariableOffset(m_phongUniformIndex, "Shininess"); + m_phongUniformOffsets.specularColor = materialSettings->GetUniformBlockVariableOffset(m_phongUniformIndex, "SpecularColor"); + } + } + + float PhongLightingMaterial::GetAlphaThreshold() const + { + NazaraAssert(HasAlphaThreshold(), "Material has no alpha threshold uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_phongUniformIndex), BufferAccess_ReadOnly); + return *AccessByOffset(mapper.GetPointer(), m_phongUniformOffsets.alphaThreshold); + } + + Color PhongLightingMaterial::GetAmbientColor() const + { + NazaraAssert(HasAmbientColor(), "Material has no ambient color uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_phongUniformIndex), BufferAccess_ReadOnly); + + const float* colorPtr = AccessByOffset(mapper.GetPointer(), m_phongUniformOffsets.ambientColor); + return Color(colorPtr[0] * 255, colorPtr[1] * 255, colorPtr[2] * 255, colorPtr[3] * 255); //< TODO: Make color able to use float + } + + Color PhongLightingMaterial::GetDiffuseColor() const + { + NazaraAssert(HasDiffuseColor(), "Material has no diffuse color uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_phongUniformIndex), BufferAccess_ReadOnly); + + const float* colorPtr = AccessByOffset(mapper.GetPointer(), m_phongUniformOffsets.diffuseColor); + return Color(colorPtr[0] * 255, colorPtr[1] * 255, colorPtr[2] * 255, colorPtr[3] * 255); //< TODO: Make color able to use float + } + + float Nz::PhongLightingMaterial::GetShininess() const + { + NazaraAssert(HasShininess(), "Material has no shininess uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_phongUniformIndex), BufferAccess_ReadOnly); + return *AccessByOffset(mapper.GetPointer(), m_phongUniformOffsets.shininess); + } + + Color PhongLightingMaterial::GetSpecularColor() const + { + NazaraAssert(HasSpecularColor(), "Material has no specular color uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_phongUniformIndex), BufferAccess_ReadOnly); + + const float* colorPtr = AccessByOffset(mapper.GetPointer(), m_phongUniformOffsets.specularColor); + return Color(colorPtr[0] * 255, colorPtr[1] * 255, colorPtr[2] * 255, colorPtr[3] * 255); //< TODO: Make color able to use float + } + + void PhongLightingMaterial::SetAlphaThreshold(float alphaThreshold) + { + NazaraAssert(HasAlphaThreshold(), "Material has no alpha threshold uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_phongUniformIndex), BufferAccess_WriteOnly); + *AccessByOffset(mapper.GetPointer(), m_phongUniformOffsets.alphaThreshold) = alphaThreshold; + } + + void PhongLightingMaterial::SetAmbientColor(const Color& ambient) + { + NazaraAssert(HasAmbientColor(), "Material has no ambient color uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_phongUniformIndex), BufferAccess_WriteOnly); + float* colorPtr = AccessByOffset(mapper.GetPointer(), m_phongUniformOffsets.ambientColor); + colorPtr[0] = ambient.r / 255.f; + colorPtr[1] = ambient.g / 255.f; + colorPtr[2] = ambient.b / 255.f; + colorPtr[3] = ambient.a / 255.f; + } + + void PhongLightingMaterial::SetDiffuseColor(const Color& diffuse) + { + NazaraAssert(HasDiffuseColor(), "Material has no diffuse color uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_phongUniformIndex), BufferAccess_WriteOnly); + float* colorPtr = AccessByOffset(mapper.GetPointer(), m_phongUniformOffsets.diffuseColor); + colorPtr[0] = diffuse.r / 255.f; + colorPtr[1] = diffuse.g / 255.f; + colorPtr[2] = diffuse.b / 255.f; + colorPtr[3] = diffuse.a / 255.f; + } + + void PhongLightingMaterial::SetSpecularColor(const Color& diffuse) + { + NazaraAssert(HasSpecularColor(), "Material has no specular color uniform"); + + BufferMapper mapper(m_material->GetUniformBuffer(m_phongUniformIndex), BufferAccess_WriteOnly); + float* colorPtr = AccessByOffset(mapper.GetPointer(), m_phongUniformOffsets.specularColor); + colorPtr[0] = diffuse.r / 255.f; + colorPtr[1] = diffuse.g / 255.f; + colorPtr[2] = diffuse.b / 255.f; + colorPtr[3] = diffuse.a / 255.f; + } + + const std::shared_ptr& PhongLightingMaterial::GetSettings() + { + return s_materialSettings; + } + + bool PhongLightingMaterial::Initialize() + { + RenderPipelineLayoutInfo info; + info.bindings.assign({ + { + "MaterialAlphaMap", + ShaderBindingType_Texture, + ShaderStageType_Fragment, + AlphaMapBinding + }, + { + "MaterialDiffuseMap", + ShaderBindingType_Texture, + ShaderStageType_Fragment, + DiffuseMapBinding + }, + { + "MaterialEmissiveMap", + ShaderBindingType_Texture, + ShaderStageType_Fragment, + EmissiveMapBinding + }, + { + "MaterialHeightMap", + ShaderBindingType_Texture, + ShaderStageType_Fragment, + HeightMapBinding + }, + { + "MaterialNormalMap", + ShaderBindingType_Texture, + ShaderStageType_Fragment, + NormalMapBinding + }, + { + "MaterialSpecularMap", + ShaderBindingType_Texture, + ShaderStageType_Fragment, + SpecularMapBinding + } + }); + + s_renderPipelineLayout = RenderPipelineLayout::New(); + s_renderPipelineLayout->Create(info); + + std::vector uniformBlocks; + + // MaterialPhongSettings + FieldOffsets phongUniformStruct(StructLayout_Std140); + + s_phongUniformOffsets.alphaThreshold = phongUniformStruct.AddField(StructFieldType_Float1); + s_phongUniformOffsets.shininess = phongUniformStruct.AddField(StructFieldType_Float1); + s_phongUniformOffsets.ambientColor = phongUniformStruct.AddField(StructFieldType_Float4); + s_phongUniformOffsets.diffuseColor = phongUniformStruct.AddField(StructFieldType_Float4); + s_phongUniformOffsets.specularColor = phongUniformStruct.AddField(StructFieldType_Float4); + + MaterialSettings::PredefinedBinding predefinedBinding; + predefinedBinding.fill(MaterialSettings::InvalidIndex); + + std::vector phongVariables; + phongVariables.assign({ + { + "AlphaThreshold", + s_phongUniformOffsets.alphaThreshold + }, + { + "Shininess", + s_phongUniformOffsets.shininess + }, + { + "AmbientColor", + s_phongUniformOffsets.ambientColor + }, + { + "DiffuseColor", + s_phongUniformOffsets.diffuseColor + }, + { + "SpecularColor", + s_phongUniformOffsets.specularColor + } + }); + + static_assert(sizeof(Vector4f) == 4 * sizeof(float), "Vector4f is expected to be exactly 4 floats wide"); + + std::vector defaultValues(phongUniformStruct.GetSize()); + *AccessByOffset(defaultValues.data(), s_phongUniformOffsets.ambientColor) = Vector4f(0.5f, 0.5f, 0.5f, 1.f); + *AccessByOffset(defaultValues.data(), s_phongUniformOffsets.diffuseColor) = Vector4f(1.f, 1.f, 1.f, 1.f); + *AccessByOffset(defaultValues.data(), s_phongUniformOffsets.specularColor) = Vector4f(1.f, 1.f, 1.f, 1.f); + *AccessByOffset(defaultValues.data(), s_phongUniformOffsets.alphaThreshold) = 0.2f; + *AccessByOffset(defaultValues.data(), s_phongUniformOffsets.shininess) = 50.f; + + s_phongUniformBlockIndex = uniformBlocks.size(); + uniformBlocks.push_back({ + "PhongSettings", + phongUniformStruct.GetSize(), + "MaterialPhongSettings", + std::move(phongVariables), + std::move(defaultValues) + }); + + std::vector sharedUniformBlock; + predefinedBinding[PredefinedShaderBinding_UboInstanceData] = sharedUniformBlock.size(); + sharedUniformBlock.push_back(PredefinedInstanceData::GetUniformBlock()); + predefinedBinding[PredefinedShaderBinding_UboLighData] = sharedUniformBlock.size(); + sharedUniformBlock.push_back(PredefinedLightData::GetUniformBlock()); + predefinedBinding[PredefinedShaderBinding_UboViewerData] = sharedUniformBlock.size(); + sharedUniformBlock.push_back(PredefinedViewerData::GetUniformBlock()); + + std::vector textures; + s_textureIndexes.alpha = textures.size(); + textures.push_back({ + "Alpha", + ImageType_2D, + "MaterialAlphaMap" + }); + + s_textureIndexes.diffuse = textures.size(); + textures.push_back({ + "Diffuse", + ImageType_2D, + "MaterialDiffuseMap" + }); + + s_textureIndexes.emissive = textures.size(); + textures.push_back({ + "Emissive", + ImageType_2D, + "MaterialEmissiveMap" + }); + + s_textureIndexes.height = textures.size(); + textures.push_back({ + "Height", + ImageType_2D, + "MaterialHeightMap" + }); + + s_textureIndexes.normal = textures.size(); + textures.push_back({ + "Normal", + ImageType_2D, + "MaterialNormalMap" + }); + + s_textureIndexes.specular = textures.size(); + textures.push_back({ + "Specular", + ImageType_2D, + "MaterialSpecularMap" + }); + + predefinedBinding[PredefinedShaderBinding_TexOverlay] = textures.size(); + textures.push_back({ + "Overlay", + ImageType_2D, + "TextureOverlay" + }); + + s_materialSettings = std::make_shared(std::move(textures), std::move(uniformBlocks), std::move(sharedUniformBlock), predefinedBinding); + + return true; + } + + void PhongLightingMaterial::Uninitialize() + { + s_renderPipelineLayout.Reset(); + s_materialSettings.reset(); + } + + std::shared_ptr PhongLightingMaterial::s_materialSettings; + std::size_t PhongLightingMaterial::s_phongUniformBlockIndex; + RenderPipelineLayoutRef PhongLightingMaterial::s_renderPipelineLayout; + PhongLightingMaterial::TextureIndexes PhongLightingMaterial::s_textureIndexes; + PhongLightingMaterial::PhongUniformOffsets PhongLightingMaterial::s_phongUniformOffsets; +} diff --git a/src/Nazara/Graphics/PredefinedShaderStructs.cpp b/src/Nazara/Graphics/PredefinedShaderStructs.cpp new file mode 100644 index 000000000..feb8f590b --- /dev/null +++ b/src/Nazara/Graphics/PredefinedShaderStructs.cpp @@ -0,0 +1,167 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include + +namespace Nz +{ + PredefinedLightData PredefinedLightData::GetOffset() + { + PredefinedLightData lightData; + + FieldOffsets lightStruct(StructLayout_Std140); + lightData.innerOffsets.type = lightStruct.AddField(StructFieldType_Int1); + lightData.innerOffsets.color = lightStruct.AddField(StructFieldType_Float4); + lightData.innerOffsets.factor = lightStruct.AddField(StructFieldType_Float2); + lightData.innerOffsets.parameter1 = lightStruct.AddField(StructFieldType_Float4); + lightData.innerOffsets.parameter2 = lightStruct.AddField(StructFieldType_Float4); + lightData.innerOffsets.parameter3 = lightStruct.AddField(StructFieldType_Float2); + lightData.innerOffsets.shadowMappingFlag = lightStruct.AddField(StructFieldType_Bool1); + + lightData.innerOffsets.totalSize = lightStruct.GetSize(); + + FieldOffsets lightDataStruct(StructLayout_Std140); + for (std::size_t& lightOffset : lightData.lightArray) + lightOffset = lightDataStruct.AddStruct(lightStruct); + + lightData.lightArraySize = lightDataStruct.GetSize(); + + return lightData; + } + + MaterialSettings::SharedUniformBlock PredefinedLightData::GetUniformBlock() + { + PredefinedLightData lightData = GetOffset(); + + std::vector lightDataVariables; + for (std::size_t i = 0; i < lightData.lightArray.size(); ++i) + { + lightDataVariables.push_back({ + "LightData[" + std::to_string(i) + "]", + lightData.lightArray[i] + }); + } + + MaterialSettings::SharedUniformBlock uniformBlock = { + "Light", + "LightData", + std::move(lightDataVariables) + }; + + return uniformBlock; + } + + PredefinedInstanceData PredefinedInstanceData::GetOffset() + { + FieldOffsets viewerStruct(StructLayout_Std140); + + PredefinedInstanceData instanceData; + instanceData.worldMatrixOffset = viewerStruct.AddMatrix(StructFieldType_Float1, 4, 4, true); + instanceData.invWorldMatrixOffset = viewerStruct.AddMatrix(StructFieldType_Float1, 4, 4, true); + + instanceData.totalSize = viewerStruct.GetSize(); + + return instanceData; + } + + MaterialSettings::SharedUniformBlock PredefinedInstanceData::GetUniformBlock() + { + PredefinedInstanceData instanceData = GetOffset(); + + std::vector instanceDataVariables; + instanceDataVariables.assign({ + { + "WorldMatrix", + instanceData.worldMatrixOffset + }, + { + "InvWorldMatrix", + instanceData.invWorldMatrixOffset + }, + }); + + MaterialSettings::SharedUniformBlock uniformBlock = { + "Instance", + "InstanceData", + std::move(instanceDataVariables) + }; + + return uniformBlock; + } + + PredefinedViewerData PredefinedViewerData::GetOffset() + { + FieldOffsets viewerStruct(StructLayout_Std140); + + PredefinedViewerData viewerData; + viewerData.projMatrixOffset = viewerStruct.AddMatrix(StructFieldType_Float1, 4, 4, true); + viewerData.invProjMatrixOffset = viewerStruct.AddMatrix(StructFieldType_Float1, 4, 4, true); + viewerData.viewMatrixOffset = viewerStruct.AddMatrix(StructFieldType_Float1, 4, 4, true); + viewerData.invViewMatrixOffset = viewerStruct.AddMatrix(StructFieldType_Float1, 4, 4, true); + viewerData.viewProjMatrixOffset = viewerStruct.AddMatrix(StructFieldType_Float1, 4, 4, true); + viewerData.invViewProjMatrixOffset = viewerStruct.AddMatrix(StructFieldType_Float1, 4, 4, true); + viewerData.targetSizeOffset = viewerStruct.AddField(StructFieldType_Float2); + viewerData.invTargetSizeOffset = viewerStruct.AddField(StructFieldType_Float2); + viewerData.eyePositionOffset = viewerStruct.AddField(StructFieldType_Float3); + + viewerData.totalSize = viewerStruct.GetSize(); + + return viewerData; + } + + MaterialSettings::SharedUniformBlock PredefinedViewerData::GetUniformBlock() + { + PredefinedViewerData viewerData = GetOffset(); + + std::vector viewerDataVariables; + viewerDataVariables.assign({ + { + "ProjMatrix", + viewerData.projMatrixOffset + }, + { + "InvProjMatrix", + viewerData.invProjMatrixOffset + }, + { + "ViewMatrix", + viewerData.viewMatrixOffset + }, + { + "InvViewMatrix", + viewerData.invViewMatrixOffset + }, + { + "ViewProjMatrix", + viewerData.viewProjMatrixOffset + }, + { + "InvViewProjMatrix", + viewerData.invViewProjMatrixOffset + }, + { + "TargetSize", + viewerData.targetSizeOffset + }, + { + "InvTargetSize", + viewerData.invTargetSizeOffset + }, + { + "EyePosition", + viewerData.eyePositionOffset + } + }); + + MaterialSettings::SharedUniformBlock uniformBlock = { + "Viewer", + "ViewerData", + std::move(viewerDataVariables) + }; + + return uniformBlock; + } +}