diff --git a/SHADE_Application/SHADE_Application.vcxproj b/SHADE_Application/SHADE_Application.vcxproj index 3d9cfd1a..11bbb48f 100644 --- a/SHADE_Application/SHADE_Application.vcxproj +++ b/SHADE_Application/SHADE_Application.vcxproj @@ -60,7 +60,7 @@ SBpch.h Level4 _DEBUG;%(PreprocessorDefinitions) - .;..\SHADE_Engine\src;src;%(AdditionalIncludeDirectories) + ..\Dependencies\spdlog\include;..\SHADE_Engine\src;src;%(AdditionalIncludeDirectories) EditAndContinue Disabled false @@ -71,7 +71,7 @@ Windows true - WinMainCRTStartup + wWinMainCRTStartup @@ -80,7 +80,7 @@ SBpch.h Level4 _RELEASE;%(PreprocessorDefinitions) - .;..\SHADE_Engine\src;src;%(AdditionalIncludeDirectories) + ..\Dependencies\spdlog\include;..\SHADE_Engine\src;src;%(AdditionalIncludeDirectories) Full true true @@ -94,16 +94,20 @@ Windows true true - WinMainCRTStartup + wWinMainCRTStartup + + + Create + diff --git a/SHADE_Application/SHADE_Application.vcxproj.filters b/SHADE_Application/SHADE_Application.vcxproj.filters new file mode 100644 index 00000000..1234632d --- /dev/null +++ b/SHADE_Application/SHADE_Application.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {D9DE78AF-4594-F1A4-CE88-EB7B3A3DE8A8} + + + {86EEB3D0-7290-DEA6-5B4B-F2FA478C65F7} + + + + + Application + + + + Scenes + + + + + Application + + + + Scenes + + + + \ No newline at end of file diff --git a/SHADE_Application/premake5.lua b/SHADE_Application/premake5.lua index 60a87928..c10b1002 100644 --- a/SHADE_Application/premake5.lua +++ b/SHADE_Application/premake5.lua @@ -8,7 +8,7 @@ project "SHADE_Application" pchheader "SBpch.h" pchsource "%{prj.location}/src/SBpch.cpp" staticruntime "on" - entrypoint "WinMainCRTStartup" + entrypoint "wWinMainCRTStartup" system ("windows") files @@ -21,8 +21,7 @@ project "SHADE_Application" includedirs { - "%{IncludeDir.GLFW}", - "%{IncludeDir.GLAD}", + "%{IncludeDir.spdlog}/include", "../SHADE_Engine/src", "src" } diff --git a/SHADE_Application/src/Application/SBApplication.cpp b/SHADE_Application/src/Application/SBApplication.cpp new file mode 100644 index 00000000..184b9611 --- /dev/null +++ b/SHADE_Application/src/Application/SBApplication.cpp @@ -0,0 +1,52 @@ +#include "SBpch.h" +#include "SBApplication.h" + +#ifdef SHEDITOR +#include "Editor/SHEditor.h" +#include "Scenes/SBEditorScene.h" +#endif // SHEDITOR + +#include +#include +#include + +namespace Sandbox +{ + bool paused = false; + void SBApplication::Initialize + ( + _In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ INT nCmdShow + ) + { + + window.Create(hInstance, hPrevInstance, lpCmdLine, nCmdShow); + + #ifdef SHEDITOR + #else + #endif + + } + + void SBApplication::Update(void) + { + //TODO: Change true to window is open + while (!window.WindowShouldClose()) + { + #ifdef SHEDITOR + #else + #endif + } + } + + + void SBApplication::Exit(void) + { + #ifdef SHEDITOR + #else + #endif + } +} + diff --git a/SHADE_Application/src/Application/SBApplication.h b/SHADE_Application/src/Application/SBApplication.h new file mode 100644 index 00000000..a1bf11eb --- /dev/null +++ b/SHADE_Application/src/Application/SBApplication.h @@ -0,0 +1,27 @@ +#ifndef SB_APPLICATION_H +#define SB_APPLICATION_H +#include +//using namespace SHADE; + +namespace Sandbox +{ + class SBApplication + { + private: + SHADE::SHWindow window; + //SHAppConfig config; + public: + SBApplication() = default; + void Initialize(_In_ HINSTANCE /*hInstance*/, + _In_opt_ HINSTANCE /*hPrevInstance*/, + _In_ LPWSTR /*lpCmdLine*/, + _In_ INT /*nCmdShow*/); + void Update(void); + void Exit(void); + private: + //std::common_type_t collisionSystemTime, physicsSystemTime, transformSystemTime, audioSystemTime, renderSystemTime, gameTime; + //std::chrono::high_resolution_clock::time_point audioStart, audioEnd, transformStart, transformEnd, physicsStart, physicsEnd, collisionStart, collisionEnd, renderStart, renderEnd, gameStart, gameEnd; + }; +} + +#endif diff --git a/SHADE_Application/src/Scenes/SBTestScene.cpp b/SHADE_Application/src/Scenes/SBTestScene.cpp new file mode 100644 index 00000000..46b1bb84 --- /dev/null +++ b/SHADE_Application/src/Scenes/SBTestScene.cpp @@ -0,0 +1,48 @@ +#include "SBpch.h" +#include "SBTestScene.h" + +using namespace SHADE; + +namespace Sandbox +{ + + void SBTestScene::WindowFocusFunc([[maybe_unused]]void* window, int focused) + { + if(focused) + { + } + else + { + } + } + + void SBTestScene::Load() + { + } + void SBTestScene::Init() + { + + } + void SBTestScene::Update(float dt) + { + (void)dt; + + } + + void SBTestScene::Render() + { + } + + void SBTestScene::Unload() + { + } + + void SBTestScene::Free() + { + //SHSerialization::SerializeScene("resources/scenes/Scene01.SHADE"); + } + +} + + + diff --git a/SHADE_Application/src/Scenes/SBTestScene.h b/SHADE_Application/src/Scenes/SBTestScene.h new file mode 100644 index 00000000..6776c671 --- /dev/null +++ b/SHADE_Application/src/Scenes/SBTestScene.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Scene/SHScene.h" +#include "Scene/SHSceneManager.h" + +namespace Sandbox +{ + class SBTestScene : public SHADE::SHScene + { + private: + EntityID camera; + + + public: + virtual void Load(); + virtual void Init(); + virtual void Update(float dt); + virtual void Render(); + virtual void Free(); + virtual void Unload(); + + //TODO: Change to new window DO IT IN CPP TOO + void WindowFocusFunc(void* window, int focused); + + SBTestScene(void) = default; + }; + +} + diff --git a/SHADE_Application/src/WinMain.cpp b/SHADE_Application/src/WinMain.cpp index 21fb268a..ea1ac5fe 100644 --- a/SHADE_Application/src/WinMain.cpp +++ b/SHADE_Application/src/WinMain.cpp @@ -1,7 +1,51 @@ #include "SBpch.h" +#include +#include +#include +#include "Application/SBApplication.h" -INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, - PSTR lpCmdLine, INT nCmdShow) + +#define _CRTDBG_MAP_ALLOC +#include +#include + +#ifdef _DEBUG +#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ ) +// Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the +// allocations to be of _CLIENT_BLOCK type +#else +#define DBG_NEW new +#endif + +INT WINAPI wWinMain +( + _In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ INT nCmdShow +) { + const SHADE::SHLogger::Config LOGGER_CONFIG{ .directoryPath = "./logs/" }; + SHADE::SHLogger::Initialise(LOGGER_CONFIG); + + try + { + #ifndef SHEDITOR + //ShowWindow(::GetConsoleWindow(), SW_HIDE); + #endif + + SHLOG_INFO("sup") + + SHADE::SHEngine::Run(hInstance, hPrevInstance, lpCmdLine, nCmdShow); + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + } + catch(...) + { + SHADE::SHExceptionHandler::HandleException(std::current_exception()); + SHADE::SHLogger::Shutdown(); + } + + SHADE::SHLogger::Shutdown(); + return 0; } \ No newline at end of file diff --git a/SHADE_Engine/SHADE_Engine.vcxproj b/SHADE_Engine/SHADE_Engine.vcxproj index ba1be338..aa4e8ff4 100644 --- a/SHADE_Engine/SHADE_Engine.vcxproj +++ b/SHADE_Engine/SHADE_Engine.vcxproj @@ -57,8 +57,8 @@ Use SHpch.h Level4 - _LIB;_GLFW_INCLUDE_NONE;MSDFGEN_USE_CPP11;_DEBUG;%(PreprocessorDefinitions) - src;..\Dependencies\assimp\include;..\Dependencies\imgui;..\Dependencies\imguizmo;..\Dependencies\imnodes;..\Dependencies\msdf;..\Dependencies\msdf\msdfgen;..\Dependencies\spdlog\include;..\Dependencies\tracy;..\Dependencies\VMA\include;..\Dependencies\yamlcpp\include;..\Dependencies\ktx\include;..\Dependencies\RTTR\include;..\Dependencies\reactphysics3d\include;$(VULKAN_SDK)\include;%(AdditionalIncludeDirectories) + _LIB;_GLFW_INCLUDE_NONE;MSDFGEN_USE_CPP11;NOMINMAX;_DEBUG;%(PreprocessorDefinitions) + src;..\Dependencies\assimp\include;..\Dependencies\imgui;..\Dependencies\imguizmo;..\Dependencies\imnodes;..\Dependencies\msdf;..\Dependencies\msdf\msdfgen;..\Dependencies\spdlog\include;..\Dependencies\tracy;..\Dependencies\VMA\include;..\Dependencies\yamlcpp\include;..\Dependencies\ktx\include;..\Dependencies\RTTR\include;..\Dependencies\reactphysics3d\include;$(VULKAN_SDK)\include;$(VULKAN_SDK)\Source\SPIRV-Reflect;%(AdditionalIncludeDirectories) EditAndContinue Disabled false @@ -71,7 +71,7 @@ true - vulkan-1.lib;assimp-vc142-mtd.lib;ktxd.lib;librttr_core_d.lib;%(AdditionalDependencies) + vulkan-1.lib;shaderc_shared.lib;assimp-vc142-mtd.lib;ktxd.lib;librttr_core_d.lib;%(AdditionalDependencies) libs;$(VULKAN_SDK)\Lib;..\Dependencies\assimp\lib\Debug;..\Dependencies\assimp\lib\Release;..\Dependencies\RTTR\lib;..\Dependencies\ktx\lib\Debug;..\Dependencies\ktx\lib\Release;%(AdditionalLibraryDirectories) @@ -80,8 +80,8 @@ Use SHpch.h Level4 - _LIB;_GLFW_INCLUDE_NONE;MSDFGEN_USE_CPP11;_RELEASE;%(PreprocessorDefinitions) - src;..\Dependencies\assimp\include;..\Dependencies\imgui;..\Dependencies\imguizmo;..\Dependencies\imnodes;..\Dependencies\msdf;..\Dependencies\msdf\msdfgen;..\Dependencies\spdlog\include;..\Dependencies\tracy;..\Dependencies\VMA\include;..\Dependencies\yamlcpp\include;..\Dependencies\ktx\include;..\Dependencies\RTTR\include;..\Dependencies\reactphysics3d\include;$(VULKAN_SDK)\include;%(AdditionalIncludeDirectories) + _LIB;_GLFW_INCLUDE_NONE;MSDFGEN_USE_CPP11;NOMINMAX;_RELEASE;%(PreprocessorDefinitions) + src;..\Dependencies\assimp\include;..\Dependencies\imgui;..\Dependencies\imguizmo;..\Dependencies\imnodes;..\Dependencies\msdf;..\Dependencies\msdf\msdfgen;..\Dependencies\spdlog\include;..\Dependencies\tracy;..\Dependencies\VMA\include;..\Dependencies\yamlcpp\include;..\Dependencies\ktx\include;..\Dependencies\RTTR\include;..\Dependencies\reactphysics3d\include;$(VULKAN_SDK)\include;$(VULKAN_SDK)\Source\SPIRV-Reflect;%(AdditionalIncludeDirectories) Full true true @@ -97,21 +97,169 @@ true - vulkan-1.lib;assimp-vc142-mt.lib;ktx.lib;librttr_core.lib;%(AdditionalDependencies) + vulkan-1.lib;shaderc_shared.lib;assimp-vc142-mt.lib;ktx.lib;librttr_core.lib;%(AdditionalDependencies) libs;$(VULKAN_SDK)\Lib;..\Dependencies\assimp\lib\Debug;..\Dependencies\assimp\lib\Release;..\Dependencies\RTTR\lib;..\Dependencies\ktx\lib\Debug;..\Dependencies\ktx\lib\Release;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + diff --git a/SHADE_Engine/SHADE_Engine.vcxproj.filters b/SHADE_Engine/SHADE_Engine.vcxproj.filters index 6ca01248..a9b0f4c4 100644 --- a/SHADE_Engine/SHADE_Engine.vcxproj.filters +++ b/SHADE_Engine/SHADE_Engine.vcxproj.filters @@ -4,19 +4,578 @@ {DBC7D3B0-C769-FE86-B024-12DB9C6585D7} + + {7FF59BF8-EB80-09BD-F491-8CB1609C65BD} + + + {340D0110-201D-ADE0-89D6-11FF75059C79} + + + {EBFC8BDC-D7F6-B42E-C063-4B3FACFC1A9B} + + + {6CD692F2-D80D-DB89-E117-3FAD4DCE0183} + + + {B3E3FAFD-9FDD-2350-884A-BA6074E389BC} + + + {8A8E2B37-7646-6D84-DF4D-46E0CB240875} + + + {1653CE33-0220-293F-2B39-17E717655ECD} + + + {92C817CE-7EC1-3620-A7F3-1BA5934B162C} + + + {17C745C0-83DD-4356-CC54-CF7738AA14DE} + + + {51443AC7-3D28-FB1C-A688-F56F928BE59E} + + + {573A6CF2-43C9-F5BB-ECE7-09B7D8550662} + + + {08DBDC43-F4D3-FB95-1D06-E11A095EDBA1} + + + {4AD5CA42-3664-540C-DF82-6807CBF064B2} + + + {FB5EE099-67EA-4D5E-70FB-D052DC05AA5E} + + + {BA26540B-263D-52A1-6FB4-DDC2DB092329} + + + {4B204703-3704-0859-A064-02AC8C67F2DA} + + + {EBA1D3FF-D75C-C3AB-8014-3CF66CAE0D3C} + + + {8CDBA7C9-F8E8-D5AF-81CF-D19AEDDBA166} + + + {2460C057-1070-6C28-7929-D14665585BC1} + + + {FBD334F8-67EA-328E-B061-BEAF1CB70316} + + + {1DD51CAD-8960-8A71-9271-0D66FE7BE671} + + + {57DAB30C-4369-3DD6-EC87-51D1D8F54D7C} + + + {9C0DAFD9-086F-8CE7-91DC-D299FD3CC3A6} + + + {EF2D07CC-DB26-261E-0459-0BA3F0B0052A} + + + {3AEF06DD-A6D2-151D-AFD5-43591B38DC6D} + + + {245F5AB0-1085-2417-F9CA-A9E2E58F49E3} + + + {03DB39DE-EFBE-FA33-581F-F5864422E5B5} + + + {576DF841-4392-47C2-6CDD-2C52586146E0} + + + {75F29FE5-6102-4CB6-CABB-B0D4B6EA3A4F} + + + {5BAB2A92-478F-EBE7-B0EF-E53A9CF2D569} + + + {B3B14D12-9FC1-F9E2-087B-5E01F4A9E87B} + + + {AFF4887C-9B2B-8A0D-4418-7010302E060F} + + + {F1B75745-5D6D-D03A-E661-CA115216C73E} + + + {AC05897C-983C-8A0D-4129-70102D3F060F} + + + {ED6CDF9B-D939-3AA7-0253-284FEE7E6F35} + + + {B3F7140E-1F0C-3DBF-E88D-E01E546139F0} + + + {16CF2D0E-82E3-55BF-4B65-F91EB73852F0} + + + Engine\ECS_Base\Components + + + Engine\ECS_Base\Components + + + Engine\ECS_Base\Entity + + + Engine\ECS_Base\General + + + Engine\ECS_Base\General + + + Engine\ECS_Base\General + + + Engine\ECS_Base\General + + + Engine\ECS_Base\General + + + Engine\ECS_Base + + + Engine\ECS_Base\System + + + Engine\ECS_Base\System + + + Engine\ECS_Base\System + + + Engine\ECS_Base\System + Engine + + Filesystem + + + Graphics\Buffers + + + Graphics\Commands + + + Graphics\Commands + + + Graphics\Commands + + + Graphics\Debugging + + + Graphics\Debugging + + + Graphics\Debugging + + + Graphics\Descriptors + + + Graphics\Descriptors + + + Graphics\Descriptors + + + Graphics\Descriptors + + + Graphics\Descriptors + + + Graphics\Devices + + + Graphics\Devices + + + Graphics\Devices + + + Graphics\Framebuffer + + + Graphics\Images + + + Graphics\Images + + + Graphics\Images + + + Graphics\Instance + + + Graphics\MiddleEnd\Interface + + + Graphics\MiddleEnd\Interface + + + Graphics\MiddleEnd\PerFrame + + + Graphics\MiddleEnd\PerFrame + + + Graphics\MiddleEnd\Shaders + + + Graphics\MiddleEnd\Shaders + + + Graphics\MiddleEnd\Shaders + + + Graphics\Pipeline + + + Graphics\Pipeline + + + Graphics\Pipeline + + + Graphics\Pipeline + + + Graphics\Pipeline + + + Graphics\Pipeline + + + Graphics\Queues + + + Graphics\RenderGraph + + + Graphics\Renderpass + + + Graphics\Renderpass + + + Graphics\Renderpass + + + Graphics\Renderpass + + + Graphics\Renderpass + + + Graphics + + + Graphics + + + Graphics + + + Graphics\Shaders\BlockInterface + + + Graphics\Shaders + + + Graphics\Shaders + + + Graphics\Shaders\spirv-reflect + + + Graphics\Swapchain + + + Graphics\Swapchain + + + Graphics\Synchronization + + + Graphics\Synchronization + + + Graphics\VertexDescriptors + + + Graphics\Windowing + + + Graphics\Windowing + + + Graphics\Windowing\Surface + + + Math + + + Math + + + Math + + + Math + + + Math\Vector + + + Math\Vector + + + Math\Vector + + + Meta + + + Resource + + + Resource + + + Resource + - + + Scene + + + Scene + + + Tools + + + Tools + + + Tools + + + Tools + + + Engine\ECS_Base\Components + + + Engine\ECS_Base\Components + + + Engine\ECS_Base\Entity + + + Engine\ECS_Base\System + + + Engine\ECS_Base\System + + + Engine\ECS_Base\System + Engine + + Filesystem + + + Graphics\Buffers + + + Graphics\Commands + + + Graphics\Commands + + + Graphics\Debugging + + + Graphics\Debugging + + + Graphics\Debugging + + + Graphics\Descriptors + + + Graphics\Descriptors + + + Graphics\Descriptors + + + Graphics\Descriptors + + + Graphics\Descriptors + + + Graphics\Devices + + + Graphics\Devices + + + Graphics\Devices + + + Graphics\Framebuffer + + + Graphics\Images + + + Graphics\Images + + + Graphics\Instance + + + Graphics\MiddleEnd\Interface + + + Graphics\MiddleEnd\Interface + + + Graphics\MiddleEnd\PerFrame + + + Graphics\MiddleEnd\PerFrame + + + Graphics\MiddleEnd\Shaders + + + Graphics\MiddleEnd\Shaders + + + Graphics\Pipeline + + + Graphics\Pipeline + + + Graphics\Pipeline + + + Graphics\Pipeline + + + Graphics\Queues + + + Graphics\RenderGraph + + + Graphics\Renderpass + + + Graphics\Renderpass + + + Graphics\Renderpass + + + Graphics + + + Graphics + + + Graphics\Shaders\BlockInterface + + + Graphics\Shaders + + + Graphics\Shaders + + + Graphics\Shaders\spirv-reflect + + + Graphics\Swapchain + + + Graphics\Synchronization + + + Graphics\Synchronization + + + Graphics\VertexDescriptors + + + Graphics\Windowing + + + Graphics\Windowing + + + Graphics\Windowing\Surface + + + Math + + + Math + + + Math + + + Math\Vector + + + Math\Vector + + + Math\Vector + + + Resource + - + + Scene + + + Tools + + + Tools + + + Tools + \ No newline at end of file diff --git a/SHADE_Engine/premake5.lua b/SHADE_Engine/premake5.lua index 39d5039f..8ed16abd 100644 --- a/SHADE_Engine/premake5.lua +++ b/SHADE_Engine/premake5.lua @@ -34,7 +34,8 @@ project "SHADE_Engine" "%{IncludeDir.ktx}/include", "%{IncludeDir.RTTR}/include", "%{IncludeDir.reactphysics3d}/include", - "%{IncludeDir.VULKAN}/include" + "%{IncludeDir.VULKAN}/include", + "%{IncludeDir.VULKAN}/Source/SPIRV-Reflect" } libdirs @@ -56,14 +57,16 @@ project "SHADE_Engine" "reactphysics3d", "imgui", "spdlog", - "vulkan-1.lib" + "vulkan-1.lib", + "shaderc_shared.lib" } defines { "_LIB", "_GLFW_INCLUDE_NONE", - "MSDFGEN_USE_CPP11" + "MSDFGEN_USE_CPP11", + "NOMINMAX" } flags diff --git a/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponent.cpp b/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponent.cpp new file mode 100644 index 00000000..b544df72 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponent.cpp @@ -0,0 +1,9 @@ +#include "SHpch.h" +#include "SHComponent.h" + +namespace SHADE +{ + SHComponent::~SHComponent() + { + } +} diff --git a/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponent.h b/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponent.h new file mode 100644 index 00000000..c9c5e6f1 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponent.h @@ -0,0 +1,121 @@ +/********************************************************************* + * \file SHComponent.h + * \author Daniel Chua Yee Chen + * \brief Declaration for the Component abstract base class. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + + +#ifndef SH_COMPONENT_H +#define SH_COMPONENT_H + +#include "SHpch.h" +#include "../SHECSMacros.h" + +namespace SHADE +{ + + class SHComponentManager; + + class SHComponent + { + friend SHComponentManager; + + private: + //The ID of the entity this component belongs to. + EntityID entityID; + //Whether this entity ID has been set once. + //This prevents the Set function from being able to change the entityID once it has been set + + + protected: + + /*!************************************************************************* + * \brief Construct a new SHComponent object + * Protected default constructor to make this an abstract base class. + ***************************************************************************/ + SHComponent() + :entityID(0),isActive(true) + { + } + + SHComponent(SHComponent const& other) + :entityID(other.entityID), isActive(other.isActive) + { + } + + public: + //Whether or not this component is active. + //Systems using this component should are responsible for checking the active state of the component before running their functionality. + bool isActive; + + /*!************************************************************************* + * \brief + * Getter function for the entityID + * \return uint32_t + * The entityID that this component belongs to. + ***************************************************************************/ + uint32_t GetEID()const + { + return this->entityID; + } + + + /*!************************************************************************* + * \brief Destroy the SHComponent object + * Default destructor for Component Base class + ***************************************************************************/ + virtual ~SHComponent(); + + /*!************************************************************************* + * \brief + * A function that is called when the entity changes its parent. + * This can remain empty if nothing has to be done before the parent changes. + * This does not change the component that the entity belongs to + * The old parent is not passed in here as the old parent might be the root node + * in which case there will be no parent. + * If needed, the old parent should be deduced using this entityID + * \param newParent + * EntityID of the new parent. + ***************************************************************************/ + virtual void ChangeParent(EntityID newParent) + { + (void)newParent; + } + + /************************************************************************** + * \brief + * This is an overloaded function of Change parent when the entity + * changes its parent to the root node. (No parent) + * + ***************************************************************************/ + virtual void ChangeParent() + { + } + + + virtual void OnDestroy() + { + } + + + virtual void OnCreate() + { + } + + SHComponent& operator=(SHComponent const& other) + { + this->entityID = other.GetEID(); + this->isActive = other.isActive; + return *this; + } + + + + + }; +} +#endif diff --git a/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponentGroup.cpp b/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponentGroup.cpp new file mode 100644 index 00000000..3ccb7778 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponentGroup.cpp @@ -0,0 +1,136 @@ +/********************************************************************* + * \file SHComponent.h + * \author Daniel Chua Yee Chen + * \brief Implementation for the SHComponentGroup class. + * The Component Group aids the engine to sort the components to align + * different component type’s data that corresponds to a single entity + * in the same index. The component group also ensure that all components + * data belonging to the group is moved to the start of the dense array. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#include "SHpch.h" +#include "SHComponentGroup.h" +#include "../System/SHComponentManager.h" + + +namespace SHADE +{ + SHComponentGroup::SHComponentGroup() + :ownershipType(OWNERSHIP_TYPE::FULL_OWNERSHIP) + { + componentTypeIDs.reserve(MAX_EID); + ownedComponentTypes.reserve(MAX_EID); + entityInGroup.reserve(MAX_EID); + } + + EntityIndex SHComponentGroup::size() noexcept + { + return (EntityIndex)entityInGroup.size(); + } + + bool SHComponentGroup::IsEntityInGroup(EntityID entityID) noexcept + { + for (auto const& eid : entityInGroup) + { + if (entityID == eid) + { + return true; + } + } + return false; + } + + bool SHComponentGroup::RemoveEntity(EntityID entityID) noexcept + { + if (!IsEntityInGroup(entityID)) + { + // This entity has not been added to the group. Ignore it. + return false; + } + + //This entity is in the group. + //Move the placement of the components in all owned component types to the relevant position and remove it from the group. + for (auto const& ownedID : ownedComponentTypes) + { + if (SHComponentManager::ComponentCount_ID(ownedID) > 2) + { + // There are components that do not belong in the group. Need to swap to the last element + SHComponentManager::SwapInDenseByIndexHash_ID((EntityIndex)entityInGroup.size() - 1, entityID, ownedID); + } + } + + for (std::vector::iterator it = entityInGroup.begin(); it != entityInGroup.end(); ++it) + { + if (*it == entityID) + { + entityInGroup.erase(it); + break; + } + } + + return true; + } + + void SHComponentGroup::Clear() noexcept + { + entityInGroup.clear(); + + } + + bool SHComponentGroup::AddComponentCheck(EntityID entityID, uint32_t componentTypeID) noexcept + { + if (IsEntityInGroup(entityID)) + { + return false; + } + + //uint32_t componentID = SHFamilyID::template GetID(); + + //Loops to check if this is one of the owned component type + for (auto const& id : componentTypeIDs) + { + //found it + if (id == componentTypeID) + { + for (auto const& typeID : componentTypeIDs) + { + if (typeID != componentTypeID && !SHComponentManager::HasComponent_ID(entityID, typeID)) + { + //This entity does not contain all other component types in this group so we don't do anything yet + return false; + } + } + //This entity contains all required component types. Add to group and swap in dense + //auto& dense = SHComponentManager::GetDense(); + + //Loop through all the owned component and sort them. + for (auto const& ownedID : ownedComponentTypes) + { + if (SHComponentManager::ComponentCount_ID(ownedID) > entityInGroup.size() + 1) + { + // There are components that do not belong in the group. Need to swap to the last element + SHComponentManager::SwapInDenseByIndexHash_ID((EntityIndex)entityInGroup.size(), entityID, ownedID); + + } + } + + // If dense.size() == entityInGroup.size() + 1. + // There is no component that is not in this group for this component type. + // Hence no sorting required. + + //Add this entityID to group + entityInGroup.push_back(entityID); + + return true; + } + } + //This component type is not in this group. + return false; + } + +} + diff --git a/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponentGroup.h b/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponentGroup.h new file mode 100644 index 00000000..3f67aff0 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/Components/SHComponentGroup.h @@ -0,0 +1,185 @@ +/********************************************************************* + * \file SHComponent.h + * \author Daniel Chua Yee Chen + * \brief Definition for the SHComponentGroup class. + * The Component Group aids the engine to sort the components to align + * different component type’s data that corresponds to a single entity + * in the same index. The component group also ensure that all components + * data belonging to the group is moved to the start of the dense array. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + + +#ifndef SH_COMPONENT_GROUP +#define SH_COMPONENT_GROUP + +#include "../SHECSMacros.h" +#include "../General/SHFamily.h" +#include "SHComponent.h" + +#include +#include +#include + +namespace SHADE +{ + enum class OWNERSHIP_TYPE + { + FULL_OWNERSHIP = 0, + PARTIAL_OWNERSHIP, + NON_OWNERSHIP, + OWNERSHIP_MAX + }; + + + //template + class SHComponentGroup + { + public: + + + //This vector store the Entity IDs of all entity that belongs to this group. + std::vector entityInGroup; + + + //template<> + //bool CheckAddComponent(); + SHComponentGroup(); + ~SHComponentGroup() = default; + + //friends SHComponentManager to allow ECSCore to create componentgroups + friend class SHComponentManager; + + /************************************************************************** + * \brief + * Get the number of entity in the group. + * \return + * Number of entity in the group. + ***************************************************************************/ + EntityIndex size() noexcept; + + /************************************************************************** + * \brief + * Check if a entity is already in the group. + * \param entityID + * The entity ID of the entity + * \return + * true if the entity belongs to the group + ***************************************************************************/ + bool IsEntityInGroup(EntityID entityID) noexcept; + + + /*!************************************************************************* + * \brief + * Checks whether the entity has fulfilled the requirements to be added into + * the Component Group after adding a specified Component Type. + * @tparam T + * Component type to be added + * \param entityID + * EntityID of the entity to be added + ***************************************************************************/ + bool AddComponentCheck(EntityID entityID, uint32_t componentTypeID) noexcept; + + + /*!************************************************************************* + * \brief + * Check if the Component type is in the component group and if the entity is in the group + * Remove it from the group if found. + * This does not handle the removal from the dense array in the sparse set. + * @tparam T + * Component type to be removed + * \param entityID + * EntityID of the entity + ***************************************************************************/ + template + std::enable_if_t, bool> RemoveComponentCheck(EntityID entityID) noexcept + { + + if (!IsEntityInGroup(entityID)) + { + // This entity has not been added to the group. Ignore it. + return false; + } + bool typeFound = false; + uint32_t componentID = SHFamilyID::template GetID(); + for (auto const& id : componentTypeIDs) + { + if (id == componentID) + { + typeFound = true; + break; + } + } + if (typeFound == false) + { + return false; //This type is not something this group needs to care about. + } + + //This entity is in the group. + //Move the placement of the components in all owned component types to the relevant position and remove it from the group. + for (auto const& ownedID : ownedComponentTypes) + { + if (SHComponentManager::ComponentCount_ID(ownedID) > entityInGroup.size() + 1) + { + // There are components that do not belong in the group. Need to swap to the last element + SHComponentManager::SwapInDenseByIndexHash_ID((EntityIndex)entityInGroup.size() - 1, entityID, ownedID); + } + } + + for (std::vector::iterator it = entityInGroup.begin(); it != entityInGroup.end(); ++it) + { + if (*it == entityID) + { + entityInGroup.erase(it); + break; + } + } + + return true; + + } + + /*!************************************************************************* + * \brief + * Checks if this entity is in the Component Group and remove it if found. + * This does not handle the removal from the dense array in the sparse set. + * \param entityID + * EntityID of the entity + ***************************************************************************/ + bool RemoveEntity(EntityID entityID) noexcept; + + + /*!************************************************************************* + * \brief + * Resets the Component Group by clearing all entity in group. + * This will not change the Component Types that belong in the group but + * only clear the entity that belongs to the group. + * This is used for scene transition where all entities are cleared. + ***************************************************************************/ + void Clear() noexcept; + + + private: + OWNERSHIP_TYPE ownershipType; + + protected: + + //This vector stores all IDs of all the component type that belongs to this group. + //This will be assigned when the group gets created and used by the group themselves when they for add/remove components. + std::vector componentTypeIDs; + + //This vector stores the IDs of all the component type that this group owns. + //Each component type can only be owned by one group. + std::vector ownedComponentTypes; + + + + }; + +} + + +#endif diff --git a/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.cpp b/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.cpp new file mode 100644 index 00000000..6005fb01 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.cpp @@ -0,0 +1,74 @@ +/********************************************************************* + * \file SHEntity.cpp + * \author Daniel Chua Yee Chen + * \brief Definition of function used in SHEntity class. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ +#include "SHpch.h" +#include "SHEntity.h" +#include "../System/SHEntityManager.h" +//#include "Scene/SHSceneGraph.h" +#include "../System/SHComponentManager.h" + +namespace SHADE +{ + + SHEntity::SHEntity() + :entityID(),isActive(true) + { + + } + + SHEntity::~SHEntity() + { + + //SHEntityManager::RemoveEntity(this->entityID); + } + + EntityID SHEntity::GetEID() noexcept + { + return this->entityID; + } + + void SHEntity::SetActive(bool active) noexcept + { + isActive = active; + SHComponentManager::SetActive(entityID, active); + } + + + + void SHEntity::SetParent(SHEntity* newParent) noexcept + { + (void)newParent; + //TODO + } + + void SHEntity::SetParent(EntityID newParentID) noexcept + { + (void)newParentID; + //TODO + } + + SHEntity* SHEntity::GetParent() noexcept + { + //TODO + return nullptr; + } + + + std::vectorconst& SHEntity::GetChildren() noexcept + { + //TODO + return std::vector{}; + } + + std::vectorconst& SHEntity::GetChildrenID() noexcept + { + return std::vector{}; + } + +} diff --git a/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.h b/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.h new file mode 100644 index 00000000..d499042c --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/Entity/SHEntity.h @@ -0,0 +1,163 @@ +/********************************************************************* + * \file SHEntity.h + * \author Daniel Chua Yee Chen + * \brief Declaration for the Entity abstract base class. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#ifndef SH_ENTITY_H +#define SH_ENTITY_H + +#include "../SHECSMacros.h" +#include "../Components/SHComponent.h" +#include "../System/SHComponentManager.h" +//#include "../../Scene/SHSceneNode.h" + + + +namespace SHADE +{ + class SHComponentManager; + class SHEntityManager; + + class SHEntity + { + public: + + + /************************************************************************** + * Friends with SHEntityManger. + * This allows SHEntityManager to generate a unique entityID for the entity + * upon creation. + * . + ***************************************************************************/ + friend SHEntityManager; + + + /*!************************************************************************* + * \brief Construct a new SHEntity object + * Default constructor for the Entity Object. + * Entities are given their entityID here. + ***************************************************************************/ + SHEntity(); + + /*!************************************************************************* + * \brief Destroy the SHEntity object + * Default destructor for the Entity object. + * Removes the entityID and queue it for recycling. + ***************************************************************************/ + virtual ~SHEntity(); + + + /*!************************************************************************* + * \brief Get the Component object + * A templated GetComponent_s function that calls GetComponent_s from SHComponentManager. + * This is for ease of use. + * @tparam T + * The type of the Component we are trying to get. + * \return + * A pointer to the Component we are trying to get. + * Returns nullptr if the entity does not have such Component. + ***************************************************************************/ + template + std::enable_if_t, T*> GetComponent() noexcept + { + + return SHComponentManager::GetComponent_s(entityID); + //return nullptr; + } + + + /*!************************************************************************* + * \brief + * Getter function for the entityID + * \return uint32_t + * The entityID of this Entity object. + ***************************************************************************/ + EntityID GetEID() noexcept; + + /*!************************************************************************* + * \brief Set the Active object + * Sets the isActive of all Components in this entity. + * This is done through SHComponentManager and this function is for ease of use. + * If derived classes of Entity overloads this function, they are expected to + * call this function using SHEntity::SetActive(active) to change the active of + * all components + * \param active + * The active state to change all components in this entity. + ***************************************************************************/ + virtual void SetActive(bool active) noexcept; + + + + /************************************************************************** + * \brief + * Change the parent of the entity object in the scene graph. + * This command will be stored in a queue and only applied when + * SHSceneGraph::UpdateHierachy is called. + * \param newParent + * A pointer to the new parent entity. + * Pass a nullptr to attach this entity to the root node instead. + ***************************************************************************/ + void SetParent(SHEntity* newParent = nullptr) noexcept; + + + /************************************************************************** + * \brief + * Change the parent of the entity object in the scene graphj. + * This command will be stored in a queue and only applied when + * SHSceneGraph::UpdateHierachy is called + * This is the overloaded version of the function that takes in a + * EntityID instead. This cannot be used to attach to the root node. + * \param newParentID + * The entityID of the new parent. + ***************************************************************************/ + void SetParent(EntityID newParentID) noexcept; + + /************************************************************************** + * \brief + * Get a pointer to the parent entity. + * \return + * Returns a pointer to the parent entity. + * Returns a nullptr if the parent node is the root node. + ***************************************************************************/ + SHEntity* GetParent() noexcept; + + + /************************************************************************** + * \brief + * Get a vector of SHEntity pointers of the children belonging to this entity. + * \return + * Return a vector of SHEntity pointers of the children belonging to this entity. + ***************************************************************************/ + std::vectorconst& GetChildren() noexcept; + + /************************************************************************** + * \brief + * Get a vector of EntityID of the children belonging to this entity. + * \return + * return a vector of EntityID of the children belonging to this entity. + ***************************************************************************/ + std::vectorconst& GetChildrenID() noexcept; + + + std::string name; + bool isActive; + + + private: + //The identifier of the entity. + //This is split into 2 x 16-bits that represents version and index respectively + //Index is used by the engine to index the entity. + //Version is used by the engine to recycle the index of the entity. + EntityID entityID; + + + }; +} + + +#endif diff --git a/SHADE_Engine/src/Engine/ECS_Base/General/SHFamily.h b/SHADE_Engine/src/Engine/ECS_Base/General/SHFamily.h new file mode 100644 index 00000000..f97bbe0e --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/General/SHFamily.h @@ -0,0 +1,88 @@ +/********************************************************************* + * \file SHFamily.h + * \author Daniel Chua Yee Chen + * \brief Declaration for the SHFamily template class. + * This class is to create a unique identifier for each derived class type. + * Example: Each Component type (SHTransformComponent / SHRenderableComponent) + * will have a different identifier generated by this class. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#ifndef SH_FAMILY_H +#define SH_FAMILY_H + +#include "../SHECSMacros.h" + + +namespace SHADE +{ + template + class SHFamilyID + { + private: + //this is used to keep track of the new current ID to be assign to a new Derived class type. + static ComponentTypeID currentID; + + /*!************************************************************************* + * \brief Construct a new SHFamilyID object + * Private constructor. + * No objects of this type should be made. + * Only use the static functions of this class + ***************************************************************************/ + SHFamilyID() + { + } + + /*!************************************************************************* + * \brief Destroy the SHFamilyID object + * Private destructor. + * No objects of this type should be made. + * Only use the static functions of this class + ***************************************************************************/ + virtual ~SHFamilyID() + { + } + public: + + /*!************************************************************************* + * \brief + * Checks if this identifier is cuurrently in use / valid. + * \param id + * Identifier to check for. + * \return bool + * true if the identifier is currently in use. + * false if the identifier is not in use. + ***************************************************************************/ + static bool IsValidID(ComponentTypeID id) noexcept + { + return(id < currentID); + } + + /*!************************************************************************* + * \brief + * Get the ID of a derived class type. + * Example of function call to this function should be: + * SHFamily::GetID(); + * @tparam DerivedClass + * The derived class type that we are trying to get the ID of. + ***************************************************************************/ + template + static ENABLE_IF_DERIVED(ComponentTypeID, BaseClass, DerivedClass) GetID() noexcept + { + //The first time a new derived class type call this get id, it will initialize id using the currentID from familyID class. + static ComponentTypeID id = currentID++; + return id; + } + }; + //initialize currentID as 0 + template + ComponentTypeID SHFamilyID::currentID = 0; + +} + + + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Engine/ECS_Base/General/SHHandleGenerator.h b/SHADE_Engine/src/Engine/ECS_Base/General/SHHandleGenerator.h new file mode 100644 index 00000000..48ae2132 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/General/SHHandleGenerator.h @@ -0,0 +1,305 @@ +/********************************************************************* + * \file SHHandleGenerator.h + * \author Daniel Chua Yee Chen + * + * \brief Declaration for the SHHandleGenerator class. + * Generates a unique identifier/handle for objects. + * This is different from SHFamily which is meant to generate identifier for object types. + * + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ +#ifndef SH_HANDLE_GENERATOR_H +#define SH_HANDLE_GENERATOR_H + +#include +#include + +#include "../SHECSMacros.h" + + + +namespace SHADE +{ + /********************************************************************* + * \brief + * Generates a unique handle of HandleType type. + * The first half of the bits will be used for version. This is used for recyling the IDs. + * The second half of the bits will be used for the index, this is also used as its ID. + * + * HandleType should always be twice the size of IndexType + *********************************************************************/ + + template + class SHHandleGenerator + { + private: + //List of all the handles in use. Handles that are deleted are still stored here. + std::vector handleList; + //The next handle to be used. + HandleType nextRecycle; + //The total number of handles that are currently deleted and waiting to be recycled. + HandleType recycleCounter; + + //The Mask is variable is used to easily separate the version and index via bit masking (bitwise &). + + + //The index mask is used to identify the index. + //(1<<16) - 1. Shift 1 bit to the left 16 times and -1. This leaves the first 16 bits as 0 and the -1 makes the second 16 bits all 1. + static const HandleType indexMask = (1 << sizeof(IndexType) * 8) - 1; + + //using the bitwise NOT, invert the index mask to get the version mask. + static const HandleType versionMask = ~indexMask; + + public: + /*!************************************************************************* + * \brief Construct a new SHHandleGenerator object + * default constructor for the HandleGenerator object. + ***************************************************************************/ + SHHandleGenerator() + :nextRecycle(0),recycleCounter(0) + { + } + + /*!************************************************************************* + * \brief Destroy the SHHandleGenerator object + * Default destructor for the HandleGenerator object. + ***************************************************************************/ + ~SHHandleGenerator() = default; + + + /*!************************************************************************* + * \brief + * Combines the 16-bit version and 16-bit index to make a 32-bit handle. + * \param verison + * The 16-bit version + * \param index + * The 16-bit index + * \return HandleType + * The resultant Handle + ***************************************************************************/ + static HandleType GetHandle(IndexType version, IndexType index) noexcept + { + return (version << sizeof(IndexType) * 8) | index; + } + + /*!************************************************************************* + * \brief + * Gets the 16-bit version from the 32-bit handle + * \param handle + * The 32-bit handle + * \return IndexType + * The 16-bit version + ***************************************************************************/ + static IndexType GetVersion(HandleType handle) noexcept + { + return handle >> sizeof(IndexType) * 8; + } + + /*!************************************************************************* + * \brief + * Gets the 16-bit index from the 32-bit handle + * \param handle + * The 32-bit handle + * \return IndexType + * The 16-bit index + ***************************************************************************/ + static IndexType GetIndex(HandleType handle) noexcept + { + return handle & indexMask; + } + + /*!************************************************************************* + * \brief + * Checks if the current handle is still valid. This checks if the index + * of the handle is in use, then checks if the version of the current index + * matches the handle we are checking for. + * \param handle + * The handle to check for. + * \return bool + * True if the handle is still valid. False if the handle has been removed + * or not in use. + ***************************************************************************/ + bool IsValid(HandleType handle) noexcept + { + IndexType index = GetIndex(handle); + if (index >= (IndexType)handleList.size()) // handle is out of range of intialized index + { + return false; + } + + return (handleList[index] == handle); // this will check if the current handle at that index has been removed. + } + + /************************************************************************** + * \brief + * Get the number of active handles. + * \return + * Number of active handles. + ***************************************************************************/ + IndexType GetActiveHandleCount() noexcept + { + return (IndexType)(handleList.size() - recycleCounter); + } + + /*!************************************************************************* + * \brief + * Request the Generator for a new handle. This will provide recycled index + * if available + * \return HandleType + * The new handle. + ***************************************************************************/ + HandleType GetNewHandle() noexcept + { + HandleType result; + + //Check if there is any index waiting to be recycled + if (recycleCounter == 0) // nothing to be recycled. Make a new one + { + result = GetHandle(0, (IndexType)handleList.size()); + handleList.push_back(result); + return result; + } + //There is a index waiting to be recycled. + result = nextRecycle; + IndexType index = GetIndex(nextRecycle); + //updates the next recycle. + nextRecycle = handleList[index]; + handleList[index] = result; + --recycleCounter; + return result; + + } + + /*!************************************************************************* + * \brief + * Removes a handle and make it invalid and queue it for recycling. + * \param handle + * The handle to remove. + ***************************************************************************/ + void RemoveHandle(HandleType handle) noexcept + { + if (!IsValid(handle)) + { + return; + } + + IndexType index = GetIndex(handle); + IndexType version = GetVersion(handle); + + //set the handle at the current index to the next thing to be recycled. + handleList[index] = nextRecycle; + + //set the next recycle to be the thing that was just deleted with its version incremented. + nextRecycle = GetHandle(version + 1, index); + ++recycleCounter; + + } + + /*!************************************************************************* + * \brief + * Clears all the handles in use and Resets the handle generation. + ***************************************************************************/ + void ClearAllHandles() noexcept + { + handleList.clear(); + nextRecycle = 0; + recycleCounter = 0; + } + + /************************************************************************** + * \brief + * Attempts to claim a specified handle. If the handle is available, the + * generator will mark it as claimed and return true. + * Updates recycles accordingly when adding extra padding to the handle list. + * + * \param handle + * The handle to claim + * + * \return + * true if the handle is available to claim. + ***************************************************************************/ + bool ClaimHandle(HandleType handle) noexcept + { + IndexType index = GetIndex(handle); + + if (handleList.size() <= index || GetIndex(handleList[index]) != index) + { + //This index is currently not in use + + if (index >= handleList.size()) + { + IndexType size = (IndexType)handleList.size(); + + for (IndexType i = 0; i < index - size; ++i) + { + handleList.push_back((IndexType)handleList.size()); + + HandleType back = handleList.back(); + //set the handle at the current index to the next thing to be recycled. + if (recycleCounter > 0) + handleList.back() = nextRecycle; + else + { + handleList.back() = (HandleType)-1; + } + + //set the next recycle to be the thing that was just deleted with its version incremented. + nextRecycle = GetHandle(0, GetIndex(back)); + ++recycleCounter; + } + handleList.push_back(handle); + + } + else + { + if (index == GetIndex(nextRecycle)) + { + index = GetIndex(nextRecycle); + //updates the next recycle. + nextRecycle = handleList[index]; + handleList[index] = handle; + --recycleCounter; + return true; + } + + if (recycleCounter > 0) + { + HandleType recycle = handleList[index]; + IndexType recycleIndex = GetIndex(nextRecycle); + HandleType i = 0; + while (i < recycleCounter) + { + if (GetIndex(handleList[recycleIndex]) == index) + break; + recycleIndex = GetIndex(handleList[recycleIndex]); + ++i; + } + --recycleCounter; + if (recycleCounter > 0 && recycleIndex < handleList.size()) + handleList[recycleIndex] = recycle; + } + handleList[index] = handle; + } + + + return true; + } + + return false; + + } + + + }; + + + + typedef SHHandleGenerator EntityHandleGenerator; + +} + + +#endif diff --git a/SHADE_Engine/src/Engine/ECS_Base/General/SHSparseBase.h b/SHADE_Engine/src/Engine/ECS_Base/General/SHSparseBase.h new file mode 100644 index 00000000..2deebe74 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/General/SHSparseBase.h @@ -0,0 +1,50 @@ +/********************************************************************* + * \file SHSparseBase.h + * \author Daniel Chua Yee Chen + * \brief Declaration for the SHSparseBase class. + * This is an abstract class for the SHSparseSet template class. + * This allows the SHSparseSetContainer class to store SparseSet of different types. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#ifndef SH_SPARSE_BASE_H +#define SH_SPARSE_BASE_H + +#include "../SHECSMacros.h" + +namespace SHADE +{ + + class SHSparseBase + { + protected: + SHSparseBase() = default; + + public: + virtual ~SHSparseBase() = default; + + virtual void Remove(EntityIndex hash) { (void)hash; }; + + virtual void Clear() {}; + + virtual void Swap(EntityIndex hash1, EntityIndex hash2) { (void)hash1; (void)hash2; }; + + virtual void SwapIndexHash(EntityIndex index1, EntityIndex hash) { (void)index1; (void)hash; }; + + virtual bool Has(EntityIndex hash) = 0; + + virtual void* Get(EntityIndex hash) { (void)hash; return nullptr; }; + + virtual void Add(EntityIndex hash) { (void)hash; }; + + virtual EntityIndex Count() = 0; + + }; + +} + + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Engine/ECS_Base/General/SHSparseSet.h b/SHADE_Engine/src/Engine/ECS_Base/General/SHSparseSet.h new file mode 100644 index 00000000..3473eb37 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/General/SHSparseSet.h @@ -0,0 +1,356 @@ +/********************************************************************* + * \file SHSparseSet.h + * \author Daniel Chua Yee Chen + * \brief Declaration for the SHSparseSet template class + * This is a container that allows for fast iteration due to contiguous memory + * storage while having fast lookup time at the cost of memory usage. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ +#ifndef SH_SPARSE_SET_H +#define SH_SPARSE_SET_H + +#include "../SHECSMacros.h" +#include "../General/SHSparseBase.h" +#include "../General/SHHandleGenerator.h" +#include +#include +#include + +namespace SHADE +{ + template + class SHSparseSet: public SHSparseBase + { + private: + //Dense array of type T + std::vector denseArray; + //This array stores the key of the corresponding element in the dense array. (eg. entity ID) + std::vector denseIndexArray; + + //Sparse Array. This array helps with look up + EntityIndex sparseArray[MAX]; + + public: + + /*!************************************************************************* + * \brief Construct a new SHSparseSet object + * Default constructor for the SparseSet class. + ***************************************************************************/ + SHSparseSet() + { + for (EntityIndex i = 0; i < MAX; ++i) + { + sparseArray[i] = UINT16_MAX; + } + + denseArray.reserve(MAX); + denseIndexArray.reserve(MAX); + } + + /*!************************************************************************* + * \brief Destroy the SHSparseSet object + * Default destructor for the SparseSet class. + ***************************************************************************/ + virtual ~SHSparseSet() + { + Clear(); + } + + /*!************************************************************************* + * \brief + * Clears all elements in the current SparseSet and resets the sparseArray. + ***************************************************************************/ + void Clear() noexcept + { + denseArray.clear(); + denseIndexArray.clear(); + for (EntityIndex i = 0; i < MAX; ++i) + { + sparseArray[i] = UINT16_MAX; + } + } + + /*!************************************************************************* + * \brief + * Check is the specified hash is in range of the sparseSet. + * \param hash + * Hash to check for. + * \return bool + * true if the specified hash is in range of the spraseSet. + ***************************************************************************/ + bool IsValid(EntityIndex hash) noexcept + { + return (hash < MAX); + } + + /*!************************************************************************* + * \brief + * Checks if this sparseSet contains an element with specified hash. + * \param hash + * The hash to check for. + * \return bool + * true if the element with this hash is found. false if no such element is found. + ***************************************************************************/ + virtual bool Has(EntityIndex hash) noexcept + { + //Check if the hash passed in is within range. + if (!IsValid(hash) || sparseArray[hash] == UINT16_MAX) + { + return false; + } + return (denseIndexArray[sparseArray[hash]] == hash); + } + + /*!************************************************************************* + * \brief + * Get a pointer to the element with the specified hash. + + * This is the safe version which does a Has() check and return a nullptr + * if it does not have such an Element + * + * \param hash + * The hash of the element to look for. + * \return T* + * returns a pointer to the element with the specified hash if found. + * returns a nullptr if no such element is found. + ***************************************************************************/ + T* GetElement_s(EntityIndex hash) noexcept + { + if (!Has(hash)) + { + return nullptr; + } + + return &denseArray[sparseArray[hash]]; + } + + /*!************************************************************************* + * \brief + * Get a pointer to the element with the specified hash. + + * This bypasses the Has() check and assumes that the hash has a Element + * in the dense array. + * + * \param hash + * The hash of the element to look for. + * \return T* + * returns a pointer to the element with the specified hash if found. + * returns a nullptr if no such element is found. + ***************************************************************************/ + T* GetElement(EntityIndex hash) noexcept + { + return &denseArray[sparseArray[hash]]; + } + + + + /*!************************************************************************* + * \brief + * Operator overload for the subscript operator. + * Gets the element by reference with the hash specified in the subscript. + * This does not handle out of range exceptions. + * \param hash + * The hash of the element we are trying to get + * \return T& + * A reference to the element found. + ***************************************************************************/ + T& operator[](EntityIndex hash) noexcept + { + return denseArray[sparseArray[hash]]; + } + + /*!************************************************************************* + * \brief + * Get the number of elements in the current SparseSet + * \return uint16_t + * The number of elements in the SparseSet. + ***************************************************************************/ + virtual EntityIndex Count() noexcept + { + return (EntityIndex)denseArray.size(); + } + + /*!************************************************************************* + * \brief + * Getter function for the dense array. + * \return std::vector& + * return a reference to the dense array. + ***************************************************************************/ + std::vector& GetDense() noexcept + { + return denseArray; + } + + /*!************************************************************************* + * \brief + * Const getter function for the dense array. + * \return std::vector& + * return a const reference to the dense array. + ***************************************************************************/ + const std::vector GetDense() const noexcept + { + return denseArray; + } + + /*!************************************************************************* + * \brief + * Swaps two elements in the dense array. + * This only swaps their position in the dense array and updates their + * Sparse index reference in the sprase array accordingly. + * This DOES NOT change the hash of the elements. + * \param hash1 + * Hash of the first element + * \param hash2 + * Hash of the second element + ***************************************************************************/ + virtual void Swap(EntityIndex hash1, EntityIndex hash2) noexcept + { + if (!Has(hash1) || !Has(hash2) || hash1 == hash2) + { + return; + } + T tempElement = denseArray[sparseArray[hash1]]; + EntityIndex tempIndex = denseIndexArray[sparseArray[hash1]]; + EntityIndex tempSparse = sparseArray[hash1]; + + denseArray[sparseArray[hash1]] = denseArray[sparseArray[hash2]]; + denseIndexArray[sparseArray[hash1]] = denseIndexArray[sparseArray[hash2]]; + sparseArray[hash1] = sparseArray[hash2]; + + denseArray[sparseArray[hash2]] = tempElement; + denseIndexArray[sparseArray[hash2]] = tempIndex; + sparseArray[hash2] = tempSparse; + + } + + /*!************************************************************************* + * \brief + * Swaps two elements in the dense array. + * This only swaps their position in the dense array and updates their + * Sparse index reference in the sprase array accordingly. + * This swaps using a index(position in the dense array) and a hash (EntityID) + * This DOES NOT change the hash of the elements. + * \param index1 + * The position in the dense array of the first element + * \param hash + * Hash of the second element + ***************************************************************************/ + virtual void SwapIndexHash(EntityIndex index1, EntityIndex hash) noexcept + { + if (index1 >= denseArray.size() || !Has(hash)) + { + return; + } + Swap(denseIndexArray[index1], hash); + } + + /*!************************************************************************* + * \brief + * Adds a new element to the SparseSet. + * \param hash + * The hash of the new element. + * Nothing is added if the SparseSet already contains an element with + * the same hash. + * \param element + * A reference to the element to be added. This will be passed in as a + * copy and the default copy constructor will be used to copy the element + * into the dense array. + ***************************************************************************/ + void Add(EntityIndex hash, T& element) noexcept + { + if (!IsValid(hash) || Has(hash)) + { + return; + } + + denseArray.emplace_back(element); + denseIndexArray.emplace_back (hash); + sparseArray[hash] = EntityIndex(denseIndexArray.size() - 1); + } + + /************************************************************************** + * \brief + * Adds a new element to the SparseSet. + * \param hash + * The hash of the new element. + * Nothing is added if the SparseSet already contains an element with + * the same hash. + * \return + * None + ***************************************************************************/ + void Add(EntityIndex hash) noexcept + { + if (!IsValid(hash) || Has(hash)) + { + return; + } + + denseArray.emplace_back(T()); + denseIndexArray.emplace_back(hash); + sparseArray[hash] = EntityIndex(denseIndexArray.size() - 1); + } + + + + /*!************************************************************************* + * \brief + * Removes an element from the Sparse Set + * \param hash + * + ***************************************************************************/ + virtual void Remove(EntityIndex hash) noexcept + { + if (!Has(hash)) + { + return; + } + + //Get the index of the item to be removed. + EntityIndex index = sparseArray[hash]; + + //Get the sparse index of the last element in the dense array. + EntityIndex lastSparse = denseIndexArray.back(); + + //Copy the contents of the last elements to the replace the element we are trying to remove + //denseArray[index] = denseArray.back(); + //denseIndexArray[index] = denseIndexArray.back(); + + //denseArray.erase(denseArray.begin() + index); + //denseIndexArray.erase(denseIndexArray.begin() + index); + Swap(hash, lastSparse); + + //update the sparse array with the new index of our last element. + sparseArray[lastSparse] = index; + sparseArray[hash] = UINT16_MAX; + + //Pop out the last element since we no longer need it + denseArray.pop_back(); + denseIndexArray.pop_back(); + } + + /************************************************************************** + * \brief + * Get the element specified by a hash. This will be casted to a void + * pointer + * \param hash + * The Index of the element. + * \return + * A void pointer to the element. + ***************************************************************************/ + virtual void* Get(EntityIndex hash) noexcept + { + if (!Has(hash)) + { + //no such element in this sparse set. + return nullptr; + } + return (void*) &denseArray[sparseArray[hash]]; + } + + }; +} + +#endif diff --git a/SHADE_Engine/src/Engine/ECS_Base/General/SHSparseSetContainer.h b/SHADE_Engine/src/Engine/ECS_Base/General/SHSparseSetContainer.h new file mode 100644 index 00000000..8698d1f5 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/General/SHSparseSetContainer.h @@ -0,0 +1,248 @@ +/********************************************************************* + * \file SHSparseSetContainer.h + * \author Daniel Chua Yee Chen + * \brief Declaration for SHSparseSetContainer template class. + * This is a container for SHSparseSets meant to contain SparseSets of + * different types that are derived from a shared Base class. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#ifndef SH_SPARSE_SET_CONTAINER_H +#define SH_SPARSE_SET_CONTAINER_H + +#include "SHSparseSet.h" +#include "SHFamily.h" +#include "../Components/SHComponent.h" +#include + +namespace SHADE +{ + template + class SHSparseSetContainer + { + private: + + + + public: + + //The container of SparseSets. + std::vector container; + + /*!************************************************************************* + * \brief + * Default constructor for the SHSparseSetContainer class + ***************************************************************************/ + SHSparseSetContainer() = default; + + /*!************************************************************************* + * \brief + * Default destructor for the SHSparseSetContainer class. + ***************************************************************************/ + ~SHSparseSetContainer() + { + for (std::vector::iterator it = container.begin(); it != container.end(); ++it) + { + delete (*it); + } + }; + + /*!************************************************************************* + * \brief + * Attempts to create a SparseSet for the specified derived class. + * If a SparseSet of the same class type has already been created, + * nothing is re-created. + * @tparam Derived + * The type of the SparseSet to create. + ***************************************************************************/ + template + ENABLE_IF_DERIVED(void, Base, Derived) CreateSparseSet() noexcept + { + EntityID typeID = SHFamilyID::template GetID(); + + if (container.size() > typeID) + { + if (container[typeID] == nullptr) + { + container[typeID] = (SHSparseBase*)new SHSparseSet(); + } + return; + } + while (container.size() <= typeID) + { + container.push_back(nullptr); + } + container[typeID] = (SHSparseBase*)new SHSparseSet(); + } + + //Cant use ENABLE_IF_DERIVED here because the macro is defined before template is defined. + //Hence the return type that is a templated class will be an issue. + + + /************************************************************************** + * \brief + * Get the sparse set specified by a type. This converts the type to + * a type ID using SHFamilyID and use that as the index. + * This will attempt to Create a sparse set if it doesn't exist. + * \return + * The base class pointer to the sparse set. + ***************************************************************************/ + template + std::enable_if_t< std::is_base_of_v,SHSparseSet* > GetSparseSet() noexcept + { + EntityID typeID = SHFamilyID::template GetID(); + if (container.size() <= typeID || container[typeID] == nullptr) + { + CreateSparseSet(); + } + + return (SHSparseSet*)container[typeID]; + + } + + + /************************************************************************** + * \brief + * Get the sparse set at the specified index. + * \param typeID + * The index of the sparse set to get. + * \return + * The base class pointer to the sparse set. + ***************************************************************************/ + SHSparseBase* GetSparseSet_ID(uint32_t typeID) noexcept + { + //assert(typeID >= container.size()); + + return container[typeID]; + } + + + /*!************************************************************************* + * \brief + * Get the number of elements in the specified SparseSet + * @tparam Derived + * The type of the SparseSet + ***************************************************************************/ + template + inline ENABLE_IF_DERIVED(EntityID, Base, Derived)Count() noexcept + { + return GetSparseSet()->Count(); + } + + /*!************************************************************************* + * \brief + * Get the dense array of the specified SparseSet + * @tparam Derived + * The type of the SparseSet + * \return + * Returns a reference to the dense array of the specified SparseSet + ***************************************************************************/ + template + inline std::enable_if_t < std::is_base_of_v, std::vector& > GetDense() noexcept + { + return GetSparseSet()->GetDense(); + } + + /*!************************************************************************* + * \brief + * Remove all elements with the specified hash from all the SparseSets in + * the container. + * \param hash + * The hash of the elements to remove. + ***************************************************************************/ + void RemoveElements(EntityIndex hash) noexcept + { + for (std::vector::iterator it = container.begin(); it != container.end(); ++it) + { + + if (*it) + { + (*it)->Remove(hash); + } + + } + } + + /*!************************************************************************* + * \brief + * Clear all SparseSets in the container. + ***************************************************************************/ + void ClearAllElements() noexcept + { + for (std::vector::iterator it = container.begin(); it != container.end(); ++it) + { + (*it)->Clear(); + } + } + + /*!************************************************************************* + * \brief + * Sets the isActive variable in the elements with the specified hash + * in all the SparseSets in the container. + * This assuumes that the element types has a isActive boolean. + * \param hash + * hash of the elements + * \param active + * The active state to set to. + ***************************************************************************/ + void SetElementActive(EntityIndex hash, bool active) noexcept + { + (void)active; + for (std::vector::iterator it = container.begin(); it != container.end(); ++it) + { + + if (*it) + { + SHComponent* comp = static_cast((*it)->Get(hash)); + if (comp) + { + comp->isActive = active; + } + + } + + } + } + + + + /************************************************************************** + * \brief + * Get the element of a specified hash from the Sparse set of indicated index. + * \param typeIndex + * The index of the sparse set which we are trying to get from + * \param hash + * The hash that the element belongs to. + * \return + * The address of the element casted to a void* + ***************************************************************************/ + void* GetElement(uint32_t typeIndex, EntityIndex hash) noexcept + { + if (typeIndex >= container.size()) + { + return nullptr; + } + return container[typeIndex]->Get(hash); + } + + /************************************************************************** + * \brief + * Get the size of the SparseSetContainer. + * \return + * The number of sparse sets in this container. + ***************************************************************************/ + + size_t Size() noexcept + { + return container.size(); + } + + + }; +} + + +#endif diff --git a/SHADE_Engine/src/Engine/ECS_Base/SHECSMacros.h b/SHADE_Engine/src/Engine/ECS_Base/SHECSMacros.h new file mode 100644 index 00000000..5243e20f --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/SHECSMacros.h @@ -0,0 +1,25 @@ +#ifndef SH_MACROS_H +#define SH_MACROS_H + +#include +#include + + + +typedef uint32_t EntityID; +typedef uint16_t EntityIndex; +typedef uint32_t ComponentTypeID; + + +const EntityIndex MAX_EID = 51000; + + + +#define ENABLE_IF_DERIVED(__RETURN__, __BASE__, __DERIVED__)\ + std::enable_if_t,__RETURN__> + + +#define ENABLE_IF_UINT(_TYPE, _RETURN)\ + typename std::enable_if<(std::is_integral<_TYPE>::value && !std::is_signed<_TYPE>::value),_RETURN>::type + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Engine/ECS_Base/System/SHComponentManager.cpp b/SHADE_Engine/src/Engine/ECS_Base/System/SHComponentManager.cpp new file mode 100644 index 00000000..be78a146 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/System/SHComponentManager.cpp @@ -0,0 +1,102 @@ +/********************************************************************* + * \file SHComponentManager.cpp + * \author Daniel Chua Yee Chen + * \brief Definition of functions for the SHComponentManager class. + * This is the interface that the systems are going to use to interacte with + * the components. The SparseSetContainer of components is stored and managed + * here. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ +#include "SHpch.h" +#include "SHComponentManager.h" +#include "SHEntityManager.h" +#include "SHSystemManager.h" + +namespace SHADE +{ + + SHSparseSetContainer SHComponentManager::componentSet; + std::vector SHComponentManager::componentGroups; + + void SHComponentManager::Exit() noexcept + { + } + + void SHComponentManager::RemoveComponentsOfEntity(EntityID entityID) noexcept + { + + if (!SHEntityManager::IsValidEID(entityID)) + { + return; + } + + for (uint32_t i = 0; i < componentSet.Size(); ++i) + { + SHComponent* comp = (SHComponent*) componentSet.GetElement(i, EntityHandleGenerator::GetIndex(entityID)); + if (comp) + { + comp->OnDestroy(); + } + } + + + + for (auto & grp : componentGroups) + { + grp.RemoveEntity(entityID); + } + + componentSet.RemoveElements(EntityHandleGenerator::GetIndex(entityID)); + + //entityHandle.RemoveHandle(entityID); + + } + + + bool SHComponentManager::HasComponent_ID(EntityID entityID, uint32_t componentTypeID) noexcept + { + return componentSet.GetSparseSet_ID(componentTypeID)->Has(EntityHandleGenerator::GetIndex(entityID)); + } + + + void SHComponentManager::SetActive(EntityID entityID, bool active) noexcept + { + componentSet.SetElementActive(EntityHandleGenerator::GetIndex(entityID), active); + } + + void SHComponentManager::SwapInDenseByIndexHash_ID(EntityIndex index, EntityID hash, uint32_t componentTypeID) noexcept + { + componentSet.GetSparseSet_ID(componentTypeID)->SwapIndexHash(index, EntityHandleGenerator::GetIndex(hash)); + } + + void SHComponentManager::ChangeParent(EntityID entity, EntityID newParent) + { + for (uint32_t i = 0; i < (uint32_t)componentSet.Size(); ++i) + { + SHComponent* component = (SHComponent*)componentSet.GetElement(i, EntityHandleGenerator::GetIndex(entity)); + if (component == nullptr) + { + continue; + } + component->ChangeParent(newParent); + + } + } + + void SHComponentManager::ChangeParent(EntityID entity) + { + for (uint32_t i = 0; i < (uint32_t)componentSet.Size(); ++i) + { + SHComponent* component = (SHComponent*)componentSet.GetElement(i, EntityHandleGenerator::GetIndex(entity)); + if (component == nullptr) + { + continue; + } + component->ChangeParent(); + } + } + +} diff --git a/SHADE_Engine/src/Engine/ECS_Base/System/SHComponentManager.h b/SHADE_Engine/src/Engine/ECS_Base/System/SHComponentManager.h new file mode 100644 index 00000000..05a3d1ee --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/System/SHComponentManager.h @@ -0,0 +1,477 @@ +/********************************************************************* + * \file SHComponentManager.h + * \author Daniel Chua Yee Chen + * \brief Declaration for the SHComponentManager class. + * This is the interface that the systems are going to use to interacte with + * the components. + * The SparseSetContainer of components is stored and managed + * here. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#ifndef SH_ENGINE_H +#define SH_ENGINE_H + +#include "../General/SHSparseSetContainer.h" +#include "../Components/SHComponent.h" +#include "../Components/SHComponentGroup.h" +//#include "Scene/SHSceneNode.h" + + +#include + +namespace SHADE +{ + class SHComponentManager + { + private: + + //The SparseSetContainer of components. + static SHSparseSetContainer componentSet; + //The Container of all Componentgroups + static std::vector componentGroups; + + friend class SHSceneNode; + + + + /************************************************************************** + * \brief + * This is called by the SHSceneNode friend class to change the parent + * of an entity. + * This function is privated as it is not for general use. + * \param entity + * The entityID of the entity which to change the parent of. + * \param newParent + * The entityID of the new parent. + ***************************************************************************/ + static void ChangeParent(EntityID entity, EntityID newParent); + + + /************************************************************************** + * \brief + * Overloaded function of Change parent to change the parent of an entity to + * the root node. + * This is called by the SHSceneNode friend class to change the parent + * of an entity. + * This function is privated as it is not for general use. + * \param entity + * The entityID of the entity which to change the parent of. + ***************************************************************************/ + static void ChangeParent(EntityID entity); + + + protected: + + public: + /*!************************************************************************* + * This class is used as a static class. + * No objects of this type should be created + ***************************************************************************/ + SHComponentManager() = delete; + ~SHComponentManager() = delete; + + + + /*!************************************************************************* + * \brief + * Cleans up the memory used in the Engine. + ***************************************************************************/ + static void Exit() noexcept; + + + + /*!************************************************************************* + * \brief + * Creates the SparseSet for the specified Component type + * @tparam T + * Component Type to create. + ***************************************************************************/ + template + static void CreateComponentSparseSet() noexcept + { + componentSet.CreateSparseSet(); + } + + /*!************************************************************************* + * \brief + * Getter for the dense array of a specified Component Type. + * @tparam T + * Component Type + * \return std::vector& + * reference to the dense array from the SparseSet of the Component Type T. + ***************************************************************************/ + template + static std::vector& GetDense() noexcept + { + return componentSet.GetDense(); + } + + + /*!************************************************************************* + * \brief + * Gets the Component of the entity with the specified entityID + * + * This is the safe version of GetComponent_s which does a HasComponent to make + * sure that the entity has such a component and returns nullptr if it doesn't + * + * This safe version also checks if the sparse set of this component type + * has been created in SHComponentManager and creates one if it doesn't + * + * @tparam T + * Type of Component to get. + * \param entityID + * EntityID of the entity that we are trying to get the component of. + * \return + * A pointer to the component of the entity. + * Returns nullptr if the entity does not contain such a component. + ***************************************************************************/ + template + static ENABLE_IF_DERIVED(T*, SHComponent, T) GetComponent_s(EntityID entityID) noexcept + { + EntityID typeID = SHFamilyID::template GetID(); + + if (componentSet.container.size() <= typeID) + { + componentSet.CreateSparseSet(); + return nullptr; + } + + + return (componentSet.GetSparseSet()->GetElement_s(EntityHandleGenerator::GetIndex(entityID))); + } + + /*!************************************************************************* + * \brief + * Gets the Component of the entity with the specified entityID + * + * This is the faster unsafe version. + * Use only when sure that the entity has such a Component type. This could + * give garbage value or the component of a different entity if the entity + * does not have the specified component type. This is usually used with + * ComponentGroups + * + * This unsafe version bypass the HasComponent check. + * + * This unsafe version bypass the check for sparseSet creation. + * + * @tparam T + * Type of Component to get. + * \param entityID + * EntityID of the entity that we are trying to get the component of. + * \return + * A pointer to the component of the entity. + ***************************************************************************/ + template + static ENABLE_IF_DERIVED(T*,SHComponent, T) GetComponent(EntityID entityID) noexcept + { + //EntityID typeID = SHFamilyID::template GetID(); + + return (componentSet.GetSparseSet()->GetElement(EntityHandleGenerator::GetIndex(entityID))); + } + + + + /*!************************************************************************* + * \brief + * Add a component to the specified entityID + * Nothing is added if the entityID already have a component of the same type. + * @tparam T + * Type of Component to be added. + * @tparam Args + * The args types to pass to the constructor of the Component + * \param entityID + * EntityID to add this component to. + * \param args + * The args to pass to the constructor of the Component + * \return + * None. + ***************************************************************************/ + template + static ENABLE_IF_DERIVED(void,SHComponent,T) AddComponent(EntityID entityID) noexcept + { + T element{}; + + componentSet.GetSparseSet_ID(SHFamilyID::GetID())->Add(EntityHandleGenerator::GetIndex(entityID)); + + + SHComponent* comp = (SHComponent*)componentSet.GetElement(SHFamilyID::GetID(), EntityHandleGenerator::GetIndex(entityID)); + comp->entityID = entityID; + + for (auto& grp : componentGroups) + { + grp.AddComponentCheck(entityID, SHFamilyID::GetID()); + } + + + if (comp) + { + comp->OnCreate(); + } + + } + + /************************************************************************** + * \brief + * Add Component using a component type ID. This assumes that the sparse + * set is already created for the component type ID. + * \param entityID + * The entity ID of the entity + * \param componentTypeID + * The Type ID of the Component Type. + * \return + * none + ***************************************************************************/ + static void AddComponent(EntityID entityID, uint32_t componentTypeID) noexcept + { + componentSet.GetSparseSet_ID(componentTypeID)->Add(EntityHandleGenerator::GetIndex(entityID)); + + + SHComponent* comp = (SHComponent*)componentSet.GetElement(componentTypeID, EntityHandleGenerator::GetIndex(entityID)); + comp->entityID = entityID; + + for (auto& grp : componentGroups) + { + grp.AddComponentCheck(entityID,componentTypeID); + } + + + if (comp) + { + comp->OnCreate(); + } + } + + + /*!************************************************************************* + * \brief + * Checks if the specified entityID has a component of specified type. + * @tparam T + * Type of Component to check for. + * \param entityID + * EntityID to check for. + * \return bool + * True if the entity has a component of specified type. + ***************************************************************************/ + template + static ENABLE_IF_DERIVED(bool, SHComponent, T) HasComponent(EntityID entityID) noexcept + { + return componentSet.GetSparseSet()->Has(EntityHandleGenerator::GetIndex(entityID)); + } + + + + /*!************************************************************************* + * \brief + * Checks if the specified entityID has a component of specified type. + * The component type is specified by a component type ID + * @tparam T + * Type of Component to check for. + * \param entityID + * EntityID to check for. + * \param componentTypeID + * The component type ID to look for + * \return bool + * True if the entity has a component of specified type. + ***************************************************************************/ + static bool HasComponent_ID(EntityID entityID, uint32_t componentTypeID) noexcept; + + + /*!************************************************************************* + * \brief + * Remove the Component of specified type from the entity. + * @tparam T + * Component type to be removed + * \param entityID + * EntityID of the object to remove the component from. + ***************************************************************************/ + template + static ENABLE_IF_DERIVED(void, SHComponent, T) RemoveComponent(EntityID entityID) noexcept + { + if (!HasComponent(entityID)) + { + return; + } + + SHComponent* comp = (SHComponent*)componentSet.GetElement(SHFamilyID::GetID(), EntityHandleGenerator::GetIndex(entityID)); + if (comp) + { + comp->OnDestroy(); + } + + for (auto& grp : componentGroups) + { + grp.RemoveComponentCheck(entityID); + } + + + componentSet.GetSparseSet()->Remove(EntityHandleGenerator::GetIndex(entityID)); + } + + /*!************************************************************************* + * \brief + * Swaps to positioning of two components in their dense array. + * This does not swap the entity that the components belong to + * @tparam T + * Type of the Component to swap. + * \param entityID1 + * entityID of the first entity. + * \param entityID2 + * entityID of the second entity. + * \return + * None. + ***************************************************************************/ + template + static ENABLE_IF_DERIVED(void, SHComponent, T) SwapInDense(EntityID entityID1, EntityID entityID2) noexcept + { + componentSet.GetSparseSet()->Swap(EntityHandleGenerator::GetIndex(entityID1), EntityHandleGenerator::GetIndex(entityID2)); + } + + /************************************************************************** + * \brief + * Swaps to positioning of two components in their dense array. + * This does not swap the entity that the components belong to. + * This is the overloaded function that takes 2 Entity Index + * \param index + * The entity Index of the first entity. + * \param hash + * The entity Index of the second entity. + * \return + * none + ***************************************************************************/ + template + static ENABLE_IF_DERIVED(void, SHComponent, T) SwapInDenseByIndex(EntityIndex index, EntityIndex hash) noexcept + { + componentSet.GetSparseSet()->SwapIndexHash(index, EntityHandleGenerator::GetIndex(hash)); + } + + /************************************************************************** + * \brief + * Swaps to positioning of two components in their dense array. + * This does not swap the entity that the components belong to. + * This swap using a component type ID + * \param index + * The entity Index of the first entity + * \param hash + * The entity ID of the second entity + * \param componentTypeID + * The Type ID of the component type ID + * \return + * + ***************************************************************************/ + static void SwapInDenseByIndexHash_ID(EntityIndex index, EntityID hash, uint32_t componentTypeID) noexcept; + + + + /*!************************************************************************* + * \brief + * Removes and destroy all components tied to an entity. + * entityID + * \param entityID + * entityID of the entity to be removed. + ***************************************************************************/ + static void RemoveComponentsOfEntity(EntityID entityID) noexcept; + + + + /************************************************************************** + * \brief + * Get the number of components in a component sparse set. + * \param componentTypeID + * The type ID of the Component Type. + * \return + * The number of components in the component sparse set. + ***************************************************************************/ + static EntityIndex ComponentCount_ID(uint32_t componentTypeID) + { + return componentSet.GetSparseSet_ID(componentTypeID)->Count(); + } + + + /*!************************************************************************* + * \brief + * Set the isActive boolean of all Components that the entity has + * \param entityID + * The entityID of the entity + * \param active + * The active state to set to + ***************************************************************************/ + static void SetActive(EntityID entityID, bool active) noexcept; + + template + static std::enable_if_t<(... && std::is_base_of_v), uint32_t> CreateComponentGroup(uint32_t numOwningComponents) + { + std::vector templateIDs{ (SHFamilyID::GetID())... }; + + for (auto& g : componentGroups) + { + for (auto const& oID : g.ownedComponentTypes) + { + for (uint32_t i = 0; i < numOwningComponents; ++i) + { + if ((templateIDs[i] == oID)) + { + assert("This Component is owned by another group"); + } + } + } + } + + SHComponentGroup grp; + for (uint32_t i = 0; i < numOwningComponents; ++i) + { + grp.ownedComponentTypes.push_back(templateIDs[i]); + } + for (uint32_t i = 0; i < templateIDs.size(); ++i) + { + grp.componentTypeIDs.push_back(templateIDs[i]); + } + + if (grp.ownedComponentTypes.size() != grp.componentTypeIDs.size()) + { + if (grp.ownedComponentTypes.empty()) + { + grp.ownershipType = OWNERSHIP_TYPE::NON_OWNERSHIP; + } + else + { + grp.ownershipType = OWNERSHIP_TYPE::PARTIAL_OWNERSHIP; + } + } + + componentGroups.push_back(grp); + return (uint32_t)componentGroups.size() - 1; + + } + + /************************************************************************** + * \brief + * Get a reference to the component group. + * \param index + * The index of the component group + * \return + * A reference to the component group. + ***************************************************************************/ + static SHComponentGroup& GetComponentGroup(uint16_t index) noexcept + { + return componentGroups[index]; + } + + static void AddScriptComponent(EntityID eid, std::string const& scriptClassName) noexcept; + + static void RemoveScriptComponent(EntityID eid, std::string const& scriptClassName) noexcept; + + + };// end SHComponentManager + + + + + +} + + +#endif diff --git a/SHADE_Engine/src/Engine/ECS_Base/System/SHEntityManager.cpp b/SHADE_Engine/src/Engine/ECS_Base/System/SHEntityManager.cpp new file mode 100644 index 00000000..4aa38112 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/System/SHEntityManager.cpp @@ -0,0 +1,232 @@ +/********************************************************************* + * \file SHEntityManager.cpp + * \author Daniel Chua Yee Chen + * \brief Implementation for the SHEntityManager class. + * Entity Manager is the interface class where users of the engine + * and its systems interact with entity data in the engine. This + * includes Creation and Destruction of entities and getting Entity by + * an ID. This manager also ensures that each entity have a unique + * handle using SHHandleGenerator. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ +#include "SHpch.h" +#include "SHEntityManager.h" +//#include "Scene/SHSceneGraph.h" +//#include "Serialization/SHSerialization.h" + +namespace SHADE +{ + std::vector> SHEntityManager::entityVec; + EntityHandleGenerator SHEntityManager::entityHandle; + + SHEntity* SHEntityManager::GetEntityByID(EntityID entityID) noexcept + { + if (!IsValidEID(entityID)) + { + return nullptr; + } + + EntityIndex eIndex = entityHandle.GetIndex(entityID); + return entityVec[eIndex].get(); + + } + + bool SHEntityManager::IsValidEID(EntityID entityID) noexcept + { + if (!entityHandle.IsValid(entityID)) + return false; + + EntityIndex eIndex = entityHandle.GetIndex(entityID); + if (entityVec.size() <= eIndex || !entityVec[eIndex]) + return false; + + return true; + + } + + EntityIndex SHEntityManager::GetEntityIndex(EntityID entityID) noexcept + { + return entityHandle.GetIndex(entityID); + } + + EntityID SHEntityManager::CreateEntity(std::vectorconst& componentTypeIDs, std::string const& name,EntityID parentEID) + { + EntityID eID = entityHandle.GetNewHandle(); + EntityIndex eIndex = entityHandle.GetIndex(eID); + if (eIndex > entityVec.size()) + { + assert("FATAL ERROR: EntityIndex out of range in Entity Creation"); + } + else if (eIndex == entityVec.size()) + { + entityVec.emplace_back(std::make_unique()); + } + else + { + if (!entityVec[eIndex]) + { + //There is still an entity stored there.Something went wrong + assert("FATAL ERROR: Entity Creation error. Entity Index Conflict"); + } + + //Reset it to a newly constructed entity + entityVec[eIndex].reset(new SHEntity()); + } + + + entityVec[eIndex]->entityID = eID; + entityVec[eIndex]->name = name; + for (auto& id : componentTypeIDs) + { + SHComponentManager::AddComponent(eID, id); + } + + //(SHComponentManager::AddComponent(eID), ...); + /*if (entityHandle.IsValid(parentEID) == false) + { + entityVec[eIndex]->sceneNode.ConnectToRoot(); + } + else + { + entityVec[eIndex]->SetParent(parentEID); + }*/ + + + //TODO Link to Scene graph. + + return eID; + + } + + EntityID SHEntityManager::CreateEntity(std::vectorconst& componentTypeIDs, EntityID desiredEID, std::string const& name, EntityID parentEID) + { + EntityID eID ; + + if (entityHandle.ClaimHandle(desiredEID) == true) + eID = desiredEID; + else + eID = entityHandle.GetNewHandle(); + + + EntityIndex eIndex = entityHandle.GetIndex(eID); + if (eIndex > entityVec.size()) + { + EntityIndex size = (EntityIndex)entityVec.size(); + for (EntityIndex i = 0; i <= eIndex - size; ++i) + { + entityVec.push_back(nullptr); + + } + entityVec[eIndex].reset(new SHEntity()); + } + else if (eIndex == entityVec.size()) + { + entityVec.emplace_back(std::make_unique()); + } + else + { + if (!entityVec[eIndex]) + { + //There is still an entity stored there.Something went wrong + assert("FATAL ERROR: Entity Creation error. Entity Index Conflict"); + } + + //Reset it to a newly constructed entity + entityVec[eIndex].reset(new SHEntity()); + } + + + entityVec[eIndex]->entityID = eID; + entityVec[eIndex]->name = name; + for (auto& id : componentTypeIDs) + { + SHComponentManager::AddComponent(eID, id); + } + + //(SHComponentManager::AddComponent(eID), ...); + + //if (entityHandle.IsValid(parentEID) == false) + //{ + // entityVec[eIndex]->sceneNode.ConnectToRoot(); + //} + //else + //{ + // entityVec[eIndex]->SetParent(parentEID); + //} + + //TODO Link to scene graph. + + + return eID; + + } + + + void SHEntityManager::DestroyEntity(EntityID eID) noexcept + { + if (!IsValidEID(eID)) + { + //Entity does not exist or already destroyed. + return; + } + EntityIndex eIndex = entityHandle.GetIndex(eID); + + //Call all the children to Destroy themselves first before the parent is destroyed. + if (entityVec[eIndex]) + { + //auto& children = entityVec[eIndex]->GetChildrenID(); + //while(!children.empty()) + //{ + // DestroyEntity(children[0]); + //} + + //SHSceneNode* parentNode = entityVec[eIndex]->GetSceneNode()->GetParent(); + + //SHSceneGraph::RemoveChild(parentNode,entityVec[eIndex].get()); + + //TODO remove from parent and recursively delete child. + + + + SHComponentManager::RemoveComponentsOfEntity(eID); + + entityHandle.RemoveHandle(eID); + + entityVec[eIndex].reset(nullptr); + } + + } + + void SHEntityManager::DestroyAllEntity() noexcept + { + for (auto& entity : entityVec) + { + + if (entity) + { + DestroyEntity(entity->GetEID()); + } + } + entityHandle.ClearAllHandles(); + } + + EntityIndex SHEntityManager::GetEntityCount() noexcept + { + return entityHandle.GetActiveHandleCount(); + } + + + /*EntityID SHEntityManager::DuplicateEntity(EntityID eid) noexcept + { + if (entityHandle.IsValid(eid) == false) + return MAX_EID; + + std::string data = SHSerialization::SerializeEntityToString(eid); + return SHSerialization::DeserializeEntityToSceneFromString(data); + }*/ + + +} diff --git a/SHADE_Engine/src/Engine/ECS_Base/System/SHEntityManager.h b/SHADE_Engine/src/Engine/ECS_Base/System/SHEntityManager.h new file mode 100644 index 00000000..11e896d5 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/System/SHEntityManager.h @@ -0,0 +1,253 @@ +/********************************************************************* + * \file SHEntityManger.h + * \author Daniel Chua Yee Chen + * \brief Definition for the SHEntityManager class. + * Entity Manager is the interface class where users of the engine + * and its systems interact with entity data in the engine. This + * includes Creation and Destruction of entities and getting Entity by + * an ID. This manager also ensures that each entity have a unique + * handle using SHHandleGenerator. + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#ifndef SH_ENTITY_MANAGER_H +#define SH_ENTITY_MANAGER_H + +#include +#include +#include "../Entity/SHEntity.h" +#include "../Components/SHComponent.h" +#include "../General/SHHandleGenerator.h" +#include "../SHECSMacros.h" + +namespace SHADE +{ + + class SHEntityManager + { + private: + static std::vector> entityVec; + //The handle generator to generate entityIDs. + static EntityHandleGenerator entityHandle; + + public: + SHEntityManager() = delete; + ~SHEntityManager() = delete; + + /************************************************************************** + * \brief + * Get a pointer to the entity using the entityID. + * \param entityID + * The entity ID of the entity + * \return + * A pointer to the entity. + * A nullptr is returned if there is no such entity or if it is destroyed. + ***************************************************************************/ + static SHEntity* GetEntityByID(EntityID entityID) noexcept; + + /************************************************************************** + * \brief + * Checks if the current Entity ID is in use and alive. + * \param entityID + * The entity ID of the entity + * \return + * true if the entity ID is in use and alive. + ***************************************************************************/ + static bool IsValidEID(EntityID entityID) noexcept; + + /************************************************************************** + * \brief + * Get the index portion of the Entity ID. + * \param entityID + * The entity ID of the entity + * \return + * The index of the entity + ***************************************************************************/ + static EntityIndex GetEntityIndex(EntityID entityID) noexcept; + + /************************************************************************** + * \brief + * Template function to Create a new Entity with Components specified + * by the template arguements. + * \param name + * Name of the entity (This is not unique) + * \param parentEID + * The entity ID of the parent. This does not call UpdateHierarchy hence + * the parent of the entity is not updated until UpdateHierarchy is called. + * \return + * EntityID of the new Entity + ***************************************************************************/ + template + static std::enable_if_t<(... && std::is_base_of_v), EntityID> CreateEntity(std::string const& name = "Default", EntityID parentEID = MAX_EID) + { + EntityID eID = entityHandle.GetNewHandle(); + EntityIndex eIndex = entityHandle.GetIndex(eID); + if (eIndex > entityVec.size()) + { + assert("FATAL ERROR: EntityIndex out of range in Entity Creation"); + } + else if (eIndex == entityVec.size()) + { + entityVec.emplace_back(std::make_unique()); + } + else + { + if (!entityVec[eIndex]) + { + //There is still an entity stored there.Something went wrong + assert("FATAL ERROR: Entity Creation error. Entity Index Conflict"); + } + + //Reset it to a newly constructed entity + entityVec[eIndex].reset(new SHEntity()); + } + entityVec[eIndex]->entityID = eID; + entityVec[eIndex]->name = name; + (SHComponentManager::AddComponent(eID),...); + + /*if (entityHandle.IsValid(parentEID) == false) + { + entityVec[eIndex]->sceneNode.ConnectToRoot(); + } + else + { + entityVec[eIndex]->SetParent(parentEID); + }*/ + + //TODO Link up with Scene graph + + return eID; + } + + template + static std::enable_if_t<(... && std::is_base_of_v), EntityID> CreateEntity(EntityID desiredEID, std::string const& name = "Default", EntityID parentEID = MAX_EID) + { + EntityID eID; + if (entityHandle.ClaimHandle(desiredEID) == true) + eID = desiredEID; + else + eID = entityHandle.GetNewHandle(); + EntityIndex eIndex = entityHandle.GetIndex(eID); + if (eIndex > entityVec.size()) + { + EntityIndex size = (EntityIndex)entityVec.size(); + for (EntityIndex i = 0; i <= eIndex - size; ++i) + { + entityVec.push_back(nullptr); + + } + entityVec[eIndex].reset(new SHEntity()); + } + else if (eIndex == entityVec.size()) + { + entityVec.emplace_back(std::make_unique()); + } + else + { + if (!entityVec[eIndex]) + { + //There is still an entity stored there.Something went wrong + assert("FATAL ERROR: Entity Creation error. Entity Index Conflict"); + } + + //Reset it to a newly constructed entity + entityVec[eIndex].reset(new SHEntity()); + } + entityVec[eIndex]->entityID = eID; + entityVec[eIndex]->name = name; + (SHComponentManager::AddComponent(eID), ...); + + /*if (entityHandle.IsValid(parentEID) == false) + { + entityVec[eIndex]->sceneNode.ConnectToRoot(); + } + else + { + entityVec[eIndex]->SetParent(parentEID); + }*/ + + + //Link up with scene graph. + + + return eID; + } + + + + + /************************************************************************** + * \brief + * Create Entity using a vector of ComponentTypeIDs. + * \param componentTypeIDs + * Vector of ComponentTypeIDs. This assumes that CreateSparseSet is called + * for these ComponentTypes. + * \param name + * Name of the Entity (this is not unique) + * \param parentEID + * The entity ID of the parent. This does not call UpdateHierarchy hence + * the parent of the entity is not updated until UpdateHierarchy is called. + * \return + * EntityID of the new Entity + ***************************************************************************/ + static EntityID CreateEntity(std::vectorconst& componentTypeIDs,std::string const& name = "Default", EntityID parentEID = MAX_EID); + + /************************************************************************** + * \brief + * Create Entity using a vector of ComponentTypeIDs. + * \param componentTypeIDs + * Vector of ComponentTypeIDs. This assumes that CreateSparseSet is called + * for these ComponentTypes. + * \param name + * Name of the Entity (this is not unique) + * \param parentEID + * The entity ID of the parent. This does not call UpdateHierarchy hence + * the parent of the entity is not updated until UpdateHierarchy is called. + * \return + * EntityID of the new Entity + ***************************************************************************/ + static EntityID CreateEntity(std::vectorconst& componentTypeIDs, EntityID desiredEID, std::string const& name = "Default", EntityID parentEID = MAX_EID); + + /************************************************************************** + * \brief + * Destroy the entity and all components tied to this entity. + * This calls Destroy Entity for all of its children as well. + * \param eID + * The entity ID of the entity + * \return + * none + ***************************************************************************/ + static void DestroyEntity(EntityID eID) noexcept; + + /************************************************************************** + * \brief + * Destroy all Entities. + * \return + * none + ***************************************************************************/ + static void DestroyAllEntity() noexcept; + + /************************************************************************** + * \brief + * Get the current number of entities. + * \return + * Number of entities. + ***************************************************************************/ + static EntityIndex GetEntityCount() noexcept; + + //static EntityID DuplicateEntity(EntityID eid) noexcept; + + protected: + + + }; + + +} + + + +#endif diff --git a/SHADE_Engine/src/Engine/ECS_Base/System/SHSystem.h b/SHADE_Engine/src/Engine/ECS_Base/System/SHSystem.h new file mode 100644 index 00000000..1a304605 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/System/SHSystem.h @@ -0,0 +1,55 @@ +/********************************************************************* + * \file SHSystem.h + * \author Daniel Chua Yee Chen + * \brief Declaration for the SHSytem abstract base class + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + +#ifndef SH_SYSTEM_H +#define SH_SYSTEM_H + +namespace SHADE +{ + class SHSystem + { + protected: + /*!************************************************************************* + * \brief + * Protected default constructor for SHSytem class + ***************************************************************************/ + SHSystem()= default; + + public: + /*!************************************************************************* + * \brief + * Destructor for SHSytem class + ***************************************************************************/ + virtual ~SHSystem() = default; + + /*!************************************************************************* + * \brief + * Pure virtual Init function. Derived class must implement this + ***************************************************************************/ + virtual void Init() = 0; + + /*!************************************************************************* + * \brief + * Pure virtual Run function. Derived class must implement this + * \param dt + * Delta time + ***************************************************************************/ + virtual void Run(float dt) = 0; + + /*!************************************************************************* + * \brief + * Pure virtual Exit function. Derived class must implement this + ***************************************************************************/ + virtual void Exit() = 0; + + }; +} + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Engine/ECS_Base/System/SHSystemManager.cpp b/SHADE_Engine/src/Engine/ECS_Base/System/SHSystemManager.cpp new file mode 100644 index 00000000..67d6f781 --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/System/SHSystemManager.cpp @@ -0,0 +1,57 @@ +/********************************************************************* + * \file SHSystemManager.cpp + * \author Daniel Chua Yee Chen + * \brief Implementation for the SHSystemManager class. + * SHSystemManager is the interface class where users of the engine create + * the systems that gives the components their functionality. This also + * ensures that the Init and Exit functions are ran at the appropriate time + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ +#include "SHpch.h" +#include "SHSystemManager.h" +#include + +namespace SHADE +{ + SHSystemManager::SystemContainer SHSystemManager::systemContainer; + + + SHSystem* SHSystemManager::GetSystem(std::string name) + { + if (systemContainer.find(name) == systemContainer.end()) + { + assert("Get System Error: No system with such name exist."); + return nullptr; + } + + return systemContainer.find(name)->second.get(); + } + + void SHSystemManager::Init() noexcept + { + for (auto& system : systemContainer) + { + system.second->Init(); +#ifdef _DEBUG + std::cout << system.first << " Init" << std::endl; +#endif + } + } + + void SHSystemManager::Exit() noexcept + { + for (auto& system : systemContainer) + { + system.second->Exit(); + //delete system.second; + } + + systemContainer.clear(); + + } + + +} diff --git a/SHADE_Engine/src/Engine/ECS_Base/System/SHSystemManager.h b/SHADE_Engine/src/Engine/ECS_Base/System/SHSystemManager.h new file mode 100644 index 00000000..d5a4866d --- /dev/null +++ b/SHADE_Engine/src/Engine/ECS_Base/System/SHSystemManager.h @@ -0,0 +1,102 @@ +/********************************************************************* + * \file SHSystemManager.h + * \author Daniel Chua Yee Chen + * \brief Declaration for the SHSystemManager class. + * SHSystemManager is the interface class where users of the engine create + * the systems that gives the components their functionality. This also ensures that the Init and Exit functions are ran at the appropriate time + * + * \copyright Copyright (c) 2021 DigiPen Institute of Technology. Reproduction + or disclosure of this file or its contents without the prior written + consent of DigiPen Institute of Technology is prohibited. + *********************************************************************/ + + +#ifndef SH_SYSTEM_MANAGER_H +#define SH_SYSTEM_MANAGER_H + + +#include +#include +#include +#include +#include "../System/SHSystem.h" + + +namespace SHADE +{ + + class SHSystemManager + { + //type definition for the container we use to store our system + using SystemContainer = std::unordered_map>; + + private: + static SystemContainer systemContainer; + + + public: + /*!************************************************************************* + * This class is used as a static class. + * No objects of this type should be created + ***************************************************************************/ + SHSystemManager() = delete; + ~SHSystemManager() = delete; + + /************************************************************************** + * \brief + * Create a system of type T and map it to a name. + * throws an error if a system with the same name already exists. + * \param name + * name of the system + * \return + * none + ***************************************************************************/ + template + static std::enable_if_t, void> CreateSystem(std::string const& name) + { + if (systemContainer.find(name) != systemContainer.end()) + { + assert("System Creation Error: System with the same name already exist."); + } + + systemContainer.emplace(name, std::make_unique()); + + + } + + /************************************************************************** + * \brief + * Get a pointer to the System with a specified name. + * \param name + * Name of the system in the map + * \return + * Base System pointer. + ***************************************************************************/ + static SHSystem* GetSystem(std::string name); + + /************************************************************************** + * \brief + * Call the Init function of all systems. + * \return + * none + ***************************************************************************/ + static void Init() noexcept; + + /************************************************************************** + * \brief + * Call the Exit function of all systems. + * \return + ***************************************************************************/ + static void Exit() noexcept; + + + protected: + + + }; + +} + + + +#endif diff --git a/SHADE_Engine/src/Engine/SHEngine.cpp b/SHADE_Engine/src/Engine/SHEngine.cpp index f65a5402..a7fec85b 100644 --- a/SHADE_Engine/src/Engine/SHEngine.cpp +++ b/SHADE_Engine/src/Engine/SHEngine.cpp @@ -1,2 +1 @@ -#include "SHpch.h" -#include "SHEngine.h" \ No newline at end of file +#include "SHpch.h" \ No newline at end of file diff --git a/SHADE_Engine/src/Engine/SHEngine.h b/SHADE_Engine/src/Engine/SHEngine.h index 7d723425..d6a50b32 100644 --- a/SHADE_Engine/src/Engine/SHEngine.h +++ b/SHADE_Engine/src/Engine/SHEngine.h @@ -1,9 +1,39 @@ -#pragma once +#ifndef SH_ENGINE_H +#define SH_ENGINE_H + +#include +#include "Meta/SHIsDetected.h" namespace SHADE { - class SHEngine - { - - }; -} \ No newline at end of file + template + using GetInit_t = decltype (std::declval().Initialize()); + + template + using GetUpdate_t = decltype (std::declval().Update()); + + template + using GetExit_t = decltype (std::declval().Exit()); + + + class SHEngine + { + + public: + template + static void Run(Args&&...args) + { + //static_assert(SHIsDetected::value, "Init Not Detected"); + static_assert(SHIsDetected::value, "Update Not Detected"); + static_assert(SHIsDetected::value, "Exit Not Detected"); + + static Application application; + + application.Initialize(std::forward(args)...); + application.Update(); + application.Exit(); + }; + }; +}; + +#endif diff --git a/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp new file mode 100644 index 00000000..4158d3c3 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp @@ -0,0 +1,460 @@ +#include "SHPch.h" +#include "SHVkBuffer.h" +#include "Graphics/Instance/SHVkInstance.h" +#include "Graphics/Commands/SHVkCommandBuffer.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + + + \param data + \param sizeToWrite + \param srcOffset + \param dstOffset + \param cmdBufferHdl + + \return + + */ + /***************************************************************************/ + void SHVkBuffer::TransferToDeviceResource(Handle const& cmdBufferHdl) noexcept + { + if (cmdBufferHdl && (bufferUsageFlags & vk::BufferUsageFlagBits::eTransferDst)) + { + vk::BufferCopy copyRegion + { + .srcOffset = 0, + .dstOffset = 0, + .size = sizeStored, + }; + cmdBufferHdl->GetVkCommandBuffer().copyBuffer(stagingBuffer, vkBuffer, 1, ©Region); + } + } + + vk::Buffer SHVkBuffer::GetVkBuffer(void) const noexcept + { + return vkBuffer; + } + + /***************************************************************************/ + /*! + + \brief + Maps the mappedPtr member pointer. + + \param vmaAllocator + Required VmaAllocator for mapping. + + */ + /***************************************************************************/ + void SHVkBuffer::Map(void) noexcept + { + if (!boundToCoherent) + vmaMapMemory(vmaAllocator, alloc, &mappedPtr); + } + + /***************************************************************************/ + /*! + + \brief + Unmaps the mappedPtr member pointer. Does not work if pointer is mapped + persistently (i.e. buffer is bound to coherent memory). + + \param vmaAllocator + Required VmaAllocator for unmapping. + + */ + /***************************************************************************/ + void SHVkBuffer::Unmap(void) noexcept + { + if (!boundToCoherent) + { + vmaUnmapMemory(vmaAllocator, alloc); + mappedPtr = nullptr; + } + } + + /***************************************************************************/ + /*! + + \brief + Writes to the mapped pointer. + + \param data + Pointer to source data. + + \param sizeToWrite + Amount to write to the buffer. + + \param srcOffset + byte offset into the source data. + + \param dstOffset + byte offset into the destination data. + + */ + /***************************************************************************/ + void SHVkBuffer::WriteToMemory(void* data, uint32_t sizeToWrite, uint32_t srcOffset, uint32_t dstOffset) noexcept + { + if (mappedPtr) + std::memcpy(static_cast(mappedPtr) + dstOffset, static_cast(data) + srcOffset, sizeToWrite); + } + + /***************************************************************************/ + /*! + + \brief + Simply writes data to a region of memory in the buffer. Mapping and + unmapping is also done if memory the buffer is bound to is HOST_VISIBLE. + Otherwise, only the copying is carried out. + + In the instance where memory is non-coherent but HOST_VISIBLE, we want to + write to data and then unmap and flush it immediately. If you want to write + to memory in random-access fashion, consider, mapping, writing a few + things, unmapping then flushing. + + \param vmaAllocator + The VMA allocator object. + + \param data + Pointer to source data. + + \param sizeToWrite + Amount to write to the buffer. + + \param srcOffset + byte offset into the source data. + + \param dstOffset + byte offset into the destination data. + + */ + /***************************************************************************/ + void SHVkBuffer::MapWriteUnmap(void* data, uint32_t sizeToWrite, uint32_t srcOffset, uint32_t dstOffset) noexcept + { + if (!boundToCoherent) + { + // map from host visible memory to pointer, do a DMA, and then unmap + Map(); + WriteToMemory(data, sizeToWrite, srcOffset, dstOffset); + Unmap(); + } + else + { + if (mappedPtr) + std::memcpy(static_cast(mappedPtr) + dstOffset, static_cast(data) + srcOffset, sizeToWrite); + } + } + + /***************************************************************************/ + /*! + + \brief + Prepares the staging buffer. Called only during initialization when it is + known that the buffer will be a GPU only resource. We want to prep the + staging buffer in advance so that transfers later will be straightforward. + + \param data + The data to write to the staging buffer. + + \param srcSize + The amount of data to transfer. + + */ + /***************************************************************************/ + void SHVkBuffer::PrepStagingBuffer(void* data, uint32_t srcSize) noexcept + { + // For creation of buffer + vk::BufferCreateInfo bufferInfo{}; + + // size stored same as GPU buffer + bufferInfo.size = sizeStored; + + // We just want to set the transfer bit + bufferInfo.usage = vk::BufferUsageFlagBits::eTransferSrc; + + // sharing mode exclusive + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + + // Set to auto detect bits + VmaAllocationCreateInfo allocCreateInfo{}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + + // We want to just write all at once. Using random access bit could make this slow + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + + // parameters of a vmaAllocation retrieved via vmaGetAllocationInfo + VmaAllocationInfo allocInfo; + + // results of allocation + VmaAllocation stagingAlloc; + + // To get around VMA's usage for C version of vulkan, create a temp first..., + VkBuffer tempBuffer{}; + + // Create the buffer... + vmaCreateBuffer(vmaAllocator, + &bufferInfo.operator VkBufferCreateInfo & (), // TODO: Verify if this works (can use renderdoc to check buffer variables?) + &allocCreateInfo, + &tempBuffer, &stagingAlloc, &allocInfo); + + // then assign it to the hpp version + stagingBuffer = tempBuffer; + + // Just map, copy then unmap + void* stagingBufferMappedPtr = nullptr; + vmaMapMemory(vmaAllocator, stagingAlloc, &stagingBufferMappedPtr); + + if (stagingBufferMappedPtr) + std::memcpy(static_cast(stagingBufferMappedPtr), static_cast(data), srcSize); + + const VkDeviceSize offsets = 0; + const VkDeviceSize sizes = srcSize; + vmaFlushAllocations(vmaAllocator, 1, &stagingAlloc, &offsets, &sizes); + + vmaUnmapMemory(vmaAllocator, stagingAlloc); + } + + /***************************************************************************/ + /*! + + \brief + Default ctor. Initializes everything to 0 or false. + + */ + /***************************************************************************/ + SHVkBuffer::SHVkBuffer(std::reference_wrapper allocator) noexcept + : vkBuffer{} + , stagingBuffer{} + , sizeStored{ 0 } + , mappedPtr{ nullptr } + , alloc {nullptr} + , randomAccessOptimized{false} + , boundToCoherent {false} + , vmaAllocator{allocator} + { + + } + + SHVkBuffer::SHVkBuffer( + uint32_t inSize, + void* data, + uint32_t srcSize, + std::reference_wrapper allocator, + vk::BufferUsageFlags bufferUsage, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags + ) noexcept + : SHVkBuffer(allocator) + { + Init(inSize, data, srcSize, bufferUsage, memUsage, allocFlags); + } + + SHVkBuffer::SHVkBuffer(SHVkBuffer&& rhs) noexcept + : vkBuffer{std::move (rhs.vkBuffer)} + , stagingBuffer{ std::move (rhs.stagingBuffer)} + , sizeStored{ std::move (rhs.sizeStored) } + , mappedPtr{ nullptr } + , alloc{ std::move (rhs.alloc) } + , randomAccessOptimized{ rhs.randomAccessOptimized } + , boundToCoherent{ rhs.boundToCoherent} + , vmaAllocator{ rhs.vmaAllocator } + , bufferUsageFlags {rhs.bufferUsageFlags} + + { + rhs.vkBuffer = VK_NULL_HANDLE; + } + + SHVkBuffer& SHVkBuffer::operator=(SHVkBuffer&& rhs) noexcept + { + if (&rhs == this) + return *this; + + vkBuffer = std::move(rhs.vkBuffer); + stagingBuffer = std::move(rhs.stagingBuffer); + sizeStored = std::move(rhs.sizeStored); + mappedPtr = nullptr; + alloc = std::move(rhs.alloc); + randomAccessOptimized = rhs.randomAccessOptimized; + boundToCoherent = rhs.boundToCoherent; + vmaAllocator = std::move (rhs.vmaAllocator); + rhs.vkBuffer = VK_NULL_HANDLE; + bufferUsageFlags = rhs.bufferUsageFlags; + + return *this; + } + + SHVkBuffer::~SHVkBuffer(void) noexcept + { + Destroy(); + } + + /***************************************************************************/ + /*! + + \brief + Initialize function. Mainly creates a vulkan buffer bound to a certain + region in a VMA memory chunk based on the size passed in. What memory + chunk this buffer is bound to is also dependent on the flag. + + \param inSize + The size of the buffer. + + \param data + Optional data for the user to pass in to perform immediate data transfer. + + \param srcSize + Amount of (optional) data to copy to the buffer. + + \param vmaAllocator + VMA allocator required for allocation to occur. + + \param bufferUsage + Simple and easy. Can be used as a combination + (e.g. eVertexBuffer | eTransferDst). + + \param memUsage + Usually set to VMA_MEMORY_USAGE_AUTO. + + \param allocFlags + Most complicated parameter. Using VMA_RANDOM_ACCESS or VMA_SEQUENTIAL + would make the buffer bind to HOST_VISIBLE memory. Using + VMA_ALLOCATION_CREATE_MAPPED_BIT will map a pointer immediately (TODO: + Need to check if this bounds the buffer to coherent virtual memory. To + make this task simple, remember to check every instance of a VMA flag, + that the Vulkan flags are set accordingly: like if with sequential, + will the coherent bit be set). + + */ + /***************************************************************************/ + void SHVkBuffer::Init ( + uint32_t inSize, + void* data, + uint32_t srcSize, + vk::BufferUsageFlags bufferUsage, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags + ) noexcept + { + sizeStored = inSize; + + // For creation of buffer + vk::BufferCreateInfo bufferInfo{}; + + // initialize size and usage (vertex, index, uniform, etc) + bufferInfo.size = sizeStored; + bufferInfo.usage = bufferUsage; + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + + // Prepare allocation parameters for call to create buffers later + VmaAllocationCreateInfo allocCreateInfo{}; + allocCreateInfo.usage = memUsage; + + // If vma allocation flags include dedicated bit, immediately activate dst bit + if (allocCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) + bufferInfo.usage |= vk::BufferUsageFlagBits::eTransferDst; + + allocCreateInfo.flags = allocFlags; + + // parameters of a vmaAllocation retrieved via vmaGetAllocationInfo + VmaAllocationInfo allocInfo; + + // To get around VMA's usage for C version of vulkan, create a temp first..., + VkBuffer tempBuffer{}; + + // Create the buffer... + auto result = vmaCreateBuffer(vmaAllocator, + &bufferInfo.operator VkBufferCreateInfo &(), + &allocCreateInfo, + &tempBuffer, &alloc, &allocInfo); + + if (result != VK_SUCCESS) + SHVulkanDebugUtil::ReportVkError(vk::Result (result), "Failed to create vulkan buffer. "); + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created buffer. "); + + // ...then assign it to the hpp version + vkBuffer = tempBuffer; + + // Set the buffer flags + bufferUsageFlags = bufferInfo.usage; + + // This probably means that a HOST_CACHED memory type is used on allocation + if (allocFlags & VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT) + randomAccessOptimized = true; + + /* There are 3 instances where buffers can be a GPU only resource : + 1. When its for a DEDICATED large chunk of memory that is usually not touch ever again + like images that get destroyed and recreated every time window resizes. + 2. When its for a memory type that is BOTH HOST_VISIBLE and DEVICE_LOCAL. This is known + base address register (BAR). If this address space is not available (which it should be on + most machines), fall back to use DEVICE_LOCAL memory and do the usual staging buffer memory transfer to + GPU resource. + 3. When the buffer is initialized with a usage dst bit. + */ + + // Get the memory property flags + VkMemoryPropertyFlags memPropFlags; + vmaGetAllocationMemoryProperties(vmaAllocator, alloc, &memPropFlags); + + // mainly host visible. Can be cached (need to flush/invalidate), uncached (always coherent) and coherent (virtual). + if(memPropFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) + { + // If memory is marked to be coherent between CPU and GPU (no need flush/invalidate) (TODO: Verify if VMA_ALLOCATION_CREATE_MAPPED_BIT is used when VMA_MEMORY_USAGE_AUTO is set) + // TODO: also verify that coherent bit = pointer is already mapped + if (memPropFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) + { + boundToCoherent = true; + mappedPtr = allocInfo.pMappedData; + } + else + mappedPtr = nullptr; + + if (data) + MapWriteUnmap(data, srcSize, 0, 0); + } + else + { + // We can prep first so that we can do transfers later via 1 cmd buffer recording + PrepStagingBuffer(data, srcSize); + + // #NoteToSelf: Command buffers used to be an optional argument to perform the transfer immediately but it was removed because we + // don't want to allow such scenarios. Ideally, we want users to be transferring data to device memory together with + // other buffers that perform similar transfers. + //if (cmdBufferHdl) // check for null handle + //{ + // // case 1 and case 3 + // if (bufferInfo.usage & vk::BufferUsageFlagBits::eTransferDst) + // { + // TransferToDeviceResource(cmdBufferHdl); + // } + // //case 2 + // else if (allocCreateInfo.flags & VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT) + // { + // // Need to check if BAR exists, if it doesn't, fall back to DEVICE_LOCAL, and do regular explicit transfers from staging + // } + //} + } + } + + /***************************************************************************/ + /*! + + \brief + Destroys the buffer. + + \param allocator + VMA allocator required to destroy the buffer. + + */ + /***************************************************************************/ + void SHVkBuffer::Destroy(void) noexcept + { + vmaDestroyBuffer(vmaAllocator, vkBuffer, alloc); + } + +} diff --git a/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h new file mode 100644 index 00000000..2ad3e4e9 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h @@ -0,0 +1,106 @@ +#ifndef SH_VK_BUFFER_H +#define SH_VK_BUFFER_H + +#include "Graphics/SHVulkanIncludes.h" +#include "vk_mem_alloc.h" +#include "Resource/Handle.h" + +namespace SHADE +{ + // Mainly so that middle end doesn't need to call "vk::" + //using SHVkBufferUsage = vk::BufferUsageFlags; + //using SHVkBufferUsageBits = vk::BufferUsageFlagBits; + + class SHVkCommandBuffer; + + class SHVkBuffer + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Vulkan handle to a vkBuffer + vk::Buffer vkBuffer; + + //! When the buffer initialized to be a GPU only resource, this buffer will + //! be necessary to perform transfer to GPU either during initialization + //! or whenever the user wants it. + vk::Buffer stagingBuffer; + + //! The amount of memory this buffer is bound to in device/host memory + uint32_t sizeStored; + + //! Persistently mapped pointer if applicable (will be void if buffer is + //! not created with the correct flags). Note that this is only used for + //! persistent mapping. One time updates do not use this pointer. + void* mappedPtr; + + //! allocation object containing details of an allocation + VmaAllocation alloc; + + //! If initialized with vma random access flag, this is true + bool randomAccessOptimized; + + //! Whether or not this buffer is bound to coherent memory + bool boundToCoherent; + + //! buffer usage info flags + vk::BufferUsageFlags bufferUsageFlags; + + //! Reference to the allocator + //VmaAllocator const& vmaAllocator; + std::reference_wrapper vmaAllocator; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void PrepStagingBuffer (void* data, uint32_t srcSize) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHVkBuffer (void) noexcept = delete; + SHVkBuffer (std::reference_wrapper allocator) noexcept; + SHVkBuffer ( + uint32_t inSize, + void* data, + uint32_t srcSize, + std::reference_wrapper allocator, + vk::BufferUsageFlags bufferUsage, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags + ) noexcept; + SHVkBuffer(SHVkBuffer&& rhs) noexcept; + SHVkBuffer& operator=(SHVkBuffer&& rhs) noexcept; + ~SHVkBuffer(void) noexcept; + + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + void Init ( + uint32_t inSize, + void* data, + uint32_t srcSize, + vk::BufferUsageFlags bufferUsage, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags + ) noexcept; + void Destroy (void) noexcept; + + void Map (void) noexcept; + void Unmap (void) noexcept; + void WriteToMemory (void* data, uint32_t sizeToWrite, uint32_t srcOffset, uint32_t dstOffset) noexcept; + void MapWriteUnmap (void* data, uint32_t sizeToWrite, uint32_t srcOffset, uint32_t dstOffset) noexcept; + void TransferToDeviceResource(Handle const& cmdBufferHdl) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + vk::Buffer GetVkBuffer (void) const noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Commands/SHCommandPoolResetMode.h b/SHADE_Engine/src/Graphics/Commands/SHCommandPoolResetMode.h new file mode 100644 index 00000000..4ecc0098 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Commands/SHCommandPoolResetMode.h @@ -0,0 +1,13 @@ +#ifndef SH_COMMAND_POOL_RESET_H +#define SH_COMMAND_POOL_RESET_H + +namespace SHADE +{ + enum class SH_CMD_POOL_RESET + { + POOL_BASED, + BUFFER_BASED, + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp new file mode 100644 index 00000000..6a307230 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp @@ -0,0 +1,641 @@ +#include "SHPch.h" +#include "SHVkCommandBuffer.h" +#include "SHVkCommandPool.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "SHVkCommandPool.h" +#include "Tools/SHLogger.h" +#include "Graphics/Renderpass/SHVkRenderpass.h" +#include "Graphics/Framebuffer/SHVkFramebuffer.h" +#include "Graphics/Pipeline/SHVkPipeline.h" +#include "Graphics/Buffers/SHVkBuffer.h" + + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Frees the command buffer. + + */ + /***************************************************************************/ + SHVkCommandBuffer::~SHVkCommandBuffer(void) noexcept + { + if (vkCommandBuffer) + parentPool->GetLogicalDevice()->GetVkLogicalDevice().freeCommandBuffers(parentPool->GetVkCommandPool(), commandBufferCount, &vkCommandBuffer); + } + + /***************************************************************************/ + /*! + + \brief + Only the command buffer is allocated using + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT bit, is resetting + individually permitted. Otherwise, throw exception. IMPORTANT NOTE: + the command buffer cannot be in the pending state!!! + + */ + /***************************************************************************/ + void SHVkCommandBuffer::Reset(void) + { + if (cmdBufferState == SH_CMD_BUFFER_STATE::PENDING) + { + SHLOG_ERROR("Command buffer in pending state, could not reset. "); + return; + } + + if (parentPool->GetPoolResetMode() != SH_CMD_POOL_RESET::BUFFER_BASED) + { + SHLOG_ERROR("Parent Command Pool was not initialized with VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT. Cannot reset independently. "); + return; + } + + vkCommandBuffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources); + + if (cmdBufferState == SH_CMD_BUFFER_STATE::RECORDING || cmdBufferState == SH_CMD_BUFFER_STATE::EXECUTABLE) + { + cmdBufferState = SH_CMD_BUFFER_STATE::INVALID; + return; + } + + if (cmdBufferState != SH_CMD_BUFFER_STATE::INVALID) + cmdBufferState = SH_CMD_BUFFER_STATE::INITIAL; + } + + /***************************************************************************/ + /*! + + \brief + Begins the command buffer. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::BeginRecording(void) noexcept + { + // Check if command buffer is ready to record. + if (cmdBufferState != SH_CMD_BUFFER_STATE::INITIAL) + { + SHLOG_ERROR("Command buffer not in initial state, cannot begin recording. "); + return; + } + + // Struct for recording + vk::CommandBufferBeginInfo beginInfo{}; + + beginInfo.flags = usageFlags; + // TODO: Allow passing in inheritance info. + + // Attempt to begin recording + if (auto result = vkCommandBuffer.begin(&beginInfo); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to begin command buffer. "); + return; + } + + // We don't need to print record success + //else + // SHVulkanDebugUtil::ReportVkSuccess("Command buffer successfully begun recording... "); + + // Set the state to recording if the call above succeeded. + cmdBufferState = SH_CMD_BUFFER_STATE::RECORDING; + } + + /***************************************************************************/ + /*! + + \brief + End the recording of a command buffer. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::EndRecording(void) noexcept + { + if (cmdBufferState != SH_CMD_BUFFER_STATE::RECORDING) + { + SHLOG_ERROR("Command Buffer not in recording state, cannot end recording. "); + return; + } + + vkCommandBuffer.end(); + cmdBufferState = SH_CMD_BUFFER_STATE::EXECUTABLE; + } + + /***************************************************************************/ + /*! + + \brief + Begins a renderpass in the command buffer. 2 important things to note + here, the command buffer used MUST be a primary command buffer and + command buffer MUST be in a recording state. + + \param renderpassHdl + Renderpass for obvious reasons. + + \param framebufferHdl + Framebuffer required in the begin info. + + \param offset + Offset of the render area in the framebuffer. + + \param extent + Extent of the render area in the framebuffer. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::BeginRenderpass(Handle const& renderpassHdl, Handle const& framebufferHdl, vk::Offset2D offset, vk::Extent2D extent) noexcept + { + // cannot begin renderpass if command buffer is primary + if (commandBufferType != SH_CMD_BUFFER_TYPE::PRIMARY) + { + SHLOG_ERROR("Cannot begin renderpass. Command buffer is not a primary command buffer. "); + return; + } + + // cannot begin renderpass if command buffer is not recording + if (cmdBufferState != SH_CMD_BUFFER_STATE::RECORDING) + { + SHLOG_ERROR("Command buffer must have started recording before a renderpass instance can begin. "); + return; + } + + vk::Extent2D framebufferExtent{ framebufferHdl->GetWidth(), framebufferHdl->GetHeight() }; + + // Prepare renderpass begin + vk::RenderPassBeginInfo renderPassInfo{}; + renderPassInfo.renderPass = renderpassHdl->GetVkRenderpass(); + renderPassInfo.framebuffer = framebufferHdl->GetVkFramebuffer(); + + // If the extent passed in is 0, use the framebuffer dimensions instead. + if (extent.width == 0 && extent.height == 0) + renderPassInfo.renderArea.extent = framebufferExtent; + else + renderPassInfo.renderArea.extent = extent; + + // assign offset + renderPassInfo.renderArea.offset = offset; + + // prepare clear colors + auto const& clearColors = renderpassHdl->GetClearColors(); + renderPassInfo.clearValueCount = static_cast(clearColors.size()); + renderPassInfo.pClearValues = clearColors.data(); + + // Check if render area is optimal + if (!IsRenderAreaOptimal(renderpassHdl, framebufferExtent, renderPassInfo.renderArea)) + SHLOG_ERROR("Render area in renderpass begin info is not optimal. See Vulkan vkGetRenderAreaGranularity for details."); + + // Begin the render pass + vkCommandBuffer.beginRenderPass (&renderPassInfo, vk::SubpassContents::eInline); + + } + + /***************************************************************************/ + /*! + + \brief + Ends a renderpass. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::EndRenderpass(void) noexcept + { + vkCommandBuffer.endRenderPass(); + } + + /***************************************************************************/ + /*! + + \brief + Sets the viewport dynamically for the command buffer. #NoteToSelf: + Dynamic state will not affect pipelines that don't use dynamic state + so there isn't a need to do any checks. Also, setting dynamic state like + this only needs to happen ONCE per command buffer UNLESS a different + viewport is to be used for different drawing commands. + + \param vpWidth + viewport width + + \param vpHeight + viewport height + + \param sWidth + Scissor extent width + + \param sHeight + Scissor extent height + + \param vpX + Viewport offset X. + + \param vpY + Viewport offset Y. + + \param sX + Scissor offset X. + + \param sY + Scissor offset Y. + + \param vpMinDepth + viewport minimum depth value + + \param vpMaxDepth + viewport maximum depth value + + + */ + /***************************************************************************/ + void SHVkCommandBuffer::SetviewportScissor(float vpWidth, float vpHeight, uint32_t sWidth, uint32_t sHeight, float vpX /*= 0.0f*/, float vpY /*= 0.0f*/, int32_t sX /*= 0.0f*/, int32_t sY /*= 0.0f*/, float vpMinDepth /*= 0.0f*/, float vpMaxDepth /*= 1.0f*/) noexcept + { + vk::Viewport dynamicViewport + { + .x = vpX, + .y = vpY, + .width = vpWidth, + .height = vpHeight, + .minDepth = vpMinDepth, + .maxDepth = vpMaxDepth, + }; + + vk::Rect2D dynamicScissor + { + .offset = vk::Offset2D{sX, sY}, + .extent = vk::Extent2D{sWidth, sHeight} + }; + + // Dynamic state set viewport and scissor + vkCommandBuffer.setScissor(0, 1, &dynamicScissor); + vkCommandBuffer.setViewport(0, 1, &dynamicViewport); + + } + + /***************************************************************************/ + /*! + + \brief + Binds a pipeline object to the command buffer. + + \param pipelineHdl + The pipeline to bind. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::BindPipeline(Handle const& pipelineHdl) noexcept + { + if (cmdBufferState != SH_CMD_BUFFER_STATE::RECORDING) + { + SHLOG_ERROR("Command buffer must have started recording before a pipeline can be bound. "); + return; + } + boundPipelineLayoutHdl = pipelineHdl->GetPipelineLayout(); + vkCommandBuffer.bindPipeline(pipelineHdl->GetPipelineBindPoint(), pipelineHdl->GetVkPipeline()); + } + + /***************************************************************************/ + /*! + + \brief + Binds a buffer to the vertex buffer binding point specified in + bindingPoint. + + \param bindingPoint + The binding point to initialize a vertex + + \param buffer + The buffer to bind + + \param offset + The offset to start from in the buffer for the first binding point. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::BindVertexBuffer (uint32_t bindingPoint, Handle const& buffer, vk::DeviceSize offset) noexcept + { + if (cmdBufferState == SH_CMD_BUFFER_STATE::RECORDING) + { + auto bufferHandle = buffer->GetVkBuffer(); + vkCommandBuffer.bindVertexBuffers (bindingPoint, 1, &bufferHandle, &offset); + } + } + + /***************************************************************************/ + /*! + + \brief + Binds an index buffer to the pipeline. + + \param buffer + The buffer to bind. + + \param startingIndex + The starting index in the index buffer. For example, 0 would mean + starting at the beginning. 5 would mean starting at byte offset + size(uint32_t) * 5. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::BindIndexBuffer(Handle const& buffer, uint32_t startingIndex) const noexcept + { + if (cmdBufferState == SH_CMD_BUFFER_STATE::RECORDING) + { + auto bufferHandle = buffer->GetVkBuffer(); + vkCommandBuffer.bindIndexBuffer (bufferHandle, sizeof (uint32_t) * startingIndex, vk::IndexType::eUint32); + } + } + + /***************************************************************************/ + /*! + + \brief + Calls vkCmdDraw. + + \param vertexCount + How many vertices to draw + + \param instanceCount + Number of instances to draw + + \param firstVertex + First vertex in the buffer of vertices to start from + + \param firstInstance + First instance to start from. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::DrawArrays(uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance) const noexcept + { + if (cmdBufferState != SH_CMD_BUFFER_STATE::RECORDING) + { + SHLOG_ERROR("Command buffer must have started recording before a pipeline can be bound. "); + return; + } + vkCommandBuffer.draw (vertexCount, instanceCount, firstVertex, firstInstance); + } + + /***************************************************************************/ + /*! + + \brief + Issues a non-instanced indexed draw call. + + \param indexCount + Number of indices to draw. + + \param firstIndex + Starting index. if the array was 0, 2, 5, 4, and we indicated this to be + 1. The draw call would start from index 2. + + \param vertexOffset + Starting vertex offset. This would indicate that vertex pulling should + start from a certain vertex. So a vertex offset of 3 (for example) would + mean an index of 0 would mean the 3rd vertex. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::DrawIndexed (uint32_t indexCount, uint32_t firstIndex, uint32_t vertexOffset) const noexcept + { + if (cmdBufferState != SH_CMD_BUFFER_STATE::RECORDING) + { + SHLOG_ERROR("Command buffer must have started recording before a pipeline can be bound. "); + return; + } + + // not an instanced call so we want to make the instanced related stuff hard coded + vkCommandBuffer.drawIndexed(indexCount, 1, firstIndex, vertexOffset, 0); + + } + + /***************************************************************************/ + /*! + + \brief + Calls vkCmdPushConstants and submits data stored in command buffer. + + */ + /***************************************************************************/ + void SHVkCommandBuffer::SubmitPushConstants(void) const noexcept + { + vkCommandBuffer.pushConstants(boundPipelineLayoutHdl->GetVkPipelineLayout(), + boundPipelineLayoutHdl->GetPushConstantInterface().GetShaderStageFlags(), + 0, + boundPipelineLayoutHdl->GetPushConstantInterface().GetSize(), pushConstantData); + } + + /***************************************************************************/ + /*! + + \brief + Simply returns the command buffer handle. + + \return + The command buffer handle. + + */ + /***************************************************************************/ + vk::CommandBuffer const& SHVkCommandBuffer::GetVkCommandBuffer(void) const noexcept + { + return vkCommandBuffer; + } + + /***************************************************************************/ + /*! + + \brief + See https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetRenderAreaGranularity.html + or look up vkGetRenderAreaGranularity. + + \param renderpassHdl + Renderpass to get info from. + + \param framebufferExtent + For the comparison. Again, look it up on the webpage. + + \param renderArea + For the comparison. Again, look it up on the webpage. + + \return + If optimal, true. otherwise false. + + */ + /***************************************************************************/ + bool SHVkCommandBuffer::IsRenderAreaOptimal(Handle const& renderpassHdl, vk::Extent2D const& framebufferExtent, vk::Rect2D const& renderArea) const noexcept + { + vk::Extent2D granularity = parentPool->GetLogicalDevice()->GetVkLogicalDevice().getRenderAreaGranularity(renderpassHdl->GetVkRenderpass()); + + return (renderArea.offset.x % granularity.width == 0 && renderArea.offset.y % granularity.height == 0 && + (renderArea.extent.width % granularity.width || renderArea.offset.x + renderArea.extent.width == framebufferExtent.width) && + (renderArea.extent.height % granularity.height || renderArea.offset.y + renderArea.extent.height == framebufferExtent.height)); + } + + /***************************************************************************/ + /*! + + \brief + Setter for the state of the command buffer. + + \param state + + \return + + */ + /***************************************************************************/ + void SHVkCommandBuffer::SetState(SH_CMD_BUFFER_STATE state) noexcept + { + cmdBufferState = state; + } + + /***************************************************************************/ + /*! + + \brief + Returns the state of the command buffer. + + \return + + */ + /***************************************************************************/ + SH_CMD_BUFFER_STATE SHVkCommandBuffer::GetState(void) const noexcept + { + return cmdBufferState; + } + + /***************************************************************************/ + /*! + + \brief + Creates a command buffer. Cmd buffer can be primary or secondary. If + secondary, flags will automatically have renderpass continue bit. Command + pool used to create this command buffer will determine whether or not + this buffer will be allocated with + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT using the reset mode. + + \param logicalDevice + Need a logical device to create a buffer. + + \param commandPool + Command buffer to allocate from. + + \param type + Type of the command buffer; primary or secondary. + + */ + /***************************************************************************/ + SHVkCommandBuffer::SHVkCommandBuffer(Handle const& commandPool, SH_CMD_BUFFER_TYPE type) noexcept + : vkCommandBuffer{ nullptr } + , cmdBufferState{ SH_CMD_BUFFER_STATE::INVALID } // Command buffer will be in initial state + , commandBufferType{ SH_CMD_BUFFER_TYPE::PRIMARY } + , parentPoolResetMode{ SH_CMD_POOL_RESET::POOL_BASED } + , usageFlags{} + , commandBufferCount{ 0 } + , parentPool{commandPool} + + { + vk::CommandBufferAllocateInfo allocateInfo{}; + allocateInfo.commandPool = commandPool->GetVkCommandPool(); + + // don't know if there are performance implications if this is set to always be 1. Could be better to create more at once + allocateInfo.commandBufferCount = 1; + + // Set the type of command buffer (primary or secondary) + switch (type) + { + case SH_CMD_BUFFER_TYPE::PRIMARY: + allocateInfo.level = vk::CommandBufferLevel::ePrimary; + break; + case SH_CMD_BUFFER_TYPE::SECONDARY: + allocateInfo.level = vk::CommandBufferLevel::eSecondary; + break; + } + + // Attempt to allocate command buffers + if (auto result = commandPool->GetLogicalDevice()->GetVkLogicalDevice().allocateCommandBuffers(&allocateInfo, &vkCommandBuffer); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Command Buffer. "); + return; + } + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created Command Buffer."); + + // Set the state only after successfully allocating + cmdBufferState = SH_CMD_BUFFER_STATE::INITIAL; + + // Parent command pool + parentPool = commandPool; + + // Save some important info + parentPoolResetMode = parentPool->GetPoolResetMode(); + commandBufferType = type; + commandBufferCount = allocateInfo.commandBufferCount; + + if (parentPool->GetIsTransient ()) + usageFlags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; + + if (commandBufferType == SH_CMD_BUFFER_TYPE::SECONDARY) + usageFlags |= vk::CommandBufferUsageFlagBits::eRenderPassContinue; + + // Reset all the push constant data to 0 + memset (pushConstantData, 0, PUSH_CONSTANT_SIZE); + } + + /***************************************************************************/ + /*! + + \brief + Move ctor. Invalidates Vulkan handles. + + \param rhs + the other command buffer. + + */ + /***************************************************************************/ + SHVkCommandBuffer::SHVkCommandBuffer(SHVkCommandBuffer&& rhs) noexcept + : vkCommandBuffer {std::move (rhs.vkCommandBuffer)} + , cmdBufferState {rhs.cmdBufferState} + , commandBufferType {rhs.commandBufferType} + , parentPoolResetMode {rhs.parentPoolResetMode} + , usageFlags {rhs.usageFlags} + , commandBufferCount {rhs.commandBufferCount} + , parentPool {rhs.parentPool} + , boundPipelineLayoutHdl{rhs.boundPipelineLayoutHdl } + { + memcpy (pushConstantData, rhs.pushConstantData, PUSH_CONSTANT_SIZE); + + rhs.vkCommandBuffer = VK_NULL_HANDLE; + } + + /***************************************************************************/ + /*! + + \brief + Move assignment operator. Invalidates Vulkan handles. + + \param rhs + The other Vulkan Handle. + + \return + a reference itself. + + */ + /***************************************************************************/ + SHVkCommandBuffer& SHVkCommandBuffer::operator=(SHVkCommandBuffer&& rhs) noexcept + { + if (&rhs == this) + return *this; + + vkCommandBuffer = std::move(rhs.vkCommandBuffer); + cmdBufferState = rhs.cmdBufferState; + commandBufferType = rhs.commandBufferType; + parentPoolResetMode = rhs.parentPoolResetMode; + usageFlags = rhs.usageFlags; + commandBufferCount = rhs.commandBufferCount; + parentPool = rhs.parentPool; + boundPipelineLayoutHdl = rhs.boundPipelineLayoutHdl; + memcpy(pushConstantData, rhs.pushConstantData, PUSH_CONSTANT_SIZE); + + rhs.vkCommandBuffer = VK_NULL_HANDLE; + + return *this; + } + +} diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h new file mode 100644 index 00000000..08fc45f7 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h @@ -0,0 +1,134 @@ +#ifndef SH_COMMAND_BUFFER_H +#define SH_COMMAND_BUFFER_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Graphics/SHVulkanDefines.h" +#include "SHCommandPoolResetMode.h" +#include "Resource/ResourceLibrary.h" +#include "Graphics/Pipeline/SHVkPipelineLayout.h" + +namespace SHADE +{ + class SHVkCommandPool; + class SHVkLogicalDevice; + class SHVkRenderpass; + class SHVkFramebuffer; + class SHVkPipeline; + class SHVkBuffer; + + enum class SH_CMD_BUFFER_TYPE + { + PRIMARY, + SECONDARY, + }; + + enum class SH_CMD_BUFFER_STATE + { + INITIAL, // Can be moved to recording state or freed + RECORDING, // vkBeginCommandBuffer changes state from initial state to recording state. + EXECUTABLE, // Can be submitted, reset or recorded to another command buffer. + INVALID, // Enters invalid state when buffer created with ONE_TIME_SUBMIT_BIT finishes execution. Primaries also enter the invalid state when secondaries enter invalid or initial state + PENDING // Cmd buffer enters this state after being submitted to queue. + }; + + class SHVkCommandBuffer + { + friend class SHVkCommandPool; + friend class ResourceLibrary; + + static constexpr uint16_t PUSH_CONSTANT_SIZE = 512; + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Vulkan handle to a command buffer + vk::CommandBuffer vkCommandBuffer; + + //! state of the command buffer. TODO: Test some state changes. + SH_CMD_BUFFER_STATE cmdBufferState; + + //! Command buffer type + SH_CMD_BUFFER_TYPE commandBufferType; + + //! Stores the reset mode of the command pool. This is so that when we + //! try to reset command buffers individually, we want to do it only when + //! the reset mode is set to COMMAND_BUFFER_RESET only. + SH_CMD_POOL_RESET parentPoolResetMode; + + //! Flags of the command buffer (ONE_TIME_USE, RENDERPASS_CONTINUE, SIMULTANEOUS_USAGE) + vk::CommandBufferUsageFlags usageFlags; + + //! When allocating, you can specify how many command buffers to allocate + uint32_t commandBufferCount; + + //! The command pool that this command buffer belongs to + Handle parentPool; + + //! The currently bound pipeline + Handle boundPipelineLayoutHdl; + + //! The push constant data for the command buffer + uint8_t pushConstantData[PUSH_CONSTANT_SIZE]; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + bool IsRenderAreaOptimal(Handle const& renderpassHdl, vk::Extent2D const& framebufferExtent, vk::Rect2D const& renderArea) const noexcept; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE GETTERS AND SETTERS */ + /*-----------------------------------------------------------------------*/ + void SetState(SH_CMD_BUFFER_STATE state) noexcept; + SH_CMD_BUFFER_STATE GetState(void) const noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHVkCommandBuffer (Handle const& commandPool, SH_CMD_BUFFER_TYPE type) noexcept; + SHVkCommandBuffer (SHVkCommandBuffer&& rhs) noexcept; + SHVkCommandBuffer (void) noexcept = delete; + ~SHVkCommandBuffer (void) noexcept; + SHVkCommandBuffer& operator= (SHVkCommandBuffer&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void Reset(void); + + // Begins and Ends + void BeginRecording (void) noexcept; + void EndRecording (void) noexcept; + void BeginRenderpass (Handle const& renderpassHdl, Handle const& framebufferHdl, vk::Offset2D offset = {0, 0}, vk::Extent2D extent = {0, 0}) noexcept; + void EndRenderpass (void) noexcept; + + // Dynamic State + void SetviewportScissor (float vpWidth, float vpHeight, uint32_t sWidth, uint32_t sHeight, float vpX = 0.0f, float vpY = 0.0f, int32_t sX = 0.0f, int32_t sY = 0.0f, float vpMinDepth = 0.0f, float vpMaxDepth = 1.0f) noexcept; + + // Binding Commands + void BindPipeline (Handle const& pipelineHdl) noexcept; + void BindVertexBuffer (uint32_t bindingPoint, Handle const& buffer, vk::DeviceSize offset) noexcept; + void BindIndexBuffer (Handle const& buffer, uint32_t startingIndex) const noexcept; + + // Draw Commands + void DrawArrays (uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance) const noexcept; + void DrawIndexed (uint32_t indexCount, uint32_t firstIndex, uint32_t vertexOffset) const noexcept; + + // Push Constant variable setting + template + void SetPushConstantVariable(std::string variableName, T const& data) noexcept + { + memcpy (static_cast(pushConstantData) + boundPipelineLayoutHdl->GetPushConstantInterface().GetOffset(variableName), &data, sizeof (T)); + }; + + void SubmitPushConstants (void) const noexcept; + + /*-----------------------------------------------------------------------*/ + /* GETTERS AND SETTERS */ + /*-----------------------------------------------------------------------*/ + vk::CommandBuffer const& GetVkCommandBuffer(void) const noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.cpp b/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.cpp new file mode 100644 index 00000000..5cf4bea4 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.cpp @@ -0,0 +1,269 @@ +#include "SHPch.h" +#include "SHVkCommandPool.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Instance/SHVkInstance.h" +#include "Resource/ResourceLibrary.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Simply creates a command pool. Flags passed in are also saved. + + \param logicalDevice + Logical device required to create the pool + + \param queueFamilyType + Every pool needs to be created from a queue family + + \param inResetMode + Whether or not the reset bit that allows resetting command buffers + individually should be set or not. + + \param inTransient + Whether or not buffers created from the pool are short-lived. IT IS + STRONGLY RECOMMENDED for this to be true. From "Writing an efficient + Vulkan renderer: In general approaches that pre-record command buffers for + parts of the scene are counter-productive since they can result in + excessive GPU load due to inefficient culling required to keep command + buffer workload large and can trigger inefficient code paths on some + tiled renderers, and instead applications should focus on improving the + threading and draw call submission cost on the CPU. NOTE: it is possible + to create transient command buffers from pools that are BUFFER_BASED, + hence why POOL_BASED != transient. + + */ + /***************************************************************************/ + SHVkCommandPool::SHVkCommandPool(Handle const& inLogicalDeviceHdl, SH_QUEUE_FAMILY_ARRAY_INDEX queueFamilyType, SH_CMD_POOL_RESET inResetMode, bool inTransient) noexcept + : vkCommandPool{ nullptr } + , queueFamilyIndex{ 0 } + , resetMode{ SH_CMD_POOL_RESET::POOL_BASED } + , primaries{} + , secondaries{} + , logicalDeviceHdl { inLogicalDeviceHdl } + , transient {false} + { + // Command pool create info + vk::CommandPoolCreateInfo poolCreateInfo{}; + + // Get the queue family index based on the queue family type passed in + poolCreateInfo.queueFamilyIndex = logicalDeviceHdl->GetQueueFamilyIndex(queueFamilyType); + + if (inResetMode == SH_CMD_POOL_RESET::BUFFER_BASED) + poolCreateInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + + if (inTransient) + poolCreateInfo.flags |= vk::CommandPoolCreateFlagBits::eTransient; + + + // Actually create the command pool and check for errors + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createCommandPool(&poolCreateInfo, nullptr, &vkCommandPool); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Command Pool! "); + return; + } + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created Command Pool."); + + // Save the queue family index + queueFamilyIndex = poolCreateInfo.queueFamilyIndex; + + // Save the reset mode + resetMode = inResetMode; + transient = inTransient; + } + + SHVkCommandPool::SHVkCommandPool(SHVkCommandPool&& rhs) noexcept + : ISelfHandle(rhs) + , vkCommandPool{ rhs.vkCommandPool } + , queueFamilyIndex{ rhs.queueFamilyIndex } + , resetMode{ rhs.resetMode } + , primaries{ std::move(rhs.primaries) } + , secondaries{ std::move(rhs.secondaries) } + , logicalDeviceHdl{ rhs.logicalDeviceHdl } + , transient{ rhs.transient } + { + rhs.vkCommandPool = VK_NULL_HANDLE; + } + + SHVkCommandPool& SHVkCommandPool::operator=(SHVkCommandPool&& rhs) noexcept + { + if (&rhs == this) + return *this; + + vkCommandPool = rhs.vkCommandPool; + queueFamilyIndex = rhs.queueFamilyIndex; + resetMode = rhs.resetMode; + primaries = std::move(rhs.primaries); + secondaries = std::move(rhs.secondaries); + logicalDeviceHdl = rhs.logicalDeviceHdl; + transient = rhs.transient; + + static_cast&>(*this) = static_cast&>(rhs); + + rhs.vkCommandPool = VK_NULL_HANDLE; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Destroys the command pool. + + */ + /***************************************************************************/ + SHVkCommandPool::~SHVkCommandPool(void) noexcept + { + // TODO: Destroy all command buffers first. Should implement a check for command buffers that are still recording or pending (submitted to queue)? + if (vkCommandPool) + { + + for (auto& pri : primaries) + pri.Free(); + for (auto& sec : secondaries) + sec.Free(); + + vkDestroyCommandPool(logicalDeviceHdl->GetVkLogicalDevice(), vkCommandPool, nullptr); + } + } + + /***************************************************************************/ + /*! + + \brief + Resets the command pool. + + \param logicalDevice + Device required to reset the command pool. + + */ + /***************************************************************************/ + void SHVkCommandPool::Reset(void) noexcept + { + // TODO: hopefully some sort of validation layer pops up here when we attempt to reset while the command buffers + // are in the pending state. Need to do a test. + logicalDeviceHdl->GetVkLogicalDevice().resetCommandPool(vkCommandPool, vk::CommandPoolResetFlagBits::eReleaseResources); + for (auto& primary : primaries) + { + if (primary->GetState() != SH_CMD_BUFFER_STATE::PENDING) + primary->SetState(SH_CMD_BUFFER_STATE::INITIAL); + else + SHLOG_ERROR("Primary command buffer in pending state, could not reset. "); + // From the spec: Any primary command buffer allocated from another VkCommandPool that is in the recording or + // executable state and has a secondary command buffer allocated from commandPool recorded into it, + // becomes invalid. TODO: Might want to check and throw exception for these conditions after making sure this actually happens using validation layers. + } + for (auto& secondary : secondaries) + { + if (secondary->GetState() != SH_CMD_BUFFER_STATE::PENDING) + secondary->SetState(SH_CMD_BUFFER_STATE::INITIAL); + else + SHLOG_ERROR("Secondary command buffer in pending state, could not reset. "); + + // TODO: Ditto from TODO in primary check + } + } + + /***************************************************************************/ + /*! + + \brief + Request a command buffer from the command pool. User can choose for it + to be secondary or primary/ + + \param parentPool + This is solely for command buffers created to know what command pool + they originated from. + + \param type + The type of the command buffer. + + */ + /***************************************************************************/ + Handle SHVkCommandPool::RequestCommandBuffer(SH_CMD_BUFFER_TYPE type) + { + // If type requested for is primary + if (type == SH_CMD_BUFFER_TYPE::PRIMARY) + { + primaries.emplace_back(SHVkInstance::GetResourceManager().Create(GetHandle(), type)); + return primaries.back(); + } + // If type requested for is secondary + else + { + secondaries.emplace_back(SHVkInstance::GetResourceManager().Create(GetHandle(), type)); + return secondaries.back(); + } + } + + /***************************************************************************/ + /*! + + \brief + Get the reset mode of the command pool. + + \return + Returns the reset mode of the command pool. + + */ + /***************************************************************************/ + SH_CMD_POOL_RESET SHVkCommandPool::GetPoolResetMode(void) const noexcept + { + return resetMode; + } + + /***************************************************************************/ + /*! + + \brief + Returns the vulkan command pool handle. + + \return + The vulkan command pool handle. + + */ + /***************************************************************************/ + vk::CommandPool SHVkCommandPool::GetVkCommandPool(void) const noexcept + { + return vkCommandPool; + } + + /***************************************************************************/ + /*! + + \brief + Returns the logical device handle stored in the command pool. + + \return + The logical device handle. + + */ + /***************************************************************************/ + Handle const& SHVkCommandPool::GetLogicalDevice(void) const noexcept + { + return logicalDeviceHdl; + } + + /***************************************************************************/ + /*! + + \brief + Returns whether or not the buffers created from this pool are + short-lived. + + \return + The transient flag. + + */ + /***************************************************************************/ + bool SHVkCommandPool::GetIsTransient(void) const noexcept + { + return transient; + } + +} diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.h b/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.h new file mode 100644 index 00000000..2bb290a7 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandPool.h @@ -0,0 +1,73 @@ +#ifndef SH_COMMAND_POOL_H +#define SH_COMMAND_POOL_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Graphics/SHVulkanDefines.h" +#include "Graphics/Queues/SHVkQueue.h" +#include "SHCommandPoolResetMode.h" +#include "SHVkCommandBuffer.h" +#include "Resource/ResourceLibrary.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + + class SHVkCommandPool : public ISelfHandle + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Vulkan handle to a command pool + vk::CommandPool vkCommandPool; + + //! The queue family index that was used to initialize this pool + SHQueueFamilyIndex queueFamilyIndex; + + //! The reset mode of the command buffer. For optimal usage, make the reset + //! mode POOL_BASED so that pools can recycle buffer memory all at once + //! instead of resetting 1 by 1. In general, we also want to re-record + //! buffers every frame instead of pre-recording, so resetting pools each + //! frame supports this concept. + SH_CMD_POOL_RESET resetMode; + + //! primary command buffers + std::vector> primaries; + + //! secondary command buffers + std::vector> secondaries; + + //! Handle to logical device for convenience + Handle logicalDeviceHdl; + + //! Whether or not buffers created from here are to be short lived + bool transient; + + public: + /*-----------------------------------------------------------------------*/ + /* CTOR AND DTOR */ + /*-----------------------------------------------------------------------*/ + SHVkCommandPool (Handle const& inLogicalDeviceHdl, SH_QUEUE_FAMILY_ARRAY_INDEX queueFamilyType, SH_CMD_POOL_RESET inResetMode, bool inTransient) noexcept; + SHVkCommandPool (void) noexcept = delete; + SHVkCommandPool(SHVkCommandPool&& rhs) noexcept; + SHVkCommandPool& operator=(SHVkCommandPool&& rhs) noexcept; + ~SHVkCommandPool(void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void Reset (void) noexcept; + Handle RequestCommandBuffer (SH_CMD_BUFFER_TYPE type); + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + SH_CMD_POOL_RESET GetPoolResetMode(void) const noexcept; + vk::CommandPool GetVkCommandPool(void) const noexcept; + Handle const& GetLogicalDevice(void) const noexcept; + bool GetIsTransient (void) const noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Debugging/SHValidationLayersQuery.cpp b/SHADE_Engine/src/Graphics/Debugging/SHValidationLayersQuery.cpp new file mode 100644 index 00000000..420fa9e5 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Debugging/SHValidationLayersQuery.cpp @@ -0,0 +1,86 @@ +#include "SHPch.h" +#include "SHValidationLayersQuery.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /*-------------------------------------------------------------------------*/ + /* STATIC VARIABLE DECLARATION */ + /*-------------------------------------------------------------------------*/ + std::vector SHValidationLayersQuery::queriedLayers; + std::vector SHValidationLayersQuery::availableLayers; + + // TODO: Not sure if necessary but it would be nice to load validation layers + // from a file. + std::vector SHValidationLayersQuery::requiredLayers = + { + "VK_LAYER_KHRONOS_validation" // standard validation layer + }; + + + /***************************************************************************/ + /*! + + \brief + Uses the hard coded required layer above to check against the validation + layers retrieved upon querying from Vulkan. The final list of layers are + ones that are both in the required container and the queried container. + + \param printInfo + If this is true, validation layer names are printed. + + \param renderdocEnabled + Pushes a validation layer for renderdoc support if true. + + */ + /***************************************************************************/ + void SHValidationLayersQuery::GenerateAvailableLayers(bool renderdocEnabled) noexcept + { + availableLayers.clear(); + + // Get the layers from vulkan + queriedLayers = vk::enumerateInstanceLayerProperties(); + + // Cherrypick those we want from the required list + for (char const* const requiredLayer : requiredLayers) + { + bool layerFound = false; + for (auto const& layer : queriedLayers) + { + if (strcmp (requiredLayer, layer.layerName) == 0) + { + availableLayers.push_back(requiredLayer); + layerFound = true; + break; + } + } + + if (!layerFound) + { + SHLOG_ERROR(std::string("validation Layer: ") + requiredLayer + std::string ("could not be found. ")); + } + } + + // if renderdoc is requested, add to validation layer. + if (renderdocEnabled) + availableLayers.push_back("VK_LAYER_RENDERDOC_Capture"); + } + + /***************************************************************************/ + /*! + + \brief + Gets the available layers after using QueryAvailableLayers. + + \return + Returns the available layers. + + + */ + /***************************************************************************/ + std::vector const& SHValidationLayersQuery::GetAvailableLayers(void) noexcept + { + return availableLayers; + } + +} diff --git a/SHADE_Engine/src/Graphics/Debugging/SHValidationLayersQuery.h b/SHADE_Engine/src/Graphics/Debugging/SHValidationLayersQuery.h new file mode 100644 index 00000000..555dc720 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Debugging/SHValidationLayersQuery.h @@ -0,0 +1,57 @@ +/**************************************************************************//*! +\file SHValidationLayersQuery.h +\author Brandon Mak +\par email: brandon.hao@digipen.edu +\date 17th May 2022 +\brief Stores the declaration of SHValidationLayersQuery class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior +written consent of DigiPen Institute of Technology is prohibited. + +*//***************************************************************************/ + +#ifndef SH_VALIDATION_LAYERS_QUERY_H +#define SH_VALIDATION_LAYERS_QUERY_H + +#include // std::vector +#include "Graphics/SHVulkanIncludes.h" + +namespace SHADE +{ + /*!************************************************************************** + + \class SHValidationLLayersQuery + + \brief + Static class to query for validation layers. Only used during Vulkan + instance creation to enable validation layers. + + ***************************************************************************/ + class SHValidationLayersQuery + { + private: + /*-----------------------------------------------------------------------*/ + /* STATIC VARIABLES */ + /*-----------------------------------------------------------------------*/ + + //! required validation layers + static std::vector requiredLayers; + + //! Queried layers + static std::vector queriedLayers; + + //! Final list that should be used to see what validation layers are available + static std::vector availableLayers; + + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC STATIC FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + static void GenerateAvailableLayers (bool renderdocEnabled) noexcept; + static std::vector const& GetAvailableLayers (void) noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.cpp b/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.cpp new file mode 100644 index 00000000..d5f0dfe0 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.cpp @@ -0,0 +1,84 @@ +#include "SHPch.h" +#include +#include "SHVkDebugMessenger.h" +#include "SHVulkanDebugUtil.h" +#include "Graphics/Instance/SHVkInstance.h" +#include "Tools/SHLogger.h" +//#include "Tools/SHLogger.h" + +namespace SHADE +{ + + /***************************************************************************/ + /*! + + \brief + Initializes a struct for debug messenger creation. + + \param[in, out] createInfo + The create info to initialize. + + \param severityFlags + The severity flags we want to have for the messenger. + + \param typeFlags + The type flags we want to have for the messenger. + + */ + /***************************************************************************/ + void SHVkDebugMessenger::InitializeDebugCreateInfo(vk::DebugUtilsMessengerCreateInfoEXT& createInfo, + VkDebugUtilsMessageSeverityFlagsEXT severityFlags, + VkDebugUtilsMessageTypeFlagsEXT typeFlags) noexcept + { + createInfo.messageSeverity = vk::DebugUtilsMessageSeverityFlagsEXT (severityFlags); + createInfo.messageType = vk::DebugUtilsMessageTypeFlagsEXT (typeFlags); + createInfo.pfnUserCallback = SHVulkanDebugUtil::GenericDebugCallback; + createInfo.pNext = nullptr; + } + + /***************************************************************************/ + /*! + + \brief + Does everything required to create a debug messenger. This includes its + create info and calling the function to create a debug messenger. + + \param vulkanInstance + Instance is required to create messenger. + + */ + /***************************************************************************/ + void SHVkDebugMessenger::Initialize(void) noexcept + { + vk::DebugUtilsMessengerCreateInfoEXT debMsgCreateInfo; + InitializeDebugCreateInfo(debMsgCreateInfo, + GenMessengerSeverity(SH_DEBUG_MSG_SEV::S_VERBOSE, SH_DEBUG_MSG_SEV::S_WARNING, SH_DEBUG_MSG_SEV::S_ERROR), + GenMessengerType(SH_DEBUG_MSG_TYPE::T_GENERAL, SH_DEBUG_MSG_TYPE::T_VALIDATION, SH_DEBUG_MSG_TYPE::T_PERFORMANCE)); + + if (vk::Result result = SHVkInstance::GetVkInstance().createDebugUtilsMessengerEXT(&debMsgCreateInfo, nullptr, &debugMessenger); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Debug Messenger! "); + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created a Debug Messenger. "); + } + } + + /***************************************************************************/ + /*! + + \brief + Simply calls a Vulkan function to destroy the debug messenger. + + \param vulkanInstance + Instance is required to destroy messenger. + + */ + /***************************************************************************/ + void SHVkDebugMessenger::Destroy(void) noexcept + { + SHVkInstance::GetVkInstance().destroyDebugUtilsMessengerEXT(debugMessenger, nullptr); + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.h b/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.h new file mode 100644 index 00000000..577090a5 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Debugging/SHVkDebugMessenger.h @@ -0,0 +1,111 @@ +#ifndef SH_DEBUG_MESSENGER_H +#define SH_DEBUG_MESSENGER_H + +#include "SHVulkanDebugUtil.h" + +namespace SHADE +{ + enum class SH_DEBUG_MSG_SEV : VkDebugUtilsMessageSeverityFlagsEXT + { + S_VERBOSE = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + S_INFO = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + S_WARNING = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + S_ERROR = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + S_FLAG = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + }; + + enum class SH_DEBUG_MSG_TYPE : VkDebugUtilsMessageTypeFlagsEXT + { + T_GENERAL = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT, + T_VALIDATION = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + T_PERFORMANCE = VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + T_FLAG_BITS = VK_DEBUG_UTILS_MESSAGE_TYPE_FLAG_BITS_MAX_ENUM_EXT, + }; + + /*!************************************************************************** + + \class SHVkDebugMessenger + + \brief + Vulkan provides debugging functionality through a messenger. This class + helps to create and initialize one. + + ***************************************************************************/ + class SHVkDebugMessenger + { + private: + /*-----------------------------------------------------------------------*/ + /* DATA MEMBERS */ + /*-----------------------------------------------------------------------*/ + + //! The vulkan debug messenger object + vk::DebugUtilsMessengerEXT debugMessenger; + + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void Initialize(void) noexcept; + void Destroy(void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC STATIC FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + static void InitializeDebugCreateInfo(vk::DebugUtilsMessengerCreateInfoEXT& createInfo, + VkDebugUtilsMessageSeverityFlagsEXT severityFlags, + VkDebugUtilsMessageTypeFlagsEXT typeFlags) noexcept; + + + /***************************************************************************/ + /*! + + \brief + When using flags to specify debug message severity, the process is + extremely cumbersome as each flag has a lengthy name. This function + along with the enum SHDebMsgSeverity helps make it more readable. Use + this function any time a VkDebugUtilsMessageSeverityFlagsEXT argument + is requested. + + \param ...severities + The combined severities. + + \return + Returns a bitfield indicated the debug severity flags. Gets passed to + Vulkan when created debug create info. + + */ + /***************************************************************************/ + template && ...)>> + static VkDebugUtilsMessageSeverityFlagsEXT GenMessengerSeverity(Sevs... severities) + { + return (static_cast(severities) | ...); + } + + /***************************************************************************/ + /*! + + \brief + When using flags to specify debug message types, the process is + extremely cumbersome as each flag has a lengthy name. This function + along with the enum SHDebMsgType helps make it more readable. Use + this function any time a VkDebugUtilsMessageTypeFlagsEXT argument + is requested. + + \param ...types + The combined types. + + \return + Returns a bitfield indicated the debug severity flags. Gets passed to + Vulkan when created debug create info. + + */ + /***************************************************************************/ + template && ...)>> + static VkDebugUtilsMessageTypeFlagsEXT GenMessengerType(Types... types) + { + return (static_cast(types) | ...); + } + }; +} + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.cpp b/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.cpp new file mode 100644 index 00000000..d63a65f7 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.cpp @@ -0,0 +1,154 @@ +#include "SHPch.h" +#include "SHVulkanDebugUtil.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Generic debug function callback to print error messages that are warnings + or errors. + + \param messageSeverity + Severity of the message + + \param messageType + Type of the message + + \param pCallbackData + Vulkan object containing useful information regarding the message. + + \param pUserData + A pointer to any kind of user data. + + \return + returns false anyway. + + */ + /***************************************************************************/ + VKAPI_ATTR VkBool32 VKAPI_CALL SHVulkanDebugUtil::GenericDebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + [[maybe_unused]] VkDebugUtilsMessageSeverityFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + [[maybe_unused]] void* pUserData) + { + if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) + ReportVkWarning(static_cast (pCallbackData->messageIdNumber), pCallbackData->pMessage); + else if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + ReportVkError(static_cast (pCallbackData->messageIdNumber), pCallbackData->pMessage); + + return VK_FALSE; + } + + /***************************************************************************/ + /*! + + \brief + Prints warnings in a formatted manner. Includes vkResult. + + \param vkResult + The vkResult to display. + + \param message + The custom user message to display. + + \param location + location contains information about the function that calls this function, + the column/row of the callsite and the filename. + + */ + /***************************************************************************/ + void SHVulkanDebugUtil::ReportVkWarning(vk::Result vkResult, std::string_view message, std::source_location const& location /*= std::source_location::current()*/) noexcept + { + //std::cout << location.file_name() << ": " << location.function_name() << "|" << location.line() << "|" << + // location.column() << "|: Warning: " << SHDebugUtil::VkResultToString(vkResult) << " | " << message << std::endl; + + std::string toLogger = "Vulkan Warning: " + std::string(SHVulkanDebugUtil::VkResultToString(vkResult)) + " | " + std::string(message); + + SHLOGV_WARNING(toLogger); + } + + + /***************************************************************************/ + /*! + + \brief + Prints errors in a formatted manner. Includes vkResult. + + \param vkResult + The vkResult to display. + + \param message + The custom user message to display. + + \param location + location contains information about the function that calls this function, + the column/row of the callsite and the filename. + + */ + /***************************************************************************/ + void SHVulkanDebugUtil::ReportVkError(vk::Result vkResult, std::string_view message, std::source_location const& location /*= std::source_location::current()*/) noexcept + { + std::string toLogger = "Vulkan Warning: " + std::string(SHVulkanDebugUtil::VkResultToString(vkResult)) + " | " + std::string(message); + + SHLOGV_ERROR(toLogger); + } + + + void SHVulkanDebugUtil::ReportVkSuccess(std::string_view message, std::source_location const& location /*= std::source_location::current()*/) noexcept + { + SHLOGV_INFO(message); + } + + /***************************************************************************/ + /*! + + \brief + Converts a vkResult enum field to a string. Function is copied entirely + from xGPU: https://github.com/LIONant-depot/xGPU/blob/d546fc202d2acddb6beeb79947860fd437046cb3/Src/Details/Vulkan/xgpu_vulkan_instance.cpp#L19 + Could not find a more straightforward way to convert enum to string. + + \param vkResult + The Vulkan result to convert to string. + + \return + returns a string_view that represents the vulkan result enum value. + + */ + /***************************************************************************/ + std::string_view SHVulkanDebugUtil::VkResultToString(vk::Result vkResult) noexcept + { + VkResult cStyleResult = static_cast(vkResult); + switch (cStyleResult) + { + // Macro to form the enum value using '##', and stringify the enum using '#' +#define STR(r) case VK_ ##r : return #r + STR(TIMEOUT); + STR(EVENT_SET); + STR(EVENT_RESET); + STR(INCOMPLETE); + STR(ERROR_OUT_OF_HOST_MEMORY); + STR(ERROR_OUT_OF_DEVICE_MEMORY); + STR(ERROR_INITIALIZATION_FAILED); + STR(ERROR_DEVICE_LOST); + STR(ERROR_MEMORY_MAP_FAILED); + STR(ERROR_LAYER_NOT_PRESENT); + STR(ERROR_EXTENSION_NOT_PRESENT); + STR(ERROR_FEATURE_NOT_PRESENT); + STR(ERROR_INCOMPATIBLE_DRIVER); + STR(ERROR_TOO_MANY_OBJECTS); + STR(ERROR_FORMAT_NOT_SUPPORTED); + STR(ERROR_SURFACE_LOST_KHR); + STR(ERROR_NATIVE_WINDOW_IN_USE_KHR); + STR(SUBOPTIMAL_KHR); + STR(ERROR_OUT_OF_DATE_KHR); + STR(ERROR_INCOMPATIBLE_DISPLAY_KHR); + STR(ERROR_VALIDATION_FAILED_EXT); + STR(ERROR_INVALID_SHADER_NV); +#undef STR + default: + return "UNKNOWN_ERROR"; + } + } +} diff --git a/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.h b/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.h new file mode 100644 index 00000000..7bf583bb --- /dev/null +++ b/SHADE_Engine/src/Graphics/Debugging/SHVulkanDebugUtil.h @@ -0,0 +1,30 @@ +#ifndef SH_DEBUG_UTIL_H +#define SH_DEBUG_UTIL_H + +#include "Graphics/SHVulkanIncludes.h" +//#include +#include + +namespace SHADE +{ + class SHVulkanDebugUtil + { + private: + static std::string_view VkResultToString(vk::Result vkResult) noexcept; + + public: + static VKAPI_ATTR VkBool32 VKAPI_CALL GenericDebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageSeverityFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData); + + static void ReportVkWarning(vk::Result vkResult, std::string_view message, std::source_location const& location = std::source_location::current()) noexcept; + static void ReportVkError(vk::Result vkResult, std::string_view message, std::source_location const& location = std::source_location::current()) noexcept; + static void ReportVkSuccess(std::string_view message, std::source_location const& location = std::source_location::current()) noexcept; + + }; + + class SHVkDebugOn + { + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolManager.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolManager.cpp new file mode 100644 index 00000000..fcbedc07 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolManager.cpp @@ -0,0 +1,2 @@ +#include "SHPch.h" +#include "SHDescriptorPoolManager.h" diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolManager.h b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolManager.h new file mode 100644 index 00000000..cbd64982 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolManager.h @@ -0,0 +1,5 @@ +#pragma once +class SHDescriptorPoolManager +{ +}; + diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolStorage.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolStorage.cpp new file mode 100644 index 00000000..e5f7c394 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolStorage.cpp @@ -0,0 +1,2 @@ +#include "SHPch.h" +#include "SHDescriptorPoolStorage.h" diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolStorage.h b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolStorage.h new file mode 100644 index 00000000..7e421006 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHDescriptorPoolStorage.h @@ -0,0 +1,5 @@ +#pragma once +class SHDescriptorPoolStorage +{ +}; + diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.cpp new file mode 100644 index 00000000..87d43255 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.cpp @@ -0,0 +1,38 @@ +#include "SHPch.h" +#include "SHVkDescriptorPool.h" + +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Instance/SHVkInstance.h" +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructor/Destructor */ + /*---------------------------------------------------------------------------------*/ + SHVkDescriptorPool::SHVkDescriptorPool(Handle device, const Config& config) + : device { device } + { + // Create the Pool + const vk::DescriptorPoolCreateInfo POOL_CREATE_INFO + { + .flags = config.Flags, + .maxSets = config.MaxSets, + .poolSizeCount = static_cast(config.Limits.size()), + .pPoolSizes = config.Limits.data() + }; + pool = device->GetVkLogicalDevice().createDescriptorPool(POOL_CREATE_INFO); + } + + SHVkDescriptorPool::~SHVkDescriptorPool() noexcept + { + if (pool) + device->GetVkLogicalDevice().destroyDescriptorPool(pool); + } + + std::vector> SHVkDescriptorPool::Allocate(const std::vector>& layouts, std::vector const& variableDescCounts) + { + SHVkInstance::GetResourceManager().Create(device, GetHandle(), layouts, variableDescCounts); + return {}; + } +} diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.h b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.h new file mode 100644 index 00000000..c3059b8b --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorPool.h @@ -0,0 +1,114 @@ +#pragma once + +// Project Includes +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkLogicalDevice; + class SHVkDescriptorSetGroup; + class SHVkDescriptorSetLayout; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// + /// + class SHVkDescriptorPool : public ISelfHandle + { + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Configuration object for the Pool Manager. + /// + struct Config + { + public: + /// + /// Describe the resource limitations of the Descriptor Pool can construct. + /// The specified type and count pairs indicate how many Descriptors will be + /// constructed in the Descriptor Pool. + /// + std::vector Limits = + { + { vk::DescriptorType::eCombinedImageSampler, 100 }, + { vk::DescriptorType::eUniformBuffer, 100 } + }; + /// + /// Maximum number of descriptor sets allowed + /// + uint32_t MaxSets = 100; + /// + /// Flags used to create the DescriptorPool + /// + vk::DescriptorPoolCreateFlags Flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; + }; + + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructor */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor for a Descriptor Pool. + /// + /// + /// The Vulkan logical device that is used to create a Descriptor Pool. + /// + /// + /// Configuration object that describes how to construct the Descriptor Pool. + /// + SHVkDescriptorPool(Handle device, const Config& config = {}); + SHVkDescriptorPool(const SHVkDescriptorPool&) = delete; + SHVkDescriptorPool(SHVkDescriptorPool&& rhs) noexcept = default; + /// + /// Destructor which will unload and deallocate all resources for this Pool. + /// + ~SHVkDescriptorPool() noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + SHVkDescriptorPool& operator=(const SHVkDescriptorPool&) = delete; + SHVkDescriptorPool& operator=(SHVkDescriptorPool&& rhs) noexcept = default; + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves the handle to the Vulkan Descriptor Pool handle. + /// + /// Handle to the Vulkan Descriptor Pool. + [[nodiscard]] + inline const vk::DescriptorPool& GetVkHandle() const { return pool; } + + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Allocates multiple DescriptorSets based on the provided layouts. + /// + /// Layouts of DescriptorSets to create. + /// + /// Thrown if an incompatible layout was provided to this Descriptor Pool. + /// + /// + /// Handles to the created Descriptor Sets. If this DescriptorPool has run out of + /// space, lesser number of Handles will be returned. + /// + std::vector> Allocate(const std::vector>& layouts, std::vector const& variableDescCounts); + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + Handle device; + vk::DescriptorPool pool; + }; +} + diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp new file mode 100644 index 00000000..6bdc5601 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp @@ -0,0 +1,85 @@ +#include "SHPch.h" +#include "SHVkDescriptorSetGroup.h" + +#include + +#include "SHVkDescriptorPool.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Allocates a bunch of descriptor sets based on how many layouts are + being passed in. + + \param deviceHdl + Logical device required for the descriptor set creation. + + + \param pool + Pool to allocate from. + + \param layouts + With N layouts provided, the function will allocate N descriptor sets. + All of which will be stored in this class. + + \param variableDescCounts + Represents the variable descriptor count for every layout passed in. + If the binding used in the layout is not marked as using a variable count, + the value for the particular layout is ignored. + + */ + /***************************************************************************/ + SHVkDescriptorSetGroup::SHVkDescriptorSetGroup(Handle deviceHdl, Handle pool, std::vector> const& layouts, std::vector const& variableDescCounts) + : device{deviceHdl} + , descPool {pool} + , descSets{} + { + // Create the layout for each concurrent frame + std::vector vkLayouts{ layouts.size() }; + for (auto& layout : layouts) + { + vkLayouts.push_back(layout->GetVkHandle()); + } + + // Check for variable descriptor count + if (variableDescCounts.size() != layouts.size()) + SHLOG_ERROR("Number of variable descriptor counts does not match number of layouts. If a layout does not use variable counts, pass in 0. "); + + // Prepare variable descriptor counts + vk::DescriptorSetVariableDescriptorCountAllocateInfo variableAllocInfo{}; + variableAllocInfo.descriptorSetCount = static_cast(variableDescCounts.size()); + variableAllocInfo.pDescriptorCounts = variableDescCounts.data(); + + // Prepare allocation information + const vk::DescriptorSetAllocateInfo DESC_SET_LAYOUT_CREATE_INFO + { + .pNext = &variableAllocInfo, + .descriptorPool = descPool->GetVkHandle(), + .descriptorSetCount = static_cast(vkLayouts.size()), + .pSetLayouts = vkLayouts.data(), + }; + + // allocate descriptor sets + descSets = device->GetVkLogicalDevice().allocateDescriptorSets(DESC_SET_LAYOUT_CREATE_INFO); + } + + /***************************************************************************/ + /*! + + \brief + Destroys all the descriptor sets in the group. + + */ + /***************************************************************************/ + SHVkDescriptorSetGroup::~SHVkDescriptorSetGroup() noexcept + { + if (!descSets.empty()) + device->GetVkLogicalDevice().freeDescriptorSets(descPool->GetVkHandle(), descSets); + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h new file mode 100644 index 00000000..b95859bb --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h @@ -0,0 +1,71 @@ + #pragma once + +// Project Includes +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkLogicalDevice; + class SHVkDescriptorPool; + class SHVkDescriptorSetLayout; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// + /// + class SHVkDescriptorSetGroup + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructs a Descriptor Set with the specified layout using the specified + /// pool meant for use with the specified surface. This Set will be created with + /// multiple Vulkan Descriptor Set objects based on the max number of concurrent + /// frames for the specified surface. + /// + /// Vulkan logical device used to create the Set. + /// Descriptor Pool used to create the Set. + /// Descriptor Set Layout to create the Set with. + SHVkDescriptorSetGroup(Handle deviceHdl, Handle pool, + std::vector> const& layouts, + std::vector const& variableDescCounts); + SHVkDescriptorSetGroup(const SHVkDescriptorSetGroup&) = delete; + SHVkDescriptorSetGroup(SHVkDescriptorSetGroup&& rhs) noexcept = default; + /// + /// Destructor which will unload and deallocate all resources for this Descriptor Set. + /// + ~SHVkDescriptorSetGroup() noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + SHVkDescriptorSetGroup& operator=(const SHVkDescriptorSetGroup&) = delete; + SHVkDescriptorSetGroup& operator=(SHVkDescriptorSetGroup&& rhs) noexcept = default; + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves the handle to the Vulkan Descriptor Set handle. + /// + /// Handle to the Vulkan Descriptor Set. + [[nodiscard]] + inline const std::vector& GetVkHandle() { return descSets; } + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + Handle device; + Handle descPool; + std::vector descSets; + }; +} diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp new file mode 100644 index 00000000..36eaa8e8 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp @@ -0,0 +1,102 @@ +#include "SHPch.h" +#include "SHVkDescriptorSetLayout.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructor/Destructor */ + /*---------------------------------------------------------------------------------*/ + SHVkDescriptorSetLayout::SHVkDescriptorSetLayout(Handle device, const std::vector& bindings) + : device { device } + , layoutDesc { bindings } + { + // Check if auto-binding point calculation configuration is valid + bool autoCalc = false; + for (const auto& binding : bindings) + { + if (binding.BindPoint == Binding::AUTO_CALC_BINDING) + { + autoCalc = true; + } + else if (autoCalc) + { + throw std::invalid_argument("For auto calculation of bindings, all bindings must be set to AUTO_CALC_BINDING!"); + } + } + + // Fill up VK bindings with auto calculated bind points if needed + std::vector layoutBindings; + layoutBindings.reserve(bindings.size()); + int bindCount = 0; + for (const auto& binding : bindings) + { + const uint32_t CURR_BIND_POINT = autoCalc ? bindCount : binding.BindPoint; + const vk::DescriptorSetLayoutBinding VK_BINDING = + { + .binding = CURR_BIND_POINT, + .descriptorType = binding.Type, + .descriptorCount = binding.DescriptorCount, + .stageFlags = binding.Stage, + .pImmutableSamplers = nullptr // We will create our own samplers + }; + layoutBindings.emplace_back(VK_BINDING); + + // Save for future reference + layoutDesc[bindCount++].BindPoint = CURR_BIND_POINT; + } + + // TODO: Check layout support with physical device + + // Prepare binding flags + std::vector combinedBindings(bindings.size()); + for (uint32_t i = 0; i < bindings.size(); ++i) + combinedBindings[i] = bindings[i].flags; + + const vk::DescriptorSetLayoutBindingFlagsCreateInfo BINDING_FLAGS_CREATE_INFO + { + .bindingCount = static_cast(bindings.size()), // Number of flags = number of bindings + .pBindingFlags = combinedBindings.data(), // address to flags + }; + + // Create the layout + const vk::DescriptorSetLayoutCreateInfo DESC_SET_LAYOUT_CREATE_INFO + { + .pNext = &BINDING_FLAGS_CREATE_INFO, + .flags = {}, + .bindingCount = static_cast(layoutBindings.size()), + .pBindings = layoutBindings.data(), + }; + setLayout = device->GetVkLogicalDevice().createDescriptorSetLayout(DESC_SET_LAYOUT_CREATE_INFO); + } + + SHVkDescriptorSetLayout::SHVkDescriptorSetLayout(SHVkDescriptorSetLayout&& rhs) noexcept + : device {rhs.device} + , setLayout {rhs.setLayout} + , layoutDesc{std::move (rhs.layoutDesc)} + { + rhs.setLayout = VK_NULL_HANDLE; + } + + SHVkDescriptorSetLayout::~SHVkDescriptorSetLayout() noexcept + { + // Destroy layout + if (setLayout) + device->GetVkLogicalDevice().destroyDescriptorSetLayout(setLayout); + } + + SHVkDescriptorSetLayout& SHVkDescriptorSetLayout::operator=(SHVkDescriptorSetLayout&& rhs) noexcept + { + if (&rhs == this) + return *this; + + device = rhs.device; + setLayout = rhs.setLayout; + layoutDesc = std::move(rhs.layoutDesc); + + rhs.setLayout = VK_NULL_HANDLE; + + return *this; + } + +} diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h new file mode 100644 index 00000000..590fd787 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h @@ -0,0 +1,108 @@ +#pragma once + +// Project Includes +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkLogicalDevice; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// RAII wrapper object for a Vulkan Descriptor Set Layout object. + /// + class SHVkDescriptorSetLayout + { + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Object that describes how a descriptor binding in a DescriptorSetLayout is + /// structured. + /// + struct Binding + { + /*-------------------------------------------------------------------------*/ + /* Constants */ + /*-------------------------------------------------------------------------*/ + /// + /// If set for the "BindPoint", binding points are automatically calculated. + /// + static constexpr uint32_t AUTO_CALC_BINDING = std::numeric_limits::max(); + + /// + /// For use in Binding DescriptorCount. + /// + static constexpr uint32_t VARIABLE_DESCRIPTOR_UPPER_BOUND = 2000; + /*-------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------*/ + /// + /// Type of element for the descriptor. + /// + vk::DescriptorType Type = {}; + /// + /// Shader stage that this binding is for. + /// + vk::ShaderStageFlags Stage = {}; + /// + /// Binding point for the Descriptor within the Descriptor Set. + /// + uint32_t BindPoint = AUTO_CALC_BINDING; + /// + /// Number of elements in the binding. When VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT + /// is used in VkDescriptorBindingFlagBits, this value represents the upper bound. + /// + uint32_t DescriptorCount = 1; + + vk::DescriptorBindingFlags flags = {}; + }; + + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + SHVkDescriptorSetLayout() = delete; + /// + /// Constructs a DescriptorSetLayout with the specified properties and device. + /// + /// + /// + SHVkDescriptorSetLayout(Handle device, const std::vector& bindings); + SHVkDescriptorSetLayout(const SHVkDescriptorSetLayout&) = delete; + SHVkDescriptorSetLayout(SHVkDescriptorSetLayout&& rhs) noexcept; + /// + /// Destructor which will unload and deallocate all resources for this Set. + /// + ~SHVkDescriptorSetLayout() noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + SHVkDescriptorSetLayout& operator=(const SHVkDescriptorSetLayout&) = delete; + SHVkDescriptorSetLayout& operator=(SHVkDescriptorSetLayout&& rhs) noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves the handle to the Vulkan Descriptor Set Layout handle. + /// + /// Handle to the Vulkan Descriptor Set Layout handle. + inline const vk::DescriptorSetLayout& GetVkHandle() const { return setLayout; } + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + Handle device; + vk::DescriptorSetLayout setLayout; + std::vector layoutDesc; // Stores description of the layout + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp new file mode 100644 index 00000000..aa442805 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp @@ -0,0 +1,637 @@ +#include "SHPch.h" +#include "SHVkLogicalDevice.h" +#include "SHVkPhysicalDevice.h" +#include "Graphics/Instance/SHVkInstance.h" +#include "Tools/SHLogger.h" +#include "Graphics/Windowing/Surface/SHVkSurface.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" +#include "Graphics/Commands/SHVkCommandPool.h" +#include "Graphics/Buffers/SHVkBuffer.h" +#include "Graphics/Images/SHVkImage.h" +#include "Graphics/Synchronization/SHVkFence.h" +#include "Graphics/Synchronization/SHVkSemaphore.h" +#include "Graphics/Shaders/SHVkShaderModule.h" +#include "Graphics/Pipeline/SHVkPipelineLayout.h" +#include "Graphics/Pipeline/SHVkPipeline.h" +#include "Graphics/Framebuffer/SHVkFramebuffer.h" +#include "Graphics/Images/SHVkImageView.h" + +namespace SHADE +{ + + + /***************************************************************************/ + /*! + + \brief + Initializes a VMA allocator. + + */ + /***************************************************************************/ + void SHVkLogicalDevice::InitializeVMA(void) noexcept + { + VmaVulkanFunctions vulkanFunctions = {}; + vulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr; + vulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo vmaCreateInfo{}; + vmaCreateInfo.vulkanApiVersion = VK_API_VERSION_1_3; + vmaCreateInfo.physicalDevice = parentPhysicalDeviceHdl->GetVkPhysicalDevice(); + vmaCreateInfo.device = vkLogicalDevice; + vmaCreateInfo.instance = SHVkInstance::GetVkInstance(); + vmaCreateInfo.pVulkanFunctions = &vulkanFunctions; + + // attempt to create the vma allocator. + vmaCreateAllocator(&vmaCreateInfo, &vmaAllocator); + } + + void SHVkLogicalDevice::InitializeQueues(std::initializer_list queueCreateParams) noexcept + { + /* Each queue family has a set amount of queues. Before we get queues from them, we want to + * reset the queue use count to 0. + */ + for (std::size_t i = 0; i < static_cast(SH_QUEUE_FAMILY_ARRAY_INDEX::MAX); ++i) + { + if (queueFamilyIndices.indices[i].has_value()) + { + queueFamUseCounts[queueFamilyIndices.indices[i].value()] = 0; + queueLibrary.emplace(queueFamilyIndices.indices[i].value(), std::vector>()); + } + } + + // actually attempt to get queue + for (auto const& queueParams : queueCreateParams) + { + // First, check if queue family exists and get index of queue family + SHQueueFamilyIndex queueFamIndex = 0; + + if (queueParams.selectionMethod == SH_QUEUE_SELECT::DEDICATED) + { + if (uint32_t arrayIndex = static_cast(queueParams.arrayIndex); queueFamilyIndices.indices[arrayIndex].has_value()) + queueFamIndex = queueFamilyIndices.indices[arrayIndex].value(); + else + { + SHLOGV_ERROR("Failed to get queue from queue family. "); + continue; + } + } + else + queueFamIndex = nonDedicatedBestIndex; + + // Actually attempt to get the queue from Vulkan and increase the number of queue obtained + queueLibrary[queueFamIndex].push_back(SHVkInstance::GetResourceManager().Create(vkLogicalDevice.getQueue(queueFamIndex, queueFamUseCounts[queueFamIndex]++), queueFamIndex, GetHandle())); + } + + } + + /***************************************************************************/ + /*! + + \brief + Initializes a logical device and the requested queues with it. Queues + are either specified as either NON_DEDICATED_BEST or DEDICATED. + NON_DEDICATED_BEST means that the function will look for a queue family + with the highest functionality and attempt to create a queue from that. + For example, a transfer queue will select a graphics queue family, even + though the graphics aren't needed. DEDICATED means that a transfer queue + request would lead to a selection of a dedicated queue family specifically + for that queue (e.g. compute -> use compute queue family). + + \param queueCreateParams + Since queues all need to be created with the logical device, the specs + of all the queues are prerequisites of the creation of the logical device. + + \param inPhysicalDevice + \param queueTypes + + \return + + + */ + /***************************************************************************/ + SHVkLogicalDevice::SHVkLogicalDevice(std::initializer_list queueCreateParams, Handle const& inPhysicalDeviceHdl) noexcept + : vkLogicalDevice{} + , parentPhysicalDeviceHdl{ inPhysicalDeviceHdl } + , queueFamilyIndices{} + , vmaAllocator{} + { + float priority = 1.0f; + + // Get all queue families + auto queueFamProps = parentPhysicalDeviceHdl->GetQueueFamilyProperties(); + + // Number of queue family properties + uint32_t queueFamPropsSize = static_cast(queueFamProps.size()); + + // Will represent the first fit queue family index + nonDedicatedBestIndex = 0xFFFF; + + // initialize queue family indices + for (uint32_t i = 0; i < queueFamPropsSize; ++i) + { + std::size_t index = 0; + + if (queueFamProps[i].queueFlags & vk::QueueFlagBits::eGraphics) + index = static_cast(SH_QUEUE_FAMILY_ARRAY_INDEX::GRAPHICS); + else if (queueFamProps[i].queueFlags & vk::QueueFlagBits::eCompute) + index = static_cast(SH_QUEUE_FAMILY_ARRAY_INDEX::COMPUTE); + else if (queueFamProps[i].queueFlags & vk::QueueFlagBits::eTransfer) + index = static_cast(SH_QUEUE_FAMILY_ARRAY_INDEX::TRANSFER); + + queueFamilyIndices.indices[index] = static_cast(i); + } + + // Find the most versatile/best queue family + nonDedicatedBestIndex = (*std::min_element(queueFamilyIndices.indices.begin(), queueFamilyIndices.indices.end())).value(); + + // Start producing queue create infos + std::vector queueCreateInfos; + for (auto const& params : queueCreateParams) + { + vk::DeviceQueueCreateInfo queueCreateInfo{}; + + // Check if we want the best or a dedicated queue family + if (params.selectionMethod == SH_QUEUE_SELECT::NON_DEDICATED_BEST) + queueCreateInfo.queueFamilyIndex = nonDedicatedBestIndex; + else + queueCreateInfo.queueFamilyIndex = queueFamilyIndices.indices[static_cast(params.arrayIndex)].value(); + + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &priority; // 1.0f + queueCreateInfos.push_back(queueCreateInfo); + } + + std::vector requiredExtensions; + requiredExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + + // Check if extensions are supported + bool extensionsSupported = parentPhysicalDeviceHdl->ExtensionsSupported(requiredExtensions); + // Log error + /*if (!extensionsSupported) + SHUtil::ReportWarning("Some of the required extensions cannot be found on the physical device. ");*/ + + vk::PhysicalDeviceFeatures features{}; // ADD MORE FEATURES HERE IF NEEDED + + // point and lines fill mode + features.fillModeNonSolid = true; + features.samplerAnisotropy = VK_TRUE; + + // for wide lines + features.wideLines = true; + + // Prepare to create the device + vk::DeviceCreateInfo deviceCreateInfo + { + .pNext = nullptr, + .queueCreateInfoCount = static_cast(queueCreateInfos.size()), + .pQueueCreateInfos = queueCreateInfos.data(), + .enabledLayerCount = 0, // deprecated and ignored + .ppEnabledLayerNames = nullptr, // deprecated and ignored + .enabledExtensionCount = !extensionsSupported ? 0 : static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = !extensionsSupported ? nullptr : requiredExtensions.data(), + .pEnabledFeatures = &features + }; + + // Actually create the device + if (auto result = parentPhysicalDeviceHdl->GetVkPhysicalDevice().createDevice(&deviceCreateInfo, nullptr, &vkLogicalDevice); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Logical Device! "); + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created a Logical Device. "); + } + + InitializeVMA(); + + // TODO: Create pipeline caches + // TODO: Create Descriptor pools + //auto poolSizes = std::array + //{ + // SHDescriptorPoolSize {SHDescriptorType::COMBINED_SAMPLER, 1000} // hard coded descriptor count + //}; + + //SHDescriptorPoolParams poolParams + //{ + // .poolSizes = poolSizes, + // .maxDescriptorSets = 1000, + //}; + + //descriptorPool.Initialize(*this, poolParams); + //deviceStorage.Init(*this, queueFamilyIndices.indices[static_cast(SH_QUEUE_FAMILY_ARRAY_INDEX::GRAPHICS)].value()); + } + + SHVkLogicalDevice::SHVkLogicalDevice(SHVkLogicalDevice&& rhs) noexcept + : vkLogicalDevice (std::move (rhs.vkLogicalDevice)) + , queueFamilyIndices(std::move (rhs.queueFamilyIndices)) + , vmaAllocator{rhs.vmaAllocator} + , nonDedicatedBestIndex {0} + , parentPhysicalDeviceHdl {rhs.parentPhysicalDeviceHdl} + { + rhs.vkLogicalDevice = VK_NULL_HANDLE; + } + + /***************************************************************************/ + /*! + + \brief + Destroy the logical device. + + */ + /***************************************************************************/ + SHVkLogicalDevice::~SHVkLogicalDevice(void) noexcept + { + //descriptorPool.Destroy(*this); + vkLogicalDevice.destroy(nullptr); + } + + /***************************************************************************/ + /*! + + \brief + TODO: Not sure what this does yet LOL. see the article on efficient + Vulkan renderer. + + \param alignment + + \return + + */ + /***************************************************************************/ + //void SHVkLogicalDevice::UpdateBufferMemoryAlignment(vk::DeviceSize alignment) noexcept + //{ + // bufferMemoryAlignment = std::max(bufferMemoryAlignment, alignment); + //} + + void SHVkLogicalDevice::WaitIdle(void) noexcept + { + vkLogicalDevice.waitIdle(); + } + + /***************************************************************************/ + /*! + + \brief + Finds the memory type based on the memory type. + + \param typeFilter + \param properties + + \return + the index to the memory type. + + */ + /***************************************************************************/ + uint32_t SHVkLogicalDevice::FindMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + auto const& memProps = parentPhysicalDeviceHdl->GetVkPhysicalDevice().getMemoryProperties(); + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) + { + if (typeFilter & (1 << i) && (memProps.memoryTypes[i].propertyFlags & properties) == properties) + return i; + } + + SHLOGV_ERROR("Failed to find memory type. "); + return VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_FLAG_BITS_MAX_ENUM; + } + + /***************************************************************************/ + /*! + + \brief + Creates a Surface in the resource manager and returns a handle to it. + + \param windowHandle + For the surface creation. + + \return + A Handle to the surface. + + */ + /***************************************************************************/ + Handle SHVkLogicalDevice::CreateSurface(HWND const& windowHandle) const noexcept + { + return SHVkInstance::GetResourceManager().Create(windowHandle, parentPhysicalDeviceHdl, GetHandle()); + } + + /***************************************************************************/ + /*! + + \brief + Creates a swapchain in the resource manager and returns a handle to it. + + \param surfaceHdl + Surface for the swapchain creation. + + \param width + Width of swapchain images + + \param height + height of swapchain images + + \param params + swapchain creation parameters (surface format, depth format, etc). + + \return + Handle to swapchain created. + + */ + /***************************************************************************/ + Handle SHVkLogicalDevice::CreateSwapchain(Handle const& surfaceHdl, uint32_t width, uint32_t height, SHSwapchainParams const& params) const noexcept + { + return SHVkInstance::GetResourceManager().Create (parentPhysicalDeviceHdl, GetHandle(), surfaceHdl, width, height, params); + } + + /***************************************************************************/ + /*! + + \brief + Creates a command pool in the resource manager and returns a handle to it. + + \param queueFamilyType + What queue family type this command pool comes from. + + \param inResetMode + BUFFER_BASED for individual resets, POOL_BASED for pool based resets ( + good for transient command buffers). + + \param inTransient + Whether or not command buffers created from this pool are short lived + or not. + + \return + A handle to the command pool created. + + */ + /***************************************************************************/ + Handle SHVkLogicalDevice::CreateCommandPool(SH_QUEUE_FAMILY_ARRAY_INDEX queueFamilyType, SH_CMD_POOL_RESET inResetMode, bool inTransient) const noexcept + { + auto newHdl = SHVkInstance::GetResourceManager().Create(GetHandle(), queueFamilyType, inResetMode, inTransient); + return newHdl; + } + + /***************************************************************************/ + /*! + + \brief + Creates a buffer in the resource manager and returns a handle to it. See + buffer init function for details on parameters. + + \return + A handle to the buffer created. + + */ + /***************************************************************************/ + Handle SHVkLogicalDevice::CreateBuffer(uint32_t inSize, void* data, uint32_t srcSize, vk::BufferUsageFlags bufferUsage, VmaMemoryUsage memUsage, VmaAllocationCreateFlags allocFlags) const noexcept + { + return SHVkInstance::GetResourceManager().Create(inSize, data, srcSize, std::cref(vmaAllocator), bufferUsage, memUsage, allocFlags); + } + + /***************************************************************************/ + /*! + + \brief + Creates an image. This ctor is mainly used for constructing images that + will be used as render targets. + + \param w + Width of image. + + \param h + Height of image. + + \param levels + Number of mip levels. + + \param format + Format of the image + + \param usage + Usage bits for the image. + + \param create + Create bits for the image. + + \return + Handle to the newly created image. + */ + /***************************************************************************/ + Handle SHVkLogicalDevice::CreateImage(uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) const noexcept + { + return SHVkInstance::GetResourceManager().Create(std::cref(vmaAllocator), w, h, levels, format, usage, create); + } + + /***************************************************************************/ + /*! + + \brief + Creates a shader module in the resource manager and returns a handle to + it. + + \param binaryData + Binary data for the module's creation. + + \param entryPoint + Entry point of the shader. + + \param stage + Shader stage. + + \return + Handle to the new shader module created. + + */ + /***************************************************************************/ + Handle SHVkLogicalDevice::CreateShaderModule(std::vector const& binaryData, std::string entryPoint, vk::ShaderStageFlagBits stage, std::string const& shaderName) noexcept + { + return SHVkInstance::GetResourceManager().Create(GetHandle(), binaryData, entryPoint, stage, shaderName); + } + + /***************************************************************************/ + /*! + + \brief + Creates a pipeline layout in the resource manager and returns a handle to + it. + + \param pipelineLayoutParams + + \return + + */ + /***************************************************************************/ + Handle SHVkLogicalDevice::CreatePipelineLayout(SHPipelineLayoutParams& pipelineLayoutParams) noexcept + { + return SHVkInstance::GetResourceManager().Create (GetHandle(), pipelineLayoutParams); + } + + /***************************************************************************/ + /*! + + \brief + Creates a pipeline in the resource manager and returns a handle to it. + + \param pipelineLayoutHdl + + + \param state + \param type + + \return + + */ + /***************************************************************************/ + Handle SHVkLogicalDevice::CreatePipeline(Handle const& pipelineLayoutHdl, SHVkPipelineState const* const state, Handle const& renderpassHdl, uint32_t subpass, SH_PIPELINE_TYPE type) noexcept + { + return SHVkInstance::GetResourceManager().Create (GetHandle(), pipelineLayoutHdl, state, renderpassHdl, subpass, type); + + } + + Handle SHVkLogicalDevice::CreateRenderpass(std::span const vkDescriptions, std::vector const& subpasses) noexcept + { + return SHVkInstance::GetResourceManager().Create (GetHandle(), vkDescriptions, subpasses); + } + + Handle SHVkLogicalDevice::CreateRenderpass(std::span const vkDescriptions, std::span const spDescs, std::span const spDeps) noexcept + { + return SHVkInstance::GetResourceManager().Create (GetHandle(), vkDescriptions, spDescs, spDeps); + } + + Handle SHVkLogicalDevice::CreateFramebuffer(Handle const& renderpassHdl, std::vector> const& attachments, uint32_t inWidth, uint32_t inHeight) noexcept + { + return SHVkInstance::GetResourceManager().Create (GetHandle(), renderpassHdl, attachments, inWidth, inHeight); + + } + + Handle SHVkLogicalDevice::CreateDescriptorSetLayout(std::vector const& bindings) noexcept + { + return SHVkInstance::GetResourceManager().Create (GetHandle(), bindings); + + } + + /***************************************************************************/ + /*! + + \brief + Creates a fence in the resource manager and returns a handle to it. + + \return + A handle to the fence created. + + */ + /***************************************************************************/ + Handle SHVkLogicalDevice::CreateFence(void) const noexcept + { + return SHVkInstance::GetResourceManager().Create(GetHandle()); + } + + /***************************************************************************/ + /*! + + \brief + Creates a semaphore in the resource manager and returns a handle to it. + + \return + A handle to the semaphore created. + + */ + /***************************************************************************/ + Handle SHVkLogicalDevice::CreateSemaphore(void) const noexcept + { + return SHVkInstance::GetResourceManager().Create(GetHandle()); + } + + /***************************************************************************/ + /*! + + \brief + Returns the vulkan logical device. + + \return + The vulkan logical device. + + */ + /***************************************************************************/ + vk::Device const& SHVkLogicalDevice::GetVkLogicalDevice(void) const noexcept + { + return vkLogicalDevice; + } + + /***************************************************************************/ + /*! + + \brief + Returns a handle to a queue given an index and given a queue family array + index. + + \param index + + + \param queueIndex + + \return + + */ + /***************************************************************************/ + Handle const& SHVkLogicalDevice::GetQueue(SH_Q_FAM queueFamArrayIndex, uint8_t queueIndex) const + { + if (queueFamilyIndices.indices[static_cast(queueFamArrayIndex)].has_value()) + { + SHQueueFamilyIndex queueFamIndex = queueFamilyIndices.indices[static_cast(queueFamArrayIndex)].value(); + if (queueIndex < queueFamUseCounts.at(queueFamIndex)) + { + return queueLibrary.at(queueFamIndex)[queueIndex]; + } + else + throw std::invalid_argument("Attempting to index for a queue that is out of range. Try requesting for more queues on device creation or request for a valid queue index. "); + } + throw std::invalid_argument("Queue family doesn't exist. "); + + } + + /***************************************************************************/ + /*! + + \brief + Getter for buffer alignment. + + \return + The member buffer alignment variable. + + */ + /***************************************************************************/ + //vk::DeviceSize SHVkLogicalDevice::GetBufferAlignment(void) const noexcept + //{ + // return bufferMemoryAlignment; + //} + + /***************************************************************************/ + /*! + + \brief + Getter for VMA Allocator. + + \return + VmaAllocator handle. + + */ + /***************************************************************************/ + VmaAllocator const& SHVkLogicalDevice::GetVMAAllocator(void) noexcept + { + return vmaAllocator; + } + + SHQueueFamilyIndex SHVkLogicalDevice::GetQueueFamilyIndex(SH_Q_FAM family) const noexcept + { + if (queueFamilyIndices.indices[static_cast(family)].has_value()) + return queueFamilyIndices.indices[static_cast(family)].value(); + else + return static_cast(SH_Q_FAM::INVALID); + } + + //SHDescriptorPool const& SHLogicalDevice::GetDescriptorPool(void) const noexcept + //{ + // return descriptorPool; + //} + +} diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h new file mode 100644 index 00000000..b8eec993 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h @@ -0,0 +1,186 @@ +#ifndef SH_LOGICAL_DEVICE_H +#define SH_LOGICAL_DEVICE_H + +#include +#include +#include +#include +#include "Graphics/SHVulkanIncludes.h" +#include "Graphics/Devices/SHVkPhysicalDevice.h" +#include "Graphics/Queues/SHVkQueue.h" +#include "Resource/Handle.h" +#include "Resource/ResourceLibrary.h" +#include "Graphics/Swapchain/SHSwapchainParams.h" +#include "Graphics/Commands/SHCommandPoolResetMode.h" +#include "Graphics/Commands/SHVkCommandPool.h" +#include "Graphics/Pipeline/SHPipelineLayoutParams.h" +#include "Graphics/Pipeline/SHPipelineState.h" +#include "Graphics/Pipeline/SHPipelineType.h" +#include "vk_mem_alloc.h" +//#include "Graphics/DescriptorSets/SHDescriptorPool.h" +#include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------*/ + /* FORWARD DECLARATIONS */ + /*-----------------------------------------------------------------------*/ + class SHVkInstance; + class SHVkSurface; + class SHVkSwapchain; + class SHVkBuffer; + class SHVkImage; + class SHVkFence; + class SHVkSemaphore; + class SHVkShaderModule; + class SHVkPipelineLayout; + class SHVkPipeline; + class SHVkFramebuffer; + class SHVkImageView; + class SHShaderBlockInterface; + + /***************************************************************************/ + /*! + + \struct SHQueueFamilyIndices + Stores Vulkan queue family indices of each queue family. + + */ + /***************************************************************************/ + struct SHQueueFamilyIndices + { + std::array, static_cast(SH_QUEUE_FAMILY_ARRAY_INDEX::MAX)> indices; + }; + + /***************************************************************************/ + /*! + + \class SHVkLogicalDevice + Stores a Vulkan logical device. + + */ + /***************************************************************************/ + class SHVkLogicalDevice : public ISelfHandle + { + private: + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //vk::DeviceSize bufferMemoryAlignment{ 64 }; + + //! Vulkan handle + vk::Device vkLogicalDevice; + + //! Handle to the physical device this logical device was created from + Handle parentPhysicalDeviceHdl; + + //! The array of queue family array indices + SHQueueFamilyIndices queueFamilyIndices{}; + + //! If the user chooses a non-dedicated queue, the device will initialize this to the best queue family index + uint32_t nonDedicatedBestIndex; + + //std::vector> queueHdls; + std::unordered_map>> queueLibrary; + + //! For use on tracking how many queues are retrieved + std::unordered_map queueFamUseCounts; + + //! main VMA allocator of this instance + VmaAllocator vmaAllocator; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void InitializeVMA (void) noexcept; + void InitializeQueues (std::initializer_list queueCreateParams) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* CTOR AND DTOR */ + /*-----------------------------------------------------------------------*/ + SHVkLogicalDevice (std::initializer_list queueCreateParams, Handle const& inPhysicalDeviceHdl) noexcept; + SHVkLogicalDevice (SHVkLogicalDevice const& rhs) = default; + SHVkLogicalDevice (SHVkLogicalDevice&& rhs) noexcept; + ~SHVkLogicalDevice (void) noexcept; + + SHVkLogicalDevice& operator= (SHVkLogicalDevice const& rhs) noexcept = default; + SHVkLogicalDevice& operator= (SHVkLogicalDevice&& rhs) noexcept = default; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + void WaitIdle (void) noexcept; + uint32_t FindMemoryType (uint32_t typeFilter, vk::MemoryPropertyFlags properties); + + Handle CreateSurface (HWND const& windowHandle) const noexcept; + Handle CreateSwapchain ( + Handle const& surfaceHdl, + uint32_t width, + uint32_t height, + SHSwapchainParams const& params + ) const noexcept; + + Handle CreateCommandPool ( + SH_QUEUE_FAMILY_ARRAY_INDEX queueFamilyType, + SH_CMD_POOL_RESET inResetMode, + bool inTransient + ) const noexcept; + + Handle CreateBuffer ( + uint32_t inSize, + void* data, + uint32_t srcSize, + vk::BufferUsageFlags bufferUsage, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags + ) const noexcept; + + Handle CreateImage ( + uint32_t w, + uint32_t h, + uint8_t levels, + vk::Format format, + vk::ImageUsageFlags usage, + vk::ImageCreateFlags create + ) const noexcept; + + Handle CreateShaderModule ( + std::vector const& binaryData, + std::string entryPoint, + vk::ShaderStageFlagBits stage, + std::string const& shaderName + ) noexcept; + + Handle CreatePipeline ( + Handle const& pipelineLayoutHdl, + SHVkPipelineState const* const state, + Handle const& renderpassHdl, + uint32_t subpass, + SH_PIPELINE_TYPE type + ) noexcept; + + Handle CreateRenderpass (std::span const vkDescriptions, std::vector const& subpasses) noexcept; + Handle CreateRenderpass (std::span const vkDescriptions, std::span const spDescs, std::span const spDeps) noexcept; + Handle CreateFramebuffer (Handle const& renderpassHdl, std::vector> const& attachments, uint32_t inWidth, uint32_t inHeight) noexcept; + Handle CreateDescriptorSetLayout (std::vector const& bindings) noexcept; + Handle CreatePipelineLayout (SHPipelineLayoutParams& pipelineLayoutParams) noexcept; + Handle CreateFence (void) const noexcept; + Handle CreateSemaphore (void) const noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + vk::Device const& GetVkLogicalDevice (void) const noexcept; + Handle const& GetQueue (SH_Q_FAM queueFamArrayIndex, uint8_t queueIndex) const; + VmaAllocator const& GetVMAAllocator (void) noexcept; + SHQueueFamilyIndex GetQueueFamilyIndex (SH_Q_FAM family) const noexcept; + //vk::DeviceSize GetBufferAlignment (void) const noexcept; + //SHDescriptorPool const& GetDescriptorPool(void) const noexcept; + + friend class SHVkInstance; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.cpp b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.cpp new file mode 100644 index 00000000..efe624a5 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.cpp @@ -0,0 +1,96 @@ +#include "SHPch.h" +#include "SHVkPhysicalDevice.h" +#include +#include +#include +#include "Tools/SHLogger.h" + +namespace SHADE +{ + SHVkPhysicalDevice::SHVkPhysicalDevice (vk::PhysicalDevice inDevice) noexcept + : vkPhysicalDevice{nullptr} + , deviceProperties{ } + , memoryProperties{ } + , queueFamilyProperties{ } + { + if (!inDevice) + { + SHLOG_ERROR("Trying to initialize a physical device but device passed in is nullptr! "); + return; + } + + vkPhysicalDevice = inDevice; + + vkPhysicalDevice.getProperties(&deviceProperties); + vkPhysicalDevice.getMemoryProperties(&memoryProperties); + + queueFamilyProperties = vkPhysicalDevice.getQueueFamilyProperties(); + + deviceFeatures = vkPhysicalDevice.getFeatures(); + } + + SHVkPhysicalDevice::SHVkPhysicalDevice(SHVkPhysicalDevice&& rhs) noexcept + : deviceProperties {rhs.deviceProperties} + , memoryProperties{ rhs.memoryProperties } + , deviceFeatures{ rhs.deviceFeatures } + , queueFamilyProperties{ rhs.queueFamilyProperties } + , vkPhysicalDevice{ std::move (rhs.vkPhysicalDevice)} + { + + } + + vk::PhysicalDeviceProperties const& SHVkPhysicalDevice::GetDeviceProperties(void) const noexcept +{ + return deviceProperties; + } + + std::vector const& SHVkPhysicalDevice::GetQueueFamilyProperties(void) const noexcept + { + return queueFamilyProperties; + } + + vk::PhysicalDeviceMemoryProperties const& SHVkPhysicalDevice::GetMemoryProperties(void) const noexcept + { + return memoryProperties; + } + + vk::DeviceSize SHVkPhysicalDevice::vkGetMaxHeapSize(void) const noexcept + { + // Get array of heaps from the properties + vk::MemoryHeap const* heapPtr = memoryProperties.memoryHeaps; + + if (heapPtr == nullptr) + return 0; + + // For looping through heaps + std::vector heapContainer(heapPtr, heapPtr + memoryProperties.memoryHeapCount); + + // Calculate biggest heap size among heaps of the device + VkDeviceSize biggestDeviceHeapSize = 0; + for (auto const& heap : heapContainer) + { + if (heap.size > biggestDeviceHeapSize) + biggestDeviceHeapSize = heap.size; + } + + return biggestDeviceHeapSize; + } + + vk::PhysicalDevice SHVkPhysicalDevice::GetVkPhysicalDevice(void) const noexcept + { + return vkPhysicalDevice; + } + + bool SHVkPhysicalDevice::ExtensionsSupported(std::vector const& requiredExtensions) const noexcept + { + std::vector availableExtensions{}; + availableExtensions = vkPhysicalDevice.enumerateDeviceExtensionProperties(); + + std::set missingExtensions(requiredExtensions.begin(), requiredExtensions.end()); + for (auto const& extension : availableExtensions) + missingExtensions.erase(extension.extensionName); + + return missingExtensions.empty(); + } + +} diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.h b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.h new file mode 100644 index 00000000..c47ab9a9 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDevice.h @@ -0,0 +1,43 @@ +#ifndef SH_PHYSICAL_DEVICE_H +#define SH_PHYSICAL_DEVICE_H + +#include +#include +#include "Graphics/Debugging/SHVulkanDebugUtil.h" + +namespace SHADE +{ + enum class SH_PHYSICAL_DEVICE_TYPE + { + BEST, + }; + + class SHVkPhysicalDevice + { + private: + vk::PhysicalDevice vkPhysicalDevice; + + vk::PhysicalDeviceProperties deviceProperties{}; + vk::PhysicalDeviceMemoryProperties memoryProperties{}; + vk::PhysicalDeviceFeatures deviceFeatures{}; + std::vector queueFamilyProperties{}; + + public: + SHVkPhysicalDevice(void) = delete; + SHVkPhysicalDevice(vk::PhysicalDevice inDevice) noexcept; + SHVkPhysicalDevice(SHVkPhysicalDevice const& rhs) noexcept = default; + SHVkPhysicalDevice(SHVkPhysicalDevice&& rhs) noexcept; + + SHVkPhysicalDevice& operator= (SHVkPhysicalDevice const& rhs) noexcept = default; + SHVkPhysicalDevice& operator= (SHVkPhysicalDevice&& rhs) noexcept = default; + + vk::PhysicalDeviceProperties const& GetDeviceProperties(void) const noexcept; + std::vector const& GetQueueFamilyProperties (void) const noexcept; + vk::PhysicalDeviceMemoryProperties const& GetMemoryProperties(void) const noexcept; + vk::DeviceSize vkGetMaxHeapSize(void) const noexcept; + vk::PhysicalDevice GetVkPhysicalDevice(void) const noexcept; + bool ExtensionsSupported(std::vector const& requiredExtensions) const noexcept; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDeviceLibrary.cpp b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDeviceLibrary.cpp new file mode 100644 index 00000000..511af8fc --- /dev/null +++ b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDeviceLibrary.cpp @@ -0,0 +1,189 @@ +#include "SHPch.h" +#include +#include +#include "SHVkPhysicalDeviceLibrary.h" +#include "Graphics/Instance/SHVkInstance.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + std::vector SHVkPhysicalDeviceLibrary::physicalDevices; + bool SHVkPhysicalDeviceLibrary::queried = false; + + /***************************************************************************/ + /*! + + \brief + Returns a c string that represents the physical device type passed in. + + \param type + Type of the physical device. + + \return + Returns a c string of the type. + + */ + /***************************************************************************/ + char const* const SHVkPhysicalDeviceLibrary::GetDeviceTypeName(vk::PhysicalDeviceType type) noexcept + { + switch (type) + { + case vk::PhysicalDeviceType::eDiscreteGpu: + return "Discrete Device"; + case vk::PhysicalDeviceType::eIntegratedGpu: + return "Integrated Device"; + default: + return "Unsupported Device"; + } + } + + /***************************************************************************/ + /*! + + \brief + Fills up physical device container. + + \param renderingInstance + Rendering Instance required to get physical devices. + + */ + /***************************************************************************/ + void SHVkPhysicalDeviceLibrary::QueryPhysicalDevices(bool printInfo /*= true*/) noexcept + { + physicalDevices = SHVkInstance::GetVkInstance().enumeratePhysicalDevices(); + if (physicalDevices.size() == 0) + { + SHLOGV_ERROR("Failed to detect physical devices. "); + } + + if (printInfo) + PrintAllPhysicalDevices(); + + // Set flag to indicate devices are queried and saved + queried = true; + } + + /***************************************************************************/ + /*! + + \brief + Attempts to get physical devices from the device library based on the + type passed in. + + \param gpuType + The type of physical device we want to get. + + \return + A subset of the queried physical devices that are of type gpuType. + + */ + /***************************************************************************/ + //std::vector> SHPhysicalDeviceLibrary::GetDevicesByType(VkPhysicalDeviceType gpuType, SHVulkanAPIVersion apiVersion) noexcept + //{ + // // If the application has not queried a physical device yet, don't return anything + // if (!queried) + // { + // SHUtil::ReportWarning("Cannot get any physical devices. Query the physical devices first. "); + // return {}; + // } + + // // Prep a container to store devices + // std::vector> devices; + + // // For all the devices queried, find ones that match the type passed in + // for (auto const& device : physicalDevices) + // { + // // Check for API version and device type + // if (device->GetDeviceProperties().apiVersion == static_cast(apiVersion) && device->GetDeviceProperties().deviceType == gpuType) + // { + // devices.push_back(device); + // } + // } + + // // Return list of devices (can be empty, meaning no devices have the type passed in) + // return devices; + //} + + /***************************************************************************/ + /*! + + \brief + Of all the discrete devices, get the best one. Right now, this just + checks the physical device heap size and uses the one with the highest. + + \param apiVersion + The API version that the device has the support. + + \return + The best physical device. + + */ + /***************************************************************************/ + vk::PhysicalDevice SHVkPhysicalDeviceLibrary::GetBestDevice(uint32_t apiVersion /*= SHVulkanAPIVersion::V_1_2*/) + { + if (!queried || physicalDevices.empty()) + return nullptr; + + vk::PhysicalDevice bestDevice = nullptr; + VkDeviceSize biggestHeapSize = 0; + for (auto const& device : physicalDevices) + { + // Check for API version and device type. Ignore if queried device doesn't support version passed in + if (device.getProperties().apiVersion < static_cast(apiVersion)) + continue; + + //VkDeviceSize biggestDeviceHeapSize = device.getMemoryProperties().; + + // Get array of heaps from the properties + vk::MemoryHeap const* heapPtr = device.getMemoryProperties().memoryHeaps; + + if (heapPtr == nullptr) + continue; + + // For looping through heaps + std::vector heapContainer(heapPtr, heapPtr + device.getMemoryProperties().memoryHeapCount); + + // Calculate biggest heap size among heaps of the device + VkDeviceSize biggestDeviceHeapSize = 0; + for (auto const& heap : heapContainer) + { + if (heap.size > biggestDeviceHeapSize) + biggestDeviceHeapSize = heap.size; + } + + // If the device contains a heap that is bigger than other cards calculated before + if (biggestDeviceHeapSize > biggestHeapSize) + { + // Make that device the best device + biggestHeapSize = biggestDeviceHeapSize; + bestDevice = device; + } + } + + return bestDevice; + } + + /***************************************************************************/ + /*! + + \brief + Prints all of the physical devices queried. Private function. For + internal use only. + + */ + /***************************************************************************/ + void SHVkPhysicalDeviceLibrary::PrintAllPhysicalDevices(void) noexcept + { + if (physicalDevices.empty()) + { + SHLOG_ERROR("No physical devices queried. Nothing to print. "); + return; + } + + SHLOG_ERROR("Successfully queried Physical Devices:"); + for (auto const& device : physicalDevices) + { + SHLOG_ERROR(std::string_view (std::string("\t-") + GetDeviceTypeName(device.getProperties().deviceType) + device.getProperties().deviceName.operator std::string())); + } + } +} diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDeviceLibrary.h b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDeviceLibrary.h new file mode 100644 index 00000000..79516987 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Devices/SHVkPhysicalDeviceLibrary.h @@ -0,0 +1,33 @@ +#ifndef SH_PHYSICAL_DEVICE_LIBRARY_H +#define SH_PHYSICAL_DEVICE_LIBRARY_H + +#include +#include +#include "Graphics/Debugging/SHVulkanDebugUtil.h" + +namespace SHADE +{ + // Rendering instance forward declaration + class SHVkInstance; + + class SHVkPhysicalDeviceLibrary + { + private: + //! Stores all the available GPUs (discrete + non-discrete) + static std::vector physicalDevices; + + //! If devices have been queried, this will be false + static bool queried; + + static char const* const GetDeviceTypeName(vk::PhysicalDeviceType type) noexcept; + static void PrintAllPhysicalDevices(void) noexcept; + + public: + static void QueryPhysicalDevices(bool printInfo) noexcept; + //static std::vector> GetDevicesByType(VkPhysicalDeviceType gpuType, SHVulkanAPIVersion apiVersion = SHVulkanAPIVersion::V_1_2) noexcept; + static vk::PhysicalDevice GetBestDevice(uint32_t apiVersion = VK_API_VERSION_1_3); + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.cpp b/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.cpp new file mode 100644 index 00000000..1386134f --- /dev/null +++ b/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.cpp @@ -0,0 +1,163 @@ +#include "SHPch.h" +#include "SHVkFramebuffer.h" +#include "Graphics/Images/SHVkImageView.h" +#include "Graphics/Images/SHVkImage.h" +#include "Graphics/Renderpass/SHVkRenderpass.h" +#include "Tools/SHLogger.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Non-default ctor. Initializes a framebuffer with attachments. + + \param logicalDevice + Required for framebuffer creation and destruction + + \param renderpass + Renderpass that the framebuffer will be compatible with. + + \param attachments + Attachments to be attached to the framebuffer. + + \return + + */ + /***************************************************************************/ + SHVkFramebuffer::SHVkFramebuffer(Handle const& inLogicalDeviceHdl, Handle const& renderpassHdl, std::vector> const& attachments, uint32_t inWidth, uint32_t inHeight) noexcept + : logicalDeviceHdl{inLogicalDeviceHdl} + , width {inWidth} + , height {inHeight} + { + // TODO: Verify if a check needs to be done to see if image dimensions are the same as the dimensions passed in + + for (auto& attachment : attachments) + { + // Not sure if its an error to pass in diff dimension images. + if (attachment->GetParentImage()->GetWidth() != (*attachments.begin())->GetParentImage()->GetWidth() || attachment->GetParentImage()->GetHeight() != (*attachments.begin())->GetParentImage()->GetHeight()) + { + SHLOG_ERROR("Dimensions of images not same as each other. Cannot create framebuffer."); + return; + } + } + + std::vector vkAttachments(attachments.size()); + + uint32_t i = 0; + for(auto const& attachment : attachments) + { + vkAttachments[i] = attachment->GetImageView(); + ++i; + } + + vk::FramebufferCreateInfo createInfo + { + .renderPass = renderpassHdl->GetVkRenderpass(), + .attachmentCount = static_cast(vkAttachments.size()), + .pAttachments = vkAttachments.data(), + .width = width, + .height = height, + .layers = 1 // TODO: Find out why this is 1 + }; + + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createFramebuffer(&createInfo, nullptr, &vkFramebuffer); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create framebuffer. "); + return; + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created framebuffer. "); + } + } + + SHVkFramebuffer::SHVkFramebuffer(SHVkFramebuffer&& rhs) noexcept + : vkFramebuffer{rhs.vkFramebuffer} + , logicalDeviceHdl {rhs.logicalDeviceHdl} + , width {rhs.width} + , height {rhs.height} + { + rhs.vkFramebuffer = VK_NULL_HANDLE; + } + + SHVkFramebuffer& SHVkFramebuffer::operator=(SHVkFramebuffer&& rhs) noexcept + { + if (&rhs == this) + return *this; + + vkFramebuffer = rhs.vkFramebuffer; + logicalDeviceHdl = rhs.logicalDeviceHdl; + width = rhs.width; + height = rhs.height; + + rhs.vkFramebuffer = VK_NULL_HANDLE; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Getter for the vulkan framebuffer handle. + + \return + The framebuffer handle. + + */ + /***************************************************************************/ + vk::Framebuffer SHVkFramebuffer::GetVkFramebuffer(void) const noexcept + { + return vkFramebuffer; + } + + /***************************************************************************/ + /*! + + \brief + Returns the framebuffer width. + + \return + The framebuffer width. + + */ + /***************************************************************************/ + uint32_t SHVkFramebuffer::GetWidth(void) const noexcept + { + return width; + } + + /***************************************************************************/ + /*! + + \brief + Returns the framebuffer height. + + \return + The framebuffer height. + + */ + /***************************************************************************/ + uint32_t SHVkFramebuffer::GetHeight(void) const noexcept + { + return height; + } + + /***************************************************************************/ + /*! + + \brief + Destroys the framebuffer. + + */ + /***************************************************************************/ + SHVkFramebuffer::~SHVkFramebuffer(void) noexcept + { + if (vkFramebuffer) + logicalDeviceHdl->GetVkLogicalDevice().destroyFramebuffer(vkFramebuffer, nullptr); + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.h b/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.h new file mode 100644 index 00000000..fa9161e8 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Framebuffer/SHVkFramebuffer.h @@ -0,0 +1,50 @@ +#ifndef SH_VK_FRAMEBUFFER_H +#define SH_VK_FRAMEBUFFER_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" +#include + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkRenderpass; + class SHVkImageView; + + /* + Renderpasses are really only meant to hold attachments. What writes are reads from it are determined by + attachments references in subpasses. + */ + class SHVkFramebuffer + { + private: + //! The vulkan framebuffer handle + vk::Framebuffer vkFramebuffer; + + //! Logical device for destruction later on + Handle logicalDeviceHdl; + + //! Width of the images in the framebuffer + uint32_t width; + + //! Height of the images in the framebuffer + uint32_t height; + + public: + SHVkFramebuffer(Handle const& inLogicalDeviceHdl, Handle const& renderpassHdl, std::vector> const& attachments, uint32_t inWidth, uint32_t inHeight) noexcept; + ~SHVkFramebuffer(void) noexcept; + + SHVkFramebuffer(SHVkFramebuffer&& rhs) noexcept; + SHVkFramebuffer& operator=(SHVkFramebuffer&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + vk::Framebuffer GetVkFramebuffer(void) const noexcept; + uint32_t GetWidth (void) const noexcept; + uint32_t GetHeight (void) const noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Images/SHImageViewDetails.h b/SHADE_Engine/src/Graphics/Images/SHImageViewDetails.h new file mode 100644 index 00000000..ab68959e --- /dev/null +++ b/SHADE_Engine/src/Graphics/Images/SHImageViewDetails.h @@ -0,0 +1,44 @@ +#ifndef SH_IMAGE_VIEW_DETAILS_H +#define SH_IMAGE_VIEW_DETAILS_H + +#include "Graphics/SHVulkanIncludes.h" + +namespace SHADE +{ + struct SHImageViewDetails + { + //! Image view type + vk::ImageViewType viewType; + + //! If the image is created with VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT bit, this format can be different from parent image + vk::Format format; + + //! The subresource mask of the image view + //! NOTE: It seems that from the specification, the image view does not have the agency to choose its own subresource aspect mask. + //! For simple use cases, the image formats corresponds to the aspect mask required : color to color, depth to depth, stencil to stencil. + //! However, because there are simply too many formats to prevent users from passing in the wrong arguments, we are letting them do it anyway. + //! Just keep this in mind. + vk::ImageAspectFlags imageAspectFlags; + + //! base mip level + uint32_t baseMipLevel; + + //! How many mips the image view has access to + uint32_t mipLevelCount; + + //! Base array layer (layer is the array index) + uint32_t baseArrayLayer; + + //! How many layers the image view has access to + uint32_t layerCount; + + //SHImageViewDetails(void) noexcept = default; + + //SHImageViewDetails(SHImageViewDetails const& rhs) noexcept = default; + //SHImageViewDetails& operator=(SHImageViewDetails const& rhs) noexcept = default; + + //SHImageViewDetails(SHImageViewDetails&& rhs) noexcept = default; + //SHImageViewDetails& operator=(SHImageViewDetails&& rhs) noexcept = default; + }; } + +#endif diff --git a/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp b/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp new file mode 100644 index 00000000..11fbe5dd --- /dev/null +++ b/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp @@ -0,0 +1,260 @@ +#include "SHPch.h" +#include "SHVkImage.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Debugging/SHVulkanDebugUtil.h" +#include "Tools/SHLogger.h" +#include "SHVkImageView.h" +#include "Graphics/Instance/SHVkInstance.h" + +namespace SHADE +{ + SHVkImage::SHVkImage( + VmaAllocator const& vmaAllocator, + SHImageCreateParams const& imageDetails, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags + ) noexcept + : imageType { imageDetails.imageType } + , width{ imageDetails.width } + , height{ imageDetails.height } + , depth{ imageDetails.depth } + , mipLevelCount{ imageDetails.levels } + , layerCount{ imageDetails.arrayLayers } + , imageFormat{ imageDetails.imageFormat } + , usageFlags{} + , createFlags{} + { + for (auto& bit : imageDetails.usageBits) + usageFlags |= bit; + + for (auto& bit : imageDetails.createBits) + createFlags |= bit; + + // If marked as 2D array compatible, image type MUST be 3D + if (createFlags & vk::ImageCreateFlagBits::e2DArrayCompatible) + { + if (imageType != vk::ImageType::e3D) + { + SHLOG_ERROR("Image is marked to be 2D Array compatible. Image type MUST hence be 3D. "); + return; + } + } + + vk::ImageCreateInfo imageCreateInfo{}; + imageCreateInfo.imageType = imageType; + imageCreateInfo.extent.width = width; + imageCreateInfo.extent.height = height; + imageCreateInfo.extent.depth = 1; + imageCreateInfo.mipLevels = mipLevelCount; + imageCreateInfo.arrayLayers = layerCount; + imageCreateInfo.format = imageFormat; + imageCreateInfo.tiling = vk::ImageTiling::eOptimal; + imageCreateInfo.initialLayout = vk::ImageLayout::eUndefined; + imageCreateInfo.usage = usageFlags; + imageCreateInfo.sharingMode = vk::SharingMode::eExclusive; + imageCreateInfo.samples = vk::SampleCountFlagBits::e1; + imageCreateInfo.flags = createFlags; + + + // Prepare allocation parameters for call to create images later + VmaAllocationCreateInfo allocCreateInfo{}; + allocCreateInfo.usage = memUsage; + allocCreateInfo.flags = allocFlags; + + VmaAllocationInfo allocInfo{}; + + VkImage tempImage; + vmaCreateImage(vmaAllocator, &imageCreateInfo.operator VkImageCreateInfo&(), &allocCreateInfo, &tempImage, &alloc, &allocInfo); + vkImage = tempImage; + + //if (allocFlags & ) + } + + /***************************************************************************/ + /*! + + \brief + This is mainly used for images that aren't created internally because + they cannot be created in the traditional way (e.g. swapchain images). + + \param inVkImage + Image already created outside + + \param width + Width of the image + + \param height + Height of the image + + \param depth + Depth of the image + + \param levels + Number of levels in the image + + \param arrayLayers + if the image is an array, this value will be > 1. + + \param imageFormat + Format of the image + + */ + /***************************************************************************/ + SHVkImage::SHVkImage(vk::Image inVkImage, vk::ImageType type, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint32_t arrayLayers, uint8_t levels, vk::Format format, vk::ImageUsageFlags flags) noexcept + : vkImage (inVkImage) + , width{ inWidth } + , height{ inHeight } + , depth{ inDepth } + , mipLevelCount{ levels } + , layerCount{ arrayLayers } + , imageFormat{ format } + , usageFlags{flags} + , alloc{} + , imageType{type} + , createFlags{} + { + } + + SHVkImage::SHVkImage(VmaAllocator const& vmaAllocator, uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) noexcept + : width {w} + , height{h} + , depth {1} + , layerCount{1} + , mipLevelCount{levels} + , imageFormat{format} + , usageFlags{usage} + , createFlags {create} + { + vk::ImageCreateInfo imageCreateInfo{}; + imageCreateInfo.imageType = vk::ImageType::e2D; + imageCreateInfo.extent.width = width; + imageCreateInfo.extent.height = height; + imageCreateInfo.extent.depth = depth; + imageCreateInfo.mipLevels = mipLevelCount; + imageCreateInfo.arrayLayers = layerCount; + imageCreateInfo.format = imageFormat; + imageCreateInfo.tiling = vk::ImageTiling::eOptimal; + imageCreateInfo.initialLayout = vk::ImageLayout::eUndefined; + imageCreateInfo.usage = usageFlags; + imageCreateInfo.sharingMode = vk::SharingMode::eExclusive; + imageCreateInfo.samples = vk::SampleCountFlagBits::e1; + imageCreateInfo.flags = createFlags; + + + // Prepare allocation parameters for call to create images later + VmaAllocationCreateInfo allocCreateInfo{}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + allocCreateInfo.flags = {}; // TODO: Make sure the vk::MemoryPropertyFlags returned from vmaGetAllocationMemoryProperties has the device local bit set + + VmaAllocationInfo allocInfo{}; + + VkImage tempImage; + auto result = vmaCreateImage(vmaAllocator, &imageCreateInfo.operator VkImageCreateInfo & (), &allocCreateInfo, &tempImage, &alloc, &allocInfo); + vkImage = tempImage; + + if (result != VK_SUCCESS) + SHVulkanDebugUtil::ReportVkError(vk::Result(result), "Failed to create vulkan image. "); + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created image. "); + } + + Handle SHVkImage::CreateImageView(Handle const& inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) const noexcept + { + return SHVkInstance::GetResourceManager().Create(inLogicalDeviceHdl, parent, createParams); + } + + void SHVkImage::LinkWithExteriorImage(vk::Image inVkImage, vk::ImageType type, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint32_t layers, uint8_t levels, vk::Format format, vk::ImageUsageFlags flags) noexcept + { + vkImage = inVkImage; + width = inWidth; + height = inHeight; + depth = inDepth; + mipLevelCount = levels; + layerCount = layers; + imageFormat = format; + usageFlags = flags; + imageType = type; + imageFormat = format; + } + + /***************************************************************************/ + /*! + + \brief + Getter for vulkan image handle. + + \return + the vulkan image handle. + + */ + /***************************************************************************/ + vk::Image SHVkImage::GetVkImage(void) const noexcept + { + return vkImage; + } + + /***************************************************************************/ + /*! + + \brief + Getter for vulkan image create flags. + + \return + The bitwise combination of create flag bits. + + */ + /***************************************************************************/ + vk::ImageCreateFlags SHVkImage::GetImageeCreateFlags(void) const noexcept + { + return createFlags; + } + + /***************************************************************************/ + /*! + + \brief + Getter for vulkan image format. + + \return + The image format. + + */ + /***************************************************************************/ + vk::Format SHVkImage::GetImageFormat(void) const noexcept + { + return imageFormat; + } + + /***************************************************************************/ + /*! + + \brief + Getter for image width. + + \return + The image width. + + */ + /***************************************************************************/ + uint32_t SHVkImage::GetWidth(void) const noexcept + { + return width; + } + + /***************************************************************************/ + /*! + + \brief + Getter for image height + + \return + The image height. + + */ + /***************************************************************************/ + uint32_t SHVkImage::GetHeight(void) const noexcept + { + return height; + } + +} diff --git a/SHADE_Engine/src/Graphics/Images/SHVkImage.h b/SHADE_Engine/src/Graphics/Images/SHVkImage.h new file mode 100644 index 00000000..53066075 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Images/SHVkImage.h @@ -0,0 +1,122 @@ +#ifndef SH_VK_IMAGE_H +#define SH_VK_IMAGE_H + +#include "SHImageViewDetails.h" +#include "Graphics/SHVulkanDefines.h" +#include "Resource/ResourceLibrary.h" +#include "vk_mem_alloc.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkImageView; + + struct SHImageCreateParams + { + //! 1D, 2D or 3D + vk::ImageType imageType; + + //! Width of the image + uint32_t width; + + //! Height of the image + uint32_t height; + + //! Depth of the image + uint32_t depth; + + //! Number of mipmaps + uint8_t levels; + + //! If image is standalone, this will be 0. + uint32_t arrayLayers = 0; + + //! format of the image + vk::Format imageFormat; + + //! Image usage bits + std::span usageBits; + + //! Image create flags + std::span createBits; + }; + + class SHVkImage + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! 1D, 2D or 3D + vk::ImageType imageType = vk::ImageType::e2D; + + //! Width of the image + uint32_t width{ 0 }; + + //! Height of the image + uint32_t height{ 0 }; + + //! Depth of the image + uint32_t depth{ 0 }; + + //! If image is standalone, this will be 0. + uint32_t layerCount = 0; + + //! Number of mipmaps + uint8_t mipLevelCount{ 1 }; + + //! format of the image + vk::Format imageFormat{}; + + //! Image usage bits + vk::ImageUsageFlags usageFlags{}; + + //! Image create flags + vk::ImageCreateFlags createFlags{}; + + //! Vulkan image handle + vk::Image vkImage{}; + + //! allocation object containing details of an allocation + VmaAllocation alloc{}; + + public: + /*-----------------------------------------------------------------------*/ + /* CTOR AND DTOR */ + /*-----------------------------------------------------------------------*/ + SHVkImage(void) noexcept = default; + + // TODO: Might need to add flags to parameters + SHVkImage( + VmaAllocator const& vmaAllocator, + SHImageCreateParams const& imageDetails, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags + ) noexcept; + + SHVkImage(vk::Image inVkImage, vk::ImageType type, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint32_t arrayLayers, uint8_t levels, vk::Format format, vk::ImageUsageFlags flags) noexcept; + SHVkImage(VmaAllocator const& vmaAllocator, uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) noexcept; + + SHVkImage(SHVkImage&& rhs) noexcept = default; + SHVkImage& operator=(SHVkImage && rhs) noexcept = default; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + Handle CreateImageView(Handle const& inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) const noexcept; + + /*-----------------------------------------------------------------------*/ + /* GETTERS AND SETTERS */ + /*-----------------------------------------------------------------------*/ + void LinkWithExteriorImage(vk::Image inVkImage, vk::ImageType type, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint32_t arrayLayers, uint8_t levels, vk::Format format, vk::ImageUsageFlags flags) noexcept; + + vk::Image GetVkImage (void) const noexcept; + vk::ImageCreateFlags GetImageeCreateFlags (void) const noexcept; + vk::Format GetImageFormat (void) const noexcept; + uint32_t GetWidth (void) const noexcept; + uint32_t GetHeight (void) const noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Images/SHVkImageView.cpp b/SHADE_Engine/src/Graphics/Images/SHVkImageView.cpp new file mode 100644 index 00000000..9d12d7cd --- /dev/null +++ b/SHADE_Engine/src/Graphics/Images/SHVkImageView.cpp @@ -0,0 +1,128 @@ +#include "SHPch.h" +#include "SHVkImageView.h" +#include "SHVkImage.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Non-default ctor. Initializes image view with image that it is a view of. + + \param parent + Parent image the view is a view of. + + */ + /***************************************************************************/ + SHVkImageView::SHVkImageView(Handle const& inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) noexcept + : parentImage{ } + , vkImageView{} + , imageViewDetails{} + , logicalDeviceHdl {inLogicalDeviceHdl} + { + auto parentImageCreateFlags = parent->GetImageeCreateFlags(); + + // 2D array image type means parent image must be 2D array compatible + if (createParams.viewType == vk::ImageViewType::e2DArray) + { + if (!(parentImageCreateFlags & vk::ImageCreateFlagBits::e2DArrayCompatible)) + { + SHLOG_ERROR("Failed to create image view. Parent image not 2D array compatible. "); + return; + } + } + + // Check if its possible for the image view to have different format than parent image + if (createParams.format != parent->GetImageFormat()) + { + if (!(parentImageCreateFlags & vk::ImageCreateFlagBits::eMutableFormat)) + { + SHLOG_ERROR("Failed to create image view. Format for image view not same as image but image not initialized with mutable format bit. "); + return; + } + } + + + vk::ImageViewCreateInfo viewCreateInfo + { + .pNext = nullptr, // Can be used to override with a VkImageViewUsageCreateInfo to override usage. See Vulkan spec page 877 for more information + .image = parent->GetVkImage(), + .viewType = createParams.viewType, + .format = createParams.format, + .components + { + .r = vk::ComponentSwizzle::eR, + .g = vk::ComponentSwizzle::eG, + .b = vk::ComponentSwizzle::eB, + .a = vk::ComponentSwizzle::eA, + }, + .subresourceRange + { + .aspectMask = createParams.imageAspectFlags, + .baseMipLevel = createParams.baseMipLevel, + .levelCount = createParams.mipLevelCount, + .baseArrayLayer = createParams.baseArrayLayer, + .layerCount = createParams.layerCount, + }, + }; + + if (auto result = inLogicalDeviceHdl->GetVkLogicalDevice().createImageView(&viewCreateInfo, nullptr, &vkImageView); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create image view! "); + return; + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created image view. "); + } + + // After success, THEN assign variables + parentImage = parent; + imageViewDetails = createParams; + } + + SHVkImageView::SHVkImageView(SHVkImageView&& rhs) noexcept + : vkImageView {std::move (rhs.vkImageView)} + , parentImage {std::move (rhs.parentImage)} + , imageViewDetails{std::move (rhs.imageViewDetails)} + , logicalDeviceHdl{ rhs.logicalDeviceHdl } // just copy doesn't matter + { + rhs.vkImageView = VK_NULL_HANDLE; + } + + + Handle const& SHVkImageView::GetParentImage(void) const noexcept + { + return parentImage; + } + + vk::ImageView SHVkImageView::GetImageView(void) const noexcept + { + return vkImageView; + } + + SHVkImageView& SHVkImageView::operator=(SHVkImageView&& rhs) noexcept + { + if (&rhs == this) + return *this; + + vkImageView = rhs.vkImageView; + parentImage = std::move(rhs.parentImage); + imageViewDetails = std::move(rhs.imageViewDetails); + logicalDeviceHdl = rhs.logicalDeviceHdl; // just copy doesn't matter + + rhs.vkImageView = VK_NULL_HANDLE; + + return *this; + } + + SHVkImageView::~SHVkImageView(void) noexcept + { + if (vkImageView) + logicalDeviceHdl->GetVkLogicalDevice().destroyImageView(vkImageView, nullptr); + } + +} diff --git a/SHADE_Engine/src/Graphics/Images/SHVkImageView.h b/SHADE_Engine/src/Graphics/Images/SHVkImageView.h new file mode 100644 index 00000000..afa357ef --- /dev/null +++ b/SHADE_Engine/src/Graphics/Images/SHVkImageView.h @@ -0,0 +1,43 @@ +#ifndef SH_VK_IMAGE_VIEW_H +#define SH_VK_IMAGE_VIEW_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" +#include "SHImageViewDetails.h" + +namespace SHADE +{ + class SHVkImage; + class SHVkLogicalDevice; + + class SHVkImageView + { + private: + //! Handle to vulkan image view + vk::ImageView vkImageView; + + //! We want to to keep a reference to the parent + Handle parentImage; + + //! For storing image view details + SHImageViewDetails imageViewDetails; + + //! Logical Device needed for creation and destruction + Handle logicalDeviceHdl; + + public: + SHVkImageView(Handle const& inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) noexcept; + ~SHVkImageView(void) noexcept; + SHVkImageView(SHVkImageView&& rhs) noexcept; + SHVkImageView& operator=(SHVkImageView&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* GETTERS AND SETTERS */ + /*-----------------------------------------------------------------------*/ + Handle const& GetParentImage(void) const noexcept; + vk::ImageView GetImageView (void) const noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Instance/SHVkInstance.cpp b/SHADE_Engine/src/Graphics/Instance/SHVkInstance.cpp new file mode 100644 index 00000000..6688f9a6 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Instance/SHVkInstance.cpp @@ -0,0 +1,266 @@ +#include "SHPch.h" +#include "SHVkInstance.h" +#include "Graphics/Debugging/SHValidationLayersQuery.h" +#include "Graphics/Debugging/SHVkDebugMessenger.h" +#include "Graphics/Devices/SHVkPhysicalDeviceLibrary.h" +#include "Tools/SHLogger.h" +#include "Graphics/Devices/SHVkPhysicalDeviceLibrary.h" +//#include + +namespace SHADE +{ + + bool SHVkInstance::debugOn; + bool SHVkInstance::renderdocOn; + bool SHVkInstance::validationLayersOn; + vk::Instance SHVkInstance::vkInstance; + SHVkDebugMessenger SHVkInstance::debugMessenger; + ResourceManager SHVkInstance::resourceManager; + + /***************************************************************************/ + /*! + + \brief + Returns a basic list of extensions for the instance initialization. + + \return + Returns a vector of extensions. + + */ + /***************************************************************************/ + std::vector SHVkInstance::GetExtensions(void) noexcept + { + // win32 will be used for windowing + std::vector extensions + { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_WIN32_SURFACE_EXTENSION_NAME + }; + + // If debug flag is true, push back the extension for debug messenger + if (debugOn) + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + // Returns the extensions + return extensions; + } + + /***************************************************************************/ + /*! + + \brief + Private function to initialize a vulkan application info struct. + + \param[in, out] appInfo + The app info struct to modify. + + */ + /***************************************************************************/ + void SHVkInstance::InitializeAppInfo(vk::ApplicationInfo& appInfo) noexcept + { + appInfo.pApplicationName = "SHADE Engine"; + appInfo.applicationVersion = 1; + appInfo.pNext = nullptr; + appInfo.pEngineName = "SHADE Engine"; + appInfo.engineVersion = 1; + + // default 1.3 (latest as of 24th May 2022) + appInfo.apiVersion = VK_API_VERSION_1_3; + } + + /***************************************************************************/ + /*! + + \brief + Simply calls a function from the physical device library to query for + all physical devices. + + */ + /***************************************************************************/ + void SHVkInstance::InitializePhysicalDevices(void) noexcept + { + SHVkPhysicalDeviceLibrary::QueryPhysicalDevices(debugOn); + } + + + /***************************************************************************/ + /*! + + \brief + Does a few things: + - Initializes an instance with extensions and (depending on flag) + validation layers. + - Initializes a debug messenger + - Query physical devices + + \param inDebugOn + If true, debug messenger will be created. + + \param inRenderdocOn + If true, renderdoc will be added to validation layers. + + \param inValidationOn + If true, validation layers will be enabled. + + */ + /***************************************************************************/ + void SHVkInstance::Init (bool inDebugOn, bool inRenderdocOn, bool inValidationOn) noexcept + { + // Init some members + debugOn = inDebugOn; + renderdocOn = inRenderdocOn; + validationLayersOn = inValidationOn; + + // Populate application info struct + vk::ApplicationInfo appInfo; + InitializeAppInfo(appInfo); + + // Get extensions + const auto extensions = GetExtensions(); + + // Prepare for instance creation. + vk::InstanceCreateInfo instanceInfo; + instanceInfo.pApplicationInfo = &appInfo; + instanceInfo.ppEnabledExtensionNames = extensions.size() ? extensions.data() : nullptr; + instanceInfo.enabledExtensionCount = static_cast(extensions.size()); + + // Load pointers to instance-independent extension functions + vk::DynamicLoader dl; + const PFN_vkGetInstanceProcAddr VK_GET_INSTANCE_PROC_ADDR = dl.getProcAddress("vkGetInstanceProcAddr"); + VULKAN_HPP_DEFAULT_DISPATCHER.init(VK_GET_INSTANCE_PROC_ADDR); + vkGetInstanceProcAddr(nullptr, ""); // HACK: HPP extension functions break without this line, do not remove! + + // If validation layers are on + if (validationLayersOn) + { + // Generate validation layers + SHValidationLayersQuery::GenerateAvailableLayers(renderdocOn); + const auto& layers = SHValidationLayersQuery::GetAvailableLayers(); + + instanceInfo.enabledLayerCount = static_cast(layers.size()); + instanceInfo.ppEnabledLayerNames = layers.size() ? layers.data() : nullptr; + } + else + { + instanceInfo.enabledLayerCount = 0; + instanceInfo.ppEnabledLayerNames = nullptr; + } + + // if debug is on, create debug messenger for instance creation. + if (debugOn) + { + vk::DebugUtilsMessengerCreateInfoEXT instanceDbgInfo; + SHVkDebugMessenger::InitializeDebugCreateInfo(instanceDbgInfo, + SHVkDebugMessenger::GenMessengerSeverity(SH_DEBUG_MSG_SEV::S_VERBOSE, SH_DEBUG_MSG_SEV::S_WARNING, SH_DEBUG_MSG_SEV::S_ERROR), + SHVkDebugMessenger::GenMessengerType(SH_DEBUG_MSG_TYPE::T_GENERAL, SH_DEBUG_MSG_TYPE::T_VALIDATION, SH_DEBUG_MSG_TYPE::T_PERFORMANCE)); + + instanceDbgInfo.pfnUserCallback = SHVulkanDebugUtil::GenericDebugCallback; + instanceInfo.pNext = static_cast(&instanceDbgInfo); + } + + // Finally create the instance + if (vk::Result result = vk::createInstance(&instanceInfo, nullptr, &vkInstance); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Vulkan instance! "); + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created a Vulkan Instance. "); + } + + // Load pointers to instance-specific extension functions + VULKAN_HPP_DEFAULT_DISPATCHER.init(vkInstance); + + + // Initialize a debug messenger + debugMessenger.Initialize(); + + // Query for physical devices + InitializePhysicalDevices(); + } + + /***************************************************************************/ + /*! + + \brief + Destroys the vulkan instance. + + */ + /***************************************************************************/ + void SHVkInstance::Destroy(void) noexcept + { + vkInstance.destroy(nullptr); + } + + /***************************************************************************/ + /*! + + \brief + Creates a physical device and returns a handle to it. + + \param inPhysicalDevice + A Vulkan physical device handle. + + \return + A SHADE handle object encapsulating a SHADE wrapper for a vulkan + physical device. + + */ + /***************************************************************************/ + Handle SHVkInstance::CreatePhysicalDevice(SH_PHYSICAL_DEVICE_TYPE type) + { + switch (type) + { + case SH_PHYSICAL_DEVICE_TYPE::BEST: + return resourceManager.Create(SHVkPhysicalDeviceLibrary::GetBestDevice()); + default: + return resourceManager.Create(SHVkPhysicalDeviceLibrary::GetBestDevice()); + } + } + + /***************************************************************************/ + /*! + + \brief + Creates a logical device and returns a handle to it. + + \param queueCreateParams + The number of queues to create from the logical device. + + \param physicalDevice + The physical device to create the logical device from. + + \return + The newly created logical device. + + */ + /***************************************************************************/ + Handle SHVkInstance::CreateLogicalDevice(std::initializer_list queueCreateParams, Handle const& physicalDeviceHdl) + { + auto newHdl = resourceManager.Create(queueCreateParams, physicalDeviceHdl); + newHdl->InitializeQueues(queueCreateParams); + return newHdl; + } + + /***************************************************************************/ + /*! + + \brief + Getter for the underlying vulkan instance. + + \return + The underlying vulkan instance. + + */ + /***************************************************************************/ + vk::Instance const& SHVkInstance::GetVkInstance(void) noexcept + { + return vkInstance; + } + + ResourceManager& SHVkInstance::GetResourceManager(void) noexcept + { + return resourceManager; + } + +} diff --git a/SHADE_Engine/src/Graphics/Instance/SHVkInstance.h b/SHADE_Engine/src/Graphics/Instance/SHVkInstance.h new file mode 100644 index 00000000..a6fb4f0f --- /dev/null +++ b/SHADE_Engine/src/Graphics/Instance/SHVkInstance.h @@ -0,0 +1,93 @@ +/**************************************************************************//*! +\file SHVkInstance.h +\author Brandon Mak +\par email: brandon.hao@digipen.edu +\date 16th May 2022 +\brief Stores the declaration of SHVkInstance class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior +written consent of DigiPen Institute of Technology is prohibited. + +*//***************************************************************************/ + +#ifndef SH_VK_INSTANCE_H +#define SH_VK_INSTANCE_H + +#include // std::vector +#include "Graphics/Debugging/SHVkDebugMessenger.h" +#include "Graphics/Devices/SHVkPhysicalDevice.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Resource/ResourceLibrary.h" + + +namespace SHADE +{ + //class SHVkLogicalDevice; + //class SHVkPhysicalDevice; + + /*!************************************************************************** + + \class SHVkInstance + + \brief + Mainly used for initializing a Vulkan Instance. + + ***************************************************************************/ + class SHVkInstance + { + private: + /*-----------------------------------------------------------------------*/ + /* DATA MEMBERS */ + /*-----------------------------------------------------------------------*/ + + //! Whether or not the debug messenger is active or not + static bool debugOn; + + //! Whether or not renderdoc is on or not. Changing this flag would + //! propagate a restart of the rendering system + static bool renderdocOn; + + //! Whether or not validation layers are enabled. + static bool validationLayersOn; + + //! Handle to the actual vulkan instance + static vk::Instance vkInstance; + + //! Debug messenger (this is not a handle (or at least I feel it shouldn't + //! be) is because it is contained within the instance class and not used + //! outside. When the instance gets destroyed, the messenger is destroyed + //! together with it + static SHVkDebugMessenger debugMessenger; + + //! Resource management for vulkan project + static ResourceManager resourceManager; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + static std::vector GetExtensions (void) noexcept; + static void InitializeAppInfo (vk::ApplicationInfo& appInfo) noexcept; + static void InitializePhysicalDevices (void) noexcept; + + public: + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + static void Init (bool inDebugOn, bool inRenderdocOn, bool inValidationOn) noexcept; + static void Destroy (void) noexcept; + + static Handle CreatePhysicalDevice (SH_PHYSICAL_DEVICE_TYPE type); + static Handle CreateLogicalDevice (std::initializer_list queueCreateParams, Handle const& physicalDeviceHdl); + + /*-----------------------------------------------------------------------*/ + /* Getters and Setters */ + /*-----------------------------------------------------------------------*/ + static vk::Instance const& GetVkInstance (void) noexcept; + static ResourceManager& GetResourceManager(void) noexcept; + }; + +} + +#endif diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp new file mode 100644 index 00000000..ad70a18f --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -0,0 +1,238 @@ +/************************************************************************************//*! +\file SHGraphicsSystem.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 21, 2022 +\brief + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#include "SHPch.h" +#include "SHGraphicsSystem.h" + +#include "Graphics/Instance/SHVkInstance.h" +#include "Graphics/Windowing/Surface/SHVkSurface.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" +//#include "SHRenderer.h" +#include "Graphics/Windowing/SHWindow.h" +#include "Graphics/MiddleEnd/PerFrame/SHPerFrameData.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*---------------------------------------------------------------------------------*/ + SHGraphicsSystem::SHGraphicsSystem(SHWindow& window) + { + // Save the SHWindow + this->window = &window; + + // Set Up Instance + SHVkInstance::Init(true, true, true); + + // Get Physical Device and Construct Logical Device + // TODO: Provide configuration for these options + physicalDevice = SHVkInstance::CreatePhysicalDevice(SH_PHYSICAL_DEVICE_TYPE::BEST); + device = SHVkInstance::CreateLogicalDevice({ SHQueueParams(SH_Q_FAM::GRAPHICS, SH_QUEUE_SELECT::DEDICATED), SHQueueParams(SH_Q_FAM::TRANSFER, SH_QUEUE_SELECT::DEDICATED) }, physicalDevice); + + // Construct surface + surface = device->CreateSurface(window.GetHWND()); + + // Construct Swapchain + auto windowDims = window.GetWindowSize(); + swapchain = device->CreateSwapchain(surface, windowDims.first, windowDims.second, SHSwapchainParams + { + .surfaceImageFormats {vk::Format::eB8G8R8A8Unorm, vk::Format::eR8G8B8A8Unorm, vk::Format::eB8G8R8Unorm, vk::Format::eR8G8B8Unorm}, + .depthFormats {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + .presentModes {vk::PresentModeKHR::eFifo, vk::PresentModeKHR::eMailbox, vk::PresentModeKHR::eImmediate}, + .vsyncOn = false, // TODO: Set to true when shipping game + }); + + window.RegisterWindowSizeCallback([&]([[maybe_unused]] uint32_t width, [[maybe_unused]] uint32_t height) + { + renderContext.SetIsResized(true); + }); + + // Create graphics queue + queue = device->GetQueue(SH_Q_FAM::GRAPHICS, 0); + + + // Create Render Context + renderContext.Init + ( + device, + SHPerFrameDataParams + { + .swapchainHdl = swapchain, + .numThreads = 1, + .cmdPoolQueueFamilyType = SH_Q_FAM::GRAPHICS, + .cmdPoolResetMode = SH_CMD_POOL_RESET::POOL_BASED, + .cmdBufferTransient = true, + } + ); + + // Create Frame and Command Buffers + for (int i = 0; i < NUM_FRAME_BUFFERS; ++i) + { + //frameBuffers[i] = device->CreateFramebuffer(renderPass, { renderContext.GetFrameData(i).swapchainImageViewHdl }, windowDims[0], windowDims[1]); + SHPerFrameData& frameData = renderContext.GetFrameData(i); + + if (frameData.cmdPoolHdls.empty()) + throw std::runtime_error("No command pools available!"); + + Handle commandPool = frameData.cmdPoolHdls[0]; + //commandBuffers[i] = commandPool->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); // works + } + + + + + + /*-----------------------------------------------------------------------*/ + /* RENDERGRAPH TESTING */ + /*-----------------------------------------------------------------------*/ + + renderGraph.Init(device, swapchain); + renderGraph.AddResource("Position", SH_ATT_DESC_TYPE::COLOR, windowDims.first, windowDims.second, vk::Format::eR16G16B16A16Sfloat); + renderGraph.AddResource("Normals", SH_ATT_DESC_TYPE::COLOR, windowDims.first, windowDims.second, vk::Format::eR16G16B16A16Sfloat); + renderGraph.AddResource("Composite", SH_ATT_DESC_TYPE::COLOR, windowDims.first, windowDims.second, vk::Format::eR16G16B16A16Sfloat); + renderGraph.AddResource("Downscale", SH_ATT_DESC_TYPE::COLOR, windowDims.first, windowDims.second, vk::Format::eR16G16B16A16Sfloat); + renderGraph.AddResource("Present", SH_ATT_DESC_TYPE::COLOR_PRESENT, windowDims.first, windowDims.second); + auto node = renderGraph.AddNode("G-Buffer", { "Position", "Normals", "Composite" }, {}); // no predecessors + + // First subpass to write to G-Buffer + auto writeSubpass = node->AddSubpass("G-Buffer Write"); + writeSubpass->AddColorOutput("Position"); + writeSubpass->AddColorOutput("Normals"); + + // Second subpass to read from G-Buffer + auto compositeSubpass = node->AddSubpass("G-Buffer Composite"); + compositeSubpass->AddColorOutput("Composite"); + compositeSubpass->AddInput("Normals"); + compositeSubpass->AddInput("Position"); + + auto compositeNode = renderGraph.AddNode("Bloom", { "Composite", "Downscale", "Present"}, {"G-Buffer"}); + auto bloomSubpass = compositeNode->AddSubpass("Downsample"); + bloomSubpass->AddInput("Composite"); + bloomSubpass->AddColorOutput("Downscale"); + bloomSubpass->AddColorOutput("Present"); + + renderGraph.Generate(); + + + /*-----------------------------------------------------------------------*/ + /* RENDERGRAPH END TESTING */ + /*-----------------------------------------------------------------------*/ + } + SHGraphicsSystem::~SHGraphicsSystem() + { + //renderPass.Free(); + renderContext.Destroy(); + queue.Free(); + swapchain.Free(); + surface.Free(); + device.Free(); + + SHVkInstance::Destroy(); + } + + /*---------------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*---------------------------------------------------------------------------------*/ + /***************************************************************************/ + /*! + + \brief + Checks for window resize and acquire next image in swapchain. + + + + */ + /***************************************************************************/ + void SHGraphicsSystem::BeginRender() + { + auto windowDims = window->GetWindowSize(); + if (renderContext.GetResizeAndReset()) + { + device->WaitIdle(); + + // Resize the swapchain + swapchain->Resize(surface, windowDims.first, windowDims.second); + + renderContext.HandleResize(); + } + + const uint32_t CURR_FRAME_IDX = renderContext.GetCurrentFrame(); + + // #BackEndTest: For for the fence initialized by queue submit + renderContext.WaitForFence(); + + // #BackEndTest: Acquire the next image in the swapchain available + renderContext.AcquireNextIamge(); + + // #BackEndTest: Get the current frame from frame manager + auto& currFrameData = renderContext.GetCurrentFrameData(); + + // #BackEndTest: Reset command pool + if (currFrameData.cmdPoolHdls.empty()) + throw std::runtime_error("No command pools available!"); + currFrameData.cmdPoolHdls[0]->Reset(); + + } + + /***************************************************************************/ + /*! + + \brief + Check if need to resize and advance the frame in the render context. + + */ + /***************************************************************************/ + void SHGraphicsSystem::EndRender() + { + const uint32_t CURR_FRAME_IDX = renderContext.GetCurrentFrame(); + auto& currFrameData = renderContext.GetCurrentFrameData(); + + // #BackEndTest: Prepare to present current image + vk::PresentInfoKHR presentInfo{}; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &currFrameData.semRenderFinishHdl->GetVkSem(); + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapchain->GetVkSwapchain(); + presentInfo.pImageIndices = &CURR_FRAME_IDX; + + // #BackEndTest: queues an image for presentation + if (auto result = device->GetQueue(SH_Q_FAM::GRAPHICS, 0)->GetVkQueue().presentKHR(&presentInfo); result != vk::Result::eSuccess) + { + // If swapchain is incompatible/outdated + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR) + { + auto windowDims = window->GetWindowSize(); + swapchain->Resize(surface, windowDims.first, windowDims.second); + + renderContext.HandleResize(); + + } + } + + // #BackEndTest: Cycle frame count + renderContext.AdvanceFrame(); + } + //Handle SHGraphicsSystem::AddRenderer() + //{ + // return Handle(); + //} + //void SHGraphicsSystem::RemoveRenderer(Handle renderer) + //{ + //} + Handle SHGraphicsSystem::AddSegment(const VkViewport& viewport, Handle imageToUse) + { + return Handle(); + } + void SHGraphicsSystem::RemoveSegment(Handle segment) + { + } +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h new file mode 100644 index 00000000..e1dff379 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h @@ -0,0 +1,129 @@ +/************************************************************************************//*! +\file SHGraphicsSystem.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 21, 2022 +\brief + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +#include + +// Project Includes +#include "Resource/Handle.h" +#include "Graphics/SHVulkanIncludes.h" +#include "Graphics/MiddleEnd/PerFrame/SHRenderContext.h" +#include "Graphics/RenderGraph/SHRenderGraph.h" + +namespace SHADE +{ + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkPhysicalDevice; + class SHVkLogicalDevice; + class SHVkSurface; + class SHVkSwapchain; + class SHScreenSegment; + //class SHRenderer; + class SHWindow; + class SHVkImage; + class SHVkFramebuffer; + class SHVkCommandBuffer; + + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Represents an axis aligned box on the screen to render to along with the + specified Image to render to that spot. + */ + /***********************************************************************************/ + struct SHScreenSegment + { + VkViewport Viewport; + Handle ImageToUse; + }; + + /***********************************************************************************/ + /*! + \brief + Manages the lifecycle and provides an interface for rendering multiple objects to + portions of the screen. + */ + /***********************************************************************************/ + class SHGraphicsSystem + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + static constexpr int NUM_FRAME_BUFFERS = 3; + + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + SHGraphicsSystem(SHWindow& window); + ~SHGraphicsSystem(); + + /*-----------------------------------------------------------------------------*/ + /* Lifecycle Functions */ + /*-----------------------------------------------------------------------------*/ + void BeginRender(); + void EndRender(); + + /*-----------------------------------------------------------------------------*/ + /* Renderers Registration Functions */ + /*-----------------------------------------------------------------------------*/ + //Handle AddRenderer(); + //void RemoveRenderer(Handle renderer); + + /*-----------------------------------------------------------------------------*/ + /* Viewport Registration Functions */ + /*-----------------------------------------------------------------------------*/ + Handle AddSegment(const VkViewport& viewport, Handle imageToUse); + void RemoveSegment(Handle segment); + + /*-----------------------------------------------------------------------------*/ + /* Getters (Temporary) */ + /*-----------------------------------------------------------------------------*/ + Handle GetDevice() const { return device; } + Handle GetSwapchain() const { return swapchain; } + Handle GetSurface() const { return surface; } + //Handle GetRenderPass() const { return renderPass; } + + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + // Owned Resources + Handle physicalDevice; + Handle device; + Handle surface; + Handle swapchain; + Handle queue; + //Handle renderPass; // Potentially bring out? + std::vector screenSegments; + SHRenderContext renderContext; + //std::array, NUM_FRAME_BUFFERS> frameBuffers; + //std::array, NUM_FRAME_BUFFERS> commandBuffers; + // Not Owned Resources + SHWindow* window; + // Renderers + //Handle debugWorldRenderer; + //Handle debugScreenRenderer; + //std::vector renderers; + SHRenderGraph renderGraph; + + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderTarget.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderTarget.cpp new file mode 100644 index 00000000..eec4d28a --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderTarget.cpp @@ -0,0 +1,2 @@ +#include "SHPch.h" +#include "SHRenderTarget.h" diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderTarget.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderTarget.h new file mode 100644 index 00000000..f4764087 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderTarget.h @@ -0,0 +1,37 @@ +/************************************************************************************//*! +\file SHRenderTarget.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Aug 21, 2022 +\brief + + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +namespace SHADE +{ + /***********************************************************************************/ + /*! + \brief + Represents an object that rendering operations can be outputted to. These are + images in general but could be a separate texture or even a swapchain image. + */ + /***********************************************************************************/ + class SHRenderTarget + { + public: + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.cpp new file mode 100644 index 00000000..bbbdd015 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.cpp @@ -0,0 +1,80 @@ +#include "SHPch.h" +#include "SHPerFrameData.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Instance/SHVkInstance.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Initializes frame data. Everything here is created brand new from + the logical device except for the swapchain images (which are retrieved + from the swapchain). + + \param logicalDeviceHdl + Required to create objects + + \param params + Stores information required to initialize frame data like the swapchain + used and command pool parameters. + + \param inFrameIndex + Every frame will have an index. + + */ + /***************************************************************************/ + void SHPerFrameData::Recreate(Handle const& logicalDeviceHdl) noexcept + { + // Swapchain recreation means the images are just relinked to SHVkImages. Handles will remain the same. There is no need for this line. + //swapchainImageHdl = params.swapchainHdl->GetSwapchainImage(frameIndex); + + SHImageViewDetails viewDetails + { + .viewType = vk::ImageViewType::e2D, + .format = swapchainImageHdl->GetImageFormat(), + .imageAspectFlags = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .mipLevelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + // Create image views for the swapchain + swapchainImageViewHdl = swapchainImageHdl->CreateImageView(logicalDeviceHdl, swapchainImageHdl, viewDetails); + + // Create a fence + fenceHdl = logicalDeviceHdl->CreateFence(); + + // scope makes it easier to navigate + semImgAvailableHdl = logicalDeviceHdl->SHVkLogicalDevice::CreateSemaphore(); + semRenderFinishHdl = logicalDeviceHdl->SHVkLogicalDevice::CreateSemaphore(); + + } + + /***************************************************************************/ + /*! + + \brief + Destroys frame data. + + \param logicalDeviceHdl + Required for destruction. + + */ + /***************************************************************************/ + void SHPerFrameData::Destroy(void) + { + // swapchainImageHdl is non-owning, so we don't destroy that here + + // TODO: Non very user friendly to have objects created from the logical device but destroyed from the resource manager. Can perhaps refactor. + SHVkInstance::GetResourceManager().Free(swapchainImageViewHdl); + + // Destroy all the synchronization objects + fenceHdl.Free(); + semImgAvailableHdl.Free(); + semRenderFinishHdl.Free(); + } +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.h b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.h new file mode 100644 index 00000000..5ac38503 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHPerFrameData.h @@ -0,0 +1,73 @@ +#ifndef SH_PER_FRAME_DATA_H +#define SH_PER_FRAME_DATA_H + +#include "Graphics/Images/SHVkImage.h" +#include "Graphics/Images/SHVkImageView.h" +#include "Graphics/Commands/SHVkCommandBuffer.h" +#include "Graphics/Commands/SHVkCommandPool.h" +#include "Graphics/Synchronization/SHVkFence.h" +#include "Graphics/Synchronization/SHVkSemaphore.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkSwapchain; + + struct SHPerFrameDataParams + { + //! Swapchain to get image data + Handle swapchainHdl{}; + + //! Number of threads will dictate how many command pools we will have + uint32_t numThreads = 0; + + //! Queue family index for command pool creation + SH_Q_FAM cmdPoolQueueFamilyType{}; + + //! Reset mode for the command pool + SH_CMD_POOL_RESET cmdPoolResetMode = SH_CMD_POOL_RESET::POOL_BASED; + + // whether or not cmd buffers from command pool should be transient + bool cmdBufferTransient = true; + }; + + //! While there are other things that could also be shared between frames, there are certain things like command buffers that fit into more than 1 contexts. + //! The data here should only consists of data that is handled in a straightforward manner. + struct SHPerFrameData + { + + //! The actual swapchain image for the frame + Handle swapchainImageHdl; + + //! The image view of the image above + Handle swapchainImageViewHdl; + + //! Command pools for the frame. We need it this way because we don't want to reset a command pool when the GPU is using it. We want a container because + //! we want 1 for each thread. // TODO: Need to verify this. + std::vector> cmdPoolHdls; + + //! Every frame needs a fence + Handle fenceHdl; + + //! Semaphore for the GPU to signal when an image is available + Handle semImgAvailableHdl; + + //! Semaphore for the GPU to signal when rendering to an image has finished (used for signal sem in vkSubmitInfo and wait sem in VkPresentInfo) + Handle semRenderFinishHdl; + + //! Every index will have a frame index + uint32_t frameIndex{ 0 }; + + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + // These are made into functions (instead of ctor and dtor) because we want to call these functions again when we resize the window + void Recreate (Handle const& logicalDeviceHdl) noexcept; + void Destroy (void); + + friend class SHRenderContext; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.cpp new file mode 100644 index 00000000..f31653a8 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.cpp @@ -0,0 +1,207 @@ +#include "SHPch.h" +#include "SHRenderContext.h" +#include "Tools/SHLogger.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Frame manager ctor. Initializes the data for all frames. + + \param inLogicalDeviceHdl + Logical device required for frame initialization. + + \param params + params used for frame data creation. + + */ + /***************************************************************************/ + void SHRenderContext::Init(Handle const& inLogicalDeviceHdl, SHPerFrameDataParams const& params) noexcept + { + perFrameDataParams = params; + + logicalDeviceHdl = inLogicalDeviceHdl; + numThreads = params.numThreads; + currentFrame = 0; + + // initialize number of threads to 1 if user passes in 0 + if (params.numThreads == 0) + { + SHLOG_ERROR("Number of threads is 0. Using 1 thread instead. "); + numThreads = 1; + } + + auto const numImages = params.swapchainHdl->GetNumImages(); + + // Once the amount of frame data is reinitialized, we don't need to touch it anymore because swapchain recreation only necessitates + // reinitializing the data, not recreating. + frameData.resize(numImages); + for (uint32_t i = 0; i < numImages; ++i) + { + // assign a set non-changing frame index + frameData[i].frameIndex = i; + + // Initialize the swapchain image handle. See first line of recreation function in frame data. + frameData[i].swapchainImageHdl = params.swapchainHdl->GetSwapchainImage(i); + + for (uint32_t j = 0; j < params.numThreads; ++j) + { + frameData[i].cmdPoolHdls.push_back(logicalDeviceHdl->CreateCommandPool(params.cmdPoolQueueFamilyType, params.cmdPoolResetMode, params.cmdBufferTransient)); + } + + } + + // Initialize all the info. + for (auto& frame : frameData) + frame.Recreate(logicalDeviceHdl); + } + + void SHRenderContext::Destroy(void) noexcept + { + for (auto& data : frameData) + { + for (auto& hdl : data.cmdPoolHdls) + hdl.Free(); + + data.cmdPoolHdls.clear(); + + data.Destroy(); + + } + + frameData.clear(); + } + + SHRenderContext::SHRenderContext(void) noexcept + : numThreads {1} + , currentFrame{0} + { + + } + + SHRenderContext::~SHRenderContext(void) noexcept + { + } + + /***************************************************************************/ + /*! + + \brief + When the window resizes, the swapchain needs to be recreated. When that + happens, the swapchain images are also new, hence necessitating the need + to update the frame data image handles. + + + \return + + */ + /***************************************************************************/ + void SHRenderContext::HandleResize(void) noexcept + { + // Destroy the frame data + for (auto& frame : frameData) + frame.Destroy(); + + for (auto& frame : frameData) + frame.Recreate(logicalDeviceHdl); + + currentFrame = 0; + } + + /***************************************************************************/ + /*! + + \brief + Acquire the index of the next image in the swapchain. Do note that this + function is non-blocking but it takes in a semaphore that will be + signaled when the image becomes available. We want to use this together + with AdvanceFrame because this engine trusts the implementation + to return swapchain images IN ORDER. When that happens the correct + behavior would be that currentFrame will end up being the same as the + what returns from vkAcquireNextImageKHR. + + */ + /***************************************************************************/ + void SHRenderContext::AcquireNextIamge(void) noexcept + { + uint32_t frameIndex = 0; + logicalDeviceHdl->GetVkLogicalDevice().acquireNextImageKHR(perFrameDataParams.swapchainHdl->GetVkSwapchain(), std::numeric_limits::max(), frameData[currentFrame].semImgAvailableHdl->GetVkSem(), VK_NULL_HANDLE, &frameIndex); + if (frameIndex != currentFrame) + { + SHLOG_ERROR("Frame index retrieved from vkAcquireNextImageKHR is not the same as currentFrame."); + } + currentFrame = frameIndex; + } + + /***************************************************************************/ + /*! + + \brief + Waits for a fence to be signaled. + + */ + /***************************************************************************/ + bool SHRenderContext::WaitForFence(void) noexcept + { + return frameData[currentFrame].fenceHdl->Wait(true, std::numeric_limits::max()); + } + + /***************************************************************************/ + /*! + + \brief + Resets a fence. + + */ + /***************************************************************************/ + void SHRenderContext::ResetFence(void) noexcept + { + frameData[currentFrame].fenceHdl->Reset(); + } + + void SHRenderContext::SetIsResized(bool resized) noexcept + { + isResized = resized; + } + + /***************************************************************************/ + /*! + + \brief + Advances the frame. The correct frame to use can also be returned by + vkAcquireNextImageKHR so this is just an aiternative. + + */ + /***************************************************************************/ + void SHRenderContext::AdvanceFrame(void) noexcept + { + currentFrame = (currentFrame + 1u) % frameData.size(); + } + + SHPerFrameData& SHRenderContext::GetCurrentFrameData(void) noexcept + { + return frameData[currentFrame]; + } + + SHPerFrameData& SHRenderContext::GetFrameData(uint32_t index) noexcept + { + return frameData[index]; + } + + uint32_t SHRenderContext::GetCurrentFrame(void) const noexcept + { + return currentFrame; + } + + bool SHRenderContext::GetResizeAndReset(void) noexcept + { + bool b = isResized; + isResized = false; + return b; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.h b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.h new file mode 100644 index 00000000..7ed8cb82 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/PerFrame/SHRenderContext.h @@ -0,0 +1,63 @@ +#ifndef SH_RENDER_CONTEXT_H +#define SH_RENDER_CONTEXT_H + +#include +#include "SHPerFrameData.h" + +namespace SHADE +{ + class SHVkSwapchain; + class SHVkLogicalDevice; + + + + //! Think of a render context like the context in OpenGL. When you call draw calls, the drivers are calling + //! queue submissions and presenting functions at the back. Frame swapping is also done there. The only difference a + //! render context in SHADE engine has is that it requires users to call these explicitly in the middle end. While there + //! is little reason the flow of the render context should deviate from its intended usage, we want to leave it up to + //! users to explicitly call functions from here so we don't risk losing opportunities for different usage. + class SHRenderContext + { + private: + //! container of frame data. Note that the manager owns the data, but the frame data themselves do not own anything. + std::vector frameData; + + //! Logical Device for creation and deletion + Handle logicalDeviceHdl; + + //! We want to save this to handle resizing without passing in arguments. + SHPerFrameDataParams perFrameDataParams; + + //! Number of threads + uint32_t numThreads; + + //! current frame being used + uint32_t currentFrame; + + bool isResized{ false }; + + public: + SHRenderContext(void) noexcept; + ~SHRenderContext(void) noexcept; + + void Init (Handle const& inLogicalDeviceHdl, SHPerFrameDataParams const& params) noexcept; + void Destroy (void) noexcept; + void HandleResize (void) noexcept; + + void AcquireNextIamge (void) noexcept; + void AdvanceFrame (void) noexcept; + + bool WaitForFence (void) noexcept; + void ResetFence (void) noexcept; + + void SetIsResized (bool resized) noexcept; + + SHPerFrameData& GetCurrentFrameData(void) noexcept; + SHPerFrameData& GetFrameData (uint32_t index) noexcept; + uint32_t GetCurrentFrame (void) const noexcept; + bool GetResizeAndReset (void) noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.cpp new file mode 100644 index 00000000..23d6323c --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.cpp @@ -0,0 +1,69 @@ +#include "SHPch.h" +#include "SHShaderModuleLibrary.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Imports all shader binaries from the source library. + + \param logicalDeviceHdl + For creating shader modules. + + \param sourceLib + The source library class that stores the container of shader binary data. + + */ + /***************************************************************************/ + void SHShaderModuleLibrary::ImportFromSourceLibrary(Handle& logicalDeviceHdl, SHShaderSourceLibrary const& sourceLib) noexcept + { + auto const& sources = sourceLib.GetSourceLibrary(); + for (auto const& source : sources) + { + vk::ShaderStageFlagBits shaderType{}; + switch (source.shaderType) + { + case SH_SHADER_TYPE::VERTEX: + shaderType = vk::ShaderStageFlagBits::eVertex; + break; + case SH_SHADER_TYPE::FRAGMENT: + shaderType = vk::ShaderStageFlagBits::eFragment; + break; + case SH_SHADER_TYPE::COMPUTE: + shaderType = vk::ShaderStageFlagBits::eCompute; + break; + default: + shaderType = vk::ShaderStageFlagBits::eVertex; + break; + } + + Handle newShaderModule = logicalDeviceHdl->CreateShaderModule(source.spirvBinary, "main", shaderType, source.name); + shaderModules.emplace(source.id, newShaderModule); + stringToID.emplace(source.name, source.id); + } + } + + /***************************************************************************/ + /*! + + \brief + Gets the shader module based on module name. + + \param shaderName + + \return + + */ + /***************************************************************************/ + Handle SHShaderModuleLibrary::GetShaderModule(std::string shaderName) const noexcept + { + if (stringToID.contains(shaderName)) + return shaderModules.at(stringToID.at(shaderName)); + else + return {}; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.h b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.h new file mode 100644 index 00000000..ed942833 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderModuleLibrary.h @@ -0,0 +1,42 @@ +#ifndef SH_SHADER_MODULE_LIBRARY_H +#define SH_SHADER_MODULE_LIBRARY_H + +#include "Graphics/Shaders/SHVkShaderModule.h" +#include "SHShaderSourceLibrary.h" +#include + +namespace SHADE +{ + class SHVkLogicalDevice; + + /* + * The purpose of this shader module library is to be separate from the source library. The source library contains + * pure shader binary data that contains no vulkan related objects. Every time we load on unload a scene/level, + * this class and the source library class is cleared of its modules and recreated. + */ + class SHShaderModuleLibrary + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Stored shader modules + std::unordered_map> shaderModules; + + //! We want some sort of interface with strings, instead of ints + std::map stringToID; + + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void ImportFromSourceLibrary(Handle& logicalDeviceHdl, SHShaderSourceLibrary const& sourceLib) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + Handle GetShaderModule(std::string shaderName) const noexcept; + }; +} + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.cpp new file mode 100644 index 00000000..dd0f1612 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.cpp @@ -0,0 +1,272 @@ +#include "SHPch.h" +#include +#include +#include "SHShaderSourceLibrary.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Initializes the directory to take assets from. TODO: Only temporary until + the resource manager is implemented. + + \param directory + + \return + + */ + /***************************************************************************/ + void SHShaderSourceLibrary::Init (std::string directory) noexcept + { + shaderDirectory = directory; + if (shaderDirectory.back() != '/') + { + shaderDirectory += '/'; + } + } + + /***************************************************************************/ + /*! + + \brief + Private member function to compile a shader STRING source to binary and + returns a vector of 4 bytes. + + \param glslSource + The GLSL string source. + + \param type + Type of the shader: vertex, fragment, compute, etc. + + \param opLevel + Optimization level. + + \return + Returns a vector of the binary data. + + */ + /***************************************************************************/ + std::vector SHShaderSourceLibrary::CompileToBinary(std::string const& glslSource, char const* const spirvFilename, SH_SHADER_TYPE type, shaderc_optimization_level opLevel /*= shaderc_optimization_level_zero*/) + { + // shaderc compiler + shaderc::Compiler compiler; + shaderc::CompileOptions options; + + options.AddMacroDefinition("MY_DEFINE", "1"); + + // Set optimization levels + if (opLevel != shaderc_optimization_level_zero) + options.SetOptimizationLevel(opLevel); + + // Attempt to get the shaderc equivalent shader stage + shaderc_shader_kind shaderKind; + switch (type) + { + case SH_SHADER_TYPE::VERTEX: + shaderKind = shaderc_shader_kind::shaderc_glsl_vertex_shader; + break; + case SH_SHADER_TYPE::FRAGMENT: + shaderKind = shaderc_shader_kind::shaderc_glsl_fragment_shader; + break; + case SH_SHADER_TYPE::COMPUTE: + shaderKind = shaderc_shader_kind::shaderc_glsl_compute_shader; + break; + default: + shaderKind = shaderc_shader_kind::shaderc_glsl_vertex_shader; + break; + } + + // Compile the shader and get the result + shaderc::SpvCompilationResult compileResult = compiler.CompileGlslToSpv(glslSource, shaderKind, spirvFilename, options); + + if (compileResult.GetCompilationStatus() != shaderc_compilation_status_success) + { + SHLOG_ERROR("Shaderc failed to compile GLSL shader to binary | " + compileResult.GetErrorMessage()); + } + + return { compileResult.begin(), compileResult.end() }; + } + + /***************************************************************************/ + /*! + + \brief + TODO: Delete after file IO is implemented. Loads a shader from disk. + + \param filePath + file path to the shader in the asset directory. + + \return + Returns the data in the file in string form. + + */ + /***************************************************************************/ + std::string SHShaderSourceLibrary::GetStringFromFile(char const* filePath) noexcept + { + // Retrieve contents from filePath + // Ensure ifstream objects can throw exceptions + std::ifstream iFile; + iFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); + std::string fileContent = ""; + + try + { + // Open file + // Read file's buffer contents into streams + iFile.open(filePath); + std::stringstream fileStream; + fileStream << iFile.rdbuf(); + + fileContent = fileStream.str(); + + // Close file handler + iFile.close(); + } + catch (std::ifstream::failure e) + { + std::cerr << "File was not successfully read" << filePath << std::endl; + } + + return fileContent; + + } + + /***************************************************************************/ + /*! + + \brief + Load a shader into the library. + + \param filePath + file path to the shader in the asset directory. + + */ + /***************************************************************************/ + bool SHShaderSourceLibrary::LoadShader (uint32_t id, std::string glslFile, SH_SHADER_TYPE type, bool checkSpirvOutdated/* = true*/, bool recompileAnyway /*= false*/) noexcept + { + //if (sourceLibrary.contains(id)) + //{ + // SHLOG_ERROR("Shader with ID passed in already exists. Use a different ID"); + // return false; + //} + + std::string fullGLSLPath = shaderDirectory + glslFile; + auto path = std::filesystem::path(fullGLSLPath); + + if (path.extension() != ".glsl") + { + SHLOG_ERROR("Shader is not GLSL file, failed to load shader. "); + return false; + } + + std::string spirvFilepath = path.replace_extension("spv").string(); + + SHShaderData newShaderData{}; + newShaderData.shaderType = type; + + // spirv file + std::ifstream spirvFile(spirvFilepath, std::ios::ate | std::ios::binary); + + // If we disable spirv validation, file is not checked + if (!recompileAnyway && + spirvFile.is_open() && + (checkSpirvOutdated ? (std::filesystem::last_write_time(spirvFilepath) > std::filesystem::last_write_time(fullGLSLPath)) : true)) + { + // Get file size of binary + uint32_t fileSize = static_cast(spirvFile.tellg()); + + // resize container to store binary + newShaderData.spirvBinary.resize(fileSize / sizeof(uint32_t)); + + // Read data from binary file to container + spirvFile.seekg(0); + spirvFile.read(reinterpret_cast(newShaderData.spirvBinary.data()), fileSize); + + // close file + spirvFile.close(); + + } + else + { + // Use glslc to generate spirv file + newShaderData.spirvBinary = CompileToBinary(GetStringFromFile(fullGLSLPath.c_str()), spirvFilepath.c_str(), type); + + std::ofstream binaryFile(spirvFilepath, std::ios::binary); + + if (binaryFile.is_open()) + { + // write all data to binary file + binaryFile.write(reinterpret_cast(newShaderData.spirvBinary.data()), newShaderData.spirvBinary.size() * sizeof(uint32_t)); + } + else + { + SHLOG_ERROR("Failed to modify spirv file. "); + return false; + } + } + + newShaderData.name = glslFile; + newShaderData.id = id; + + sourceLibrary.emplace_back(std::move (newShaderData)); + return true; + } + + /***************************************************************************/ + /*! + + \brief + Gets the entire source library. + + \return + The container of binary data. + + */ + /***************************************************************************/ + std::vector const& SHShaderSourceLibrary::GetSourceLibrary(void) const noexcept + { + return sourceLibrary; + } + + /***************************************************************************/ + /*! + + \brief + Move ctor for shader data. + + \param rhs + The other shader data + + */ + /***************************************************************************/ + SHShaderData::SHShaderData(SHShaderData&& rhs) noexcept + : spirvBinary{ std::move (rhs.spirvBinary)} + , shaderType{std::move (rhs.shaderType)} + , name{ std::move (rhs.name)} + , id {std::move (rhs.id)} + { + + } + + /***************************************************************************/ + /*! + + \brief + Default ctor for shader data. Does nothing. + + */ + /***************************************************************************/ + SHShaderData::SHShaderData(void) noexcept + : spirvBinary{} + , shaderType{SH_SHADER_TYPE::VERTEX} + , name{ } + , id{ } + + { + + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.h b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.h new file mode 100644 index 00000000..505afa97 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderSourceLibrary.h @@ -0,0 +1,67 @@ +#ifndef SH_SHADER_SOURCE_LIBRARY_H +#define SH_SHADER_SOURCE_LIBRARY_H + +#include +#include "SHShaderType.h" +#include "shaderc/shaderc.hpp" + +namespace SHADE +{ + struct SHShaderData + { + /*-----------------------------------------------------------------------*/ + /* MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! container storing the spirv binary + std::vector spirvBinary; + + //! For the compilation of the shader. Vulkan backend will use it too + SH_SHADER_TYPE shaderType; + + //! Name of the shader file (without parent path) + std::string name; + + //! id of the shader + uint32_t id; + + SHShaderData(void) noexcept; + SHShaderData(SHShaderData&& rhs) noexcept; + }; + + // TODO: This class is purely temporary and will be converted/changed when XQ implements his resource manager + class SHShaderSourceLibrary + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Stores all the source data. Take note that the source here is GLSL source and NOT binary data. + //! Binary data gets passed to the backend to convert to spirv. + std::vector sourceLibrary; + + //! The directory where the shaders are located. + std::string shaderDirectory; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + std::vector CompileToBinary(std::string const& glslSource, char const* const spirvFilename, SH_SHADER_TYPE type, shaderc_optimization_level opLevel = shaderc_optimization_level_zero); + + // TODO: Delete after file IO is implemented + std::string GetStringFromFile(char const* filePath) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void Init (std::string directory) noexcept; + bool LoadShader (uint32_t id, std::string glslFile, SH_SHADER_TYPE type, bool checkSpirvOutdated = true, bool recompileAnyway = false) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + std::vector const& GetSourceLibrary(void) const noexcept; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderType.h b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderType.h new file mode 100644 index 00000000..bf4828b5 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Shaders/SHShaderType.h @@ -0,0 +1,14 @@ +#ifndef SH_SHADER_TYPE_H +#define SH_SHADER_TYPE_H + +namespace SHADE +{ + enum class SH_SHADER_TYPE + { + VERTEX, + FRAGMENT, + COMPUTE + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h new file mode 100644 index 00000000..961b3f4c --- /dev/null +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h @@ -0,0 +1,30 @@ +#ifndef SH_PIPELINE_LAYOUT_PARAMS_H +#define SH_PIPELINE_LAYOUT_PARAMS_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" +#include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" + +namespace SHADE +{ + class SHVkShaderModule; + + struct SHPipelineLayoutParams + { + /*-----------------------------------------------------------------------*/ + /* MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Shader modules for pipeline creation. See pipeline layout for details. + //! Note that this will be moved to pipeline layout. + std::vector> shaderModules; + + //! There's a huge chance that there is going to be a layout for a descriptor + //! set that is to be used for many shaders (i.e. a global descriptor set + //! used just for textures or lights for example). In that case, we still + //! want to use the layout to initialize the pipeline layout but we do not + //! want to use it for allocating descriptor sets. + std::vector> globalDescSetLayouts = {}; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.cpp b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.cpp new file mode 100644 index 00000000..8179444f --- /dev/null +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.cpp @@ -0,0 +1,238 @@ +#include "SHPch.h" +#include "SHPipelineState.h" + +namespace SHADE +{ + + void SHVkPipelineState::AddDefaultColorBlendAttachment(void) noexcept + { + vk::PipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = true; + colorBlendAttachment.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha; + colorBlendAttachment.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha; + colorBlendAttachment.colorBlendOp = vk::BlendOp::eAdd; + colorBlendAttachment.srcAlphaBlendFactor = vk::BlendFactor::eOne; + colorBlendAttachment.dstAlphaBlendFactor = vk::BlendFactor::eZero; + colorBlendAttachment.alphaBlendOp = vk::BlendOp::eAdd; + + colorBlendState.attachments.push_back(colorBlendAttachment); + } + + void SHVkPipelineState::SetInputAssemblyState(SHInputAssemblyState const& state) noexcept + { + dirty = true; + inputAssemblyState = state; + } + + void SHVkPipelineState::SetRasterizationState(SHRasterizationState const& state) noexcept + { + dirty = true; + rasterizationState = state; + } + + void SHVkPipelineState::SetViewportState(SHViewportState const& state) noexcept + { + dirty = true; + viewportState = state; + } + + void SHVkPipelineState::SetMultiSampleState(SHMultisampleState const& state) noexcept + { + dirty = true; + multisampleState = state; + } + + void SHVkPipelineState::SetDepthStencilState(SHDepthStencilState const& state) noexcept + { + dirty = true; + depthStencilState = state; + } + + void SHVkPipelineState::SetColorBlenState(SHColorBlendState const& state) noexcept + { + dirty = true; + colorBlendState = state; + } + + void SHVkPipelineState::SetRenderpass(Handle const& inRenderpassHdl) noexcept + { + dirty = true; + renderpassHdl = inRenderpassHdl; + } + + void SHVkPipelineState::SetSubpassIndex(uint32_t index) noexcept + { + dirty = true; + subpassIndex = index; + } + + SHVertexInputState const& SHVkPipelineState::GetVertexInputState(void) const noexcept + { + return vertexInputState; + } + + SHInputAssemblyState const& SHVkPipelineState::GetInputAssemblyState(void) const noexcept + { + return inputAssemblyState; + } + + SHRasterizationState const& SHVkPipelineState::GetRasterizationState(void) const noexcept + { + return rasterizationState; + } + + SHViewportState const& SHVkPipelineState::GetViewportState(void) const noexcept + { + return viewportState; + } + + SHMultisampleState const& SHVkPipelineState::GetMultisampleState(void) const noexcept + { + return multisampleState; + } + + SHDepthStencilState const& SHVkPipelineState::GetDepthStencilState(void) const noexcept + { + return depthStencilState; + } + + SHColorBlendState const& SHVkPipelineState::GetColorBlenState(void) const noexcept + { + return colorBlendState; + } + + Handle const& SHVkPipelineState::GetRenderpass(void) const noexcept + { + return renderpassHdl; + } + + uint32_t SHVkPipelineState::GetSubpassIndex(void) const noexcept + { + return subpassIndex; + } + + void SHVkPipelineState::SetDirty(bool isDirty) noexcept + { + dirty = isDirty; + } + + std::tuple SHVertexInputState::GetInfoFromAttribFormat(SHAttribFormat attribFormat) const noexcept + { + switch (attribFormat) + { + case SHAttribFormat::FLOAT_1D: + return std::make_tuple(1, 4, vk::Format::eR32Sfloat); + case SHAttribFormat::FLOAT_2D: + return std::make_tuple(1, 8, vk::Format::eR32G32Sfloat); + case SHAttribFormat::FLOAT_3D: + return std::make_tuple(1, 12, vk::Format::eR32G32B32Sfloat); + case SHAttribFormat::FLOAT_4D: + return std::make_tuple(1, 16, vk::Format::eR32G32B32A32Sfloat); + + // Matrix formats onwards all return less than actual bytes required but the numSlots will adjust offsets accordingly + case SHAttribFormat::MAT_2D: + return std::make_tuple(2, 8, vk::Format::eR32G32Sfloat); + case SHAttribFormat::MAT_3D: + return std::make_tuple(3, 12, vk::Format::eR32G32B32Sfloat); + case SHAttribFormat::MAT_4D: + return std::make_tuple(4, 16, vk::Format::eR32G32B32A32Sfloat); + break; + } + return std::make_tuple(0, 0, vk::Format::eR32Sfloat); + } + + SHVertexInputState::SHVertexInputState(void) noexcept + : bindings{} + , attributes{} + { + + } + + SHVertexInputState::SHVertexInputState(SHVertexInputState const& rhs) noexcept + : bindings{rhs.bindings} + , attributes{rhs.attributes} + { + + } + + SHVertexInputState::SHVertexInputState(SHVertexInputState&& rhs) noexcept + : bindings{ std::move (rhs.bindings) } + , attributes{ std::move (rhs.attributes) } + { + + } + + SHVertexInputState& SHVertexInputState::operator=(SHVertexInputState const& rhs) noexcept + { + if (&rhs == this) + return *this; + + bindings = rhs.bindings; + attributes = rhs.attributes; + + return *this; + } + + SHVertexInputState& SHVertexInputState::operator=(SHVertexInputState&& rhs) noexcept + { + if (&rhs == this) + return *this; + + bindings = std::move(rhs.bindings); + attributes = std::move(rhs.attributes); + + return *this; + } + + void SHVertexInputState::AddBinding(bool instanced, bool calcOffset, std::initializer_list inAttribs) noexcept + { + // add a binding and get ref to it + bindings.emplace_back(); + auto& binding = bindings.back(); + + // if binding is instanced, attributes are per instance, if not attributes here are per vertex + if (instanced) + binding.inputRate = vk::VertexInputRate::eInstance; + else + binding.inputRate = vk::VertexInputRate::eVertex; + + // Offset is 0 at first (for first element) + uint32_t offset = 0; + + // for every attribute passed in + for (auto const& attrib : inAttribs) + { + // Get number of slots needed, the bytes required and format of the attribute + auto const& [slots, bytes, format] = GetInfoFromAttribFormat(attrib.attribFormat); + + // For each attribute slot the attribute needs + for (uint32_t i = 0; i < slots; ++i) + { + // Add an attribute description + attributes.emplace_back(); + auto& vertexAttrib = attributes.back(); + + // The binding for that attribute description is index of the new binding created earlier in this function + vertexAttrib.binding = static_cast(bindings.size() - 1); + + // Attribute location. New index is simply + 1 of the previous. Starts from 0 obviously + vertexAttrib.location = static_cast(attributes.size () - 1); + + // Get the vkFormat associated with the SHAttribFormat + vertexAttrib.format = format; + + // if calcOffset is false, we want to use the calcuated one. If not, use the attribute's offset passed in. + vertexAttrib.offset = calcOffset ? offset : attrib.offset; + + // For when we want to use calculated offset and for the stride. + offset += bytes; + } + } + + // Stride is always all the offsets added together (Unless of course under special conditions which this class doesn't cover). + binding.stride = offset; + + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.h b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.h new file mode 100644 index 00000000..7c24aab7 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineState.h @@ -0,0 +1,225 @@ +#ifndef SH_PIPELINE_STATE_H +#define SH_PIPELINE_STATE_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Graphics/Renderpass/SHVkRenderpass.h" +#include "Graphics/VertexDescriptors/SHVertexAttribute.h" +#include + +namespace SHADE +{ + // This is mainly for non-interleaved attributes where we have a separate buffer for + // each vertex attribute. + struct SHVertexInputState + { + private: + using numAttribSlots = uint32_t; + using bytesRequired = uint32_t; + + //! Vertex attribute bindings + std::vector bindings; + + //! Vertex attributes + std::vector attributes; + + std::tuple GetInfoFromAttribFormat(SHAttribFormat attribFormat) const noexcept; + + public: + SHVertexInputState(void) noexcept; + SHVertexInputState(SHVertexInputState const& rhs) noexcept; + SHVertexInputState(SHVertexInputState&& rhs) noexcept; + + SHVertexInputState& operator= (SHVertexInputState const& rhs) noexcept; + SHVertexInputState& operator= (SHVertexInputState&& rhs) noexcept; + + void AddBinding(bool instanced, bool calcOffset, std::initializer_list inAttribs) noexcept; + + friend class SHVkPipelineState; + friend class SHVkPipeline; + }; + + struct SHInputAssemblyState + { + //! Primitive topology + vk::PrimitiveTopology topology{ vk::PrimitiveTopology::eTriangleList }; + + //! For drawElements this will enable primitive restarting + bool primitiveRestart{ VK_FALSE }; + }; + + struct SHRasterizationState + { + //! is depth clamping enabled + bool depthClamp{ VK_FALSE }; + + //! Rasterizer discard + bool rasterizerDiscard{ VK_FALSE }; + + //! Fill, line, point + vk::PolygonMode polygonMode{ vk::PolygonMode::eFill}; + + //! cull front, back or both faces + vk::CullModeFlags cull_mode{ vk::CullModeFlagBits::eBack }; + + //! CW or CCW + vk::FrontFace frontFacingOrientation{ vk::FrontFace::eCounterClockwise }; + + bool depthBias{ VK_FALSE }; + }; + + struct SHViewportState + { + //! Number of viewports + uint32_t viewportCount{ 1 }; + + //! number of scissors + uint32_t scissorCount{ 1 }; + }; + + struct SHMultisampleState + { + //! number of rasterization samples + vk::SampleCountFlagBits rasterizationSamples{ vk::SampleCountFlagBits::e1 }; + + //! Sample shading + bool sampleShading{ false }; + + // Minimum sample shading + float minSampleShading{ 1.0f }; + + //! Sample mask + vk::SampleMask sampleMask{ 0 }; + + //! Alpha to coverage + bool alphaToCoverage{ false }; + + //! Alpha to one + bool alphaToOne{ false }; + }; + + + struct SHStencilOpState + { + //! When stencil test fails + vk::StencilOp failOp { vk::StencilOp::eReplace}; + + //! When stencil test passes + vk::StencilOp passOp{ vk::StencilOp::eReplace }; + + //! When stencil test passes + vk::StencilOp depthFailOp{ vk::StencilOp::eReplace }; + + //! The compare function for stencil tests + vk::CompareOp compareOp{ vk::CompareOp::eNever}; + }; + + struct SHDepthStencilState + { + bool depthTest{ VK_TRUE }; + + bool depthWrite{ VK_TRUE }; + + // Note: Using Reversed depth-buffer for increased precision, so Greater depth values are kept + vk::CompareOp depthCompare{ vk::CompareOp::eGreater }; + + bool depthBounds{ VK_FALSE }; + + bool stencilTest{ VK_FALSE }; + + SHStencilOpState front{}; + + SHStencilOpState back{}; + }; + + struct SHColorBlendState + { + VkBool32 logic_op_enable{ VK_TRUE }; + + vk::LogicOp logic_op{ VK_LOGIC_OP_COPY }; + + std::vector attachments; + }; + + // TODO: Specialization constants + + class SHVkPipelineState + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! If any of the pipeline states change, set to true + bool dirty{ false }; + + //! Vertex input state + SHVertexInputState vertexInputState; + + //! Input assembly + SHInputAssemblyState inputAssemblyState; + + //! Rasterization state + SHRasterizationState rasterizationState; + + //! Viewport state + SHViewportState viewportState; + + //! Multisample state + SHMultisampleState multisampleState; + + //! Depth Stencil state + SHDepthStencilState depthStencilState; + + //! Color blend state + SHColorBlendState colorBlendState; + + //! Renderpass that is compatible with the pipeline + Handle renderpassHdl; + + //! Subpass index + uint32_t subpassIndex; + + friend class SHVkPipeline; + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void AddDefaultColorBlendAttachment (void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + + template , SHVertexInputState>>> + void SetVertexInputState (T&& state) noexcept; + void SetInputAssemblyState(SHInputAssemblyState const& state) noexcept; + void SetRasterizationState(SHRasterizationState const& state) noexcept; + void SetViewportState (SHViewportState const& state) noexcept; + void SetMultiSampleState (SHMultisampleState const& state) noexcept; + void SetDepthStencilState (SHDepthStencilState const& state) noexcept; + void SetColorBlenState (SHColorBlendState const& state) noexcept; + void SetRenderpass (Handle const& inRenderpassHdl) noexcept; + void SetSubpassIndex (uint32_t index) noexcept; + + SHVertexInputState const& GetVertexInputState (void) const noexcept; + SHInputAssemblyState const& GetInputAssemblyState (void) const noexcept; + SHRasterizationState const& GetRasterizationState (void) const noexcept; + SHViewportState const& GetViewportState (void) const noexcept; + SHMultisampleState const& GetMultisampleState (void) const noexcept; + SHDepthStencilState const& GetDepthStencilState (void) const noexcept; + SHColorBlendState const& GetColorBlenState (void) const noexcept; + Handle const& GetRenderpass (void) const noexcept; + uint32_t GetSubpassIndex (void) const noexcept; + + void SetDirty(bool isDirty) noexcept; + }; + + template + void SHVkPipelineState::SetVertexInputState(T&& state) noexcept + { + //static_assert(std::is_same_v, SHVertexInputState>); + vertexInputState = std::forward(state); + } + +} + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineType.h b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineType.h new file mode 100644 index 00000000..e7f5b6a8 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineType.h @@ -0,0 +1,13 @@ +#ifndef SH_PIPELINE_TYPE_H +#define SH_PIPELINE_TYPE_H + +namespace SHADE +{ + enum class SH_PIPELINE_TYPE + { + GRAPHICS, + COMPUTE, + }; +} + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPushConstantInterface.cpp b/SHADE_Engine/src/Graphics/Pipeline/SHPushConstantInterface.cpp new file mode 100644 index 00000000..affc1f53 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPushConstantInterface.cpp @@ -0,0 +1,91 @@ +#include "SHPch.h" +#include "SHPushConstantInterface.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Adds a variable and its offset to the interface. + + \param variableName + The push constant variable's name. + + \param offset + The push constant variable's offset. + + */ + /***************************************************************************/ + void SHPushConstantInterface::AddOffset(std::string variableName, uint32_t offset) noexcept + { + offsets.try_emplace(std::move (variableName), offset); + } + + void SHPushConstantInterface::Reset(void) noexcept + { + offsets.clear(); + } + + /***************************************************************************/ + /*! + + \brief + Sets the size of the push constant interface. + + \param inSize + Size to set to. + + */ + /***************************************************************************/ + void SHPushConstantInterface::SetSize(uint32_t inSize) noexcept + { + size = inSize; + } + + void SHPushConstantInterface::SetShaderStageFlags(vk::ShaderStageFlags flags) noexcept + { + shaderStages = flags; + } + + /***************************************************************************/ + /*! + + \brief + Retrieves an offset given a variable name. + + \param variableName + The variable's offset we want to retrieve. + + \return + The variable's offset. + + */ + /***************************************************************************/ + uint32_t SHPushConstantInterface::GetOffset(std::string const& variableName) const noexcept + { + return offsets.at (variableName); + } + + /***************************************************************************/ + /*! + + \brief + Getter for the size. + + \return + The size required for the push constant memory. + + */ + /***************************************************************************/ + uint32_t SHPushConstantInterface::GetSize(void) const noexcept + { + return size; + } + + vk::ShaderStageFlags SHPushConstantInterface::GetShaderStageFlags(void) const noexcept + { + return shaderStages; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPushConstantInterface.h b/SHADE_Engine/src/Graphics/Pipeline/SHPushConstantInterface.h new file mode 100644 index 00000000..1e9d9ef2 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPushConstantInterface.h @@ -0,0 +1,53 @@ +#ifndef SH_PUSH_CONSTANT_INTERFACE_H +#define SH_PUSH_CONSTANT_INTERFACE_H + +#include +#include "Graphics/Shaders/SHShaderReflected.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \class SHPushConstantInterface + When we want to send data into the push constant memory in command + buffers, the middle end usage is very raw. Vulkan API mainly expects + the offset and data. This class will store the offsets and they can + be accessed via this class. + + */ + /***************************************************************************/ + class SHPushConstantInterface + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Stores the offset to variables in push constant structs + std::unordered_map offsets; + + //! The size in bytes the push constant requires + uint32_t size{ 0 }; + + vk::ShaderStageFlags shaderStages; + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void AddOffset (std::string variableName, uint32_t offset) noexcept; + void Reset (void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + void SetSize (uint32_t inSize) noexcept; + void SetShaderStageFlags (vk::ShaderStageFlags flags) noexcept; + + uint32_t GetOffset (std::string const& variableName) const noexcept; + uint32_t GetSize (void) const noexcept; + vk::ShaderStageFlags GetShaderStageFlags (void) const noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.cpp b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.cpp new file mode 100644 index 00000000..263ae578 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.cpp @@ -0,0 +1,427 @@ +#include "SHPch.h" +#include "SHVkPipeline.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Shaders/SHVkShaderModule.h" +#include "Graphics/Debugging/SHVulkanDebugUtil.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Creates a graphics pipeline from the pipeline state. + + */ + /***************************************************************************/ + void SHVkPipeline::CreateGraphicsPipeline(void) noexcept + { + // Alot of the code is here is just boilerplate assignment. It may look + // like alot but the code is self explanatory. + + vk::GraphicsPipelineCreateInfo gpCreateInfo{}; + + auto const& shaderModules = pipelineLayout->GetShaderModules(); + + std::vector shaderPipelineInfos{shaderModules.size()}; + uint32_t numShaderModules = static_cast(shaderPipelineInfos.size()); + + + for (uint32_t i = 0; i < numShaderModules; ++i) + { + auto& info = shaderPipelineInfos[i]; + info.stage = shaderModules[i]->GetShaderStageFlagBits(); + info.pName = shaderModules[i]->GetEntryPoint().c_str(); + info.module = shaderModules[i]->GetVkShaderModule(); + } + + // TODO: specialization info + + gpCreateInfo.stageCount = numShaderModules; + gpCreateInfo.pStages = shaderPipelineInfos.data(); + + vk::PipelineVertexInputStateCreateInfo vertexInputState{}; + auto const& viState = pipelineState.GetVertexInputState(); + vertexInputState.pVertexBindingDescriptions = viState.bindings.data(); + vertexInputState.vertexBindingDescriptionCount = static_cast(viState.bindings.size()); + vertexInputState.pVertexAttributeDescriptions = viState.attributes.data(); + vertexInputState.vertexAttributeDescriptionCount = static_cast(viState.attributes.size()); + + vk::PipelineInputAssemblyStateCreateInfo inputAssemblyState{}; + auto const& iaState = pipelineState.GetInputAssemblyState(); + inputAssemblyState.topology = iaState.topology; + inputAssemblyState.primitiveRestartEnable = iaState.primitiveRestart; + + vk::PipelineRasterizationStateCreateInfo rasterizationState{}; + auto const& rasterState = pipelineState.GetRasterizationState(); + rasterizationState.depthClampEnable = rasterState.depthClamp; + rasterizationState.rasterizerDiscardEnable = rasterState.rasterizerDiscard; + rasterizationState.polygonMode = rasterState.polygonMode; + rasterizationState.cullMode = rasterState.cull_mode; + rasterizationState.frontFace = rasterState.frontFacingOrientation; + rasterizationState.depthBiasEnable = rasterState.depthBias; + rasterizationState.depthBiasClamp = 1.0f; + rasterizationState.depthBiasSlopeFactor = 1.0f; + rasterizationState.lineWidth = 1.0f; + + + + vk::PipelineViewportStateCreateInfo viewportState{}; + auto const& vpState = pipelineState.GetViewportState(); + viewportState.viewportCount = vpState.viewportCount; + viewportState.scissorCount = vpState.scissorCount; + + vk::PipelineDepthStencilStateCreateInfo depthStencilState{}; + auto const& dsState = pipelineState.GetDepthStencilState(); + depthStencilState.depthTestEnable = dsState.depthTest; + depthStencilState.depthWriteEnable = dsState.depthWrite; + depthStencilState.depthCompareOp = dsState.depthCompare; + depthStencilState.depthBoundsTestEnable = dsState.depthBounds; + depthStencilState.stencilTestEnable = dsState.stencilTest; + depthStencilState.front.failOp = dsState.front.failOp; + depthStencilState.front.passOp = dsState.front.passOp; + depthStencilState.front.depthFailOp = dsState.front.depthFailOp; + depthStencilState.front.compareOp = dsState.front.compareOp; + depthStencilState.front.compareMask = ~0U; + depthStencilState.front.writeMask = ~0U; + depthStencilState.front.reference = ~0U; + depthStencilState.back.failOp = dsState.back.failOp; + depthStencilState.back.passOp = dsState.back.passOp; + depthStencilState.back.depthFailOp = dsState.back.depthFailOp; + depthStencilState.back.compareOp = dsState.back.compareOp; + depthStencilState.back.compareMask = ~0U; + depthStencilState.back.writeMask = ~0U; + depthStencilState.back.reference = ~0U; + + + vk::PipelineColorBlendStateCreateInfo colorBlendState{}; + auto const& cbState = pipelineState.colorBlendState; + + colorBlendState.logicOpEnable = cbState.logic_op_enable; + colorBlendState.logicOp = cbState.logic_op; + + colorBlendState.attachmentCount = static_cast(cbState.attachments.size()); + colorBlendState.pAttachments = reinterpret_cast(cbState.attachments.data()); + + colorBlendState.blendConstants[0] = 1.0f; + colorBlendState.blendConstants[1] = 1.0f; + colorBlendState.blendConstants[2] = 1.0f; + colorBlendState.blendConstants[3] = 1.0f; + + std::array dynamicStates = + { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + vk::DynamicState::eLineWidth, + vk::DynamicState::eDepthBias, + vk::DynamicState::eBlendConstants, + vk::DynamicState::eDepthBounds, + vk::DynamicState::eStencilCompareMask, + vk::DynamicState::eStencilWriteMask, + vk::DynamicState::eStencilReference, + }; + + vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo + { + .pNext = nullptr, + .flags{}, + .dynamicStateCount = static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data() + }; + + vk::PipelineMultisampleStateCreateInfo multisamplingCreateInfo{}; + auto const& msState = pipelineState.multisampleState; + + multisamplingCreateInfo.sampleShadingEnable = msState.sampleShading; + multisamplingCreateInfo.rasterizationSamples = msState.rasterizationSamples; + multisamplingCreateInfo.minSampleShading = msState.minSampleShading; + + if (msState.sampleMask != 0) + multisamplingCreateInfo.pSampleMask = &msState.sampleMask; + + multisamplingCreateInfo.alphaToCoverageEnable = msState.alphaToCoverage; + multisamplingCreateInfo.alphaToOneEnable = msState.alphaToOne; + + + gpCreateInfo.pVertexInputState = &vertexInputState; + gpCreateInfo.pInputAssemblyState = &inputAssemblyState; + gpCreateInfo.pMultisampleState = &multisamplingCreateInfo; + gpCreateInfo.pRasterizationState = &rasterizationState; + gpCreateInfo.pViewportState = &viewportState; + gpCreateInfo.pDepthStencilState = &depthStencilState; + gpCreateInfo.pColorBlendState = &colorBlendState; + gpCreateInfo.pDynamicState = &dynamicStateCreateInfo; + gpCreateInfo.layout = pipelineLayout->GetVkPipelineLayout(); + + gpCreateInfo.renderPass = pipelineState.GetRenderpass()->GetVkRenderpass(); + gpCreateInfo.subpass = pipelineState.GetSubpassIndex(); + + gpCreateInfo.basePipelineHandle = VK_NULL_HANDLE; + gpCreateInfo.basePipelineIndex = -1; + + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createGraphicsPipelines(VK_NULL_HANDLE, 1, &gpCreateInfo, nullptr, &vkPipeline); result != vk::Result::eSuccess) + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Graphics Pipeline. "); + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created a Graphics Pipeline. "); + created = true; + } + } + + void SHVkPipeline::CreateComputePipeline(void) noexcept + { + + } + + + /***************************************************************************/ + /*! + + \brief + Non-default ctor. + + \param inLogicalDeviceHdl + Needed for creation and destruction. + + \param inPipelineLayout + The pipeline layout to use. + + \param state + The pipeline state to copy from. This can be nullptr, in which case the + default constructed pipeline state will be used and initialized with + the renderpass and subpass passed in. + + \param renderpassHdl + The renderpass that this pipeline will be used in. If state is not + nullptr, this parameter is ignored. + + \param subpass + The subpass that this pipeline will be used in. If state is not + nullptr, this parameter is ignored. + + \param type + The type of the pipeline. + + */ + /***************************************************************************/ + SHVkPipeline::SHVkPipeline(Handle const& inLogicalDeviceHdl, Handle const& inPipelineLayout, SHVkPipelineState const* const state, Handle const& renderpassHdl, uint32_t subpass, SH_PIPELINE_TYPE type) noexcept + : pipelineState{ } // copy the pipeline state + , pipelineType {type} + , vkPipeline {VK_NULL_HANDLE} + , logicalDeviceHdl{ inLogicalDeviceHdl } + , pipelineLayout { inPipelineLayout } + , created {false} + { + // We want to create a pipeline + if (state != nullptr) + { + pipelineState = *state; + + // Creates a pipeline based on type + ConstructPipeline(); + } + else + { + pipelineState.SetRenderpass(renderpassHdl); + pipelineState.SetSubpassIndex(subpass); + } + } + + /***************************************************************************/ + /*! + + \brief + Move ctor. + + \param rhs + The other SHVkPipeline + + */ + /***************************************************************************/ + SHVkPipeline::SHVkPipeline(SHVkPipeline&& rhs) noexcept + : vkPipeline {rhs.vkPipeline} + , pipelineState {rhs.pipelineState} + , pipelineType {rhs.pipelineType} + , created {rhs.created} + , logicalDeviceHdl{ rhs.logicalDeviceHdl } + , pipelineLayout { rhs.pipelineLayout } + { + vkPipeline = VK_NULL_HANDLE; + } + + /***************************************************************************/ + /*! + + \brief + Destructor. Destroys the pipeline. + + */ + /***************************************************************************/ + SHVkPipeline::~SHVkPipeline(void) noexcept + { + logicalDeviceHdl->GetVkLogicalDevice().destroyPipeline(vkPipeline, nullptr); + } + + /***************************************************************************/ + /*! + + \brief + move assignment operator overload. + + \param rhs + The other pipeline. + + \return + The reference to the pipeline. + + */ + /***************************************************************************/ + SHVkPipeline& SHVkPipeline::operator=(SHVkPipeline&& rhs) noexcept + { + if (&rhs == this) + return *this; + + vkPipeline = rhs.vkPipeline; + pipelineState = rhs.pipelineState; + pipelineType = rhs.pipelineType; + created = rhs.created; + logicalDeviceHdl = rhs.logicalDeviceHdl; + + vkPipeline = VK_NULL_HANDLE; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + For users to call when they created the pipeline with the default + constructor. + + \param type + The type of the pipeline (graphics, compute, etc). + + */ + /***************************************************************************/ + void SHVkPipeline::ConstructPipeline(void) noexcept + { + // if it was created before, destroy it + if (created) + logicalDeviceHdl->GetVkLogicalDevice().destroyPipeline(vkPipeline, nullptr); + + // Set to false again. If creation succeeds after this, this will be true + created = false; + + // Create pipeline based on type + switch (pipelineType) + { + case SH_PIPELINE_TYPE::GRAPHICS: + CreateGraphicsPipeline(); + break; + case SH_PIPELINE_TYPE::COMPUTE: + CreateComputePipeline(); + break; + } + } + + /***************************************************************************/ + /*! + + \brief + Sets the pipeline type. + + \param type + The pipeline type. + + */ + /***************************************************************************/ + void SHVkPipeline::SetPipelineType(SH_PIPELINE_TYPE type) noexcept + { + pipelineState.SetDirty(true); + pipelineType = type; + } + + /***************************************************************************/ + /*! + + \brief + Returns a reference to the pipeline state. Not a const reference because + we want to be able to modify the pipeline state. + + \return + Reference to the stored pipeline state. + + */ + /***************************************************************************/ + SHVkPipelineState& SHVkPipeline::GetPipelineState(void) noexcept + { + return pipelineState; + } + + /***************************************************************************/ + /*! + + \brief + Derives a pipeline bind point from the type of the pipeline and returns + it. + + \return + The derived bind point type. + + */ + /***************************************************************************/ + vk::PipelineBindPoint SHVkPipeline::GetPipelineBindPoint(void) const noexcept + { + switch (pipelineType) + { + case SH_PIPELINE_TYPE::GRAPHICS: + return vk::PipelineBindPoint::eGraphics; + case SH_PIPELINE_TYPE::COMPUTE: + return vk::PipelineBindPoint::eCompute; + break; + default: + return vk::PipelineBindPoint::eGraphics; + break; + + } + } + + /***************************************************************************/ + /*! + + \brief + Gets the vulkan pipeline handle. + + \return + The vulkan pipeline handle. + + */ + /***************************************************************************/ + vk::Pipeline SHVkPipeline::GetVkPipeline(void) const noexcept + { + return vkPipeline; + } + + /***************************************************************************/ + /*! + + \brief + Returns whether or not the pipeline has been created. + + + \return + + */ + /***************************************************************************/ + bool SHVkPipeline::GetIsCreated(void) const noexcept + { + return created; + } + + Handle SHVkPipeline::GetPipelineLayout(void) const noexcept + { + return pipelineLayout; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.h b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.h new file mode 100644 index 00000000..9a9d78f4 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipeline.h @@ -0,0 +1,80 @@ +#ifndef SH_VK_PIPELINE_H +#define SH_VK_PIPELINE_H + +#include "SHPipelineState.h" +#include "SHPipelineType.h" +#include "Resource/Handle.h" +#include "Graphics/Pipeline/SHVkPipelineLayout.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + + class SHVkPipeline + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Vulkan pipeline handle + vk::Pipeline vkPipeline; + + //! pipeline state + SHVkPipelineState pipelineState; + + //! The type of the pipeline (affects creation) + SH_PIPELINE_TYPE pipelineType; + + //! Logical device needed for creation and destruction of pipeline + Handle logicalDeviceHdl; + + //! Pipeline Layout used for the pipeline + Handle pipelineLayout; + + //! Sometimes we want to modify some state values before we create the + //! pipeline. This boolean just helps to identify if the pipeline has + //! already been created or not + bool created; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void CreateGraphicsPipeline(void) noexcept; + void CreateComputePipeline (void) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHVkPipeline (Handle const& inLogicalDeviceHdl, + Handle const& inPipelineLayout, + SHVkPipelineState const* const state, + Handle const& renderpassHdl, + uint32_t subpass, + SH_PIPELINE_TYPE type) noexcept; + + SHVkPipeline (SHVkPipeline&& rhs) noexcept; + ~SHVkPipeline (void) noexcept; + + SHVkPipeline& operator= (SHVkPipeline&& rhs) noexcept; + // TODO: move ctors + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void ConstructPipeline(void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + void SetPipelineType (SH_PIPELINE_TYPE type) noexcept; + + SHVkPipelineState& GetPipelineState (void) noexcept; + vk::PipelineBindPoint GetPipelineBindPoint (void) const noexcept; + vk::Pipeline GetVkPipeline (void) const noexcept; + bool GetIsCreated (void) const noexcept; + Handle GetPipelineLayout (void) const noexcept; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.cpp b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.cpp new file mode 100644 index 00000000..cc738aed --- /dev/null +++ b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.cpp @@ -0,0 +1,424 @@ +#include "SHPch.h" +#include "SHVkPipelineLayout.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Shaders/SHVkShaderModule.h" +#include "Tools/SHLogger.h" +#include "Graphics/Instance/SHVkInstance.h" + +namespace SHADE +{ + + void SHVkPipelineLayout::PreparePushConstantInterface (void) noexcept + { + // The starting of every push constant block, will get updated later. + uint32_t startOffset = 0; + + // To check for duplicates + std::vector pcInfos; + vk::ShaderStageFlags stageFlags; + + for (auto& shaderModule : shaderModules) + { + // References for convenience + auto const& reflectedData = shaderModule->GetReflectedData(); + auto const& pcInfo = reflectedData.GetPushConstantInfo(); + + // If a push constant block exists for the shader module + if (pcInfo.memberCount != 0) + { + bool exists = false; + + // Check if push constant block already exists + for (uint32_t i = 0; i < pcInfos.size(); ++i) + { + // If there is a block with the same name, member count and size + if (std::strcmp(pcInfos[i]->name, pcInfo.name) == 0 && pcInfos[i]->memberCount == pcInfo.memberCount && pcInfos[i]->size == pcInfo.size) + { + // We just take the existing pc range we built earlier, and allow it to be accessed in potentially other shader stages + vkPcRanges[i].stageFlags |= shaderModule->GetShaderStageFlagBits(); + + // Set flag and stop checking + exists = true; + break; + } + } + + // If the block doesn't exist yet + if (!exists) + { + // Loop through all member variables of the new push constant block + for (uint32_t i = 0; i < pcInfo.memberCount; ++i) + { + std::string variableName; + variableName.reserve(50); + variableName += pcInfo.name; + variableName += "."; + variableName += pcInfo.members[i].name; + + // Add the variable's offset info to the interface + pushConstantInterface.AddOffset(std::move(variableName), startOffset + pcInfo.members[i].offset); + } + + // New push constant range + vk::PushConstantRange newRange; + + // set offset and size + newRange.offset = startOffset; + newRange.size = pcInfo.size; + + // Stage flags will be whatever shader stage of the shader that contains the push constant block + newRange.stageFlags = shaderModule->GetShaderStageFlagBits(); + + // Add to the list foe checking later + pcInfos.push_back(&pcInfo); + + // For pipeline layout to consume + vkPcRanges.push_back(newRange); + + // Next push constant block will start next to the previous push constant block + startOffset += pcInfo.size; + } + + } + + stageFlags |= shaderModule->GetShaderStageFlagBits(); + } + + // After all the sizes of the push constant blocks have been added, record the size in the interface + pushConstantInterface.SetSize(startOffset); + pushConstantInterface.SetShaderStageFlags(stageFlags); + + } + + void SHVkPipelineLayout::PrepareDescriptorLayout(void) noexcept + { + enum class PARSE_RESULT + { + STAGE_APPENDED, + REJECTED, + DOES_NOT_EXIST, + }; + + // Function that attempts to take a binding and see if it already exists. + auto TryParseExistingBinding = [](std::vector& bindings, vk::DescriptorType type, uint32_t bindPoint, vk::ShaderStageFlagBits stage) + { + for (auto& binding : bindings) + { + // If some existing binding is detected... + if (bindPoint == binding.BindPoint) + { + // ...and the existing binding has the same descriptor type + if (type == binding.Type) + { + // Append shader stage since it exists in more than 1 shader + binding.Stage |= stage; + return PARSE_RESULT::STAGE_APPENDED; + } + else + { + SHLOG_ERROR("Binding Set pair already parsed with a different descriptor type and therefore unavailable. "); + return PARSE_RESULT::REJECTED; + } + } + } + return PARSE_RESULT::DOES_NOT_EXIST; + }; + + + // KEY is set index, VALUE is vector of bindings (perfect for descriptor set layout construction). We + // want this to be map because we want to add the descriptor layouts IN ORDER. + std::map> setsWithBindings; + + //! Now we take descriptor set info from all shaders and prepare some bindings for the descriptor set + for (auto& shaderModule : shaderModules) + { + auto const& descBindingInfo = shaderModule->GetReflectedData().GetDescriptorBindingInfo(); + auto const& reflectedSets = descBindingInfo.GetReflectedSets(); + + // If there are no descriptor sets for the shader module, just skip to the next one + if (reflectedSets.empty()) + continue; + + // For every descriptor set + for (auto const* const set : reflectedSets) + { + // For descriptor sets that are found in the shader that are meant to be global descriptors, don't parse them + if (set->set < descriptorSetLayoutsGlobal.size()) + continue; + + // Convenient variable for the current descriptor set index + uint32_t const CURRENT_SET = set->set; + + for (uint32_t i = 0; i < set->binding_count; ++i) + { + // for convenience + SpvReflectDescriptorBinding const* const reflectedBinding = set->bindings[i]; + + // If same set binding pair is found + if (TryParseExistingBinding(setsWithBindings[CURRENT_SET], SHShaderDescriptorBindingInfo::ConvertFromReflectDescType (reflectedBinding->descriptor_type), reflectedBinding->binding, shaderModule->GetShaderStageFlagBits()) == PARSE_RESULT::DOES_NOT_EXIST) + { + // New binding for the set binding pair number above + SHVkDescriptorSetLayout::Binding newBinding; + + // Straightforward stuff. + newBinding.BindPoint = reflectedBinding->binding; + newBinding.Stage = shaderModule->GetShaderStageFlagBits(); + newBinding.Type = descBindingInfo.ConvertFromReflectDescType(reflectedBinding->descriptor_type); + + // In reality, the check for variable descriptor sets do not exists in spirv-reflect. Fortunately, when a shader + // defines a boundless descriptor binding in the shader, the information reflected makes the array dimensions + // contain a 1 element of value 1. Knowing that having an array [1] doesn't make sense, we can use this to + // signify a variable sized binding. + if (reflectedBinding->array.dims[0] == 1) + { + // variable binding has to be the last in the set + if (i == set->binding_count - 1) + { + // Set to predefined upper bound. this probably doesn't do any allocations yet. It just tells Vulkan this is the maximum allowed + newBinding.DescriptorCount = SHVkDescriptorSetLayout::Binding::VARIABLE_DESCRIPTOR_UPPER_BOUND; + + // Set the flags for variable bindings + newBinding.flags = vk::DescriptorBindingFlagBits::eVariableDescriptorCount; + } + else + SHLOG_ERROR("Variable size binding is detected, but the binding is not the last binding of the set and is therefore invalid. "); + } + + setsWithBindings[CURRENT_SET].emplace_back(newBinding); + } + } + } // for reflected descriptor set + } // for shader module + + + // Now we want to create a container to contain all the required descriptor set for both allocation and pipelines + descriptorSetLayoutsAllocate.reserve(setsWithBindings.size()); + + // Create the descriptor set layouts here for each set in setWithBindings. Essentially you would have + // 1 descriptor set layout for every descriptor set detected. + for (auto const& set : setsWithBindings) + { + auto newDescriptorSetLayout = logicalDeviceHdl->CreateDescriptorSetLayout(set.second); + descriptorSetLayoutsAllocate.push_back(newDescriptorSetLayout); + } + + // Prepare the vulkan layouts for pipeline layout creation + PrepareVkDescriptorSetLayouts(); + } + + /***************************************************************************/ + /*! + + \brief + Prepares the vulkan descriptor set layout for pipeline layout consumption. + + */ + /***************************************************************************/ + void SHVkPipelineLayout::PrepareVkDescriptorSetLayouts(void) noexcept + { + // Settle allocate layouts first + vkDescriptorSetLayoutsAllocate.reserve(descriptorSetLayoutsAllocate.size()); + for (auto const& layout : descriptorSetLayoutsAllocate) + { + vkDescriptorSetLayoutsAllocate.emplace_back(layout->GetVkHandle()); + } + + // pipeline layouts contain global layouts first, then layouts for allocation + vkDescriptorSetLayoutsPipeline.reserve(descriptorSetLayoutsAllocate.size() + descriptorSetLayoutsGlobal.size()); + + // First we insert the global layouts + for (auto const& layout : descriptorSetLayoutsGlobal) + vkDescriptorSetLayoutsPipeline.emplace_back(layout->GetVkHandle()); + + // Then we append layouts for allocation at the back of the vector + std::copy(vkDescriptorSetLayoutsAllocate.begin(), vkDescriptorSetLayoutsAllocate.end(), std::back_inserter(vkDescriptorSetLayoutsPipeline)); + } + + /***************************************************************************/ + /*! + + \brief + Destroys the pipeline layout and descriptor layouts used. + + */ + /***************************************************************************/ + void SHVkPipelineLayout::Destroy(void) noexcept + { + // Kill the pipeline layout if required + if (vkPipelineLayout) + logicalDeviceHdl->GetVkLogicalDevice().destroyPipelineLayout(vkPipelineLayout, nullptr); + + // Reset push constant interface before reflecting + pushConstantInterface.Reset(); + + // Clear pc ranges + vkPcRanges.clear(); + + // Kill all descriptor set layouts + for (auto& layout : descriptorSetLayoutsGlobal) + SHVkInstance::GetResourceManager().Free(layout); + + descriptorSetLayoutsGlobal.clear(); + + for (auto& layout : descriptorSetLayoutsAllocate) + SHVkInstance::GetResourceManager().Free(layout); + + descriptorSetLayoutsAllocate.clear(); + + // We don't need to use vulkan to destroy descriptor sets here since they are just owned by the container with handles + vkDescriptorSetLayoutsAllocate.clear(); + vkDescriptorSetLayoutsPipeline.clear(); + } + + /***************************************************************************/ + /*! + + \brief + Non-default ctor. Initializes shader modules used for the pipeline. + + \param inLogicalDeviceHdl + logical device required for creation and destruction + + \param shaderModules + Shader modules that will be used in pipeline (not pipeline layout) + creation. + + */ + /***************************************************************************/ + SHVkPipelineLayout::SHVkPipelineLayout(Handle const& inLogicalDeviceHdl, SHPipelineLayoutParams& pipelineLayoutParams) noexcept + : vkPipelineLayout {VK_NULL_HANDLE} + , shaderModules{std::move (pipelineLayoutParams.shaderModules)} + , logicalDeviceHdl {inLogicalDeviceHdl} + , pushConstantInterface{} + , vkPcRanges{} + , descriptorSetLayoutsGlobal{pipelineLayoutParams.globalDescSetLayouts } // do a copy, some other pipeline layout might need this + , descriptorSetLayoutsAllocate{} + , vkDescriptorSetLayoutsAllocate{} + , vkDescriptorSetLayoutsPipeline{} + { + for (auto& mod : shaderModules) + { + mod->AddCallback([this]() + { + RecreateIfNeeded(); + } + ); + } + + RecreateIfNeeded (); + } + + /***************************************************************************/ + /*! + + \brief + Move ctor. Does what it says. + + \param rhs + The other pipeline layout to move from. + + */ + /***************************************************************************/ + SHVkPipelineLayout::SHVkPipelineLayout(SHVkPipelineLayout&& rhs) noexcept + : logicalDeviceHdl {rhs.logicalDeviceHdl} + , vkPipelineLayout {rhs.vkPipelineLayout} + , shaderModules {std::move (rhs.shaderModules)} + , pushConstantInterface {std::move (rhs.pushConstantInterface)} + , vkPcRanges {std::move (rhs.vkPcRanges)} + , descriptorSetLayoutsGlobal {std::move (rhs.descriptorSetLayoutsGlobal)} + , descriptorSetLayoutsAllocate {std::move (rhs.descriptorSetLayoutsAllocate)} + , vkDescriptorSetLayoutsAllocate{std::move (rhs.vkDescriptorSetLayoutsAllocate)} + , vkDescriptorSetLayoutsPipeline{std::move (rhs.vkDescriptorSetLayoutsAllocate)} + { + rhs.vkPipelineLayout = VK_NULL_HANDLE; + } + + /***************************************************************************/ + /*! + + \brief + Destructor for the pipeline layout. + + */ + /***************************************************************************/ + SHVkPipelineLayout::~SHVkPipelineLayout(void) noexcept + { + Destroy(); + } + + void SHVkPipelineLayout::RecreateIfNeeded(void) noexcept + { + Destroy(); + + vk::PipelineLayoutCreateInfo plCreateInfo{}; + + // Prepare push constant information + PreparePushConstantInterface(); + + // This takes the reflection information from all the shader modules used in the pipeline and creates descriptor set layouts + // for the pipeline + PrepareDescriptorLayout(); + + // Set push constant data to pipeline layout + plCreateInfo.pushConstantRangeCount = static_cast(vkPcRanges.size()); + plCreateInfo.pPushConstantRanges = vkPcRanges.data(); + + // To initialize the descriptor set layouts for the pipeline layout. + plCreateInfo.setLayoutCount = static_cast(vkDescriptorSetLayoutsPipeline.size()); + plCreateInfo.pSetLayouts = vkDescriptorSetLayoutsPipeline.data(); + + if (auto const RESULT = logicalDeviceHdl->GetVkLogicalDevice().createPipelineLayout(&plCreateInfo, nullptr, &vkPipelineLayout); RESULT != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(RESULT, "Failed to create Pipeline Layout. "); + return; + } + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created Pipeline Layout. "); + } + + std::vector> const& SHVkPipelineLayout::GetShaderModules(void) const noexcept + { + return shaderModules; + } + + vk::PipelineLayout SHVkPipelineLayout::GetVkPipelineLayout(void) const noexcept + { + return vkPipelineLayout; + } + + SHPushConstantInterface const& SHVkPipelineLayout::GetPushConstantInterface(void) const noexcept + { + return pushConstantInterface; + } + + Handle SHVkPipelineLayout::GetShaderBlockInterface(uint32_t set, uint32_t binding, vk::ShaderStageFlagBits shaderStage) const noexcept + { + for (auto& shader : shaderModules) + { + if (shader->GetShaderStageFlagBits() == shaderStage) + return shader->GetReflectedData().GetDescriptorBindingInfo().GetShaderBlockInterface(set, binding); + } + return {}; + } + + SHVkPipelineLayout& SHVkPipelineLayout::operator=(SHVkPipelineLayout&& rhs) noexcept + { + if (&rhs == this) + return *this; + + logicalDeviceHdl = rhs.logicalDeviceHdl; + vkPipelineLayout = rhs.vkPipelineLayout; + shaderModules = std::move(rhs.shaderModules); + pushConstantInterface = std::move(rhs.pushConstantInterface); + vkPcRanges = std::move(rhs.vkPcRanges); + descriptorSetLayoutsGlobal = std::move(rhs.descriptorSetLayoutsGlobal); + descriptorSetLayoutsAllocate = std::move(rhs.descriptorSetLayoutsAllocate); + vkDescriptorSetLayoutsAllocate = std::move(rhs.vkDescriptorSetLayoutsAllocate); + vkDescriptorSetLayoutsPipeline = std::move(rhs.vkDescriptorSetLayoutsAllocate); + + rhs.vkPipelineLayout = VK_NULL_HANDLE; + + return *this; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.h b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.h new file mode 100644 index 00000000..bce827a7 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Pipeline/SHVkPipelineLayout.h @@ -0,0 +1,80 @@ +#ifndef SH_VK_PIPELINE_LAYOUT_H +#define SH_VK_PIPELINE_LAYOUT_H + +#include "SHPipelineLayoutParams.h" +#include "SHPushConstantInterface.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + + class SHVkPipelineLayout + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Logical device used for creation and destruction. + Handle logicalDeviceHdl; + + //! The Vulkan handle to a pipeline layout + vk::PipelineLayout vkPipelineLayout; + + //! Shader modules used by the pipeline. Note that these modules themselves + //! are not used in pipeline layout creation. We mainly have it here so that + //! pipelines can simply take in this class and the shader modules would + //! be included. + std::vector> shaderModules; + + //! Push constant interface + SHPushConstantInterface pushConstantInterface; + + //! Push constant ranges + std::vector vkPcRanges; + + //! List of global set layouts + std::vector> descriptorSetLayoutsGlobal; + + //! List of set layouts to allocate descriptor sets for. Set indices here should + //! also be smaller than the global ones passed in. + std::vector> descriptorSetLayoutsAllocate; + + //! We want to store this also because we need to allocate later + std::vector vkDescriptorSetLayoutsAllocate; + + //! Store for pipeline layout recreation + std::vector vkDescriptorSetLayoutsPipeline; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void PreparePushConstantInterface (void) noexcept; + void PrepareDescriptorLayout (void) noexcept; + void PrepareVkDescriptorSetLayouts(void) noexcept; + void Destroy (void) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHVkPipelineLayout (Handle const& inLogicalDeviceHdl, SHPipelineLayoutParams& pipelineLayoutParams) noexcept; + SHVkPipelineLayout (SHVkPipelineLayout&& rhs) noexcept; + ~SHVkPipelineLayout (void) noexcept; + SHVkPipelineLayout& operator= (SHVkPipelineLayout&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void RecreateIfNeeded (void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + std::vector> const& GetShaderModules (void) const noexcept; + vk::PipelineLayout GetVkPipelineLayout (void) const noexcept; + SHPushConstantInterface const& GetPushConstantInterface (void) const noexcept; + Handle GetShaderBlockInterface (uint32_t set, uint32_t binding, vk::ShaderStageFlagBits shaderStage) const noexcept; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Queues/SHVkQueue.cpp b/SHADE_Engine/src/Graphics/Queues/SHVkQueue.cpp new file mode 100644 index 00000000..9f8c529c --- /dev/null +++ b/SHADE_Engine/src/Graphics/Queues/SHVkQueue.cpp @@ -0,0 +1,86 @@ +#include "SHPch.h" +#include "SHVkQueue.h" +#include "Tools/SHLogger.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Synchronization/SHVkSemaphore.h" +#include "Graphics/Synchronization/SHVkFence.h" +#include "Graphics/Commands/SHVkCommandBuffer.h" + + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Non default ctor to initialize variables. + + \param inVkQueue + The vulkan queue handle to initialize this class with. + + \param parent + The queue family this queue originated from. + + */ + /***************************************************************************/ + SHVkQueue::SHVkQueue(vk::Queue inVkQueue, SHQueueFamilyIndex parent, Handle const& inLogicalDeviceHdl) noexcept + : vkQueue{ inVkQueue } + , parentFamily{ parent } + , logicalDeviceHdl{ inLogicalDeviceHdl } + { + + } + + vk::Queue SHVkQueue::GetVkQueue(void) const noexcept + { + return vkQueue; + } + + void SHVkQueue::SubmitCommandBuffer(std::initializer_list> cmdBuffers, std::initializer_list> signalSems /*= {}*/, std::initializer_list> waitSems /*= {}*/, vk::PipelineStageFlagBits waitDstStageMask /*= {}*/, Handle const& optionalFence /*= {}*/) noexcept + { + std::vector vkWaitSems{ waitSems.size() }; + uint32_t i = 0; + for (auto& sem : waitSems) + { + vkWaitSems[i] = sem->GetVkSem(); + ++i; + } + + std::vector vkSignalSems{ signalSems.size() }; + i = 0; + for (auto& sem : signalSems) + { + vkSignalSems[i] = sem->GetVkSem(); + ++i; + } + + std::vector vkCmdBuffers{ cmdBuffers.size() }; + i = 0; + for (auto& cmdBuffer : cmdBuffers) + { + vkCmdBuffers[i] = cmdBuffer->GetVkCommandBuffer(); + ++i; + } + + vk::PipelineStageFlags mask = waitDstStageMask; + + vk::SubmitInfo submitInfo + { + .waitSemaphoreCount = static_cast(vkWaitSems.size()), + .pWaitSemaphores = vkWaitSems.data(), + .pWaitDstStageMask = &mask, + .commandBufferCount = static_cast(vkCmdBuffers.size()), + .pCommandBuffers = vkCmdBuffers.data(), + .signalSemaphoreCount = static_cast(vkSignalSems.size()), + .pSignalSemaphores = vkSignalSems.data(), + }; + + // #BackEndTest: Submit the queue + if (auto result = vkQueue.submit(1, &submitInfo, (optionalFence) ? optionalFence->GetVkFence() : nullptr); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to submit command buffer. "); + } + + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Queues/SHVkQueue.h b/SHADE_Engine/src/Graphics/Queues/SHVkQueue.h new file mode 100644 index 00000000..203ae59e --- /dev/null +++ b/SHADE_Engine/src/Graphics/Queues/SHVkQueue.h @@ -0,0 +1,54 @@ +#ifndef SH_QUEUE_H +#define SH_QUEUE_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Graphics/SHVulkanDefines.h" +#include "Resource/Handle.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkCommandBuffer; + class SHVkFence; + class SHVkSemaphore; + + enum class SH_QUEUE_FAMILY_ARRAY_INDEX : std::size_t + { + GRAPHICS = 0, + COMPUTE = 1, + TRANSFER = 2, + MAX = 3, + INVALID = 0xFFFF, + }; + + using SH_Q_FAM = SH_QUEUE_FAMILY_ARRAY_INDEX; + + enum class SH_QUEUE_SELECT + { + DEDICATED, + NON_DEDICATED_BEST // First fit means attempting to select the most versatile queue family + }; + + struct SHQueueParams + { + SH_QUEUE_FAMILY_ARRAY_INDEX arrayIndex; + SH_QUEUE_SELECT selectionMethod; + }; + + class SHVkQueue + { + private: + + vk::Queue vkQueue; + SHQueueFamilyIndex parentFamily; + Handle logicalDeviceHdl; + + public: + SHVkQueue(vk::Queue inVkQueue, SHQueueFamilyIndex parent, Handle const& inLogicalDeviceHdl) noexcept; + vk::Queue GetVkQueue(void) const noexcept; + + void SubmitCommandBuffer(std::initializer_list> cmdBuffers, std::initializer_list> signalSems = {}, std::initializer_list> waitSems = {}, vk::PipelineStageFlagBits waitDstStageMask = {}, Handle const& fence = {}) noexcept; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp new file mode 100644 index 00000000..a86913e4 --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp @@ -0,0 +1,941 @@ +#include "SHPch.h" +#include "SHRenderGraph.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" +#include "Graphics/Framebuffer/SHVkFramebuffer.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Images/SHVkImage.h" +#include "Graphics/Images/SHVkImageView.h" +#include "Graphics/Framebuffer/SHVkFramebuffer.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Non-default ctor for the resource. Using the type of the resource, we + decide whether or not we create a resource or link with a swapchain + resource (image). + + \param logicalDevice + Logical device required to create an image resource if the type is NOT + SH_ATT_DESC_TYPE::COLOR_PRESENT. + + \param swapchain + Swapchain required to get swapchain image if the type IS + SH_ATT_DESC_TYPE::COLOR_PRESENT. + + \param type + Type of the image resource. + + \param format + Format of the image resource. + + \param w + Width of the image resource. + + \param h + Height of the image resource. + + \param levels + Number of mipmap levels of the image resource. + + \param createFlags + Create flags used when an image resource needs to be created. + + */ + /***************************************************************************/ + SHRenderGraphResource::SHRenderGraphResource(Handle const& logicalDevice, Handle const& swapchain, std::string const& name, SH_ATT_DESC_TYPE type, vk::Format format, uint32_t w, uint32_t h, uint8_t levels, vk::ImageCreateFlagBits createFlags) noexcept + : resourceType{ type } + , resourceFormat{ format } + , images{} + , imageViews{} + , width{ w } + , height{ h } + , mipLevels{ levels } + , resourceName{ name } + { + // If the resource type is an arbitrary image and not swapchain image + if (type != SH_ATT_DESC_TYPE::COLOR_PRESENT) + { + vk::ImageAspectFlags imageAspectFlags; + vk::ImageUsageFlags usage = {}; + + // Check the resource type and set image usage flags and image aspect flags accordingly + switch (resourceType) + { + case SH_ATT_DESC_TYPE::COLOR: + usage |= vk::ImageUsageFlagBits::eColorAttachment; + imageAspectFlags |= vk::ImageAspectFlagBits::eColor; + break; + case SH_ATT_DESC_TYPE::DEPTH: + usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; + imageAspectFlags |= vk::ImageAspectFlagBits::eDepth; + break; + case SH_ATT_DESC_TYPE::STENCIL: + usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; + imageAspectFlags |= vk::ImageAspectFlagBits::eStencil; + break; + case SH_ATT_DESC_TYPE::DEPTH_STENCIL: + usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; + imageAspectFlags |= vk::ImageAspectFlagBits::eStencil | vk::ImageAspectFlagBits::eDepth; + break; + } + + // The resource is not a swapchain image, just use the first slot of the vector + images.push_back(logicalDevice->CreateImage(width, height, mipLevels, resourceFormat, usage, createFlags)); + + // prepare image view details + SHImageViewDetails viewDetails + { + .viewType = vk::ImageViewType::e2D, + .format = images[0]->GetImageFormat(), + .imageAspectFlags = imageAspectFlags, + .baseMipLevel = 0, + .mipLevelCount = mipLevels, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + // just 1 image view created + imageViews.push_back(images[0]->CreateImageView(logicalDevice, images[0], viewDetails)); + } + else // if swapchain image resource + { + // Prepare image view details + SHImageViewDetails viewDetails + { + .viewType = vk::ImageViewType::e2D, + .format = swapchain->GetSurfaceFormatKHR().format, + .imageAspectFlags = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .mipLevelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + // We want an image handle for every swapchain image + images.resize(swapchain->GetNumImages()); + imageViews.resize(swapchain->GetNumImages()); + + for (uint32_t i = 0; i < swapchain->GetNumImages(); ++i) + { + images[i] = swapchain->GetSwapchainImage(i); + imageViews[i] = images[i]->CreateImageView(logicalDevice, images[i], viewDetails); + } + } + } + + /***************************************************************************/ + /*! + + \brief + Move ctor for resource. + + \param rhs + The other resource. + + */ + /***************************************************************************/ + SHRenderGraphResource::SHRenderGraphResource(SHRenderGraphResource&& rhs) noexcept + : resourceName{ std::move(rhs.resourceName) } + , resourceType{ std::move(rhs.resourceType) } + , images{ std::move(rhs.images) } + , imageViews{ std::move(rhs.imageViews) } + , resourceFormat{ std::move(rhs.resourceFormat) } + , width{ rhs.width } + , height{ rhs.height } + , mipLevels{ rhs.mipLevels } + { + + } + + /***************************************************************************/ + /*! + + \brief + Move assignment operator. + + \param rhs + The other resource. + + \return + + */ + /***************************************************************************/ + SHRenderGraphResource& SHRenderGraphResource::operator=(SHRenderGraphResource&& rhs) noexcept + { + if (this == &rhs) + return *this; + + resourceName = std::move(rhs.resourceName); + resourceType = std::move(rhs.resourceType); + images = std::move(rhs.images); + imageViews = std::move(rhs.imageViews); + resourceFormat = std::move(rhs.resourceFormat); + width = rhs.width; + height = rhs.height; + mipLevels = rhs.mipLevels; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Destructor for resource. + + */ + /***************************************************************************/ + SHRenderGraphResource::~SHRenderGraphResource(void) noexcept + { + + } + + /***************************************************************************/ + /*! + + \brief + Subpass non-default constructor. Simply initializes variables. + + \param mapping + Mapping from a resource handle to an attachment reference referencing + the resource. + + \param resources + A mapping from string to render graph resource. + + */ + /***************************************************************************/ + SHRenderGraphNode::SHSubpass::SHSubpass(std::unordered_map const* mapping, std::unordered_map> const* resources) noexcept + : resourceAttachmentMapping{ mapping } + , ptrToResources{ resources } + { + + } + + /***************************************************************************/ + /*! + + \brief + Move constructor for subpass. + + \param rhs + The subpass the move from. + + */ + /***************************************************************************/ + SHRenderGraphNode::SHSubpass::SHSubpass(SHSubpass&& rhs) noexcept + : colorReferences{ std::move(rhs.colorReferences) } + , depthReferences{ std::move(rhs.depthReferences) } + , inputReferences{ std::move(rhs.inputReferences) } + , resourceAttachmentMapping{ rhs.resourceAttachmentMapping } + , ptrToResources{ rhs.ptrToResources } + { + + } + + /***************************************************************************/ + /*! + + \brief + Move assignment operator for subpass. + + \param rhs + subpass to move from. + + */ + /***************************************************************************/ + SHRenderGraphNode::SHSubpass& SHRenderGraphNode::SHSubpass::operator=(SHSubpass&& rhs) noexcept + { + if (this == &rhs) + return *this; + + colorReferences = std::move(rhs.colorReferences); + depthReferences = std::move(rhs.depthReferences); + inputReferences = std::move(rhs.inputReferences); + resourceAttachmentMapping = rhs.resourceAttachmentMapping; + ptrToResources = rhs.ptrToResources; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Adds a color output to a subpass. Takes in a string and finds the + attachment index to create the vk::SubpassReference. + + \param resourceToReference + Resource name to find resource to attach. + + */ + /***************************************************************************/ + void SHRenderGraphNode::SHSubpass::AddColorOutput(std::string resourceToReference) noexcept + { + colorReferences.push_back({ resourceAttachmentMapping->at(ptrToResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eColorAttachmentOptimal }); + } + + /***************************************************************************/ + /*! + + \brief + Adds a depth output to a subpass. Takes in a string and finds the + attachment index to create the vk::SubpassReference. + + \param resourceToReference + Resource name to find resource to attach. + + \param attachmentDescriptionType + Depending on the type of the resource, initialize the image layout + appropriately. + + */ + /***************************************************************************/ + void SHRenderGraphNode::SHSubpass::AddDepthOutput(std::string resourceToReference, SH_ATT_DESC_TYPE attachmentDescriptionType) noexcept + { + vk::ImageLayout imageLayout; + switch (attachmentDescriptionType) + { + case SH_ATT_DESC_TYPE::DEPTH: + imageLayout = vk::ImageLayout::eDepthAttachmentOptimal; + break; + case SH_ATT_DESC_TYPE::STENCIL: + imageLayout = vk::ImageLayout::eStencilAttachmentOptimal; + break; + case SH_ATT_DESC_TYPE::DEPTH_STENCIL: + imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + break; + default: + //Invalid + return; + } + depthReferences.push_back({ resourceAttachmentMapping->at(ptrToResources->at(resourceToReference).GetId().Raw), imageLayout }); + } + + /***************************************************************************/ + /*! + + \brief + Adds a input output to a subpass. Takes in a string and finds the + attachment index to create the vk::SubpassReference. + + \param resourceToReference + Resource name to find resource to attach. + + */ + /***************************************************************************/ + void SHRenderGraphNode::SHSubpass::AddInput(std::string resourceToReference) noexcept + { + inputReferences.push_back({ resourceAttachmentMapping->at(ptrToResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eShaderReadOnlyOptimal }); + } + + /***************************************************************************/ + /*! + + \brief + Creates a renderpass for the node. Uses subpass and attachment + descriptions already configured beforehand in the render graph. + + */ + /***************************************************************************/ + void SHRenderGraphNode::CreateRenderpass(void) noexcept + { + renderpass = logicalDeviceHdl->CreateRenderpass(attachmentDescriptions, spDescs, spDeps); + } + + /***************************************************************************/ + /*! + + \brief + Creates a framebuffer from the images used in the renderpass. + + */ + /***************************************************************************/ + void SHRenderGraphNode::CreateFramebuffer(void) noexcept + { + for (uint32_t i = 0; i < framebuffers.size(); ++i) + { + std::vector> imageViews(attResources.size()); + uint32_t fbWidth = std::numeric_limits::max(); + uint32_t fbHeight = std::numeric_limits::max(); + + for (uint32_t j = 0; j < attResources.size(); ++j) + { + uint32_t imageViewIndex = (attResources[j]->resourceType == SH_ATT_DESC_TYPE::COLOR_PRESENT) ? i : 0; + imageViews[j] = attResources[j]->imageViews[imageViewIndex]; + + // We want the minimum dimensions for the framebuffer because the image attachments referenced cannot have dimensions smaller than the framebuffer's + if (fbWidth > attResources[j]->width) + fbWidth = attResources[j]->width; + if (fbHeight > attResources[j]->height) + fbHeight = attResources[j]->height; + } + + + framebuffers[i] = logicalDeviceHdl->CreateFramebuffer(renderpass, imageViews, fbWidth, fbHeight); + } + } + + /***************************************************************************/ + /*! + + \brief + Render Graph node constructor. Note that we do not create the renderpass + yet. This is because layouts of attachment descriptions facilitate image + transitions and we cannot know guarantee layouts until we've seen all + renderpasses and their subpasses in the graph. + + \param swapchain + Swapchain required to query number of images as parameters for number + of framebuffers to create. + + \param attachmentDescriptionTypes + + + \return + + */ + /***************************************************************************/ + SHRenderGraphNode::SHRenderGraphNode(ResourceManager& rm, Handle const& logicalDevice, Handle const& swapchain, std::vector> attRes, std::vector> predecessors, std::unordered_map> const* resources) noexcept + : logicalDeviceHdl{ logicalDevice } + , renderpass{} + , framebuffers{} + , prereqNodes{ std::move(predecessors) } + , attachmentDescriptions{} + , resourceAttachmentMapping{} + , attResources{ std::move(attRes) } + , subpasses{} + , executed{ false } + , configured{ false } + , resourceManager{ rm } + , ptrToResources{ resources } + { + attachmentDescriptions.resize(attResources.size()); + + bool containsSwapchainImage = false; + for (uint32_t i = 0; i < attResources.size(); ++i) + { + // As mentioned above we don't initialize much here because it's dependent on how other renderpasses are configured. + vk::AttachmentDescription& newDesc = attachmentDescriptions[i]; + newDesc.samples = vk::SampleCountFlagBits::e1; + + // We set this to clear first. If later we find out that some predecessor is writing to the same attachment, + // we set the pred's storeOp to eStore and "this" loadOp to eLoad. + newDesc.loadOp = vk::AttachmentLoadOp::eClear; + newDesc.storeOp = vk::AttachmentStoreOp::eDontCare; + + newDesc.stencilLoadOp = vk::AttachmentLoadOp::eClear; + newDesc.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; + + newDesc.format = attResources[i]->resourceFormat; + + if (attResources[i]->resourceType == SH_ATT_DESC_TYPE::COLOR_PRESENT) + containsSwapchainImage = true; + + resourceAttachmentMapping.try_emplace(attResources[i].GetId().Raw, i); + } + + if (!containsSwapchainImage) + framebuffers.resize(1); + else + framebuffers.resize(swapchain->GetNumImages()); + + // At this point, we could configure framebuffers if we had the renderpass object but we don't so their creation has to be + // deferred to when renderpasses are also created. + } + + SHRenderGraphNode::SHRenderGraphNode(SHRenderGraphNode&& rhs) noexcept + : resourceManager{ rhs.resourceManager } + , logicalDeviceHdl{ rhs.logicalDeviceHdl } + , renderpass{ rhs.renderpass } + , framebuffers{ std::move(rhs.framebuffers) } + , prereqNodes{ std::move(rhs.prereqNodes) } + , attachmentDescriptions{ std::move(rhs.attachmentDescriptions) } + , attResources{ std::move(rhs.attResources) } + , subpasses{ std::move(rhs.subpasses) } + , resourceAttachmentMapping{ std::move(rhs.resourceAttachmentMapping) } + , subpassIndexing{ std::move(rhs.subpassIndexing) } + , configured{ rhs.configured } + , executed{ rhs.executed } + , ptrToResources{ rhs.ptrToResources } + { + rhs.renderpass = {}; + } + + SHRenderGraphNode& SHRenderGraphNode::operator=(SHRenderGraphNode&& rhs) noexcept + { + if (&rhs == this) + return *this; + + resourceManager = rhs.resourceManager; + logicalDeviceHdl = rhs.logicalDeviceHdl; + renderpass = rhs.renderpass; + framebuffers = std::move(rhs.framebuffers); + prereqNodes = std::move(rhs.prereqNodes); + attachmentDescriptions = std::move(rhs.attachmentDescriptions); + attResources = std::move(rhs.attResources); + subpasses = std::move(rhs.subpasses); + resourceAttachmentMapping = std::move(rhs.resourceAttachmentMapping); + subpassIndexing = std::move(rhs.subpassIndexing); + ptrToResources = std::move(rhs.ptrToResources); + + rhs.renderpass = {}; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Add subpasses to the renderpass and returns a reference to it. + + \param subpassName + Name of the subpass. + + \return + Handle to the new subpass. + + */ + /***************************************************************************/ + Handle SHRenderGraphNode::AddSubpass(std::string subpassName) noexcept + { + // if subpass already exists, don't add. + if (subpassIndexing.contains(subpassName)) + { + SHLOG_ERROR("Subpass already exists."); + return{}; + } + + // Add subpass to container and create mapping for it + subpasses.emplace_back(resourceManager.Create(&resourceAttachmentMapping, ptrToResources)); + subpassIndexing.try_emplace(subpassName, subpasses.size() - 1); + return subpasses.at(subpassIndexing[subpassName]); + } + + /***************************************************************************/ + /*! + + \brief + Attempts to add a resource that will be used globally in the render graph. + + \param resourceName + Name of the resource. + + \param type + The type of the resource. + + \param w + Width of the resource. If set to uint32_t(-1), use swapchain image dims. + + \param h + Height of the resource. If set to uint32_t(-1), use swapchain image dims. + + \param format + The format that the resource is using. If w and h are set to uint32_t(-1), + use swapchain image format. + + \param levels + Number of mipmap levels of the resource. + + */ + /***************************************************************************/ + void SHRenderGraph::AddResource(std::string resourceName, SH_ATT_DESC_TYPE type, uint32_t w /*= static_cast(-1)*/, uint32_t h /*= static_cast(-1)*/, vk::Format format/* = vk::Format::eB8G8R8A8Unorm*/, uint32_t levels /*= 1*/, vk::ImageCreateFlagBits createFlags /*= {}*/) + { + // If we set to + if (w == static_cast(-1) && h == static_cast(-1)) + { + w = swapchainHdl->GetSwapchainImage(0)->GetWidth(); + w = swapchainHdl->GetSwapchainImage(0)->GetHeight(); + format = swapchainHdl->GetSurfaceFormatKHR().format; + } + + graphResources.try_emplace(resourceName, resourceManager.Create(logicalDeviceHdl, swapchainHdl, resourceName, type, format, w, h, levels, createFlags)); + } + + /***************************************************************************/ + /*! + + \brief + Loops through all nodes and configures their attachment descriptions. + + \return + + */ + /***************************************************************************/ + void SHRenderGraph::ConfigureAttachmentDescriptions(void) noexcept + { + // First we want to take all the attachment descriptions and initialize the + // finalLayout to whatever layout is specified in the last subpass that references the attachment. + + for (auto& node : nodes) + { + // key is handle ID, value is pair (first is initial layout, second is final layout). + std::unordered_map resourceAttLayouts; + if (node->subpasses.empty()) + { + SHLOG_ERROR("Node does not contain a subpass. Cannot configure attachment descriptions as a result. "); + return; + } + + for (auto& subpass : node->subpasses) + { + for (auto& color : subpass->colorReferences) + { + if (node->attResources[color.attachment]->resourceType == SH_ATT_DESC_TYPE::COLOR_PRESENT) + resourceAttLayouts[color.attachment] = vk::ImageLayout::ePresentSrcKHR; + else + resourceAttLayouts[color.attachment] = color.layout; + } + + for (auto& depth : subpass->depthReferences) + resourceAttLayouts[depth.attachment] = depth.layout; + + for (auto& input : subpass->inputReferences) + resourceAttLayouts[input.attachment] = input.layout; + } + + for (uint32_t i = 0; i < node->attachmentDescriptions.size(); ++i) + { + auto& att = node->attachmentDescriptions[i]; + att.initialLayout = vk::ImageLayout::eUndefined; + att.finalLayout = resourceAttLayouts[i]; + } + } + + // at this point all attachment descs will have their final layouts initialized as if they were standalone and did + // not have any predecessor nodes + + // Now we use the predecessor attachment description and first subpass to configure the current node's attachment descriptions. + // This is so that we minimize image layout transitions. + for (auto& node : nodes) + { + for (auto& prereq : node->prereqNodes) + { + // Look through all resources used by this node + for (auto& predResource : prereq->attResources) + { + // if a predecessor's resource is used by this node, we want to copy the final layout from the pred to the initial of this node + if (uint64_t resourceID = predResource.GetId().Raw; node->resourceAttachmentMapping.contains(resourceID)) + { + // Get the resource's attachment index in BOTH the predecessor and the current node + uint32_t prereqResourceAttIndex = prereq->resourceAttachmentMapping[resourceID]; + uint32_t resourceAttIndex = node->resourceAttachmentMapping[resourceID]; + + // Use the resource attachment index to get the attachment description in the renderpass + auto& attDesc = node->attachmentDescriptions[resourceAttIndex]; + auto& predAttDesc = prereq->attachmentDescriptions[prereqResourceAttIndex]; + + // We want the predecessor to preserve the attachment and we want this node to load the attachment + attDesc.loadOp = vk::AttachmentLoadOp::eLoad; + predAttDesc.storeOp = vk::AttachmentStoreOp::eStore; + + // TODO: Stecil load and store + + // When an image is done being used in a renderpass, the image layout will end up being the finalLayout + // value of the attachment description. We want this to carry over to the next renderpass; specifically + // to have the initialLayout of the attachment description in the next renderpass match the finalLayout in the previous. + attDesc.initialLayout = predAttDesc.finalLayout; + } + } + } + } + } + + /***************************************************************************/ + /*! + + \brief + Configures the supasses; mainly the subpass descriptions and the + subpass dependencies involved between subpasses. + + + \return + + */ + /***************************************************************************/ + void SHRenderGraph::ConfigureSubpasses(void) noexcept + { + // For all nodes + for (auto& node : nodes) + { + // Create subpass description and dependencies based on number of subpasses + node->spDescs.resize(node->subpasses.size()); + node->spDeps.resize(node->subpasses.size()); + + // Now we want to loop through all attachments in all subpasses in the node and query + // the resources being used. For each resource we want to query the type and record it + // in bit fields (1 bit for each subpass). + uint32_t colorRead = 0, colorWrite = 0, depthRead = 0, depthWrite = 0, inputDependencies = 0; + + uint32_t i = 0; + + // For all subpasses (see above description about bit field for this). + for (auto& subpass : node->subpasses) + { + // Configure subpass description + auto& desc = node->spDescs[i]; + desc.pColorAttachments = subpass->colorReferences.data(); + desc.colorAttachmentCount = static_cast(subpass->colorReferences.size()); + desc.pInputAttachments = subpass->inputReferences.data(); + desc.inputAttachmentCount = static_cast(subpass->inputReferences.size()); + desc.pDepthStencilAttachment = subpass->depthReferences.data(); + desc.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; // TODO: Just graphics for now. See if its possible to allow user defined params. + + // Get reference to subpass description + auto& dep = node->spDeps[i]; + + // Configure subpass index for dependencies + dep.srcSubpass = (i == 0) ? VK_SUBPASS_EXTERNAL : i - 1; + dep.dstSubpass = i; + + // First we want to see if the subpass has color, depth or input attachments and set bit field accordingly + if (subpass->colorReferences.size()) + { + colorRead |= (1 << i); + colorWrite |= (1 << i); + } + + // Same thing for depth + if (subpass->depthReferences.size()) + { + depthRead |= (1 << i); + depthWrite |= (1 << i); + } + if (subpass->inputReferences.size()) + inputDependencies |= (1 << i); + + // Input attachments can be any type, so we need to check what type it is + for (auto& inputAtt : subpass->inputReferences) + { + auto resource = node->attResources[inputAtt.attachment]; + if (resource->resourceType == SH_ATT_DESC_TYPE::COLOR) + colorRead |= (1 << i); + else if (resource->resourceType == SH_ATT_DESC_TYPE::DEPTH_STENCIL) + depthRead |= (1 << i); + } + + ++i; + } + + // Loop through all subpasses again but this time we use the bit field to initialize + // the dependencies. + for (i = 0; i < node->subpasses.size(); ++i) + { + vk::PipelineStageFlags srcStage; + vk::PipelineStageFlags dstStage; + vk::AccessFlags srcAccess; + vk::AccessFlags dstAccess; + auto& dep = node->spDeps[i]; + + if (colorRead & (1 << i)) + { + srcStage |= vk::PipelineStageFlagBits::eColorAttachmentOutput; + dstStage |= vk::PipelineStageFlagBits::eColorAttachmentOutput; + srcAccess |= vk::AccessFlagBits::eColorAttachmentWrite; + dstAccess |= vk::AccessFlagBits::eColorAttachmentRead; + } + if (colorWrite & (1 << i)) + { + srcStage |= vk::PipelineStageFlagBits::eColorAttachmentOutput; + dstStage |= vk::PipelineStageFlagBits::eColorAttachmentOutput; + srcAccess |= vk::AccessFlagBits::eColorAttachmentWrite; + dstAccess |= vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eColorAttachmentRead; + } + if (depthRead & (1 << i)) + { + srcStage |= vk::PipelineStageFlagBits::eEarlyFragmentTests; + dstStage |= vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests; + srcAccess |= vk::AccessFlagBits::eDepthStencilAttachmentWrite; + dstAccess |= vk::AccessFlagBits::eDepthStencilAttachmentRead; + } + if (depthWrite & (1 << i)) + { + srcStage |= vk::PipelineStageFlagBits::eEarlyFragmentTests; + dstStage |= vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests; + srcAccess |= vk::AccessFlagBits::eDepthStencilAttachmentWrite; + dstAccess |= vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite; + } + if (inputDependencies & (1 << i)) + { + dstStage |= vk::PipelineStageFlagBits::eFragmentShader; + dstAccess |= vk::AccessFlagBits::eInputAttachmentRead; + } + + //// If subpass of first renderpass, stage flag should be bottom of pipe + //if (&node == &nodes.front() && i == 0) + // srcStage = vk::PipelineStageFlagBits::eBottomOfPipe; + + //// If subpass of last renderpass, stage flag should be bottom of pipe + //if (&node == &nodes.back() && i == node->subpasses.size() - 1) + // dstStage = vk::PipelineStageFlagBits::eTopOfPipe; + + dep.srcStageMask = srcStage; + dep.dstStageMask = dstStage; + dep.srcAccessMask = srcAccess; + dep.dstAccessMask = dstAccess; + + dep.srcStageMask = srcStage; + } + } + } + + /***************************************************************************/ + /*! + + \brief + Simply loops through all nodes and create renderpasses. + + */ + /***************************************************************************/ + void SHRenderGraph::ConfigureRenderpasses(void) noexcept + { + for (auto& node : nodes) + { + node->CreateRenderpass(); + } + } + + /***************************************************************************/ + /*! + + \brief + Simply loops through all nodes and create framebuffers. + + */ + /***************************************************************************/ + void SHRenderGraph::ConfigureFramebuffers(void) noexcept + { + for (auto& node : nodes) + { + node->CreateFramebuffer(); + } + } + + /***************************************************************************/ + /*! + + \brief + Init function. Doesn't do much except initialize device and swapchain + handle. Graph should start out empty. + + \param swapchain + Used for querying number of images later. + + \param logicalDevice + Used for initializing logicalDeviceHdl. + + */ + /***************************************************************************/ + void SHRenderGraph::Init(Handle const& logicalDevice, Handle const& swapchain) noexcept + { + logicalDeviceHdl = logicalDevice; + swapchainHdl = swapchain; + } + + /***************************************************************************/ + /*! + + \brief + Default ctor, doesn't do much. + + + \return + + */ + /***************************************************************************/ + SHRenderGraph::SHRenderGraph(void) noexcept + : logicalDeviceHdl{ } + , swapchainHdl{ } + , nodes{} + , graphResources{} + , resourceManager{} + + { + + } + + /***************************************************************************/ + /*! + + \brief + Adds a renderpass to the graph. + + \param nodeName + The name of the renderpass + + \param attachmentDescriptions + The attachment description types for the node. See SHRenderGraphNode + ctor for more details. + + \return + Reference to the node. + + */ + /***************************************************************************/ + Handle SHRenderGraph::AddNode(std::string nodeName, std::initializer_list resourceNames, std::initializer_list predecessorNodes) noexcept + { + if (nodeIndexing.contains(nodeName)) + { + SHLOG_ERROR("Node already exists, cannot add node. "); + return {}; + } + + std::vector> resources; + for (auto const& name : resourceNames) + { + // If the resource that the new node is requesting for exists, allow the graph to reference it + if (graphResources.contains(name)) + resources.push_back(graphResources.at(name)); + else + { + SHLOG_ERROR("Resource doesn't exist in graph yet. Cannot create new node."); + return{}; + } + } + + // Predecessor nodes for this node + std::vector> predecessors; + for (auto& pred : predecessorNodes) + { + // If predecessor exists, allow new node to reference it + if (nodeIndexing.contains(pred)) + predecessors.push_back(nodes[nodeIndexing[pred]]); + else + { + SHLOG_ERROR("Predecessor doesn't exist in graph yet. Cannot create new node."); + return {}; + } + } + + nodes.emplace_back(resourceManager.Create(resourceManager, logicalDeviceHdl, swapchainHdl, std::move(resources), std::move(predecessors), &graphResources)); + nodeIndexing.emplace(nodeName, nodes.size() - 1); + return nodes.at(nodeIndexing[nodeName]); + } + + /***************************************************************************/ + /*! + + \brief + Generates and configures all renderpass nodes. + Does a few things. + 1. Using predecessors and subpasses, configure attachment descriptions. + 2. Using predecessors, configure subpass description and dependencies. + 3. Create Renderpasses in nodes. + 4. Create framebuffers in nodes using renderpasses. + + */ + /***************************************************************************/ + void SHRenderGraph::Generate(void) noexcept + { + ConfigureAttachmentDescriptions(); + ConfigureSubpasses(); + ConfigureRenderpasses(); + ConfigureFramebuffers(); + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h new file mode 100644 index 00000000..f81e84bd --- /dev/null +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h @@ -0,0 +1,233 @@ +#ifndef SH_RENDER_GRAPH_H +#define SH_RENDER_GRAPH_H + +#include "Graphics/Renderpass/SHVkRenderpass.h" +#include "Resource/ResourceLibrary.h" + +#include +#include + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkFramebuffer; + class SHVkSwapchain; + class SHVkImage; + class SHVkImageView; + class SHVkFramebuffer; + + // Used for attachment description creation for renderpass node + enum class SH_ATT_DESC_TYPE + { + COLOR, + COLOR_PRESENT, + DEPTH, + STENCIL, + DEPTH_STENCIL, + }; + + class SHRenderGraphResource + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Name of the resource + std::string resourceName; + + //! Used for initializing image layouts + SH_ATT_DESC_TYPE resourceType; + + //! The resource itself (this is a vector because if the resource happens + //! to be a swapchain image, then we need however many frames in flight). + //! However when it's not a swapchain image, we want this container to be size 1 + //! because writing to these images will not interfere with images in the previous + //! frame, unlike the swapchain image. + std::vector> images; + + //! Views to resources (vector because same rationale as images. see above). + std::vector> imageViews; + + //! Image format + vk::Format resourceFormat; + + //! width of the resource + uint32_t width; + + //! Height of the resource + uint32_t height; + + //! Number of mipmap levels + uint8_t mipLevels; + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHRenderGraphResource(Handle const& logicalDevice, Handle const& swapchain, std::string const& name, SH_ATT_DESC_TYPE type, vk::Format format, uint32_t w, uint32_t h, uint8_t levels, vk::ImageCreateFlagBits createFlags) noexcept; + SHRenderGraphResource(SHRenderGraphResource&& rhs) noexcept; + SHRenderGraphResource& operator=(SHRenderGraphResource&& rhs) noexcept; + ~SHRenderGraphResource (void) noexcept; + + friend class SHRenderGraphNode; + friend class SHRenderGraph; + }; + + class SHRenderGraphNode + { + public: + + class SHSubpass + { + public: + SHSubpass(std::unordered_map const* mapping, std::unordered_map> const* ptrToResources) noexcept; + SHSubpass(SHSubpass&& rhs) noexcept; + SHSubpass& operator=(SHSubpass&& rhs) noexcept; + + void AddColorOutput (std::string resourceToReference) noexcept; + void AddDepthOutput (std::string resourceToReference, SH_ATT_DESC_TYPE attachmentDescriptionType = SH_ATT_DESC_TYPE::DEPTH_STENCIL) noexcept; + void AddInput (std::string resourceToReference) noexcept; + + private: + /*---------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*---------------------------------------------------------------------*/ + //! Color attachments + std::vector colorReferences; + + //! Depth attachments + std::vector depthReferences; + + //! Input attachments + std::vector inputReferences; + + //! For getting attachment reference indices using handles + std::unordered_map const* resourceAttachmentMapping; + + //! Pointer to resources in the render graph (for getting handle IDs) + std::unordered_map> const* ptrToResources; + + friend class SHRenderGraphNode; + friend class SHRenderGraph; + }; + + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + ResourceManager& resourceManager; + + //! For Vulkan object creation + Handle logicalDeviceHdl; + + //! Each node will have a renderpass and each renderpass will have its own subpasses. + //! These subpasses will execute sequentially. + Handle renderpass; + + //! Framebuffers used in this renderpass. If renderpass contains usage of a swapchain image + //! used for presenting, then we cannot use just 1 framebuffer, we need to have 1 for however many frames in flight. + std::vector> framebuffers; + + //! Nodes that must finish execution before this node is executed will be in this container + std::vector> prereqNodes; + + //! Container of Attachment descriptions + std::vector attachmentDescriptions; + + //! Resources used in this renderpass + std::vector> attResources; + + //! Vector of subpasses + std::vector> subpasses; + + //! Descriptions to pass to renderpass for renderpass creation. We want to keep this here because + std::vector spDescs; + + //! Subpass dependencies for renderpass creation + std::vector spDeps; + + //! For indexing resources fast + std::unordered_map resourceAttachmentMapping; + + //! For indexing subpasses + std::map subpassIndexing; + + //! Pointer to resources in the render graph (for getting handle IDs) + std::unordered_map> const* ptrToResources; + + //! Whether or not the node has finished execution + bool executed; + + //! Whether or not the node has been configured already or not + bool configured; + + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void CreateRenderpass (void) noexcept; + void CreateFramebuffer(void) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHRenderGraphNode (ResourceManager& rm, Handle const& logicalDevice, Handle const& swapchain, std::vector> attRes, std::vector> predecessors, std::unordered_map> const* resources) noexcept; + SHRenderGraphNode(SHRenderGraphNode&& rhs) noexcept; + SHRenderGraphNode& operator= (SHRenderGraphNode&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + Handle AddSubpass (std::string subpassName) noexcept; + + friend class SHRenderGraph; + }; + + class SHRenderGraph + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void ConfigureAttachmentDescriptions (void) noexcept; + void ConfigureSubpasses (void) noexcept; + void ConfigureRenderpasses (void) noexcept; + void ConfigureFramebuffers (void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + Handle logicalDeviceHdl; + + //! swapchain used for querying image count + Handle swapchainHdl; + + //! For indexing render graph node container + std::map nodeIndexing; + + //! Render graph nodes + std::vector> nodes; + + //! Resources that exist for the entire render graph + std::unordered_map> graphResources; + + //! Resource library for graph handles + ResourceManager resourceManager; + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHRenderGraph (void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void Init (Handle const& logicalDevice, Handle const& swapchain) noexcept; + void AddResource(std::string resourceName, SH_ATT_DESC_TYPE type, uint32_t w = static_cast(-1), uint32_t h = static_cast(-1), vk::Format format = vk::Format::eB8G8R8A8Unorm, uint32_t levels = 1, vk::ImageCreateFlagBits createFlags = {}); + Handle AddNode (std::string nodeName, std::initializer_list resourceNames, std::initializer_list predecessorNodes) noexcept; + void Generate (void) noexcept; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachDescGen.cpp b/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachDescGen.cpp new file mode 100644 index 00000000..aecebf4f --- /dev/null +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachDescGen.cpp @@ -0,0 +1,48 @@ +#include "SHPch.h" +#include "SHVkAttachDescGen.h" +#include "Graphics/Swapchain/SHVkSwapchain.h" + +namespace SHADE +{ + vk::AttachmentDescription SHVkAttachDescGen::GenGenericColorPresent(Handle const& swapchain) noexcept + { + return vk::AttachmentDescription + { + .format = swapchain->GetSurfaceFormatKHR().format, + .samples = vk::SampleCountFlagBits::e1, // > 1 for multisampling + + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + + // Ditto but with stencil + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + + // Layout of image when render pass begins and ends + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::ePresentSrcKHR + }; + } + + vk::AttachmentDescription SHVkAttachDescGen::GenDepthStencil(Handle const& swapchain) noexcept + { + return vk::AttachmentDescription + { + .format = swapchain->GetDepthFormat(), + .samples = vk::SampleCountFlagBits::e1, // > 1 for multisampling + + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + + // Ditto but with stencil + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + + // Layout of image when render pass begins and ends + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::ePresentSrcKHR + }; + + } + +} diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachDescGen.h b/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachDescGen.h new file mode 100644 index 00000000..a44f4f27 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachDescGen.h @@ -0,0 +1,20 @@ +#ifndef SH_VK_ATTACHMENT_DESC_GEN_H +#define SH_VK_ATTACHMENT_DESC_GEN_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" + +namespace SHADE +{ + class SHVkSwapchain; + + class SHVkAttachDescGen + { + public: + static vk::AttachmentDescription GenGenericColorPresent (Handle const& swapchain) noexcept; + static vk::AttachmentDescription GenDepthStencil (Handle const& swapchain) noexcept; + }; + +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachment.h b/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachment.h new file mode 100644 index 00000000..d93b4d1a --- /dev/null +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkAttachment.h @@ -0,0 +1,44 @@ +#ifndef SH_VK_ATTACHMENT_H +#define SH_VK_ATTACHMENT_H + +namespace SHADE +{ + //struct SHVkAttachmentReference + //{ + // //! Identifies the attachment index of in the array of attachments in renderpass + // uint32_t attachment; + + // //! What layout the attachment uses during the subpass + //vk::ImageLayout imageLayout; + //}; + + //struct SHVkAttachmentDescription + //{ + // //! Specifies that the attachment aliases the same device memory as other attachments + // bool mayAlias; + + // //! specifies the format of the image that will be used for the attachment + // vk::Format format; + + // //! Number of samples of the image + // uint8_t samples; + + // //! How to treat contents of an attachment at the beginning of a subpass where it is FIRST used + // vk::AttachmentLoadOp loadOp; + + // //! How to treat contents of an attachment at the beginning of a subpass where it is FIRST used + // vk::AttachmentStoreOp storeOp; + + // // TODO: We don't really want to mess with stencils yet, so this is left out + // // stecilLoadOp + // // stecilStoreOp + + // // layout that the attachment will be in when render pass instance begins + // vk::ImageLayout initialLayout; + + // // layout that the attachment will be in when render pass instance ends + // vk::ImageLayout finalLayout; + //}; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.cpp b/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.cpp new file mode 100644 index 00000000..53e3326e --- /dev/null +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.cpp @@ -0,0 +1,240 @@ +#include "SHPch.h" +#include "SHVkRenderpass.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/SHVkUtil.h" +#include "Graphics/Framebuffer/SHVkFramebuffer.h" + +namespace SHADE +{ + + /***************************************************************************/ + /*! + + \brief + Renderpass constructor. + + \param vkDescriptions + Descriptions of the attachments. You can create your own description + using SHVkAttachmentGen or create your own and pass them here. + + \param subpass + Subpasses are all specified through the constructor. #NoteToSelf: This + implementation of passing in an entire subpass (which is essentially all + the variables that vulkan needs) seems rather poorly abstracted. The goal + was to omit a few variables that could be generated by allowing users + to specify some parameters. However due to a lack of experience with + Vulkan, this is the way to go for now. + + */ + /***************************************************************************/ + SHVkRenderpass::SHVkRenderpass(Handle const& inLogicalDeviceHdl, std::span const vkDescriptions, std::vector const& subpasses) noexcept + : logicalDeviceHdl {inLogicalDeviceHdl} + , clearColors{} + { + // TODO: temporary only + clearColors[0].color = { {{0.0f, 0.0f, 0.0f, 1.0f}} }; + clearColors[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + + vk::RenderPassCreateInfo renderPassCreateInfo{}; + std::vector subpassDeps; + + // For validating the depth ref + auto isValidDepthRef = [&](vk::AttachmentReference const& depthRef) -> bool + { + return !(depthRef.attachment == static_cast (-1) && depthRef.layout == vk::ImageLayout::eUndefined); + }; + + uint32_t subpassIndex = 0; + if (!subpasses.empty()) + { + for (auto& subpass : subpasses) + { + subpassDescriptions.emplace_back(); + auto& spDesc = subpassDescriptions.back(); + + spDesc.pColorAttachments = subpass.colorRefs.data(); + spDesc.colorAttachmentCount = static_cast(subpass.colorRefs.size()); + spDesc.pInputAttachments = subpass.inputRefs.data(); + spDesc.inputAttachmentCount = static_cast(subpass.inputRefs.size()); + spDesc.pipelineBindPoint = subpass.pipelineBindPoint; + + // We want to leave the depth stencil ref if its layout is undefined and attachment index is invalid + if (isValidDepthRef(subpass.depthStencilRefs)) + spDesc.pDepthStencilAttachment = &subpass.depthStencilRefs; + + vk::SubpassDependency dependency + { + // we want previous subpass dependent on this subpass. If first subpass, no dependency + .srcSubpass = subpassIndex == 0 ? VK_SUBPASS_EXTERNAL : subpass.dependentSubpassIndex, + .dstSubpass = subpassIndex == 0 ? 0 : subpassIndex + 1, + + // Only when these stages are done will commands in dst subpass be executed + .srcStageMask = subpass.srcStageMask, + + // commands here will only run when src subpass has fully completed + .dstStageMask = subpass.dstStageMask, + + // What is the access scope of the previous subpass + .srcAccessMask = subpass.srcAccessMask, + + // What is the access scope of the current subpass + .dstAccessMask = subpass.dstAccessMask, + }; + + // Push a new dependency + subpassDeps.push_back(dependency); + + ++subpassIndex; + } + + // Renderpass create info for render pass creation + renderPassCreateInfo.attachmentCount = static_cast(vkDescriptions.size()); + renderPassCreateInfo.pAttachments = vkDescriptions.data(); + renderPassCreateInfo.subpassCount = static_cast(subpassDescriptions.size()); + renderPassCreateInfo.pSubpasses = subpassDescriptions.data(); + renderPassCreateInfo.dependencyCount = static_cast(subpassDeps.size()); + renderPassCreateInfo.pDependencies = subpassDeps.data(); + + } + // No subpasses passed in, create a default one. + else + { + // Create color attachment references for every color attachment passed in + std::vector colorRefs; + + // If have depth attachment, account for that one. + vk::AttachmentReference depthStencilRef + { + .attachment = VK_ATTACHMENT_UNUSED, + .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal + }; + + // Loop through every reference + for (uint32_t i = 0; i < vkDescriptions.size(); ++i) + { + // If no depth stencil attachment has been found yet + if (depthStencilRef.attachment == VK_ATTACHMENT_UNUSED) + { + // If the description of the attachment has a format that is depth stencil related + if (SHVkUtil::IsDepthStencilAttachment(vkDescriptions[i].format)) + { + // Save the index of the depth stencil attachment description + depthStencilRef.attachment = i; + continue; + } + } + + // If its not depth stencil, it is a color attachment. Push it in the container. + colorRefs.push_back(vk::AttachmentReference + { + .attachment = i, + .layout = vk::ImageLayout::eGeneral, + }); + } + + // Default subpass has references to all attachments in the renderpass (or more technically the framebuffer) + vk::SubpassDescription defaultSubpass; + defaultSubpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; + defaultSubpass.colorAttachmentCount = static_cast(colorRefs.size()); + defaultSubpass.pColorAttachments = colorRefs.data(); + defaultSubpass.pDepthStencilAttachment = (depthStencilRef.attachment == VK_ATTACHMENT_UNUSED) ? &depthStencilRef : nullptr; + + // Prepare the renderpass create info + renderPassCreateInfo.attachmentCount = static_cast(vkDescriptions.size()); + renderPassCreateInfo.pAttachments = vkDescriptions.data(); + renderPassCreateInfo.subpassCount = 1; + renderPassCreateInfo.pSubpasses = &defaultSubpass; + + // No dependencies since only 1 subpass + renderPassCreateInfo.dependencyCount = 0; + renderPassCreateInfo.pDependencies = nullptr; + } + + + // Create the renderpass + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createRenderPass(&renderPassCreateInfo, nullptr, &vkRenderpass); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Renderpass. "); + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created Renderpass. "); + } + } + + SHVkRenderpass::SHVkRenderpass(Handle const& inLogicalDeviceHdl, std::span const vkDescriptions, std::span const spDescs, std::span const spDeps) noexcept + : logicalDeviceHdl{ inLogicalDeviceHdl } + , clearColors{} + { + // TODO: temporary only + clearColors[0].color = { {{0.0f, 0.0f, 0.0f, 1.0f}} }; + clearColors[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + + subpassDescriptions.resize (spDescs.size()); + for (uint32_t i = 0; i < subpassDescriptions.size(); ++i) + { + subpassDescriptions[i] = spDescs[i]; + } + + vk::RenderPassCreateInfo renderPassCreateInfo{}; + + renderPassCreateInfo.attachmentCount = static_cast(vkDescriptions.size()); + renderPassCreateInfo.pAttachments = vkDescriptions.data(); + renderPassCreateInfo.subpassCount = static_cast(subpassDescriptions.size()); + renderPassCreateInfo.pSubpasses = subpassDescriptions.data(); + renderPassCreateInfo.dependencyCount = static_cast(spDeps.size()); + renderPassCreateInfo.pDependencies = spDeps.data(); + + // Create the renderpass + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createRenderPass(&renderPassCreateInfo, nullptr, &vkRenderpass); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Renderpass. "); + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created Renderpass. "); + } + } + + SHVkRenderpass::SHVkRenderpass(SHVkRenderpass&& rhs) noexcept + : vkRenderpass {rhs.vkRenderpass} + , logicalDeviceHdl {rhs.logicalDeviceHdl} + , subpassDescriptions {std::move (rhs.subpassDescriptions)} + , clearColors {std::move (rhs.clearColors)} + { + rhs.vkRenderpass = VK_NULL_HANDLE; + } + + + SHVkRenderpass& SHVkRenderpass::operator=(SHVkRenderpass&& rhs) noexcept + { + if (&rhs == this) + return *this; + + vkRenderpass = rhs.vkRenderpass; + logicalDeviceHdl = rhs.logicalDeviceHdl; + subpassDescriptions = std::move(rhs.subpassDescriptions); + clearColors = std::move(rhs.clearColors); + + rhs.vkRenderpass = VK_NULL_HANDLE; + + return *this; + } + + SHVkRenderpass::~SHVkRenderpass(void) noexcept + { + logicalDeviceHdl->GetVkLogicalDevice().destroyRenderPass(vkRenderpass, nullptr); + } + + + vk::RenderPass SHVkRenderpass::GetVkRenderpass(void) const noexcept + { + return vkRenderpass; + } + + std::array const& SHVkRenderpass::GetClearColors(void) const noexcept + { + return clearColors; + } + +} diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.h b/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.h new file mode 100644 index 00000000..6ffd6c38 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkRenderpass.h @@ -0,0 +1,61 @@ +#ifndef SH_VK_RENDERPASS_H +#define SH_VK_RENDERPASS_H + +#include "SHVkAttachDescGen.h" +#include "SHVkSubpassParams.h" +#include "Resource/Handle.h" +#include + +namespace SHADE +{ + class SHVkLogicalDevice; + class SHVkFramebuffer; + + class SHVkRenderpass + { + private: + /*-----------------------------------------------------------------------*/ + /* STATIC CONSTEXPR VALUES */ + /*-----------------------------------------------------------------------*/ + static constexpr uint32_t NUM_CLEAR_COLORS = 2; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Vulkan handle to a renderpass + vk::RenderPass vkRenderpass; + + //! Logical device required for creation + Handle logicalDeviceHdl; + + //! Container of subpass information used to construct subpasses + std::vector subpassDescriptions; + + //! Clear colors for the color and depth + std::array clearColors; + + public: + /*-----------------------------------------------------------------------*/ + /* CTOR AND DTOR */ + /*-----------------------------------------------------------------------*/ + // NOTE: I don't really exposing Vulkan objects as params but this is for performance + SHVkRenderpass(Handle const& inLogicalDeviceHdl, std::span const vkDescriptions, std::vector const& subpasses) noexcept; + SHVkRenderpass(Handle const& inLogicalDeviceHdl, std::span const vkDescriptions, std::span const spDescs, std::span const spDeps) noexcept; + ~SHVkRenderpass(void) noexcept; + + SHVkRenderpass(SHVkRenderpass&& rhs) noexcept; + SHVkRenderpass& operator=(SHVkRenderpass&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + vk::RenderPass GetVkRenderpass(void) const noexcept; + std::array const& GetClearColors (void) const noexcept; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassDescription.h b/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassDescription.h new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassDescription.h @@ -0,0 +1 @@ +#pragma once diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassParams.cpp b/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassParams.cpp new file mode 100644 index 00000000..a1cafc97 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassParams.cpp @@ -0,0 +1,7 @@ +#include "SHPch.h" +#include "SHVkSubpassParams.h" + +namespace SHADE +{ + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassParams.h b/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassParams.h new file mode 100644 index 00000000..bf789c54 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Renderpass/SHVkSubpassParams.h @@ -0,0 +1,41 @@ +#ifndef SH_VK_SUBPASS_PARAMS_H +#define SH_VK_SUBPASS_PARAMS_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" +#include + +namespace SHADE +{ + struct SHVkSubpassParams + { + // pipeline bind point + vk::PipelineBindPoint pipelineBindPoint; + + //! Color attachment references + std::vector colorRefs; + + //! Input attachment references + std::vector inputRefs; + + //! Depth stencil attachment + vk::AttachmentReference depthStencilRefs {static_cast(-1), vk::ImageLayout::eUndefined}; + + //! Src stage mask + vk::PipelineStageFlags srcStageMask; + + //! Src stage mask + vk::PipelineStageFlags dstStageMask; + + //! Src access mask + vk::AccessFlags srcAccessMask; + + //! Dst access mask + vk::AccessFlags dstAccessMask; + + uint32_t dependentSubpassIndex = VK_SUBPASS_EXTERNAL; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/SHVkUtil.cpp b/SHADE_Engine/src/Graphics/SHVkUtil.cpp new file mode 100644 index 00000000..2fa03fbf --- /dev/null +++ b/SHADE_Engine/src/Graphics/SHVkUtil.cpp @@ -0,0 +1,22 @@ +#include "SHPch.h" +#include "SHVkUtil.h" + +namespace SHADE +{ + + bool SHVkUtil::IsDepthOnlyFormat(vk::Format format) noexcept + { + return format == vk::Format::eD16Unorm || + format == vk::Format::eD32Sfloat; + + } + + bool SHVkUtil::IsDepthStencilAttachment(vk::Format format) noexcept + { + return format == vk::Format::eD16UnormS8Uint || + format == vk::Format::eD24UnormS8Uint || + format == vk::Format::eD32SfloatS8Uint || + IsDepthOnlyFormat(format); + } + +} diff --git a/SHADE_Engine/src/Graphics/SHVkUtil.h b/SHADE_Engine/src/Graphics/SHVkUtil.h new file mode 100644 index 00000000..4513f59c --- /dev/null +++ b/SHADE_Engine/src/Graphics/SHVkUtil.h @@ -0,0 +1,16 @@ +#ifndef SH__VK_UTIL_H +#define SH__VK_UTIL_H + +#include "SHVulkanIncludes.h" + +namespace SHADE +{ + class SHVkUtil + { + public: + static bool IsDepthOnlyFormat (vk::Format format) noexcept; + static bool IsDepthStencilAttachment(vk::Format format) noexcept; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/SHVulkanDefines.h b/SHADE_Engine/src/Graphics/SHVulkanDefines.h new file mode 100644 index 00000000..542f379d --- /dev/null +++ b/SHADE_Engine/src/Graphics/SHVulkanDefines.h @@ -0,0 +1,12 @@ +#ifndef SH_VULKAN_DEFINES_H +#define SH_VULKAN_DEFINES_H + +#include + +namespace SHADE +{ + using SHQueueFamilyIndex = uint32_t; + +} + +#endif diff --git a/SHADE_Engine/src/Graphics/SHVulkanIncludes.cpp b/SHADE_Engine/src/Graphics/SHVulkanIncludes.cpp new file mode 100644 index 00000000..e58edea0 --- /dev/null +++ b/SHADE_Engine/src/Graphics/SHVulkanIncludes.cpp @@ -0,0 +1,7 @@ +#include "SHPch.h" +#include "SHVulkanIncludes.h" + +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE + +#define VMA_IMPLEMENTATION +#include "vk_mem_alloc.h" diff --git a/SHADE_Engine/src/Graphics/SHVulkanIncludes.h b/SHADE_Engine/src/Graphics/SHVulkanIncludes.h new file mode 100644 index 00000000..472226a0 --- /dev/null +++ b/SHADE_Engine/src/Graphics/SHVulkanIncludes.h @@ -0,0 +1,10 @@ +#ifndef SH_VULKAN_INCLUDES_H +#define SH_VULKAN_INCLUDES_H + +#define VK_USE_PLATFORM_WIN32_KHR +#define VULKAN_HPP_NO_CONSTRUCTORS +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 +#define VULKAN_HPP_NO_NODISCARD_WARNINGS +#include + +#endif diff --git a/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.cpp b/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.cpp new file mode 100644 index 00000000..2135b9dd --- /dev/null +++ b/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.cpp @@ -0,0 +1,61 @@ +#include "SHPch.h" +#include "SHShaderBlockInterface.h" + +namespace SHADE +{ + + void SHShaderBlockInterface::AddVariable(std::string name, Variable&& newVariable) noexcept + { + variables.try_emplace (std::move(name), std::move (newVariable)); + } + + SHShaderBlockInterface::Variable const* const SHShaderBlockInterface::GetVariable(std::string const& variableName) const noexcept + { + if (variables.contains(variableName)) + return &variables.at(variableName); + + return nullptr; + } + + SHShaderBlockInterface::SHShaderBlockInterface(void) noexcept + : variables{} + , bytesRequired{ 0 } + { + + } + + SHShaderBlockInterface::SHShaderBlockInterface(SHShaderBlockInterface&& rhs) noexcept + : variables{ std::move(rhs.variables) } + , bytesRequired {std::move (rhs.bytesRequired)} + { + + } + + void SHShaderBlockInterface::SetBytesRequired(uint32_t bytes) noexcept + { + bytesRequired = bytes; + } + + + uint32_t SHShaderBlockInterface::GetBytesRequired(void) const noexcept + { + return bytesRequired; + } + + SHADE::SHShaderBlockInterface& SHShaderBlockInterface::operator=(SHShaderBlockInterface&& rhs) noexcept + { + if (&rhs == this) + return *this; + + variables = std::move(rhs.variables); + bytesRequired = std::move(rhs.bytesRequired); + + return *this; + } + + SHShaderBlockInterface::~SHShaderBlockInterface(void) noexcept + { + variables.clear(); + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h b/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h new file mode 100644 index 00000000..38c01bde --- /dev/null +++ b/SHADE_Engine/src/Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace SHADE +{ + + class SHShaderBlockInterface + { + public: + struct Variable + { + //! Offset of the variable in the block + uint32_t offset; + }; + private: + + //! container of variable information + std::unordered_map variables; + + //! bytes required by the block (includes padding). This variable is required + uint32_t bytesRequired; + + public: + void AddVariable (std::string name, Variable&& newVariable) noexcept; + Variable const* const GetVariable (std::string const& variableName) const noexcept; + + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHShaderBlockInterface(void) noexcept; + ~SHShaderBlockInterface(void) noexcept; + SHShaderBlockInterface(SHShaderBlockInterface&& rhs) noexcept; + SHShaderBlockInterface& operator=(SHShaderBlockInterface&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + void SetBytesRequired (uint32_t bytes) noexcept; + uint32_t GetBytesRequired (void) const noexcept; + }; +} diff --git a/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.cpp b/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.cpp new file mode 100644 index 00000000..8beb7d98 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.cpp @@ -0,0 +1,327 @@ +#include "SHPch.h" +#include "SHShaderReflected.h" +#include "Tools/SHLogger.h" +#include "Graphics/Instance/SHVkInstance.h" + + +namespace SHADE +{ + + void SHShaderDescriptorBindingInfo::AddBlockInterface(uint32_t set, uint32_t binding, Handle newBlockInterface) noexcept + { + BindingAndSetHash newIndex = binding; + newIndex |= static_cast(set) << 32; + blockInterfaces.emplace (newIndex, newBlockInterface); + } + + /***************************************************************************/ + /*! + + \brief + Reflects all the push constant information. + + */ + /***************************************************************************/ + void SHShaderReflected::ReflectPushConstantInfo(void) noexcept + { + // Skip all the initialization if there are no push constant blocks + if (reflectModule->push_constant_block_count == 0) + return; + + uint32_t pcCount = reflectModule->push_constant_block_count; + + // Array because we don't want to dynamically allocate memory + std::array pcVariable{}; + spvReflectEnumeratePushConstantBlocks(reflectModule.get(), &pcCount, pcVariable.data()); + + SpvReflectBlockVariable* var = pcVariable[0]; + + // Size that the push constant takes + pcInfo.size = var->size; + + // Name of the push constant struct (not the variable) + pcInfo.name = reflectModule->push_constant_blocks->name; + + // initialize member data + pcInfo.memberCount = reflectModule->push_constant_blocks->member_count; + pcInfo.members = reflectModule->push_constant_blocks->members; + } + + /***************************************************************************/ + /*! + + \brief + Reflects Descriptor set layout info. + + */ + /***************************************************************************/ + void SHShaderReflected::ReflectDescriptorBindingInfo(void) noexcept + { + if (reflectModule->descriptor_binding_count == 0) + return; + + // To improve performance, we are not going to check for duplicate set + // binding pairs. See SHShaderDescriptorBindingInfo for more details. + descBindingInfo.reflectedSets.resize(reflectModule->descriptor_set_count); + + spvReflectEnumerateDescriptorSets(reflectModule.get(), &reflectModule->descriptor_set_count, descBindingInfo.reflectedSets.data()); + } + + /***************************************************************************/ + /*! + + \brief + Reflects any shader block information from the shader. + + */ + /***************************************************************************/ + void SHShaderReflected::ReflectShaderBlockInterfaceInfo(void) noexcept + { + std::function, uint32_t, uint32_t&, std::string)> recurseForInfo; + recurseForInfo = [&recurseForInfo] + ( + SpvReflectBlockVariable const* const parentBlock, + Handle interfaceHdl, + uint32_t parentOffset, + uint32_t& biggestAlignment, + std::string parentVarName + ) -> void + { + for (uint32_t memberIndex = 0; memberIndex < parentBlock->member_count; ++memberIndex) + { + auto member = parentBlock->members[memberIndex]; + uint32_t dim = member.type_description->traits.numeric.vector.component_count; + + // If type it is basic type (float, double, vec, mat), we query offset and give it a name. + // After that we add variable to the interface. + switch (member.type_description->op) + { + case SpvOp::SpvOpTypeFloat: + interfaceHdl->AddVariable(parentVarName + std::string(member.name), SHShaderBlockInterface::Variable(parentOffset + member.offset)); + biggestAlignment = std::max (biggestAlignment, 4u); + break; + case SpvOp::SpvOpTypeVector: + interfaceHdl->AddVariable(parentVarName + std::string(member.name), SHShaderBlockInterface::Variable(parentOffset + member.offset)); + if (dim == 3) + dim = 4; + biggestAlignment = std::max (biggestAlignment, dim * member.type_description->traits.numeric.scalar.width / 8); + break; + case SpvOp::SpvOpTypeInt: + interfaceHdl->AddVariable(parentVarName + std::string(member.name), SHShaderBlockInterface::Variable(parentOffset + member.offset)); + biggestAlignment = std::max(biggestAlignment, 4u); + break; + case SpvOp::SpvOpTypeStruct: + recurseForInfo(&member, interfaceHdl, member.offset, biggestAlignment, parentVarName + std::string(member.name) + "."); + break; + } + } + }; + + for (auto const* const set : descBindingInfo.reflectedSets) + { + // Convenient variable for the current descriptor set index + uint32_t const CURRENT_SET = set->set; + + for (uint32_t i = 0; i < set->binding_count; ++i) + { + auto const* const binding = set->bindings[i]; + + if (binding->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER || + binding->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER || + binding->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) + { + // Create new interface. For this new interface, we want to start from the name of the variable + // used to identify the descriptor. However any variable that will exist in the interface won't contain + // the base variable of the descriptor. So e.g.: it won't be materialBuffer.data.color, it'll just + // be data.color. + auto newInterface = SHVkInstance::GetResourceManager().Create(); + uint32_t biggestAlignment = 0; + uint32_t bytesRequired = 0; + + recurseForInfo (&binding->block, newInterface, 0u, biggestAlignment, ""); + + uint32_t blockSize = 0; + if (binding->block.members) + { + auto const* const member = &binding->block.members[binding->block.member_count - 1]; + blockSize = member->offset + member->size; + } + + while (bytesRequired < blockSize) + bytesRequired += biggestAlignment; + + newInterface->SetBytesRequired (bytesRequired); + + descBindingInfo.AddBlockInterface (CURRENT_SET, binding->binding, newInterface); + } + } + } + } + + vk::DescriptorType SHShaderDescriptorBindingInfo::ConvertFromReflectDescType(SpvReflectDescriptorType const& type) noexcept + { + switch (type) + { + case SpvReflectDescriptorType::SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: + return vk::DescriptorType::eCombinedImageSampler; + case SpvReflectDescriptorType::SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE: + return vk::DescriptorType::eSampledImage; + case SpvReflectDescriptorType::SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER: + return vk::DescriptorType::eUniformBuffer; + case SpvReflectDescriptorType::SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: + return vk::DescriptorType::eUniformBufferDynamic; + case SpvReflectDescriptorType::SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER: + return vk::DescriptorType::eStorageBuffer; + case SpvReflectDescriptorType::SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: + return vk::DescriptorType::eStorageBufferDynamic; + case SpvReflectDescriptorType::SPV_REFLECT_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: + return vk::DescriptorType::eInputAttachment; + default: + return vk::DescriptorType::eCombinedImageSampler; + break; + } + } + + std::vector const& SHShaderDescriptorBindingInfo::GetReflectedSets(void) const noexcept + { + return reflectedSets; + } + + Handle SHShaderDescriptorBindingInfo::GetShaderBlockInterface(uint32_t set, uint32_t binding) const noexcept + { + SHShaderDescriptorBindingInfo::BindingAndSetHash hash = binding; + hash |= static_cast(set) << 32; + if (blockInterfaces.contains(hash)) + return blockInterfaces.at(hash); + + return {}; + } + + SHShaderReflected::SHShaderReflected(SHShaderReflected&& rhs) noexcept + : reflectModule {std::move (rhs.reflectModule)} + , pcInfo{ std::move(rhs.pcInfo) } + , descBindingInfo{std::move (rhs.descBindingInfo)} + { + + } + + /***************************************************************************/ + /*! + + \brief + Default constructor. Just moves the reflect module. + + */ + /***************************************************************************/ + SHShaderReflected::SHShaderReflected(void) noexcept + : reflectModule{std::move (std::make_unique())} + , pcInfo {} + , descBindingInfo{} + { + + } + + /***************************************************************************/ + /*! + + \brief + Move assignment operator. + + \param rhs + The object to move from. + + \return + A reference to this object. + + */ + /***************************************************************************/ + SHShaderReflected& SHShaderReflected::operator=(SHShaderReflected&& rhs) noexcept + { + if (&rhs == this) + return *this; + + reflectModule = std::move (rhs.reflectModule); + pcInfo = rhs.pcInfo; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Creates a spirv reflection module and reflect based on flags passed in. + + \param spirvBin + The spirv binary. + + \param spirvSize + The spirv binary size. + + \param shaderName + The name of the shader to print during error. + + */ + /***************************************************************************/ + void SHShaderReflected::Reflect(void* spirvBin, uint32_t spirvSize, std::string const& shaderName) noexcept + { + if (!HasReflected()) + { + if (auto result = spvReflectCreateShaderModule(spirvSize, spirvBin, reflectModule.get()); result != SPV_REFLECT_RESULT_SUCCESS) + { + SHLOG_ERROR("Failed to create shader reflect module" + shaderName); + return; + } + + + // Reflect all the push constant blocks + ReflectPushConstantInfo(); + + // Reflect descriptor set information + ReflectDescriptorBindingInfo(); + + // Reflect shader block information + ReflectShaderBlockInterfaceInfo(); + } + } + + /***************************************************************************/ + /*! + + \brief + Checks if the source in the reflect shader module is not nullptr to + determine if the module has been reflected. + + \return + If source is not nullptr, return true, otherwise return false. + + + */ + /***************************************************************************/ + bool SHShaderReflected::HasReflected(void) const noexcept + { + return reflectModule->_internal != nullptr; + } + + /***************************************************************************/ + /*! + + \brief + Getter for the push constant info. + + \return + Reference to push constant info. + + */ + /***************************************************************************/ + SHShaderPushConstantInfo const& SHShaderReflected::GetPushConstantInfo(void) const noexcept + { + return pcInfo; + } + + SHShaderDescriptorBindingInfo const& SHShaderReflected::GetDescriptorBindingInfo(void) const noexcept + { + return descBindingInfo; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.h b/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.h new file mode 100644 index 00000000..1250b54f --- /dev/null +++ b/SHADE_Engine/src/Graphics/Shaders/SHShaderReflected.h @@ -0,0 +1,110 @@ +#ifndef SH_SHADER_REFLECTED_H +#define SH_SHADER_REFLECTED_H + +#include "spirv-reflect/spirv_reflect.h" +#include "Graphics/SHVulkanIncludes.h" +#include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" +#include "Graphics/Shaders/BlockInterface/SHShaderBlockInterface.h" +#include + +namespace SHADE +{ + struct SHShaderDescriptorBindingInfo + { + public: + using BindingAndSetHash = uint64_t; + + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Descriptor sets parsed in the shader + std::vector reflectedSets; + + //! The interface for the block in the shader + std::unordered_map> blockInterfaces; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void AddBlockInterface(uint32_t set, uint32_t binding, Handle newBlockInterface) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + static vk::DescriptorType ConvertFromReflectDescType(SpvReflectDescriptorType const& type) noexcept; + + /*-----------------------------------------------------------------------*/ + /* GETTERS AND SETTERS */ + /*-----------------------------------------------------------------------*/ + std::vector const& GetReflectedSets (void) const noexcept; + Handle GetShaderBlockInterface (uint32_t set, uint32_t binding) const noexcept; + + friend class SHShaderReflected; + + }; + + struct SHShaderPushConstantInfo + { + //! Size of the push constant block + uint32_t size; + + //! Name of the push constant block (not the variable). + //! Do not delete this, it is non-owning + const char* name; + + //! Number of member variables + uint32_t memberCount; + + //! Pointer to members. Do not delete. This is non-owning + SpvReflectBlockVariable* members; + + }; + + class SHShaderReflected + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Reflected module + std::unique_ptr reflectModule; + + //! Reflected push constant information + SHShaderPushConstantInfo pcInfo; + + //! Binding information for descriptor layout creation + SHShaderDescriptorBindingInfo descBindingInfo; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void ReflectPushConstantInfo (void) noexcept; + void ReflectDescriptorBindingInfo (void) noexcept; + void ReflectShaderBlockInterfaceInfo (void) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHShaderReflected(void) noexcept; + SHShaderReflected(SHShaderReflected&& rhs) noexcept; + SHShaderReflected& operator=(SHShaderReflected&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void Reflect (void* spirvBin, uint32_t spirvSize, std::string const& shaderName) noexcept; + bool HasReflected (void) const noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + SHShaderPushConstantInfo const& GetPushConstantInfo (void) const noexcept; + SHShaderDescriptorBindingInfo const& GetDescriptorBindingInfo (void) const noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Shaders/SHVkShaderModule.cpp b/SHADE_Engine/src/Graphics/Shaders/SHVkShaderModule.cpp new file mode 100644 index 00000000..c1fac76c --- /dev/null +++ b/SHADE_Engine/src/Graphics/Shaders/SHVkShaderModule.cpp @@ -0,0 +1,115 @@ +#include "SHPch.h" +#include "SHVkShaderModule.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Debugging/SHVulkanDebugUtil.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + + SHVkShaderModule::SHVkShaderModule(Handle const& inLogicalDeviceHdl, std::vector const& binaryData, std::string inEntryPoint, vk::ShaderStageFlagBits stage, std::string const& name) noexcept + : logicalDeviceHdl {inLogicalDeviceHdl} + , shaderStage {stage} + , entryPoint {inEntryPoint} + , vkShaderModule {nullptr} + , spirvBinary{} + , shaderName {name} + , reflectedData {} + { + // Prepare the create info + vk::ShaderModuleCreateInfo moduleCreateInfo + { + .codeSize = binaryData.size() * sizeof (uint32_t), + .pCode = binaryData.data(), + }; + + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createShaderModule(&moduleCreateInfo, nullptr, &vkShaderModule); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create shader module. "); + return; + } + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created shader module."); + + // TODO: Right now, this is doing a copy, we need to figure out if its better to just move from the resource management (source library) instead. The hope is that + // shader modules only need 1 of themselves. + spirvBinary = binaryData; + } + + SHVkShaderModule::SHVkShaderModule(SHVkShaderModule&& rhs) noexcept + : vkShaderModule {rhs.vkShaderModule} + , spirvBinary{ std::move(rhs.spirvBinary)} + , shaderStage {rhs.shaderStage} + , entryPoint {std::move (rhs.entryPoint)} + , reflectedData {std::move (rhs.reflectedData)} + , shaderName {std::move (rhs.shaderName)} + , logicalDeviceHdl {rhs.logicalDeviceHdl} + { + rhs.vkShaderModule = VK_NULL_HANDLE; + } + + SHVkShaderModule& SHVkShaderModule::operator=(SHVkShaderModule&& rhs) noexcept + { + if (&rhs == this) + return *this; + + vkShaderModule = rhs.vkShaderModule; + spirvBinary = std::move(rhs.spirvBinary); + shaderStage = rhs.shaderStage; + entryPoint = std::move(rhs.entryPoint); + reflectedData = std::move(rhs.reflectedData); + shaderName = std::move(rhs.shaderName); + logicalDeviceHdl = std::move(rhs.logicalDeviceHdl); + + rhs.vkShaderModule = VK_NULL_HANDLE; + return *this; + } + + SHVkShaderModule::~SHVkShaderModule(void) noexcept + { + logicalDeviceHdl->GetVkLogicalDevice().destroyShaderModule (vkShaderModule, nullptr); + } + + void SHVkShaderModule::Reflect(void) noexcept + { + auto cModule = vkShaderModule.operator VkShaderModule(); + if (cModule) + reflectedData.Reflect(spirvBinary.data(), static_cast(sizeof (uint32_t) * spirvBinary.size()), shaderName); + else + { + SHLOG_ERROR("Cannot reflect shader module. Shader module has either not been created or failed to create. "); + } + } + + void SHVkShaderModule::OnChange(void) noexcept + { + for (auto& callback : onChangeCallbacks) + callback(); + } + + void SHVkShaderModule::AddCallback(SHShaderChangeCallback&& callback) noexcept + { + onChangeCallbacks.emplace_back(std::move(callback)); + } + + std::string const& SHVkShaderModule::GetEntryPoint(void) const noexcept + { + return entryPoint; + } + + vk::ShaderStageFlagBits SHVkShaderModule::GetShaderStageFlagBits(void) const noexcept + { + return shaderStage; + } + + vk::ShaderModule SHVkShaderModule::GetVkShaderModule(void) const noexcept + { + return vkShaderModule; + } + + SHShaderReflected const& SHVkShaderModule::GetReflectedData(void) const noexcept + { + return reflectedData; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Shaders/SHVkShaderModule.h b/SHADE_Engine/src/Graphics/Shaders/SHVkShaderModule.h new file mode 100644 index 00000000..2973d734 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Shaders/SHVkShaderModule.h @@ -0,0 +1,83 @@ +#ifndef SH_VK_SHADER_MODULE_H +#define SH_VK_SHADER_MODULE_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" +#include "SHShaderReflected.h" +#include + +namespace SHADE +{ + class SHVkLogicalDevice; + + class SHVkShaderModule + { + public: + /*-----------------------------------------------------------------------*/ + /* TYPE DEFINITIONS */ + /*-----------------------------------------------------------------------*/ + using SHShaderChangeCallback = std::function; + + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Vulkan handle for shader module + vk::ShaderModule vkShaderModule; + + //! the spir-v binary code + std::vector spirvBinary; + + //! Shader stage of the shader (MUST ONLY BE 1 VALUE AND NOT BIT COMBINATION) + vk::ShaderStageFlagBits shaderStage; + + //! shader entry point (e.g. main) + std::string entryPoint; + + //! Device for creation and deletion of shader module + Handle logicalDeviceHdl; + + //! Shader reflection information + SHShaderReflected reflectedData; + + //! Shader module's name + std::string shaderName; + + //! When shaders get hot-reloaded, this container could serve as an event + //! response to call all the functions that need to be called. Specifically + //! pipeline layouts that need to re-parse the newly reflected data and create + //! descriptor set layouts and push constant ranges. + std::vector onChangeCallbacks; + + // #NoteToSelf: From Tomas module, pipeline shader stage create info isn't created here + // because the struct allows specialization info which should not be part of a module itself. + // This struct should be created in the pipeline instead. + + + public: + /*-----------------------------------------------------------------------*/ + /* CTORS AND DTORS */ + /*-----------------------------------------------------------------------*/ + SHVkShaderModule(Handle const& inLogicalDeviceHdl, std::vector const& binaryData, std::string inEntryPoint, vk::ShaderStageFlagBits stage, std::string const& name) noexcept; + ~SHVkShaderModule(void) noexcept; + SHVkShaderModule(SHVkShaderModule&& rhs) noexcept; + SHVkShaderModule& operator= (SHVkShaderModule&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void Reflect (void) noexcept; + void OnChange (void) noexcept; + void AddCallback (SHShaderChangeCallback&& callback) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + std::string const& GetEntryPoint (void) const noexcept; + vk::ShaderStageFlagBits GetShaderStageFlagBits (void) const noexcept; + vk::ShaderModule GetVkShaderModule (void) const noexcept; + SHShaderReflected const& GetReflectedData (void) const noexcept; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Shaders/spirv-reflect/spirv_reflect.cpp b/SHADE_Engine/src/Graphics/Shaders/spirv-reflect/spirv_reflect.cpp new file mode 100644 index 00000000..5ef4b4db --- /dev/null +++ b/SHADE_Engine/src/Graphics/Shaders/spirv-reflect/spirv_reflect.cpp @@ -0,0 +1,5046 @@ +/* + Copyright 2017-2022 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "SHPch.h" +#include "spirv_reflect.h" +#include +#include +#include + +#if defined(WIN32) + #define _CRTDBG_MAP_ALLOC + #include + #include +#else + #include +#endif + +#if defined(SPIRV_REFLECT_ENABLE_ASSERTS) + #define SPV_REFLECT_ASSERT(COND) \ + assert(COND); +#else +#define SPV_REFLECT_ASSERT(COND) +#endif + +// Temporary enums until these make it into SPIR-V/Vulkan +// clang-format off +enum { + SpvReflectOpDecorateId = 332, + SpvReflectOpDecorateStringGOOGLE = 5632, + SpvReflectOpMemberDecorateStringGOOGLE = 5633, + SpvReflectDecorationHlslCounterBufferGOOGLE = 5634, + SpvReflectDecorationHlslSemanticGOOGLE = 5635 +}; +// clang-format on + +// clang-format off +enum { + SPIRV_STARTING_WORD_INDEX = 5, + SPIRV_WORD_SIZE = sizeof(uint32_t), + SPIRV_BYTE_WIDTH = 8, + SPIRV_MINIMUM_FILE_SIZE = SPIRV_STARTING_WORD_INDEX * SPIRV_WORD_SIZE, + SPIRV_DATA_ALIGNMENT = 4 * SPIRV_WORD_SIZE, // 16 + SPIRV_ACCESS_CHAIN_INDEX_OFFSET = 4, +}; +// clang-format on + +// clang-format off +enum { + INVALID_VALUE = 0xFFFFFFFF, +}; +// clang-format on + +// clang-format off +enum { + MAX_NODE_NAME_LENGTH = 1024, +}; +// clang-format on + +// clang-format off +enum { + IMAGE_SAMPLED = 1, + IMAGE_STORAGE = 2 +}; +// clang-format on + +// clang-format off +typedef struct SpvReflectPrvArrayTraits { + uint32_t element_type_id; + uint32_t length_id; +} SpvReflectPrvArrayTraits; +// clang-format on + +// clang-format off +typedef struct SpvReflectPrvImageTraits { + uint32_t sampled_type_id; + SpvDim dim; + uint32_t depth; + uint32_t arrayed; + uint32_t ms; + uint32_t sampled; + SpvImageFormat image_format; +} SpvReflectPrvImageTraits; +// clang-format on + +// clang-format off +typedef struct SpvReflectPrvNumberDecoration { + uint32_t word_offset; + uint32_t value; +} SpvReflectPrvNumberDecoration; +// clang-format on + +// clang-format off +typedef struct SpvReflectPrvStringDecoration { + uint32_t word_offset; + const char* value; +} SpvReflectPrvStringDecoration; +// clang-format on + +// clang-format off +typedef struct SpvReflectPrvDecorations { + bool is_relaxed_precision; + bool is_block; + bool is_buffer_block; + bool is_row_major; + bool is_column_major; + bool is_built_in; + bool is_noperspective; + bool is_flat; + bool is_non_writable; + bool is_non_readable; + SpvReflectPrvNumberDecoration set; + SpvReflectPrvNumberDecoration binding; + SpvReflectPrvNumberDecoration input_attachment_index; + SpvReflectPrvNumberDecoration location; + SpvReflectPrvNumberDecoration offset; + SpvReflectPrvNumberDecoration uav_counter_buffer; + SpvReflectPrvStringDecoration semantic; + uint32_t array_stride; + uint32_t matrix_stride; + SpvBuiltIn built_in; +} SpvReflectPrvDecorations; +// clang-format on + +// clang-format off +typedef struct SpvReflectPrvNode { + uint32_t result_id; + SpvOp op; + uint32_t result_type_id; + uint32_t type_id; + SpvStorageClass storage_class; + uint32_t word_offset; + uint32_t word_count; + bool is_type; + + SpvReflectPrvArrayTraits array_traits; + SpvReflectPrvImageTraits image_traits; + uint32_t image_type_id; + + const char* name; + SpvReflectPrvDecorations decorations; + uint32_t member_count; + const char** member_names; + SpvReflectPrvDecorations* member_decorations; +} SpvReflectPrvNode; +// clang-format on + +// clang-format off +typedef struct SpvReflectPrvString { + uint32_t result_id; + const char* string; +} SpvReflectPrvString; +// clang-format on + +// clang-format off +typedef struct SpvReflectPrvFunction { + uint32_t id; + uint32_t callee_count; + uint32_t* callees; + struct SpvReflectPrvFunction** callee_ptrs; + uint32_t accessed_ptr_count; + uint32_t* accessed_ptrs; +} SpvReflectPrvFunction; +// clang-format on + +// clang-format off +typedef struct SpvReflectPrvAccessChain { + uint32_t result_id; + uint32_t result_type_id; + // + // Pointing to the base of a composite object. + // Generally the id of descriptor block variable + uint32_t base_id; + // + // From spec: + // The first index in Indexes will select the + // top-level member/element/component/element + // of the base composite + uint32_t index_count; + uint32_t* indexes; +} SpvReflectPrvAccessChain; +// clang-format on + +// clang-format off +typedef struct SpvReflectPrvParser { + size_t spirv_word_count; + uint32_t* spirv_code; + uint32_t string_count; + SpvReflectPrvString* strings; + SpvSourceLanguage source_language; + uint32_t source_language_version; + uint32_t source_file_id; + const char* source_embedded; + size_t node_count; + SpvReflectPrvNode* nodes; + uint32_t entry_point_count; + uint32_t function_count; + SpvReflectPrvFunction* functions; + uint32_t access_chain_count; + SpvReflectPrvAccessChain* access_chains; + + uint32_t type_count; + uint32_t descriptor_count; + uint32_t push_constant_count; +} SpvReflectPrvParser; +// clang-format on + +static uint32_t Max( + uint32_t a, + uint32_t b) +{ + return a > b ? a : b; +} + +static uint32_t RoundUp( + uint32_t value, + uint32_t multiple) +{ + assert(multiple && ((multiple & (multiple - 1)) == 0)); + return (value + multiple - 1) & ~(multiple - 1); +} + +#define IsNull(ptr) \ + (ptr == NULL) + +#define IsNotNull(ptr) \ + (ptr != NULL) + +#define SafeFree(ptr) \ + { \ + free((void*)ptr); \ + ptr = NULL; \ + } + +static int SortCompareUint32( + const void* a, + const void* b) +{ + const uint32_t* p_a = (const uint32_t*)a; + const uint32_t* p_b = (const uint32_t*)b; + + return (int)*p_a - (int)*p_b; +} + +// +// De-duplicates a sorted array and returns the new size. +// +// Note: The array doesn't actually need to be sorted, just +// arranged into "runs" so that all the entries with one +// value are adjacent. +// +static size_t DedupSortedUint32(uint32_t* arr, size_t size) +{ + if (size == 0) { + return 0; + } + size_t dedup_idx = 0; + for (size_t i = 0; i < size; ++i) { + if (arr[dedup_idx] != arr[i]) { + ++dedup_idx; + arr[dedup_idx] = arr[i]; + } + } + return dedup_idx+1; +} + +static bool SearchSortedUint32( + const uint32_t* arr, + size_t size, + uint32_t target) +{ + size_t lo = 0; + size_t hi = size; + while (lo < hi) { + size_t mid = (hi - lo) / 2 + lo; + if (arr[mid] == target) { + return true; + } else if (arr[mid] < target) { + lo = mid+1; + } else { + hi = mid; + } + } + return false; +} + +static SpvReflectResult IntersectSortedUint32( + const uint32_t* p_arr0, + size_t arr0_size, + const uint32_t* p_arr1, + size_t arr1_size, + uint32_t** pp_res, + size_t* res_size +) +{ + *pp_res = NULL; + *res_size = 0; + if (IsNull(p_arr0) || IsNull(p_arr1)) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + const uint32_t* arr0_end = p_arr0 + arr0_size; + const uint32_t* arr1_end = p_arr1 + arr1_size; + + const uint32_t* idx0 = p_arr0; + const uint32_t* idx1 = p_arr1; + while (idx0 != arr0_end && idx1 != arr1_end) { + if (*idx0 < *idx1) { + ++idx0; + } else if (*idx0 > *idx1) { + ++idx1; + } else { + ++*res_size; + ++idx0; + ++idx1; + } + } + + if (*res_size > 0) { + *pp_res = (uint32_t*)calloc(*res_size, sizeof(**pp_res)); + if (IsNull(*pp_res)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + uint32_t* idxr = *pp_res; + idx0 = p_arr0; + idx1 = p_arr1; + while (idx0 != arr0_end && idx1 != arr1_end) { + if (*idx0 < *idx1) { + ++idx0; + } else if (*idx0 > *idx1) { + ++idx1; + } else { + *(idxr++) = *idx0; + ++idx0; + ++idx1; + } + } + } + return SPV_REFLECT_RESULT_SUCCESS; +} + + +static bool InRange( + const SpvReflectPrvParser* p_parser, + uint32_t index) +{ + bool in_range = false; + if (IsNotNull(p_parser)) { + in_range = (index < p_parser->spirv_word_count); + } + return in_range; +} + +static SpvReflectResult ReadU32( + SpvReflectPrvParser* p_parser, + uint32_t word_offset, + uint32_t* p_value) +{ + assert(IsNotNull(p_parser)); + assert(IsNotNull(p_parser->spirv_code)); + assert(InRange(p_parser, word_offset)); + SpvReflectResult result = SPV_REFLECT_RESULT_ERROR_SPIRV_UNEXPECTED_EOF; + if (IsNotNull(p_parser) && IsNotNull(p_parser->spirv_code) && InRange(p_parser, word_offset)) { + *p_value = *(p_parser->spirv_code + word_offset); + result = SPV_REFLECT_RESULT_SUCCESS; + } + return result; +} + +#define CHECKED_READU32(parser, word_offset, value) \ + { \ + SpvReflectResult checked_readu32_result = ReadU32(parser, \ + word_offset, (uint32_t*)&(value)); \ + if (checked_readu32_result != SPV_REFLECT_RESULT_SUCCESS) { \ + return checked_readu32_result; \ + } \ + } + +#define CHECKED_READU32_CAST(parser, word_offset, cast_to_type, value) \ + { \ + uint32_t checked_readu32_cast_u32 = UINT32_MAX; \ + SpvReflectResult checked_readu32_cast_result = ReadU32(parser, \ + word_offset, \ + (uint32_t*)&(checked_readu32_cast_u32)); \ + if (checked_readu32_cast_result != SPV_REFLECT_RESULT_SUCCESS) { \ + return checked_readu32_cast_result; \ + } \ + value = (cast_to_type)checked_readu32_cast_u32; \ + } + +#define IF_READU32(result, parser, word_offset, value) \ + if ((result) == SPV_REFLECT_RESULT_SUCCESS) { \ + result = ReadU32(parser, word_offset, (uint32_t*)&(value)); \ + } + +#define IF_READU32_CAST(result, parser, word_offset, cast_to_type, value) \ + if ((result) == SPV_REFLECT_RESULT_SUCCESS) { \ + uint32_t if_readu32_cast_u32 = UINT32_MAX; \ + result = ReadU32(parser, word_offset, &if_readu32_cast_u32); \ + if ((result) == SPV_REFLECT_RESULT_SUCCESS) { \ + value = (cast_to_type)if_readu32_cast_u32; \ + } \ + } + +static SpvReflectResult ReadStr( + SpvReflectPrvParser* p_parser, + uint32_t word_offset, + uint32_t word_index, + uint32_t word_count, + uint32_t* p_buf_size, + char* p_buf +) +{ + uint32_t limit = (word_offset + word_count); + assert(IsNotNull(p_parser)); + assert(IsNotNull(p_parser->spirv_code)); + assert(InRange(p_parser, limit)); + SpvReflectResult result = SPV_REFLECT_RESULT_ERROR_SPIRV_UNEXPECTED_EOF; + if (IsNotNull(p_parser) && IsNotNull(p_parser->spirv_code) && InRange(p_parser, limit)) { + const char* c_str = (const char*)(p_parser->spirv_code + word_offset + word_index); + uint32_t n = word_count * SPIRV_WORD_SIZE; + uint32_t length_with_terminator = 0; + for (uint32_t i = 0; i < n; ++i) { + char c = *(c_str + i); + if (c == 0) { + length_with_terminator = i + 1; + break; + } + } + + if (length_with_terminator > 0) { + result = SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + if (IsNotNull(p_buf_size) && IsNotNull(p_buf)) { + result = SPV_REFLECT_RESULT_ERROR_RANGE_EXCEEDED; + if (length_with_terminator <= *p_buf_size) { + memset(p_buf, 0, *p_buf_size); + memcpy(p_buf, c_str, length_with_terminator); + result = SPV_REFLECT_RESULT_SUCCESS; + } + } + else { + if (IsNotNull(p_buf_size)) { + *p_buf_size = length_with_terminator; + result = SPV_REFLECT_RESULT_SUCCESS; + } + } + } + } + return result; +} + +static SpvReflectDecorationFlags ApplyDecorations(const SpvReflectPrvDecorations* p_decoration_fields) +{ + SpvReflectDecorationFlags decorations = SPV_REFLECT_DECORATION_NONE; + if (p_decoration_fields->is_relaxed_precision) { + decorations |= SPV_REFLECT_DECORATION_RELAXED_PRECISION; + } + if (p_decoration_fields->is_block) { + decorations |= SPV_REFLECT_DECORATION_BLOCK; + } + if (p_decoration_fields->is_buffer_block) { + decorations |= SPV_REFLECT_DECORATION_BUFFER_BLOCK; + } + if (p_decoration_fields->is_row_major) { + decorations |= SPV_REFLECT_DECORATION_ROW_MAJOR; + } + if (p_decoration_fields->is_column_major) { + decorations |= SPV_REFLECT_DECORATION_COLUMN_MAJOR; + } + if (p_decoration_fields->is_built_in) { + decorations |= SPV_REFLECT_DECORATION_BUILT_IN; + } + if (p_decoration_fields->is_noperspective) { + decorations |= SPV_REFLECT_DECORATION_NOPERSPECTIVE; + } + if (p_decoration_fields->is_flat) { + decorations |= SPV_REFLECT_DECORATION_FLAT; + } + if (p_decoration_fields->is_non_writable) { + decorations |= SPV_REFLECT_DECORATION_NON_WRITABLE; + } + if (p_decoration_fields->is_non_readable) { + decorations |= SPV_REFLECT_DECORATION_NON_READABLE; + } + return decorations; +} + +static void ApplyNumericTraits(const SpvReflectTypeDescription* p_type, SpvReflectNumericTraits* p_numeric_traits) +{ + memcpy(p_numeric_traits, &p_type->traits.numeric, sizeof(p_type->traits.numeric)); +} + +static void ApplyArrayTraits(const SpvReflectTypeDescription* p_type, SpvReflectArrayTraits* p_array_traits) +{ + memcpy(p_array_traits, &p_type->traits.array, sizeof(p_type->traits.array)); +} + +static SpvReflectPrvNode* FindNode( + SpvReflectPrvParser* p_parser, + uint32_t result_id) +{ + SpvReflectPrvNode* p_node = NULL; + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_elem = &(p_parser->nodes[i]); + if (p_elem->result_id == result_id) { + p_node = p_elem; + break; + } + } + return p_node; +} + +static SpvReflectTypeDescription* FindType(SpvReflectShaderModule* p_module, uint32_t type_id) +{ + SpvReflectTypeDescription* p_type = NULL; + for (size_t i = 0; i < p_module->_internal->type_description_count; ++i) { + SpvReflectTypeDescription* p_elem = &(p_module->_internal->type_descriptions[i]); + if (p_elem->id == type_id) { + p_type = p_elem; + break; + } + } + return p_type; +} + +static SpvReflectResult CreateParser( + size_t size, + void* p_code, + SpvReflectPrvParser* p_parser) +{ + if (p_code == NULL) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + if (size < SPIRV_MINIMUM_FILE_SIZE) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_CODE_SIZE; + } + if ((size % 4) != 0) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_CODE_SIZE; + } + + p_parser->spirv_word_count = size / SPIRV_WORD_SIZE; + p_parser->spirv_code = (uint32_t*)p_code; + + if (p_parser->spirv_code[0] != SpvMagicNumber) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_MAGIC_NUMBER; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static void DestroyParser(SpvReflectPrvParser* p_parser) +{ + if (!IsNull(p_parser->nodes)) { + // Free nodes + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if (IsNotNull(p_node->member_names)) { + SafeFree(p_node->member_names); + } + if (IsNotNull(p_node->member_decorations)) { + SafeFree(p_node->member_decorations); + } + } + + // Free functions + for (size_t i = 0; i < p_parser->function_count; ++i) { + SafeFree(p_parser->functions[i].callees); + SafeFree(p_parser->functions[i].callee_ptrs); + SafeFree(p_parser->functions[i].accessed_ptrs); + } + + // Free access chains + for (uint32_t i = 0; i < p_parser->access_chain_count; ++i) { + SafeFree(p_parser->access_chains[i].indexes); + } + + SafeFree(p_parser->nodes); + SafeFree(p_parser->strings); + SafeFree(p_parser->source_embedded); + SafeFree(p_parser->functions); + SafeFree(p_parser->access_chains); + p_parser->node_count = 0; + } +} + +static SpvReflectResult ParseNodes(SpvReflectPrvParser* p_parser) +{ + assert(IsNotNull(p_parser)); + assert(IsNotNull(p_parser->spirv_code)); + + uint32_t* p_spirv = p_parser->spirv_code; + uint32_t spirv_word_index = SPIRV_STARTING_WORD_INDEX; + + // Count nodes + uint32_t node_count = 0; + while (spirv_word_index < p_parser->spirv_word_count) { + uint32_t word = p_spirv[spirv_word_index]; + SpvOp op = (SpvOp)(word & 0xFFFF); + uint32_t node_word_count = (word >> 16) & 0xFFFF; + if (node_word_count == 0) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_INSTRUCTION; + } + if (op == SpvOpAccessChain) { + ++(p_parser->access_chain_count); + } + spirv_word_index += node_word_count; + ++node_count; + } + + if (node_count == 0) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_UNEXPECTED_EOF; + } + + // Allocate nodes + p_parser->node_count = node_count; + p_parser->nodes = (SpvReflectPrvNode*)calloc(p_parser->node_count, sizeof(*(p_parser->nodes))); + if (IsNull(p_parser->nodes)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + // Mark all nodes with an invalid state + for (uint32_t i = 0; i < node_count; ++i) { + p_parser->nodes[i].op = (SpvOp)INVALID_VALUE; + p_parser->nodes[i].storage_class = (SpvStorageClass)INVALID_VALUE; + p_parser->nodes[i].decorations.set.value = (uint32_t)INVALID_VALUE; + p_parser->nodes[i].decorations.binding.value = (uint32_t)INVALID_VALUE; + p_parser->nodes[i].decorations.location.value = (uint32_t)INVALID_VALUE; + p_parser->nodes[i].decorations.offset.value = (uint32_t)INVALID_VALUE; + p_parser->nodes[i].decorations.uav_counter_buffer.value = (uint32_t)INVALID_VALUE; + p_parser->nodes[i].decorations.built_in = (SpvBuiltIn)INVALID_VALUE; + } + // Mark source file id node + p_parser->source_file_id = (uint32_t)INVALID_VALUE; + p_parser->source_embedded = NULL; + + // Function node + uint32_t function_node = (uint32_t)INVALID_VALUE; + + // Allocate access chain + if (p_parser->access_chain_count > 0) { + p_parser->access_chains = (SpvReflectPrvAccessChain*)calloc(p_parser->access_chain_count, sizeof(*(p_parser->access_chains))); + if (IsNull(p_parser->access_chains)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + + // Parse nodes + uint32_t node_index = 0; + uint32_t access_chain_index = 0; + spirv_word_index = SPIRV_STARTING_WORD_INDEX; + while (spirv_word_index < p_parser->spirv_word_count) { + uint32_t word = p_spirv[spirv_word_index]; + SpvOp op = (SpvOp)(word & 0xFFFF); + uint32_t node_word_count = (word >> 16) & 0xFFFF; + + SpvReflectPrvNode* p_node = &(p_parser->nodes[node_index]); + p_node->op = op; + p_node->word_offset = spirv_word_index; + p_node->word_count = node_word_count; + + switch (p_node->op) { + default: break; + + case SpvOpString: { + ++(p_parser->string_count); + } + break; + + case SpvOpSource: { + CHECKED_READU32_CAST(p_parser, p_node->word_offset + 1, SpvSourceLanguage, p_parser->source_language); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_parser->source_language_version); + if (p_node->word_count >= 4) { + CHECKED_READU32(p_parser, p_node->word_offset + 3, p_parser->source_file_id); + } + if (p_node->word_count >= 5) { + const char* p_source = (const char*)(p_parser->spirv_code + p_node->word_offset + 4); + + const size_t source_len = strlen(p_source); + char* p_source_temp = (char*)calloc(source_len + 1, sizeof(char*)); + + if (IsNull(p_source_temp)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + #ifdef _WIN32 + strcpy_s(p_source_temp, source_len + 1, p_source); + #else + strcpy(p_source_temp, p_source); + #endif + + p_parser->source_embedded = p_source_temp; + } + } + break; + + case SpvOpSourceContinued: { + const char* p_source = (const char*)(p_parser->spirv_code + p_node->word_offset + 1); + + const size_t source_len = strlen(p_source); + const size_t embedded_source_len = strlen(p_parser->source_embedded); + char* p_continued_source = (char*)calloc(source_len + embedded_source_len + 1, sizeof(char*)); + + if (IsNull(p_continued_source)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + #ifdef _WIN32 + strcpy_s(p_continued_source, embedded_source_len + 1, p_parser->source_embedded); + strcat_s(p_continued_source, source_len + 1, p_source); + #else + strcpy(p_continued_source, p_parser->source_embedded); + strcat(p_continued_source, p_source); + #endif + + SafeFree(p_parser->source_embedded); + p_parser->source_embedded = p_continued_source; + } + break; + + case SpvOpEntryPoint: { + ++(p_parser->entry_point_count); + } + break; + + case SpvOpName: + case SpvOpMemberName: + { + uint32_t member_offset = (p_node->op == SpvOpMemberName) ? 1 : 0; + uint32_t name_start = p_node->word_offset + member_offset + 2; + p_node->name = (const char*)(p_parser->spirv_code + name_start); + } + break; + + case SpvOpTypeStruct: + { + p_node->member_count = p_node->word_count - 2; + } // Fall through + case SpvOpTypeVoid: + case SpvOpTypeBool: + case SpvOpTypeInt: + case SpvOpTypeFloat: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + case SpvOpTypeSampler: + case SpvOpTypeOpaque: + case SpvOpTypeFunction: + case SpvOpTypeEvent: + case SpvOpTypeDeviceEvent: + case SpvOpTypeReserveId: + case SpvOpTypeQueue: + case SpvOpTypePipe: + case SpvOpTypeAccelerationStructureKHR: + case SpvOpTypeRayQueryKHR: + { + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->result_id); + p_node->is_type = true; + } + break; + + case SpvOpTypeImage: { + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->result_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->image_traits.sampled_type_id); + CHECKED_READU32(p_parser, p_node->word_offset + 3, p_node->image_traits.dim); + CHECKED_READU32(p_parser, p_node->word_offset + 4, p_node->image_traits.depth); + CHECKED_READU32(p_parser, p_node->word_offset + 5, p_node->image_traits.arrayed); + CHECKED_READU32(p_parser, p_node->word_offset + 6, p_node->image_traits.ms); + CHECKED_READU32(p_parser, p_node->word_offset + 7, p_node->image_traits.sampled); + CHECKED_READU32(p_parser, p_node->word_offset + 8, p_node->image_traits.image_format); + p_node->is_type = true; + } + break; + + case SpvOpTypeSampledImage: { + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->result_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->image_type_id); + p_node->is_type = true; + } + break; + + case SpvOpTypeArray: { + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->result_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->array_traits.element_type_id); + CHECKED_READU32(p_parser, p_node->word_offset + 3, p_node->array_traits.length_id); + p_node->is_type = true; + } + break; + + case SpvOpTypeRuntimeArray: { + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->result_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->array_traits.element_type_id); + p_node->is_type = true; + } + break; + + case SpvOpTypePointer: { + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->result_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->storage_class); + CHECKED_READU32(p_parser, p_node->word_offset + 3, p_node->type_id); + p_node->is_type = true; + } + break; + + case SpvOpTypeForwardPointer: + { + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->result_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->storage_class); + p_node->is_type = true; + } + break; + + case SpvOpConstantTrue: + case SpvOpConstantFalse: + case SpvOpConstant: + case SpvOpConstantComposite: + case SpvOpConstantSampler: + case SpvOpConstantNull: { + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->result_type_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->result_id); + } + break; + + case SpvOpSpecConstantTrue: + case SpvOpSpecConstantFalse: + case SpvOpSpecConstant: + case SpvOpSpecConstantComposite: + case SpvOpSpecConstantOp: { + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->result_type_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->result_id); + } + break; + + case SpvOpVariable: + { + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->type_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->result_id); + CHECKED_READU32(p_parser, p_node->word_offset + 3, p_node->storage_class); + } + break; + + case SpvOpLoad: + { + // Only load enough so OpDecorate can reference the node, skip the remaining operands. + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_node->result_type_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->result_id); + } + break; + + case SpvOpAccessChain: + { + SpvReflectPrvAccessChain* p_access_chain = &(p_parser->access_chains[access_chain_index]); + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_access_chain->result_type_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_access_chain->result_id); + CHECKED_READU32(p_parser, p_node->word_offset + 3, p_access_chain->base_id); + // + // SPIRV_ACCESS_CHAIN_INDEX_OFFSET (4) is the number of words up until the first index: + // [Node, Result Type Id, Result Id, Base Id, ] + // + p_access_chain->index_count = (node_word_count - SPIRV_ACCESS_CHAIN_INDEX_OFFSET); + if (p_access_chain->index_count > 0) { + p_access_chain->indexes = (uint32_t*)calloc(p_access_chain->index_count, sizeof(*(p_access_chain->indexes))); + if (IsNull( p_access_chain->indexes)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + // Parse any index values for access chain + for (uint32_t index_index = 0; index_index < p_access_chain->index_count; ++index_index) { + // Read index id + uint32_t index_id = 0; + CHECKED_READU32(p_parser, p_node->word_offset + SPIRV_ACCESS_CHAIN_INDEX_OFFSET + index_index, index_id); + // Find OpConstant node that contains index value + SpvReflectPrvNode* p_index_value_node = FindNode(p_parser, index_id); + if ((p_index_value_node != NULL) && (p_index_value_node->op == SpvOpConstant)) { + // Read index value + uint32_t index_value = UINT32_MAX; + CHECKED_READU32(p_parser, p_index_value_node->word_offset + 3, index_value); + assert(index_value != UINT32_MAX); + // Write index value to array + p_access_chain->indexes[index_index] = index_value; + } + } + } + ++access_chain_index; + } + break; + + case SpvOpFunction: + { + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_node->result_id); + // Count function definitions, not function declarations. To determine + // the difference, set an in-function variable, and then if an OpLabel + // is reached before the end of the function increment the function + // count. + function_node = node_index; + } + break; + + case SpvOpLabel: + { + if (function_node != (uint32_t)INVALID_VALUE) { + SpvReflectPrvNode* p_func_node = &(p_parser->nodes[function_node]); + CHECKED_READU32(p_parser, p_func_node->word_offset + 2, p_func_node->result_id); + ++(p_parser->function_count); + } + } // Fall through + + case SpvOpFunctionEnd: + { + function_node = (uint32_t)INVALID_VALUE; + } + break; + } + + if (p_node->is_type) { + ++(p_parser->type_count); + } + + spirv_word_index += node_word_count; + ++node_index; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseStrings(SpvReflectPrvParser* p_parser) +{ + assert(IsNotNull(p_parser)); + assert(IsNotNull(p_parser->spirv_code)); + assert(IsNotNull(p_parser->nodes)); + + // Early out + if (p_parser->string_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + if (IsNotNull(p_parser) && IsNotNull(p_parser->spirv_code) && IsNotNull(p_parser->nodes)) { + // Allocate string storage + p_parser->strings = (SpvReflectPrvString*)calloc(p_parser->string_count, sizeof(*(p_parser->strings))); + + uint32_t string_index = 0; + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if (p_node->op != SpvOpString) { + continue; + } + + // Paranoid check against string count + assert(string_index < p_parser->string_count); + if (string_index >= p_parser->string_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + // Result id + SpvReflectPrvString* p_string = &(p_parser->strings[string_index]); + CHECKED_READU32(p_parser, p_node->word_offset + 1, p_string->result_id); + + // String + uint32_t string_start = p_node->word_offset + 2; + p_string->string = (const char*)(p_parser->spirv_code + string_start); + + // Increment string index + ++string_index; + } + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseSource(SpvReflectPrvParser* p_parser, SpvReflectShaderModule* p_module) +{ + assert(IsNotNull(p_parser)); + assert(IsNotNull(p_parser->spirv_code)); + + if (IsNotNull(p_parser) && IsNotNull(p_parser->spirv_code)) { + // Source file + if (IsNotNull(p_parser->strings)) { + for (uint32_t i = 0; i < p_parser->string_count; ++i) { + SpvReflectPrvString* p_string = &(p_parser->strings[i]); + if (p_string->result_id == p_parser->source_file_id) { + p_module->source_file = p_string->string; + break; + } + } + } + + //Source code + if (IsNotNull(p_parser->source_embedded)) + { + const size_t source_len = strlen(p_parser->source_embedded); + char* p_source = (char*)calloc(source_len + 1, sizeof(char*)); + + if (IsNull(p_source)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + #ifdef _WIN32 + strcpy_s(p_source, source_len + 1, p_parser->source_embedded); + #else + strcpy(p_source, p_parser->source_embedded); + #endif + + p_module->source_source = p_source; + } + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseFunction( + SpvReflectPrvParser* p_parser, + SpvReflectPrvNode* p_func_node, + SpvReflectPrvFunction* p_func, + size_t first_label_index) +{ + p_func->id = p_func_node->result_id; + + p_func->callee_count = 0; + p_func->accessed_ptr_count = 0; + + for (size_t i = first_label_index; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if (p_node->op == SpvOpFunctionEnd) { + break; + } + switch (p_node->op) { + case SpvOpFunctionCall: { + ++(p_func->callee_count); + } + break; + case SpvOpLoad: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpPtrAccessChain: + case SpvOpArrayLength: + case SpvOpGenericPtrMemSemantics: + case SpvOpInBoundsPtrAccessChain: + case SpvOpStore: + case SpvOpImageTexelPointer: + { + ++(p_func->accessed_ptr_count); + } + break; + case SpvOpCopyMemory: + case SpvOpCopyMemorySized: + { + p_func->accessed_ptr_count += 2; + } + break; + default: break; + } + } + + if (p_func->callee_count > 0) { + p_func->callees = (uint32_t*)calloc(p_func->callee_count, + sizeof(*(p_func->callees))); + if (IsNull(p_func->callees)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + + if (p_func->accessed_ptr_count > 0) { + p_func->accessed_ptrs = (uint32_t*)calloc(p_func->accessed_ptr_count, + sizeof(*(p_func->accessed_ptrs))); + if (IsNull(p_func->accessed_ptrs)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + + p_func->callee_count = 0; + p_func->accessed_ptr_count = 0; + for (size_t i = first_label_index; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if (p_node->op == SpvOpFunctionEnd) { + break; + } + switch (p_node->op) { + case SpvOpFunctionCall: { + CHECKED_READU32(p_parser, p_node->word_offset + 3, + p_func->callees[p_func->callee_count]); + (++p_func->callee_count); + } + break; + case SpvOpLoad: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpPtrAccessChain: + case SpvOpArrayLength: + case SpvOpGenericPtrMemSemantics: + case SpvOpInBoundsPtrAccessChain: + case SpvOpImageTexelPointer: + { + CHECKED_READU32(p_parser, p_node->word_offset + 3, + p_func->accessed_ptrs[p_func->accessed_ptr_count]); + (++p_func->accessed_ptr_count); + } + break; + case SpvOpStore: + { + CHECKED_READU32(p_parser, p_node->word_offset + 2, + p_func->accessed_ptrs[p_func->accessed_ptr_count]); + (++p_func->accessed_ptr_count); + } + break; + case SpvOpCopyMemory: + case SpvOpCopyMemorySized: + { + CHECKED_READU32(p_parser, p_node->word_offset + 2, + p_func->accessed_ptrs[p_func->accessed_ptr_count]); + (++p_func->accessed_ptr_count); + CHECKED_READU32(p_parser, p_node->word_offset + 3, + p_func->accessed_ptrs[p_func->accessed_ptr_count]); + (++p_func->accessed_ptr_count); + } + break; + default: break; + } + } + + if (p_func->callee_count > 0) { + qsort(p_func->callees, p_func->callee_count, + sizeof(*(p_func->callees)), SortCompareUint32); + } + p_func->callee_count = (uint32_t)DedupSortedUint32(p_func->callees, + p_func->callee_count); + + if (p_func->accessed_ptr_count > 0) { + qsort(p_func->accessed_ptrs, p_func->accessed_ptr_count, + sizeof(*(p_func->accessed_ptrs)), SortCompareUint32); + } + p_func->accessed_ptr_count = (uint32_t)DedupSortedUint32(p_func->accessed_ptrs, + p_func->accessed_ptr_count); + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static int SortCompareFunctions( + const void* a, + const void* b) +{ + const SpvReflectPrvFunction* af = (const SpvReflectPrvFunction*)a; + const SpvReflectPrvFunction* bf = (const SpvReflectPrvFunction*)b; + return (int)af->id - (int)bf->id; +} + +static SpvReflectResult ParseFunctions(SpvReflectPrvParser* p_parser) +{ + assert(IsNotNull(p_parser)); + assert(IsNotNull(p_parser->spirv_code)); + assert(IsNotNull(p_parser->nodes)); + + if (IsNotNull(p_parser) && IsNotNull(p_parser->spirv_code) && IsNotNull(p_parser->nodes)) { + if (p_parser->function_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + p_parser->functions = (SpvReflectPrvFunction*)calloc(p_parser->function_count, + sizeof(*(p_parser->functions))); + if (IsNull(p_parser->functions)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + size_t function_index = 0; + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if (p_node->op != SpvOpFunction) { + continue; + } + + // Skip over function declarations that aren't definitions + bool func_definition = false; + // Intentionally reuse i to avoid iterating over these nodes more than + // once + for (; i < p_parser->node_count; ++i) { + if (p_parser->nodes[i].op == SpvOpLabel) { + func_definition = true; + break; + } + if (p_parser->nodes[i].op == SpvOpFunctionEnd) { + break; + } + } + if (!func_definition) { + continue; + } + + SpvReflectPrvFunction* p_function = &(p_parser->functions[function_index]); + + SpvReflectResult result = ParseFunction(p_parser, p_node, p_function, i); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + + ++function_index; + } + + qsort(p_parser->functions, p_parser->function_count, + sizeof(*(p_parser->functions)), SortCompareFunctions); + + // Once they're sorted, link the functions with pointers to improve graph + // traversal efficiency + for (size_t i = 0; i < p_parser->function_count; ++i) { + SpvReflectPrvFunction* p_func = &(p_parser->functions[i]); + if (p_func->callee_count == 0) { + continue; + } + p_func->callee_ptrs = (SpvReflectPrvFunction**)calloc(p_func->callee_count, + sizeof(*(p_func->callee_ptrs))); + for (size_t j = 0, k = 0; j < p_func->callee_count; ++j) { + while (p_parser->functions[k].id != p_func->callees[j]) { + ++k; + if (k >= p_parser->function_count) { + // Invalid called function ID somewhere + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + } + p_func->callee_ptrs[j] = &(p_parser->functions[k]); + } + } + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseMemberCounts(SpvReflectPrvParser* p_parser) +{ + assert(IsNotNull(p_parser)); + assert(IsNotNull(p_parser->spirv_code)); + assert(IsNotNull(p_parser->nodes)); + + if (IsNotNull(p_parser) && IsNotNull(p_parser->spirv_code) && IsNotNull(p_parser->nodes)) { + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if ((p_node->op != SpvOpMemberName) && (p_node->op != SpvOpMemberDecorate)) { + continue; + } + + uint32_t target_id = 0; + uint32_t member_index = (uint32_t)INVALID_VALUE; + CHECKED_READU32(p_parser, p_node->word_offset + 1, target_id); + CHECKED_READU32(p_parser, p_node->word_offset + 2, member_index); + SpvReflectPrvNode* p_target_node = FindNode(p_parser, target_id); + // Not all nodes get parsed, so FindNode returning NULL is expected. + if (IsNull(p_target_node)) { + continue; + } + + if (member_index == INVALID_VALUE) { + return SPV_REFLECT_RESULT_ERROR_RANGE_EXCEEDED; + } + + p_target_node->member_count = Max(p_target_node->member_count, member_index + 1); + } + + for (uint32_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if (p_node->member_count == 0) { + continue; + } + + p_node->member_names = (const char **)calloc(p_node->member_count, sizeof(*(p_node->member_names))); + if (IsNull(p_node->member_names)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + p_node->member_decorations = (SpvReflectPrvDecorations*)calloc(p_node->member_count, sizeof(*(p_node->member_decorations))); + if (IsNull(p_node->member_decorations)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + } + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseNames(SpvReflectPrvParser* p_parser) +{ + assert(IsNotNull(p_parser)); + assert(IsNotNull(p_parser->spirv_code)); + assert(IsNotNull(p_parser->nodes)); + + if (IsNotNull(p_parser) && IsNotNull(p_parser->spirv_code) && IsNotNull(p_parser->nodes)) { + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if ((p_node->op != SpvOpName) && (p_node->op != SpvOpMemberName)) { + continue; + } + + uint32_t target_id = 0; + CHECKED_READU32(p_parser, p_node->word_offset + 1, target_id); + SpvReflectPrvNode* p_target_node = FindNode(p_parser, target_id); + // Not all nodes get parsed, so FindNode returning NULL is expected. + if (IsNull(p_target_node)) { + continue; + } + + const char** pp_target_name = &(p_target_node->name); + if (p_node->op == SpvOpMemberName) { + uint32_t member_index = UINT32_MAX; + CHECKED_READU32(p_parser, p_node->word_offset + 2, member_index); + pp_target_name = &(p_target_node->member_names[member_index]); + } + + *pp_target_name = p_node->name; + } + } + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseDecorations(SpvReflectPrvParser* p_parser) +{ + for (uint32_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + + if (((uint32_t)p_node->op != (uint32_t)SpvOpDecorate) && + ((uint32_t)p_node->op != (uint32_t)SpvOpMemberDecorate) && + ((uint32_t)p_node->op != (uint32_t)SpvReflectOpDecorateId) && + ((uint32_t)p_node->op != (uint32_t)SpvReflectOpDecorateStringGOOGLE) && + ((uint32_t)p_node->op != (uint32_t)SpvReflectOpMemberDecorateStringGOOGLE)) + { + continue; + } + + // Need to adjust the read offset if this is a member decoration + uint32_t member_offset = 0; + if (p_node->op == SpvOpMemberDecorate) { + member_offset = 1; + } + + // Get decoration + uint32_t decoration = (uint32_t)INVALID_VALUE; + CHECKED_READU32(p_parser, p_node->word_offset + member_offset + 2, decoration); + + // Filter out the decoration that do not affect reflection, otherwise + // there will be random crashes because the nodes aren't found. + bool skip = false; + switch (decoration) { + default: { + skip = true; + } + break; + case SpvDecorationRelaxedPrecision: + case SpvDecorationBlock: + case SpvDecorationBufferBlock: + case SpvDecorationColMajor: + case SpvDecorationRowMajor: + case SpvDecorationArrayStride: + case SpvDecorationMatrixStride: + case SpvDecorationBuiltIn: + case SpvDecorationNoPerspective: + case SpvDecorationFlat: + case SpvDecorationNonWritable: + case SpvDecorationNonReadable: + case SpvDecorationLocation: + case SpvDecorationBinding: + case SpvDecorationDescriptorSet: + case SpvDecorationOffset: + case SpvDecorationInputAttachmentIndex: + case SpvReflectDecorationHlslCounterBufferGOOGLE: + case SpvReflectDecorationHlslSemanticGOOGLE: { + skip = false; + } + break; + } + if (skip) { + continue; + } + + // Find target target node + uint32_t target_id = 0; + CHECKED_READU32(p_parser, p_node->word_offset + 1, target_id); + SpvReflectPrvNode* p_target_node = FindNode(p_parser, target_id); + if (IsNull(p_target_node)) { + if ((p_node->op == (uint32_t)SpvOpDecorate) && (decoration == SpvDecorationRelaxedPrecision)) { + // Many OPs can be decorated that we don't care about. Ignore those. + // See https://github.com/KhronosGroup/SPIRV-Reflect/issues/134 + continue; + } + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + // Get decorations + SpvReflectPrvDecorations* p_target_decorations = &(p_target_node->decorations); + // Update pointer if this is a member member decoration + if (p_node->op == SpvOpMemberDecorate) { + uint32_t member_index = (uint32_t)INVALID_VALUE; + CHECKED_READU32(p_parser, p_node->word_offset + 2, member_index); + p_target_decorations = &(p_target_node->member_decorations[member_index]); + } + + switch (decoration) { + default: break; + + case SpvDecorationRelaxedPrecision: { + p_target_decorations->is_relaxed_precision = true; + } + break; + + case SpvDecorationBlock: { + p_target_decorations->is_block = true; + } + break; + + case SpvDecorationBufferBlock: { + p_target_decorations->is_buffer_block = true; + } + break; + + case SpvDecorationColMajor: { + p_target_decorations->is_column_major = true; + } + break; + + case SpvDecorationRowMajor: { + p_target_decorations->is_row_major = true; + } + break; + + case SpvDecorationArrayStride: { + uint32_t word_offset = p_node->word_offset + member_offset + 3; + CHECKED_READU32(p_parser, word_offset, p_target_decorations->array_stride); + } + break; + + case SpvDecorationMatrixStride: { + uint32_t word_offset = p_node->word_offset + member_offset + 3; + CHECKED_READU32(p_parser, word_offset, p_target_decorations->matrix_stride); + } + break; + + case SpvDecorationBuiltIn: { + p_target_decorations->is_built_in = true; + uint32_t word_offset = p_node->word_offset + member_offset + 3; + CHECKED_READU32_CAST(p_parser, word_offset, SpvBuiltIn, p_target_decorations->built_in); + } + break; + + case SpvDecorationNoPerspective: { + p_target_decorations->is_noperspective = true; + } + break; + + case SpvDecorationFlat: { + p_target_decorations->is_flat = true; + } + break; + + case SpvDecorationNonWritable: { + p_target_decorations->is_non_writable = true; + } + break; + + case SpvDecorationNonReadable: { + p_target_decorations->is_non_readable = true; + } + break; + + case SpvDecorationLocation: { + uint32_t word_offset = p_node->word_offset + member_offset + 3; + CHECKED_READU32(p_parser, word_offset, p_target_decorations->location.value); + p_target_decorations->location.word_offset = word_offset; + } + break; + + case SpvDecorationBinding: { + uint32_t word_offset = p_node->word_offset + member_offset+ 3; + CHECKED_READU32(p_parser, word_offset, p_target_decorations->binding.value); + p_target_decorations->binding.word_offset = word_offset; + } + break; + + case SpvDecorationDescriptorSet: { + uint32_t word_offset = p_node->word_offset + member_offset+ 3; + CHECKED_READU32(p_parser, word_offset, p_target_decorations->set.value); + p_target_decorations->set.word_offset = word_offset; + } + break; + + case SpvDecorationOffset: { + uint32_t word_offset = p_node->word_offset + member_offset+ 3; + CHECKED_READU32(p_parser, word_offset, p_target_decorations->offset.value); + p_target_decorations->offset.word_offset = word_offset; + } + break; + + case SpvDecorationInputAttachmentIndex: { + uint32_t word_offset = p_node->word_offset + member_offset+ 3; + CHECKED_READU32(p_parser, word_offset, p_target_decorations->input_attachment_index.value); + p_target_decorations->input_attachment_index.word_offset = word_offset; + } + break; + + case SpvReflectDecorationHlslCounterBufferGOOGLE: { + uint32_t word_offset = p_node->word_offset + member_offset+ 3; + CHECKED_READU32(p_parser, word_offset, p_target_decorations->uav_counter_buffer.value); + p_target_decorations->uav_counter_buffer.word_offset = word_offset; + } + break; + + case SpvReflectDecorationHlslSemanticGOOGLE: { + uint32_t word_offset = p_node->word_offset + member_offset + 3; + p_target_decorations->semantic.value = (const char*)(p_parser->spirv_code + word_offset); + p_target_decorations->semantic.word_offset = word_offset; + } + break; + } + } + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult EnumerateAllUniforms( + SpvReflectShaderModule* p_module, + size_t* p_uniform_count, + uint32_t** pp_uniforms +) +{ + *p_uniform_count = p_module->descriptor_binding_count; + if (*p_uniform_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + *pp_uniforms = (uint32_t*)calloc(*p_uniform_count, sizeof(**pp_uniforms)); + + if (IsNull(*pp_uniforms)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + for (size_t i = 0; i < *p_uniform_count; ++i) { + (*pp_uniforms)[i] = p_module->descriptor_bindings[i].spirv_id; + } + qsort(*pp_uniforms, *p_uniform_count, sizeof(**pp_uniforms), + SortCompareUint32); + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseType( + SpvReflectPrvParser* p_parser, + SpvReflectPrvNode* p_node, + SpvReflectPrvDecorations* p_struct_member_decorations, + SpvReflectShaderModule* p_module, + SpvReflectTypeDescription* p_type +) +{ + SpvReflectResult result = SPV_REFLECT_RESULT_SUCCESS; + + if (p_node->member_count > 0) { + p_type->member_count = p_node->member_count; + p_type->members = (SpvReflectTypeDescription*)calloc(p_type->member_count, sizeof(*(p_type->members))); + if (IsNotNull(p_type->members)) { + // Mark all members types with an invalid state + for (size_t i = 0; i < p_type->members->member_count; ++i) { + SpvReflectTypeDescription* p_member_type = &(p_type->members[i]); + p_member_type->id = (uint32_t)INVALID_VALUE; + p_member_type->op = (SpvOp)INVALID_VALUE; + p_member_type->storage_class = (SpvStorageClass)INVALID_VALUE; + } + } + else { + result = SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + + if (result == SPV_REFLECT_RESULT_SUCCESS) { + // Since the parse descends on type information, these will get overwritten + // if not guarded against assignment. Only assign if the id is invalid. + if (p_type->id == INVALID_VALUE) { + p_type->id = p_node->result_id; + p_type->op = p_node->op; + p_type->decoration_flags = 0; + } + // Top level types need to pick up decorations from all types below it. + // Issue and fix here: https://github.com/chaoticbob/SPIRV-Reflect/issues/64 + p_type->decoration_flags = ApplyDecorations(&p_node->decorations); + + switch (p_node->op) { + default: break; + case SpvOpTypeVoid: + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_VOID; + break; + + case SpvOpTypeBool: + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_BOOL; + break; + + case SpvOpTypeInt: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_INT; + IF_READU32(result, p_parser, p_node->word_offset + 2, p_type->traits.numeric.scalar.width); + IF_READU32(result, p_parser, p_node->word_offset + 3, p_type->traits.numeric.scalar.signedness); + } + break; + + case SpvOpTypeFloat: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_FLOAT; + IF_READU32(result, p_parser, p_node->word_offset + 2, p_type->traits.numeric.scalar.width); + } + break; + + case SpvOpTypeVector: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_VECTOR; + uint32_t component_type_id = (uint32_t)INVALID_VALUE; + IF_READU32(result, p_parser, p_node->word_offset + 2, component_type_id); + IF_READU32(result, p_parser, p_node->word_offset + 3, p_type->traits.numeric.vector.component_count); + // Parse component type + SpvReflectPrvNode* p_next_node = FindNode(p_parser, component_type_id); + if (IsNotNull(p_next_node)) { + result = ParseType(p_parser, p_next_node, NULL, p_module, p_type); + } + else { + result = SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + SPV_REFLECT_ASSERT(false); + } + } + break; + + case SpvOpTypeMatrix: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_MATRIX; + uint32_t column_type_id = (uint32_t)INVALID_VALUE; + IF_READU32(result, p_parser, p_node->word_offset + 2, column_type_id); + IF_READU32(result, p_parser, p_node->word_offset + 3, p_type->traits.numeric.matrix.column_count); + SpvReflectPrvNode* p_next_node = FindNode(p_parser, column_type_id); + if (IsNotNull(p_next_node)) { + result = ParseType(p_parser, p_next_node, NULL, p_module, p_type); + } + else { + result = SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + SPV_REFLECT_ASSERT(false); + } + p_type->traits.numeric.matrix.row_count = p_type->traits.numeric.vector.component_count; + p_type->traits.numeric.matrix.stride = p_node->decorations.matrix_stride; + // NOTE: Matrix stride is decorated using OpMemberDecoreate - not OpDecoreate. + if (IsNotNull(p_struct_member_decorations)) { + p_type->traits.numeric.matrix.stride = p_struct_member_decorations->matrix_stride; + } + } + break; + + case SpvOpTypeImage: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_EXTERNAL_IMAGE; + uint32_t sampled_type_id = (uint32_t)INVALID_VALUE; + IF_READU32(result, p_parser, p_node->word_offset + 2, sampled_type_id); + SpvReflectPrvNode* p_next_node = FindNode(p_parser, sampled_type_id); + if (IsNotNull(p_next_node)) { + result = ParseType(p_parser, p_next_node, NULL, p_module, p_type); + } + else { + result = SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + IF_READU32_CAST(result, p_parser, p_node->word_offset + 3, SpvDim, p_type->traits.image.dim); + IF_READU32(result, p_parser, p_node->word_offset + 4, p_type->traits.image.depth); + IF_READU32(result, p_parser, p_node->word_offset + 5, p_type->traits.image.arrayed); + IF_READU32(result, p_parser, p_node->word_offset + 6, p_type->traits.image.ms); + IF_READU32(result, p_parser, p_node->word_offset + 7, p_type->traits.image.sampled); + IF_READU32_CAST(result, p_parser, p_node->word_offset + 8, SpvImageFormat, p_type->traits.image.image_format); + } + break; + + case SpvOpTypeSampler: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_EXTERNAL_SAMPLER; + } + break; + + case SpvOpTypeSampledImage: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_EXTERNAL_SAMPLED_IMAGE; + uint32_t image_type_id = (uint32_t)INVALID_VALUE; + IF_READU32(result, p_parser, p_node->word_offset + 2, image_type_id); + SpvReflectPrvNode* p_next_node = FindNode(p_parser, image_type_id); + if (IsNotNull(p_next_node)) { + result = ParseType(p_parser, p_next_node, NULL, p_module, p_type); + } + else { + result = SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + SPV_REFLECT_ASSERT(false); + } + } + break; + + case SpvOpTypeArray: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_ARRAY; + if (result == SPV_REFLECT_RESULT_SUCCESS) { + uint32_t element_type_id = (uint32_t)INVALID_VALUE; + uint32_t length_id = (uint32_t)INVALID_VALUE; + IF_READU32(result, p_parser, p_node->word_offset + 2, element_type_id); + IF_READU32(result, p_parser, p_node->word_offset + 3, length_id); + // NOTE: Array stride is decorated using OpDecorate instead of + // OpMemberDecorate, even if the array is apart of a struct. + p_type->traits.array.stride = p_node->decorations.array_stride; + // Get length for current dimension + SpvReflectPrvNode* p_length_node = FindNode(p_parser, length_id); + if (IsNotNull(p_length_node)) { + uint32_t dim_index = p_type->traits.array.dims_count; + if (p_length_node->op == SpvOpSpecConstant || + p_length_node->op == SpvOpSpecConstantOp) { + p_type->traits.array.dims[dim_index] = 0xFFFFFFFF; + p_type->traits.array.spec_constant_op_ids[dim_index] = length_id; + p_type->traits.array.dims_count += 1; + } else { + uint32_t length = 0; + IF_READU32(result, p_parser, p_length_node->word_offset + 3, length); + if (result == SPV_REFLECT_RESULT_SUCCESS) { + // Write the array dim and increment the count and offset + p_type->traits.array.dims[dim_index] = length; + p_type->traits.array.spec_constant_op_ids[dim_index] = 0xFFFFFFFF; + p_type->traits.array.dims_count += 1; + } else { + result = SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + SPV_REFLECT_ASSERT(false); + } + } + // Parse next dimension or element type + SpvReflectPrvNode* p_next_node = FindNode(p_parser, element_type_id); + if (IsNotNull(p_next_node)) { + result = ParseType(p_parser, p_next_node, NULL, p_module, p_type); + } + } + else { + result = SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + SPV_REFLECT_ASSERT(false); + } + } + } + break; + + case SpvOpTypeRuntimeArray: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_ARRAY; + uint32_t element_type_id = (uint32_t)INVALID_VALUE; + IF_READU32(result, p_parser, p_node->word_offset + 2, element_type_id); + p_type->traits.array.stride = p_node->decorations.array_stride; + uint32_t dim_index = p_type->traits.array.dims_count; + p_type->traits.array.dims[dim_index] = 0; + p_type->traits.array.spec_constant_op_ids[dim_index] = 0; + p_type->traits.array.dims_count += 1; + // Parse next dimension or element type + SpvReflectPrvNode* p_next_node = FindNode(p_parser, element_type_id); + if (IsNotNull(p_next_node)) { + result = ParseType(p_parser, p_next_node, NULL, p_module, p_type); + } + else { + result = SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + SPV_REFLECT_ASSERT(false); + } + } + break; + + case SpvOpTypeStruct: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_STRUCT; + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_EXTERNAL_BLOCK; + uint32_t word_index = 2; + uint32_t member_index = 0; + for (; word_index < p_node->word_count; ++word_index, ++member_index) { + uint32_t member_id = (uint32_t)INVALID_VALUE; + IF_READU32(result, p_parser, p_node->word_offset + word_index, member_id); + // Find member node + SpvReflectPrvNode* p_member_node = FindNode(p_parser, member_id); + if (IsNull(p_member_node)) { + result = SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + SPV_REFLECT_ASSERT(false); + break; + } + + // Member decorations + SpvReflectPrvDecorations* p_member_decorations = &p_node->member_decorations[member_index]; + + assert(member_index < p_type->member_count); + // Parse member type + SpvReflectTypeDescription* p_member_type = &(p_type->members[member_index]); + p_member_type->id = member_id; + p_member_type->op = p_member_node->op; + result = ParseType(p_parser, p_member_node, p_member_decorations, p_module, p_member_type); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + break; + } + // This looks wrong + //p_member_type->type_name = p_member_node->name; + p_member_type->struct_member_name = p_node->member_names[member_index]; + } + } + break; + + case SpvOpTypeOpaque: break; + + case SpvOpTypePointer: { + IF_READU32_CAST(result, p_parser, p_node->word_offset + 2, SpvStorageClass, p_type->storage_class); + uint32_t type_id = (uint32_t)INVALID_VALUE; + IF_READU32(result, p_parser, p_node->word_offset + 3, type_id); + // Parse type + SpvReflectPrvNode* p_next_node = FindNode(p_parser, type_id); + if (IsNotNull(p_next_node)) { + result = ParseType(p_parser, p_next_node, NULL, p_module, p_type); + } + else { + result = SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + SPV_REFLECT_ASSERT(false); + } + } + break; + + case SpvOpTypeAccelerationStructureKHR: { + p_type->type_flags |= SPV_REFLECT_TYPE_FLAG_EXTERNAL_ACCELERATION_STRUCTURE; + } + break; + } + + if (result == SPV_REFLECT_RESULT_SUCCESS) { + // Names get assigned on the way down. Guard against names + // get overwritten on the way up. + if (IsNull(p_type->type_name)) { + p_type->type_name = p_node->name; + } + } + } + + return result; +} + +static SpvReflectResult ParseTypes( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module) +{ + if (p_parser->type_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + p_module->_internal->type_description_count = p_parser->type_count; + p_module->_internal->type_descriptions = (SpvReflectTypeDescription*)calloc(p_module->_internal->type_description_count, + sizeof(*(p_module->_internal->type_descriptions))); + if (IsNull(p_module->_internal->type_descriptions)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + // Mark all types with an invalid state + for (size_t i = 0; i < p_module->_internal->type_description_count; ++i) { + SpvReflectTypeDescription* p_type = &(p_module->_internal->type_descriptions[i]); + p_type->id = (uint32_t)INVALID_VALUE; + p_type->op = (SpvOp)INVALID_VALUE; + p_type->storage_class = (SpvStorageClass)INVALID_VALUE; + } + + size_t type_index = 0; + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if (! p_node->is_type) { + continue; + } + + SpvReflectTypeDescription* p_type = &(p_module->_internal->type_descriptions[type_index]); + SpvReflectResult result = ParseType(p_parser, p_node, NULL, p_module, p_type); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + ++type_index; + } + return SPV_REFLECT_RESULT_SUCCESS; +} + +static int SortCompareDescriptorBinding(const void* a, const void* b) +{ + const SpvReflectDescriptorBinding* p_elem_a = (const SpvReflectDescriptorBinding*)a; + const SpvReflectDescriptorBinding* p_elem_b = (const SpvReflectDescriptorBinding*)b; + int value = (int)(p_elem_a->binding) - (int)(p_elem_b->binding); + if (value == 0) { + // use spirv-id as a tiebreaker to ensure a stable ordering, as they're guaranteed + // unique. + assert(p_elem_a->spirv_id != p_elem_b->spirv_id); + value = (int)(p_elem_a->spirv_id) - (int)(p_elem_b->spirv_id); + } + return value; +} + +static SpvReflectResult ParseDescriptorBindings( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module) +{ + p_module->descriptor_binding_count = 0; + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if ((p_node->op != SpvOpVariable) || + ((p_node->storage_class != SpvStorageClassUniform) && + (p_node->storage_class != SpvStorageClassStorageBuffer) && + (p_node->storage_class != SpvStorageClassUniformConstant))) + { + continue; + } + if ((p_node->decorations.set.value == INVALID_VALUE) || (p_node->decorations.binding.value == INVALID_VALUE)) { + continue; + } + + p_module->descriptor_binding_count += 1; + } + + if (p_module->descriptor_binding_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + p_module->descriptor_bindings = (SpvReflectDescriptorBinding*)calloc(p_module->descriptor_binding_count, sizeof(*(p_module->descriptor_bindings))); + if (IsNull(p_module->descriptor_bindings)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + // Mark all types with an invalid state + for (uint32_t descriptor_index = 0; descriptor_index < p_module->descriptor_binding_count; ++descriptor_index) { + SpvReflectDescriptorBinding* p_descriptor = &(p_module->descriptor_bindings[descriptor_index]); + p_descriptor->binding = (uint32_t)INVALID_VALUE; + p_descriptor->input_attachment_index = (uint32_t)INVALID_VALUE; + p_descriptor->set = (uint32_t)INVALID_VALUE; + p_descriptor->descriptor_type = (SpvReflectDescriptorType)INVALID_VALUE; + p_descriptor->uav_counter_id = (uint32_t)INVALID_VALUE; + } + + size_t descriptor_index = 0; + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if ((p_node->op != SpvOpVariable) || + ((p_node->storage_class != SpvStorageClassUniform) && + (p_node->storage_class != SpvStorageClassStorageBuffer) && + (p_node->storage_class != SpvStorageClassUniformConstant))) + { + continue; + } + if ((p_node->decorations.set.value == INVALID_VALUE) || (p_node->decorations.binding.value == INVALID_VALUE)) { + continue; + } + + SpvReflectTypeDescription* p_type = FindType(p_module, p_node->type_id); + if (IsNull(p_type)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + // If the type is a pointer, resolve it. We need to retain the storage class + // from the pointer so that we can use it to deduce deescriptor types. + SpvStorageClass pointer_storage_class = SpvStorageClassMax; + if (p_type->op == SpvOpTypePointer) { + pointer_storage_class = p_type->storage_class; + // Find the type's node + SpvReflectPrvNode* p_type_node = FindNode(p_parser, p_type->id); + if (IsNull(p_type_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + // Should be the resolved type + p_type = FindType(p_module, p_type_node->type_id); + if (IsNull(p_type)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + } + + SpvReflectDescriptorBinding* p_descriptor = &p_module->descriptor_bindings[descriptor_index]; + p_descriptor->spirv_id = p_node->result_id; + p_descriptor->name = p_node->name; + p_descriptor->binding = p_node->decorations.binding.value; + p_descriptor->input_attachment_index = p_node->decorations.input_attachment_index.value; + p_descriptor->set = p_node->decorations.set.value; + p_descriptor->count = 1; + p_descriptor->uav_counter_id = p_node->decorations.uav_counter_buffer.value; + p_descriptor->type_description = p_type; + p_descriptor->decoration_flags = ApplyDecorations(&p_node->decorations); + + // If this is in the StorageBuffer storage class, it's for sure a storage + // buffer descriptor. We need to handle this case earlier because in SPIR-V + // there are two ways to indicate a storage buffer: + // 1) Uniform storage class + BufferBlock decoration, or + // 2) StorageBuffer storage class + Buffer decoration. + // The 1) way is deprecated since SPIR-V v1.3. But the Buffer decoration is + // also used together with Uniform storage class to mean uniform buffer.. + // We'll handle the pre-v1.3 cases in ParseDescriptorType(). + if (pointer_storage_class == SpvStorageClassStorageBuffer) { + p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER; + } + + // Copy image traits + if ((p_type->type_flags & SPV_REFLECT_TYPE_FLAG_EXTERNAL_MASK) == SPV_REFLECT_TYPE_FLAG_EXTERNAL_IMAGE) { + memcpy(&p_descriptor->image, &p_type->traits.image, sizeof(p_descriptor->image)); + } + + // This is a workaround for: https://github.com/KhronosGroup/glslang/issues/1096 + { + const uint32_t resource_mask = SPV_REFLECT_TYPE_FLAG_EXTERNAL_SAMPLED_IMAGE | SPV_REFLECT_TYPE_FLAG_EXTERNAL_IMAGE; + if ((p_type->type_flags & resource_mask) == resource_mask) { + memcpy(&p_descriptor->image, &p_type->traits.image, sizeof(p_descriptor->image)); + } + } + + // Copy array traits + if (p_type->traits.array.dims_count > 0) { + p_descriptor->array.dims_count = p_type->traits.array.dims_count; + for (uint32_t dim_index = 0; dim_index < p_type->traits.array.dims_count; ++dim_index) { + uint32_t dim_value = p_type->traits.array.dims[dim_index]; + p_descriptor->array.dims[dim_index] = dim_value; + p_descriptor->count *= dim_value; + } + } + + // Count + + + p_descriptor->word_offset.binding = p_node->decorations.binding.word_offset; + p_descriptor->word_offset.set = p_node->decorations.set.word_offset; + + ++descriptor_index; + } + + if (p_module->descriptor_binding_count > 0) { + qsort(p_module->descriptor_bindings, + p_module->descriptor_binding_count, + sizeof(*(p_module->descriptor_bindings)), + SortCompareDescriptorBinding); + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseDescriptorType(SpvReflectShaderModule* p_module) +{ + if (p_module->descriptor_binding_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + for (uint32_t descriptor_index = 0; descriptor_index < p_module->descriptor_binding_count; ++descriptor_index) { + SpvReflectDescriptorBinding* p_descriptor = &(p_module->descriptor_bindings[descriptor_index]); + SpvReflectTypeDescription* p_type = p_descriptor->type_description; + + if ((int)p_descriptor->descriptor_type == (int)INVALID_VALUE) { + switch (p_type->type_flags & SPV_REFLECT_TYPE_FLAG_EXTERNAL_MASK) { + default: assert(false && "unknown type flag"); break; + + case SPV_REFLECT_TYPE_FLAG_EXTERNAL_IMAGE: { + if (p_descriptor->image.dim == SpvDimBuffer) { + switch (p_descriptor->image.sampled) { + default: assert(false && "unknown texel buffer sampled value"); break; + case IMAGE_SAMPLED: p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; break; + case IMAGE_STORAGE: p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER; break; + } + } + else if(p_descriptor->image.dim == SpvDimSubpassData) { + p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; + } + else { + switch (p_descriptor->image.sampled) { + default: assert(false && "unknown image sampled value"); break; + case IMAGE_SAMPLED: p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE; break; + case IMAGE_STORAGE: p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_IMAGE; break; + } + } + } + break; + + case SPV_REFLECT_TYPE_FLAG_EXTERNAL_SAMPLER: { + p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLER; + } + break; + + case (SPV_REFLECT_TYPE_FLAG_EXTERNAL_SAMPLED_IMAGE | SPV_REFLECT_TYPE_FLAG_EXTERNAL_IMAGE): { + // This is a workaround for: https://github.com/KhronosGroup/glslang/issues/1096 + if (p_descriptor->image.dim == SpvDimBuffer) { + switch (p_descriptor->image.sampled) { + default: assert(false && "unknown texel buffer sampled value"); break; + case IMAGE_SAMPLED: p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; break; + case IMAGE_STORAGE: p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER; break; + } + } + else { + p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + } + } + break; + + case SPV_REFLECT_TYPE_FLAG_EXTERNAL_BLOCK: { + if (p_type->decoration_flags & SPV_REFLECT_DECORATION_BLOCK) { + p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + } + else if (p_type->decoration_flags & SPV_REFLECT_DECORATION_BUFFER_BLOCK) { + p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER; + } + else { + assert(false && "unknown struct"); + } + } + break; + + case SPV_REFLECT_TYPE_FLAG_EXTERNAL_ACCELERATION_STRUCTURE: { + p_descriptor->descriptor_type = SPV_REFLECT_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; + } + break; + } + } + + switch (p_descriptor->descriptor_type) { + case SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLER : p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_SAMPLER; break; + case SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER : p_descriptor->resource_type = (SpvReflectResourceType)(SPV_REFLECT_RESOURCE_FLAG_SAMPLER | SPV_REFLECT_RESOURCE_FLAG_SRV); break; + case SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE : p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_SRV; break; + case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_IMAGE : p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_UAV; break; + case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER : p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_SRV; break; + case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER : p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_UAV; break; + case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER : p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_CBV; break; + case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC : p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_CBV; break; + case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER : p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_UAV; break; + case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC : p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_UAV; break; + case SPV_REFLECT_DESCRIPTOR_TYPE_INPUT_ATTACHMENT : break; + case SPV_REFLECT_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR : p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_SRV; break; + } + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseUAVCounterBindings(SpvReflectShaderModule* p_module) +{ + char name[MAX_NODE_NAME_LENGTH]; + const char* k_count_tag = "@count"; + + for (uint32_t descriptor_index = 0; descriptor_index < p_module->descriptor_binding_count; ++descriptor_index) { + SpvReflectDescriptorBinding* p_descriptor = &(p_module->descriptor_bindings[descriptor_index]); + + if (p_descriptor->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) { + continue; + } + + SpvReflectDescriptorBinding* p_counter_descriptor = NULL; + // Use UAV counter buffer id if present... + if (p_descriptor->uav_counter_id != UINT32_MAX) { + for (uint32_t counter_descriptor_index = 0; counter_descriptor_index < p_module->descriptor_binding_count; ++counter_descriptor_index) { + SpvReflectDescriptorBinding* p_test_counter_descriptor = &(p_module->descriptor_bindings[counter_descriptor_index]); + if (p_test_counter_descriptor->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) { + continue; + } + if (p_descriptor->uav_counter_id == p_test_counter_descriptor->spirv_id) { + p_counter_descriptor = p_test_counter_descriptor; + break; + } + } + } + // ...otherwise use old @count convention. + else { + const size_t descriptor_name_length = p_descriptor->name? strlen(p_descriptor->name): 0; + + memset(name, 0, MAX_NODE_NAME_LENGTH); + memcpy(name, p_descriptor->name, descriptor_name_length); +#if defined(_WIN32) + strcat_s(name, MAX_NODE_NAME_LENGTH, k_count_tag); +#else + strcat(name, k_count_tag); +#endif + + for (uint32_t counter_descriptor_index = 0; counter_descriptor_index < p_module->descriptor_binding_count; ++counter_descriptor_index) { + SpvReflectDescriptorBinding* p_test_counter_descriptor = &(p_module->descriptor_bindings[counter_descriptor_index]); + if (p_test_counter_descriptor->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) { + continue; + } + if (p_test_counter_descriptor->name && strcmp(name, p_test_counter_descriptor->name) == 0) { + p_counter_descriptor = p_test_counter_descriptor; + break; + } + } + } + + if (p_counter_descriptor != NULL) { + p_descriptor->uav_counter_binding = p_counter_descriptor; + } + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseDescriptorBlockVariable( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module, + SpvReflectTypeDescription* p_type, + SpvReflectBlockVariable* p_var +) +{ + bool has_non_writable = false; + + if (IsNotNull(p_type->members) && (p_type->member_count > 0)) { + p_var->member_count = p_type->member_count; + p_var->members = (SpvReflectBlockVariable*)calloc(p_var->member_count, sizeof(*p_var->members)); + if (IsNull(p_var->members)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + SpvReflectPrvNode* p_type_node = FindNode(p_parser, p_type->id); + if (IsNull(p_type_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + // Resolve to element type if current type is array or run time array + while (p_type_node->op == SpvOpTypeArray || p_type_node->op == SpvOpTypeRuntimeArray) { + if (p_type_node->op == SpvOpTypeArray) { + p_type_node = FindNode(p_parser, p_type_node->array_traits.element_type_id); + } + else { + // Element type description + SpvReflectTypeDescription* p_type_temp = FindType(p_module, p_type_node->array_traits.element_type_id); + if (IsNull(p_type_temp)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + // Element type node + p_type_node = FindNode(p_parser, p_type_temp->id); + } + if (IsNull(p_type_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + } + + // Parse members + for (uint32_t member_index = 0; member_index < p_type->member_count; ++member_index) { + SpvReflectTypeDescription* p_member_type = &p_type->members[member_index]; + SpvReflectBlockVariable* p_member_var = &p_var->members[member_index]; + bool is_struct = (p_member_type->type_flags & SPV_REFLECT_TYPE_FLAG_STRUCT) == SPV_REFLECT_TYPE_FLAG_STRUCT; + if (is_struct) { + SpvReflectResult result = ParseDescriptorBlockVariable(p_parser, p_module, p_member_type, p_member_var); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + } + + p_member_var->name = p_type_node->member_names[member_index]; + p_member_var->offset = p_type_node->member_decorations[member_index].offset.value; + p_member_var->decoration_flags = ApplyDecorations(&p_type_node->member_decorations[member_index]); + p_member_var->flags |= SPV_REFLECT_VARIABLE_FLAGS_UNUSED; + if (!has_non_writable && (p_member_var->decoration_flags & SPV_REFLECT_DECORATION_NON_WRITABLE)) { + has_non_writable = true; + } + ApplyNumericTraits(p_member_type, &p_member_var->numeric); + if (p_member_type->op == SpvOpTypeArray) { + ApplyArrayTraits(p_member_type, &p_member_var->array); + } + + p_member_var->type_description = p_member_type; + } + } + + p_var->name = p_type->type_name; + p_var->type_description = p_type; + if (has_non_writable) { + p_var->decoration_flags |= SPV_REFLECT_DECORATION_NON_WRITABLE; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseDescriptorBlockVariableSizes( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module, + bool is_parent_root, + bool is_parent_aos, + bool is_parent_rta, + SpvReflectBlockVariable* p_var +) +{ + if (p_var->member_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + // Absolute offsets + for (uint32_t member_index = 0; member_index < p_var->member_count; ++member_index) { + SpvReflectBlockVariable* p_member_var = &p_var->members[member_index]; + if (is_parent_root) { + p_member_var->absolute_offset = p_member_var->offset; + } + else { + p_member_var->absolute_offset = is_parent_aos ? 0 : p_member_var->offset + p_var->absolute_offset; + } + } + + // Size + for (uint32_t member_index = 0; member_index < p_var->member_count; ++member_index) { + SpvReflectBlockVariable* p_member_var = &p_var->members[member_index]; + SpvReflectTypeDescription* p_member_type = p_member_var->type_description; + + switch (p_member_type->op) { + case SpvOpTypeBool: { + p_member_var->size = SPIRV_WORD_SIZE; + } + break; + + case SpvOpTypeInt: + case SpvOpTypeFloat: { + p_member_var->size = p_member_type->traits.numeric.scalar.width / SPIRV_BYTE_WIDTH; + } + break; + + case SpvOpTypeVector: { + uint32_t size = p_member_type->traits.numeric.vector.component_count * + (p_member_type->traits.numeric.scalar.width / SPIRV_BYTE_WIDTH); + p_member_var->size = size; + } + break; + + case SpvOpTypeMatrix: { + if (p_member_var->decoration_flags & SPV_REFLECT_DECORATION_COLUMN_MAJOR) { + p_member_var->size = p_member_var->numeric.matrix.column_count * p_member_var->numeric.matrix.stride; + } + else if (p_member_var->decoration_flags & SPV_REFLECT_DECORATION_ROW_MAJOR) { + p_member_var->size = p_member_var->numeric.matrix.row_count * p_member_var->numeric.matrix.stride; + } + } + break; + + case SpvOpTypeArray: { + // If array of structs, parse members first... + bool is_struct = (p_member_type->type_flags & SPV_REFLECT_TYPE_FLAG_STRUCT) == SPV_REFLECT_TYPE_FLAG_STRUCT; + if (is_struct) { + SpvReflectResult result = ParseDescriptorBlockVariableSizes(p_parser, p_module, false, true, is_parent_rta, p_member_var); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + } + // ...then array + uint32_t element_count = (p_member_var->array.dims_count > 0 ? 1 : 0); + for (uint32_t i = 0; i < p_member_var->array.dims_count; ++i) { + element_count *= p_member_var->array.dims[i]; + } + p_member_var->size = element_count * p_member_var->array.stride; + } + break; + + case SpvOpTypeRuntimeArray: { + bool is_struct = (p_member_type->type_flags & SPV_REFLECT_TYPE_FLAG_STRUCT) == SPV_REFLECT_TYPE_FLAG_STRUCT; + if (is_struct) { + SpvReflectResult result = ParseDescriptorBlockVariableSizes(p_parser, p_module, false, true, true, p_member_var); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + } + } + break; + + case SpvOpTypeStruct: { + SpvReflectResult result = ParseDescriptorBlockVariableSizes(p_parser, p_module, false, is_parent_aos, is_parent_rta, p_member_var); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + } + break; + + default: + break; + } + } + + // Parse padded size using offset difference for all member except for the last entry... + for (uint32_t member_index = 0; member_index < (p_var->member_count - 1); ++member_index) { + SpvReflectBlockVariable* p_member_var = &p_var->members[member_index]; + SpvReflectBlockVariable* p_next_member_var = &p_var->members[member_index + 1]; + p_member_var->padded_size = p_next_member_var->offset - p_member_var->offset; + if (p_member_var->size > p_member_var->padded_size) { + p_member_var->size = p_member_var->padded_size; + } + if (is_parent_rta) { + p_member_var->padded_size = p_member_var->size; + } + } + // ...last entry just gets rounded up to near multiple of SPIRV_DATA_ALIGNMENT, which is 16 and + // subtract the offset. + if (p_var->member_count > 0) { + SpvReflectBlockVariable* p_member_var = &p_var->members[p_var->member_count - 1]; + p_member_var->padded_size = RoundUp(p_member_var->offset + p_member_var->size, SPIRV_DATA_ALIGNMENT) - p_member_var->offset; + if (p_member_var->size > p_member_var->padded_size) { + p_member_var->size = p_member_var->padded_size; + } + if (is_parent_rta) { + p_member_var->padded_size = p_member_var->size; + } + } + + // @TODO validate this with assertion + p_var->size = p_var->members[p_var->member_count - 1].offset + + p_var->members[p_var->member_count - 1].padded_size; + p_var->padded_size = p_var->size; + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static void MarkSelfAndAllMemberVarsAsUsed(SpvReflectBlockVariable* p_var) +{ + // Clear the current variable's USED flag + p_var->flags &= ~SPV_REFLECT_VARIABLE_FLAGS_UNUSED; + + SpvOp op_type = p_var->type_description->op; + switch (op_type) { + default: break; + + case SpvOpTypeArray: { + } + break; + + case SpvOpTypeStruct: { + for (uint32_t i = 0; i < p_var->member_count; ++i) { + SpvReflectBlockVariable* p_member_var = &p_var->members[i]; + MarkSelfAndAllMemberVarsAsUsed(p_member_var); + } + } + break; + } +} + +static SpvReflectResult ParseDescriptorBlockVariableUsage( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module, + SpvReflectPrvAccessChain* p_access_chain, + uint32_t index_index, + SpvOp override_op_type, + SpvReflectBlockVariable* p_var +) +{ + (void)p_parser; + (void)p_access_chain; + (void)p_var; + + // Clear the current variable's UNUSED flag + p_var->flags &= ~SPV_REFLECT_VARIABLE_FLAGS_UNUSED; + + // Parsing arrays requires overriding the op type for + // for the lowest dim's element type. + SpvOp op_type = p_var->type_description->op; + if (override_op_type != (SpvOp)INVALID_VALUE) { + op_type = override_op_type; + } + + switch (op_type) { + default: break; + + case SpvOpTypeArray: { + // Parse through array's type hierarchy to find the actual/non-array element type + SpvReflectTypeDescription* p_type = p_var->type_description; + while ((p_type->op == SpvOpTypeArray) && (index_index < p_access_chain->index_count)) { + // Find the array element type id + SpvReflectPrvNode* p_node = FindNode(p_parser, p_type->id); + if (p_node == NULL) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + uint32_t element_type_id = p_node->array_traits.element_type_id; + // Get the array element type + p_type = FindType(p_module, element_type_id); + if (p_type == NULL) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + // Next access chain index + index_index += 1; + } + + // Only continue parsing if there's remaining indices in the access + // chain. If the end of the access chain has been reach then all + // remaining variables (including those in struct hierarchies) + // are considered USED. + // + // See: https://github.com/KhronosGroup/SPIRV-Reflect/issues/78 + // + if (index_index < p_access_chain->index_count) { + // Parse current var again with a type override and advanced index index + SpvReflectResult result = ParseDescriptorBlockVariableUsage( + p_parser, + p_module, + p_access_chain, + index_index, + p_type->op, + p_var); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + } + else { + // Clear UNUSED flag for remaining variables + MarkSelfAndAllMemberVarsAsUsed(p_var); + } + } + break; + + case SpvOpTypeStruct: { + assert(p_var->member_count > 0); + if (p_var->member_count == 0) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_UNEXPECTED_BLOCK_DATA; + } + // Get member variable at the access's chain current index + uint32_t index = p_access_chain->indexes[index_index]; + if (index >= p_var->member_count) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_BLOCK_MEMBER_REFERENCE; + } + SpvReflectBlockVariable* p_member_var = &p_var->members[index]; + + // Next access chain index + index_index += 1; + + // Only continue parsing if there's remaining indices in the access + // chain. If the end of the access chain has been reach then all + // remaining variables (including those in struct hierarchies) + // are considered USED. + // + // See: https://github.com/KhronosGroup/SPIRV-Reflect/issues/78 + // + if (index_index < p_access_chain->index_count) { + SpvReflectResult result = ParseDescriptorBlockVariableUsage( + p_parser, + p_module, + p_access_chain, + index_index, + (SpvOp)INVALID_VALUE, + p_member_var); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + } + else { + // Clear UNUSED flag for remaining variables + MarkSelfAndAllMemberVarsAsUsed(p_member_var); + } + //SpvReflectBlockVariable* p_member_var = &p_var->members[index]; + //if (index_index < p_access_chain->index_count) { + // SpvReflectResult result = ParseDescriptorBlockVariableUsage( + // p_parser, + // p_module, + // p_access_chain, + // index_index + 1, + // (SpvOp)INVALID_VALUE, + // p_member_var); + // if (result != SPV_REFLECT_RESULT_SUCCESS) { + // return result; + // } + //} + } + break; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseDescriptorBlocks( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module) +{ + if (p_module->descriptor_binding_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + for (uint32_t descriptor_index = 0; descriptor_index < p_module->descriptor_binding_count; ++descriptor_index) { + SpvReflectDescriptorBinding* p_descriptor = &(p_module->descriptor_bindings[descriptor_index]); + SpvReflectTypeDescription* p_type = p_descriptor->type_description; + if ((p_descriptor->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) && + (p_descriptor->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) ) + { + continue; + } + + // Mark UNUSED + p_descriptor->block.flags |= SPV_REFLECT_VARIABLE_FLAGS_UNUSED; + // Parse descriptor block + SpvReflectResult result = ParseDescriptorBlockVariable(p_parser, p_module, p_type, &p_descriptor->block); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + + for (uint32_t access_chain_index = 0; access_chain_index < p_parser->access_chain_count; ++access_chain_index) { + SpvReflectPrvAccessChain* p_access_chain = &(p_parser->access_chains[access_chain_index]); + // Skip any access chains that aren't touching this descriptor block + if (p_descriptor->spirv_id != p_access_chain->base_id) { + continue; + } + result = ParseDescriptorBlockVariableUsage( + p_parser, + p_module, + p_access_chain, + 0, + (SpvOp)INVALID_VALUE, + &p_descriptor->block); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + } + + p_descriptor->block.name = p_descriptor->name; + + bool is_parent_rta = (p_descriptor->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER); + result = ParseDescriptorBlockVariableSizes(p_parser, p_module, true, false, is_parent_rta, &p_descriptor->block); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + + if (is_parent_rta) { + p_descriptor->block.size = 0; + p_descriptor->block.padded_size = 0; + } + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseFormat( + const SpvReflectTypeDescription* p_type, + SpvReflectFormat* p_format +) +{ + SpvReflectResult result = SPV_REFLECT_RESULT_ERROR_INTERNAL_ERROR; + bool signedness = (p_type->traits.numeric.scalar.signedness != 0); + uint32_t bit_width = p_type->traits.numeric.scalar.width; + if (p_type->type_flags & SPV_REFLECT_TYPE_FLAG_VECTOR) { + uint32_t component_count = p_type->traits.numeric.vector.component_count; + if (p_type->type_flags & SPV_REFLECT_TYPE_FLAG_FLOAT) { + switch (bit_width) { + case 32: { + switch (component_count) { + case 2: *p_format = SPV_REFLECT_FORMAT_R32G32_SFLOAT; break; + case 3: *p_format = SPV_REFLECT_FORMAT_R32G32B32_SFLOAT; break; + case 4: *p_format = SPV_REFLECT_FORMAT_R32G32B32A32_SFLOAT; break; + } + } + break; + + case 64: { + switch (component_count) { + case 2: *p_format = SPV_REFLECT_FORMAT_R64G64_SFLOAT; break; + case 3: *p_format = SPV_REFLECT_FORMAT_R64G64B64_SFLOAT; break; + case 4: *p_format = SPV_REFLECT_FORMAT_R64G64B64A64_SFLOAT; break; + } + } + } + result = SPV_REFLECT_RESULT_SUCCESS; + } + else if (p_type->type_flags & (SPV_REFLECT_TYPE_FLAG_INT | SPV_REFLECT_TYPE_FLAG_BOOL)) { + switch (bit_width) { + case 32: { + switch (component_count) { + case 2: *p_format = signedness ? SPV_REFLECT_FORMAT_R32G32_SINT : SPV_REFLECT_FORMAT_R32G32_UINT; break; + case 3: *p_format = signedness ? SPV_REFLECT_FORMAT_R32G32B32_SINT : SPV_REFLECT_FORMAT_R32G32B32_UINT; break; + case 4: *p_format = signedness ? SPV_REFLECT_FORMAT_R32G32B32A32_SINT : SPV_REFLECT_FORMAT_R32G32B32A32_UINT; break; + } + } + break; + + case 64: { + switch (component_count) { + case 2: *p_format = signedness ? SPV_REFLECT_FORMAT_R64G64_SINT : SPV_REFLECT_FORMAT_R64G64_UINT; break; + case 3: *p_format = signedness ? SPV_REFLECT_FORMAT_R64G64B64_SINT : SPV_REFLECT_FORMAT_R64G64B64_UINT; break; + case 4: *p_format = signedness ? SPV_REFLECT_FORMAT_R64G64B64A64_SINT : SPV_REFLECT_FORMAT_R64G64B64A64_UINT; break; + } + } + } + result = SPV_REFLECT_RESULT_SUCCESS; + } + } + else if (p_type->type_flags & SPV_REFLECT_TYPE_FLAG_FLOAT) { + switch(bit_width) { + case 32: + *p_format = SPV_REFLECT_FORMAT_R32_SFLOAT; + break; + case 64: + *p_format = SPV_REFLECT_FORMAT_R64_SFLOAT; + break; + } + result = SPV_REFLECT_RESULT_SUCCESS; + } + else if (p_type->type_flags & (SPV_REFLECT_TYPE_FLAG_INT | SPV_REFLECT_TYPE_FLAG_BOOL)) { + switch(bit_width) { + case 32: + *p_format = signedness ? SPV_REFLECT_FORMAT_R32_SINT : SPV_REFLECT_FORMAT_R32_UINT; break; + break; + case 64: + *p_format = signedness ? SPV_REFLECT_FORMAT_R64_SINT : SPV_REFLECT_FORMAT_R64_UINT; break; + } + result = SPV_REFLECT_RESULT_SUCCESS; + } + else if (p_type->type_flags & SPV_REFLECT_TYPE_FLAG_STRUCT) { + *p_format = SPV_REFLECT_FORMAT_UNDEFINED; + result = SPV_REFLECT_RESULT_SUCCESS; + } + return result; +} + +static SpvReflectResult ParseInterfaceVariable( + SpvReflectPrvParser* p_parser, + const SpvReflectPrvDecorations* p_var_node_decorations, + const SpvReflectPrvDecorations* p_type_node_decorations, + SpvReflectShaderModule* p_module, + SpvReflectTypeDescription* p_type, + SpvReflectInterfaceVariable* p_var, + bool* p_has_built_in +) +{ + SpvReflectPrvNode* p_type_node = FindNode(p_parser, p_type->id); + if (IsNull(p_type_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + + if (p_type->member_count > 0) { + p_var->member_count = p_type->member_count; + p_var->members = (SpvReflectInterfaceVariable*)calloc(p_var->member_count, sizeof(*p_var->members)); + if (IsNull(p_var->members)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + for (uint32_t member_index = 0; member_index < p_type_node->member_count; ++member_index) { + SpvReflectPrvDecorations* p_member_decorations = &p_type_node->member_decorations[member_index]; + SpvReflectTypeDescription* p_member_type = &p_type->members[member_index]; + SpvReflectInterfaceVariable* p_member_var = &p_var->members[member_index]; + SpvReflectResult result = ParseInterfaceVariable(p_parser, NULL, p_member_decorations, p_module, p_member_type, p_member_var, p_has_built_in); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + SPV_REFLECT_ASSERT(false); + return result; + } + } + } + + p_var->name = p_type_node->name; + p_var->decoration_flags = ApplyDecorations(p_type_node_decorations); + if (p_var_node_decorations != NULL) { + p_var->decoration_flags |= ApplyDecorations(p_var_node_decorations); + } + p_var->built_in = p_type_node_decorations->built_in; + ApplyNumericTraits(p_type, &p_var->numeric); + if (p_type->op == SpvOpTypeArray) { + ApplyArrayTraits(p_type, &p_var->array); + } + + p_var->type_description = p_type; + + *p_has_built_in |= p_type_node_decorations->is_built_in; + + // Only parse format for interface variables that are input or output + if ((p_var->storage_class == SpvStorageClassInput) || (p_var->storage_class == SpvStorageClassOutput)) { + SpvReflectResult result = ParseFormat(p_var->type_description, &p_var->format); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + SPV_REFLECT_ASSERT(false); + return result; + } + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseInterfaceVariables( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module, + SpvReflectEntryPoint* p_entry, + uint32_t interface_variable_count, + uint32_t* p_interface_variable_ids +) +{ + if (interface_variable_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + p_entry->interface_variable_count = interface_variable_count; + p_entry->input_variable_count = 0; + p_entry->output_variable_count = 0; + for (size_t i = 0; i < interface_variable_count; ++i) { + uint32_t var_result_id = *(p_interface_variable_ids + i); + SpvReflectPrvNode* p_node = FindNode(p_parser, var_result_id); + if (IsNull(p_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + + if (p_node->storage_class == SpvStorageClassInput) { + p_entry->input_variable_count += 1; + } + else if (p_node->storage_class == SpvStorageClassOutput) { + p_entry->output_variable_count += 1; + } + } + + if (p_entry->input_variable_count > 0) { + p_entry->input_variables = (SpvReflectInterfaceVariable**)calloc(p_entry->input_variable_count, sizeof(*(p_entry->input_variables))); + if (IsNull(p_entry->input_variables)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + + if (p_entry->output_variable_count > 0) { + p_entry->output_variables = (SpvReflectInterfaceVariable**)calloc(p_entry->output_variable_count, sizeof(*(p_entry->output_variables))); + if (IsNull(p_entry->output_variables)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + + if (p_entry->interface_variable_count > 0) { + p_entry->interface_variables = (SpvReflectInterfaceVariable*)calloc(p_entry->interface_variable_count, sizeof(*(p_entry->interface_variables))); + if (IsNull(p_entry->interface_variables)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + + size_t input_index = 0; + size_t output_index = 0; + for (size_t i = 0; i < interface_variable_count; ++i) { + uint32_t var_result_id = *(p_interface_variable_ids + i); + SpvReflectPrvNode* p_node = FindNode(p_parser, var_result_id); + if (IsNull(p_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + + SpvReflectTypeDescription* p_type = FindType(p_module, p_node->type_id); + if (IsNull(p_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + // If the type is a pointer, resolve it + if (p_type->op == SpvOpTypePointer) { + // Find the type's node + SpvReflectPrvNode* p_type_node = FindNode(p_parser, p_type->id); + if (IsNull(p_type_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + // Should be the resolved type + p_type = FindType(p_module, p_type_node->type_id); + if (IsNull(p_type)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + } + + SpvReflectPrvNode* p_type_node = FindNode(p_parser, p_type->id); + if (IsNull(p_type_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + + SpvReflectInterfaceVariable* p_var = &(p_entry->interface_variables[i]); + p_var->storage_class = p_node->storage_class; + + bool has_built_in = p_node->decorations.is_built_in; + SpvReflectResult result = ParseInterfaceVariable( + p_parser, + &p_node->decorations, + &p_type_node->decorations, + p_module, + p_type, + p_var, + &has_built_in); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + SPV_REFLECT_ASSERT(false); + return result; + } + + // Input and output variables + if (p_var->storage_class == SpvStorageClassInput) { + p_entry->input_variables[input_index] = p_var; + ++input_index; + } + else if (p_node->storage_class == SpvStorageClassOutput) { + p_entry->output_variables[output_index] = p_var; + ++output_index; + } + + // SPIR-V result id + p_var->spirv_id = p_node->result_id; + // Name + p_var->name = p_node->name; + // Semantic + p_var->semantic = p_node->decorations.semantic.value; + + // Decorate with built-in if any member is built-in + if (has_built_in) { + p_var->decoration_flags |= SPV_REFLECT_DECORATION_BUILT_IN; + } + + // Location is decorated on OpVariable node, not the type node. + p_var->location = p_node->decorations.location.value; + p_var->word_offset.location = p_node->decorations.location.word_offset; + + // Built in + if (p_node->decorations.is_built_in) { + p_var->built_in = p_node->decorations.built_in; + } + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult EnumerateAllPushConstants( + SpvReflectShaderModule* p_module, + size_t* p_push_constant_count, + uint32_t** p_push_constants +) +{ + *p_push_constant_count = p_module->push_constant_block_count; + if (*p_push_constant_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + *p_push_constants = (uint32_t*)calloc(*p_push_constant_count, sizeof(**p_push_constants)); + + if (IsNull(*p_push_constants)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + for (size_t i = 0; i < *p_push_constant_count; ++i) { + (*p_push_constants)[i] = p_module->push_constant_blocks[i].spirv_id; + } + qsort(*p_push_constants, *p_push_constant_count, sizeof(**p_push_constants), + SortCompareUint32); + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult TraverseCallGraph( + SpvReflectPrvParser* p_parser, + SpvReflectPrvFunction* p_func, + size_t* p_func_count, + uint32_t* p_func_ids, + uint32_t depth +) +{ + if (depth > p_parser->function_count) { + // Vulkan does not permit recursion (Vulkan spec Appendix A): + // "Recursion: The static function-call graph for an entry point must not + // contain cycles." + return SPV_REFLECT_RESULT_ERROR_SPIRV_RECURSION; + } + if (IsNotNull(p_func_ids)) { + p_func_ids[(*p_func_count)++] = p_func->id; + } else { + ++*p_func_count; + } + for (size_t i = 0; i < p_func->callee_count; ++i) { + SpvReflectResult result = TraverseCallGraph( + p_parser, p_func->callee_ptrs[i], p_func_count, p_func_ids, depth + 1); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + } + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseStaticallyUsedResources( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module, + SpvReflectEntryPoint* p_entry, + size_t uniform_count, + uint32_t* uniforms, + size_t push_constant_count, + uint32_t* push_constants +) +{ + // Find function with the right id + SpvReflectPrvFunction* p_func = NULL; + for (size_t i = 0; i < p_parser->function_count; ++i) { + if (p_parser->functions[i].id == p_entry->id) { + p_func = &(p_parser->functions[i]); + break; + } + } + if (p_func == NULL) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + + size_t called_function_count = 0; + SpvReflectResult result = TraverseCallGraph( + p_parser, + p_func, + &called_function_count, + NULL, + 0); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + + uint32_t* p_called_functions = NULL; + if (called_function_count > 0) { + p_called_functions = (uint32_t*)calloc(called_function_count, sizeof(*p_called_functions)); + if (IsNull(p_called_functions)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + + called_function_count = 0; + result = TraverseCallGraph( + p_parser, + p_func, + &called_function_count, + p_called_functions, + 0); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + + if (called_function_count > 0) { + qsort( + p_called_functions, + called_function_count, + sizeof(*p_called_functions), + SortCompareUint32); + } + called_function_count = DedupSortedUint32(p_called_functions, called_function_count); + + uint32_t used_variable_count = 0; + for (size_t i = 0, j = 0; i < called_function_count; ++i) { + // No need to bounds check j because a missing ID issue would have been + // found during TraverseCallGraph + while (p_parser->functions[j].id != p_called_functions[i]) { + ++j; + } + used_variable_count += p_parser->functions[j].accessed_ptr_count; + } + uint32_t* used_variables = NULL; + if (used_variable_count > 0) { + used_variables = (uint32_t*)calloc(used_variable_count, + sizeof(*used_variables)); + if (IsNull(used_variables)) { + SafeFree(p_called_functions); + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + used_variable_count = 0; + for (size_t i = 0, j = 0; i < called_function_count; ++i) { + while (p_parser->functions[j].id != p_called_functions[i]) { + ++j; + } + + memcpy(&used_variables[used_variable_count], + p_parser->functions[j].accessed_ptrs, + p_parser->functions[j].accessed_ptr_count * sizeof(*used_variables)); + used_variable_count += p_parser->functions[j].accessed_ptr_count; + } + SafeFree(p_called_functions); + + if (used_variable_count > 0) { + qsort(used_variables, used_variable_count, sizeof(*used_variables), + SortCompareUint32); + } + used_variable_count = (uint32_t)DedupSortedUint32(used_variables, + used_variable_count); + + // Do set intersection to find the used uniform and push constants + size_t used_uniform_count = 0; + // + SpvReflectResult result0 = IntersectSortedUint32( + used_variables, + used_variable_count, + uniforms, + uniform_count, + &p_entry->used_uniforms, + &used_uniform_count); + + size_t used_push_constant_count = 0; + // + SpvReflectResult result1 = IntersectSortedUint32( + used_variables, + used_variable_count, + push_constants, + push_constant_count, + &p_entry->used_push_constants, + &used_push_constant_count); + + for (uint32_t j = 0; j < p_module->descriptor_binding_count; ++j) { + SpvReflectDescriptorBinding* p_binding = &p_module->descriptor_bindings[j]; + bool found = SearchSortedUint32( + used_variables, + used_variable_count, + p_binding->spirv_id); + if (found) { + p_binding->accessed = 1; + } + } + + SafeFree(used_variables); + if (result0 != SPV_REFLECT_RESULT_SUCCESS) { + return result0; + } + if (result1 != SPV_REFLECT_RESULT_SUCCESS) { + return result1; + } + + p_entry->used_uniform_count = (uint32_t)used_uniform_count; + p_entry->used_push_constant_count = (uint32_t)used_push_constant_count; + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseEntryPoints( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module) +{ + if (p_parser->entry_point_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + p_module->entry_point_count = p_parser->entry_point_count; + p_module->entry_points = (SpvReflectEntryPoint*)calloc(p_module->entry_point_count, + sizeof(*(p_module->entry_points))); + if (IsNull(p_module->entry_points)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + SpvReflectResult result; + size_t uniform_count = 0; + uint32_t* uniforms = NULL; + if ((result = EnumerateAllUniforms(p_module, &uniform_count, &uniforms)) != + SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + size_t push_constant_count = 0; + uint32_t* push_constants = NULL; + if ((result = EnumerateAllPushConstants(p_module, &push_constant_count, &push_constants)) != + SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + + size_t entry_point_index = 0; + for (size_t i = 0; entry_point_index < p_parser->entry_point_count && i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if (p_node->op != SpvOpEntryPoint) { + continue; + } + + SpvReflectEntryPoint* p_entry_point = &(p_module->entry_points[entry_point_index]); + CHECKED_READU32_CAST(p_parser, p_node->word_offset + 1, SpvExecutionModel, p_entry_point->spirv_execution_model); + CHECKED_READU32(p_parser, p_node->word_offset + 2, p_entry_point->id); + + switch (p_entry_point->spirv_execution_model) { + default: break; + case SpvExecutionModelVertex : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_VERTEX_BIT; break; + case SpvExecutionModelTessellationControl : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_TESSELLATION_CONTROL_BIT; break; + case SpvExecutionModelTessellationEvaluation : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; break; + case SpvExecutionModelGeometry : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_GEOMETRY_BIT; break; + case SpvExecutionModelFragment : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_FRAGMENT_BIT; break; + case SpvExecutionModelGLCompute : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_COMPUTE_BIT; break; + case SpvExecutionModelTaskNV : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_TASK_BIT_NV; break; + case SpvExecutionModelMeshNV : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_MESH_BIT_NV; break; + case SpvExecutionModelRayGenerationKHR : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_RAYGEN_BIT_KHR; break; + case SpvExecutionModelIntersectionKHR : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_INTERSECTION_BIT_KHR; break; + case SpvExecutionModelAnyHitKHR : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_ANY_HIT_BIT_KHR; break; + case SpvExecutionModelClosestHitKHR : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; break; + case SpvExecutionModelMissKHR : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_MISS_BIT_KHR; break; + case SpvExecutionModelCallableKHR : p_entry_point->shader_stage = SPV_REFLECT_SHADER_STAGE_CALLABLE_BIT_KHR; break; + } + + ++entry_point_index; + + // Name length is required to calculate next operand + uint32_t name_start_word_offset = 3; + uint32_t name_length_with_terminator = 0; + result = ReadStr(p_parser, p_node->word_offset + name_start_word_offset, 0, p_node->word_count, &name_length_with_terminator, NULL); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + p_entry_point->name = (const char*)(p_parser->spirv_code + p_node->word_offset + name_start_word_offset); + + uint32_t name_word_count = RoundUp(name_length_with_terminator, SPIRV_WORD_SIZE) / SPIRV_WORD_SIZE; + uint32_t interface_variable_count = (p_node->word_count - (name_start_word_offset + name_word_count)); + uint32_t* p_interface_variables = NULL; + if (interface_variable_count > 0) { + p_interface_variables = (uint32_t*)calloc(interface_variable_count, sizeof(*(p_interface_variables))); + if (IsNull(p_interface_variables)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + + for (uint32_t var_index = 0; var_index < interface_variable_count; ++var_index) { + uint32_t var_result_id = (uint32_t)INVALID_VALUE; + uint32_t offset = name_start_word_offset + name_word_count + var_index; + CHECKED_READU32(p_parser, p_node->word_offset + offset, var_result_id); + p_interface_variables[var_index] = var_result_id; + } + + result = ParseInterfaceVariables( + p_parser, + p_module, + p_entry_point, + interface_variable_count, + p_interface_variables); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + SafeFree(p_interface_variables); + + result = ParseStaticallyUsedResources( + p_parser, + p_module, + p_entry_point, + uniform_count, + uniforms, + push_constant_count, + push_constants); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + } + + SafeFree(uniforms); + SafeFree(push_constants); + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseExecutionModes( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module) +{ + assert(IsNotNull(p_parser)); + assert(IsNotNull(p_parser->nodes)); + assert(IsNotNull(p_module)); + + if (IsNotNull(p_parser) && IsNotNull(p_parser->spirv_code) && IsNotNull(p_parser->nodes)) { + for (size_t node_idx = 0; node_idx < p_parser->node_count; ++node_idx) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[node_idx]); + if (p_node->op != SpvOpExecutionMode) { + continue; + } + + // Read entry point id + uint32_t entry_point_id = 0; + CHECKED_READU32(p_parser, p_node->word_offset + 1, entry_point_id); + + // Find entry point + SpvReflectEntryPoint* p_entry_point = NULL; + for (size_t entry_point_idx = 0; entry_point_idx < p_module->entry_point_count; ++entry_point_idx) { + if (p_module->entry_points[entry_point_idx].id == entry_point_id) { + p_entry_point = &p_module->entry_points[entry_point_idx]; + break; + } + } + // Bail if entry point is null + if (IsNull(p_entry_point)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ENTRY_POINT; + } + + // Read execution mode + uint32_t execution_mode = (uint32_t)INVALID_VALUE; + CHECKED_READU32(p_parser, p_node->word_offset + 2, execution_mode); + + // Parse execution mode + switch (execution_mode) { + default: { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_EXECUTION_MODE; + } + break; + + case SpvExecutionModeInvocations: { + CHECKED_READU32(p_parser, p_node->word_offset + 3, p_entry_point->invocations); + } + break; + + case SpvExecutionModeSpacingEqual: + case SpvExecutionModeSpacingFractionalEven: + case SpvExecutionModeSpacingFractionalOdd: + case SpvExecutionModeVertexOrderCw: + case SpvExecutionModeVertexOrderCcw: + case SpvExecutionModePixelCenterInteger: + case SpvExecutionModeOriginUpperLeft: + case SpvExecutionModeOriginLowerLeft: + case SpvExecutionModeEarlyFragmentTests: + case SpvExecutionModePointMode: + case SpvExecutionModeXfb: + case SpvExecutionModeDepthReplacing: + case SpvExecutionModeDepthGreater: + case SpvExecutionModeDepthLess: + case SpvExecutionModeDepthUnchanged: + break; + + case SpvExecutionModeLocalSize: { + CHECKED_READU32(p_parser, p_node->word_offset + 3, p_entry_point->local_size.x); + CHECKED_READU32(p_parser, p_node->word_offset + 4, p_entry_point->local_size.y); + CHECKED_READU32(p_parser, p_node->word_offset + 5, p_entry_point->local_size.z); + } + break; + + case SpvExecutionModeLocalSizeHint: + case SpvExecutionModeInputPoints: + case SpvExecutionModeInputLines: + case SpvExecutionModeInputLinesAdjacency: + case SpvExecutionModeTriangles: + case SpvExecutionModeInputTrianglesAdjacency: + case SpvExecutionModeQuads: + case SpvExecutionModeIsolines: + case SpvExecutionModeOutputVertices: { + CHECKED_READU32(p_parser, p_node->word_offset + 3, p_entry_point->output_vertices); + } + break; + + case SpvExecutionModeOutputPoints: + case SpvExecutionModeOutputLineStrip: + case SpvExecutionModeOutputTriangleStrip: + case SpvExecutionModeVecTypeHint: + case SpvExecutionModeContractionOff: + case SpvExecutionModeInitializer: + case SpvExecutionModeFinalizer: + case SpvExecutionModeSubgroupSize: + case SpvExecutionModeSubgroupsPerWorkgroup: + case SpvExecutionModeSubgroupsPerWorkgroupId: + case SpvExecutionModeLocalSizeId: + case SpvExecutionModeLocalSizeHintId: + case SpvExecutionModePostDepthCoverage: + case SpvExecutionModeDenormPreserve: + case SpvExecutionModeDenormFlushToZero: + case SpvExecutionModeSignedZeroInfNanPreserve: + case SpvExecutionModeRoundingModeRTE: + case SpvExecutionModeRoundingModeRTZ: + case SpvExecutionModeStencilRefReplacingEXT: + case SpvExecutionModeOutputLinesNV: + case SpvExecutionModeOutputPrimitivesNV: + case SpvExecutionModeOutputTrianglesNV: + break; + } + p_entry_point->execution_mode_count++; + } + uint32_t* indices = (uint32_t*)calloc(p_module->entry_point_count, sizeof(indices)); + if (IsNull(indices)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + for (size_t entry_point_idx = 0; entry_point_idx < p_module->entry_point_count; ++entry_point_idx) { + SpvReflectEntryPoint* p_entry_point = &p_module->entry_points[entry_point_idx]; + p_entry_point->execution_modes = + (SpvExecutionMode*)calloc(p_entry_point->execution_mode_count, sizeof(*p_entry_point->execution_modes)); + if (IsNull(p_entry_point->execution_modes)) { + SafeFree(indices); + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + + for (size_t node_idx = 0; node_idx < p_parser->node_count; ++node_idx) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[node_idx]); + if (p_node->op != SpvOpExecutionMode) { + continue; + } + + // Read entry point id + uint32_t entry_point_id = 0; + CHECKED_READU32(p_parser, p_node->word_offset + 1, entry_point_id); + + // Find entry point + SpvReflectEntryPoint* p_entry_point = NULL; + uint32_t* idx = NULL; + for (size_t entry_point_idx = 0; entry_point_idx < p_module->entry_point_count; ++entry_point_idx) { + if (p_module->entry_points[entry_point_idx].id == entry_point_id) { + p_entry_point = &p_module->entry_points[entry_point_idx]; + idx = &indices[entry_point_idx]; + break; + } + } + + // Read execution mode + uint32_t execution_mode = (uint32_t)INVALID_VALUE; + CHECKED_READU32(p_parser, p_node->word_offset + 2, execution_mode); + p_entry_point->execution_modes[(*idx)++] = (SpvExecutionMode)execution_mode; + } + SafeFree(indices); + } + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParsePushConstantBlocks( + SpvReflectPrvParser* p_parser, + SpvReflectShaderModule* p_module) +{ + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if ((p_node->op != SpvOpVariable) || (p_node->storage_class != SpvStorageClassPushConstant)) { + continue; + } + + p_module->push_constant_block_count += 1; + } + + if (p_module->push_constant_block_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + p_module->push_constant_blocks = (SpvReflectBlockVariable*)calloc(p_module->push_constant_block_count, sizeof(*p_module->push_constant_blocks)); + if (IsNull(p_module->push_constant_blocks)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + + uint32_t push_constant_index = 0; + for (size_t i = 0; i < p_parser->node_count; ++i) { + SpvReflectPrvNode* p_node = &(p_parser->nodes[i]); + if ((p_node->op != SpvOpVariable) || (p_node->storage_class != SpvStorageClassPushConstant)) { + continue; + } + + SpvReflectTypeDescription* p_type = FindType(p_module, p_node->type_id); + if (IsNull(p_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + // If the type is a pointer, resolve it + if (p_type->op == SpvOpTypePointer) { + // Find the type's node + SpvReflectPrvNode* p_type_node = FindNode(p_parser, p_type->id); + if (IsNull(p_type_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + // Should be the resolved type + p_type = FindType(p_module, p_type_node->type_id); + if (IsNull(p_type)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + } + + SpvReflectPrvNode* p_type_node = FindNode(p_parser, p_type->id); + if (IsNull(p_type_node)) { + return SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE; + } + + SpvReflectBlockVariable* p_push_constant = &p_module->push_constant_blocks[push_constant_index]; + p_push_constant->spirv_id = p_node->result_id; + SpvReflectResult result = ParseDescriptorBlockVariable(p_parser, p_module, p_type, p_push_constant); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + result = ParseDescriptorBlockVariableSizes(p_parser, p_module, true, false, false, p_push_constant); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + return result; + } + + ++push_constant_index; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static int SortCompareDescriptorSet(const void* a, const void* b) +{ + const SpvReflectDescriptorSet* p_elem_a = (const SpvReflectDescriptorSet*)a; + const SpvReflectDescriptorSet* p_elem_b = (const SpvReflectDescriptorSet*)b; + int value = (int)(p_elem_a->set) - (int)(p_elem_b->set); + // We should never see duplicate descriptor set numbers in a shader; if so, a tiebreaker + // would be needed here. + assert(value != 0); + return value; +} + +static SpvReflectResult ParseEntrypointDescriptorSets(SpvReflectShaderModule* p_module) { + // Update the entry point's sets + for (uint32_t i = 0; i < p_module->entry_point_count; ++i) { + SpvReflectEntryPoint* p_entry = &p_module->entry_points[i]; + for (uint32_t j = 0; j < p_entry->descriptor_set_count; ++j) { + SafeFree(p_entry->descriptor_sets[j].bindings); + } + SafeFree(p_entry->descriptor_sets); + p_entry->descriptor_set_count = 0; + for (uint32_t j = 0; j < p_module->descriptor_set_count; ++j) { + const SpvReflectDescriptorSet* p_set = &p_module->descriptor_sets[j]; + for (uint32_t k = 0; k < p_set->binding_count; ++k) { + bool found = SearchSortedUint32( + p_entry->used_uniforms, + p_entry->used_uniform_count, + p_set->bindings[k]->spirv_id); + if (found) { + ++p_entry->descriptor_set_count; + break; + } + } + } + + p_entry->descriptor_sets = NULL; + if (p_entry->descriptor_set_count > 0) { + p_entry->descriptor_sets = (SpvReflectDescriptorSet*)calloc(p_entry->descriptor_set_count, + sizeof(*p_entry->descriptor_sets)); + if (IsNull(p_entry->descriptor_sets)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + } + p_entry->descriptor_set_count = 0; + for (uint32_t j = 0; j < p_module->descriptor_set_count; ++j) { + const SpvReflectDescriptorSet* p_set = &p_module->descriptor_sets[j]; + uint32_t count = 0; + for (uint32_t k = 0; k < p_set->binding_count; ++k) { + bool found = SearchSortedUint32( + p_entry->used_uniforms, + p_entry->used_uniform_count, + p_set->bindings[k]->spirv_id); + if (found) { + ++count; + } + } + if (count == 0) { + continue; + } + SpvReflectDescriptorSet* p_entry_set = &p_entry->descriptor_sets[ + p_entry->descriptor_set_count++]; + p_entry_set->set = p_set->set; + p_entry_set->bindings = (SpvReflectDescriptorBinding**)calloc(count, + sizeof(*p_entry_set->bindings)); + if (IsNull(p_entry_set->bindings)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + for (uint32_t k = 0; k < p_set->binding_count; ++k) { + bool found = SearchSortedUint32( + p_entry->used_uniforms, + p_entry->used_uniform_count, + p_set->bindings[k]->spirv_id); + if (found) { + p_entry_set->bindings[p_entry_set->binding_count++] = p_set->bindings[k]; + } + } + } + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult ParseDescriptorSets(SpvReflectShaderModule* p_module) +{ + // Count the descriptors in each set + for (uint32_t i = 0; i < p_module->descriptor_binding_count; ++i) { + SpvReflectDescriptorBinding* p_descriptor = &(p_module->descriptor_bindings[i]); + + // Look for a target set using the descriptor's set number + SpvReflectDescriptorSet* p_target_set = NULL; + for (uint32_t j = 0; j < SPV_REFLECT_MAX_DESCRIPTOR_SETS; ++j) { + SpvReflectDescriptorSet* p_set = &p_module->descriptor_sets[j]; + if (p_set->set == p_descriptor->set) { + p_target_set = p_set; + break; + } + } + + // If a target set isn't found, find the first available one. + if (IsNull(p_target_set)) { + for (uint32_t j = 0; j < SPV_REFLECT_MAX_DESCRIPTOR_SETS; ++j) { + SpvReflectDescriptorSet* p_set = &p_module->descriptor_sets[j]; + if (p_set->set == (uint32_t)INVALID_VALUE) { + p_target_set = p_set; + p_target_set->set = p_descriptor->set; + break; + } + } + } + + if (IsNull(p_target_set)) { + return SPV_REFLECT_RESULT_ERROR_INTERNAL_ERROR; + } + + p_target_set->binding_count += 1; + } + + // Count the descriptor sets + for (uint32_t i = 0; i < SPV_REFLECT_MAX_DESCRIPTOR_SETS; ++i) { + const SpvReflectDescriptorSet* p_set = &p_module->descriptor_sets[i]; + if (p_set->set != (uint32_t)INVALID_VALUE) { + p_module->descriptor_set_count += 1; + } + } + + // Sort the descriptor sets based on numbers + if (p_module->descriptor_set_count > 0) { + qsort(p_module->descriptor_sets, + p_module->descriptor_set_count, + sizeof(*(p_module->descriptor_sets)), + SortCompareDescriptorSet); + } + + // Build descriptor pointer array + for (uint32_t i = 0; i descriptor_set_count; ++i) { + SpvReflectDescriptorSet* p_set = &(p_module->descriptor_sets[i]); + p_set->bindings = (SpvReflectDescriptorBinding **)calloc(p_set->binding_count, sizeof(*(p_set->bindings))); + + uint32_t descriptor_index = 0; + for (uint32_t j = 0; j < p_module->descriptor_binding_count; ++j) { + SpvReflectDescriptorBinding* p_descriptor = &(p_module->descriptor_bindings[j]); + if (p_descriptor->set == p_set->set) { + assert(descriptor_index < p_set->binding_count); + p_set->bindings[descriptor_index] = p_descriptor; + ++descriptor_index; + } + } + } + + return ParseEntrypointDescriptorSets(p_module); +} + +static SpvReflectResult DisambiguateStorageBufferSrvUav(SpvReflectShaderModule* p_module) +{ + if (p_module->descriptor_binding_count == 0) { + return SPV_REFLECT_RESULT_SUCCESS; + } + + for (uint32_t descriptor_index = 0; descriptor_index < p_module->descriptor_binding_count; ++descriptor_index) { + SpvReflectDescriptorBinding* p_descriptor = &(p_module->descriptor_bindings[descriptor_index]); + // Skip everything that isn't a STORAGE_BUFFER descriptor + if (p_descriptor->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) { + continue; + } + + // + // Vulkan doesn't disambiguate between SRVs and UAVs so they + // come back as STORAGE_BUFFER. The block parsing process will + // mark a block as non-writable should any member of the block + // or its descendants are non-writable. + // + if (p_descriptor->block.decoration_flags & SPV_REFLECT_DECORATION_NON_WRITABLE) { + p_descriptor->resource_type = SPV_REFLECT_RESOURCE_FLAG_SRV; + } + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +static SpvReflectResult SynchronizeDescriptorSets(SpvReflectShaderModule* p_module) +{ + // Free and reset all descriptor set numbers + for (uint32_t i = 0; i < SPV_REFLECT_MAX_DESCRIPTOR_SETS; ++i) { + SpvReflectDescriptorSet* p_set = &p_module->descriptor_sets[i]; + SafeFree(p_set->bindings); + p_set->binding_count = 0; + p_set->set = (uint32_t)INVALID_VALUE; + } + // Set descriptor set count to zero + p_module->descriptor_set_count = 0; + + SpvReflectResult result = ParseDescriptorSets(p_module); + return result; +} + +static SpvReflectResult CreateShaderModule( + uint32_t flags, + size_t size, + const void* p_code, + SpvReflectShaderModule* p_module +) +{ + // Initialize all module fields to zero + memset(p_module, 0, sizeof(*p_module)); + + // Allocate module internals +#ifdef __cplusplus + p_module->_internal = (SpvReflectShaderModule::Internal*)calloc(1, sizeof(*(p_module->_internal))); +#else + p_module->_internal = calloc(1, sizeof(*(p_module->_internal))); +#endif + if (IsNull(p_module->_internal)) { + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + // Copy flags + p_module->_internal->module_flags = flags; + // Figure out if we need to copy the SPIR-V code or not + if (flags & SPV_REFLECT_MODULE_FLAG_NO_COPY) { + // Set internal size and pointer to args passed in + p_module->_internal->spirv_size = size; +#if defined(__cplusplus) + p_module->_internal->spirv_code = const_cast(static_cast(p_code)); // cast that const away +#else + p_module->_internal->spirv_code = (void*)p_code; // cast that const away +#endif + p_module->_internal->spirv_word_count = (uint32_t)(size / SPIRV_WORD_SIZE); + } + else { + // Allocate SPIR-V code storage + p_module->_internal->spirv_size = size; + p_module->_internal->spirv_code = (uint32_t*)calloc(1, p_module->_internal->spirv_size); + p_module->_internal->spirv_word_count = (uint32_t)(size / SPIRV_WORD_SIZE); + if (IsNull(p_module->_internal->spirv_code)) { + SafeFree(p_module->_internal); + return SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED; + } + // Copy SPIR-V to code storage + memcpy(p_module->_internal->spirv_code, p_code, size); + } + + SpvReflectPrvParser parser = { 0 }; + SpvReflectResult result = CreateParser(p_module->_internal->spirv_size, + p_module->_internal->spirv_code, + &parser); + + // Generator + { + const uint32_t* p_ptr = (const uint32_t*)p_module->_internal->spirv_code; + p_module->generator = (SpvReflectGenerator)((*(p_ptr + 2) & 0xFFFF0000) >> 16); + } + + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseNodes(&parser); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseStrings(&parser); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseSource(&parser, p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseFunctions(&parser); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseMemberCounts(&parser); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseNames(&parser); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseDecorations(&parser); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + + // Start of reflection data parsing + if (result == SPV_REFLECT_RESULT_SUCCESS) { + p_module->source_language = parser.source_language; + p_module->source_language_version = parser.source_language_version; + + // Zero out descriptor set data + p_module->descriptor_set_count = 0; + memset(p_module->descriptor_sets, 0, SPV_REFLECT_MAX_DESCRIPTOR_SETS * sizeof(*p_module->descriptor_sets)); + // Initialize descriptor set numbers + for (uint32_t set_number = 0; set_number < SPV_REFLECT_MAX_DESCRIPTOR_SETS; ++set_number) { + p_module->descriptor_sets[set_number].set = (uint32_t)INVALID_VALUE; + } + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseTypes(&parser, p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseDescriptorBindings(&parser, p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseDescriptorType(p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseUAVCounterBindings(p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseDescriptorBlocks(&parser, p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParsePushConstantBlocks(&parser, p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseEntryPoints(&parser, p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS && p_module->entry_point_count > 0) { + SpvReflectEntryPoint* p_entry = &(p_module->entry_points[0]); + p_module->entry_point_name = p_entry->name; + p_module->entry_point_id = p_entry->id; + p_module->spirv_execution_model = p_entry->spirv_execution_model; + p_module->shader_stage = p_entry->shader_stage; + p_module->input_variable_count = p_entry->input_variable_count; + p_module->input_variables = p_entry->input_variables; + p_module->output_variable_count = p_entry->output_variable_count; + p_module->output_variables = p_entry->output_variables; + p_module->interface_variable_count = p_entry->interface_variable_count; + p_module->interface_variables = p_entry->interface_variables; + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = DisambiguateStorageBufferSrvUav(p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = SynchronizeDescriptorSets(p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + if (result == SPV_REFLECT_RESULT_SUCCESS) { + result = ParseExecutionModes(&parser, p_module); + SPV_REFLECT_ASSERT(result == SPV_REFLECT_RESULT_SUCCESS); + } + + // Destroy module if parse was not successful + if (result != SPV_REFLECT_RESULT_SUCCESS) { + spvReflectDestroyShaderModule(p_module); + } + + DestroyParser(&parser); + + return result; +} + +SpvReflectResult spvReflectCreateShaderModule( + size_t size, + const void* p_code, + SpvReflectShaderModule* p_module +) +{ + return CreateShaderModule(0, size, p_code, p_module); +} + +SpvReflectResult spvReflectCreateShaderModule2( + uint32_t flags, + size_t size, + const void* p_code, + SpvReflectShaderModule* p_module +) +{ + return CreateShaderModule(flags, size, p_code, p_module); +} + +SpvReflectResult spvReflectGetShaderModule( + size_t size, + const void* p_code, + SpvReflectShaderModule* p_module +) +{ + return spvReflectCreateShaderModule(size, p_code, p_module); +} + +static void SafeFreeTypes(SpvReflectTypeDescription* p_type) +{ + if (IsNull(p_type)) { + return; + } + + if (IsNotNull(p_type->members)) { + for (size_t i = 0; i < p_type->member_count; ++i) { + SpvReflectTypeDescription* p_member = &p_type->members[i]; + SafeFreeTypes(p_member); + } + + SafeFree(p_type->members); + p_type->members = NULL; + } +} + +static void SafeFreeBlockVariables(SpvReflectBlockVariable* p_block) +{ + if (IsNull(p_block)) { + return; + } + + if (IsNotNull(p_block->members)) { + for (size_t i = 0; i < p_block->member_count; ++i) { + SpvReflectBlockVariable* p_member = &p_block->members[i]; + SafeFreeBlockVariables(p_member); + } + + SafeFree(p_block->members); + p_block->members = NULL; + } +} + +static void SafeFreeInterfaceVariable(SpvReflectInterfaceVariable* p_interface) +{ + if (IsNull(p_interface)) { + return; + } + + if (IsNotNull(p_interface->members)) { + for (size_t i = 0; i < p_interface->member_count; ++i) { + SpvReflectInterfaceVariable* p_member = &p_interface->members[i]; + SafeFreeInterfaceVariable(p_member); + } + + SafeFree(p_interface->members); + p_interface->members = NULL; + } +} + +void spvReflectDestroyShaderModule(SpvReflectShaderModule* p_module) +{ + if (IsNull(p_module->_internal)) { + return; + } + + SafeFree(p_module->source_source); + + // Descriptor set bindings + for (size_t i = 0; i < p_module->descriptor_set_count; ++i) { + SpvReflectDescriptorSet* p_set = &p_module->descriptor_sets[i]; + free(p_set->bindings); + } + + // Descriptor binding blocks + for (size_t i = 0; i < p_module->descriptor_binding_count; ++i) { + SpvReflectDescriptorBinding* p_descriptor = &p_module->descriptor_bindings[i]; + SafeFreeBlockVariables(&p_descriptor->block); + } + SafeFree(p_module->descriptor_bindings); + + // Entry points + for (size_t i = 0; i < p_module->entry_point_count; ++i) { + SpvReflectEntryPoint* p_entry = &p_module->entry_points[i]; + for (size_t j = 0; j < p_entry->interface_variable_count; j++) { + SafeFreeInterfaceVariable(&p_entry->interface_variables[j]); + } + for (uint32_t j = 0; j < p_entry->descriptor_set_count; ++j) { + SafeFree(p_entry->descriptor_sets[j].bindings); + } + SafeFree(p_entry->descriptor_sets); + SafeFree(p_entry->input_variables); + SafeFree(p_entry->output_variables); + SafeFree(p_entry->interface_variables); + SafeFree(p_entry->used_uniforms); + SafeFree(p_entry->used_push_constants); + SafeFree(p_entry->execution_modes); + } + SafeFree(p_module->entry_points); + + // Push constants + for (size_t i = 0; i < p_module->push_constant_block_count; ++i) { + SafeFreeBlockVariables(&p_module->push_constant_blocks[i]); + } + SafeFree(p_module->push_constant_blocks); + + // Type infos + for (size_t i = 0; i < p_module->_internal->type_description_count; ++i) { + SpvReflectTypeDescription* p_type = &p_module->_internal->type_descriptions[i]; + if (IsNotNull(p_type->members)) { + SafeFreeTypes(p_type); + } + SafeFree(p_type->members); + } + SafeFree(p_module->_internal->type_descriptions); + + // Free SPIR-V code if there was a copy + if ((p_module->_internal->module_flags & SPV_REFLECT_MODULE_FLAG_NO_COPY) == 0) { + SafeFree(p_module->_internal->spirv_code); + } + // Free internal + SafeFree(p_module->_internal); +} + +uint32_t spvReflectGetCodeSize(const SpvReflectShaderModule* p_module) +{ + if (IsNull(p_module)) { + return 0; + } + + return (uint32_t)(p_module->_internal->spirv_size); +} + +const uint32_t* spvReflectGetCode(const SpvReflectShaderModule* p_module) +{ + if (IsNull(p_module)) { + return NULL; + } + + return p_module->_internal->spirv_code; +} + +const SpvReflectEntryPoint* spvReflectGetEntryPoint( + const SpvReflectShaderModule* p_module, + const char* entry_point +) { + if (IsNull(p_module) || IsNull(entry_point)) { + return NULL; + } + + for (uint32_t i = 0; i < p_module->entry_point_count; ++i) { + if (strcmp(p_module->entry_points[i].name, entry_point) == 0) { + return &p_module->entry_points[i]; + } + } + return NULL; +} + +SpvReflectResult spvReflectEnumerateDescriptorBindings( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectDescriptorBinding** pp_bindings +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + if (IsNotNull(pp_bindings)) { + if (*p_count != p_module->descriptor_binding_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + for (uint32_t index = 0; index < *p_count; ++index) { + SpvReflectDescriptorBinding* p_bindings = (SpvReflectDescriptorBinding*)&p_module->descriptor_bindings[index]; + pp_bindings[index] = p_bindings; + } + } + else { + *p_count = p_module->descriptor_binding_count; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectEnumerateEntryPointDescriptorBindings( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectDescriptorBinding** pp_bindings +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + const SpvReflectEntryPoint* p_entry = + spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + return SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + + uint32_t count = 0; + for (uint32_t i = 0; i < p_module->descriptor_binding_count; ++i) { + bool found = SearchSortedUint32( + p_entry->used_uniforms, + p_entry->used_uniform_count, + p_module->descriptor_bindings[i].spirv_id); + if (found) { + if (IsNotNull(pp_bindings)) { + if (count >= *p_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + pp_bindings[count++] = (SpvReflectDescriptorBinding*)&p_module->descriptor_bindings[i]; + } else { + ++count; + } + } + } + if (IsNotNull(pp_bindings)) { + if (count != *p_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + } else { + *p_count = count; + } + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectEnumerateDescriptorSets( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectDescriptorSet** pp_sets +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + if (IsNotNull(pp_sets)) { + if (*p_count != p_module->descriptor_set_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + for (uint32_t index = 0; index < *p_count; ++index) { + SpvReflectDescriptorSet* p_set = (SpvReflectDescriptorSet*)&p_module->descriptor_sets[index]; + pp_sets[index] = p_set; + } + } + else { + *p_count = p_module->descriptor_set_count; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectEnumerateEntryPointDescriptorSets( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectDescriptorSet** pp_sets +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + const SpvReflectEntryPoint* p_entry = + spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + return SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + + if (IsNotNull(pp_sets)) { + if (*p_count != p_entry->descriptor_set_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + for (uint32_t index = 0; index < *p_count; ++index) { + SpvReflectDescriptorSet* p_set = (SpvReflectDescriptorSet*)&p_entry->descriptor_sets[index]; + pp_sets[index] = p_set; + } + } + else { + *p_count = p_entry->descriptor_set_count; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectEnumerateInterfaceVariables( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + if (IsNotNull(pp_variables)) { + if (*p_count != p_module->interface_variable_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + for (uint32_t index = 0; index < *p_count; ++index) { + SpvReflectInterfaceVariable* p_var = &p_module->interface_variables[index]; + pp_variables[index] = p_var; + } + } + else { + *p_count = p_module->interface_variable_count; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectEnumerateEntryPointInterfaceVariables( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + const SpvReflectEntryPoint* p_entry = + spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + return SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + + if (IsNotNull(pp_variables)) { + if (*p_count != p_entry->interface_variable_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + for (uint32_t index = 0; index < *p_count; ++index) { + SpvReflectInterfaceVariable* p_var = &p_entry->interface_variables[index]; + pp_variables[index] = p_var; + } + } + else { + *p_count = p_entry->interface_variable_count; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectEnumerateInputVariables( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + if (IsNotNull(pp_variables)) { + if (*p_count != p_module->input_variable_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + for (uint32_t index = 0; index < *p_count; ++index) { + SpvReflectInterfaceVariable* p_var = p_module->input_variables[index]; + pp_variables[index] = p_var; + } + } + else { + *p_count = p_module->input_variable_count; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectEnumerateEntryPointInputVariables( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + const SpvReflectEntryPoint* p_entry = + spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + return SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + + if (IsNotNull(pp_variables)) { + if (*p_count != p_entry->input_variable_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + for (uint32_t index = 0; index < *p_count; ++index) { + SpvReflectInterfaceVariable* p_var = p_entry->input_variables[index]; + pp_variables[index] = p_var; + } + } + else { + *p_count = p_entry->input_variable_count; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectEnumerateOutputVariables( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + if (IsNotNull(pp_variables)) { + if (*p_count != p_module->output_variable_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + for (uint32_t index = 0; index < *p_count; ++index) { + SpvReflectInterfaceVariable* p_var = p_module->output_variables[index]; + pp_variables[index] = p_var; + } + } + else { + *p_count = p_module->output_variable_count; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectEnumerateEntryPointOutputVariables( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + const SpvReflectEntryPoint* p_entry = + spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + return SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + + if (IsNotNull(pp_variables)) { + if (*p_count != p_entry->output_variable_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + for (uint32_t index = 0; index < *p_count; ++index) { + SpvReflectInterfaceVariable* p_var = p_entry->output_variables[index]; + pp_variables[index] = p_var; + } + } + else { + *p_count = p_entry->output_variable_count; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectEnumeratePushConstantBlocks( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectBlockVariable** pp_blocks +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + if (pp_blocks != NULL) { + if (*p_count != p_module->push_constant_block_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + + for (uint32_t index = 0; index < *p_count; ++index) { + SpvReflectBlockVariable* p_push_constant_blocks = (SpvReflectBlockVariable*)&p_module->push_constant_blocks[index]; + pp_blocks[index] = p_push_constant_blocks; + } + } + else { + *p_count = p_module->push_constant_block_count; + } + + return SPV_REFLECT_RESULT_SUCCESS; +} +SpvReflectResult spvReflectEnumeratePushConstants( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectBlockVariable** pp_blocks +) +{ + return spvReflectEnumeratePushConstantBlocks(p_module, p_count, pp_blocks); +} + +SpvReflectResult spvReflectEnumerateEntryPointPushConstantBlocks( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectBlockVariable** pp_blocks +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_count)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + + const SpvReflectEntryPoint* p_entry = + spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + return SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + + uint32_t count = 0; + for (uint32_t i = 0; i < p_module->push_constant_block_count; ++i) { + bool found = SearchSortedUint32(p_entry->used_push_constants, + p_entry->used_push_constant_count, + p_module->push_constant_blocks[i].spirv_id); + if (found) { + if (IsNotNull(pp_blocks)) { + if (count >= *p_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + pp_blocks[count++] = (SpvReflectBlockVariable*)&p_module->push_constant_blocks[i]; + } else { + ++count; + } + } + } + if (IsNotNull(pp_blocks)) { + if (count != *p_count) { + return SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH; + } + } else { + *p_count = count; + } + return SPV_REFLECT_RESULT_SUCCESS; +} + +const SpvReflectDescriptorBinding* spvReflectGetDescriptorBinding( + const SpvReflectShaderModule* p_module, + uint32_t binding_number, + uint32_t set_number, + SpvReflectResult* p_result +) +{ + const SpvReflectDescriptorBinding* p_descriptor = NULL; + if (IsNotNull(p_module)) { + for (uint32_t index = 0; index < p_module->descriptor_binding_count; ++index) { + const SpvReflectDescriptorBinding* p_potential = &p_module->descriptor_bindings[index]; + if ((p_potential->binding == binding_number) && (p_potential->set == set_number)) { + p_descriptor = p_potential; + break; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_descriptor) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_descriptor; +} + +const SpvReflectDescriptorBinding* spvReflectGetEntryPointDescriptorBinding( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t binding_number, + uint32_t set_number, + SpvReflectResult* p_result +) +{ + const SpvReflectEntryPoint* p_entry = + spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + const SpvReflectDescriptorBinding* p_descriptor = NULL; + if (IsNotNull(p_module)) { + for (uint32_t index = 0; index < p_module->descriptor_binding_count; ++index) { + const SpvReflectDescriptorBinding* p_potential = &p_module->descriptor_bindings[index]; + bool found = SearchSortedUint32( + p_entry->used_uniforms, + p_entry->used_uniform_count, + p_potential->spirv_id); + if ((p_potential->binding == binding_number) && (p_potential->set == set_number) && found) { + p_descriptor = p_potential; + break; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_descriptor) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_descriptor; +} + +const SpvReflectDescriptorSet* spvReflectGetDescriptorSet( + const SpvReflectShaderModule* p_module, + uint32_t set_number, + SpvReflectResult* p_result +) +{ + const SpvReflectDescriptorSet* p_set = NULL; + if (IsNotNull(p_module)) { + for (uint32_t index = 0; index < p_module->descriptor_set_count; ++index) { + const SpvReflectDescriptorSet* p_potential = &p_module->descriptor_sets[index]; + if (p_potential->set == set_number) { + p_set = p_potential; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_set) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_set; +} + +const SpvReflectDescriptorSet* spvReflectGetEntryPointDescriptorSet( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t set_number, + SpvReflectResult* p_result) +{ + const SpvReflectDescriptorSet* p_set = NULL; + if (IsNotNull(p_module)) { + const SpvReflectEntryPoint* p_entry = spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + for (uint32_t index = 0; index < p_entry->descriptor_set_count; ++index) { + const SpvReflectDescriptorSet* p_potential = &p_entry->descriptor_sets[index]; + if (p_potential->set == set_number) { + p_set = p_potential; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_set) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_set; +} + + +const SpvReflectInterfaceVariable* spvReflectGetInputVariableByLocation( + const SpvReflectShaderModule* p_module, + uint32_t location, + SpvReflectResult* p_result +) +{ + if (location == INVALID_VALUE) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + const SpvReflectInterfaceVariable* p_var = NULL; + if (IsNotNull(p_module)) { + for (uint32_t index = 0; index < p_module->input_variable_count; ++index) { + const SpvReflectInterfaceVariable* p_potential = p_module->input_variables[index]; + if (p_potential->location == location) { + p_var = p_potential; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_var) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_var; +} +const SpvReflectInterfaceVariable* spvReflectGetInputVariable( + const SpvReflectShaderModule* p_module, + uint32_t location, + SpvReflectResult* p_result +) +{ + return spvReflectGetInputVariableByLocation(p_module, location, p_result); +} + +const SpvReflectInterfaceVariable* spvReflectGetEntryPointInputVariableByLocation( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t location, + SpvReflectResult* p_result +) +{ + if (location == INVALID_VALUE) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + + const SpvReflectInterfaceVariable* p_var = NULL; + if (IsNotNull(p_module)) { + const SpvReflectEntryPoint* p_entry = + spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + for (uint32_t index = 0; index < p_entry->input_variable_count; ++index) { + const SpvReflectInterfaceVariable* p_potential = p_entry->input_variables[index]; + if (p_potential->location == location) { + p_var = p_potential; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_var) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_var; +} + +const SpvReflectInterfaceVariable* spvReflectGetInputVariableBySemantic( + const SpvReflectShaderModule* p_module, + const char* semantic, + SpvReflectResult* p_result +) +{ + if (IsNull(semantic)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + return NULL; + } + if (semantic[0] == '\0') { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + const SpvReflectInterfaceVariable* p_var = NULL; + if (IsNotNull(p_module)) { + for (uint32_t index = 0; index < p_module->input_variable_count; ++index) { + const SpvReflectInterfaceVariable* p_potential = p_module->input_variables[index]; + if (p_potential->semantic != NULL && strcmp(p_potential->semantic, semantic) == 0) { + p_var = p_potential; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_var) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_var; +} + +const SpvReflectInterfaceVariable* spvReflectGetEntryPointInputVariableBySemantic( + const SpvReflectShaderModule* p_module, + const char* entry_point, + const char* semantic, + SpvReflectResult* p_result +) +{ + if (IsNull(semantic)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + return NULL; + } + if (semantic[0] == '\0') { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + const SpvReflectInterfaceVariable* p_var = NULL; + if (IsNotNull(p_module)) { + const SpvReflectEntryPoint* p_entry = spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + for (uint32_t index = 0; index < p_entry->input_variable_count; ++index) { + const SpvReflectInterfaceVariable* p_potential = p_entry->input_variables[index]; + if (p_potential->semantic != NULL && strcmp(p_potential->semantic, semantic) == 0) { + p_var = p_potential; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_var) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_var; +} + +const SpvReflectInterfaceVariable* spvReflectGetOutputVariableByLocation( + const SpvReflectShaderModule* p_module, + uint32_t location, + SpvReflectResult* p_result +) +{ + if (location == INVALID_VALUE) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + const SpvReflectInterfaceVariable* p_var = NULL; + if (IsNotNull(p_module)) { + for (uint32_t index = 0; index < p_module->output_variable_count; ++index) { + const SpvReflectInterfaceVariable* p_potential = p_module->output_variables[index]; + if (p_potential->location == location) { + p_var = p_potential; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_var) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_var; +} +const SpvReflectInterfaceVariable* spvReflectGetOutputVariable( + const SpvReflectShaderModule* p_module, + uint32_t location, + SpvReflectResult* p_result +) +{ + return spvReflectGetOutputVariableByLocation(p_module, location, p_result); +} + +const SpvReflectInterfaceVariable* spvReflectGetEntryPointOutputVariableByLocation( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t location, + SpvReflectResult* p_result +) +{ + if (location == INVALID_VALUE) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + + const SpvReflectInterfaceVariable* p_var = NULL; + if (IsNotNull(p_module)) { + const SpvReflectEntryPoint* p_entry = spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + for (uint32_t index = 0; index < p_entry->output_variable_count; ++index) { + const SpvReflectInterfaceVariable* p_potential = p_entry->output_variables[index]; + if (p_potential->location == location) { + p_var = p_potential; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_var) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_var; +} + +const SpvReflectInterfaceVariable* spvReflectGetOutputVariableBySemantic( + const SpvReflectShaderModule* p_module, + const char* semantic, + SpvReflectResult* p_result +) +{ + if (IsNull(semantic)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + return NULL; + } + if (semantic[0] == '\0') { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + const SpvReflectInterfaceVariable* p_var = NULL; + if (IsNotNull(p_module)) { + for (uint32_t index = 0; index < p_module->output_variable_count; ++index) { + const SpvReflectInterfaceVariable* p_potential = p_module->output_variables[index]; + if (p_potential->semantic != NULL && strcmp(p_potential->semantic, semantic) == 0) { + p_var = p_potential; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_var) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_var; +} + +const SpvReflectInterfaceVariable* spvReflectGetEntryPointOutputVariableBySemantic( + const SpvReflectShaderModule* p_module, + const char* entry_point, + const char* semantic, + SpvReflectResult* p_result) +{ + if (IsNull(semantic)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + return NULL; + } + if (semantic[0] == '\0') { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + const SpvReflectInterfaceVariable* p_var = NULL; + if (IsNotNull(p_module)) { + const SpvReflectEntryPoint* p_entry = spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + for (uint32_t index = 0; index < p_entry->output_variable_count; ++index) { + const SpvReflectInterfaceVariable* p_potential = p_entry->output_variables[index]; + if (p_potential->semantic != NULL && strcmp(p_potential->semantic, semantic) == 0) { + p_var = p_potential; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_var) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_var; +} + +const SpvReflectBlockVariable* spvReflectGetPushConstantBlock( + const SpvReflectShaderModule* p_module, + uint32_t index, + SpvReflectResult* p_result +) +{ + const SpvReflectBlockVariable* p_push_constant = NULL; + if (IsNotNull(p_module)) { + if (index < p_module->push_constant_block_count) { + p_push_constant = &p_module->push_constant_blocks[index]; + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_push_constant) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_push_constant; +} +const SpvReflectBlockVariable* spvReflectGetPushConstant( + const SpvReflectShaderModule* p_module, + uint32_t index, + SpvReflectResult* p_result +) +{ + return spvReflectGetPushConstantBlock(p_module, index, p_result); +} + +const SpvReflectBlockVariable* spvReflectGetEntryPointPushConstantBlock( + const SpvReflectShaderModule* p_module, + const char* entry_point, + SpvReflectResult* p_result) +{ + const SpvReflectBlockVariable* p_push_constant = NULL; + if (IsNotNull(p_module)) { + const SpvReflectEntryPoint* p_entry = + spvReflectGetEntryPoint(p_module, entry_point); + if (IsNull(p_entry)) { + if (IsNotNull(p_result)) { + *p_result = SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; + } + return NULL; + } + for (uint32_t i = 0; i < p_module->push_constant_block_count; ++i) { + bool found = SearchSortedUint32( + p_entry->used_push_constants, + p_entry->used_push_constant_count, + p_module->push_constant_blocks[i].spirv_id); + if (found) { + p_push_constant = &p_module->push_constant_blocks[i]; + break; + } + } + } + if (IsNotNull(p_result)) { + *p_result = IsNotNull(p_push_constant) + ? SPV_REFLECT_RESULT_SUCCESS + : (IsNull(p_module) ? SPV_REFLECT_RESULT_ERROR_NULL_POINTER + : SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND); + } + return p_push_constant; +} + +SpvReflectResult spvReflectChangeDescriptorBindingNumbers( + SpvReflectShaderModule* p_module, + const SpvReflectDescriptorBinding* p_binding, + uint32_t new_binding_number, + uint32_t new_set_binding +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_binding)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + + SpvReflectDescriptorBinding* p_target_descriptor = NULL; + for (uint32_t index = 0; index < p_module->descriptor_binding_count; ++index) { + if(&p_module->descriptor_bindings[index] == p_binding) { + p_target_descriptor = &p_module->descriptor_bindings[index]; + break; + } + } + + if (IsNotNull(p_target_descriptor)) { + if (p_target_descriptor->word_offset.binding > (p_module->_internal->spirv_word_count - 1)) { + return SPV_REFLECT_RESULT_ERROR_RANGE_EXCEEDED; + } + // Binding number + if (new_binding_number != (uint32_t)SPV_REFLECT_BINDING_NUMBER_DONT_CHANGE) { + uint32_t* p_code = p_module->_internal->spirv_code + p_target_descriptor->word_offset.binding; + *p_code = new_binding_number; + p_target_descriptor->binding = new_binding_number; + } + // Set number + if (new_set_binding != (uint32_t)SPV_REFLECT_SET_NUMBER_DONT_CHANGE) { + uint32_t* p_code = p_module->_internal->spirv_code + p_target_descriptor->word_offset.set; + *p_code = new_set_binding; + p_target_descriptor->set = new_set_binding; + } + } + + SpvReflectResult result = SPV_REFLECT_RESULT_SUCCESS; + if (new_set_binding != (uint32_t)SPV_REFLECT_SET_NUMBER_DONT_CHANGE) { + result = SynchronizeDescriptorSets(p_module); + } + return result; +} +SpvReflectResult spvReflectChangeDescriptorBindingNumber( + SpvReflectShaderModule* p_module, + const SpvReflectDescriptorBinding* p_descriptor_binding, + uint32_t new_binding_number, + uint32_t optional_new_set_number +) +{ + return spvReflectChangeDescriptorBindingNumbers( + p_module,p_descriptor_binding, + new_binding_number, + optional_new_set_number); +} + +SpvReflectResult spvReflectChangeDescriptorSetNumber( + SpvReflectShaderModule* p_module, + const SpvReflectDescriptorSet* p_set, + uint32_t new_set_number +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_set)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + SpvReflectDescriptorSet* p_target_set = NULL; + for (uint32_t index = 0; index < SPV_REFLECT_MAX_DESCRIPTOR_SETS; ++index) { + // The descriptor sets for specific entry points might not be in this set, + // so just match on set index. + if (p_module->descriptor_sets[index].set == p_set->set) { + p_target_set = (SpvReflectDescriptorSet*)p_set; + break; + } + } + + SpvReflectResult result = SPV_REFLECT_RESULT_SUCCESS; + if (IsNotNull(p_target_set) && new_set_number != (uint32_t)SPV_REFLECT_SET_NUMBER_DONT_CHANGE) { + for (uint32_t index = 0; index < p_target_set->binding_count; ++index) { + SpvReflectDescriptorBinding* p_descriptor = p_target_set->bindings[index]; + if (p_descriptor->word_offset.set > (p_module->_internal->spirv_word_count - 1)) { + return SPV_REFLECT_RESULT_ERROR_RANGE_EXCEEDED; + } + + uint32_t* p_code = p_module->_internal->spirv_code + p_descriptor->word_offset.set; + *p_code = new_set_number; + p_descriptor->set = new_set_number; + } + + result = SynchronizeDescriptorSets(p_module); + } + + return result; +} + +static SpvReflectResult ChangeVariableLocation( + SpvReflectShaderModule* p_module, + SpvReflectInterfaceVariable* p_variable, + uint32_t new_location +) +{ + if (p_variable->word_offset.location > (p_module->_internal->spirv_word_count - 1)) { + return SPV_REFLECT_RESULT_ERROR_RANGE_EXCEEDED; + } + uint32_t* p_code = p_module->_internal->spirv_code + p_variable->word_offset.location; + *p_code = new_location; + p_variable->location = new_location; + return SPV_REFLECT_RESULT_SUCCESS; +} + +SpvReflectResult spvReflectChangeInputVariableLocation( + SpvReflectShaderModule* p_module, + const SpvReflectInterfaceVariable* p_input_variable, + uint32_t new_location +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_input_variable)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + for (uint32_t index = 0; index < p_module->input_variable_count; ++index) { + if(p_module->input_variables[index] == p_input_variable) { + return ChangeVariableLocation(p_module, p_module->input_variables[index], new_location); + } + } + return SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; +} + +SpvReflectResult spvReflectChangeOutputVariableLocation( + SpvReflectShaderModule* p_module, + const SpvReflectInterfaceVariable* p_output_variable, + uint32_t new_location +) +{ + if (IsNull(p_module)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + if (IsNull(p_output_variable)) { + return SPV_REFLECT_RESULT_ERROR_NULL_POINTER; + } + for (uint32_t index = 0; index < p_module->output_variable_count; ++index) { + if(p_module->output_variables[index] == p_output_variable) { + return ChangeVariableLocation(p_module, p_module->output_variables[index], new_location); + } + } + return SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND; +} + +const char* spvReflectSourceLanguage(SpvSourceLanguage source_lang) +{ + switch (source_lang) { + case SpvSourceLanguageUnknown : return "Unknown"; + case SpvSourceLanguageESSL : return "ESSL"; + case SpvSourceLanguageGLSL : return "GLSL"; + case SpvSourceLanguageOpenCL_C : return "OpenCL_C"; + case SpvSourceLanguageOpenCL_CPP : return "OpenCL_CPP"; + case SpvSourceLanguageHLSL : return "HLSL"; + case SpvSourceLanguageCPP_for_OpenCL : return "CPP_for_OpenCL"; + case SpvSourceLanguageMax: + break; + } + return ""; +} + +const char* spvReflectBlockVariableTypeName( + const SpvReflectBlockVariable* p_var +) +{ + if (p_var == NULL) { + return NULL; + } + return p_var->type_description->type_name; +} diff --git a/SHADE_Engine/src/Graphics/Shaders/spirv-reflect/spirv_reflect.h b/SHADE_Engine/src/Graphics/Shaders/spirv-reflect/spirv_reflect.h new file mode 100644 index 00000000..02b81613 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Shaders/spirv-reflect/spirv_reflect.h @@ -0,0 +1,2282 @@ +/* + Copyright 2017-2022 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + +VERSION HISTORY + + 1.0 (2018-03-27) Initial public release + +*/ + +/*! + + @file spirv_reflect.h + +*/ +#ifndef SPIRV_REFLECT_H +#define SPIRV_REFLECT_H + +#if defined(SPIRV_REFLECT_USE_SYSTEM_SPIRV_H) +#include +#else +#include "./include/spirv/unified1/spirv.h" +#endif + + +#include +#include + +#ifdef _MSC_VER + #define SPV_REFLECT_DEPRECATED(msg_str) __declspec(deprecated("This symbol is deprecated. Details: " msg_str)) +#elif defined(__clang__) + #define SPV_REFLECT_DEPRECATED(msg_str) __attribute__((deprecated(msg_str))) +#elif defined(__GNUC__) + #if GCC_VERSION >= 40500 + #define SPV_REFLECT_DEPRECATED(msg_str) __attribute__((deprecated(msg_str))) + #else + #define SPV_REFLECT_DEPRECATED(msg_str) __attribute__((deprecated)) + #endif +#else + #define SPV_REFLECT_DEPRECATED(msg_str) +#endif + +/*! @enum SpvReflectResult + +*/ +typedef enum SpvReflectResult { + SPV_REFLECT_RESULT_SUCCESS, + SPV_REFLECT_RESULT_NOT_READY, + SPV_REFLECT_RESULT_ERROR_PARSE_FAILED, + SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED, + SPV_REFLECT_RESULT_ERROR_RANGE_EXCEEDED, + SPV_REFLECT_RESULT_ERROR_NULL_POINTER, + SPV_REFLECT_RESULT_ERROR_INTERNAL_ERROR, + SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH, + SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND, + SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_CODE_SIZE, + SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_MAGIC_NUMBER, + SPV_REFLECT_RESULT_ERROR_SPIRV_UNEXPECTED_EOF, + SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ID_REFERENCE, + SPV_REFLECT_RESULT_ERROR_SPIRV_SET_NUMBER_OVERFLOW, + SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_STORAGE_CLASS, + SPV_REFLECT_RESULT_ERROR_SPIRV_RECURSION, + SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_INSTRUCTION, + SPV_REFLECT_RESULT_ERROR_SPIRV_UNEXPECTED_BLOCK_DATA, + SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_BLOCK_MEMBER_REFERENCE, + SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_ENTRY_POINT, + SPV_REFLECT_RESULT_ERROR_SPIRV_INVALID_EXECUTION_MODE, +} SpvReflectResult; + +/*! @enum SpvReflectModuleFlagBits + +SPV_REFLECT_MODULE_FLAG_NO_COPY - Disables copying of SPIR-V code + when a SPIRV-Reflect shader module is created. It is the + responsibility of the calling program to ensure that the pointer + remains valid and the memory it's pointing to is not freed while + SPIRV-Reflect operations are taking place. Freeing the backing + memory will cause undefined behavior or most likely a crash. + This is flag is intended for cases where the memory overhead of + storing the copied SPIR-V is undesirable. + +*/ +typedef enum SpvReflectModuleFlagBits { + SPV_REFLECT_MODULE_FLAG_NONE = 0x00000000, + SPV_REFLECT_MODULE_FLAG_NO_COPY = 0x00000001, +} SpvReflectModuleFlagBits; + +typedef uint32_t SpvReflectModuleFlags; + +/*! @enum SpvReflectTypeFlagBits + +*/ +typedef enum SpvReflectTypeFlagBits { + SPV_REFLECT_TYPE_FLAG_UNDEFINED = 0x00000000, + SPV_REFLECT_TYPE_FLAG_VOID = 0x00000001, + SPV_REFLECT_TYPE_FLAG_BOOL = 0x00000002, + SPV_REFLECT_TYPE_FLAG_INT = 0x00000004, + SPV_REFLECT_TYPE_FLAG_FLOAT = 0x00000008, + SPV_REFLECT_TYPE_FLAG_VECTOR = 0x00000100, + SPV_REFLECT_TYPE_FLAG_MATRIX = 0x00000200, + SPV_REFLECT_TYPE_FLAG_EXTERNAL_IMAGE = 0x00010000, + SPV_REFLECT_TYPE_FLAG_EXTERNAL_SAMPLER = 0x00020000, + SPV_REFLECT_TYPE_FLAG_EXTERNAL_SAMPLED_IMAGE = 0x00040000, + SPV_REFLECT_TYPE_FLAG_EXTERNAL_BLOCK = 0x00080000, + SPV_REFLECT_TYPE_FLAG_EXTERNAL_ACCELERATION_STRUCTURE = 0x00100000, + SPV_REFLECT_TYPE_FLAG_EXTERNAL_MASK = 0x00FF0000, + SPV_REFLECT_TYPE_FLAG_STRUCT = 0x10000000, + SPV_REFLECT_TYPE_FLAG_ARRAY = 0x20000000, +} SpvReflectTypeFlagBits; + +typedef uint32_t SpvReflectTypeFlags; + +/*! @enum SpvReflectDecorationBits + +NOTE: HLSL row_major and column_major decorations are reversed + in SPIR-V. Meaning that matrices declrations with row_major + will get reflected as column_major and vice versa. The + row and column decorations get appied during the compilation. + SPIRV-Reflect reads the data as is and does not make any + attempt to correct it to match what's in the source. + +*/ +typedef enum SpvReflectDecorationFlagBits { + SPV_REFLECT_DECORATION_NONE = 0x00000000, + SPV_REFLECT_DECORATION_BLOCK = 0x00000001, + SPV_REFLECT_DECORATION_BUFFER_BLOCK = 0x00000002, + SPV_REFLECT_DECORATION_ROW_MAJOR = 0x00000004, + SPV_REFLECT_DECORATION_COLUMN_MAJOR = 0x00000008, + SPV_REFLECT_DECORATION_BUILT_IN = 0x00000010, + SPV_REFLECT_DECORATION_NOPERSPECTIVE = 0x00000020, + SPV_REFLECT_DECORATION_FLAT = 0x00000040, + SPV_REFLECT_DECORATION_NON_WRITABLE = 0x00000080, + SPV_REFLECT_DECORATION_RELAXED_PRECISION = 0x00000100, + SPV_REFLECT_DECORATION_NON_READABLE = 0x00000200, +} SpvReflectDecorationFlagBits; + +typedef uint32_t SpvReflectDecorationFlags; + +/*! @enum SpvReflectResourceType + +*/ +typedef enum SpvReflectResourceType { + SPV_REFLECT_RESOURCE_FLAG_UNDEFINED = 0x00000000, + SPV_REFLECT_RESOURCE_FLAG_SAMPLER = 0x00000001, + SPV_REFLECT_RESOURCE_FLAG_CBV = 0x00000002, + SPV_REFLECT_RESOURCE_FLAG_SRV = 0x00000004, + SPV_REFLECT_RESOURCE_FLAG_UAV = 0x00000008, +} SpvReflectResourceType; + +/*! @enum SpvReflectFormat + +*/ +typedef enum SpvReflectFormat { + SPV_REFLECT_FORMAT_UNDEFINED = 0, // = VK_FORMAT_UNDEFINED + SPV_REFLECT_FORMAT_R32_UINT = 98, // = VK_FORMAT_R32_UINT + SPV_REFLECT_FORMAT_R32_SINT = 99, // = VK_FORMAT_R32_SINT + SPV_REFLECT_FORMAT_R32_SFLOAT = 100, // = VK_FORMAT_R32_SFLOAT + SPV_REFLECT_FORMAT_R32G32_UINT = 101, // = VK_FORMAT_R32G32_UINT + SPV_REFLECT_FORMAT_R32G32_SINT = 102, // = VK_FORMAT_R32G32_SINT + SPV_REFLECT_FORMAT_R32G32_SFLOAT = 103, // = VK_FORMAT_R32G32_SFLOAT + SPV_REFLECT_FORMAT_R32G32B32_UINT = 104, // = VK_FORMAT_R32G32B32_UINT + SPV_REFLECT_FORMAT_R32G32B32_SINT = 105, // = VK_FORMAT_R32G32B32_SINT + SPV_REFLECT_FORMAT_R32G32B32_SFLOAT = 106, // = VK_FORMAT_R32G32B32_SFLOAT + SPV_REFLECT_FORMAT_R32G32B32A32_UINT = 107, // = VK_FORMAT_R32G32B32A32_UINT + SPV_REFLECT_FORMAT_R32G32B32A32_SINT = 108, // = VK_FORMAT_R32G32B32A32_SINT + SPV_REFLECT_FORMAT_R32G32B32A32_SFLOAT = 109, // = VK_FORMAT_R32G32B32A32_SFLOAT + SPV_REFLECT_FORMAT_R64_UINT = 110, // = VK_FORMAT_R64_UINT + SPV_REFLECT_FORMAT_R64_SINT = 111, // = VK_FORMAT_R64_SINT + SPV_REFLECT_FORMAT_R64_SFLOAT = 112, // = VK_FORMAT_R64_SFLOAT + SPV_REFLECT_FORMAT_R64G64_UINT = 113, // = VK_FORMAT_R64G64_UINT + SPV_REFLECT_FORMAT_R64G64_SINT = 114, // = VK_FORMAT_R64G64_SINT + SPV_REFLECT_FORMAT_R64G64_SFLOAT = 115, // = VK_FORMAT_R64G64_SFLOAT + SPV_REFLECT_FORMAT_R64G64B64_UINT = 116, // = VK_FORMAT_R64G64B64_UINT + SPV_REFLECT_FORMAT_R64G64B64_SINT = 117, // = VK_FORMAT_R64G64B64_SINT + SPV_REFLECT_FORMAT_R64G64B64_SFLOAT = 118, // = VK_FORMAT_R64G64B64_SFLOAT + SPV_REFLECT_FORMAT_R64G64B64A64_UINT = 119, // = VK_FORMAT_R64G64B64A64_UINT + SPV_REFLECT_FORMAT_R64G64B64A64_SINT = 120, // = VK_FORMAT_R64G64B64A64_SINT + SPV_REFLECT_FORMAT_R64G64B64A64_SFLOAT = 121, // = VK_FORMAT_R64G64B64A64_SFLOAT +} SpvReflectFormat; + +/*! @enum SpvReflectVariableFlagBits + +*/ +enum SpvReflectVariableFlagBits{ + SPV_REFLECT_VARIABLE_FLAGS_NONE = 0x00000000, + SPV_REFLECT_VARIABLE_FLAGS_UNUSED = 0x00000001, +}; + +typedef uint32_t SpvReflectVariableFlags; + +/*! @enum SpvReflectDescriptorType + +*/ +typedef enum SpvReflectDescriptorType { + SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLER = 0, // = VK_DESCRIPTOR_TYPE_SAMPLER + SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER = 1, // = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER + SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE = 2, // = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE + SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_IMAGE = 3, // = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE + SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER = 4, // = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER + SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER = 5, // = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER + SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER = 6, // = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER + SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER = 7, // = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER + SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC = 8, // = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC + SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC = 9, // = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC + SPV_REFLECT_DESCRIPTOR_TYPE_INPUT_ATTACHMENT = 10, // = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT + SPV_REFLECT_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR = 1000150000 // = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR +} SpvReflectDescriptorType; + +/*! @enum SpvReflectShaderStageFlagBits + +*/ +typedef enum SpvReflectShaderStageFlagBits { + SPV_REFLECT_SHADER_STAGE_VERTEX_BIT = 0x00000001, // = VK_SHADER_STAGE_VERTEX_BIT + SPV_REFLECT_SHADER_STAGE_TESSELLATION_CONTROL_BIT = 0x00000002, // = VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT + SPV_REFLECT_SHADER_STAGE_TESSELLATION_EVALUATION_BIT = 0x00000004, // = VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT + SPV_REFLECT_SHADER_STAGE_GEOMETRY_BIT = 0x00000008, // = VK_SHADER_STAGE_GEOMETRY_BIT + SPV_REFLECT_SHADER_STAGE_FRAGMENT_BIT = 0x00000010, // = VK_SHADER_STAGE_FRAGMENT_BIT + SPV_REFLECT_SHADER_STAGE_COMPUTE_BIT = 0x00000020, // = VK_SHADER_STAGE_COMPUTE_BIT + SPV_REFLECT_SHADER_STAGE_TASK_BIT_NV = 0x00000040, // = VK_SHADER_STAGE_TASK_BIT_NV + SPV_REFLECT_SHADER_STAGE_MESH_BIT_NV = 0x00000080, // = VK_SHADER_STAGE_MESH_BIT_NV + SPV_REFLECT_SHADER_STAGE_RAYGEN_BIT_KHR = 0x00000100, // = VK_SHADER_STAGE_RAYGEN_BIT_KHR + SPV_REFLECT_SHADER_STAGE_ANY_HIT_BIT_KHR = 0x00000200, // = VK_SHADER_STAGE_ANY_HIT_BIT_KHR + SPV_REFLECT_SHADER_STAGE_CLOSEST_HIT_BIT_KHR = 0x00000400, // = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR + SPV_REFLECT_SHADER_STAGE_MISS_BIT_KHR = 0x00000800, // = VK_SHADER_STAGE_MISS_BIT_KHR + SPV_REFLECT_SHADER_STAGE_INTERSECTION_BIT_KHR = 0x00001000, // = VK_SHADER_STAGE_INTERSECTION_BIT_KHR + SPV_REFLECT_SHADER_STAGE_CALLABLE_BIT_KHR = 0x00002000, // = VK_SHADER_STAGE_CALLABLE_BIT_KHR + +} SpvReflectShaderStageFlagBits; + +/*! @enum SpvReflectGenerator + +*/ +typedef enum SpvReflectGenerator { + SPV_REFLECT_GENERATOR_KHRONOS_LLVM_SPIRV_TRANSLATOR = 6, + SPV_REFLECT_GENERATOR_KHRONOS_SPIRV_TOOLS_ASSEMBLER = 7, + SPV_REFLECT_GENERATOR_KHRONOS_GLSLANG_REFERENCE_FRONT_END = 8, + SPV_REFLECT_GENERATOR_GOOGLE_SHADERC_OVER_GLSLANG = 13, + SPV_REFLECT_GENERATOR_GOOGLE_SPIREGG = 14, + SPV_REFLECT_GENERATOR_GOOGLE_RSPIRV = 15, + SPV_REFLECT_GENERATOR_X_LEGEND_MESA_MESAIR_SPIRV_TRANSLATOR = 16, + SPV_REFLECT_GENERATOR_KHRONOS_SPIRV_TOOLS_LINKER = 17, + SPV_REFLECT_GENERATOR_WINE_VKD3D_SHADER_COMPILER = 18, + SPV_REFLECT_GENERATOR_CLAY_CLAY_SHADER_COMPILER = 19, +} SpvReflectGenerator; + +enum { + SPV_REFLECT_MAX_ARRAY_DIMS = 32, + SPV_REFLECT_MAX_DESCRIPTOR_SETS = 64, +}; + +enum { + SPV_REFLECT_BINDING_NUMBER_DONT_CHANGE = ~0, + SPV_REFLECT_SET_NUMBER_DONT_CHANGE = ~0 +}; + +typedef struct SpvReflectNumericTraits { + struct Scalar { + uint32_t width; + uint32_t signedness; + } scalar; + + struct Vector { + uint32_t component_count; + } vector; + + struct Matrix { + uint32_t column_count; + uint32_t row_count; + uint32_t stride; // Measured in bytes + } matrix; +} SpvReflectNumericTraits; + +typedef struct SpvReflectImageTraits { + SpvDim dim; + uint32_t depth; + uint32_t arrayed; + uint32_t ms; // 0: single-sampled; 1: multisampled + uint32_t sampled; + SpvImageFormat image_format; +} SpvReflectImageTraits; + +typedef struct SpvReflectArrayTraits { + uint32_t dims_count; + // Each entry is: 0xFFFFFFFF for a specialization constant dimension, + // 0 for a runtime array dimension, and the array length otherwise. + uint32_t dims[SPV_REFLECT_MAX_ARRAY_DIMS]; + // Stores Ids for dimensions that are specialization constants + uint32_t spec_constant_op_ids[SPV_REFLECT_MAX_ARRAY_DIMS]; + uint32_t stride; // Measured in bytes +} SpvReflectArrayTraits; + +typedef struct SpvReflectBindingArrayTraits { + uint32_t dims_count; + uint32_t dims[SPV_REFLECT_MAX_ARRAY_DIMS]; +} SpvReflectBindingArrayTraits; + +/*! @struct SpvReflectTypeDescription + +*/ +typedef struct SpvReflectTypeDescription { + uint32_t id; + SpvOp op; + const char* type_name; + const char* struct_member_name; + SpvStorageClass storage_class; + SpvReflectTypeFlags type_flags; + SpvReflectDecorationFlags decoration_flags; + + struct Traits { + SpvReflectNumericTraits numeric; + SpvReflectImageTraits image; + SpvReflectArrayTraits array; + } traits; + + uint32_t member_count; + struct SpvReflectTypeDescription* members; +} SpvReflectTypeDescription; + + +/*! @struct SpvReflectInterfaceVariable + +*/ +typedef struct SpvReflectInterfaceVariable { + uint32_t spirv_id; + const char* name; + uint32_t location; + SpvStorageClass storage_class; + const char* semantic; + SpvReflectDecorationFlags decoration_flags; + SpvBuiltIn built_in; + SpvReflectNumericTraits numeric; + SpvReflectArrayTraits array; + + uint32_t member_count; + struct SpvReflectInterfaceVariable* members; + + SpvReflectFormat format; + + // NOTE: SPIR-V shares type references for variables + // that have the same underlying type. This means + // that the same type name will appear for multiple + // variables. + SpvReflectTypeDescription* type_description; + + struct { + uint32_t location; + } word_offset; +} SpvReflectInterfaceVariable; + +/*! @struct SpvReflectBlockVariable + +*/ +typedef struct SpvReflectBlockVariable { + uint32_t spirv_id; + const char* name; + uint32_t offset; // Measured in bytes + uint32_t absolute_offset; // Measured in bytes + uint32_t size; // Measured in bytes + uint32_t padded_size; // Measured in bytes + SpvReflectDecorationFlags decoration_flags; + SpvReflectNumericTraits numeric; + SpvReflectArrayTraits array; + SpvReflectVariableFlags flags; + + uint32_t member_count; + struct SpvReflectBlockVariable* members; + + SpvReflectTypeDescription* type_description; +} SpvReflectBlockVariable; + +/*! @struct SpvReflectDescriptorBinding + +*/ +typedef struct SpvReflectDescriptorBinding { + uint32_t spirv_id; + const char* name; + uint32_t binding; + uint32_t input_attachment_index; + uint32_t set; + SpvReflectDescriptorType descriptor_type; + SpvReflectResourceType resource_type; + SpvReflectImageTraits image; + SpvReflectBlockVariable block; + SpvReflectBindingArrayTraits array; + uint32_t count; + uint32_t accessed; + uint32_t uav_counter_id; + struct SpvReflectDescriptorBinding* uav_counter_binding; + + SpvReflectTypeDescription* type_description; + + struct { + uint32_t binding; + uint32_t set; + } word_offset; + + SpvReflectDecorationFlags decoration_flags; +} SpvReflectDescriptorBinding; + +/*! @struct SpvReflectDescriptorSet + +*/ +typedef struct SpvReflectDescriptorSet { + uint32_t set; + uint32_t binding_count; + SpvReflectDescriptorBinding** bindings; +} SpvReflectDescriptorSet; + +/*! @struct SpvReflectEntryPoint + + */ +typedef struct SpvReflectEntryPoint { + const char* name; + uint32_t id; + + SpvExecutionModel spirv_execution_model; + SpvReflectShaderStageFlagBits shader_stage; + + uint32_t input_variable_count; + SpvReflectInterfaceVariable** input_variables; + uint32_t output_variable_count; + SpvReflectInterfaceVariable** output_variables; + uint32_t interface_variable_count; + SpvReflectInterfaceVariable* interface_variables; + + uint32_t descriptor_set_count; + SpvReflectDescriptorSet* descriptor_sets; + + uint32_t used_uniform_count; + uint32_t* used_uniforms; + uint32_t used_push_constant_count; + uint32_t* used_push_constants; + + uint32_t execution_mode_count; + SpvExecutionMode* execution_modes; + + struct LocalSize { + uint32_t x; + uint32_t y; + uint32_t z; + } local_size; + uint32_t invocations; // valid for geometry + uint32_t output_vertices; // valid for geometry, tesselation +} SpvReflectEntryPoint; + +/*! @struct SpvReflectShaderModule + +*/ +typedef struct SpvReflectShaderModule { + SpvReflectGenerator generator; + const char* entry_point_name; + uint32_t entry_point_id; + uint32_t entry_point_count; + SpvReflectEntryPoint* entry_points; + SpvSourceLanguage source_language; + uint32_t source_language_version; + const char* source_file; + const char* source_source; + SpvExecutionModel spirv_execution_model; // Uses value(s) from first entry point + SpvReflectShaderStageFlagBits shader_stage; // Uses value(s) from first entry point + uint32_t descriptor_binding_count; // Uses value(s) from first entry point + SpvReflectDescriptorBinding* descriptor_bindings; // Uses value(s) from first entry point + uint32_t descriptor_set_count; // Uses value(s) from first entry point + SpvReflectDescriptorSet descriptor_sets[SPV_REFLECT_MAX_DESCRIPTOR_SETS]; // Uses value(s) from first entry point + uint32_t input_variable_count; // Uses value(s) from first entry point + SpvReflectInterfaceVariable** input_variables; // Uses value(s) from first entry point + uint32_t output_variable_count; // Uses value(s) from first entry point + SpvReflectInterfaceVariable** output_variables; // Uses value(s) from first entry point + uint32_t interface_variable_count; // Uses value(s) from first entry point + SpvReflectInterfaceVariable* interface_variables; // Uses value(s) from first entry point + uint32_t push_constant_block_count; // Uses value(s) from first entry point + SpvReflectBlockVariable* push_constant_blocks; // Uses value(s) from first entry point + + struct Internal { + SpvReflectModuleFlags module_flags; + size_t spirv_size; + uint32_t* spirv_code; + uint32_t spirv_word_count; + + size_t type_description_count; + SpvReflectTypeDescription* type_descriptions; + } * _internal; + +} SpvReflectShaderModule; + +#if defined(__cplusplus) +extern "C" { +#endif + +/*! @fn spvReflectCreateShaderModule + + @param size Size in bytes of SPIR-V code. + @param p_code Pointer to SPIR-V code. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @return SPV_REFLECT_RESULT_SUCCESS on success. + +*/ +SpvReflectResult spvReflectCreateShaderModule( + size_t size, + const void* p_code, + SpvReflectShaderModule* p_module +); + +/*! @fn spvReflectCreateShaderModule2 + + @param flags Flags for module creations. + @param size Size in bytes of SPIR-V code. + @param p_code Pointer to SPIR-V code. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @return SPV_REFLECT_RESULT_SUCCESS on success. + +*/ +SpvReflectResult spvReflectCreateShaderModule2( + SpvReflectModuleFlags flags, + size_t size, + const void* p_code, + SpvReflectShaderModule* p_module +); + +SPV_REFLECT_DEPRECATED("renamed to spvReflectCreateShaderModule") +SpvReflectResult spvReflectGetShaderModule( + size_t size, + const void* p_code, + SpvReflectShaderModule* p_module +); + + +/*! @fn spvReflectDestroyShaderModule + + @param p_module Pointer to an instance of SpvReflectShaderModule. + +*/ +void spvReflectDestroyShaderModule(SpvReflectShaderModule* p_module); + + +/*! @fn spvReflectGetCodeSize + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @return Returns the size of the SPIR-V in bytes + +*/ +uint32_t spvReflectGetCodeSize(const SpvReflectShaderModule* p_module); + + +/*! @fn spvReflectGetCode + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @return Returns a const pointer to the compiled SPIR-V bytecode. + +*/ +const uint32_t* spvReflectGetCode(const SpvReflectShaderModule* p_module); + +/*! @fn spvReflectGetEntryPoint + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point Name of the requested entry point. + @return Returns a const pointer to the requested entry point, + or NULL if it's not found. +*/ +const SpvReflectEntryPoint* spvReflectGetEntryPoint( + const SpvReflectShaderModule* p_module, + const char* entry_point +); + +/*! @fn spvReflectEnumerateDescriptorBindings + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_count If pp_bindings is NULL, the module's descriptor binding + count (across all descriptor sets) will be stored here. + If pp_bindings is not NULL, *p_count must contain the + module's descriptor binding count. + @param pp_bindings If NULL, the module's total descriptor binding count + will be written to *p_count. + If non-NULL, pp_bindings must point to an array with + *p_count entries, where pointers to the module's + descriptor bindings will be written. The caller must not + free the binding pointers written to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateDescriptorBindings( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectDescriptorBinding** pp_bindings +); + +/*! @fn spvReflectEnumerateEntryPointDescriptorBindings + @brief Creates a listing of all descriptor bindings that are used in the + static call tree of the given entry point. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point The name of the entry point to get the descriptor bindings for. + @param p_count If pp_bindings is NULL, the entry point's descriptor binding + count (across all descriptor sets) will be stored here. + If pp_bindings is not NULL, *p_count must contain the + entry points's descriptor binding count. + @param pp_bindings If NULL, the entry point's total descriptor binding count + will be written to *p_count. + If non-NULL, pp_bindings must point to an array with + *p_count entries, where pointers to the entry point's + descriptor bindings will be written. The caller must not + free the binding pointers written to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateEntryPointDescriptorBindings( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectDescriptorBinding** pp_bindings +); + +/*! @fn spvReflectEnumerateDescriptorSets + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_count If pp_sets is NULL, the module's descriptor set + count will be stored here. + If pp_sets is not NULL, *p_count must contain the + module's descriptor set count. + @param pp_sets If NULL, the module's total descriptor set count + will be written to *p_count. + If non-NULL, pp_sets must point to an array with + *p_count entries, where pointers to the module's + descriptor sets will be written. The caller must not + free the descriptor set pointers written to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateDescriptorSets( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectDescriptorSet** pp_sets +); + +/*! @fn spvReflectEnumerateEntryPointDescriptorSets + @brief Creates a listing of all descriptor sets and their bindings that are + used in the static call tree of a given entry point. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point The name of the entry point to get the descriptor bindings for. + @param p_count If pp_sets is NULL, the module's descriptor set + count will be stored here. + If pp_sets is not NULL, *p_count must contain the + module's descriptor set count. + @param pp_sets If NULL, the module's total descriptor set count + will be written to *p_count. + If non-NULL, pp_sets must point to an array with + *p_count entries, where pointers to the module's + descriptor sets will be written. The caller must not + free the descriptor set pointers written to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateEntryPointDescriptorSets( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectDescriptorSet** pp_sets +); + + +/*! @fn spvReflectEnumerateInterfaceVariables + @brief If the module contains multiple entry points, this will only get + the interface variables for the first one. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_count If pp_variables is NULL, the module's interface variable + count will be stored here. + If pp_variables is not NULL, *p_count must contain + the module's interface variable count. + @param pp_variables If NULL, the module's interface variable count will be + written to *p_count. + If non-NULL, pp_variables must point to an array with + *p_count entries, where pointers to the module's + interface variables will be written. The caller must not + free the interface variables written to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateInterfaceVariables( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +); + +/*! @fn spvReflectEnumerateEntryPointInterfaceVariables + @brief Enumerate the interface variables for a given entry point. + @param entry_point The name of the entry point to get the interface variables for. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_count If pp_variables is NULL, the entry point's interface variable + count will be stored here. + If pp_variables is not NULL, *p_count must contain + the entry point's interface variable count. + @param pp_variables If NULL, the entry point's interface variable count will be + written to *p_count. + If non-NULL, pp_variables must point to an array with + *p_count entries, where pointers to the entry point's + interface variables will be written. The caller must not + free the interface variables written to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateEntryPointInterfaceVariables( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +); + + +/*! @fn spvReflectEnumerateInputVariables + @brief If the module contains multiple entry points, this will only get + the input variables for the first one. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_count If pp_variables is NULL, the module's input variable + count will be stored here. + If pp_variables is not NULL, *p_count must contain + the module's input variable count. + @param pp_variables If NULL, the module's input variable count will be + written to *p_count. + If non-NULL, pp_variables must point to an array with + *p_count entries, where pointers to the module's + input variables will be written. The caller must not + free the interface variables written to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateInputVariables( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +); + +/*! @fn spvReflectEnumerateEntryPointInputVariables + @brief Enumerate the input variables for a given entry point. + @param entry_point The name of the entry point to get the input variables for. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_count If pp_variables is NULL, the entry point's input variable + count will be stored here. + If pp_variables is not NULL, *p_count must contain + the entry point's input variable count. + @param pp_variables If NULL, the entry point's input variable count will be + written to *p_count. + If non-NULL, pp_variables must point to an array with + *p_count entries, where pointers to the entry point's + input variables will be written. The caller must not + free the interface variables written to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateEntryPointInputVariables( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +); + + +/*! @fn spvReflectEnumerateOutputVariables + @brief Note: If the module contains multiple entry points, this will only get + the output variables for the first one. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_count If pp_variables is NULL, the module's output variable + count will be stored here. + If pp_variables is not NULL, *p_count must contain + the module's output variable count. + @param pp_variables If NULL, the module's output variable count will be + written to *p_count. + If non-NULL, pp_variables must point to an array with + *p_count entries, where pointers to the module's + output variables will be written. The caller must not + free the interface variables written to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateOutputVariables( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +); + +/*! @fn spvReflectEnumerateEntryPointOutputVariables + @brief Enumerate the output variables for a given entry point. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point The name of the entry point to get the output variables for. + @param p_count If pp_variables is NULL, the entry point's output variable + count will be stored here. + If pp_variables is not NULL, *p_count must contain + the entry point's output variable count. + @param pp_variables If NULL, the entry point's output variable count will be + written to *p_count. + If non-NULL, pp_variables must point to an array with + *p_count entries, where pointers to the entry point's + output variables will be written. The caller must not + free the interface variables written to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateEntryPointOutputVariables( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +); + + +/*! @fn spvReflectEnumeratePushConstantBlocks + @brief Note: If the module contains multiple entry points, this will only get + the push constant blocks for the first one. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_count If pp_blocks is NULL, the module's push constant + block count will be stored here. + If pp_blocks is not NULL, *p_count must + contain the module's push constant block count. + @param pp_blocks If NULL, the module's push constant block count + will be written to *p_count. + If non-NULL, pp_blocks must point to an + array with *p_count entries, where pointers to + the module's push constant blocks will be written. + The caller must not free the block variables written + to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumeratePushConstantBlocks( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectBlockVariable** pp_blocks +); +SPV_REFLECT_DEPRECATED("renamed to spvReflectEnumeratePushConstantBlocks") +SpvReflectResult spvReflectEnumeratePushConstants( + const SpvReflectShaderModule* p_module, + uint32_t* p_count, + SpvReflectBlockVariable** pp_blocks +); + +/*! @fn spvReflectEnumerateEntryPointPushConstantBlocks + @brief Enumerate the push constant blocks used in the static call tree of a + given entry point. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_count If pp_blocks is NULL, the entry point's push constant + block count will be stored here. + If pp_blocks is not NULL, *p_count must + contain the entry point's push constant block count. + @param pp_blocks If NULL, the entry point's push constant block count + will be written to *p_count. + If non-NULL, pp_blocks must point to an + array with *p_count entries, where pointers to + the entry point's push constant blocks will be written. + The caller must not free the block variables written + to this array. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of the + failure. + +*/ +SpvReflectResult spvReflectEnumerateEntryPointPushConstantBlocks( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t* p_count, + SpvReflectBlockVariable** pp_blocks +); + + +/*! @fn spvReflectGetDescriptorBinding + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param binding_number The "binding" value of the requested descriptor + binding. + @param set_number The "set" value of the requested descriptor binding. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the module contains a descriptor binding that + matches the provided [binding_number, set_number] + values, a pointer to that binding is returned. The + caller must not free this pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. +@note If the module contains multiple desriptor bindings + with the same set and binding numbers, there are + no guarantees about which binding will be returned. + +*/ +const SpvReflectDescriptorBinding* spvReflectGetDescriptorBinding( + const SpvReflectShaderModule* p_module, + uint32_t binding_number, + uint32_t set_number, + SpvReflectResult* p_result +); + +/*! @fn spvReflectGetEntryPointDescriptorBinding + @brief Get the descriptor binding with the given binding number and set + number that is used in the static call tree of a certain entry + point. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point The entry point to get the binding from. + @param binding_number The "binding" value of the requested descriptor + binding. + @param set_number The "set" value of the requested descriptor binding. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the entry point contains a descriptor binding that + matches the provided [binding_number, set_number] + values, a pointer to that binding is returned. The + caller must not free this pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. +@note If the entry point contains multiple desriptor bindings + with the same set and binding numbers, there are + no guarantees about which binding will be returned. + +*/ +const SpvReflectDescriptorBinding* spvReflectGetEntryPointDescriptorBinding( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t binding_number, + uint32_t set_number, + SpvReflectResult* p_result +); + + +/*! @fn spvReflectGetDescriptorSet + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param set_number The "set" value of the requested descriptor set. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the module contains a descriptor set with the + provided set_number, a pointer to that set is + returned. The caller must not free this pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. + +*/ +const SpvReflectDescriptorSet* spvReflectGetDescriptorSet( + const SpvReflectShaderModule* p_module, + uint32_t set_number, + SpvReflectResult* p_result +); + +/*! @fn spvReflectGetEntryPointDescriptorSet + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point The entry point to get the descriptor set from. + @param set_number The "set" value of the requested descriptor set. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the entry point contains a descriptor set with the + provided set_number, a pointer to that set is + returned. The caller must not free this pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. + +*/ +const SpvReflectDescriptorSet* spvReflectGetEntryPointDescriptorSet( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t set_number, + SpvReflectResult* p_result +); + + +/* @fn spvReflectGetInputVariableByLocation + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param location The "location" value of the requested input variable. + A location of 0xFFFFFFFF will always return NULL + with *p_result == ELEMENT_NOT_FOUND. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the module contains an input interface variable + with the provided location value, a pointer to that + variable is returned. The caller must not free this + pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. +@note + +*/ +const SpvReflectInterfaceVariable* spvReflectGetInputVariableByLocation( + const SpvReflectShaderModule* p_module, + uint32_t location, + SpvReflectResult* p_result +); +SPV_REFLECT_DEPRECATED("renamed to spvReflectGetInputVariableByLocation") +const SpvReflectInterfaceVariable* spvReflectGetInputVariable( + const SpvReflectShaderModule* p_module, + uint32_t location, + SpvReflectResult* p_result +); + +/* @fn spvReflectGetEntryPointInputVariableByLocation + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point The entry point to get the input variable from. + @param location The "location" value of the requested input variable. + A location of 0xFFFFFFFF will always return NULL + with *p_result == ELEMENT_NOT_FOUND. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the entry point contains an input interface variable + with the provided location value, a pointer to that + variable is returned. The caller must not free this + pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. +@note + +*/ +const SpvReflectInterfaceVariable* spvReflectGetEntryPointInputVariableByLocation( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t location, + SpvReflectResult* p_result +); + +/* @fn spvReflectGetInputVariableBySemantic + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param semantic The "semantic" value of the requested input variable. + A semantic of NULL will return NULL. + A semantic of "" will always return NULL with + *p_result == ELEMENT_NOT_FOUND. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the module contains an input interface variable + with the provided semantic, a pointer to that + variable is returned. The caller must not free this + pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. +@note + +*/ +const SpvReflectInterfaceVariable* spvReflectGetInputVariableBySemantic( + const SpvReflectShaderModule* p_module, + const char* semantic, + SpvReflectResult* p_result +); + +/* @fn spvReflectGetEntryPointInputVariableBySemantic + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point The entry point to get the input variable from. + @param semantic The "semantic" value of the requested input variable. + A semantic of NULL will return NULL. + A semantic of "" will always return NULL with + *p_result == ELEMENT_NOT_FOUND. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the entry point contains an input interface variable + with the provided semantic, a pointer to that + variable is returned. The caller must not free this + pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. +@note + +*/ +const SpvReflectInterfaceVariable* spvReflectGetEntryPointInputVariableBySemantic( + const SpvReflectShaderModule* p_module, + const char* entry_point, + const char* semantic, + SpvReflectResult* p_result +); + +/* @fn spvReflectGetOutputVariableByLocation + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param location The "location" value of the requested output variable. + A location of 0xFFFFFFFF will always return NULL + with *p_result == ELEMENT_NOT_FOUND. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the module contains an output interface variable + with the provided location value, a pointer to that + variable is returned. The caller must not free this + pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. +@note + +*/ +const SpvReflectInterfaceVariable* spvReflectGetOutputVariableByLocation( + const SpvReflectShaderModule* p_module, + uint32_t location, + SpvReflectResult* p_result +); +SPV_REFLECT_DEPRECATED("renamed to spvReflectGetOutputVariableByLocation") +const SpvReflectInterfaceVariable* spvReflectGetOutputVariable( + const SpvReflectShaderModule* p_module, + uint32_t location, + SpvReflectResult* p_result +); + +/* @fn spvReflectGetEntryPointOutputVariableByLocation + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point The entry point to get the output variable from. + @param location The "location" value of the requested output variable. + A location of 0xFFFFFFFF will always return NULL + with *p_result == ELEMENT_NOT_FOUND. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the entry point contains an output interface variable + with the provided location value, a pointer to that + variable is returned. The caller must not free this + pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. +@note + +*/ +const SpvReflectInterfaceVariable* spvReflectGetEntryPointOutputVariableByLocation( + const SpvReflectShaderModule* p_module, + const char* entry_point, + uint32_t location, + SpvReflectResult* p_result +); + +/* @fn spvReflectGetOutputVariableBySemantic + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param semantic The "semantic" value of the requested output variable. + A semantic of NULL will return NULL. + A semantic of "" will always return NULL with + *p_result == ELEMENT_NOT_FOUND. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the module contains an output interface variable + with the provided semantic, a pointer to that + variable is returned. The caller must not free this + pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. +@note + +*/ +const SpvReflectInterfaceVariable* spvReflectGetOutputVariableBySemantic( + const SpvReflectShaderModule* p_module, + const char* semantic, + SpvReflectResult* p_result +); + +/* @fn spvReflectGetEntryPointOutputVariableBySemantic + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point The entry point to get the output variable from. + @param semantic The "semantic" value of the requested output variable. + A semantic of NULL will return NULL. + A semantic of "" will always return NULL with + *p_result == ELEMENT_NOT_FOUND. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the entry point contains an output interface variable + with the provided semantic, a pointer to that + variable is returned. The caller must not free this + pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. +@note + +*/ +const SpvReflectInterfaceVariable* spvReflectGetEntryPointOutputVariableBySemantic( + const SpvReflectShaderModule* p_module, + const char* entry_point, + const char* semantic, + SpvReflectResult* p_result +); + +/*! @fn spvReflectGetPushConstantBlock + + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param index The index of the desired block within the module's + array of push constant blocks. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the provided index is within range, a pointer to + the corresponding push constant block is returned. + The caller must not free this pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. + +*/ +const SpvReflectBlockVariable* spvReflectGetPushConstantBlock( + const SpvReflectShaderModule* p_module, + uint32_t index, + SpvReflectResult* p_result +); +SPV_REFLECT_DEPRECATED("renamed to spvReflectGetPushConstantBlock") +const SpvReflectBlockVariable* spvReflectGetPushConstant( + const SpvReflectShaderModule* p_module, + uint32_t index, + SpvReflectResult* p_result +); + +/*! @fn spvReflectGetEntryPointPushConstantBlock + @brief Get the push constant block corresponding to the given entry point. + As by the Vulkan specification there can be no more than one push + constant block used by a given entry point, so if there is one it will + be returned, otherwise NULL will be returned. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param entry_point The entry point to get the push constant block from. + @param p_result If successful, SPV_REFLECT_RESULT_SUCCESS will be + written to *p_result. Otherwise, a error code + indicating the cause of the failure will be stored + here. + @return If the provided index is within range, a pointer to + the corresponding push constant block is returned. + The caller must not free this pointer. + If no match can be found, or if an unrelated error + occurs, the return value will be NULL. Detailed + error results are written to *pResult. + +*/ +const SpvReflectBlockVariable* spvReflectGetEntryPointPushConstantBlock( + const SpvReflectShaderModule* p_module, + const char* entry_point, + SpvReflectResult* p_result +); + + +/*! @fn spvReflectChangeDescriptorBindingNumbers + @brief Assign new set and/or binding numbers to a descriptor binding. + In addition to updating the reflection data, this function modifies + the underlying SPIR-V bytecode. The updated code can be retrieved + with spvReflectGetCode(). If the binding is used in multiple + entry points within the module, it will be changed in all of them. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_binding Pointer to the descriptor binding to modify. + @param new_binding_number The new binding number to assign to the + provided descriptor binding. + To leave the binding number unchanged, pass + SPV_REFLECT_BINDING_NUMBER_DONT_CHANGE. + @param new_set_number The new set number to assign to the + provided descriptor binding. Successfully changing + a descriptor binding's set number invalidates all + existing SpvReflectDescriptorBinding and + SpvReflectDescriptorSet pointers from this module. + To leave the set number unchanged, pass + SPV_REFLECT_SET_NUMBER_DONT_CHANGE. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of + the failure. +*/ +SpvReflectResult spvReflectChangeDescriptorBindingNumbers( + SpvReflectShaderModule* p_module, + const SpvReflectDescriptorBinding* p_binding, + uint32_t new_binding_number, + uint32_t new_set_number +); +SPV_REFLECT_DEPRECATED("Renamed to spvReflectChangeDescriptorBindingNumbers") +SpvReflectResult spvReflectChangeDescriptorBindingNumber( + SpvReflectShaderModule* p_module, + const SpvReflectDescriptorBinding* p_descriptor_binding, + uint32_t new_binding_number, + uint32_t optional_new_set_number +); + +/*! @fn spvReflectChangeDescriptorSetNumber + @brief Assign a new set number to an entire descriptor set (including + all descriptor bindings in that set). + In addition to updating the reflection data, this function modifies + the underlying SPIR-V bytecode. The updated code can be retrieved + with spvReflectGetCode(). If the descriptor set is used in + multiple entry points within the module, it will be modified in all + of them. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_set Pointer to the descriptor binding to modify. + @param new_set_number The new set number to assign to the + provided descriptor set, and all its descriptor + bindings. Successfully changing a descriptor + binding's set number invalidates all existing + SpvReflectDescriptorBinding and + SpvReflectDescriptorSet pointers from this module. + To leave the set number unchanged, pass + SPV_REFLECT_SET_NUMBER_DONT_CHANGE. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of + the failure. +*/ +SpvReflectResult spvReflectChangeDescriptorSetNumber( + SpvReflectShaderModule* p_module, + const SpvReflectDescriptorSet* p_set, + uint32_t new_set_number +); + +/*! @fn spvReflectChangeInputVariableLocation + @brief Assign a new location to an input interface variable. + In addition to updating the reflection data, this function modifies + the underlying SPIR-V bytecode. The updated code can be retrieved + with spvReflectGetCode(). + It is the caller's responsibility to avoid assigning the same + location to multiple input variables. If the input variable is used + by multiple entry points in the module, it will be changed in all of + them. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_input_variable Pointer to the input variable to update. + @param new_location The new location to assign to p_input_variable. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of + the failure. + +*/ +SpvReflectResult spvReflectChangeInputVariableLocation( + SpvReflectShaderModule* p_module, + const SpvReflectInterfaceVariable* p_input_variable, + uint32_t new_location +); + + +/*! @fn spvReflectChangeOutputVariableLocation + @brief Assign a new location to an output interface variable. + In addition to updating the reflection data, this function modifies + the underlying SPIR-V bytecode. The updated code can be retrieved + with spvReflectGetCode(). + It is the caller's responsibility to avoid assigning the same + location to multiple output variables. If the output variable is used + by multiple entry points in the module, it will be changed in all of + them. + @param p_module Pointer to an instance of SpvReflectShaderModule. + @param p_output_variable Pointer to the output variable to update. + @param new_location The new location to assign to p_output_variable. + @return If successful, returns SPV_REFLECT_RESULT_SUCCESS. + Otherwise, the error code indicates the cause of + the failure. + +*/ +SpvReflectResult spvReflectChangeOutputVariableLocation( + SpvReflectShaderModule* p_module, + const SpvReflectInterfaceVariable* p_output_variable, + uint32_t new_location +); + + +/*! @fn spvReflectSourceLanguage + + @param source_lang The source language code. + @return Returns string of source language specified in \a source_lang. + The caller must not free the memory associated with this string. +*/ +const char* spvReflectSourceLanguage(SpvSourceLanguage source_lang); + +/*! @fn spvReflectBlockVariableTypeName + + @param p_var Pointer to block variable. + @return Returns string of block variable's type description type name + or NULL if p_var is NULL. +*/ +const char* spvReflectBlockVariableTypeName( + const SpvReflectBlockVariable* p_var +); + +#if defined(__cplusplus) +}; +#endif + +#if defined(__cplusplus) +#include +#include +#include + +namespace spv_reflect { + +/*! \class ShaderModule + +*/ +class ShaderModule { +public: + ShaderModule(); + ShaderModule(size_t size, const void* p_code, SpvReflectModuleFlags flags = SPV_REFLECT_MODULE_FLAG_NONE); + ShaderModule(const std::vector& code, SpvReflectModuleFlags flags = SPV_REFLECT_MODULE_FLAG_NONE); + ShaderModule(const std::vector& code, SpvReflectModuleFlags flags = SPV_REFLECT_MODULE_FLAG_NONE); + ~ShaderModule(); + + ShaderModule(ShaderModule&& other); + ShaderModule& operator=(ShaderModule&& other); + + SpvReflectResult GetResult() const; + + const SpvReflectShaderModule& GetShaderModule() const; + + uint32_t GetCodeSize() const; + const uint32_t* GetCode() const; + + const char* GetEntryPointName() const; + + const char* GetSourceFile() const; + + uint32_t GetEntryPointCount() const; + const char* GetEntryPointName(uint32_t index) const; + SpvReflectShaderStageFlagBits GetEntryPointShaderStage(uint32_t index) const; + + SpvReflectShaderStageFlagBits GetShaderStage() const; + SPV_REFLECT_DEPRECATED("Renamed to GetShaderStage") + SpvReflectShaderStageFlagBits GetVulkanShaderStage() const { + return GetShaderStage(); + } + + SpvReflectResult EnumerateDescriptorBindings(uint32_t* p_count, SpvReflectDescriptorBinding** pp_bindings) const; + SpvReflectResult EnumerateEntryPointDescriptorBindings(const char* entry_point, uint32_t* p_count, SpvReflectDescriptorBinding** pp_bindings) const; + SpvReflectResult EnumerateDescriptorSets( uint32_t* p_count, SpvReflectDescriptorSet** pp_sets) const ; + SpvReflectResult EnumerateEntryPointDescriptorSets(const char* entry_point, uint32_t* p_count, SpvReflectDescriptorSet** pp_sets) const ; + SpvReflectResult EnumerateInterfaceVariables(uint32_t* p_count, SpvReflectInterfaceVariable** pp_variables) const; + SpvReflectResult EnumerateEntryPointInterfaceVariables(const char* entry_point, uint32_t* p_count, SpvReflectInterfaceVariable** pp_variables) const; + SpvReflectResult EnumerateInputVariables(uint32_t* p_count,SpvReflectInterfaceVariable** pp_variables) const; + SpvReflectResult EnumerateEntryPointInputVariables(const char* entry_point, uint32_t* p_count, SpvReflectInterfaceVariable** pp_variables) const; + SpvReflectResult EnumerateOutputVariables(uint32_t* p_count,SpvReflectInterfaceVariable** pp_variables) const; + SpvReflectResult EnumerateEntryPointOutputVariables(const char* entry_point, uint32_t* p_count, SpvReflectInterfaceVariable** pp_variables) const; + SpvReflectResult EnumeratePushConstantBlocks(uint32_t* p_count, SpvReflectBlockVariable** pp_blocks) const; + SpvReflectResult EnumerateEntryPointPushConstantBlocks(const char* entry_point, uint32_t* p_count, SpvReflectBlockVariable** pp_blocks) const; + SPV_REFLECT_DEPRECATED("Renamed to EnumeratePushConstantBlocks") + SpvReflectResult EnumeratePushConstants(uint32_t* p_count, SpvReflectBlockVariable** pp_blocks) const { + return EnumeratePushConstantBlocks(p_count, pp_blocks); + } + + const SpvReflectDescriptorBinding* GetDescriptorBinding(uint32_t binding_number, uint32_t set_number, SpvReflectResult* p_result = nullptr) const; + const SpvReflectDescriptorBinding* GetEntryPointDescriptorBinding(const char* entry_point, uint32_t binding_number, uint32_t set_number, SpvReflectResult* p_result = nullptr) const; + const SpvReflectDescriptorSet* GetDescriptorSet(uint32_t set_number, SpvReflectResult* p_result = nullptr) const; + const SpvReflectDescriptorSet* GetEntryPointDescriptorSet(const char* entry_point, uint32_t set_number, SpvReflectResult* p_result = nullptr) const; + const SpvReflectInterfaceVariable* GetInputVariableByLocation(uint32_t location, SpvReflectResult* p_result = nullptr) const; + SPV_REFLECT_DEPRECATED("Renamed to GetInputVariableByLocation") + const SpvReflectInterfaceVariable* GetInputVariable(uint32_t location, SpvReflectResult* p_result = nullptr) const { + return GetInputVariableByLocation(location, p_result); + } + const SpvReflectInterfaceVariable* GetEntryPointInputVariableByLocation(const char* entry_point, uint32_t location, SpvReflectResult* p_result = nullptr) const; + const SpvReflectInterfaceVariable* GetInputVariableBySemantic(const char* semantic, SpvReflectResult* p_result = nullptr) const; + const SpvReflectInterfaceVariable* GetEntryPointInputVariableBySemantic(const char* entry_point, const char* semantic, SpvReflectResult* p_result = nullptr) const; + const SpvReflectInterfaceVariable* GetOutputVariableByLocation(uint32_t location, SpvReflectResult* p_result = nullptr) const; + SPV_REFLECT_DEPRECATED("Renamed to GetOutputVariableByLocation") + const SpvReflectInterfaceVariable* GetOutputVariable(uint32_t location, SpvReflectResult* p_result = nullptr) const { + return GetOutputVariableByLocation(location, p_result); + } + const SpvReflectInterfaceVariable* GetEntryPointOutputVariableByLocation(const char* entry_point, uint32_t location, SpvReflectResult* p_result = nullptr) const; + const SpvReflectInterfaceVariable* GetOutputVariableBySemantic(const char* semantic, SpvReflectResult* p_result = nullptr) const; + const SpvReflectInterfaceVariable* GetEntryPointOutputVariableBySemantic(const char* entry_point, const char* semantic, SpvReflectResult* p_result = nullptr) const; + const SpvReflectBlockVariable* GetPushConstantBlock(uint32_t index, SpvReflectResult* p_result = nullptr) const; + SPV_REFLECT_DEPRECATED("Renamed to GetPushConstantBlock") + const SpvReflectBlockVariable* GetPushConstant(uint32_t index, SpvReflectResult* p_result = nullptr) const { + return GetPushConstantBlock(index, p_result); + } + const SpvReflectBlockVariable* GetEntryPointPushConstantBlock(const char* entry_point, SpvReflectResult* p_result = nullptr) const; + + SpvReflectResult ChangeDescriptorBindingNumbers(const SpvReflectDescriptorBinding* p_binding, + uint32_t new_binding_number = SPV_REFLECT_BINDING_NUMBER_DONT_CHANGE, + uint32_t optional_new_set_number = SPV_REFLECT_SET_NUMBER_DONT_CHANGE); + SPV_REFLECT_DEPRECATED("Renamed to ChangeDescriptorBindingNumbers") + SpvReflectResult ChangeDescriptorBindingNumber(const SpvReflectDescriptorBinding* p_binding, uint32_t new_binding_number = SPV_REFLECT_BINDING_NUMBER_DONT_CHANGE, + uint32_t new_set_number = SPV_REFLECT_SET_NUMBER_DONT_CHANGE) { + return ChangeDescriptorBindingNumbers(p_binding, new_binding_number, new_set_number); + } + SpvReflectResult ChangeDescriptorSetNumber(const SpvReflectDescriptorSet* p_set, uint32_t new_set_number = SPV_REFLECT_SET_NUMBER_DONT_CHANGE); + SpvReflectResult ChangeInputVariableLocation(const SpvReflectInterfaceVariable* p_input_variable, uint32_t new_location); + SpvReflectResult ChangeOutputVariableLocation(const SpvReflectInterfaceVariable* p_output_variable, uint32_t new_location); + +private: + // Make noncopyable + ShaderModule(const ShaderModule&); + ShaderModule& operator=(const ShaderModule&); + +private: + mutable SpvReflectResult m_result = SPV_REFLECT_RESULT_NOT_READY; + SpvReflectShaderModule m_module = {}; +}; + + +// ================================================================================================= +// ShaderModule +// ================================================================================================= + +/*! @fn ShaderModule + +*/ +inline ShaderModule::ShaderModule() {} + + +/*! @fn ShaderModule + + @param size + @param p_code + +*/ +inline ShaderModule::ShaderModule(size_t size, const void* p_code, SpvReflectModuleFlags flags) { + m_result = spvReflectCreateShaderModule2( + flags, + size, + p_code, + &m_module); +} + +/*! @fn ShaderModule + + @param code + +*/ +inline ShaderModule::ShaderModule(const std::vector& code, SpvReflectModuleFlags flags) { + m_result = spvReflectCreateShaderModule2( + flags, + code.size(), + code.data(), + &m_module); +} + +/*! @fn ShaderModule + + @param code + +*/ +inline ShaderModule::ShaderModule(const std::vector& code, SpvReflectModuleFlags flags) { + m_result = spvReflectCreateShaderModule2( + flags, + code.size() * sizeof(uint32_t), + code.data(), + &m_module); +} + +/*! @fn ~ShaderModule + +*/ +inline ShaderModule::~ShaderModule() { + spvReflectDestroyShaderModule(&m_module); +} + + +inline ShaderModule::ShaderModule(ShaderModule&& other) +{ + *this = std::move(other); +} + +inline ShaderModule& ShaderModule::operator=(ShaderModule&& other) +{ + m_result = std::move(other.m_result); + m_module = std::move(other.m_module); + + other.m_module = {}; + return *this; +} + +/*! @fn GetResult + + @return + +*/ +inline SpvReflectResult ShaderModule::GetResult() const { + return m_result; +} + + +/*! @fn GetShaderModule + + @return + +*/ +inline const SpvReflectShaderModule& ShaderModule::GetShaderModule() const { + return m_module; +} + + +/*! @fn GetCodeSize + + @return + + */ +inline uint32_t ShaderModule::GetCodeSize() const { + return spvReflectGetCodeSize(&m_module); +} + + +/*! @fn GetCode + + @return + +*/ +inline const uint32_t* ShaderModule::GetCode() const { + return spvReflectGetCode(&m_module); +} + + +/*! @fn GetEntryPoint + + @return Returns entry point + +*/ +inline const char* ShaderModule::GetEntryPointName() const { + return this->GetEntryPointName(0); +} + +/*! @fn GetEntryPoint + + @return Returns entry point + +*/ +inline const char* ShaderModule::GetSourceFile() const { + return m_module.source_file; +} + +/*! @fn GetEntryPointCount + + @param + @return +*/ +inline uint32_t ShaderModule::GetEntryPointCount() const { + return m_module.entry_point_count; +} + +/*! @fn GetEntryPointName + + @param index + @return +*/ +inline const char* ShaderModule::GetEntryPointName(uint32_t index) const { + return m_module.entry_points[index].name; +} + +/*! @fn GetEntryPointShaderStage + + @param index + @return Returns the shader stage for the entry point at \b index +*/ +inline SpvReflectShaderStageFlagBits ShaderModule::GetEntryPointShaderStage(uint32_t index) const { + return m_module.entry_points[index].shader_stage; +} + +/*! @fn GetShaderStage + + @return Returns shader stage for the first entry point + +*/ +inline SpvReflectShaderStageFlagBits ShaderModule::GetShaderStage() const { + return m_module.shader_stage; +} + +/*! @fn EnumerateDescriptorBindings + + @param count + @param p_binding_numbers + @param pp_bindings + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateDescriptorBindings( + uint32_t* p_count, + SpvReflectDescriptorBinding** pp_bindings +) const +{ + m_result = spvReflectEnumerateDescriptorBindings( + &m_module, + p_count, + pp_bindings); + return m_result; +} + +/*! @fn EnumerateEntryPointDescriptorBindings + + @param entry_point + @param count + @param pp_bindings + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateEntryPointDescriptorBindings( + const char* entry_point, + uint32_t* p_count, + SpvReflectDescriptorBinding** pp_bindings +) const +{ + m_result = spvReflectEnumerateEntryPointDescriptorBindings( + &m_module, + entry_point, + p_count, + pp_bindings); + return m_result; +} + + +/*! @fn EnumerateDescriptorSets + + @param count + @param pp_sets + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateDescriptorSets( + uint32_t* p_count, + SpvReflectDescriptorSet** pp_sets +) const +{ + m_result = spvReflectEnumerateDescriptorSets( + &m_module, + p_count, + pp_sets); + return m_result; +} + +/*! @fn EnumerateEntryPointDescriptorSets + + @param entry_point + @param count + @param pp_sets + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateEntryPointDescriptorSets( + const char* entry_point, + uint32_t* p_count, + SpvReflectDescriptorSet** pp_sets +) const +{ + m_result = spvReflectEnumerateEntryPointDescriptorSets( + &m_module, + entry_point, + p_count, + pp_sets); + return m_result; +} + + +/*! @fn EnumerateInterfaceVariables + + @param count + @param pp_variables + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateInterfaceVariables( + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) const +{ + m_result = spvReflectEnumerateInterfaceVariables( + &m_module, + p_count, + pp_variables); + return m_result; +} + +/*! @fn EnumerateEntryPointInterfaceVariables + + @param entry_point + @param count + @param pp_variables + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateEntryPointInterfaceVariables( + const char* entry_point, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) const +{ + m_result = spvReflectEnumerateEntryPointInterfaceVariables( + &m_module, + entry_point, + p_count, + pp_variables); + return m_result; +} + + +/*! @fn EnumerateInputVariables + + @param count + @param pp_variables + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateInputVariables( + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) const +{ + m_result = spvReflectEnumerateInputVariables( + &m_module, + p_count, + pp_variables); + return m_result; +} + +/*! @fn EnumerateEntryPointInputVariables + + @param entry_point + @param count + @param pp_variables + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateEntryPointInputVariables( + const char* entry_point, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) const +{ + m_result = spvReflectEnumerateEntryPointInputVariables( + &m_module, + entry_point, + p_count, + pp_variables); + return m_result; +} + + +/*! @fn EnumerateOutputVariables + + @param count + @param pp_variables + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateOutputVariables( + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) const +{ + m_result = spvReflectEnumerateOutputVariables( + &m_module, + p_count, + pp_variables); + return m_result; +} + +/*! @fn EnumerateEntryPointOutputVariables + + @param entry_point + @param count + @param pp_variables + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateEntryPointOutputVariables( + const char* entry_point, + uint32_t* p_count, + SpvReflectInterfaceVariable** pp_variables +) const +{ + m_result = spvReflectEnumerateEntryPointOutputVariables( + &m_module, + entry_point, + p_count, + pp_variables); + return m_result; +} + + +/*! @fn EnumeratePushConstantBlocks + + @param count + @param pp_blocks + @return + +*/ +inline SpvReflectResult ShaderModule::EnumeratePushConstantBlocks( + uint32_t* p_count, + SpvReflectBlockVariable** pp_blocks +) const +{ + m_result = spvReflectEnumeratePushConstantBlocks( + &m_module, + p_count, + pp_blocks); + return m_result; +} + +/*! @fn EnumerateEntryPointPushConstantBlocks + + @param entry_point + @param count + @param pp_blocks + @return + +*/ +inline SpvReflectResult ShaderModule::EnumerateEntryPointPushConstantBlocks( + const char* entry_point, + uint32_t* p_count, + SpvReflectBlockVariable** pp_blocks +) const +{ + m_result = spvReflectEnumerateEntryPointPushConstantBlocks( + &m_module, + entry_point, + p_count, + pp_blocks); + return m_result; +} + + +/*! @fn GetDescriptorBinding + + @param binding_number + @param set_number + @param p_result + @return + +*/ +inline const SpvReflectDescriptorBinding* ShaderModule::GetDescriptorBinding( + uint32_t binding_number, + uint32_t set_number, + SpvReflectResult* p_result +) const +{ + return spvReflectGetDescriptorBinding( + &m_module, + binding_number, + set_number, + p_result); +} + +/*! @fn GetEntryPointDescriptorBinding + + @param entry_point + @param binding_number + @param set_number + @param p_result + @return + +*/ +inline const SpvReflectDescriptorBinding* ShaderModule::GetEntryPointDescriptorBinding( + const char* entry_point, + uint32_t binding_number, + uint32_t set_number, + SpvReflectResult* p_result +) const +{ + return spvReflectGetEntryPointDescriptorBinding( + &m_module, + entry_point, + binding_number, + set_number, + p_result); +} + + +/*! @fn GetDescriptorSet + + @param set_number + @param p_result + @return + +*/ +inline const SpvReflectDescriptorSet* ShaderModule::GetDescriptorSet( + uint32_t set_number, + SpvReflectResult* p_result +) const +{ + return spvReflectGetDescriptorSet( + &m_module, + set_number, + p_result); +} + +/*! @fn GetEntryPointDescriptorSet + + @param entry_point + @param set_number + @param p_result + @return + +*/ +inline const SpvReflectDescriptorSet* ShaderModule::GetEntryPointDescriptorSet( + const char* entry_point, + uint32_t set_number, + SpvReflectResult* p_result +) const +{ + return spvReflectGetEntryPointDescriptorSet( + &m_module, + entry_point, + set_number, + p_result); +} + + +/*! @fn GetInputVariable + + @param location + @param p_result + @return + +*/ +inline const SpvReflectInterfaceVariable* ShaderModule::GetInputVariableByLocation( + uint32_t location, + SpvReflectResult* p_result +) const +{ + return spvReflectGetInputVariableByLocation( + &m_module, + location, + p_result); +} +inline const SpvReflectInterfaceVariable* ShaderModule::GetInputVariableBySemantic( + const char* semantic, + SpvReflectResult* p_result +) const +{ + return spvReflectGetInputVariableBySemantic( + &m_module, + semantic, + p_result); +} + +/*! @fn GetEntryPointInputVariable + + @param entry_point + @param location + @param p_result + @return + +*/ +inline const SpvReflectInterfaceVariable* ShaderModule::GetEntryPointInputVariableByLocation( + const char* entry_point, + uint32_t location, + SpvReflectResult* p_result +) const +{ + return spvReflectGetEntryPointInputVariableByLocation( + &m_module, + entry_point, + location, + p_result); +} +inline const SpvReflectInterfaceVariable* ShaderModule::GetEntryPointInputVariableBySemantic( + const char* entry_point, + const char* semantic, + SpvReflectResult* p_result +) const +{ + return spvReflectGetEntryPointInputVariableBySemantic( + &m_module, + entry_point, + semantic, + p_result); +} + + +/*! @fn GetOutputVariable + + @param location + @param p_result + @return + +*/ +inline const SpvReflectInterfaceVariable* ShaderModule::GetOutputVariableByLocation( + uint32_t location, + SpvReflectResult* p_result +) const +{ + return spvReflectGetOutputVariableByLocation( + &m_module, + location, + p_result); +} +inline const SpvReflectInterfaceVariable* ShaderModule::GetOutputVariableBySemantic( + const char* semantic, + SpvReflectResult* p_result +) const +{ + return spvReflectGetOutputVariableBySemantic(&m_module, + semantic, + p_result); +} + +/*! @fn GetEntryPointOutputVariable + + @param entry_point + @param location + @param p_result + @return + +*/ +inline const SpvReflectInterfaceVariable* ShaderModule::GetEntryPointOutputVariableByLocation( + const char* entry_point, + uint32_t location, + SpvReflectResult* p_result +) const +{ + return spvReflectGetEntryPointOutputVariableByLocation( + &m_module, + entry_point, + location, + p_result); +} +inline const SpvReflectInterfaceVariable* ShaderModule::GetEntryPointOutputVariableBySemantic( + const char* entry_point, + const char* semantic, + SpvReflectResult* p_result +) const +{ + return spvReflectGetEntryPointOutputVariableBySemantic( + &m_module, + entry_point, + semantic, + p_result); +} + + +/*! @fn GetPushConstant + + @param index + @param p_result + @return + +*/ +inline const SpvReflectBlockVariable* ShaderModule::GetPushConstantBlock( + uint32_t index, + SpvReflectResult* p_result +) const +{ + return spvReflectGetPushConstantBlock( + &m_module, + index, + p_result); +} + +/*! @fn GetEntryPointPushConstant + + @param entry_point + @param index + @param p_result + @return + +*/ +inline const SpvReflectBlockVariable* ShaderModule::GetEntryPointPushConstantBlock( + const char* entry_point, + SpvReflectResult* p_result +) const +{ + return spvReflectGetEntryPointPushConstantBlock( + &m_module, + entry_point, + p_result); +} + + +/*! @fn ChangeDescriptorBindingNumbers + + @param p_binding + @param new_binding_number + @param new_set_number + @return + +*/ +inline SpvReflectResult ShaderModule::ChangeDescriptorBindingNumbers( + const SpvReflectDescriptorBinding* p_binding, + uint32_t new_binding_number, + uint32_t new_set_number +) +{ + return spvReflectChangeDescriptorBindingNumbers( + &m_module, + p_binding, + new_binding_number, + new_set_number); +} + + +/*! @fn ChangeDescriptorSetNumber + + @param p_set + @param new_set_number + @return + +*/ +inline SpvReflectResult ShaderModule::ChangeDescriptorSetNumber( + const SpvReflectDescriptorSet* p_set, + uint32_t new_set_number +) +{ + return spvReflectChangeDescriptorSetNumber( + &m_module, + p_set, + new_set_number); +} + + +/*! @fn ChangeInputVariableLocation + + @param p_input_variable + @param new_location + @return + +*/ +inline SpvReflectResult ShaderModule::ChangeInputVariableLocation( + const SpvReflectInterfaceVariable* p_input_variable, + uint32_t new_location) +{ + return spvReflectChangeInputVariableLocation( + &m_module, + p_input_variable, + new_location); +} + + +/*! @fn ChangeOutputVariableLocation + + @param p_input_variable + @param new_location + @return + +*/ +inline SpvReflectResult ShaderModule::ChangeOutputVariableLocation( + const SpvReflectInterfaceVariable* p_output_variable, + uint32_t new_location) +{ + return spvReflectChangeOutputVariableLocation( + &m_module, + p_output_variable, + new_location); +} + +} // namespace spv_reflect +#endif // defined(__cplusplus) +#endif // SPIRV_REFLECT_H diff --git a/SHADE_Engine/src/Graphics/Swapchain/SHSwapchainParams.h b/SHADE_Engine/src/Graphics/Swapchain/SHSwapchainParams.h new file mode 100644 index 00000000..dc64e07c --- /dev/null +++ b/SHADE_Engine/src/Graphics/Swapchain/SHSwapchainParams.h @@ -0,0 +1,22 @@ +#ifndef SH_SWAPCHAIN_PARAMS_H +#define SH_SWAPCHAIN_PARAMS_H + +#include +#include "Graphics/SHVulkanIncludes.h" + +namespace SHADE +{ + struct SHSwapchainParams + { + std::vector surfaceImageFormats; + std::vector depthFormats; + std::vector presentModes; + vk::ImageTiling imageTiling{ vk::ImageTiling::eOptimal }; + vk::FormatFeatureFlags formatFeatureFlag{ vk::FormatFeatureFlagBits::eDepthStencilAttachment }; + vk::ColorSpaceKHR colorSpace{ vk::ColorSpaceKHR::eSrgbNonlinear }; + bool vsyncOn{ false }; + uint32_t idealImageCount { 3 }; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.cpp b/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.cpp new file mode 100644 index 00000000..4dd6f9d6 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.cpp @@ -0,0 +1,346 @@ +#include "SHPch.h" +#include "SHVkSwapchain.h" +#include "Graphics/Devices/SHVkPhysicalDevice.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Windowing/Surface/SHVkSurface.h" +#include "Tools/SHLogger.h" +#include "Graphics/Images/SHVkImage.h" +#include "Graphics/Instance/SHVkInstance.h" + +namespace SHADE +{ + + SHVkSwapchain::SHSwapChainDetails::SHSwapChainDetails(Handle const& physicalDeviceHdl, Handle const& surfaceHdl) noexcept + { + if (auto result = physicalDeviceHdl->GetVkPhysicalDevice().getSurfaceCapabilitiesKHR(surfaceHdl->GetVkSurface(), &vkCapabilities); result != vk::Result::eSuccess) + SHVulkanDebugUtil::ReportVkError(result, "Failed to get physical device surface capabilities. "); + + vkSurfaceFormats = physicalDeviceHdl->GetVkPhysicalDevice().getSurfaceFormatsKHR(surfaceHdl->GetVkSurface()); + vkPresentModes = physicalDeviceHdl->GetVkPhysicalDevice().getSurfacePresentModesKHR(surfaceHdl->GetVkSurface()); + + if (vkSurfaceFormats.size() == 0) + SHLOG_ERROR("Failed to get surface formats from the physical device. "); + + if (vkPresentModes.size() == 0) + SHLOG_ERROR("Failed to get present modes from the physical device. "); + } + + vk::SurfaceFormatKHR SHVkSwapchain::ChooseSwapSurfaceFormat(std::vector const& surfaceFormats) const noexcept + { + // If there is only 1 format found and it is undefined + if (surfaceFormats.size() == 1) + { + if (surfaceFormats[0].format == vk::Format::eUndefined) + { + // select the format that the user requested as high priority and use that + vk::SurfaceFormatKHR format; + format.format = swapchainParams.surfaceImageFormats[0]; + format.colorSpace = swapchainParams.colorSpace; + return format; + } + } + else // if there is more than 1 format + { + // If more than 1 formats are requested + for (auto const& format : swapchainParams.surfaceImageFormats) + { + // Loop through formats available + for (auto const& foundFormat : surfaceFormats) + { + // if format and color space are available, return it + if (format == foundFormat.format && foundFormat.colorSpace == swapchainParams.colorSpace) + return foundFormat; + } + } + } + + // At this point, none of the formats requested by the user are found. Just return the first one given by Vulkan + return surfaceFormats[0]; + + } + + vk::Format SHVkSwapchain::ChooseDepthFormat(void) const noexcept + { + // For all the formats requested in order + for (auto const& format : swapchainParams.depthFormats) + { + vk::FormatProperties prop; + + // Attempt to get the property from vulkan, and return if matches depth format requested + prop = physicalDeviceHdl->GetVkPhysicalDevice().getFormatProperties(format); + if ((swapchainParams.imageTiling == vk::ImageTiling::eLinear && (prop.linearTilingFeatures & swapchainParams.formatFeatureFlag) == swapchainParams.formatFeatureFlag) || + (swapchainParams.imageTiling == vk::ImageTiling::eOptimal && (prop.optimalTilingFeatures & swapchainParams.formatFeatureFlag) == swapchainParams.formatFeatureFlag)) + return format; + } + + SHLOG_ERROR("Unable to find a required depth format. "); + return swapchainParams.depthFormats[0]; + + } + + vk::PresentModeKHR SHVkSwapchain::ChooseSwapPresentMode(std::vector const& presentModes) noexcept + { + // If the first requested format is already FIFO + if (swapchainParams.presentModes[0] == vk::PresentModeKHR::eFifo) + return vk::PresentModeKHR::eFifo; // return FIFO + + // Default to FIFO. Vulkan requires this format to be requested + vk::PresentModeKHR finalMode = vk::PresentModeKHR::eFifo; + + // If immediate is supported or not + bool immediateSupported = false; + + // Loop through all present modes of the user + for (auto const& mode : swapchainParams.presentModes) + { + // Loop through all present nodes found + for (auto const& foundMode : presentModes) + { + // See if immediate mode is supported + if (foundMode == vk::PresentModeKHR::eImmediate) + immediateSupported = true; + + // If the node requested for is found + if (foundMode == mode) + finalMode = foundMode; + } + } + + // If vsync is in + if (swapchainParams.vsyncOn) + { + // If the mode requested and found is immediate, use relaxed instead + if (finalMode == vk::PresentModeKHR::eImmediate) + finalMode = vk::PresentModeKHR::eFifoRelaxed; + } + else + { + // If immediate is supported, just choose immediate + if (immediateSupported) + finalMode = vk::PresentModeKHR::eImmediate; + } + + return vk::PresentModeKHR::eFifo; + + } + + vk::Extent2D SHVkSwapchain::ChooseSwapExtent(vk::SurfaceCapabilitiesKHR const& capabilities, uint32_t windowWidth, uint32_t windowHeight) noexcept + { + if (capabilities.currentExtent.width != UINT32_MAX) + return capabilities.currentExtent; + else + { + vk::Extent2D actualExtent = { windowWidth, windowHeight }; + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + void SHVkSwapchain::RetrieveSwapchainImages(void) noexcept + { + std::vector tempImageVector(imageCount); + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().getSwapchainImagesKHR(vkSwapchain, &imageCount, tempImageVector.data()); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to retrieve swapchain images. "); + return; + } + + for (uint32_t i = 0; i < imageCount; ++i) + { + swapchainImages[i]->LinkWithExteriorImage(tempImageVector[i], vk::ImageType::e2D, width, height, 1, 1, 0, vkSurfaceFormat.format, vkImageUsageFlags); + } + } + + SHVkSwapchain::SHVkSwapchain(Handle const& inPhysicalDeviceHdl, Handle const& inLogicalDeviceHdl, Handle const& surfaceHdl, uint32_t width, uint32_t height, SHSwapchainParams const& params) noexcept + : swapchainParams{} + , vkSwapchain{} + , vkSurfaceFormat{} + , vkPresentMode{} + , vkDepthFormat{} + , vkSwapExtent{} + , imageCount{0} + , logicalDeviceHdl{inLogicalDeviceHdl} + , swapchainImages{} + , physicalDeviceHdl {inPhysicalDeviceHdl} + { + swapchainParams = params; + + for (uint32_t i = 0; i < MAX_SWAPCHAIN_IMAGES; ++i) + { + swapchainImages[i] = SHVkInstance::GetResourceManager().Create(); + } + + Resize(surfaceHdl, width, height); + } + + SHVkSwapchain::~SHVkSwapchain(void) noexcept + { + logicalDeviceHdl->GetVkLogicalDevice().destroySwapchainKHR(vkSwapchain, nullptr); + } + + /***************************************************************************/ + /*! + + \brief + This function should be called every time the window is resized. + + \param surfaceHdl + surface needed for swapchain creation. + + \param width + width of images in swapchain. + + \param height + height of images in swapchain. + + */ + /***************************************************************************/ + void SHVkSwapchain::Resize(Handle const& surfaceHdl, uint32_t inWidth, uint32_t inHeight) noexcept + { + width = inWidth; + height = inHeight; + + SHSwapChainDetails swapchainDetails (physicalDeviceHdl, surfaceHdl); + + vkSurfaceFormat = ChooseSwapSurfaceFormat(swapchainDetails.vkSurfaceFormats); + vkPresentMode = ChooseSwapPresentMode(swapchainDetails.vkPresentModes); + vkDepthFormat = ChooseDepthFormat(); + vkSwapExtent = ChooseSwapExtent(swapchainDetails.vkCapabilities, width, height); + + // we want to add 1 because we want to always have a frame available at a vertical blanking period. Double buffering cannot always guarantee this because + // the CPU cannot always guarantee it will process the frame fast enough to shove the image into the queue before the VBP. When this happens, the GPU will continue + // processing the current frame and wait for the next VBP, which is wasted idling time on both CPU and GPU. + imageCount = std::max(swapchainDetails.vkCapabilities.minImageCount, swapchainParams.idealImageCount); + if (swapchainDetails.vkCapabilities.maxImageCount > 0 && imageCount > swapchainDetails.vkCapabilities.maxImageCount) + imageCount = swapchainDetails.vkCapabilities.maxImageCount; + + vk::SwapchainKHR oldSwapchain = vkSwapchain; + vkSwapchain = nullptr; + + // Wait for device to finish rendering + logicalDeviceHdl->GetVkLogicalDevice().waitIdle(); + + vk::SwapchainCreateInfoKHR swapchainCreateInfo{}; + + swapchainCreateInfo.pNext = nullptr; + swapchainCreateInfo.surface = surfaceHdl->GetVkSurface(); + swapchainCreateInfo.minImageCount = imageCount; + swapchainCreateInfo.imageColorSpace = vkSurfaceFormat.colorSpace; + swapchainCreateInfo.imageFormat = vkSurfaceFormat.format; + swapchainCreateInfo.imageExtent = vkSwapExtent; + swapchainCreateInfo.imageArrayLayers = 1; + + swapchainCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment; + swapchainCreateInfo.imageSharingMode = vk::SharingMode::eExclusive; // queue family and graphics family are the same + swapchainCreateInfo.pQueueFamilyIndices = nullptr; // being explicit + swapchainCreateInfo.queueFamilyIndexCount = 0; // being explicit + swapchainCreateInfo.preTransform = swapchainDetails.vkCapabilities.currentTransform; // identity bit + + // Flag to use when surface is composited together with other surfaces on certain window systems + swapchainCreateInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + swapchainCreateInfo.presentMode = vkPresentMode; + swapchainCreateInfo.clipped = VK_TRUE; // clips obscured pixels + swapchainCreateInfo.oldSwapchain = oldSwapchain; + + if (swapchainDetails.vkCapabilities.supportedUsageFlags & vk::ImageUsageFlagBits::eTransferSrc) + swapchainCreateInfo.imageUsage |= vk::ImageUsageFlagBits::eTransferSrc; + if (swapchainDetails.vkCapabilities.supportedUsageFlags & vk::ImageUsageFlagBits::eTransferDst) + swapchainCreateInfo.imageUsage |= vk::ImageUsageFlagBits::eTransferDst; + + + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createSwapchainKHR(&swapchainCreateInfo, nullptr, &vkSwapchain); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Swapchain."); + } + else + SHVulkanDebugUtil::ReportVkSuccess("Successfully created Swapchain. "); + + uint32_t tempCount = 0; + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().getSwapchainImagesKHR(vkSwapchain, &tempCount, nullptr); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to get swapchain image count. "); + } + else + { + if (tempCount == imageCount) + SHVulkanDebugUtil::ReportVkSuccess("Images in swapchain matches requested. "); + else + { + SHLOG_ERROR("Images in swapchain do not match requested. "); + } + } + + // Save the image usage flags + vkImageUsageFlags = swapchainCreateInfo.imageUsage; + + // Retrieve and store handles to swapchain images + RetrieveSwapchainImages(); + + + if (oldSwapchain) + { + logicalDeviceHdl->GetVkLogicalDevice().destroySwapchainKHR(oldSwapchain, nullptr); + } + + } + + /***************************************************************************/ + /*! + + \brief + Get the number of swapchain images. + + \return + The number of swapchain images. + + */ + /***************************************************************************/ + uint32_t SHVkSwapchain::GetNumImages(void) const noexcept + { + return imageCount; + } + + /***************************************************************************/ + /*! + + \brief + Returns one of the swapchain images. + + \param index + Index of the image we want to obtain. + + \return + A handle to the swapchain image. + + */ + /***************************************************************************/ + Handle SHVkSwapchain::GetSwapchainImage(uint32_t index) const noexcept + { + // Index is not valid + if (index >= imageCount) + { + SHLOG_ERROR("Attempting to get images with index that is out of range. "); + return {}; + } + + return swapchainImages[index]; + } + + vk::SwapchainKHR const& SHVkSwapchain::GetVkSwapchain(void) const noexcept + { + return vkSwapchain; + } + + vk::SurfaceFormatKHR SHVkSwapchain::GetSurfaceFormatKHR(void) const noexcept + { + return vkSurfaceFormat; + } + + vk::Format SHVkSwapchain::GetDepthFormat(void) const noexcept + { + return vkDepthFormat; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.h b/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.h new file mode 100644 index 00000000..28bd7a4e --- /dev/null +++ b/SHADE_Engine/src/Graphics/Swapchain/SHVkSwapchain.h @@ -0,0 +1,107 @@ +#ifndef SH_VK_SWAPCHAIN_H +#define SH_VK_SWAPCHAIN_H + +#include +#include +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/ResourceLibrary.h" +#include "Graphics/Swapchain/SHSwapchainParams.h" + +namespace SHADE +{ + class SHVkSurface; + class SHVkLogicalDevice; + class SHVkPhysicalDevice; + class SHVkImage; + + + class SHVkSwapchain + { + private: + static constexpr int MAX_SWAPCHAIN_IMAGES = 10; + + struct SHSwapChainDetails + { + vk::SurfaceCapabilitiesKHR vkCapabilities{}; + std::vector vkSurfaceFormats{}; + std::vector vkPresentModes{}; + + SHSwapChainDetails(Handle const& physicalDeviceHdl, Handle const& surfaceHdl) noexcept; + }; + + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! The minimum number of presentable images that the application needs + uint32_t imageCount; + + //! width of images in the swapchain + uint32_t width; + + //! height of images in the swapchain + uint32_t height; + + //! We want to keep a copy of this to help us select formats during resizing + SHSwapchainParams swapchainParams; + + //! Vulkan swapchain handle + vk::SwapchainKHR vkSwapchain; + + //! The color format the swapchain will use the interpret the data in its queue + vk::SurfaceFormatKHR vkSurfaceFormat; + + //! The method the swapchain will use to handle its queue + vk::PresentModeKHR vkPresentMode; + + //! The depth format the swapchain will use + vk::Format vkDepthFormat; + + //! Images that are pushed the the swapchain have to be off this size/resolution + vk::Extent2D vkSwapExtent; + + //! Swapchain image usage flags + vk::ImageUsageFlags vkImageUsageFlags; + + //! Handle to the logical device that created this swapchain + Handle logicalDeviceHdl; + + //! Store it for convenience later + Handle physicalDeviceHdl; + + //! Swapchain images + std::array, MAX_SWAPCHAIN_IMAGES> swapchainImages; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + vk::SurfaceFormatKHR ChooseSwapSurfaceFormat (std::vector const& surfaceFormats) const noexcept; + vk::Format ChooseDepthFormat (void) const noexcept; + vk::PresentModeKHR ChooseSwapPresentMode (std::vector const& presentModes) noexcept; + vk::Extent2D ChooseSwapExtent (vk::SurfaceCapabilitiesKHR const& capabilities, uint32_t windowWidth, uint32_t windowHeight) noexcept; + void RetrieveSwapchainImages (void) noexcept; + + public: + /*-----------------------------------------------------------------------*/ + /* CTOR AND DTOR */ + /*-----------------------------------------------------------------------*/ + SHVkSwapchain (Handle const& inPhysicalDeviceHdl, Handle const& inLogicalDeviceHdl, Handle const& surfaceHdl, uint32_t width, uint32_t height, SHSwapchainParams const& params) noexcept; + ~SHVkSwapchain (void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void Resize (Handle const& surfaceHdl, uint32_t inWidth, uint32_t inHeight) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + uint32_t GetNumImages (void) const noexcept; + Handle GetSwapchainImage (uint32_t index) const noexcept; + vk::SwapchainKHR const& GetVkSwapchain (void) const noexcept; + vk::SurfaceFormatKHR GetSurfaceFormatKHR (void) const noexcept; + vk::Format GetDepthFormat (void) const noexcept; + }; +} + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Synchronization/SHVkFence.cpp b/SHADE_Engine/src/Graphics/Synchronization/SHVkFence.cpp new file mode 100644 index 00000000..4fcffda3 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Synchronization/SHVkFence.cpp @@ -0,0 +1,116 @@ +#include "SHPch.h" +#include "SHVkFence.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Debugging/SHVulkanDebugUtil.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Non-Default ctor. Simply creates a fence. + + \param inLogicalDeviceHdl + Logical device for the creation and destruction. + + */ + /***************************************************************************/ + SHVkFence::SHVkFence(Handle const& inLogicalDeviceHdl) noexcept + : logicalDeviceHdl{inLogicalDeviceHdl} + , vkFence{VK_NULL_HANDLE} + { + vk::FenceCreateInfo fenceCreateInfo{}; + fenceCreateInfo.flags = vk::FenceCreateFlagBits::eSignaled; + + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createFence(&fenceCreateInfo, nullptr, &vkFence); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Vulkan Fence. "); + return; + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created fence. "); + } + } + + SHVkFence::SHVkFence(SHVkFence&& rhs) noexcept + : vkFence{rhs.vkFence} + , logicalDeviceHdl {rhs.logicalDeviceHdl} + { + rhs.vkFence = VK_NULL_HANDLE; + } + + SHVkFence& SHVkFence::operator=(SHVkFence&& rhs) noexcept + { + if (&rhs == this) + return *this; + + vkFence = rhs.vkFence; + logicalDeviceHdl = rhs.logicalDeviceHdl; + rhs.vkFence = VK_NULL_HANDLE; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Destructor. Simply destroys the fence. + + */ + /***************************************************************************/ + SHVkFence::~SHVkFence(void) noexcept + { + if (vkFence) + logicalDeviceHdl->GetVkLogicalDevice().destroyFence(vkFence, nullptr); + } + + bool SHVkFence::Wait(bool waitAll, uint64_t timer) noexcept + { + while (true) + { + // Wait for frame fence to be signaled (usually will be signaled after image is done being used for presentation) + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().waitForFences(1, &vkFence, waitAll, timer); result != vk::Result::eSuccess) + { + if (result == vk::Result::eTimeout) + { + std::cout << "Fence timeout. " << std::endl; + SHVulkanDebugUtil::ReportVkWarning(result, "Fence timeout. "); + return false; + } + + SHVulkanDebugUtil::ReportVkWarning(result, "Fence error. "); + } + else + return true; + } + } + + void SHVkFence::Reset(void) noexcept + { + // Reset fences + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().resetFences(1, &vkFence); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkWarning(result, "Failed to reset fence!"); + } + } + + /***************************************************************************/ + /*! + + \brief + Getter for the Vulkan fence. + + \return + The Vulkan fence handle. + + */ + /***************************************************************************/ + vk::Fence SHVkFence::GetVkFence(void) const noexcept + { + return vkFence; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Synchronization/SHVkFence.h b/SHADE_Engine/src/Graphics/Synchronization/SHVkFence.h new file mode 100644 index 00000000..02445d42 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Synchronization/SHVkFence.h @@ -0,0 +1,44 @@ +#ifndef SH_VK_FENCE_H +#define SH_VK_FENCE_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + + class SHVkFence + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Vulkan handle + vk::Fence vkFence; + + //! Logical device required for creation and destruction + Handle logicalDeviceHdl; + + public: + SHVkFence (Handle const& inLogicalDeviceHdl) noexcept; + ~SHVkFence (void) noexcept; + + SHVkFence (SHVkFence&& rhs) noexcept; + SHVkFence& operator=(SHVkFence && rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + bool Wait (bool waitAll, uint64_t timer) noexcept; + void Reset (void) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + vk::Fence GetVkFence(void) const noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Synchronization/SHVkSemaphore.cpp b/SHADE_Engine/src/Graphics/Synchronization/SHVkSemaphore.cpp new file mode 100644 index 00000000..b5eaa9aa --- /dev/null +++ b/SHADE_Engine/src/Graphics/Synchronization/SHVkSemaphore.cpp @@ -0,0 +1,85 @@ +#include "SHPch.h" +#include "SHVkSemaphore.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Debugging/SHVulkanDebugUtil.h" + +namespace SHADE +{ + /***************************************************************************/ + /*! + + \brief + Non-Default ctor. Simply creates a Semaphore. + + \param inLogicalDeviceHdl + Logical device for the creation and destruction. + + */ + /***************************************************************************/ + SHVkSemaphore::SHVkSemaphore(Handle const& inLogicalDeviceHdl) noexcept + : logicalDeviceHdl{ inLogicalDeviceHdl } + , vkSem{} + { + vk::SemaphoreCreateInfo semCreateInfo{}; + + if (auto result = logicalDeviceHdl->GetVkLogicalDevice().createSemaphore(&semCreateInfo, nullptr, &vkSem); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Failed to create Vulkan Semaphore. "); + return; + } + else + { + SHVulkanDebugUtil::ReportVkSuccess("Successfully created Semaphore. "); + } + } + + SHVkSemaphore::SHVkSemaphore(SHVkSemaphore&& rhs) noexcept + : vkSem {rhs.vkSem} + , logicalDeviceHdl{rhs.logicalDeviceHdl} + { + rhs.vkSem = VK_NULL_HANDLE; + } + + SHVkSemaphore& SHVkSemaphore::operator=(SHVkSemaphore&& rhs) noexcept + { + if (&rhs == this) + return *this; + + vkSem = rhs.vkSem; + logicalDeviceHdl = rhs.logicalDeviceHdl; + rhs.vkSem = VK_NULL_HANDLE; + + return *this; + } + + /***************************************************************************/ + /*! + + \brief + Destructor. Simply destroys the Semaphore. + + */ + /***************************************************************************/ + SHVkSemaphore::~SHVkSemaphore(void) noexcept + { + if (vkSem) + logicalDeviceHdl->GetVkLogicalDevice().destroySemaphore(vkSem, nullptr); + } + + /***************************************************************************/ + /*! + + \brief + Getter for the Vulkan Semaphore. + + \return + The Vulkan Semaphore handle. + + */ + /***************************************************************************/ + vk::Semaphore const& SHVkSemaphore::GetVkSem(void) const noexcept + { + return vkSem; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Synchronization/SHVkSemaphore.h b/SHADE_Engine/src/Graphics/Synchronization/SHVkSemaphore.h new file mode 100644 index 00000000..57f7d7df --- /dev/null +++ b/SHADE_Engine/src/Graphics/Synchronization/SHVkSemaphore.h @@ -0,0 +1,38 @@ +#ifndef SH_VK_SEMAPHORE_H +#define SH_VK_SEMAPHORE_H + +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/Handle.h" + +namespace SHADE +{ + class SHVkLogicalDevice; + + class SHVkSemaphore + { + private: + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER VARIABLES */ + /*-----------------------------------------------------------------------*/ + //! Vulkan handle + vk::Semaphore vkSem; + + //! Logical device required for creation and destruction + Handle logicalDeviceHdl; + + public: + SHVkSemaphore(Handle const& inLogicalDeviceHdl) noexcept; + ~SHVkSemaphore(void) noexcept; + + SHVkSemaphore (SHVkSemaphore&& rhs) noexcept; + SHVkSemaphore& operator=(SHVkSemaphore&& rhs) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + vk::Semaphore const& GetVkSem(void) const noexcept; + + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/VertexDescriptors/SHVertexAttribute.cpp b/SHADE_Engine/src/Graphics/VertexDescriptors/SHVertexAttribute.cpp new file mode 100644 index 00000000..7a5767aa --- /dev/null +++ b/SHADE_Engine/src/Graphics/VertexDescriptors/SHVertexAttribute.cpp @@ -0,0 +1,13 @@ +#include "SHPch.h" +#include "SHVertexAttribute.h" + +namespace SHADE +{ + SHVertexAttribute::SHVertexAttribute(SHAttribFormat format, uint32_t inOffset/* = 0*/) noexcept + : attribFormat {format} + , offset {inOffset} + { + + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/VertexDescriptors/SHVertexAttribute.h b/SHADE_Engine/src/Graphics/VertexDescriptors/SHVertexAttribute.h new file mode 100644 index 00000000..fd8ee3d1 --- /dev/null +++ b/SHADE_Engine/src/Graphics/VertexDescriptors/SHVertexAttribute.h @@ -0,0 +1,38 @@ +#ifndef SH_VERTEX_ATTRIBUTE_H +#define SH_VERTEX_ATTRIBUTE_H + +#include "Graphics/SHVulkanIncludes.h" + +namespace SHADE +{ + enum class SHAttribFormat + { + // Can add more when needed. Only remember to upate GetCountFromFormat when adding. + FLOAT_1D, + FLOAT_2D, + FLOAT_3D, + FLOAT_4D, + + // TODO: Since matrices are interpreted very different in the shaders, a test here + // needs to be done: Can we have 2 attributes at offsets 0 and 8 respectively such + // that a mat2 can be interpreted as (x, y, x, y), (o, o, o, o) instead of (x, y, o, o), (o, o, o, o)? + MAT_2D, + MAT_3D, + MAT_4D + }; + + struct SHVertexAttribute + { + //! Attribute format + SHAttribFormat attribFormat = SHAttribFormat::FLOAT_1D; + + //! Offset of the attribute in the binding. This is only used if the binding is + //! initialized to calculate offset instead of use the one in this struct. + uint32_t offset = 0; + + SHVertexAttribute(SHAttribFormat format, uint32_t inOffset = 0) noexcept; + }; + +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Windowing/SHWIndowMap.cpp b/SHADE_Engine/src/Graphics/Windowing/SHWIndowMap.cpp new file mode 100644 index 00000000..b13feced --- /dev/null +++ b/SHADE_Engine/src/Graphics/Windowing/SHWIndowMap.cpp @@ -0,0 +1,32 @@ +#include "SHPch.h" +#include "SHWindow.h" +#include "SHWindowMap.h" + +namespace SHADE +{ + void SHWindowMap::AddWindow(HWND hwnd, WindowPtr window) + { + if (hwnd && !GetWindow(hwnd)) + { + if (windowMap.empty()) + mainWindow = hwnd; + windowMap.insert({ hwnd, window }); + } + } + + SHWindowMap::WindowPtr SHWindowMap::GetMainWindow() + { + return GetWindow(mainWindow); + } + + SHWindowMap::WindowPtr SHWindowMap::GetWindow(HWND hwnd) + { + auto it = windowMap.find(hwnd); + return it != windowMap.end() ? it->second : nullptr; + } + + void SHWindowMap::RemoveWindow(HWND hwnd) + { + windowMap.erase(hwnd); + } +} diff --git a/SHADE_Engine/src/Graphics/Windowing/SHWindow.cpp b/SHADE_Engine/src/Graphics/Windowing/SHWindow.cpp new file mode 100644 index 00000000..6ac6672b --- /dev/null +++ b/SHADE_Engine/src/Graphics/Windowing/SHWindow.cpp @@ -0,0 +1,391 @@ +#include "SHPch.h" +#include "SHWindowMap.h" +#include "SHWindow.h" + + +namespace SHADE +{ + SHWindow::SHWindow() + { + } + + SHWindow::~SHWindow() + { + if (wndHWND) + { + Close(); + } + } + + bool SHWindow::Create(WindowData windowData, HWND parent) + { + return Create(::GetModuleHandle(nullptr), nullptr, nullptr, 0, windowData, parent); + } + + bool SHWindow::Create(HINSTANCE in_hInstance, [[maybe_unused]] HINSTANCE hPrevInstance, [[maybe_unused]] LPWSTR lpCmdLine, [[maybe_unused]] int nCmdShow, WindowData const& windowData, HWND parent) + { + this->hInstance = in_hInstance; + this->wndData = windowData; + + windowBeingCreated = this; + + wndClass.cbSize = sizeof(WNDCLASSEX); + wndClass.style = CS_HREDRAW | CS_VREDRAW; + wndClass.lpfnWndProc = WndProcStatic; + wndClass.cbWndExtra = WS_EX_NOPARENTNOTIFY; //Do not notify parent wnd if any + wndClass.hInstance = hInstance; + wndClass.hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); + wndClass.hIconSm = ::LoadIcon(nullptr, IDI_WINLOGO); + wndClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW); + wndClass.hbrBackground = reinterpret_cast(::GetStockObject(windowData.bgColor)); + wndClass.lpszClassName = wndData.name.c_str(); + + if (!RegisterClassEx(&wndClass)) + { + return false; + } + + int screenWidth = GetSystemMetrics(SM_CXSCREEN); + int screenHeight = GetSystemMetrics(SM_CYSCREEN); + + DWORD dwExStyle{}; //extended window style + + if (wndData.isFullscreen) + { + dwExStyle = WS_EX_APPWINDOW | WS_EX_ACCEPTFILES; + dwStyle = WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + } + else + { + dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; + + if (wndData.frameEnabled) + { + dwStyle = WNDSTYLE::SHWS_WINDOWED; + } + else + { + dwStyle = WNDSTYLE::SHWS_BORDERLESS; + } + if(!wndData.resizable) + { + dwStyle ^= WS_THICKFRAME; + } + if(!wndData.minimizable) + { + dwStyle ^= WS_MINIMIZEBOX; + } + if (!wndData.maximizable) + { + dwStyle ^= WS_MAXIMIZEBOX; + } + if(parent != nullptr) + { + dwStyle |= WS_CHILDWINDOW; + } + } + + //DPI_AWARENESS_CONTEXT prevDPIContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + RECT windowRect; + windowRect.left = wndData.x; //or CW_USEDEFAULT ? + windowRect.right = wndData.isFullscreen ? screenWidth : wndData.width; + windowRect.top = wndData.y; //or CW_USEDEFAULT ? + windowRect.bottom = wndData.isFullscreen ? screenHeight : wndData.height; + AdjustWindowRectEx(&windowRect, dwStyle, false, dwExStyle); + + //Create window + wndHWND = CreateWindowEx(0, (LPWSTR) wndData.name.c_str(), (LPWSTR)wndData.title.c_str(), dwStyle, 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, parent, NULL, hInstance, NULL); + + if (!wndHWND) + { + //DWORD err = GetLastError(); + return false; + } + + if (wndData.isVisible) + { + ShowWindow(wndHWND, SW_SHOW); + SetForegroundWindow(wndHWND); + SetFocus(wndHWND); + } + + if (!wndData.icoPath.empty()) + { + //HICON hIcon = ::LoadIcon(nullptr, MAKEINTRESOURCE(wndData.icoPath.c_str())); + //::SendMessage(wndHWND, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + //::SendMessage(wndHWND, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + } + + windowBeingCreated = nullptr; + + return true; + } + + void SHWindow::Destroy() + { + windowMap.RemoveWindow(wndHWND); + if(::IsWindow(wndHWND)) + { + ::DestroyWindow(wndHWND); + } + wndHWND = nullptr; + } + + std::wstring SHWindow::GetTitle() const + { + char str[MAX_BUFFER]{ 0 }; + GetWindowTextA(wndHWND, str, MAX_BUFFER); + return std::wstring(std::begin(str), std::end(str)); + } + + void SHWindow::SetTitle(std::wstring title) + { + wndData.title = title; + SetWindowText(wndHWND, LPCWSTR(wndData.title.c_str())); + } + + SHWindow::SHVec2 SHWindow::GetPosition() const + { + RECT rect; + GetWindowRect(wndHWND, &rect); + return SHVec2(static_cast(rect.left), static_cast(rect.top)); + } + + void SHWindow::SetPosition(unsigned x, unsigned y) + { + SetWindowPos(wndHWND, 0, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE); + wndData.x = x; + wndData.y = y; + } + + SHWindow::SHVec2 SHWindow::GetWindowSize() const + { + RECT rect; + GetClientRect(wndHWND, &rect); + return SHVec2(static_cast(rect.right - rect.left), static_cast(rect.bottom - rect.top)); + } + + SHWindow::SHVec2 SHWindow::GetCurrentDisplaySize() const + { + unsigned screenWidth = GetSystemMetrics(SM_CXSCREEN); + unsigned screenHeight = GetSystemMetrics(SM_CYSCREEN); + return SHVec2(screenWidth, screenHeight); + } + + void SHWindow::SetMouseVisible(bool show) + { + ShowCursor(show ? TRUE : FALSE); + } + + void SHWindow::SetMousePosition(unsigned x, unsigned y) + { + SetCursorPos(x, y); + } + + //unsigned SHWindow::GetBGColor() + //{ + // return 0; + //} + + void SHWindow::SetBGColor(unsigned color) + { + bgColor = color; + } + + void SHWindow::Minimize() + { + ShowWindow(wndHWND, SW_MINIMIZE); + } + + void SHWindow::Maximize() + { + if (!IsZoomed(wndHWND)) + { + ShowWindow(wndHWND, SW_MAXIMIZE); + } + else + { + ShowWindow(wndHWND, SW_RESTORE); + } + } + + void SHWindow::Close() + { + ::CloseWindow(wndHWND); + } + + bool SHWindow::WindowShouldClose() + { + bool bContinue = true; + if (wndHWND == nullptr) + return true; + { + MSG Message; + while (PeekMessageW(&Message, NULL, 0, 0, PM_REMOVE)) + { + if (WM_QUIT == Message.message) + { + bContinue = false; + break; + } + else + { + TranslateMessage(&Message); + DispatchMessageW(&Message); + } + } + } + return !bContinue; + } + + //void SHWindow::SetTaskbarProgress(float progress) + //{ + + //} + + HINSTANCE SHWindow::GetHInstance() + { + return hInstance; + } + + HWND SHWindow::GetHWND() + { + return wndHWND; + } + + const WindowData SHWindow::GetWindowData() + { + return wndData; + } + + SHWindow::CALLBACKID SHWindow::RegisterWindowSizeCallback(WindowResizeCallbackFn callback) + { + windowResizeCallbacks.try_emplace(windowResizeCallbackCount, callback); + return windowResizeCallbackCount++; + } + + void SHWindow::UnregisterWindowSizeCallback(CALLBACKID const& callbackid) + { + windowResizeCallbacks.erase(callbackid); + } + + LRESULT SHWindow::WndProcStatic(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) + { + auto window = windowMap.GetWindow(hwnd); + if (!window) + { + window = windowBeingCreated; + if (window) + { + window->SetHWND(hwnd); + windowMap.AddWindow(hwnd, window); + } + } + + if (window) + { + return window->WndProc(hwnd, msg, wparam, lparam); + } + else + { + return ::DefWindowProc(hwnd, msg, wparam, lparam); + } + } + + LRESULT SHWindow::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) + { + return WndProcDef(hwnd, msg, wparam, lparam); + } + + LRESULT SHWindow::WndProcDef(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) + { + switch (msg) + { + case WM_CREATE: + OnCreate(hwnd, reinterpret_cast(wparam)); + break; + case WM_DESTROY: + OnDestroy(); + return 0; + case WM_DROPFILES: + //OnFileDrop(reinterpret_cast(wparam)); + break; + case WM_ENTERSIZEMOVE: + case WM_EXITSIZEMOVE: + { + SIZE size{}; + //OnSize(msg, 0, size); + break; + } + case WM_GETMINMAXINFO: + { + LPMINMAXINFO lpminmaxinfo = (LPMINMAXINFO)lparam; + lpminmaxinfo->ptMinTrackSize = { static_cast(wndData.minWidth), static_cast(wndData.minHeight) }; + lpminmaxinfo->ptMaxTrackSize = { static_cast(wndData.maxWidth), static_cast(wndData.maxHeight)}; + break; + } + case WM_SIZE: + { + SIZE size = { LOWORD(lparam), HIWORD(lparam) }; + OnSize(msg, static_cast(wparam), size); + break; + } + case WM_PAINT: + ::ValidateRect(hwnd, nullptr); + break; + case WM_WINDOWPOSCHANGING: + { + OnPosChange(reinterpret_cast(lparam)); + break; + } + default: + return ::DefWindowProc(hwnd, msg, wparam, lparam); + } + return ::DefWindowProc(hwnd, msg, wparam, lparam); + } + + void SHWindow::SetHWND(HWND hwnd) + { + this->wndHWND = hwnd; + } + + void SHWindow::OnCreate([[maybe_unused]] HWND hwnd, [[maybe_unused]] LPCREATESTRUCT create_struct) + { + + } + + void SHWindow::OnDestroy() + { + this->Destroy(); + } + + //void SHWindow::OnFileDrop(HDROP drop) + //{ + //} + + void SHWindow::OnSize([[maybe_unused]] UINT msg,[[maybe_unused]] UINT type, SIZE size) + { + wndData.width = static_cast(size.cx); + wndData.height = static_cast(size.cy); + for (auto const& entry : windowResizeCallbacks) + { + entry.second(static_cast(wndData.width), static_cast(wndData.height)); + } + } + + void SHWindow::OnPosChange(LPWINDOWPOS pos) + { + wndData.x = pos->x; + wndData.y = pos->y; + } + + void SHWindow::OnPaint(HDC hdc, LPPAINTSTRUCT paint) + { + ::BeginPaint(wndHWND, paint); + const RECT* rect = &wndRect; + ::FillRect(hdc, rect, reinterpret_cast(::GetStockObject(wndData.bgColor))); + ::EndPaint(wndHWND, paint); + } + +} diff --git a/SHADE_Engine/src/Graphics/Windowing/SHWindow.h b/SHADE_Engine/src/Graphics/Windowing/SHWindow.h new file mode 100644 index 00000000..a70058a1 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Windowing/SHWindow.h @@ -0,0 +1,169 @@ +#ifndef SH_WINDOW_H +#define SH_WINDOW_H + +#include +#include +#include +#include "SHWindowMap.h" + +namespace SHADE +{ + constexpr uint16_t MAX_BUFFER = 1024; + + enum WNDSTYLE : DWORD + { + SHWS_WINDOWED = WS_OVERLAPPEDWINDOW, + SHWS_BORDERLESS = WS_CAPTION | WS_OVERLAPPED | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX + }; + + struct WindowData + { + unsigned x = 0; + + unsigned y = 0; + + unsigned width = 1920; + + unsigned height = 1080; + + unsigned minWidth = 300; + + unsigned minHeight = 300; + + unsigned maxWidth = USHRT_MAX; + + unsigned maxHeight = USHRT_MAX; + + bool centered = true; + + bool resizable = true; + + bool closable = true; + + bool minimizable = true; + + bool maximizable = true; + + //bool canFullscreen = true; + + unsigned bgColor = WHITE_BRUSH; + + //bool transparent = false; + + bool frameEnabled = true; + + bool shadowEnabled = true; + + bool isVisible = true; + + bool isFullscreen = false; + + bool modal = false; + + std::wstring title = L"SHADE ENGINE"; + + std::wstring name = L"SHADEEngineApp"; + + std::string icoPath = ""; + }; + + class SHWindow + { + public: + using SHVec2 = std::pair; + typedef std::function WindowResizeCallbackFn; + typedef uint16_t CALLBACKID; + SHWindow(); + + ~SHWindow(); + + bool Create(WindowData = WindowData(), HWND parent = nullptr); + + bool Create(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow, WindowData const& windowData = WindowData(), HWND parent = NULL); + + void Destroy(); + + std::wstring GetTitle() const; + + void SetTitle(std::wstring title); + + SHVec2 GetPosition() const; + + void SetPosition(unsigned x, unsigned y); + //void SetPosition(SHMathVec2U); + + SHVec2 GetWindowSize() const; + + //Get size of display the window is in (whichever window contains the window origin) + SHVec2 GetCurrentDisplaySize() const; + + void SetMouseVisible(bool show); + + void SetMousePosition(unsigned x, unsigned y); + + //unsigned GetBGColor(); + + void SetBGColor(unsigned color); + + void Minimize(); + + void Maximize(); + + void Close(); + + bool WindowShouldClose(); + //void SetTaskbarProgress(float progress); + + HINSTANCE GetHInstance(); + + HWND GetHWND(); + + const WindowData GetWindowData(); + + CALLBACKID RegisterWindowSizeCallback(WindowResizeCallbackFn); + void UnregisterWindowSizeCallback(CALLBACKID const& callbackid); + + protected: + static LRESULT CALLBACK WndProcStatic(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + LRESULT WndProcDef(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + void SetHWND(HWND hwnd); + + + HWND wndHWND = nullptr; + + HINSTANCE hInstance = nullptr; + + WindowData wndData; + + WNDCLASSEX wndClass{}; + + RECT wndRect{}; + + DEVMODE dmScreenInfo{}; + + DWORD exStyle{}; + DWORD dwStyle{}; + + unsigned bgColor = 0xFFFFFF; + + HFONT font; + + std::unordered_map windowResizeCallbacks; + CALLBACKID windowResizeCallbackCount{}; + //TODO: Shift to events abstraction + + void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); + void OnDestroy(); + //void OnFileDrop(HDROP drop); + void OnSize(UINT msg, UINT type, SIZE size); + void OnPosChange(LPWINDOWPOS pos); + void OnPaint(HDC hdc, LPPAINTSTRUCT paint); + }; + static SHWindowMap windowMap; + static SHWindow* windowBeingCreated = nullptr; +} +#endif diff --git a/SHADE_Engine/src/Graphics/Windowing/SHWindowMap.h b/SHADE_Engine/src/Graphics/Windowing/SHWindowMap.h new file mode 100644 index 00000000..008981a3 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Windowing/SHWindowMap.h @@ -0,0 +1,33 @@ +#ifndef SH_WINDOW_MAP_H +#define SH_WINDOW_MAP_H + +#include +#include + +namespace SHADE +{ + class SHWindow; + class SHWindowMap + { + typedef SHWindow* WindowPtr; + public: + void AddWindow(HWND hwnd, WindowPtr window); + + WindowPtr GetMainWindow(); + WindowPtr GetWindow(HWND hwnd); + + void RemoveWindow(HWND hwnd); + + SHWindowMap() = default; + ~SHWindowMap() = default; + private: + SHWindowMap(const SHWindowMap&) = delete; + SHWindowMap operator=(const SHWindowMap&) = delete; + + std::unordered_map windowMap; + + HWND mainWindow = nullptr; + }; +} + +#endif diff --git a/SHADE_Engine/src/Graphics/Windowing/Surface/SHVkSurface.cpp b/SHADE_Engine/src/Graphics/Windowing/Surface/SHVkSurface.cpp new file mode 100644 index 00000000..f9cb16c6 --- /dev/null +++ b/SHADE_Engine/src/Graphics/Windowing/Surface/SHVkSurface.cpp @@ -0,0 +1,56 @@ +#include "SHPch.h" +#include "SHVkSurface.h" +#include "Graphics/Devices/SHVkPhysicalDevice.h" +#include "Graphics/Devices/SHVkLogicalDevice.h" +#include "Graphics/Instance/SHVkInstance.h" +#include "Graphics/Debugging/SHVulkanDebugUtil.h" +#include "Tools/SHLogger.h" + +namespace SHADE +{ + SHVkSurface::SHVkSurface(HWND const& windowHandle, Handle const& physicalDeviceHdl, Handle const& logicalDeviceHdl) noexcept + { + // Surface create info + vk::Win32SurfaceCreateInfoKHR surfaceCreateInfo + { + .pNext = nullptr, + .flags = static_cast(0), + .hinstance = GetModuleHandle(nullptr), + .hwnd = windowHandle + }; + + // Create the win32 surface + if (auto result = SHVkInstance::GetVkInstance().createWin32SurfaceKHR (&surfaceCreateInfo, nullptr, &vkSurface); result != vk::Result::eSuccess) + { + SHVulkanDebugUtil::ReportVkError(result, "Vulkan failed to create window surface! "); + } + + // The reason why we check this here and not in SHLogicalDevice is because the surface is needed + vk::Bool32 presentSupport = VK_FALSE; + + SHQueueFamilyIndex graphicsQueueIndex = logicalDeviceHdl->GetQueueFamilyIndex(SH_Q_FAM::GRAPHICS); + + if (graphicsQueueIndex == static_cast(SH_Q_FAM::INVALID)) + { + SHLOG_ERROR("Failed to find a graphics queue."); + return; + } + + auto result = physicalDeviceHdl->GetVkPhysicalDevice().getSurfaceSupportKHR(graphicsQueueIndex, vkSurface, &presentSupport); + if (result != vk::Result::eSuccess || !presentSupport) + { + SHVulkanDebugUtil::ReportVkError(result, "Physical device does not have surface support. "); + } + } + + SHVkSurface::~SHVkSurface(void) noexcept + { + SHVkInstance::GetVkInstance().destroySurfaceKHR(vkSurface, nullptr); + } + + vk::SurfaceKHR SHVkSurface::GetVkSurface(void) const noexcept + { + return vkSurface; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Windowing/Surface/SHVkSurface.h b/SHADE_Engine/src/Graphics/Windowing/Surface/SHVkSurface.h new file mode 100644 index 00000000..0328fecc --- /dev/null +++ b/SHADE_Engine/src/Graphics/Windowing/Surface/SHVkSurface.h @@ -0,0 +1,29 @@ +#ifndef SH_VK_SURFACE_H +#define SH_VK_SURFACE_H + +#include +#include "Graphics/SHVulkanIncludes.h" +#include "Resource/ResourceLibrary.h" + +namespace SHADE +{ + class SHVkPhysicalDevice; + class SHVkLogicalDevice; + + class SHVkSurface + { + private: + vk::SurfaceKHR vkSurface; + + public: + SHVkSurface (HWND const& windowHandle, Handle const& physicalDeviceHdl, Handle const& logicalDeviceHdl) noexcept; + SHVkSurface (SHVkSurface&& rhs) noexcept = default; + SHVkSurface& operator= (SHVkSurface&& rhs) noexcept = default; + ~SHVkSurface (void) noexcept; + + + vk::SurfaceKHR GetVkSurface(void) const noexcept; + }; +} + +#endif \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHMath.h b/SHADE_Engine/src/Math/SHMath.h new file mode 100644 index 00000000..9763abe2 --- /dev/null +++ b/SHADE_Engine/src/Math/SHMath.h @@ -0,0 +1,9 @@ +#pragma once + +#include "SHMathHelpers.h" + +#include "Vector/SHVec2.h" +#include "Vector/SHVec3.h" +#include "Vector/SHVec4.h" + +#include "SHMatrix.h" \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHMathHelpers.cpp b/SHADE_Engine/src/Math/SHMathHelpers.cpp new file mode 100644 index 00000000..f3608778 --- /dev/null +++ b/SHADE_Engine/src/Math/SHMathHelpers.cpp @@ -0,0 +1,34 @@ +/**************************************************************************************** + * \file SHMathHelpers.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for various mathematical helper functions. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include +#include + +// Primary Header +#include "SHMathHelpers.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + std::default_random_engine SHMath::rng; + + /*-----------------------------------------------------------------------------------*/ + /* Static Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHMath::Initialise() + { + const unsigned SEED = static_cast(std::chrono::system_clock::now().time_since_epoch().count()); + rng.seed(SEED); + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHMathHelpers.h b/SHADE_Engine/src/Math/SHMathHelpers.h new file mode 100644 index 00000000..9135230e --- /dev/null +++ b/SHADE_Engine/src/Math/SHMathHelpers.h @@ -0,0 +1,100 @@ +/**************************************************************************************** + * \file SHMathHelpers.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for various mathematical helper functions. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include +#include +#include +#include + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Concepts */ + /*-----------------------------------------------------------------------------------*/ + + template + concept IsArithmetic = std::is_arithmetic_v; + + template + concept IsIntegral = std::is_integral_v; + + template + concept IsFloatingPoint = std::is_floating_point_v; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SHMath + { + public: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + + /** Standard Epsilon value for comparing Single-Precision Floating-Point values. */ + static constexpr float EPSILON = 0.001f; + + /** Single-Precision Floating-Point value of infinity */ + static constexpr float INF = std::numeric_limits::infinity(); + + static constexpr float PI = std::numbers::pi_v; + static constexpr float HALF_PI = PI * 0.5f; + static constexpr float TWO_PI = 2.0f * PI; + + /*---------------------------------------------------------------------------------*/ + /* Static Function Members */ + /*---------------------------------------------------------------------------------*/ + + static void Initialise (); + + template + [[nodiscard]] static constexpr T DegreesToRadians (T angleInDeg); + + template + [[nodiscard]] static constexpr T RadiansToDegrees (T angleInRad); + + template + [[nodiscard]] static T Lerp (T a, T b, T alpha); + + template + [[nodiscard]] static T ClampedLerp (T a, T b, T alpha, T alphaMin, T alphaMax); + + template + [[nodiscard]] static T Wrap (T value, T min, T max); + + template + [[nodiscard]] static T GenerateRandomNumber (T lowerBound = 0, T upperBound = 1); + + /** + * @brief Compares two floating-point values for equality within given tolerances. + * @tparam T A floating-point type + * @param lhs A floating-point value. + * @param rhs A floating-point value. + * @param absTolerance The absolute tolerance to compare the values for equality. + * @param relTolerance The relative tolerance for comparing large values for equality. + * @returns True if the values are equal within the specified tolerances. + */ + template + [[nodiscard]] static bool CompareFloat (T lhs, T rhs, T absTolerance = EPSILON, T relTolerance = EPSILON); + + private: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + static std::default_random_engine rng; + }; + +} // namespace SHADE + +#include "SHMathHelpers.hpp" \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHMathHelpers.hpp b/SHADE_Engine/src/Math/SHMathHelpers.hpp new file mode 100644 index 00000000..f0a1de12 --- /dev/null +++ b/SHADE_Engine/src/Math/SHMathHelpers.hpp @@ -0,0 +1,90 @@ +/**************************************************************************************** + * \file SHMathHelpers.hpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for various templated mathematical helper functions. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Primary Header +#include "SHMathHelpers.h" + +#include +#include + +// TODOs (Diren): Include pch? + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Function Members Definitions */ + /*-----------------------------------------------------------------------------------*/ + + template + constexpr T SHMath::DegreesToRadians(T angleInDeg) + { + return angleInDeg * static_cast(PI / 180.0f); + } + + template + constexpr T SHMath::RadiansToDegrees(T angleInRad) + { + return angleInRad * static_cast(180.0f / PI); + } + + template + T SHMath::Lerp(T a, T b, T alpha) + { + return a + alpha * (b - a); + } + + template + T SHMath::ClampedLerp(T a, T b, T alpha, T alphaMin, T alphaMax) + { + const T T_ACTUAL = std::clamp(alpha, alphaMin, alphaMax); + return a + T_ACTUAL * (b - a); + } + + template + T SHMath::Wrap(T value, T min, T max) + { + while (value < min) + { + value = max - (min - value); + } + + while (value > max) + { + value = min + (value - max); + } + + return value; + } + + template + T SHMath::GenerateRandomNumber(T lowerBound, T upperBound) + { + if constexpr (IsIntegral) + { + std::uniform_int_distribution distribution(lowerBound, upperBound); + return distribution(rng); + } + if constexpr (IsFloatingPoint) + { + std::uniform_real_distribution distribution(lowerBound, upperBound); + return distribution(rng); + } + } + + + template + bool CompareFloat(T lhs, T rhs, T absTolerance, T relTolerance) + { + return std::fabs(lhs - rhs) <= std::max(absTolerance, relTolerance * std::max(abs(lhs), abs(rhs))); + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHMatrix.cpp b/SHADE_Engine/src/Math/SHMatrix.cpp new file mode 100644 index 00000000..8e8281d0 --- /dev/null +++ b/SHADE_Engine/src/Math/SHMatrix.cpp @@ -0,0 +1,601 @@ +/**************************************************************************************** + * \file SHMatrix.hpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Matrix. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHMatrix.h" + +#include "Vector/SHVec2.h" +#include "Vector/SHVec3.h" +#include "Vector/SHVec4.h" +#include "SHQuaternion.h" + +using namespace DirectX; + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHMatrix SHMatrix::Identity + { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHMatrix::SHMatrix() noexcept + : XMFLOAT4X4 + ( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ) + {} + + SHMatrix::SHMatrix + ( + const SHVec4& r0, const SHVec4& r1, + const SHVec4& r2, const SHVec4& r3 + ) noexcept + : XMFLOAT4X4 + ( + r0.x, r0.y, r0.z, r0.w, + r1.x, r1.y, r1.z, r1.w, + r2.x, r2.y, r2.z, r2.w, + r3.x, r3.y, r3.z, r3.w + ) + {} + + SHMatrix::SHMatrix + ( + float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33 + ) noexcept + : XMFLOAT4X4 + ( + m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33 + ) + {} + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHMatrix& SHMatrix::operator+=(const SHMatrix& rhs) noexcept + { + return *this = *this + rhs; + } + + SHMatrix& SHMatrix::operator-=(const SHMatrix& rhs) noexcept + { + return *this = *this - rhs; + } + + SHMatrix& SHMatrix::operator*=(const SHMatrix& rhs) noexcept + { + return *this = *this * rhs; + } + + SHMatrix& SHMatrix::operator*=(float rhs) noexcept + { + return *this = *this * rhs; + } + + SHMatrix& SHMatrix::operator/=(const SHMatrix& rhs) noexcept + { + return *this = *this / rhs; + } + + SHMatrix& SHMatrix::operator/=(float rhs) noexcept + { + return *this = *this / rhs; + } + + SHMatrix SHMatrix::operator+(const SHMatrix& rhs) const noexcept + { + SHMatrix result; + + const XMVECTOR L1 = XMLoadFloat4(reinterpret_cast(&_11)); + const XMVECTOR L2 = XMLoadFloat4(reinterpret_cast(&_21)); + const XMVECTOR L3 = XMLoadFloat4(reinterpret_cast(&_31)); + const XMVECTOR L4 = XMLoadFloat4(reinterpret_cast(&_41)); + + const XMVECTOR R1 = XMLoadFloat4(reinterpret_cast(&rhs._11)); + const XMVECTOR R2 = XMLoadFloat4(reinterpret_cast(&rhs._21)); + const XMVECTOR R3 = XMLoadFloat4(reinterpret_cast(&rhs._31)); + const XMVECTOR R4 = XMLoadFloat4(reinterpret_cast(&rhs._41)); + + XMStoreFloat4(reinterpret_cast(&result._11), XMVectorAdd(L1, R1)); + XMStoreFloat4(reinterpret_cast(&result._21), XMVectorAdd(L2, R2)); + XMStoreFloat4(reinterpret_cast(&result._31), XMVectorAdd(L3, R3)); + XMStoreFloat4(reinterpret_cast(&result._41), XMVectorAdd(L4, R4)); + + return result; + } + + SHMatrix SHMatrix::operator-(const SHMatrix& rhs) const noexcept + { + SHMatrix result; + + const XMVECTOR L1 = XMLoadFloat4(reinterpret_cast(&_11)); + const XMVECTOR L2 = XMLoadFloat4(reinterpret_cast(&_21)); + const XMVECTOR L3 = XMLoadFloat4(reinterpret_cast(&_31)); + const XMVECTOR L4 = XMLoadFloat4(reinterpret_cast(&_41)); + + const XMVECTOR R1 = XMLoadFloat4(reinterpret_cast(&rhs._11)); + const XMVECTOR R2 = XMLoadFloat4(reinterpret_cast(&rhs._21)); + const XMVECTOR R3 = XMLoadFloat4(reinterpret_cast(&rhs._31)); + const XMVECTOR R4 = XMLoadFloat4(reinterpret_cast(&rhs._41)); + + XMStoreFloat4(reinterpret_cast(&result._11), XMVectorSubtract(L1, R1)); + XMStoreFloat4(reinterpret_cast(&result._21), XMVectorSubtract(L2, R2)); + XMStoreFloat4(reinterpret_cast(&result._31), XMVectorSubtract(L3, R3)); + XMStoreFloat4(reinterpret_cast(&result._41), XMVectorSubtract(L4, R4)); + + return result; + } + + SHMatrix SHMatrix::operator-() const noexcept + { + SHMatrix result; + + const XMVECTOR L1 = XMLoadFloat4(reinterpret_cast(&_11)); + const XMVECTOR L2 = XMLoadFloat4(reinterpret_cast(&_21)); + const XMVECTOR L3 = XMLoadFloat4(reinterpret_cast(&_31)); + const XMVECTOR L4 = XMLoadFloat4(reinterpret_cast(&_41)); + + XMStoreFloat4(reinterpret_cast(&result._11), XMVectorNegate(L1)); + XMStoreFloat4(reinterpret_cast(&result._21), XMVectorNegate(L2)); + XMStoreFloat4(reinterpret_cast(&result._31), XMVectorNegate(L3)); + XMStoreFloat4(reinterpret_cast(&result._41), XMVectorNegate(L4)); + + return result; + } + + SHMatrix SHMatrix::operator*(const SHMatrix& rhs) const noexcept + { + SHMatrix result; + + const XMMATRIX M1 = XMLoadFloat4x4(this); + const XMMATRIX M2 = XMLoadFloat4x4(&rhs); + + XMStoreFloat4x4(&result, XMMatrixMultiply(M1, M2)); + return result; + } + + SHVec3 SHMatrix::operator*(const SHVec3& rhs) const noexcept + { + return SHVec3::Transform(rhs, *this); + } + + SHVec4 SHMatrix::operator*(const SHVec4& rhs) const noexcept + { + return SHVec4::Transform3D(rhs, *this); + } + + SHMatrix SHMatrix::operator*(float rhs) const noexcept + { + SHMatrix result; + + const XMVECTOR L1 = XMLoadFloat4(reinterpret_cast(&_11)); + const XMVECTOR L2 = XMLoadFloat4(reinterpret_cast(&_21)); + const XMVECTOR L3 = XMLoadFloat4(reinterpret_cast(&_31)); + const XMVECTOR L4 = XMLoadFloat4(reinterpret_cast(&_41)); + + XMStoreFloat4(reinterpret_cast(&result._11), XMVectorScale(L1, rhs)); + XMStoreFloat4(reinterpret_cast(&result._21), XMVectorScale(L2, rhs)); + XMStoreFloat4(reinterpret_cast(&result._31), XMVectorScale(L3, rhs)); + XMStoreFloat4(reinterpret_cast(&result._41), XMVectorScale(L4, rhs)); + + return result; + } + + SHMatrix SHMatrix::operator/(const SHMatrix& rhs) const noexcept + { + SHMatrix result; + + const XMVECTOR L1 = XMLoadFloat4(reinterpret_cast(&_11)); + const XMVECTOR L2 = XMLoadFloat4(reinterpret_cast(&_21)); + const XMVECTOR L3 = XMLoadFloat4(reinterpret_cast(&_31)); + const XMVECTOR L4 = XMLoadFloat4(reinterpret_cast(&_41)); + + const XMVECTOR R1 = XMLoadFloat4(reinterpret_cast(&rhs._11)); + const XMVECTOR R2 = XMLoadFloat4(reinterpret_cast(&rhs._21)); + const XMVECTOR R3 = XMLoadFloat4(reinterpret_cast(&rhs._31)); + const XMVECTOR R4 = XMLoadFloat4(reinterpret_cast(&rhs._41)); + + XMStoreFloat4(reinterpret_cast(&result._11), XMVectorDivide(L1, R1)); + XMStoreFloat4(reinterpret_cast(&result._21), XMVectorDivide(L2, R2)); + XMStoreFloat4(reinterpret_cast(&result._31), XMVectorDivide(L3, R3)); + XMStoreFloat4(reinterpret_cast(&result._41), XMVectorDivide(L4, R4)); + + return result; + } + + SHMatrix SHMatrix::operator/(float rhs) const noexcept + { + SHMatrix result; + + const XMVECTOR L1 = XMLoadFloat4(reinterpret_cast(&_11)); + const XMVECTOR L2 = XMLoadFloat4(reinterpret_cast(&_21)); + const XMVECTOR L3 = XMLoadFloat4(reinterpret_cast(&_31)); + const XMVECTOR L4 = XMLoadFloat4(reinterpret_cast(&_41)); + + const float INV_RHS = 1.0f / rhs; + + XMStoreFloat4(reinterpret_cast(&result._11), XMVectorScale(L1, INV_RHS)); + XMStoreFloat4(reinterpret_cast(&result._21), XMVectorScale(L2, INV_RHS)); + XMStoreFloat4(reinterpret_cast(&result._31), XMVectorScale(L3, INV_RHS)); + XMStoreFloat4(reinterpret_cast(&result._41), XMVectorScale(L4, INV_RHS)); + + return result; + } + + bool SHMatrix::operator==(const SHMatrix& rhs) const noexcept + { + const XMVECTOR L1 = XMLoadFloat4(reinterpret_cast(&_11)); + const XMVECTOR L2 = XMLoadFloat4(reinterpret_cast(&_21)); + const XMVECTOR L3 = XMLoadFloat4(reinterpret_cast(&_31)); + const XMVECTOR L4 = XMLoadFloat4(reinterpret_cast(&_41)); + + const XMVECTOR R1 = XMLoadFloat4(reinterpret_cast(&rhs._11)); + const XMVECTOR R2 = XMLoadFloat4(reinterpret_cast(&rhs._21)); + const XMVECTOR R3 = XMLoadFloat4(reinterpret_cast(&rhs._31)); + const XMVECTOR R4 = XMLoadFloat4(reinterpret_cast(&rhs._41)); + + return + ( + XMVector4Equal(L1, R1) + && XMVector4Equal(L2, R2) + && XMVector4Equal(L3, R4) + && XMVector4Equal(L4, R4) + ) != 0; + } + + + bool SHMatrix::operator!=(const SHMatrix& rhs) const noexcept + { + const XMVECTOR L1 = XMLoadFloat4(reinterpret_cast(&_11)); + const XMVECTOR L2 = XMLoadFloat4(reinterpret_cast(&_21)); + const XMVECTOR L3 = XMLoadFloat4(reinterpret_cast(&_31)); + const XMVECTOR L4 = XMLoadFloat4(reinterpret_cast(&_41)); + + const XMVECTOR R1 = XMLoadFloat4(reinterpret_cast(&rhs._11)); + const XMVECTOR R2 = XMLoadFloat4(reinterpret_cast(&rhs._21)); + const XMVECTOR R3 = XMLoadFloat4(reinterpret_cast(&rhs._31)); + const XMVECTOR R4 = XMLoadFloat4(reinterpret_cast(&rhs._41)); + + return + ( + XMVector4NotEqual(L1, R1) + || XMVector4NotEqual(L2, R2) + || XMVector4NotEqual(L3, R4) + || XMVector4NotEqual(L4, R4) + ) != 0; + } + + SHMatrix operator*(float lhs, const SHMatrix& rhs) noexcept + { + return rhs * lhs; + } + + + /*-----------------------------------------------------------------------------------*/ + /* Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHMatrix::Transpose() noexcept + { + const XMMATRIX M = XMLoadFloat4x4(this); + XMStoreFloat4x4(this, XMMatrixTranspose(M)); + } + + void SHMatrix::Invert() noexcept + { + const XMMATRIX M = XMLoadFloat4x4(this); + XMStoreFloat4x4(this, XMMatrixInverse(nullptr, M)); + } + + float SHMatrix::Determinant() const noexcept + { + const XMMATRIX M = XMLoadFloat4x4(this); + return XMVectorGetX(XMMatrixDeterminant(M)); + } + + std::string SHMatrix::ToString() const noexcept + { + std::stringstream ss; + ss << std::fixed << std::setprecision(3); + ss << "| " << _11 << ", " << _12 << ", " << _13 << ", " << _14 << " |\n"; + ss << "| " << _21 << ", " << _22 << ", " << _23 << ", " << _24 << " |\n"; + ss << "| " << _31 << ", " << _32 << ", " << _33 << ", " << _34 << " |\n"; + ss << "| " << _41 << ", " << _42 << ", " << _43 << ", " << _44 << " |"; + return ss.str(); + } + + + /*-----------------------------------------------------------------------------------*/ + /* Static Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHMatrix SHMatrix::Transpose(const SHMatrix& matrix) noexcept + { + SHMatrix result; + + const XMMATRIX M = XMLoadFloat4x4(&matrix); + XMStoreFloat4x4(&result, XMMatrixTranspose(M)); + return result; + } + + SHMatrix SHMatrix::Inverse(const SHMatrix& matrix) noexcept + { + SHMatrix result; + + const XMMATRIX M = XMLoadFloat4x4(&matrix); + XMStoreFloat4x4(&result, XMMatrixInverse(nullptr, M)); + return result; + } + + SHMatrix SHMatrix::Translate(float x, float y, float z) noexcept + { + SHMatrix result; + XMStoreFloat4x4(&result, XMMatrixTranslation(x, y, z)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::Translate(const SHVec3& pos) noexcept + { + SHMatrix result; + XMStoreFloat4x4(&result, XMMatrixTranslation(pos.x, pos.y, pos.z)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::Rotate(const SHVec3& axis, float angleInRad) noexcept + { + SHMatrix result; + + const XMVECTOR A = XMLoadFloat3(&axis); + XMStoreFloat4x4(&result, XMMatrixRotationAxis(A, angleInRad)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::Rotate(float yaw, float pitch, float roll) noexcept + { + SHMatrix result; + XMStoreFloat4x4(&result, XMMatrixRotationRollPitchYaw(pitch, yaw, roll)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::Rotate(const SHVec3& eulerAngles) noexcept + { + SHMatrix result; + XMStoreFloat4x4(&result, XMMatrixRotationRollPitchYaw(eulerAngles.x, eulerAngles.y, eulerAngles.z)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::Rotate(const SHQuaternion& q) noexcept + { + SHMatrix result; + + const XMVECTOR Q = XMLoadFloat4(&q); + XMStoreFloat4x4(&result, XMMatrixRotationQuaternion(Q)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::RotateX(float angleInRad) noexcept + { + SHMatrix result; + XMStoreFloat4x4(&result, XMMatrixRotationX(angleInRad)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::RotateY(float angleInRad) noexcept + { + SHMatrix result; + XMStoreFloat4x4(&result, XMMatrixRotationY(angleInRad)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::RotateZ(float angleInRad) noexcept + { + SHMatrix result; + XMStoreFloat4x4(&result, XMMatrixRotationZ(angleInRad)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::Scale(float uniformScaleFactor) noexcept + { + SHMatrix result; + XMStoreFloat4x4(&result, XMMatrixScaling(uniformScaleFactor, uniformScaleFactor, uniformScaleFactor)); + + return result; + } + + SHMatrix SHMatrix::Scale(float x, float y, float z) noexcept + { + SHMatrix result; + XMStoreFloat4x4(&result, XMMatrixScaling(x, y, z)); + + return result; + } + + SHMatrix SHMatrix::Scale(const SHVec3& scale) noexcept + { + SHMatrix result; + XMStoreFloat4x4(&result, XMMatrixScaling(scale.x, scale.y, scale.z)); + + return result; + } + + SHMatrix SHMatrix::LookAtRH(const SHVec3& eye, const SHVec3& target, const SHVec3& up) noexcept + { + SHMatrix result; + + const XMVECTOR EYE = XMLoadFloat3(&eye); + const XMVECTOR TGT = XMLoadFloat3(&target); + const XMVECTOR UP = XMLoadFloat3(&up); + + XMStoreFloat4x4(&result, XMMatrixLookAtRH(EYE, TGT, UP)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::LookAtLH(const SHVec3& eye, const SHVec3& target, const SHVec3& up) noexcept + { + SHMatrix result; + + const XMVECTOR EYE = XMLoadFloat3(&eye); + const XMVECTOR TGT = XMLoadFloat3(&target); + const XMVECTOR UP = XMLoadFloat3(&up); + + XMStoreFloat4x4(&result, XMMatrixLookAtLH(EYE, TGT, UP)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::CamToWorldRH(const SHVec3& pos, const SHVec3& forward, const SHVec3& up) noexcept + { + SHMatrix result; + + const SHVec3 FWD_HAT = SHVec3::Normalise(-forward); + + const XMVECTOR Z_HAT = XMVector3Normalize(XMLoadFloat3(&FWD_HAT)); + const XMVECTOR X_HAT = XMVector3Normalize(XMVector3Cross(XMLoadFloat3(&up), Z_HAT)); + const XMVECTOR Y_HAT = XMVector3Cross(Z_HAT, X_HAT); + + XMStoreFloat3(reinterpret_cast(&result._11), X_HAT); + XMStoreFloat3(reinterpret_cast(&result._21), Y_HAT); + XMStoreFloat3(reinterpret_cast(&result._31), Z_HAT); + + result._41 = pos.x; + result._42 = pos.y; + result._43 = pos.z; + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::CamToWorldLH(const SHVec3& pos, const SHVec3& forward, const SHVec3& up) noexcept + { + SHMatrix result; + + const SHVec3 FWD_HAT = SHVec3::Normalise(forward); + + const XMVECTOR Z_HAT = XMVector3Normalize(XMLoadFloat3(&FWD_HAT)); + const XMVECTOR X_HAT = XMVector3Normalize(XMVector3Cross(XMLoadFloat3(&up), Z_HAT)); + const XMVECTOR Y_HAT = XMVector3Cross(Z_HAT, X_HAT); + + XMStoreFloat3(reinterpret_cast(&result._11), X_HAT); + XMStoreFloat3(reinterpret_cast(&result._21), Y_HAT); + XMStoreFloat3(reinterpret_cast(&result._31), Z_HAT); + + result._41 = pos.x; + result._42 = pos.x; + result._43 = pos.x; + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::PerspectiveFovRH(float fov, float aspectRatio, float nearPlane, float farPlane) noexcept + { + SHMatrix result; + + XMStoreFloat4x4(&result, XMMatrixPerspectiveFovRH(fov, aspectRatio, nearPlane, farPlane)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::PerspectiveFovLH(float fov, float aspectRatio, float nearPlane, float farPlane) noexcept + { + SHMatrix result; + + XMStoreFloat4x4(&result, XMMatrixPerspectiveFovLH(fov, aspectRatio, nearPlane, farPlane)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::PerspectiveRH(float width, float height, float nearPlane, float farPlane) noexcept + { + SHMatrix result; + + XMStoreFloat4x4(&result, XMMatrixPerspectiveRH(width, height, nearPlane, farPlane)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::PerspectiveLH(float width, float height, float nearPlane, float farPlane) noexcept + { + SHMatrix result; + + XMStoreFloat4x4(&result, XMMatrixPerspectiveLH(width, height, nearPlane, farPlane)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::OrthographicRH(float width, float height, float nearPlane, float farPlane) noexcept + { + SHMatrix result; + + XMStoreFloat4x4(&result, XMMatrixOrthographicRH(width, height, nearPlane, farPlane)); + + result.Transpose(); + return result; + } + + SHMatrix SHMatrix::OrthographicLH(float width, float height, float nearPlane, float farPlane) noexcept + { + SHMatrix result; + + XMStoreFloat4x4(&result, XMMatrixOrthographicLH(width, height, nearPlane, farPlane)); + + result.Transpose(); + return result; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHMatrix.h b/SHADE_Engine/src/Math/SHMatrix.h new file mode 100644 index 00000000..2aab05ab --- /dev/null +++ b/SHADE_Engine/src/Math/SHMatrix.h @@ -0,0 +1,144 @@ +/**************************************************************************************** + * \file SHMatrix.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Matrix. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include +#include + +#include "Vector/SHVec4.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHVec2; + class SHVec3; + class SHVec4; + class SHQuaternion; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief Interface for a Column-Major Row Vector 4x4 Matrix. + */ + class SHMatrix : public DirectX::XMFLOAT4X4 + { + public: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr size_t SIZE = 16U; + static constexpr size_t NUM_ROWS = 4U; + static constexpr size_t NUM_COLS = 4U; + + static const SHMatrix Identity; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHMatrix (const SHMatrix& rhs) = default; + SHMatrix (SHMatrix&& rhs) = default; + ~SHMatrix () = default; + + SHMatrix () noexcept; + SHMatrix ( const SHVec4& r0, + const SHVec4& r1, + const SHVec4& r2, + const SHVec4& r3 = SHVec4::UnitW + ) noexcept; + SHMatrix ( + float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30 = 0.0f, float m31 = 0.0f, float m32 = 0.0f, float m33 = 1.0f + ) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHMatrix& operator= (const SHMatrix& rhs) = default; + SHMatrix& operator= (SHMatrix&& rhs) = default; + + SHMatrix& operator+= (const SHMatrix& rhs) noexcept; + SHMatrix& operator-= (const SHMatrix& rhs) noexcept; + SHMatrix& operator*= (const SHMatrix& rhs) noexcept; + SHMatrix& operator*= (float rhs) noexcept; + SHMatrix& operator/= (const SHMatrix& rhs) noexcept; + SHMatrix& operator/= (float rhs) noexcept; + + SHMatrix operator+ (const SHMatrix& rhs) const noexcept; + SHMatrix operator- (const SHMatrix& rhs) const noexcept; + SHMatrix operator- () const noexcept; + SHMatrix operator* (const SHMatrix& rhs) const noexcept; + SHVec3 operator* (const SHVec3& rhs) const noexcept; + SHVec4 operator* (const SHVec4& rhs) const noexcept; + SHMatrix operator* (float rhs) const noexcept; + SHMatrix operator/ (const SHMatrix& rhs) const noexcept; + SHMatrix operator/ (float rhs) const noexcept; + + bool operator== (const SHMatrix& rhs) const noexcept; + bool operator!= (const SHMatrix& rhs) const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void Transpose () noexcept; + void Invert () noexcept; + + [[nodiscard]] float Determinant () const noexcept; + [[nodiscard]] std::string ToString () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Static Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] static SHMatrix Transpose (const SHMatrix& matrix) noexcept; + [[nodiscard]] static SHMatrix Inverse (const SHMatrix& matrix) noexcept; + + [[nodiscard]] static SHMatrix Translate (float x, float y, float z) noexcept; + [[nodiscard]] static SHMatrix Translate (const SHVec3& pos) noexcept; + + [[nodiscard]] static SHMatrix Rotate (const SHVec3& axis, float angleInRad) noexcept; + [[nodiscard]] static SHMatrix Rotate (float yaw, float pitch, float roll) noexcept; + [[nodiscard]] static SHMatrix Rotate (const SHVec3& eulerAngles) noexcept; + [[nodiscard]] static SHMatrix Rotate (const SHQuaternion& q) noexcept; + [[nodiscard]] static SHMatrix RotateX (float angleInRad) noexcept; + [[nodiscard]] static SHMatrix RotateY (float angleInRad) noexcept; + [[nodiscard]] static SHMatrix RotateZ (float angleInRad) noexcept; + + [[nodiscard]] static SHMatrix Scale (float uniformScaleFactor) noexcept; + [[nodiscard]] static SHMatrix Scale (float x, float y, float z) noexcept; + [[nodiscard]] static SHMatrix Scale (const SHVec3& scale) noexcept; + + [[nodiscard]] static SHMatrix LookAtRH (const SHVec3& eye, const SHVec3& target, const SHVec3& up) noexcept; + [[nodiscard]] static SHMatrix LookAtLH (const SHVec3& eye, const SHVec3& target, const SHVec3& up) noexcept; + [[nodiscard]] static SHMatrix CamToWorldRH (const SHVec3& pos, const SHVec3& forward, const SHVec3& up) noexcept; + [[nodiscard]] static SHMatrix CamToWorldLH (const SHVec3& pos, const SHVec3& forward, const SHVec3& up) noexcept; + [[nodiscard]] static SHMatrix PerspectiveFovRH (float fov, float aspectRatio, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix PerspectiveFovLH (float fov, float aspectRatio, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix PerspectiveRH (float width, float height, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix PerspectiveLH (float width, float height, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix OrthographicRH (float width, float height, float nearPlane, float farPlane) noexcept; + [[nodiscard]] static SHMatrix OrthographicLH (float width, float height, float nearPlane, float farPlane) noexcept; + + // TODO(Diren): Billboard, Shadow, Projection & Reflection + }; + + SHMatrix operator*(float lhs, const SHMatrix& rhs) noexcept; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHQuaternion.cpp b/SHADE_Engine/src/Math/SHQuaternion.cpp new file mode 100644 index 00000000..208f131d --- /dev/null +++ b/SHADE_Engine/src/Math/SHQuaternion.cpp @@ -0,0 +1,308 @@ +/**************************************************************************************** + * \file SHQuaternion.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Quaternion. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHQuaternion.h" +// Project Headers +#include "Vector/SHVec3.h" +#include "SHMatrix.h" +#include "Tools/SHLogger.h" + +using namespace DirectX; + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHQuaternion SHQuaternion::Identity{ 0.0f, 0.0f, 0.0f, 1.0f }; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHQuaternion::SHQuaternion() noexcept + : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) + {} + + SHQuaternion::SHQuaternion(float _x, float _y, float _z, float _w) noexcept + : XMFLOAT4( _x, _y, _z, _w ) + {} + + SHQuaternion::SHQuaternion(float yaw, float pitch, float roll) noexcept + : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) + { + XMStoreFloat4(this, XMQuaternionRotationRollPitchYaw(pitch, yaw, roll)); + } + + SHQuaternion::SHQuaternion(const SHVec3& eulerAngles) noexcept + : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) + { + const XMVECTOR V = XMLoadFloat3(&eulerAngles); + XMStoreFloat4(this, XMQuaternionRotationRollPitchYawFromVector(V)); + } + + SHQuaternion::SHQuaternion(const SHVec3& axis, float angleInRad) noexcept + : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) + { + const XMVECTOR AXIS = XMLoadFloat3(&axis); + XMStoreFloat4(this, XMQuaternionRotationAxis(AXIS, angleInRad)); + } + + SHQuaternion::SHQuaternion(const SHMatrix& rotationMatrix) noexcept + : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) + { + const XMMATRIX M = XMLoadFloat4x4(&rotationMatrix); + XMStoreFloat4(this, XMQuaternionRotationMatrix(M)); + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHQuaternion& SHQuaternion::operator+=(const SHQuaternion& rhs) noexcept + { + return *this = *this + rhs; + } + + SHQuaternion& SHQuaternion::operator-=(const SHQuaternion& rhs) noexcept + { + return *this = *this - rhs; + } + + SHQuaternion& SHQuaternion::operator*=(const SHQuaternion& rhs) noexcept + { + return *this = *this * rhs; + } + + SHQuaternion& SHQuaternion::operator*=(float rhs) noexcept + { + return *this = *this * rhs; + } + + SHQuaternion& SHQuaternion::operator/=(const SHQuaternion& rhs) noexcept + { + return *this = *this / rhs; + } + + SHQuaternion SHQuaternion::operator+(const SHQuaternion& rhs) const noexcept + { + SHQuaternion result; + + const XMVECTOR Q1 = XMLoadFloat4(this); + const XMVECTOR Q2 = XMLoadFloat4(&rhs); + + XMStoreFloat4(&result, XMVectorAdd(Q1, Q2)); + return result; + } + + SHQuaternion SHQuaternion::operator-(const SHQuaternion& rhs) const noexcept + { + SHQuaternion result; + + const XMVECTOR Q1 = XMLoadFloat4(this); + const XMVECTOR Q2 = XMLoadFloat4(&rhs); + + XMStoreFloat4(&result, XMVectorSubtract(Q1, Q2)); + return result; + } + + SHQuaternion SHQuaternion::operator-() const noexcept + { + SHQuaternion result; + + const XMVECTOR Q = XMLoadFloat4(this); + + XMStoreFloat4(&result, XMVectorNegate(Q)); + return result; + } + + SHQuaternion SHQuaternion::operator*(const SHQuaternion& rhs) const noexcept + { + SHQuaternion result; + + const XMVECTOR Q1 = XMLoadFloat4(this); + const XMVECTOR Q2 = XMLoadFloat4(&rhs); + + XMStoreFloat4(&result, XMQuaternionMultiply(Q1, Q2)); + return result; + } + + SHQuaternion SHQuaternion::operator*(float rhs) const noexcept + { + SHQuaternion result; + + const XMVECTOR Q = XMLoadFloat4(this); + + XMStoreFloat4(&result, XMVectorScale(Q, rhs)); + return result; + } + + SHQuaternion SHQuaternion::operator/(const SHQuaternion& rhs) const noexcept + { + SHQuaternion result; + + const XMVECTOR Q1 = XMLoadFloat4(this); + const XMVECTOR Q2 = XMQuaternionInverse(XMLoadFloat4(&rhs)); + + XMStoreFloat4(&result, XMQuaternionMultiply(Q1, Q2)); + return result; + } + + bool SHQuaternion::operator==(const SHQuaternion& rhs) const noexcept + { + const XMVECTOR Q1 = XMLoadFloat4(this); + const XMVECTOR Q2 = XMLoadFloat4(&rhs); + + return XMQuaternionEqual(Q1, Q2); + } + + bool SHQuaternion::operator!=(const SHQuaternion& rhs) const noexcept + { + const XMVECTOR Q1 = XMLoadFloat4(this); + const XMVECTOR Q2 = XMLoadFloat4(&rhs); + + return XMQuaternionNotEqual(Q1, Q2); + } + + SHQuaternion operator*(float lhs, const SHQuaternion& rhs) noexcept + { + return rhs * lhs; + } + + /*-----------------------------------------------------------------------------------*/ + /* Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHQuaternion::Invert() noexcept + { + const XMVECTOR Q = XMLoadFloat4(this); + XMStoreFloat4(this, XMQuaternionInverse(Q)); + } + + float SHQuaternion::Length() const noexcept + { + const XMVECTOR Q = XMLoadFloat4(this); + return XMVectorGetX(XMQuaternionLength(Q)); + } + + float SHQuaternion::LengthSquared() const noexcept + { + const XMVECTOR Q = XMLoadFloat4(this); + return XMVectorGetX(XMQuaternionLengthSq(Q)); + } + + float SHQuaternion::Dot(const SHQuaternion& rhs) const noexcept + { + const XMVECTOR Q1 = XMLoadFloat4(this); + const XMVECTOR Q2 = XMLoadFloat4(&rhs); + + return XMVectorGetX(XMQuaternionDot(Q1, Q2)); + } + + SHQuaternion SHQuaternion::RotateTowards(const SHQuaternion&, float) const noexcept + { + SHQuaternion result; + + // TODO (Diren) + + return result; + } + + SHVec3 SHQuaternion::ToEuler() const noexcept + { + // TODO (Diren) + + return SHVec3::Zero; + } + + std::string SHQuaternion::ToString() const noexcept + { + std::stringstream ss; + ss << std::fixed << std::setprecision(3); + ss << "<" << x << ", " << y << ", " << z << ", " << w <<">"; + return ss.str(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Static Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHQuaternion SHQuaternion::Normalise(const SHQuaternion& q) noexcept + { + SHQuaternion result; + + const XMVECTOR Q = XMLoadFloat4(&q); + + XMStoreFloat4(&result, XMQuaternionNormalize(Q)); + return result; + } + + SHQuaternion SHQuaternion::Conjugate(const SHQuaternion& q) noexcept + { + SHQuaternion result; + + const XMVECTOR Q = XMLoadFloat4(&q); + + XMStoreFloat4(&result, XMQuaternionConjugate(Q)); + return result; + } + + SHQuaternion SHQuaternion::Inverse(const SHQuaternion& q) noexcept + { + SHQuaternion result; + + const XMVECTOR Q = XMLoadFloat4(&q); + XMStoreFloat4(&result, XMQuaternionInverse(Q)); + + return result; + } + + + float SHQuaternion::Angle(const SHQuaternion&, const SHQuaternion&) noexcept + { + // TODO (Diren) + + return 0.0f; + } + + SHQuaternion SHQuaternion::Lerp(const SHQuaternion&, const SHQuaternion&, float) noexcept + { + SHQuaternion result; + + // TODO (Diren) + + return result; + } + + SHQuaternion SHQuaternion::Slerp(const SHQuaternion& q1, const SHQuaternion& q2, float t) noexcept + { + SHQuaternion result; + + const XMVECTOR Q1 = XMLoadFloat4(&q1); + const XMVECTOR Q2 = XMLoadFloat4(&q2); + + XMStoreFloat4(&result, XMQuaternionSlerp(Q1, Q2, t)); + return result; + } + + SHQuaternion SHQuaternion::Rotate(const SHVec3& , const SHVec3&) noexcept + { + SHQuaternion result; + + // TODO (Diren) + + return result; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHQuaternion.h b/SHADE_Engine/src/Math/SHQuaternion.h new file mode 100644 index 00000000..27088284 --- /dev/null +++ b/SHADE_Engine/src/Math/SHQuaternion.h @@ -0,0 +1,106 @@ +/**************************************************************************************** + * \file SHQuaternion.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Quaternion. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include +#include + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + + class SHVec3; + class SHMatrix; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SHQuaternion : public DirectX::XMFLOAT4 + { + public: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + + static const SHQuaternion Identity; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHQuaternion (const SHQuaternion& rhs) = default; + SHQuaternion (SHQuaternion&& rhs) = default; + + SHQuaternion () noexcept; + SHQuaternion (float x, float y, float z, float w) noexcept; + SHQuaternion (float yaw, float pitch, float roll) noexcept; + SHQuaternion (const SHVec3& eulerAngles) noexcept; + SHQuaternion (const SHVec3& axis, float angleInRad) noexcept; + SHQuaternion (const SHMatrix& rotationMatrix) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] SHQuaternion& operator= (const SHQuaternion& rhs) = default; + [[nodiscard]] SHQuaternion& operator= (SHQuaternion&& rhs) = default; + + [[nodiscard]] SHQuaternion& operator+= (const SHQuaternion& rhs) noexcept; + [[nodiscard]] SHQuaternion& operator-= (const SHQuaternion& rhs) noexcept; + [[nodiscard]] SHQuaternion& operator*= (const SHQuaternion& rhs) noexcept; + [[nodiscard]] SHQuaternion& operator*= (float rhs) noexcept; + [[nodiscard]] SHQuaternion& operator/= (const SHQuaternion& rhs) noexcept; + + [[nodiscard]] SHQuaternion operator+ (const SHQuaternion& rhs) const noexcept; + [[nodiscard]] SHQuaternion operator- (const SHQuaternion& rhs) const noexcept; + [[nodiscard]] SHQuaternion operator- () const noexcept; + [[nodiscard]] SHQuaternion operator* (const SHQuaternion& rhs) const noexcept; + [[nodiscard]] SHQuaternion operator* (float rhs) const noexcept; + [[nodiscard]] SHQuaternion operator/ (const SHQuaternion& rhs) const noexcept; + + [[nodiscard]] bool operator== (const SHQuaternion& rhs) const noexcept; + [[nodiscard]] bool operator!= (const SHQuaternion& rhs) const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void Invert () noexcept; + + [[nodiscard]] float Length () const noexcept; + [[nodiscard]] float LengthSquared () const noexcept; + [[nodiscard]] float Dot (const SHQuaternion& rhs) const noexcept; + [[nodiscard]] SHQuaternion RotateTowards (const SHQuaternion& target, float maxAngleInRad) const noexcept; + + [[nodiscard]] SHVec3 ToEuler () const noexcept; + [[nodiscard]] std::string ToString () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Static Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] static SHQuaternion Normalise (const SHQuaternion& q) noexcept; + [[nodiscard]] static SHQuaternion Conjugate (const SHQuaternion& q) noexcept; + [[nodiscard]] static SHQuaternion Inverse (const SHQuaternion& q) noexcept; + [[nodiscard]] static float Angle (const SHQuaternion& q1, const SHQuaternion& q2) noexcept; + + [[nodiscard]] static SHQuaternion Lerp (const SHQuaternion& q1, const SHQuaternion& q2, float t) noexcept; + [[nodiscard]] static SHQuaternion Slerp (const SHQuaternion& q1, const SHQuaternion& q2, float t) noexcept; + + [[nodiscard]] static SHQuaternion Rotate (const SHVec3& from, const SHVec3& to) noexcept; + }; + + SHQuaternion operator*(float lhs, const SHQuaternion& rhs) noexcept; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec2.cpp b/SHADE_Engine/src/Math/Vector/SHVec2.cpp new file mode 100644 index 00000000..72c80a50 --- /dev/null +++ b/SHADE_Engine/src/Math/Vector/SHVec2.cpp @@ -0,0 +1,460 @@ +/**************************************************************************************** + * \file SHVec2.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for 2D Vector. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHVec2.h" +// Project Headers +#include "Math/SHMatrix.h" +#include "Tools/SHLogger.h" + +using namespace DirectX; + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + SHVec2 const SHVec2::Zero { 0.0f, 0.0f }; + SHVec2 const SHVec2::One { 1.0f, 1.0f }; + SHVec2 const SHVec2::Left { -1.0f, 0.0f }; + SHVec2 const SHVec2::Right { 1.0f, 0.0f }; + SHVec2 const SHVec2::Up { 0.0f, 1.0f }; + SHVec2 const SHVec2::Down { 0.0f, -1.0f }; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec2::SHVec2() noexcept + : XMFLOAT2( 0.0f, 0.0f ) + {} + + SHVec2::SHVec2(float _x, float _y) noexcept + : XMFLOAT2( _x, _y ) + {} + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec2& SHVec2::operator+=(const SHVec2& rhs) noexcept + { + return *this = *this + rhs; + } + + SHVec2& SHVec2::operator-=(const SHVec2& rhs) noexcept + { + return *this = *this - rhs; + } + + SHVec2& SHVec2::operator*=(const SHVec2& rhs) noexcept + { + return *this = *this * rhs; + } + + SHVec2& SHVec2::operator*=(float rhs) noexcept + { + return *this = *this * rhs; + } + + SHVec2& SHVec2::operator/=(const SHVec2& rhs) noexcept + { + return *this = *this / rhs; + } + + SHVec2& SHVec2::operator/=(float rhs) noexcept + { + return *this = *this / rhs; + } + + SHVec2 SHVec2::operator+(const SHVec2& rhs) const noexcept + { + SHVec2 result; + + const XMVECTOR V1 = XMLoadFloat2(this); + const XMVECTOR V2 = XMLoadFloat2(&rhs); + + XMStoreFloat2(&result, XMVectorAdd(V1, V2)); + return result; + } + + SHVec2 SHVec2::operator-(const SHVec2& rhs) const noexcept + { + SHVec2 result; + + const XMVECTOR V1 = XMLoadFloat2(this); + const XMVECTOR V2 = XMLoadFloat2(&rhs); + + XMStoreFloat2(&result, XMVectorSubtract(V1, V2)); + return result; + } + + SHVec2 SHVec2::operator-() const noexcept + { + return SHVec2{ -x, -y }; + } + + SHVec2 SHVec2::operator*(const SHVec2& rhs) const noexcept + { + SHVec2 result; + + const XMVECTOR V1 = XMLoadFloat2(this); + const XMVECTOR V2 = XMLoadFloat2(&rhs); + + XMStoreFloat2(&result, XMVectorMultiply(V1, V2)); + return result; + } + + SHVec2 SHVec2::operator*(float rhs) const noexcept + { + SHVec2 result; + + const XMVECTOR V = XMLoadFloat2(this); + + XMStoreFloat2(&result, XMVectorScale(V, rhs)); + return result; + } + + SHVec2 SHVec2::operator/(const SHVec2& rhs) const noexcept + { + SHVec2 result; + + const XMVECTOR V1 = XMLoadFloat2(this); + const XMVECTOR V2 = XMLoadFloat2(&rhs); + + XMStoreFloat2(&result, XMVectorDivide(V1, V2)); + return result; + } + + SHVec2 SHVec2::operator/(float rhs) const noexcept + { + SHVec2 result; + + const XMVECTOR V = XMLoadFloat2(this); + + XMStoreFloat2(&result, XMVectorScale(V, 1.0f / rhs)); + return result; + } + + bool SHVec2::operator==(const SHVec2& rhs) const noexcept + { + const XMVECTOR V1 = XMLoadFloat2(this); + const XMVECTOR V2 = XMLoadFloat2(&rhs); + + return XMVector2Equal(V1, V2); + } + + bool SHVec2::operator!=(const SHVec2& rhs) const noexcept + { + const XMVECTOR V1 = XMLoadFloat2(this); + const XMVECTOR V2 = XMLoadFloat2(&rhs); + + return XMVector2NotEqual(V1, V2); + } + + float SHVec2::operator[](int index) + { + if (index >= SIZE || index < 0) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + default: return 0.0f; + } + } + + float SHVec2::operator[](size_t index) + { + if (index >= SIZE) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + default: return 0.0f; + } + } + + float SHVec2::operator[](int index) const + { + if (index >= SIZE || index < 0) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + default: return 0.0f; + } + } + + float SHVec2::operator[](size_t index) const + { + if (index >= SIZE) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + default: return 0.0f; + } + } + + SHVec2 operator* (float lhs, const SHVec2& rhs) noexcept + { + SHVec2 result; + + const XMVECTOR V = XMLoadFloat2(&rhs); + + XMStoreFloat2(&result, XMVectorScale(V, lhs)); + return result; + } + + /*-----------------------------------------------------------------------------------*/ + /* Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + float SHVec2::Length() const noexcept + { + const XMVECTOR V = XMLoadFloat2(this); + + return XMVectorGetX(XMVector2Length(V)); + } + + float SHVec2::LengthSquared() const noexcept + { + const XMVECTOR V = XMLoadFloat2(this); + + return XMVectorGetX(XMVector2LengthSq(V)); + } + + std::string SHVec2::ToString() const noexcept + { + std::stringstream ss; + ss << std::fixed << std::setprecision(3); + ss << "<" << x << ", " << y << ">"; + return ss.str(); + } + + float SHVec2::Dot(const SHVec2& rhs) const noexcept + { + const XMVECTOR V1 = XMLoadFloat2(this); + const XMVECTOR V2 = XMLoadFloat2(&rhs); + + return XMVectorGetX(XMVector2Dot(V1, V2)); + } + + SHVec2 SHVec2::Cross(const SHVec2& rhs) const noexcept + { + SHVec2 result; + + const XMVECTOR V1 = XMLoadFloat2(this); + const XMVECTOR V2 = XMLoadFloat2(&rhs); + + XMStoreFloat2(&result, XMVector2Cross(V1, V2)); + return result; + } + + /*-----------------------------------------------------------------------------------*/ + /* Static Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec2 SHVec2::Normalise(const SHVec2& vec2) noexcept + { + SHVec2 result; + + const XMVECTOR V = XMLoadFloat2(&vec2); + + XMStoreFloat2(&result, XMVector2Normalize(V)); + return result; + } + + SHVec2 SHVec2::Abs(const SHVec2& vec2) noexcept + { + return SHVec2{ std::fabs(vec2.x), std::fabs(vec2.y) }; + } + + SHVec2 SHVec2::Min(const std::initializer_list& vec2s) noexcept + { + if (vec2s.size() == 0) + { + SHLOG_WARNING("No arguments passed in! Min value is a default SHVec2.") + return SHVec2{}; + } + + SHVec2 result; + + XMVECTOR min = XMLoadFloat2(&(*vec2s.begin())); + for (auto it = vec2s.begin() + 1; it != vec2s.end(); ++it) + { + const XMVECTOR tmp = XMLoadFloat2(&(*it)); + min = XMVectorMin(min, tmp); + } + + XMStoreFloat2(&result, min); + return result; + } + + SHVec2 SHVec2::Max(const std::initializer_list& vec2s) noexcept + { + if (vec2s.size() == 0) + { + SHLOG_WARNING("No arguments passed in! Max value is a default SHVec2.") + return SHVec2{}; + } + + SHVec2 result; + + XMVECTOR max = XMLoadFloat2(&(*vec2s.begin())); + for (auto it = vec2s.begin() + 1; it != vec2s.end(); ++it) + { + const XMVECTOR tmp = XMLoadFloat2(&(*it)); + max = XMVectorMax(max, tmp); + } + + XMStoreFloat2(&result, max); + return result; + } + + SHVec2 SHVec2::Clamp(const SHVec2& v, const SHVec2& vMin, const SHVec2& vMax) noexcept + { + SHVec2 result; + + const XMVECTOR V = XMLoadFloat2(&v); + const XMVECTOR MIN = XMLoadFloat2(&vMin); + const XMVECTOR MAX = XMLoadFloat2(&vMax); + + XMStoreFloat2(&result, XMVectorClamp(V, MIN, MAX)); + return result; + } + + SHVec2 SHVec2::Lerp(const SHVec2& a, const SHVec2& b, float t) noexcept + { + SHVec2 result; + + const XMVECTOR V1 = XMLoadFloat2(&a); + const XMVECTOR V2 = XMLoadFloat2(&b); + + XMStoreFloat2(&result, XMVectorLerp(V1, V2, t)); + return result; + } + + SHVec2 SHVec2::ClampedLerp(const SHVec2& a, const SHVec2& b, float t, float tMin, float tMax) noexcept + { + return Lerp(a, b, std::clamp(t, tMin, tMax)); + } + + float SHVec2::Distance(const SHVec2& lhs, const SHVec2& rhs) noexcept + { + return (lhs - rhs).Length(); + } + + float SHVec2::DistanceSquared(const SHVec2& lhs, const SHVec2& rhs) noexcept + { + return (lhs - rhs).LengthSquared(); + } + + float SHVec2::Angle(const SHVec2& lhs, const SHVec2& rhs) noexcept + { + const XMVECTOR V1 = XMLoadFloat2(&lhs); + const XMVECTOR V2 = XMLoadFloat2(&rhs); + + return XMVectorGetX(XMVector2AngleBetweenVectors(V1, V2)); + } + + float SHVec2::Dot(const SHVec2& lhs, const SHVec2& rhs) noexcept + { + return lhs.Dot(rhs); + } + + SHVec2 SHVec2::Project(const SHVec2& v, const SHVec2& u) noexcept + { + SHVec2 result; + + const XMVECTOR U = XMLoadFloat2(&u); + const float V_DOT_U = Dot(v, u); + const float U_LENSQ = u.LengthSquared(); + + XMStoreFloat2(&result, XMVectorScale(U, V_DOT_U / U_LENSQ)); + return result; + } + + SHVec2 SHVec2::Reflect(const SHVec2& v, const SHVec2& normal) noexcept + { + SHVec2 result; + + const XMVECTOR V = XMLoadFloat2(&v); + const XMVECTOR N = XMLoadFloat2(&normal); + + XMStoreFloat2(&result, XMVector2Reflect(V, N)); + return result; + } + + SHVec2 SHVec2::Rotate(const SHVec2& v, float angleInRad) noexcept + { + SHVec2 result; + + const XMVECTOR V = XMLoadFloat2(&v); + const XMMATRIX R = XMMatrixRotationZ(angleInRad); + + XMStoreFloat2(&result, XMVector2Transform(V, R)); + return result; + } + + SHVec2 SHVec2::Transform(const SHVec2& v, const SHMatrix& transformMtx) noexcept + { + SHVec2 result; + + const XMVECTOR V = XMLoadFloat2(&v); + const XMMATRIX TF = XMLoadFloat4x4(&transformMtx); + + XMStoreFloat2(&result, XMVector2TransformCoord(V, TF)); + return result; + } + + SHVec2 SHVec2::Cross(float lhs, const SHVec2& rhs) noexcept + { + SHVec2 result; + + const XMFLOAT3 LHS { 0.0f, 0.0f, lhs }; + const XMFLOAT3 RHS { rhs.x, rhs.y, 0.0f }; + + const XMVECTOR V1 = XMLoadFloat3(&LHS); + const XMVECTOR V2 = XMLoadFloat3(&RHS); + + XMStoreFloat2(&result, XMVector3Cross(V1, V2)); + return result; + } + + SHVec2 SHVec2::Cross(const SHVec2& lhs, float rhs) noexcept + { + SHVec2 result; + + const XMFLOAT3 LHS { lhs.x, lhs.y, 0.0f }; + const XMFLOAT3 RHS { 0.0f, 0.0f, rhs }; + + const XMVECTOR V1 = XMLoadFloat3(&LHS); + const XMVECTOR V2 = XMLoadFloat3(&RHS); + + XMStoreFloat2(&result, XMVector3Cross(V1, V2)); + return result; + } + + float SHVec2::Cross(const SHVec2& lhs, const SHVec2& rhs) noexcept + { + return (lhs.x * rhs.y) - (lhs.y * rhs.x); + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec2.h b/SHADE_Engine/src/Math/Vector/SHVec2.h new file mode 100644 index 00000000..a64d4bb0 --- /dev/null +++ b/SHADE_Engine/src/Math/Vector/SHVec2.h @@ -0,0 +1,122 @@ +/**************************************************************************************** + * \file SHVec2.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for 2D Vector. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHMatrix; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SHVec2 : public DirectX::XMFLOAT2 + { + public: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr size_t SIZE = 2U; + + static const SHVec2 Zero; + static const SHVec2 One; + static const SHVec2 Left; + static const SHVec2 Right; + static const SHVec2 Up; + static const SHVec2 Down; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHVec2 (const SHVec2& rhs) = default; + SHVec2 (SHVec2&& rhs) = default; + ~SHVec2 () = default; + + SHVec2 () noexcept; + SHVec2 (float x, float y) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] SHVec2& operator= (const SHVec2& rhs) = default; + [[nodiscard]] SHVec2& operator= (SHVec2&& rhs) = default; + + [[nodiscard]] SHVec2& operator+= (const SHVec2& rhs) noexcept; + [[nodiscard]] SHVec2& operator-= (const SHVec2& rhs) noexcept; + [[nodiscard]] SHVec2& operator*= (const SHVec2& rhs) noexcept; + [[nodiscard]] SHVec2& operator*= (float rhs) noexcept; + [[nodiscard]] SHVec2& operator/= (const SHVec2& rhs) noexcept; + [[nodiscard]] SHVec2& operator/= (float rhs) noexcept; + + [[nodiscard]] SHVec2 operator+ (const SHVec2& rhs) const noexcept; + [[nodiscard]] SHVec2 operator- (const SHVec2& rhs) const noexcept; + [[nodiscard]] SHVec2 operator- () const noexcept; + [[nodiscard]] SHVec2 operator* (const SHVec2& rhs) const noexcept; + [[nodiscard]] SHVec2 operator* (float rhs) const noexcept; + [[nodiscard]] SHVec2 operator/ (const SHVec2& rhs) const noexcept; + [[nodiscard]] SHVec2 operator/ (float rhs) const noexcept; + + [[nodiscard]] bool operator== (const SHVec2& rhs) const noexcept; + [[nodiscard]] bool operator!= (const SHVec2& rhs) const noexcept; + + [[nodiscard]] float operator[] (int index); + [[nodiscard]] float operator[] (size_t index); + [[nodiscard]] float operator[] (int index) const; + [[nodiscard]] float operator[] (size_t index) const; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] float Length () const noexcept; + [[nodiscard]] float LengthSquared () const noexcept; + [[nodiscard]] std::string ToString () const noexcept; + + [[nodiscard]] float Dot (const SHVec2& rhs) const noexcept; + [[nodiscard]] SHVec2 Cross (const SHVec2& rhs) const noexcept; + /*---------------------------------------------------------------------------------*/ + /* Static Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] static SHVec2 Normalise (const SHVec2& vec2) noexcept; + [[nodiscard]] static SHVec2 Abs (const SHVec2& vec2) noexcept; + [[nodiscard]] static SHVec2 Min (const std::initializer_list& vec2s) noexcept; + [[nodiscard]] static SHVec2 Max (const std::initializer_list& vec2s) noexcept; + [[nodiscard]] static SHVec2 Clamp (const SHVec2& v, const SHVec2& vMin, const SHVec2& vMax) noexcept; + [[nodiscard]] static SHVec2 Lerp (const SHVec2& a, const SHVec2& b, float t) noexcept; + [[nodiscard]] static SHVec2 ClampedLerp (const SHVec2& a, const SHVec2& b, float t, float tMin = 0.0f, float tMax = 1.0f) noexcept; + + [[nodiscard]] static float Distance (const SHVec2& lhs, const SHVec2& rhs) noexcept; + [[nodiscard]] static float DistanceSquared (const SHVec2& lhs, const SHVec2& rhs) noexcept; + [[nodiscard]] static float Angle (const SHVec2& lhs, const SHVec2& rhs) noexcept; + [[nodiscard]] static float Dot (const SHVec2& lhs, const SHVec2& rhs) noexcept; + [[nodiscard]] static SHVec2 Project (const SHVec2& v, const SHVec2& u) noexcept; + [[nodiscard]] static SHVec2 Reflect (const SHVec2& v, const SHVec2& normal) noexcept; + [[nodiscard]] static SHVec2 Rotate (const SHVec2& v, float angleInRad) noexcept; + [[nodiscard]] static SHVec2 Transform (const SHVec2& v, const SHMatrix& transformMtx) noexcept; + [[nodiscard]] static SHVec2 Cross (float lhs, const SHVec2& rhs) noexcept; + [[nodiscard]] static SHVec2 Cross (const SHVec2& lhs, float rhs) noexcept; + [[nodiscard]] static float Cross (const SHVec2& lhs, const SHVec2& rhs) noexcept; + }; + + SHVec2 operator* (float lhs, const SHVec2& rhs) noexcept; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec3.cpp b/SHADE_Engine/src/Math/Vector/SHVec3.cpp new file mode 100644 index 00000000..73030f9c --- /dev/null +++ b/SHADE_Engine/src/Math/Vector/SHVec3.cpp @@ -0,0 +1,477 @@ +/**************************************************************************************** + * \file SHVec3.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for 3D Vector. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHVec3.h" +// Project Headers +#include "Math/SHMatrix.h" +#include "Tools/SHLogger.h" + +using namespace DirectX; + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + SHVec3 const SHVec3::Zero { 0.0f, 0.0f, 0.0f }; + SHVec3 const SHVec3::One { 1.0f, 1.0f, 1.0f }; + SHVec3 const SHVec3::Left { -1.0f, 0.0f, 0.0f }; + SHVec3 const SHVec3::Right { 1.0f, 0.0f, 0.0f }; + SHVec3 const SHVec3::Up { 0.0f, 1.0f, 0.0f }; + SHVec3 const SHVec3::Down { 0.0f, -1.0f, 0.0f }; + SHVec3 const SHVec3::Forward { 0.0f, 0.0f, 1.0f }; + SHVec3 const SHVec3::Back { 0.0f, 0.0f, -1.0f }; + SHVec3 const SHVec3::UnitX { 1.0f, 0.0f, 0.0f }; + SHVec3 const SHVec3::UnitY { 0.0f, 1.0f, 0.0f }; + SHVec3 const SHVec3::UnitZ { 0.0f, 0.0f, 1.0f }; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec3::SHVec3() noexcept + : XMFLOAT3( 0.0f, 0.0f, 0.0f ) + {} + + SHVec3::SHVec3(float _x, float _y, float _z) noexcept + : XMFLOAT3( _x, _y, _z ) + {} + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec3& SHVec3::operator+=(const SHVec3& rhs) noexcept + { + return *this = *this + rhs; + } + + SHVec3& SHVec3::operator-=(const SHVec3& rhs) noexcept + { + return *this = *this - rhs; + } + + SHVec3& SHVec3::operator*=(const SHVec3& rhs) noexcept + { + return *this = *this * rhs; + } + + SHVec3& SHVec3::operator*=(float rhs) noexcept + { + return *this = *this * rhs; + } + + SHVec3& SHVec3::operator/=(const SHVec3& rhs) noexcept + { + return *this = *this / rhs; + } + + SHVec3& SHVec3::operator/=(float rhs) noexcept + { + return *this = *this / rhs; + } + + SHVec3 SHVec3::operator+(const SHVec3& rhs) const noexcept + { + SHVec3 result; + + const XMVECTOR V1 = XMLoadFloat3(this); + const XMVECTOR V2 = XMLoadFloat3(&rhs); + + XMStoreFloat3(&result, XMVectorAdd(V1, V2)); + return result; + } + + SHVec3 SHVec3::operator-(const SHVec3& rhs) const noexcept + { + SHVec3 result; + + const XMVECTOR V1 = XMLoadFloat3(this); + const XMVECTOR V2 = XMLoadFloat3(&rhs); + + XMStoreFloat3(&result, XMVectorSubtract(V1, V2)); + return result; + } + + SHVec3 SHVec3::operator-() const noexcept + { + return SHVec3{ -x, -y, -z }; + } + + + SHVec3 SHVec3::operator*(const SHVec3& rhs) const noexcept + { + SHVec3 result; + + const XMVECTOR V1 = XMLoadFloat3(this); + const XMVECTOR V2 = XMLoadFloat3(&rhs); + + XMStoreFloat3(&result, XMVectorMultiply(V1, V2)); + return result; + } + + SHVec3 SHVec3::operator*(float rhs) const noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(this); + + XMStoreFloat3(&result, XMVectorScale(V, rhs)); + return result; + } + + SHVec3 SHVec3::operator/(const SHVec3& rhs) const noexcept + { + SHVec3 result; + + const XMVECTOR V1 = XMLoadFloat3(this); + const XMVECTOR V2 = XMLoadFloat3(&rhs); + + XMStoreFloat3(&result, XMVectorDivide(V1, V2)); + return result; + } + + SHVec3 SHVec3::operator/(float rhs) const noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(this); + + XMStoreFloat3(&result, XMVectorScale(V, 1.0f / rhs)); + return result; + } + + bool SHVec3::operator==(const SHVec3& rhs) const noexcept + { + const XMVECTOR V1 = XMLoadFloat3(this); + const XMVECTOR V2 = XMLoadFloat3(&rhs); + + return XMVector3Equal(V1, V2); + } + + bool SHVec3::operator!=(const SHVec3& rhs) const noexcept + { + const XMVECTOR V1 = XMLoadFloat3(this); + const XMVECTOR V2 = XMLoadFloat3(&rhs); + + return XMVector3NotEqual(V1, V2); + } + + float SHVec3::operator[](int index) + { + if (index >= SIZE || index < 0) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + default: return 0.0f; + } + } + + float SHVec3::operator[](size_t index) + { + if (index >= SIZE) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + default: return 0.0f; + } + } + + float SHVec3::operator[](int index) const + { + if (index >= SIZE || index < 0) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + default: return 0.0f; + } + } + + float SHVec3::operator[](size_t index) const + { + if (index >= SIZE) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + default: return 0.0f; + } + } + + SHVec3 operator* (float lhs, const SHVec3& rhs) noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(&rhs); + + XMStoreFloat3(&result, XMVectorScale(V, lhs)); + return result; + } + + /*-----------------------------------------------------------------------------------*/ + /* Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + float SHVec3::Length() const noexcept + { + const XMVECTOR V = XMLoadFloat3(this); + + return XMVectorGetX(XMVector3Length(V)); + } + + float SHVec3::LengthSquared() const noexcept + { + const XMVECTOR V = XMLoadFloat3(this); + + return XMVectorGetX(XMVector3LengthSq(V)); + } + + std::string SHVec3::ToString() const noexcept + { + std::stringstream ss; + ss << std::fixed << std::setprecision(3); + ss << "<" << x << ", " << y << ", " << z << ">"; + return ss.str(); + } + + float SHVec3::Dot(const SHVec3& rhs) const noexcept + { + const XMVECTOR V1 = XMLoadFloat3(this); + const XMVECTOR V2 = XMLoadFloat3(&rhs); + + return XMVectorGetX(XMVector3Dot(V1, V2)); + } + + SHVec3 SHVec3::Cross(const SHVec3& rhs) const noexcept + { + SHVec3 result; + + const XMVECTOR V1 = XMLoadFloat3(this); + const XMVECTOR V2 = XMLoadFloat3(&rhs); + + XMStoreFloat3(&result, XMVector3Cross(V1, V2)); + return result; + } + + /*-----------------------------------------------------------------------------------*/ + /* Static Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec3 SHVec3::Normalise(const SHVec3& v) noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(&v); + + XMStoreFloat3(&result, XMVector3Normalize(V)); + return result; + } + + SHVec3 SHVec3::Abs(const SHVec3& v) noexcept + { + return SHVec3{ std::fabs(v.x), std::fabs(v.y), std::fabs(v.z) }; + } + + SHVec3 SHVec3::Min(const std::initializer_list& vs) noexcept + { + if (vs.size() == 0) + { + SHLOG_WARNING("No arguments passed in! Min value is a default SHVec3.") + return SHVec3{}; + } + + SHVec3 result; + + XMVECTOR min = XMLoadFloat3(&(*vs.begin())); + for (auto it = vs.begin() + 1; it != vs.end(); ++it) + { + const XMVECTOR tmp = XMLoadFloat3(&(*it)); + min = XMVectorMin(min, tmp); + } + + XMStoreFloat3(&result, min); + return result; + } + + SHVec3 SHVec3::Max(const std::initializer_list& vs) noexcept + { + if (vs.size() == 0) + { + SHLOG_WARNING("No arguments passed in! Max value is a default SHVec3.") + return SHVec3{}; + } + + SHVec3 result; + + XMVECTOR max = XMLoadFloat3(&(*vs.begin())); + for (auto it = vs.begin() + 1; it != vs.end(); ++it) + { + const XMVECTOR tmp = XMLoadFloat3(&(*it)); + max = XMVectorMax(max, tmp); + } + + XMStoreFloat3(&result, max); + return result; + } + + SHVec3 SHVec3::Clamp(const SHVec3& v, const SHVec3& vMin, const SHVec3& vMax) noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(&v); + const XMVECTOR MIN = XMLoadFloat3(&vMin); + const XMVECTOR MAX = XMLoadFloat3(&vMax); + + XMStoreFloat3(&result, XMVectorClamp(V, MIN, MAX)); + return result; + } + + SHVec3 SHVec3::Lerp(const SHVec3& a, const SHVec3& b, float t) noexcept + { + SHVec3 result; + + const XMVECTOR V1 = XMLoadFloat3(&a); + const XMVECTOR V2 = XMLoadFloat3(&b); + + XMStoreFloat3(&result, XMVectorLerp(V1, V2, t)); + return result; + } + + SHVec3 SHVec3::ClampedLerp(const SHVec3& a, const SHVec3& b, float t, float tMin, float tMax) noexcept + { + return Lerp(a, b, std::clamp(t, tMin, tMax)); + } + + float SHVec3::Distance(const SHVec3& lhs, const SHVec3& rhs) noexcept + { + return (lhs - rhs).Length(); + } + + float SHVec3::DistanceSquared(const SHVec3& lhs, const SHVec3& rhs) noexcept + { + return (lhs - rhs).LengthSquared(); + } + + float SHVec3::Angle(const SHVec3& lhs, const SHVec3& rhs) noexcept + { + const XMVECTOR V1 = XMLoadFloat3(&lhs); + const XMVECTOR V2 = XMLoadFloat3(&rhs); + + return XMVectorGetX(XMVector3AngleBetweenVectors(V1, V2)); + } + + float SHVec3::Dot(const SHVec3& lhs, const SHVec3& rhs) noexcept + { + return lhs.Dot(rhs); + } + + SHVec3 SHVec3::Cross(const SHVec3& lhs, const SHVec3& rhs) noexcept + { + return lhs.Cross(rhs); + } + + SHVec3 SHVec3::Project(const SHVec3& v, const SHVec3& u) noexcept + { + SHVec3 result; + + const XMVECTOR U = XMLoadFloat3(&u); + const float V_DOT_U = Dot(v, u); + const float U_LENSQ = u.LengthSquared(); + + XMStoreFloat3(&result, XMVectorScale(U, V_DOT_U / U_LENSQ)); + return result; + } + + SHVec3 SHVec3::Reflect(const SHVec3& v, const SHVec3& normal) noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(&v); + const XMVECTOR N = XMLoadFloat3(&normal); + + XMStoreFloat3(&result, XMVector3Reflect(V, N)); + return result; + } + + SHVec3 SHVec3::Rotate(const SHVec3& v, const SHVec3& axis, float angleInRad) noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(&v); + + const XMVECTOR AXIS = XMLoadFloat3(&axis); + const XMVECTOR Q = XMQuaternionRotationAxis(AXIS, angleInRad); + + XMStoreFloat3(&result, XMVector3Rotate(V, Q)); + return result; + } + + SHVec3 SHVec3::RotateX(const SHVec3& v, float angleInRad) noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(&v); + const XMMATRIX R = XMMatrixRotationX(angleInRad); + + XMStoreFloat3(&result, XMVector3TransformCoord(V, R)); + return result; + } + + SHVec3 SHVec3::RotateY(const SHVec3& v, float angleInRad) noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(&v); + const XMMATRIX R = XMMatrixRotationY(angleInRad); + + XMStoreFloat3(&result, XMVector3TransformCoord(V, R)); + return result; + } + + SHVec3 SHVec3::RotateZ(const SHVec3& v, float angleInRad) noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(&v); + const XMMATRIX R = XMMatrixRotationZ(angleInRad); + + XMStoreFloat3(&result, XMVector3TransformCoord(V, R)); + return result; + } + + SHVec3 SHVec3::Transform(const SHVec3& v, const SHMatrix& transformMtx) noexcept + { + SHVec3 result; + + const XMVECTOR V = XMLoadFloat3(&v); + const XMMATRIX TF = XMLoadFloat4x4(&transformMtx); + + XMStoreFloat3(&result, XMVector3TransformCoord(V, TF)); + return result; + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec3.h b/SHADE_Engine/src/Math/Vector/SHVec3.h new file mode 100644 index 00000000..e172e824 --- /dev/null +++ b/SHADE_Engine/src/Math/Vector/SHVec3.h @@ -0,0 +1,129 @@ +/**************************************************************************************** + * \file SHVec3.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for 3D Vector. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHMatrix; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SHVec3 : public DirectX::XMFLOAT3 + { + public: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr size_t SIZE = 3U; + + static const SHVec3 Zero; + static const SHVec3 One; + static const SHVec3 Left; + static const SHVec3 Right; + static const SHVec3 Up; + static const SHVec3 Down; + static const SHVec3 Forward; + static const SHVec3 Back; + static const SHVec3 UnitX; + static const SHVec3 UnitY; + static const SHVec3 UnitZ; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHVec3 (const SHVec3& rhs) = default; + SHVec3 (SHVec3&& rhs) = default; + ~SHVec3 () = default; + + SHVec3 () noexcept; + SHVec3 (float x, float y, float z) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] SHVec3& operator= (const SHVec3& rhs) = default; + [[nodiscard]] SHVec3& operator= (SHVec3&& rhs) = default; + + [[nodiscard]] SHVec3& operator+= (const SHVec3& rhs) noexcept; + [[nodiscard]] SHVec3& operator-= (const SHVec3& rhs) noexcept; + [[nodiscard]] SHVec3& operator*= (const SHVec3& rhs) noexcept; + [[nodiscard]] SHVec3& operator*= (float rhs) noexcept; + [[nodiscard]] SHVec3& operator/= (const SHVec3& rhs) noexcept; + [[nodiscard]] SHVec3& operator/= (float rhs) noexcept; + + [[nodiscard]] SHVec3 operator+ (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec3 operator- (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec3 operator- () const noexcept; + [[nodiscard]] SHVec3 operator* (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec3 operator* (float rhs) const noexcept; + [[nodiscard]] SHVec3 operator/ (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec3 operator/ (float rhs) const noexcept; + + [[nodiscard]] bool operator== (const SHVec3& rhs) const noexcept; + [[nodiscard]] bool operator!= (const SHVec3& rhs) const noexcept; + + [[nodiscard]] float operator[] (int index); + [[nodiscard]] float operator[] (size_t index); + [[nodiscard]] float operator[] (int index) const; + [[nodiscard]] float operator[] (size_t index) const; + + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] float Length () const noexcept; + [[nodiscard]] float LengthSquared () const noexcept; + [[nodiscard]] std::string ToString () const noexcept; + + [[nodiscard]] float Dot (const SHVec3& rhs) const noexcept; + [[nodiscard]] SHVec3 Cross (const SHVec3& rhs) const noexcept; + /*---------------------------------------------------------------------------------*/ + /* Static Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] static SHVec3 Normalise (const SHVec3& v) noexcept; + [[nodiscard]] static SHVec3 Abs (const SHVec3& v) noexcept; + [[nodiscard]] static SHVec3 Min (const std::initializer_list& vs) noexcept; + [[nodiscard]] static SHVec3 Max (const std::initializer_list& vs) noexcept; + [[nodiscard]] static SHVec3 Clamp (const SHVec3& v, const SHVec3& vMin, const SHVec3& vMax) noexcept; + [[nodiscard]] static SHVec3 Lerp (const SHVec3& a, const SHVec3& b, float t) noexcept; + [[nodiscard]] static SHVec3 ClampedLerp (const SHVec3& a, const SHVec3& b, float t, float tMin = 0.0f, float tMax = 1.0f) noexcept; + + [[nodiscard]] static float Distance (const SHVec3& lhs, const SHVec3& rhs) noexcept; + [[nodiscard]] static float DistanceSquared (const SHVec3& lhs, const SHVec3& rhs) noexcept; + [[nodiscard]] static float Angle (const SHVec3& lhs, const SHVec3& rhs) noexcept; + [[nodiscard]] static float Dot (const SHVec3& lhs, const SHVec3& rhs) noexcept; + [[nodiscard]] static SHVec3 Cross (const SHVec3& lhs, const SHVec3& rhs) noexcept; + [[nodiscard]] static SHVec3 Project (const SHVec3& v, const SHVec3& u) noexcept; + [[nodiscard]] static SHVec3 Reflect (const SHVec3& v, const SHVec3& normal) noexcept; + [[nodiscard]] static SHVec3 Rotate (const SHVec3& v, const SHVec3& axis, float angleInRad) noexcept; + [[nodiscard]] static SHVec3 RotateX (const SHVec3& v, float angleInRad) noexcept; + [[nodiscard]] static SHVec3 RotateY (const SHVec3& v, float angleInRad) noexcept; + [[nodiscard]] static SHVec3 RotateZ (const SHVec3& v, float angleInRad) noexcept; + [[nodiscard]] static SHVec3 Transform (const SHVec3& v, const SHMatrix& transformMtx) noexcept; + }; + + SHVec3 operator* (float lhs, const SHVec3& rhs) noexcept; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec4.cpp b/SHADE_Engine/src/Math/Vector/SHVec4.cpp new file mode 100644 index 00000000..5d75af33 --- /dev/null +++ b/SHADE_Engine/src/Math/Vector/SHVec4.cpp @@ -0,0 +1,529 @@ +/**************************************************************************************** + * \file SHVec4.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for 4D Vector. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHVec4.h" +// Project Headers +#include "Math/SHMatrix.h" +#include "Tools/SHLogger.h" + +using namespace DirectX; + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + SHVec4 const SHVec4::Zero { 0.0f, 0.0f, 0.0f, 0.0f }; + SHVec4 const SHVec4::One { 1.0f, 1.0f, 1.0f, 1.0f }; + SHVec4 const SHVec4::UnitX { 1.0f, 0.0f, 0.0f, 0.0f }; + SHVec4 const SHVec4::UnitY { 0.0f, 1.0f, 0.0f, 0.0f }; + SHVec4 const SHVec4::UnitZ { 0.0f, 0.0f, 1.0f, 0.0f }; + SHVec4 const SHVec4::UnitW { 0.0f, 0.0f, 0.0f, 1.0f }; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec4::SHVec4() noexcept + : XMFLOAT4( 0.0f, 0.0f, 0.0f, 0.0f ) + {} + + SHVec4::SHVec4(float _x, float _y, float _z, float _w) noexcept + : XMFLOAT4( _x, _y, _z, _w ) + {} + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec4& SHVec4::operator+=(const SHVec4& rhs) noexcept + { + return *this = *this + rhs; + } + + SHVec4& SHVec4::operator-=(const SHVec4& rhs) noexcept + { + return *this = *this - rhs; + } + + SHVec4& SHVec4::operator*=(const SHVec4& rhs) noexcept + { + return *this = *this * rhs; + } + + SHVec4& SHVec4::operator*=(float rhs) noexcept + { + return *this = *this * rhs; + } + + SHVec4& SHVec4::operator/=(const SHVec4& rhs) noexcept + { + return *this = *this / rhs; + } + + SHVec4& SHVec4::operator/=(float rhs) noexcept + { + return *this = *this / rhs; + } + + SHVec4 SHVec4::operator+(const SHVec4& rhs) const noexcept + { + SHVec4 result; + + const XMVECTOR V1 = XMLoadFloat4(this); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + XMStoreFloat4(&result, XMVectorAdd(V1, V2)); + return result; + } + + SHVec4 SHVec4::operator-(const SHVec4& rhs) const noexcept + { + SHVec4 result; + + const XMVECTOR V1 = XMLoadFloat4(this); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + XMStoreFloat4(&result, XMVectorSubtract(V1, V2)); + return result; + } + + SHVec4 SHVec4::operator-() const noexcept + { + return SHVec4{ -x, -y, -z, -w }; + } + + SHVec4 SHVec4::operator*(const SHVec4& rhs) const noexcept + { + SHVec4 result; + + const XMVECTOR V1 = XMLoadFloat4(this); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + XMStoreFloat4(&result, XMVectorMultiply(V1, V2)); + return result; + } + + SHVec4 SHVec4::operator*(float rhs) const noexcept + { + SHVec4 result; + + const XMVECTOR V = XMLoadFloat4(this); + + XMStoreFloat4(&result, XMVectorScale(V, rhs)); + return result; + } + + SHVec4 SHVec4::operator/(const SHVec4& rhs) const noexcept + { + SHVec4 result; + + const XMVECTOR V1 = XMLoadFloat4(this); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + XMStoreFloat4(&result, XMVectorDivide(V1, V2)); + return result; + } + + SHVec4 SHVec4::operator/(float rhs) const noexcept + { + SHVec4 result; + + const XMVECTOR V = XMLoadFloat4(this); + + XMStoreFloat4(&result, XMVectorScale(V, 1.0f / rhs)); + return result; + } + + bool SHVec4::operator==(const SHVec4& rhs) const noexcept + { + const XMVECTOR V1 = XMLoadFloat4(this); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + return XMVector4Equal(V1, V2); + } + + bool SHVec4::operator!=(const SHVec4& rhs) const noexcept + { + const XMVECTOR V1 = XMLoadFloat4(this); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + return XMVector4NotEqual(V1, V2); + } + + float SHVec4::operator[](int index) + { + if (index >= SIZE || index < 0) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + case 3: return w; + default: return 0.0f; + } + } + + float SHVec4::operator[](size_t index) + { + if (index >= SIZE) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + case 3: return w; + default: return 0.0f; + } + } + + float SHVec4::operator[](int index) const + { + if (index >= SIZE || index < 0) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + case 3: return w; + default: return 0.0f; + } + } + + float SHVec4::operator[](size_t index) const + { + if (index >= SIZE) + throw std::invalid_argument("Index out of range!"); + + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + case 3: return w; + default: return 0.0f; + } + } + + SHVec4 operator* (float lhs, const SHVec4& rhs) noexcept + { + SHVec4 result; + + const XMVECTOR V = XMLoadFloat4(&rhs); + + XMStoreFloat4(&result, XMVectorScale(V, lhs)); + return result; + } + + /*-----------------------------------------------------------------------------------*/ + /* Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + float SHVec4::Length() const noexcept + { + const XMVECTOR V = XMLoadFloat4(this); + + return XMVectorGetX(XMVector4Length(V)); + } + + float SHVec4::Length3D() const noexcept + { + const XMVECTOR V = XMLoadFloat4(this); + + return XMVectorGetX(XMVector3Length(V)); + } + + float SHVec4::LengthSquared() const noexcept + { + const XMVECTOR V = XMLoadFloat4(this); + + return XMVectorGetX(XMVector4LengthSq(V)); + } + + float SHVec4::LengthSquared3D() const noexcept + { + const XMVECTOR V = XMLoadFloat4(this); + + return XMVectorGetX(XMVector3LengthSq(V)); + } + + std::string SHVec4::ToString() const noexcept + { + std::stringstream ss; + ss << std::fixed << std::setprecision(3); + ss << "<" << x << ", " << y << ", " << z << ", " << w <<">"; + return ss.str(); + } + + float SHVec4::Dot(const SHVec4& rhs) const noexcept + { + const XMVECTOR V1 = XMLoadFloat4(this); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + return XMVectorGetX(XMVector4Dot(V1, V2)); + } + + float SHVec4::Dot3D(const SHVec4& rhs) const noexcept + { + const XMVECTOR V1 = XMLoadFloat4(this); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + return XMVectorGetX(XMVector3Dot(V1, V2)); + } + + SHVec4 SHVec4::Cross3D(const SHVec4& rhs) const noexcept + { + SHVec4 result; + + const XMVECTOR V1 = XMLoadFloat4(this); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + XMStoreFloat4(&result, XMVector3Cross(V1, V2)); + result.w = 1.0f; + return result; + } + + SHVec4 SHVec4::Cross(const SHVec4& v1, const SHVec4& v2) const noexcept + { + SHVec4 result; + + const XMVECTOR V3 = XMLoadFloat4(this); + const XMVECTOR V1 = XMLoadFloat4(&v1); + const XMVECTOR V2 = XMLoadFloat4(&v2); + + XMStoreFloat4(&result, XMVector4Cross(V3, V1, V2)); + return result; + } + + /*-----------------------------------------------------------------------------------*/ + /* Static Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec4 SHVec4::Normalise(const SHVec4& v) noexcept + { + SHVec4 result; + + const XMVECTOR V = XMLoadFloat4(&v); + + XMStoreFloat4(&result, XMVector4Normalize(V)); + return result; + } + + SHVec4 SHVec4::Normalise3D(const SHVec4& v) noexcept + { + SHVec4 result; + + const XMVECTOR V = XMLoadFloat4(&v); + + XMStoreFloat4(&result, XMVector3Normalize(V)); + result.w = 1.0f; + return result; + } + + SHVec4 SHVec4::Abs(const SHVec4& v) noexcept + { + return SHVec4{ std::fabs(v.x), std::fabs(v.y), std::fabs(v.z), std::fabs(v.w) }; + } + + SHVec4 SHVec4::Min(const std::initializer_list& vs) noexcept + { + if (vs.size() == 0) + { + SHLOG_WARNING("No arguments passed in! Min value is a default SHVec4.") + return SHVec4{}; + } + + SHVec4 result; + + XMVECTOR min = XMLoadFloat4(&(*vs.begin())); + for (auto it = vs.begin() + 1; it != vs.end(); ++it) + { + const XMVECTOR tmp = XMLoadFloat4(&(*it)); + min = XMVectorMin(min, tmp); + } + + XMStoreFloat4(&result, min); + return result; + } + + SHVec4 SHVec4::Max(const std::initializer_list& vs) noexcept + { + if (vs.size() == 0) + { + SHLOG_WARNING("No arguments passed in! Max value is a default SHVec4.") + return SHVec4{}; + } + + SHVec4 result; + + XMVECTOR max = XMLoadFloat4(&(*vs.begin())); + for (auto it = vs.begin() + 1; it != vs.end(); ++it) + { + const XMVECTOR tmp = XMLoadFloat4(&(*it)); + max = XMVectorMax(max, tmp); + } + + XMStoreFloat4(&result, max); + return result; + } + + SHVec4 SHVec4::Clamp(const SHVec4& v, const SHVec4& vMin, const SHVec4& vMax) noexcept + { + SHVec4 result; + + const XMVECTOR V = XMLoadFloat4(&v); + const XMVECTOR MIN = XMLoadFloat4(&vMin); + const XMVECTOR MAX = XMLoadFloat4(&vMax); + + XMStoreFloat4(&result, XMVectorClamp(V, MIN, MAX)); + return result; + } + + SHVec4 SHVec4::Lerp(const SHVec4& a, const SHVec4& b, float t) noexcept + { + SHVec4 result; + + const XMVECTOR V1 = XMLoadFloat4(&a); + const XMVECTOR V2 = XMLoadFloat4(&b); + + XMStoreFloat4(&result, XMVectorLerp(V1, V2, t)); + return result; + } + + SHVec4 SHVec4::ClampedLerp(const SHVec4& a, const SHVec4& b, float t, float tMin, float tMax) noexcept + { + return Lerp(a, b, std::clamp(t, tMin, tMax)); + } + + float SHVec4::Distance(const SHVec4& lhs, const SHVec4& rhs) noexcept + { + return (lhs - rhs).Length(); + } + + float SHVec4::Distance3D(const SHVec4& lhs, const SHVec4& rhs) noexcept + { + return (lhs - rhs).Length3D(); + } + + float SHVec4::DistanceSquared(const SHVec4& lhs, const SHVec4& rhs) noexcept + { + return (lhs - rhs).LengthSquared(); + } + + float SHVec4::DistanceSquared3D(const SHVec4& lhs, const SHVec4& rhs) noexcept + { + return (lhs - rhs).LengthSquared3D(); + } + + float SHVec4::Angle(const SHVec4& lhs, const SHVec4& rhs) noexcept + { + const XMVECTOR V1 = XMLoadFloat4(&lhs); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + return XMVectorGetX(XMVector4AngleBetweenVectors(V1, V2)); + } + + float SHVec4::Angle3D(const SHVec4& lhs, const SHVec4& rhs) noexcept + { + const XMVECTOR V1 = XMLoadFloat4(&lhs); + const XMVECTOR V2 = XMLoadFloat4(&rhs); + + return XMVectorGetX(XMVector3AngleBetweenVectors(V1, V2)); + } + + float SHVec4::Dot(const SHVec4& lhs, const SHVec4& rhs) noexcept + { + return lhs.Dot(rhs); + } + + float SHVec4::Dot3D(const SHVec4& lhs, const SHVec4& rhs) noexcept + { + return lhs.Dot3D(rhs); + } + + SHVec4 SHVec4::Cross3D(const SHVec4& lhs, const SHVec4& rhs) noexcept + { + return lhs.Cross3D(rhs); + } + + SHVec4 SHVec4::Cross(const SHVec4& v1, const SHVec4& v2, const SHVec4& v3) noexcept + { + return v1.Cross(v2, v3); + } + + SHVec4 SHVec4::Project(const SHVec4& v, const SHVec4& u) noexcept + { + SHVec4 result; + + const XMVECTOR U = XMLoadFloat4(&u); + const float V_DOT_U = Dot(v, u); + const float U_LENSQ = u.LengthSquared(); + + XMStoreFloat4(&result, XMVectorScale(U, V_DOT_U / U_LENSQ)); + return result; + } + + SHVec4 SHVec4::Project3D(const SHVec4& v, const SHVec4& u) noexcept + { + SHVec4 result; + + const XMVECTOR U = XMLoadFloat4(&u); + const float V_DOT_U = Dot3D(v, u); + const float U_LENSQ = u.LengthSquared3D(); + + XMStoreFloat4(&result, XMVectorScale(U, V_DOT_U / U_LENSQ)); + result.w = 1.0f; + return result; + } + + SHVec4 SHVec4::Reflect(const SHVec4& v, const SHVec4& normal) noexcept + { + SHVec4 result; + + const XMVECTOR V = XMLoadFloat4(&v); + const XMVECTOR N = XMLoadFloat4(&normal); + + XMStoreFloat4(&result, XMVector4Reflect(V, N)); + result.w = 1.0f; + return result; + } + + SHVec4 SHVec4::Reflect3D(const SHVec4& v, const SHVec4& normal) noexcept + { + SHVec4 result; + + const XMVECTOR V = XMLoadFloat4(&v); + const XMVECTOR N = XMLoadFloat4(&normal); + + XMStoreFloat4(&result, XMVector3Reflect(V, N)); + result.w = 1.0f; + return result; + } + + SHVec4 SHVec4::Transform3D(const SHVec4& v, const SHMatrix& transformMtx) noexcept + { + SHVec4 result; + + const XMVECTOR V = XMLoadFloat4(&v); + const XMMATRIX TF = XMLoadFloat4x4(&transformMtx); + + XMStoreFloat4(&result, XMVector3TransformCoord(V, TF)); + return result; + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Vector/SHVec4.h b/SHADE_Engine/src/Math/Vector/SHVec4.h new file mode 100644 index 00000000..c4caf2c8 --- /dev/null +++ b/SHADE_Engine/src/Math/Vector/SHVec4.h @@ -0,0 +1,133 @@ +/**************************************************************************************** + * \file SHVec4.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for 4D Vector. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHMatrix; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SHVec4 : public DirectX::XMFLOAT4 + { + public: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr size_t SIZE = 4U; + + static const SHVec4 Zero; + static const SHVec4 One; + static const SHVec4 UnitX; + static const SHVec4 UnitY; + static const SHVec4 UnitZ; + static const SHVec4 UnitW; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHVec4 (const SHVec4& rhs) = default; + SHVec4 (SHVec4&& rhs) = default; + ~SHVec4 () = default; + + SHVec4 () noexcept; + SHVec4 (float x, float y, float z, float w) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] SHVec4& operator= (const SHVec4& rhs) = default; + [[nodiscard]] SHVec4& operator= (SHVec4&& rhs) = default; + + [[nodiscard]] SHVec4& operator+= (const SHVec4& rhs) noexcept; + [[nodiscard]] SHVec4& operator-= (const SHVec4& rhs) noexcept; + [[nodiscard]] SHVec4& operator*= (const SHVec4& rhs) noexcept; + [[nodiscard]] SHVec4& operator*= (float rhs) noexcept; + [[nodiscard]] SHVec4& operator/= (const SHVec4& rhs) noexcept; + [[nodiscard]] SHVec4& operator/= (float rhs) noexcept; + + [[nodiscard]] SHVec4 operator+ (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 operator- (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 operator- () const noexcept; + [[nodiscard]] SHVec4 operator* (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 operator* (float rhs) const noexcept; + [[nodiscard]] SHVec4 operator/ (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 operator/ (float rhs) const noexcept; + + [[nodiscard]] bool operator== (const SHVec4& rhs) const noexcept; + [[nodiscard]] bool operator!= (const SHVec4& rhs) const noexcept; + + [[nodiscard]] float operator[] (int index); + [[nodiscard]] float operator[] (size_t index); + [[nodiscard]] float operator[] (int index) const; + [[nodiscard]] float operator[] (size_t index) const; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] float Length () const noexcept; + [[nodiscard]] float Length3D () const noexcept; + [[nodiscard]] float LengthSquared () const noexcept; + [[nodiscard]] float LengthSquared3D () const noexcept; + [[nodiscard]] std::string ToString () const noexcept; + + [[nodiscard]] float Dot (const SHVec4& rhs) const noexcept; + [[nodiscard]] float Dot3D (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 Cross3D (const SHVec4& rhs) const noexcept; + [[nodiscard]] SHVec4 Cross (const SHVec4& v1, const SHVec4& v2) const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Static Function Members */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] static SHVec4 Normalise (const SHVec4& v) noexcept; + [[nodiscard]] static SHVec4 Normalise3D (const SHVec4& v) noexcept; + [[nodiscard]] static SHVec4 Abs (const SHVec4& v) noexcept; + [[nodiscard]] static SHVec4 Min (const std::initializer_list& vs) noexcept; + [[nodiscard]] static SHVec4 Max (const std::initializer_list& vs) noexcept; + [[nodiscard]] static SHVec4 Clamp (const SHVec4& v, const SHVec4& vMin, const SHVec4& vMax) noexcept; + [[nodiscard]] static SHVec4 Lerp (const SHVec4& a, const SHVec4& b, float t) noexcept; + [[nodiscard]] static SHVec4 ClampedLerp (const SHVec4& a, const SHVec4& b, float t, float tMin = 0.0f, float tMax = 1.0f) noexcept; + + [[nodiscard]] static float Distance (const SHVec4& lhs, const SHVec4& rhs) noexcept; + [[nodiscard]] static float Distance3D (const SHVec4& lhs, const SHVec4& rhs) noexcept; + [[nodiscard]] static float DistanceSquared (const SHVec4& lhs, const SHVec4& rhs) noexcept; + [[nodiscard]] static float DistanceSquared3D (const SHVec4& lhs, const SHVec4& rhs) noexcept; + [[nodiscard]] static float Angle (const SHVec4& lhs, const SHVec4& rhs) noexcept; + [[nodiscard]] static float Angle3D (const SHVec4& lhs, const SHVec4& rhs) noexcept; + [[nodiscard]] static float Dot (const SHVec4& lhs, const SHVec4& rhs) noexcept; + [[nodiscard]] static float Dot3D (const SHVec4& lhs, const SHVec4& rhs) noexcept; + [[nodiscard]] static SHVec4 Cross3D (const SHVec4& lhs, const SHVec4& rhs) noexcept; + [[nodiscard]] static SHVec4 Cross (const SHVec4& v1, const SHVec4& v2, const SHVec4& v3) noexcept; + [[nodiscard]] static SHVec4 Project (const SHVec4& v, const SHVec4& u) noexcept; + [[nodiscard]] static SHVec4 Project3D (const SHVec4& v, const SHVec4& u) noexcept; + [[nodiscard]] static SHVec4 Reflect (const SHVec4& v, const SHVec4& normal) noexcept; + [[nodiscard]] static SHVec4 Reflect3D (const SHVec4& v, const SHVec4& normal) noexcept; + [[nodiscard]] static SHVec4 Transform3D (const SHVec4& v, const SHMatrix& transformMtx) noexcept; + + }; + + SHVec4 operator* (float lhs, const SHVec4& rhs) noexcept; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Meta/SHIsDetected.h b/SHADE_Engine/src/Meta/SHIsDetected.h new file mode 100644 index 00000000..2e945786 --- /dev/null +++ b/SHADE_Engine/src/Meta/SHIsDetected.h @@ -0,0 +1,25 @@ +#ifndef SH_HAS_MEMBER_H +#define SH_HAS_MEMBER_H + +#include + +namespace SHADE +{ + template