diff --git a/Application/src/KarmaApp.cpp b/Application/src/KarmaApp.cpp index 4cdaf301..76bec48b 100644 --- a/Application/src/KarmaApp.cpp +++ b/Application/src/KarmaApp.cpp @@ -17,7 +17,7 @@ class ExampleLayer : public Karma::Layer std::shared_ptr shaderUniform; shaderUniform.reset(Karma::UniformBufferObject::Create({ Karma::ShaderDataType::Mat4, Karma::ShaderDataType::Mat4 }, 0)); - m_BlueSQShader.reset(Karma::Shader::Create("../Resources/Shaders/shader.vert", "../Resources/Shaders/shader.frag", shaderUniform, "CylinderShader")); + m_BlueSQShader.reset(Karma::Shader::Create("../Resources/Shaders/shader.vert", "../Resources/Shaders/shader.frag", "CylinderShader")); m_SquareMat.reset(new Karma::Material()); m_SquareMat->AddShader(m_BlueSQShader); @@ -31,7 +31,7 @@ class ExampleLayer : public Karma::Layer m_Scene.reset(new Karma::Scene()); m_Scene->AddCamera(m_Camera); - m_Scene->AddVertexArray(m_SquareVA); + //m_Scene->AddVertexArray(m_SquareVA); m_Scene->SetClearColor({ 0.0f, 0.0f, 0.0f, 1 }); diff --git a/CMakeLists.txt b/CMakeLists.txt index b2601f88..d16db6af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,10 +34,16 @@ set(MAC_PACKAGE FALSE) # This is so that .app can be installed in KarmaBin if (APPLE) if(MAC_PACKAGE) + # We will be needing the install scheme so generate all the schemes set(CMAKE_XCODE_GENERATE_SCHEME ON CACHE BOOL "") set(CMAKE_INSTALL_PREFIX "${REPOSITORYROOT}/KarmaBin" CACHE PATH "Custom install location for .app bundle" FORCE) else() + # No need for generating all the schemes. Selected schemes can be + # be generated for appropriate targets. See Pranjal + # set_target_properties(Pranjal PROPERTIES + # XCODE_GENERATE_SCHEME TRUE + # XCODE_SCHEME_WORKING_DIRECTORY "${REPOSITORYROOT}/KarmaBin") set(CMAKE_XCODE_GENERATE_SCHEME OFF CACHE BOOL "") endif() endif() @@ -81,13 +87,13 @@ add_subdirectory(Karma) list(APPEND ESSENTIAL_LIBS Karma) # Build the Client -add_subdirectory(Application) +# add_subdirectory(Application) # Build the Editor add_subdirectory(Pranjal) # Relevant linking of finished Application with the Engine! -target_link_libraries(SandBox PUBLIC KarmaEngine) +# target_link_libraries(SandBox PUBLIC KarmaEngine) target_link_libraries(Pranjal PUBLIC KarmaEngine) if (APPLE AND MAC_PACKAGE) diff --git a/GenerateProjects.bat b/GenerateProjects.bat index c69c2f43..eaea3e58 100644 --- a/GenerateProjects.bat +++ b/GenerateProjects.bat @@ -8,7 +8,7 @@ set /p Input=Enter y(Yes) or n(No): If /I "%Input%"=="y" goto yes goto no :yes -KarmaBasedProjectName.sln +start "" Game_Of_Lands.slnx exit :no echo Suit yourself! Goodbye! diff --git a/Karma/src/Karma.h b/Karma/src/Karma.h index b94f41f8..8c204cae 100644 --- a/Karma/src/Karma.h +++ b/Karma/src/Karma.h @@ -3,10 +3,10 @@ // To be included in clients // Game code specific includes -#include "Karma/GameFramework/Actor.h" -#include "Karma/GameFramework/World.h" +#include "GameFramework/Actor.h" +#include "GameFramework/World.h" -#include "Karma/GameFramework/Level.h" +#include "Engine/Level.h" #include "Karma/Application.h" #include "Karma/Layer.h" @@ -14,6 +14,7 @@ #include "Karma/KarmaGui/KarmaGuiLayer.h" #include "Karma/KarmaGui/KarmaGui.h" #include "Karma/KarmaGui/KarmaGuiInternal.h" +#include "Karma/KarmaGui/KarmaGuizmo.h" #include "Karma/KarmaGui/KarmaGuiRenderer.h" #include "Karma/Renderer/Shader.h" diff --git a/Karma/src/Karma/Animation/Animation.h b/Karma/src/Karma/Animation/Animation.h index 8d64c552..f6c3fbad 100644 --- a/Karma/src/Karma/Animation/Animation.h +++ b/Karma/src/Karma/Animation/Animation.h @@ -1,7 +1,5 @@ #pragma once -#include "krpch.h" - namespace Karma { class KARMA_API Animation @@ -12,4 +10,4 @@ namespace Karma }; -} \ No newline at end of file +} diff --git a/Karma/src/Karma/Application.cpp b/Karma/src/Karma/Application.cpp index 923fe423..19408b55 100644 --- a/Karma/src/Karma/Application.cpp +++ b/Karma/src/Karma/Application.cpp @@ -1,11 +1,11 @@ #include "Application.h" -#include "Karma/Log.h" #include "Karma/Input.h" #include "Karma/Renderer/Renderer.h" #include "chrono" #include "Engine/Engine.h" #include "Core/UObjectGlobals.h"// to be bundled appropriately in core.h #include "Core/Package.h" +#include "KarmaRHI/KarmaRHI.h" namespace Karma { @@ -31,6 +31,9 @@ namespace Karma m_Window = Window::Create(); m_Window->SetEventCallback(KR_BIND_EVENT_FN(Application::OnEvent)); // Setting the listener + // Initialize RHI (Render Hardware Interface) + RHIInit(); + m_LayerStack = new LayerStack(); // Graphics API Vulkan or OpenGL should have been completely initialized by here @@ -48,6 +51,10 @@ namespace Karma // and its context. KR_CORE_INFO("Deleting stacks"); delete m_LayerStack; + + // Deinitialize RHI + RHIExit(); + KR_CORE_INFO("Deleting window"); delete m_Window; s_Instance = nullptr; @@ -102,7 +109,14 @@ namespace Karma deltaTime /= 1000000.0f; // Tick KEngine - GEngine->Tick(deltaTime, false); + if(GEngine) + { + GEngine->Tick(deltaTime, false); + } + + // Ponder over the sequence of this updation + // this includes glfwPollEvents() + m_Window->OnUpdate(); for (auto layer : *m_LayerStack) { @@ -118,8 +132,6 @@ namespace Karma } m_KarmaGuiLayer->End(); - - m_Window->OnUpdate(); } } diff --git a/Karma/src/Karma/Application.h b/Karma/src/Karma/Application.h index 30c84e7a..1c7c2005 100644 --- a/Karma/src/Karma/Application.h +++ b/Karma/src/Karma/Application.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Window.h" #include "Karma/Events/ApplicationEvent.h" #include "Karma/Events/ControllerDeviceEvent.h" diff --git a/Karma/src/Karma/Core.h b/Karma/src/Karma/Core.h index 801f0153..df9ce42a 100644 --- a/Karma/src/Karma/Core.h +++ b/Karma/src/Karma/Core.h @@ -109,6 +109,14 @@ #define KR_CORE_ASSERT(expr, ...) #endif + /** + * @brief Denotes code paths that should never be reached. + * + * @note Taken from the UE + */ +#define checkNoEntry() KR_CORE_ASSERT(false, "Enclosing block should never be called") + + /** * @brief Macro for std::bind routine. See https://en.cppreference.com/w/cpp/utility/functional/bind * diff --git a/Karma/src/Karma/Core/Field.h b/Karma/src/Karma/Core/Field.h index cd2031ab..4b430be5 100644 --- a/Karma/src/Karma/Core/Field.h +++ b/Karma/src/Karma/Core/Field.h @@ -10,7 +10,6 @@ #pragma once -#include "krpch.h" #include "UObjectGlobals.h" namespace Karma diff --git a/Karma/src/Karma/Core/GFrameworkMacros.h b/Karma/src/Karma/Core/GFrameworkMacros.h index a65ce7b7..d5020b9e 100644 --- a/Karma/src/Karma/Core/GFrameworkMacros.h +++ b/Karma/src/Karma/Core/GFrameworkMacros.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - enum EInternal {EC_InternalUseOnlyConstructor}; typedef void (*ClassConstructorType) (const Karma::FObjectInitializer&); @@ -37,12 +35,19 @@ typedef void (*ClassConstructorType) (const Karma::FObjectInitializer&); * calls default constructor with placement new during UObjectAllocation * (*InClass->m_ClassConstructor)(FObjectInitializer) * + * Also defines the routine to destroy (call class destructor) accessible from + * UObjectBase. See KarmaSmriti::ShutDown() for the application. + * * @see StaticConstructObject_Internal() in UObjectGlobals.cpp * @remark In UE, this is done in ObjectMacros.h, #define DECLARE_CLASS * */ #define DECLARE_KARMA_CLASS(TClass, TSuperClass) \ public: \ + inline virtual void ShivaUObject() override\ + {\ + this->~TClass();\ + } \ DEFINE_DEFAULT_CONSTRUCTOR_CALL(TClass) \ /** Typedef for the base class ({{ typedef-type }}) */ \ typedef TSuperClass Super;\ diff --git a/Karma/src/Karma/Core/KarmaTypes.h b/Karma/src/Karma/Core/KarmaTypes.h index 8d36e993..9bd508fb 100644 --- a/Karma/src/Karma/Core/KarmaTypes.h +++ b/Karma/src/Karma/Core/KarmaTypes.h @@ -1,7 +1,7 @@ /** * @file KarmaTypes.h * @author Ravi Mohan (the_cowboy) - * @brief This file contains the types used in game logic + * @brief This file contains the custom types used in Engine's logic * @version 1.0 * @date March 30, 2023 * @@ -15,6 +15,16 @@ #include #include #include +#include + +enum class EAllowShrinking +{ + /** Allow the array to shrink its allocation if possible */ + Yes, + + /** Prevent the array from shrinking its allocation */ + No +}; /** @brief Specifies why an actor is being deleted/removed from a level */ namespace EEndPlayReason @@ -36,7 +46,8 @@ namespace EEndPlayReason /** * @brief euphemism for no index situations - * @todo usage hasn't caught on in usual engine routines + * + * @see KarmaVector::Find, KarmaVector::RemoveSingleSwap etc */ enum { INDEX_NONE = -1 }; @@ -90,6 +101,29 @@ class KarmaMap { } + /** + * @brief Addes a key-value pair to the map + * + * @since Karma 1.0.0 + */ + void Add(const KeyType& Key, const ValueType& Value) + { + m_KeyValuePair.insert(typename AMap::value_type(Key, Value)); + } + + /** + * @brief Checks if the map contains a specified key + * + * @param Key The key to search for. + * @return true if the map contains the specified key, false otherwise. + * + * @since Karma 1.0.0 + */ + bool Contains(const KeyType& Key) const + { + return m_KeyValuePair.find(Key) != m_KeyValuePair.end(); + } + /** * @brief Find the value associated with a specified key, or if none exists, * adds a value using the default constructor. @@ -140,12 +174,69 @@ class KarmaMap return nullptr; } + + /** + * @brief Overloaded subscript operator for accessing values by key + * + * @param Key The key whose associated value is to be accessed. + * @return A reference to the value associated with the specified key. + * + * @note If the key does not exist in the map, a new entry is created with a default-constructed value. A mutable reference + * is returned. + * @since Karma 1.0.0 + */ + ValueType& operator[](const KeyType& Key) + { + return m_KeyValuePair[Key]; + } + + /** + * @brief Overloaded subscript operator for accessing values by key (const version) + * + * @param Key The key whose associated value is to be accessed. + * @return A const reference to the value associated with the specified key. + * + * @note Returns immutable reference + * @since Karma 1.0.0 + */ + const ValueType& operator[](const KeyType& Key) const + { + return m_KeyValuePair.at(Key); + } + + uint32_t Num() const + { + return (uint32_t) m_KeyValuePair.size(); + } + + auto begin() + { + return m_KeyValuePair.begin(); + } + + auto end() + { + return m_KeyValuePair.end(); + } + + auto begin() const + { + return m_KeyValuePair.cbegin(); + } + + auto end() const + { + return m_KeyValuePair.cend(); + } + protected: AMap m_KeyValuePair; }; /** - * @brief Karma's std::vector wrapper + * @brief Karma's std::vector wrapper with additional functionalities + * + * @since Karma 1.0.0 */ template class KarmaVector @@ -172,6 +263,10 @@ class KarmaVector /** * @brief Removes an element from the vector * + * The function removes all occurrences of the specified element from the vector + * + * @note The vector size is reduced by the number of removed elements + * * @param aBlock The element to be removed * @since Karma 1.0.0 */ @@ -208,11 +303,21 @@ class KarmaVector } /** - * Adds unique element to array if it doesn't exist. + * @brief Sanity check for the index + * + * @since Karma 1.0.0 + */ + void RangeCheck(uint32_t Index) const + { + KR_CORE_ASSERT((Index >= 0 && Index < Num()), "KarmaVector: Range check failed"); + } + + /** + * Adds unique element to array if the element doesn't exist. * * Move semantics version. * - * @param Item Item to add. + * @param Item Item to add * @returns Index of the element in the array. * * @see Add, AddDefaulted, AddZeroed, Append, Insert @@ -234,7 +339,7 @@ class KarmaVector } /** - * Finds element within the array. + * Finds first occurance of an element within the array. * * @param Item Item to look for. * @returns Index of the found element. INDEX_NONE otherwise. @@ -294,6 +399,53 @@ class KarmaVector return false; } + /** + * @brief Removes an element at given location, swapping the last element into its place, and + * shrinking the array. + * + * @param Index The index of the element to be removed + * @param AllowShrinking Whether to allow the array to shrink its allocation if possible + * + * + * @note This implementation is differenct from UE and here vector shrinks automatically + * @since Karma 1.0.0 + */ + void RemoveAtSwap(int32_t Index/*, EAllowShrinking AllowShrinking = EAllowShrinking::Yes*/) + { + RangeCheck(Index); + RemoveAtSwapImpl(Index); + + //if (AllowShrinking == EAllowShrinking::Yes) + //{ + // not functional yet + //} + } + + /** + * @brief Removes a first appearence of single element from the array, swapping the last element + * into its place, and shrinking the array. + * + * @param Item The item to be removed + * @param AllowShrinking Whether to allow the array to shrink its allocation if possible + * @returns The number of elements removed (0 or 1) + * + * @note AllowShrinking is automatic different from UE + * @since Karma 1.0.0 + */ + uint32_t RemoveSingleSwap(const BuildingBlock& Item/*, EAllowShrinking AllowShrinking = EAllowShrinking::Yes*/) + { + int32_t Index = Find(Item); + + if (Index == INDEX_NONE) + { + return 0; + } + + RemoveAtSwap(Index/*, AllowShrinking*/); + + return 1; + } + /** * @brief Returns the total number of elements in a vector * @@ -337,6 +489,11 @@ class KarmaVector m_Elements.clear(); } + void Resize(uint32_t NewSize) + { + m_Elements.resize(NewSize); + } + /** * @brief Just clear the elements * @@ -363,6 +520,14 @@ class KarmaVector m_Elements[Index] = Value; } + /** + * @brief Clears the vector from all the elements + * + * @note Duplicate of SmartReset + * @since Karma 1.0.0 + */ + inline void Clear() { m_Elements.clear(); } + /** * @brief Getter for the elements of vector * @@ -380,7 +545,7 @@ class KarmaVector /** * @brief Getter for first vector element - * + * * @since Karma 1.0.0 */ typename std::vector::iterator begin() @@ -398,6 +563,16 @@ class KarmaVector return m_Elements.end(); } + typename std::vector::const_iterator begin() const + { + return m_Elements.cbegin(); + } + + typename std::vector::const_iterator end() const + { + return m_Elements.cend(); + } + /** * Returns the UObject corresponding to index. Be advised this is only for very low level use. * @@ -410,7 +585,7 @@ class KarmaVector { KR_CORE_ASSERT(Index >= 0, ""); - if(Index < m_Elements.size()) + if(Index < Num()) { return m_Elements.at(Index); } @@ -431,9 +606,54 @@ class KarmaVector */ FORCEINLINE bool IsValidIndex(int32_t Index) const { - return Index >= 0 && Index < m_Elements.size(); + return Index >= 0 && Index < Num(); } + /** + * @brief Helper function to return typed pointer to the first entry of array + * + * @since Karma 1.0.0 + */ + FORCEINLINE BuildingBlock const* GetData() const { return m_Elements.data(); } + + BuildingBlock& operator[](int32_t Index) + { + RangeCheck(Index); + return m_Elements[Index]; + } + + const BuildingBlock& operator[](int32_t Index) const + { + RangeCheck(Index); + return m_Elements[Index]; + } + +private: + /** + * @brief Internal implementation of RemoveAtSwap + * + * @param Index The index of the element to be removed + * @since Karma 1.0.0 + */ + void RemoveAtSwapImpl(int32_t Index) + { + const BuildingBlock* Data = GetData(); + const BuildingBlock* Dest = Data + Index; + + // Number of elements (after the generated vacancy) to move + const int32_t NumElementsAfterHole = Num() - Index - 1; + const int32_t NumElementsToMoveIntoHole = glm::min(1, NumElementsAfterHole); + + if (NumElementsToMoveIntoHole) + { + //std::memmove((void *) Dest, (const void*)(Data + Num() - NumElementsToMoveIntoHole), + // sizeof(BuildingBlock) * NumElementsToMoveIntoHole); + const int32_t LastElementIndex = Num() - 1; + + m_Elements[Index] = m_Elements[LastElementIndex]; + } + m_Elements.pop_back(); + } protected: std::vector m_Elements; @@ -457,6 +677,19 @@ enum ETravelType TRAVEL_MAX, }; +/** Whether to teleport physics body or not */ +enum class ETeleportType : uint8_t +{ + /** Do not teleport physics body. This means velocity will reflect the movement between initial and final position, and collisions along the way will occur */ + None, + + /** Teleport physics body so that velocity remains the same and no collision occurs */ + TeleportPhysics, + + /** Teleport physics body and reset physics state completely */ + ResetPhysics, +}; + /** * @brief Helper for obtaining the default Url configuration */ diff --git a/Karma/src/Karma/Core/Package.h b/Karma/src/Karma/Core/Package.h index b938921a..5a143514 100644 --- a/Karma/src/Karma/Core/Package.h +++ b/Karma/src/Karma/Core/Package.h @@ -10,7 +10,6 @@ #pragma once -#include "krpch.h" #include "Object.h" namespace Karma diff --git a/Karma/src/Karma/Core/SubClassOf.h b/Karma/src/Karma/Core/SubClassOf.h index a602dbbe..9356a3e4 100644 --- a/Karma/src/Karma/Core/SubClassOf.h +++ b/Karma/src/Karma/Core/SubClassOf.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - #include "Class.h" #include "Field.h" diff --git a/Karma/src/Karma/Core/TrueCore/GenericPlatformMemory.h b/Karma/src/Karma/Core/TrueCore/GenericPlatformMemory.h index 337b90a2..52671ec6 100644 --- a/Karma/src/Karma/Core/TrueCore/GenericPlatformMemory.h +++ b/Karma/src/Karma/Core/TrueCore/GenericPlatformMemory.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - namespace Karma { typedef size_t SIZE_T; diff --git a/Karma/src/Karma/Core/TrueCore/KarmaMemory.h b/Karma/src/Karma/Core/TrueCore/KarmaMemory.h index c9e9f8b6..44c84e7b 100644 --- a/Karma/src/Karma/Core/TrueCore/KarmaMemory.h +++ b/Karma/src/Karma/Core/TrueCore/KarmaMemory.h @@ -12,8 +12,6 @@ #pragma once -#include "krpch.h" - #ifdef KR_WINDOWS_PLATFORM #include "Platform/Windows/Core/WindowsPlatformMemory.h" #elif KR_MAC_PLATFORM diff --git a/Karma/src/Karma/Core/TrueCore/KarmaSmriti.cpp b/Karma/src/Karma/Core/TrueCore/KarmaSmriti.cpp index 7647ccc5..4f5e7fef 100644 --- a/Karma/src/Karma/Core/TrueCore/KarmaSmriti.cpp +++ b/Karma/src/Karma/Core/TrueCore/KarmaSmriti.cpp @@ -1,7 +1,8 @@ #include "KarmaSmriti.h" #include "KarmaMemory.h" - +#include "UObjectBase.h" #include "Core/UObjectAllocator.h" +#include namespace Karma { @@ -30,6 +31,18 @@ namespace Karma void KarmaSmriti::ShutDown() { + for (FRawObjectIterator ObjectItr; ObjectItr; ++ObjectItr) + { + if ((*ObjectItr)->m_Object) + { + (*ObjectItr)->m_Object->ShivaUObject(); + } + else + { + KR_INFO("Can't delete {0}", (*ObjectItr)->m_Object->GetName()); + } + } + FMemory::SystemFree(m_pMemBlock); KR_CORE_INFO("Freed Karma's memory softbed"); } diff --git a/Karma/src/Karma/Core/TrueCore/KarmaSmriti.h b/Karma/src/Karma/Core/TrueCore/KarmaSmriti.h index 9e971c92..fc9de626 100644 --- a/Karma/src/Karma/Core/TrueCore/KarmaSmriti.h +++ b/Karma/src/Karma/Core/TrueCore/KarmaSmriti.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - #include "Core/UObjectAllocator.h" namespace Karma diff --git a/Karma/src/Karma/Core/UObjectAllocator.cpp b/Karma/src/Karma/Core/UObjectAllocator.cpp index fa8ddf79..1523a530 100644 --- a/Karma/src/Karma/Core/UObjectAllocator.cpp +++ b/Karma/src/Karma/Core/UObjectAllocator.cpp @@ -2,12 +2,17 @@ #include "Karma/Core/TrueCore/KarmaMemory.h" #include "Karma/Ganit/KarmaMath.h" #include "Core/UObjectBase.h" +#include namespace Karma { /** Global UObjectBase allocator */ FUObjectAllocator GUObjectAllocator; + FUObjectAllocator::~FUObjectAllocator() + { + } + void FUObjectAllocator::AllocatePermanentObjectPool(int32_t InPermanentObjectPoolSize) { m_PermanentObjectPoolSize = InPermanentObjectPoolSize; diff --git a/Karma/src/Karma/Core/UObjectAllocator.h b/Karma/src/Karma/Core/UObjectAllocator.h index e9ef318b..14e95a1c 100644 --- a/Karma/src/Karma/Core/UObjectAllocator.h +++ b/Karma/src/Karma/Core/UObjectAllocator.h @@ -17,6 +17,8 @@ #pragma once +#include "KarmaTypes.h" + namespace Karma { class UObjectBase; @@ -123,6 +125,8 @@ namespace Karma { } + ~FUObjectAllocator(); + /** * Allocates and initializes the permanent object pool. * diff --git a/Karma/src/Karma/Core/UObjectBase.h b/Karma/src/Karma/Core/UObjectBase.h index 12a6cb94..d2b2227f 100644 --- a/Karma/src/Karma/Core/UObjectBase.h +++ b/Karma/src/Karma/Core/UObjectBase.h @@ -62,7 +62,7 @@ namespace Karma public: /** - * Constructor used by StaticAllocateObject + * @brief Constructor used by StaticAllocateObject * @param InClass non NULL, this gives the class of the new object, if known at this time * @param InFlags RF_Flags to assign * @param InOuter outer for this object (UObject this object resides in, for instance LevelToSpawnIn is outer for AActor) @@ -73,8 +73,18 @@ namespace Karma */ UObjectBase(UClass* inClass, EObjectFlags inFlags, EInternalObjectFlags inInternalFlags, UObject* inOuter, const std::string& inName); + ~UObjectBase() {}; + + /** + * @brief One function to destroy them + * + * @see KarmaSmriti::ShutDown() + * @since Karma 1.0.0 + */ + virtual void ShivaUObject() { this->~UObjectBase(); } + /** - * Walks up the list of outers until it finds a package directly associated with the object. + * @brief Walks up the list of outers until it finds a package directly associated with the object. * * @return the package the object is in * @since Karma 1.0.0 diff --git a/Karma/src/Karma/Core/UObjectGlobals.cpp b/Karma/src/Karma/Core/UObjectGlobals.cpp index 0d7d33bd..3aae4af4 100644 --- a/Karma/src/Karma/Core/UObjectGlobals.cpp +++ b/Karma/src/Karma/Core/UObjectGlobals.cpp @@ -503,6 +503,18 @@ namespace Karma return Object; } + UObject* CreateDefaultSubobject(UObject* Outer, const std::string& SubobjectFName, const UClass* SubobjectClass, bool bIsRequired, bool bIsAbstract) + { + if (SubobjectFName == "") + { + KR_CORE_WARN("CreateDefaultSubobject called with invalid name"); + } + + UObject* Result = nullptr; + + return Result; + } + UPackage* CreatePackage(const std::string& PackageName) { std::string InName; @@ -596,6 +608,10 @@ namespace Karma return true; } + FUObjectArray::~FUObjectArray() + { + } + void GetObjectsOfClass(const UClass* ClassToLookFor, KarmaVector& Results, bool bIncludeDerivedClasses, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags) { //SCOPE_CYCLE_COUNTER(STAT_Hash_GetObjectsOfClass); diff --git a/Karma/src/Karma/Core/UObjectGlobals.h b/Karma/src/Karma/Core/UObjectGlobals.h index c7aa3668..9c42b631 100644 --- a/Karma/src/Karma/Core/UObjectGlobals.h +++ b/Karma/src/Karma/Core/UObjectGlobals.h @@ -10,7 +10,7 @@ #pragma once -#include "krpch.h" +#include "KarmaTypes.h" namespace Karma { @@ -540,6 +540,8 @@ namespace Karma * @since Karma 1.0.0 */ bool IsValid(const UObjectBase* Object) const; + + ~FUObjectArray(); }; /** @@ -814,6 +816,20 @@ FUNCTION_NON_NULL_RETURN_START*/ return static_cast(StaticConstructObject_Internal(Params)); }*/ +/** + * @brief Create a component or subobject that will be instanced inside all instances of this class. + * + * @param Outer outer to construct the subobject in + * @param SubobjectName name of the new component + * @param ReturnType class of return type, all overrides must be of this type + * @param ClassToConstructByDefault if the derived class has not overridden, create a component of this type + * @param bIsRequired true if the component is required and will always be created even if DoNotCreateDefaultSubobject was specified. + * @param bIsTransient true if the component is being assigned to a transient property + * + * @note This is work in progress, not fully functional yet + */ +KARMA_API UObject* CreateDefaultSubobject(UObject* Outer, std::string SubobjectFName, const UClass* ReturnType, const UClass* ClassToCreateByDefault, bool bIsRequired = true, bool bIsTransient = false); + /** * A routine to find if the object is instantiated already. May need to modify in accordance with thread safety in future * UE name StaticFindObjectFastInternal diff --git a/Karma/src/Karma/GameFramework/ActorComponent.cpp b/Karma/src/Karma/Engine/Classes/Components/ActorComponent.cpp similarity index 97% rename from Karma/src/Karma/GameFramework/ActorComponent.cpp rename to Karma/src/Karma/Engine/Classes/Components/ActorComponent.cpp index 78db2f30..f9893835 100644 --- a/Karma/src/Karma/GameFramework/ActorComponent.cpp +++ b/Karma/src/Karma/Engine/Classes/Components/ActorComponent.cpp @@ -61,6 +61,11 @@ namespace Karma return m_OwnerPrivate; } + void UActorComponent::SetOwner(AActor* NewOwner) + { + m_OwnerPrivate = NewOwner; + } + void UActorComponent::Activate(bool bReset) { if (bReset || ShouldActivate() == true) diff --git a/Karma/src/Karma/GameFramework/ActorComponent.h b/Karma/src/Karma/Engine/Classes/Components/ActorComponent.h similarity index 88% rename from Karma/src/Karma/GameFramework/ActorComponent.h rename to Karma/src/Karma/Engine/Classes/Components/ActorComponent.h index b195b897..74520879 100644 --- a/Karma/src/Karma/GameFramework/ActorComponent.h +++ b/Karma/src/Karma/Engine/Classes/Components/ActorComponent.h @@ -9,9 +9,8 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Core/Object.h" +#include "Core/KarmaTypes.h" class AActor; @@ -32,6 +31,21 @@ namespace Karma Instance, }; + /** + * @brief Information about how to update transform when something is moved + */ + enum class EUpdateTransformFlags : int32 + { + /** Default options */ + None = 0x0, + /** Don't update the underlying physics */ + SkipPhysicsUpdate = 0x1, + /** The update is coming as a result of the parent updating (i.e. not called directly) */ + PropagateFromParent = 0x2, + /** Only update child transform if attached to parent via a socket */ + OnlyUpdateIfUsingSocket = 0x4 + }; + /** * @brief ActorComponent is the base class for components that define reusable behavior that can be added to different types of Actors. * ActorComponents that have a transform are known as SceneComponents and those that can be rendered are PrimitiveComponents. @@ -116,6 +130,14 @@ namespace Karma */ AActor* GetOwner() const; + /** + * @brief Sets the owner of this component + * + * @param NewOwner - The new owner actor for this component + * @since Karma 1.0.0 + */ + void SetOwner(AActor* NewOwner); + /** * @brief Indicates that OnCreatedComponent has been called, but OnDestroyedComponent has not yet * @@ -137,6 +159,13 @@ namespace Karma */ inline bool IsRegistered() const { return m_bRegistered; } + /** + * @brief Recalculate the value of our component to world transform + * + * @since Karma 1.0.0 + */ + virtual void UpdateComponentToWorld(EUpdateTransformFlags UpdateTransformFlags = EUpdateTransformFlags::None, ETeleportType Teleport = ETeleportType::None) {} + /** * @brief Returns whether the component is active or not * diff --git a/Karma/src/Karma/GameFramework/ChildActorComponent.cpp b/Karma/src/Karma/Engine/Classes/Components/ChildActorComponent.cpp similarity index 100% rename from Karma/src/Karma/GameFramework/ChildActorComponent.cpp rename to Karma/src/Karma/Engine/Classes/Components/ChildActorComponent.cpp diff --git a/Karma/src/Karma/GameFramework/ChildActorComponent.h b/Karma/src/Karma/Engine/Classes/Components/ChildActorComponent.h similarity index 98% rename from Karma/src/Karma/GameFramework/ChildActorComponent.h rename to Karma/src/Karma/Engine/Classes/Components/ChildActorComponent.h index 6c6c7691..ac84785b 100644 --- a/Karma/src/Karma/GameFramework/ChildActorComponent.h +++ b/Karma/src/Karma/Engine/Classes/Components/ChildActorComponent.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "SceneComponent.h" #include "SubClassOf.h" diff --git a/Karma/src/Karma/Engine/Classes/Components/MeshComponent.cpp b/Karma/src/Karma/Engine/Classes/Components/MeshComponent.cpp new file mode 100644 index 00000000..085ca58b --- /dev/null +++ b/Karma/src/Karma/Engine/Classes/Components/MeshComponent.cpp @@ -0,0 +1 @@ +#include "MeshComponent.h" \ No newline at end of file diff --git a/Karma/src/Karma/Engine/Classes/Components/MeshComponent.h b/Karma/src/Karma/Engine/Classes/Components/MeshComponent.h new file mode 100644 index 00000000..04f457e1 --- /dev/null +++ b/Karma/src/Karma/Engine/Classes/Components/MeshComponent.h @@ -0,0 +1,26 @@ +/** + * @file MeshComponent.h + * @brief Declaration of the MeshComponent class, representing a PrimitiveComponent that handles mesh rendering in the game engine. + * @author Ravi Mohan (the_cowboy) + * @date December 9, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once +#include "Components/PrimitiveComponent.h" + +namespace Karma +{ + /** + * @brief A component that represents a mesh in the game engine, responsible for rendering and managing mesh data. + * + * MeshComponent is an abstract base for any component that is an instance of a renderable collection of triangles. + * + * @since Karma 1.0.0 + */ + class KARMA_API UMeshComponent : public UPrimitiveComponent + { + DECLARE_KARMA_CLASS(UMeshComponent, UPrimitiveComponent) + }; +} \ No newline at end of file diff --git a/Karma/src/Karma/Engine/Classes/Components/PrimitiveComponent.cpp b/Karma/src/Karma/Engine/Classes/Components/PrimitiveComponent.cpp new file mode 100644 index 00000000..f624fa53 --- /dev/null +++ b/Karma/src/Karma/Engine/Classes/Components/PrimitiveComponent.cpp @@ -0,0 +1,31 @@ +#include "PrimitiveComponent.h" +#include "Buffer.h" + +namespace Karma +{ + UPrimitiveComponent::UPrimitiveComponent() : USceneComponent() + { + m_ComponentTransformUniform.reset(UniformBufferObject::Create({ ShaderDataType::Mat4 }, 0)); + + m_UniformTranformMatrix = GetComponentTransform().ToMatrixWithScale(); + + UBODataPointer uModelMatrix(&m_UniformTranformMatrix); + m_ComponentTransformUniform->UpdateUniforms(uModelMatrix); + } + + void UPrimitiveComponent::BeginPlay() + { + } + + void UPrimitiveComponent::SetCachedMaxDrawDistance(const float newCachedMaxDrawDistance) + { + } + + void UPrimitiveComponent::SetWorldTransform(const FTransform& NewTransform) + { + Super::SetWorldTransform(NewTransform); + + m_UniformTranformMatrix = GetComponentTransform().ToMatrixWithScale(); + } +} + diff --git a/Karma/src/Karma/GameFramework/PrimitiveComponent.h b/Karma/src/Karma/Engine/Classes/Components/PrimitiveComponent.h similarity index 73% rename from Karma/src/Karma/GameFramework/PrimitiveComponent.h rename to Karma/src/Karma/Engine/Classes/Components/PrimitiveComponent.h index ed94eaa5..19d024a4 100644 --- a/Karma/src/Karma/GameFramework/PrimitiveComponent.h +++ b/Karma/src/Karma/Engine/Classes/Components/PrimitiveComponent.h @@ -9,22 +9,29 @@ */ #pragma once -#include "krpch.h" - #include "SceneComponent.h" namespace Karma { + class UniformBufferObject; + /** * @brief PrimitiveComponents are SceneComponents that contain or generate some sort of geometry, generally to be rendered or used as collision data. + * * There are several subclasses for the various types of geometry, but the most common by far are the ShapeComponents (Capsule, Sphere, Box), StaticMeshComponent, and SkeletalMeshComponent. * ShapeComponents generate geometry that is used for collision detection but are not rendered, while StaticMeshComponents and SkeletalMeshComponents contain pre-built geometry that is rendered, but can also be used for collision detection. + * + * @see UStaticMeshComponent + * @since Karma 1.0.0 */ class KARMA_API UPrimitiveComponent : public USceneComponent { DECLARE_KARMA_CLASS(UPrimitiveComponent, USceneComponent) public: + + UPrimitiveComponent(); + /** Controls whether the primitive component should cast a shadow or not. */ uint8_t CastShadow : 1; @@ -47,6 +54,15 @@ namespace Karma mutable float LastRenderTimeOnScreen; //friend class FPrimitiveSceneInfo; + + /** + * @brief Uniform buffer object for the mesh's transformation matrix + * + * Used to upload the actor's transform to the GPU for rendering + */ + std::shared_ptr m_ComponentTransformUniform; + + glm::mat4 m_UniformTranformMatrix; public: /** @@ -56,5 +72,16 @@ namespace Karma * @since Karma 1.0.0 */ void SetCachedMaxDrawDistance(const float newCachedMaxDrawDistance); + + /** + * @brief Getter for the transform uniform buffer object + * + * @return std::shared_ptr The uniform buffer object for the mesh's transformation matrix. + * + * @since Karma 1.0.0 + */ + std::shared_ptr GetComponentTransformUniform() const { return m_ComponentTransformUniform; } + + virtual void SetWorldTransform(const FTransform& NewTransform) override; }; } diff --git a/Karma/src/Karma/GameFramework/SceneComponent.cpp b/Karma/src/Karma/Engine/Classes/Components/SceneComponent.cpp similarity index 80% rename from Karma/src/Karma/GameFramework/SceneComponent.cpp rename to Karma/src/Karma/Engine/Classes/Components/SceneComponent.cpp index 826ebe06..271d78d2 100644 --- a/Karma/src/Karma/GameFramework/SceneComponent.cpp +++ b/Karma/src/Karma/Engine/Classes/Components/SceneComponent.cpp @@ -8,6 +8,8 @@ namespace Karma USceneComponent::USceneComponent() : UActorComponent() { m_AttachParent = nullptr; + + m_ComponentToWorld = FTransform::Identity(); } void USceneComponent::SetWorldLocation(glm::vec3 newLocation) @@ -62,6 +64,15 @@ namespace Karma void USceneComponent::SetWorldTransform(const FTransform& NewTransform) { + // I couldn't find this in UE code, however perplexity mentions this + // Ok in the UE, they first do the computation to get relative transform + + // SetWorldTransform -> SetRelativeTransform -> SetRelativeLocationAndRotation (and Scale) -> MoveComponent + // MoveComponentImpl -> ConditionalUpdateComponentToWorld ->UpdateComponentToWorld + // -> UpdateComponentToWorldWithParent -> ComponentToWorld = NewTransform; + // So we directly set the ComponentToWorld here first since we don't have attach to parent logic yet + m_ComponentToWorld = NewTransform; + // If attached to something, transform into local space if (GetAttachParent() != nullptr) { @@ -91,4 +102,4 @@ namespace Karma SetRelativeTransform(NewTransform/*, bSweep, OutSweepHitResult, Teleport*/); } } -} \ No newline at end of file +} diff --git a/Karma/src/Karma/GameFramework/SceneComponent.h b/Karma/src/Karma/Engine/Classes/Components/SceneComponent.h similarity index 93% rename from Karma/src/Karma/GameFramework/SceneComponent.h rename to Karma/src/Karma/Engine/Classes/Components/SceneComponent.h index df623ceb..04757e39 100644 --- a/Karma/src/Karma/GameFramework/SceneComponent.h +++ b/Karma/src/Karma/Engine/Classes/Components/SceneComponent.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "ActorComponent.h" #include "glm/glm.hpp" @@ -177,7 +175,7 @@ namespace Karma * If CCD is on and not teleporting, this will affect objects along the entire sweep volume. * @since Karma 1.0.0 */ - void SetWorldTransform(const FTransform& NewTransform/*, bool bSweep = false, FHitResult* OutSweepHitResult = nullptr, ETeleportType Teleport = ETeleportType::None*/); + virtual void SetWorldTransform(const FTransform& NewTransform/*, bool bSweep = false, FHitResult* OutSweepHitResult = nullptr, ETeleportType Teleport = ETeleportType::None*/); /** * @brief Get the SceneComponent we are attached to. @@ -268,6 +266,17 @@ namespace Karma */ void SetRelativeTransform(const FTransform& NewTransform/*, bool bSweep = false, FHitResult* OutSweepHitResult = nullptr, ETeleportType Teleport = ETeleportType::None*/); + /** + * @brief Recalculate the value of our component to world transform + * + * @since Karma 1.0.0 + */ + virtual void UpdateComponentToWorld(EUpdateTransformFlags UpdateTransformFlags = EUpdateTransformFlags::None, ETeleportType Teleport = ETeleportType::None) override final + { + //UpdateComponentToWorldWithParent(GetAttachParent(), GetAttachSocketName(), UpdateTransformFlags, RelativeRotationCache.RotatorToQuat(GetRelativeRotation()), Teleport); + + } + /** * @brief Getter for the m_AttachSocektName * diff --git a/Karma/src/Karma/Engine/Classes/Components/StaticMeshComponent.cpp b/Karma/src/Karma/Engine/Classes/Components/StaticMeshComponent.cpp new file mode 100644 index 00000000..625e2d94 --- /dev/null +++ b/Karma/src/Karma/Engine/Classes/Components/StaticMeshComponent.cpp @@ -0,0 +1,12 @@ +#include "StaticMeshComponent.h" + +namespace Karma +{ + UStaticMeshComponent::UStaticMeshComponent(const std::string& name) + { + // Constructor implementation (if any) + } + UStaticMeshComponent::~UStaticMeshComponent() + { + } +} // namespace Karma \ No newline at end of file diff --git a/Karma/src/Karma/Engine/Classes/Components/StaticMeshComponent.h b/Karma/src/Karma/Engine/Classes/Components/StaticMeshComponent.h new file mode 100644 index 00000000..4b92abc4 --- /dev/null +++ b/Karma/src/Karma/Engine/Classes/Components/StaticMeshComponent.h @@ -0,0 +1,68 @@ +/** + * @file StaticMeshComponent.h + * @brief Declaration of the StaticMeshComponent class, representing a component that handles static mesh rendering in the game engine. + * @author Ravi Mohan (the_cowboy) + * @date December 9, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include "Components/MeshComponent.h" +#include "Renderer/Mesh.h" + +namespace Karma +{ + /** + * @brief A component that represents a static mesh in the game engine, responsible for rendering and managing static mesh data. + * + * StaticMeshComponent is used to render a static mesh asset in the world. It is a subclass of MeshComponent and provides functionality specific to static meshes. + * + * @since Karma 1.0.0 + */ + class KARMA_API UStaticMeshComponent : public UMeshComponent + { + DECLARE_KARMA_CLASS(UStaticMeshComponent, UMeshComponent) + + private: + + /** + * @brief The static mesh asset associated with this component for rendering + * + * @note This is analogous to Unreal Engine's UStaticMeshComponent::StaticMesh + */ + std::shared_ptr m_StaticMesh; + + public: + /** + * @brief Constructor for StaticMeshComponent + * + * @param name The name of the static mesh component. + */ + UStaticMeshComponent(const std::string& name = "StaticMeshComponent"); + + /** + * @brief Destructor for StaticMeshComponent + */ + virtual ~UStaticMeshComponent(); + + // Additional methods and members specific to StaticMeshComponent can be added here. + + /** + * @brief Sets the static mesh associated with this component. + * + * @param staticMesh The static mesh to associate with this component. + * @since Karma 1.0.0 + */ + void SetStaticMesh(std::shared_ptr staticMesh) { m_StaticMesh = staticMesh; } + + /** + * @brief Gets the static mesh associated with this component. + * + * @return std::shared_ptr The static mesh associated with this component. + * @since Karma 1.0.0 + */ + std::shared_ptr GetStaticMesh() const { return m_StaticMesh; } + }; +} diff --git a/Karma/src/Karma/GameFramework/Level.cpp b/Karma/src/Karma/Engine/Classes/Engine/Level.cpp similarity index 100% rename from Karma/src/Karma/GameFramework/Level.cpp rename to Karma/src/Karma/Engine/Classes/Engine/Level.cpp diff --git a/Karma/src/Karma/GameFramework/Level.h b/Karma/src/Karma/Engine/Classes/Engine/Level.h similarity index 99% rename from Karma/src/Karma/GameFramework/Level.h rename to Karma/src/Karma/Engine/Classes/Engine/Level.h index 07d95a28..4f0201fd 100644 --- a/Karma/src/Karma/GameFramework/Level.h +++ b/Karma/src/Karma/Engine/Classes/Engine/Level.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Core/Object.h" namespace Karma diff --git a/Karma/src/Karma/Engine/Classes/Engine/StaticMeshActor.cpp b/Karma/src/Karma/Engine/Classes/Engine/StaticMeshActor.cpp new file mode 100644 index 00000000..b645c3dd --- /dev/null +++ b/Karma/src/Karma/Engine/Classes/Engine/StaticMeshActor.cpp @@ -0,0 +1,46 @@ +#include "StaticMeshActor.h" +#include "Mesh.h" + +#include + +namespace Karma +{ + AStaticMeshActor::AStaticMeshActor(const std::string& name) + : AActor() + { + if (!m_StaticMeshComponent) + { + // Unreal uses CreateDefaultSubobject for creating components in Actors + // may need to implement similar functionality later + m_StaticMeshComponent = NewObject(this, UStaticMeshComponent::StaticClass(), "StaticMeshComponent"); + m_StaticMeshComponent->SetOwner(this); + } + + m_RootComponent = m_StaticMeshComponent; + } + + void AStaticMeshActor::Tick(float DeltaSeconds) + { + FTransform AnimTransform = GetTransform(); + + AnimTransform.SetRotation(TRotator(glm::vec3(0.f, 0.f, m_RotationAngle))); + + //SetActorTransform(AnimTransform); + + m_RotationAngle += 0.78f * DeltaSeconds * m_RotationSpeed; + + if(m_RotationAngle > 360.f) + { + m_RotationAngle -= 360.f; + } + } + + void AStaticMeshActor::LoadMeshFromFile(const std::string& filePath) + { + std::shared_ptr loadedMesh; + loadedMesh.reset(new Mesh(filePath)); + + m_StaticMeshComponent->SetStaticMesh(loadedMesh); + m_RotationSpeed = glm::linearRand(0.2f, 1.0f); + } +} diff --git a/Karma/src/Karma/Engine/Classes/Engine/StaticMeshActor.h b/Karma/src/Karma/Engine/Classes/Engine/StaticMeshActor.h new file mode 100644 index 00000000..2c23a7f9 --- /dev/null +++ b/Karma/src/Karma/Engine/Classes/Engine/StaticMeshActor.h @@ -0,0 +1,69 @@ +/** + * @file StaticMeshActor.h + * @brief Declaration of the StaticMeshActor class, representing an actor with a static mesh component in the game world. + * @author Ravi Mohan (the_cowboy) + * @date December 7, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include "GameFramework/Actor.h" +#include "Components/StaticMeshComponent.h" + +namespace Karma +{ + class UniformBufferObject; + + /** + * @brief An actor that contains a static mesh component, allowing the mesh to be rendered in the game world. + * + * @since Karma 1.0.0 + */ + class KARMA_API AStaticMeshActor : public AActor + { + DECLARE_KARMA_CLASS(AStaticMeshActor, AActor) + + private: + /** + * @brief The static mesh component associated with this actor for rendering + * + * @note This is analogous to Unreal Engine's AStaticMeshActor::StaticMeshComponent + */ + //std::shared_ptr m_StaticMeshComponent; + UStaticMeshComponent* m_StaticMeshComponent; + + // Experimental + float m_RotationAngle; + float m_RotationSpeed; + + public: + /** + * @brief Constructor for StaticMeshActor + * + * @param name The name of the static mesh actor. + */ + AStaticMeshActor(const std::string& name = "StaticMeshActor"); + + /** + * @brief Loads a static mesh from a file and assigns it to the static mesh component. + * + * @param filePath The file path of the static mesh to load. + * + * @since Karma 1.0.0 + */ + void LoadMeshFromFile(const std::string& filePath); + + /** + * @brief Getter for the static mesh component + * + * @return UStaticMeshComponent* The static mesh component of this actor. + * + * @since Karma 1.0.0 + */ + UStaticMeshComponent* GetStaticMeshComponent() const { return m_StaticMeshComponent; } + + virtual void Tick(float DeltaSeconds) override; + }; +} diff --git a/Karma/src/Karma/GameFramework/Actor.cpp b/Karma/src/Karma/Engine/Classes/GameFramework/Actor.cpp similarity index 96% rename from Karma/src/Karma/GameFramework/Actor.cpp rename to Karma/src/Karma/Engine/Classes/GameFramework/Actor.cpp index 2284daca..daf5273a 100644 --- a/Karma/src/Karma/GameFramework/Actor.cpp +++ b/Karma/src/Karma/Engine/Classes/GameFramework/Actor.cpp @@ -1,5 +1,5 @@ #include "Actor.h" -#include "GameFramework/Level.h" +#include "Engine/Level.h" #include "World.h" #include "GameFramework/Pawn.h" #include "Ganit/Transform.h" @@ -333,6 +333,17 @@ namespace Karma } } + bool AActor::SetActorTransform(const FTransform& NewTransform) + { + if(m_RootComponent) + { + m_RootComponent->SetWorldTransform(NewTransform); + return true; + } + + return false; + } + void AActor::DispatchBeginPlay(bool bFromLevelStreaming) { UWorld* World = (!HasActorBegunPlay() && IsValidChecked(this) ? GetWorld() : nullptr); @@ -461,8 +472,8 @@ namespace Karma // Respect any non-default transform value that the root component may have received from the archetype that's owned // by the native CDO, so the final transform might not always necessarily equate to the passed-in UserSpawnTransform. - const FTransform RootTransform(SceneRootComponent->GetRelativeRotation(), SceneRootComponent->GetRelativeLocation(), SceneRootComponent->GetRelativeScale3D()); - const FTransform FinalRootComponentTransform = RootTransform * UserSpawnTransform; + /*const FTransform RootTransform(SceneRootComponent->GetRelativeRotation(), SceneRootComponent->GetRelativeLocation(), SceneRootComponent->GetRelativeScale3D());*/ + const FTransform FinalRootComponentTransform = /*RootTransform */ UserSpawnTransform; SceneRootComponent->SetWorldTransform(FinalRootComponentTransform/*, false, nullptr, ETeleportType::ResetPhysics*/); } @@ -544,6 +555,19 @@ namespace Karma return SceneRootComponent; } + bool AActor::SetActorLocation(const glm::vec3& NewLocation) + { + return true; + } + + bool AActor::SetActorRotation(TRotator NewRotator) + { + return true; + } + + void AActor::SetActorScale3D(glm::vec3 NewScale3D) + {} + bool AActor::SetRootComponent(class USceneComponent* NewRootComponent) { /** Only components owned by this actor can be used as a its root component. */ diff --git a/Karma/src/Karma/GameFramework/Actor.h b/Karma/src/Karma/Engine/Classes/GameFramework/Actor.h similarity index 88% rename from Karma/src/Karma/GameFramework/Actor.h rename to Karma/src/Karma/Engine/Classes/GameFramework/Actor.h index 68d76b5d..b4a82192 100644 --- a/Karma/src/Karma/GameFramework/Actor.h +++ b/Karma/src/Karma/Engine/Classes/GameFramework/Actor.h @@ -10,10 +10,11 @@ #pragma once -#include "krpch.h" +#include "Karma/Core.h" +#include "Karma/Log.h" +#include "Karma/Core/KarmaTypes.h" #include "Object.h" - -#include "GameFramework/SceneComponent.h" +#include "Components/SceneComponent.h" namespace Karma { @@ -143,6 +144,7 @@ namespace Karma uint8_t m_bAutoDestroyWhenFinished : 1; public: + /** * Return the ULevel that this Actor is part of. * @@ -305,6 +307,8 @@ namespace Karma { OutComponents.Add(tempSceneComponent); } + + // shouldn't we have iterator++ } } @@ -350,8 +354,35 @@ namespace Karma { return ActorToWorld(); } + + /** + * Set the Actors transform to the specified one. + * @param NewTransform The new transform. + * @param bSweep Whether we sweep to the destination location, triggering overlaps along the way and stopping short of the target if blocked by something. + * Only the root component is swept and checked for blocking collision, child components move without sweeping. If collision is off, this has no effect. + * @param bTeleport Whether we teleport the physics state (if physics collision is enabled for this object). + * If true, physics velocity for this object is unchanged (so ragdoll parts are not affected by change in location). + * If false, physics velocity is updated based on the change in position (affecting ragdoll parts). + * If CCD is on and not teleporting, this will affect objects along the entire swept volume. + * Note that when teleporting, any child/attached components will be teleported too, maintaining their current offset even if they are being simulated. + * Setting the transform without teleporting will not update the transform of simulated child/attached components. + * + * @todo Hit detection when collision system is installed + * @since Karma 1.0.0 + */ + bool SetActorTransform(const FTransform& NewTransform/*, bool bSweep=false, FHitResult* OutSweepHitResult=nullptr, ETeleportType Teleport = ETeleportType::None*/); - /** + /** + * @brief Move the actor instantly to the specified location. + */ + bool SetActorLocation(const glm::vec3& NewLocation); + + bool SetActorRotation(TRotator NewRotation); + + /** Set the Actor's world-space scale. */ + void SetActorScale3D(glm::vec3 NewScale3D); + + /** * Get the local-to-world transform of the RootComponent. Identical to GetTransform(). * * @warning Need to test this function rigorously before using diff --git a/Karma/src/Karma/GameFramework/ActorIterator.cpp b/Karma/src/Karma/Engine/Classes/GameFramework/ActorIterator.cpp similarity index 100% rename from Karma/src/Karma/GameFramework/ActorIterator.cpp rename to Karma/src/Karma/Engine/Classes/GameFramework/ActorIterator.cpp diff --git a/Karma/src/Karma/GameFramework/ActorIterator.h b/Karma/src/Karma/Engine/Classes/GameFramework/ActorIterator.h similarity index 99% rename from Karma/src/Karma/GameFramework/ActorIterator.h rename to Karma/src/Karma/Engine/Classes/GameFramework/ActorIterator.h index ea12bbc6..c3fbcf44 100644 --- a/Karma/src/Karma/GameFramework/ActorIterator.h +++ b/Karma/src/Karma/Engine/Classes/GameFramework/ActorIterator.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "KarmaUtilities.h" namespace Karma diff --git a/Karma/src/Karma/GameFramework/Pawn.cpp b/Karma/src/Karma/Engine/Classes/GameFramework/Pawn.cpp similarity index 100% rename from Karma/src/Karma/GameFramework/Pawn.cpp rename to Karma/src/Karma/Engine/Classes/GameFramework/Pawn.cpp diff --git a/Karma/src/Karma/GameFramework/Pawn.h b/Karma/src/Karma/Engine/Classes/GameFramework/Pawn.h similarity index 100% rename from Karma/src/Karma/GameFramework/Pawn.h rename to Karma/src/Karma/Engine/Classes/GameFramework/Pawn.h diff --git a/Karma/src/Karma/GameFramework/World.cpp b/Karma/src/Karma/Engine/Classes/GameFramework/World.cpp similarity index 99% rename from Karma/src/Karma/GameFramework/World.cpp rename to Karma/src/Karma/Engine/Classes/GameFramework/World.cpp index 376d3bd4..deef7d91 100644 --- a/Karma/src/Karma/GameFramework/World.cpp +++ b/Karma/src/Karma/Engine/Classes/GameFramework/World.cpp @@ -2,7 +2,7 @@ #include "Core/UObjectGlobals.h" #include "GameFramework/Actor.h" #include "Core/Class.h" -#include "GameFramework/Level.h" +#include "Engine/Level.h" #include "Ganit/Transform.h" #include "Level.h" #include "Karma/Core/Package.h" diff --git a/Karma/src/Karma/GameFramework/World.h b/Karma/src/Karma/Engine/Classes/GameFramework/World.h similarity index 98% rename from Karma/src/Karma/GameFramework/World.h rename to Karma/src/Karma/Engine/Classes/GameFramework/World.h index 5752373d..84907b7e 100644 --- a/Karma/src/Karma/GameFramework/World.h +++ b/Karma/src/Karma/Engine/Classes/GameFramework/World.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - #include "Object.h" #include "SubClassOf.h" @@ -186,6 +184,12 @@ namespace Karma return CastChecked(SpawnActor(Class, nullptr, SpawnParameters), ECastCheckedType::NullAllowed); } + template< class T > + T* SpawnActor(UClass* Class, FTransform const* Transform, const FActorSpawnParameters& SpawnParameters = FActorSpawnParameters()) + { + return CastChecked(SpawnActor(Class, Transform, SpawnParameters), ECastCheckedType::NullAllowed); + } + /** * Getter for m_PersistentLevel * diff --git a/Karma/src/Karma/GameFramework/WorldSettings.cpp b/Karma/src/Karma/Engine/Classes/GameFramework/WorldSettings.cpp similarity index 100% rename from Karma/src/Karma/GameFramework/WorldSettings.cpp rename to Karma/src/Karma/Engine/Classes/GameFramework/WorldSettings.cpp diff --git a/Karma/src/Karma/GameFramework/WorldSettings.h b/Karma/src/Karma/Engine/Classes/GameFramework/WorldSettings.h similarity index 96% rename from Karma/src/Karma/GameFramework/WorldSettings.h rename to Karma/src/Karma/Engine/Classes/GameFramework/WorldSettings.h index b042e22d..0832c13d 100644 --- a/Karma/src/Karma/GameFramework/WorldSettings.h +++ b/Karma/src/Karma/Engine/Classes/GameFramework/WorldSettings.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Actor.h" namespace Karma diff --git a/Karma/src/Karma/Engine/Engine.h b/Karma/src/Karma/Engine/Engine.h index 346ac91d..5d462b49 100644 --- a/Karma/src/Karma/Engine/Engine.h +++ b/Karma/src/Karma/Engine/Engine.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - #include "Object.h" #include "Core/KarmaTypes.h" diff --git a/Karma/src/Karma/Engine/GameInstance.h b/Karma/src/Karma/Engine/GameInstance.h index adf280c2..0f5f9ba6 100644 --- a/Karma/src/Karma/Engine/GameInstance.h +++ b/Karma/src/Karma/Engine/GameInstance.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - #include "Object.h" namespace Karma diff --git a/Karma/src/Karma/Engine/GameViewportClient.h b/Karma/src/Karma/Engine/GameViewportClient.h index 5348d93c..d2c63997 100644 --- a/Karma/src/Karma/Engine/GameViewportClient.h +++ b/Karma/src/Karma/Engine/GameViewportClient.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - #include "Object.h" namespace Karma diff --git a/Karma/src/Karma/EntryPoint.h b/Karma/src/Karma/EntryPoint.h index 9b49b590..63dcd529 100644 --- a/Karma/src/Karma/EntryPoint.h +++ b/Karma/src/Karma/EntryPoint.h @@ -36,7 +36,6 @@ int main(int argc, char** argv) { // TODO: add engine initialization code for various systems Karma::Log::Init(); - Karma::RenderCommand::Init(); KR_INFO("Hello Cowboy. Your lucky number is {0}", 7); auto app = Karma::CreateApplication(); @@ -48,7 +47,6 @@ int main(int argc, char** argv) delete app; - Karma::RenderCommand::DeInit(); Karma::Input::DeInit(); return 0; @@ -72,7 +70,6 @@ int main(int argc, char** argv) { // TODO: add engine initialization code for various systems Karma::Log::Init(); - Karma::RenderCommand::Init(); KR_INFO("Hello Cowboy. Your lucky number is {0}", 7); auto app = Karma::CreateApplication(); @@ -84,7 +81,6 @@ int main(int argc, char** argv) delete app; - Karma::RenderCommand::DeInit(); Karma::Input::DeInit(); return 0; @@ -109,7 +105,6 @@ int main(int argc, char** argv) { // TODO: add engine initialization code for various systems Karma::Log::Init(); - Karma::RenderCommand::Init(); KR_INFO("Hello Cowboy. Your lucky number is {0}", 7); auto app = Karma::CreateApplication(); @@ -120,7 +115,6 @@ int main(int argc, char** argv) app->Run(); delete app; - Karma::RenderCommand::DeInit(); Karma::Input::DeInit(); return 0; diff --git a/Karma/src/Karma/Events/ApplicationEvent.h b/Karma/src/Karma/Events/ApplicationEvent.h index 21c8b8cf..e3c8d84f 100644 --- a/Karma/src/Karma/Events/ApplicationEvent.h +++ b/Karma/src/Karma/Events/ApplicationEvent.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Event.h" namespace Karma diff --git a/Karma/src/Karma/Events/ControllerDeviceEvent.h b/Karma/src/Karma/Events/ControllerDeviceEvent.h index 518051f9..1b759f03 100644 --- a/Karma/src/Karma/Events/ControllerDeviceEvent.h +++ b/Karma/src/Karma/Events/ControllerDeviceEvent.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Event.h" namespace Karma diff --git a/Karma/src/Karma/Events/Event.h b/Karma/src/Karma/Events/Event.h index 99dc47e2..fe6b379e 100644 --- a/Karma/src/Karma/Events/Event.h +++ b/Karma/src/Karma/Events/Event.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - namespace Karma { /** diff --git a/Karma/src/Karma/Events/MouseEvent.h b/Karma/src/Karma/Events/MouseEvent.h index a0429e3a..b3114af8 100644 --- a/Karma/src/Karma/Events/MouseEvent.h +++ b/Karma/src/Karma/Events/MouseEvent.h @@ -9,7 +9,6 @@ */ #pragma once -#include "krpch.h" #include "Event.h" namespace Karma diff --git a/Karma/src/Karma/GameFramework/PrimitiveComponent.cpp b/Karma/src/Karma/GameFramework/PrimitiveComponent.cpp deleted file mode 100644 index ee61a65e..00000000 --- a/Karma/src/Karma/GameFramework/PrimitiveComponent.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "PrimitiveComponent.h" - -namespace Karma -{ - void UPrimitiveComponent::BeginPlay() - { - } - - void UPrimitiveComponent::SetCachedMaxDrawDistance(const float newCachedMaxDrawDistance) - { - } -} \ No newline at end of file diff --git a/Karma/src/Karma/Ganit/KarmaMath.h b/Karma/src/Karma/Ganit/KarmaMath.h index f05c38bb..6172efb0 100644 --- a/Karma/src/Karma/Ganit/KarmaMath.h +++ b/Karma/src/Karma/Ganit/KarmaMath.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - #include "glm/common.hpp" namespace Karma diff --git a/Karma/src/Karma/Ganit/Transform.cpp b/Karma/src/Karma/Ganit/Transform.cpp index 72446a99..a7d1d570 100644 --- a/Karma/src/Karma/Ganit/Transform.cpp +++ b/Karma/src/Karma/Ganit/Transform.cpp @@ -1,4 +1,5 @@ #include "Transform.h" +#include namespace Karma { @@ -13,9 +14,9 @@ namespace Karma TRotator::TRotator(glm::vec3 eulerAngles) { - m_Yaw = eulerAngles.y; - m_Pitch = eulerAngles.z; - m_Roll = eulerAngles.x; + m_Yaw = eulerAngles.x; + m_Pitch = eulerAngles.y; + m_Roll = eulerAngles.z; } TRotator TRotator::Inverse() const @@ -144,4 +145,39 @@ namespace Karma OutTransform->SetScale3D(glm::vec3(ScaleA.x * ScaleB.x, ScaleA.y * ScaleB.y, ScaleA.z * ScaleB.z));// = VectorMultiply(ScaleA, ScaleB); //} } + + glm::mat4 FTransform::ToMatrixWithScale() const + { + glm::mat4 ReturnMatrix = glm::mat4(1.0f); + + ReturnMatrix = glm::scale(ReturnMatrix, m_Scale3D); + ReturnMatrix *= glm::mat4_cast(m_Rotation.ToQuat()); + + ReturnMatrix = glm::translate(ReturnMatrix, m_Translation); + + return ReturnMatrix; + } + + void FTransform::ToTransform(const glm::mat4 Matrix) + { + glm::vec3 scale(0), translation(0), skew(0); + glm::quat rotation; + glm::vec4 perspective(0); + + glm::decompose(Matrix, scale, rotation, translation, skew, perspective); + + TRotator rotator = TRotator(glm::eulerAngles(rotation)); + + //FTransform transform = FTransform::Identity(); + + m_Translation = translation; + m_Rotation = rotator; + + m_Scale3D = scale; + + m_Translation[0] = (m_Scale3D[0] != 0) ? m_Translation[0] / m_Scale3D[0] : m_Translation[0]; + m_Translation[1] = (m_Scale3D[1] != 0) ? m_Translation[1] / m_Scale3D[1] : m_Translation[1]; + + m_Translation[2] = (m_Scale3D[2] != 0) ? m_Translation[2] / m_Scale3D[2] : m_Translation[2]; + } } diff --git a/Karma/src/Karma/Ganit/Transform.h b/Karma/src/Karma/Ganit/Transform.h index 360ef7fb..53a16bcc 100644 --- a/Karma/src/Karma/Ganit/Transform.h +++ b/Karma/src/Karma/Ganit/Transform.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "glm/glm.hpp" #include #define GLM_ENABLE_EXPERIMENTAL @@ -21,7 +19,7 @@ namespace Karma #define KR_SMALL_NUMBER (1.e-8f) /** - * Floating point quaternion that can represent a rotation about an axis in 3-D space. + * @brief Floating point quaternion that can represent a rotation about an axis in 3-D space. * The X, Y, Z, W components also double as the Axis/Angle format. * * Order matters when composing quaternions: C = A * B will yield a quaternion C that logically @@ -30,6 +28,8 @@ namespace Karma * * Example: LocalToWorld = (LocalToWorld * DeltaRotation) will change rotation in local space by DeltaRotation. * Example: LocalToWorld = (DeltaRotation * LocalToWorld) will change rotation in world space by DeltaRotation. + * + * @since Karma 1.0.0 */ struct KARMA_API TQuaternion { @@ -48,7 +48,7 @@ namespace Karma }; /** - * Implements a container for rotation information. + * @brief Implements a container for rotation information. * * All rotation values are stored in degrees. * @@ -60,6 +60,7 @@ namespace Karma * Note that these conventions differ from quaternion axis/angle. UE Quat always considers a positive angle to be a left-handed rotation, * whereas Rotator treats yaw as left-handed but pitch and roll as right-handed. * + * @since Karma 1.0.0 */ struct KARMA_API TRotator { @@ -76,7 +77,7 @@ namespace Karma float m_Roll; /** - * Returns the counter of this rotation governed by Yaw, Pitch, and Roll in the + * @brief Returns the counter of this rotation governed by Yaw, Pitch, and Roll in the * fashion above */ inline TRotator Inverse() const; @@ -93,12 +94,16 @@ namespace Karma glm::vec3 returnVector(tempResult.x, tempResult.y, tempResult.z); return returnVector; - + } + + inline glm::quat ToQuat() const + { + return glm::quat(glm::vec3(m_Pitch, m_Yaw, m_Roll)); } }; /** - * Transform composed of Scale, Rotation (as a quaternion), and Translation. + * @brief Transform composed of Scale, Rotation (as a quaternion), and Translation. * * Transforms can be used to convert from one space to another, for example by transforming * positions and directions from local space to world space. @@ -111,6 +116,10 @@ namespace Karma * * Example: LocalToWorld = (DeltaRotation * LocalToWorld) will change rotation in local space by DeltaRotation. * Example: LocalToWorld = (LocalToWorld * DeltaRotation) will change rotation in world space by DeltaRotation. + * + * @note We will be following Unreal Engine's left handed convention, meaning +X is forward, +Y is right, +Z is up. + * + * @since Karma 1.0.0 */ class KARMA_API FTransform { @@ -143,22 +152,26 @@ namespace Karma FTransform GetRelativeTransform(const FTransform& RelativeToWhat) const; /** - * Return a transform that is the result of this multiplied by another transform. + * @brief Return a transform that is the result of this multiplied by another transform. * Order matters when composing transforms : C = A * B will yield a transform C that logically first applies A then B to any subsequent transformation. * * @param Other other transform by which to multiply. * @return new transform: this * Other + * + * @since Karma 1.0.0 */ FTransform operator*(const FTransform& Other) const; /** - * Create a new transform: OutTransform = A * B. + * @brief Create a new transform: OutTransform = A * B. * * Order matters when composing transforms : A * B will yield a transform that logically first applies A then B to any subsequent transformation. * * @param OutTransform pointer to transform that will store the result of A * B. * @param A Transform A. * @param B Transform B. + * + * @since Karma 1.0.0 */ inline static void Multiply(FTransform* OutTransform, const FTransform* A, const FTransform* B); @@ -226,6 +239,10 @@ namespace Karma { m_Scale3D = Other.GetScale3D(); } + + glm::mat4 ToMatrixWithScale() const; + + void ToTransform(const glm::mat4 Matrix); public: static FTransform m_Identity; diff --git a/Karma/src/Karma/Input.h b/Karma/src/Karma/Input.h index 99a57741..21ec2489 100644 --- a/Karma/src/Karma/Input.h +++ b/Karma/src/Karma/Input.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Log.h" #include "Karma/Events/Event.h" #include "Karma/Window.h" diff --git a/Karma/src/Karma/KarmaGui/KarmaGui.cpp b/Karma/src/Karma/KarmaGui/KarmaGui.cpp index 7adf8634..737efbf9 100644 --- a/Karma/src/Karma/KarmaGui/KarmaGui.cpp +++ b/Karma/src/Karma/KarmaGui/KarmaGui.cpp @@ -7009,7 +7009,7 @@ void Karma::KarmaGui::SetKeyboardFocusHere(int offset) KarmaGuiContext& g = *GKarmaGui; KGGuiWindow* window = g.CurrentWindow; KR_CORE_ASSERT(offset >= -1, ""); // -1 is allowed but not below - KR_CORE_INFO("SetKeyboardFocusHere(%d) in window \"%s\"\n", offset, window->Name); + KR_CORE_INFO("SetKeyboardFocusHere(%d) in window \"%s\"", offset, window->Name); // It makes sense in the vast majority of cases to never interrupt a drag and drop. // When we refactor this function into ActivateItem() we may want to make this an option. @@ -7017,7 +7017,7 @@ void Karma::KarmaGui::SetKeyboardFocusHere(int offset) // is also automatically dropped in the event g.ActiveId is stolen. if (g.DragDropActive || g.MovingWindow != NULL) { - KR_CORE_INFO("SetKeyboardFocusHere() ignored while DragDropActive!\n"); + KR_CORE_INFO("SetKeyboardFocusHere() ignored while DragDropActive!"); return; } @@ -10132,7 +10132,7 @@ void Karma::KarmaGuiInternal::SetNavWindow(KGGuiWindow* window) KarmaGuiContext& g = *GKarmaGui; if (g.NavWindow != window) { - KR_CORE_INFO("[focus] SetNavWindow(\"{0}\")\n", window ? window->Name : ""); + KR_CORE_INFO("[focus] SetNavWindow(\"{0}\")", window ? window->Name : ""); g.NavWindow = window; } g.NavInitRequest = g.NavMoveSubmitted = g.NavMoveScoringItems = false; @@ -10614,7 +10614,7 @@ void Karma::KarmaGuiInternal::NavInitWindow(KGGuiWindow* window, bool force_rein bool init_for_nav = false; if (window == window->RootWindow || (window->Flags & KGGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) init_for_nav = true; - KR_CORE_INFO("[nav] NavInitRequest: from NavInitWindow(), init_for_nav={0}, window=\"{1}\"", init_for_nav, window->Name); + KR_CORE_INFO("[nav] NavInitRequest: from NavInitWindow(), init_for_nav={0}, window=\"{1}\"", init_for_nav, window->Name); if (init_for_nav) { SetNavID(0, g.NavLayer, window->NavRootFocusScopeId, KGRect()); @@ -10858,7 +10858,7 @@ void Karma::KarmaGuiInternal::NavInitRequestApplyResult() // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) // FIXME-NAV: On _NavFlattened windows, g.NavWindow will only be updated during subsequent frame. Not a problem currently. - KR_CORE_INFO("[nav] NavInitRequest: ApplyResult: NavID {0} Window \"{1}\"", g.NavInitResultId, g.NavWindow->Name); + KR_CORE_INFO("[nav] NavInitRequest: ApplyResult: NavID {0} Window \"{1}\"", g.NavInitResultId, g.NavWindow->Name); SetNavID(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel); g.NavIdIsAlive = true; // Mark as alive from previous frame as we got a result if (g.NavInitRequestFromMove) @@ -10929,7 +10929,7 @@ void Karma::KarmaGuiInternal::NavUpdateCreateMoveRequest() // Moving with no reference triggers an init request (will be used as a fallback if the direction fails to find a match) if (g.NavMoveSubmitted && g.NavId == 0) { - KR_CORE_INFO("[nav] NavInitRequest: from move, window \"{0}\"", window ? window->Name : ""); + KR_CORE_INFO("[nav] NavInitRequest: from move, window \"{0}\"", window ? window->Name : ""); g.NavInitRequest = g.NavInitRequestFromMove = true; g.NavInitResultId = 0; g.NavDisableHighlight = false; @@ -14403,7 +14403,7 @@ static void Karma::DockNodeAddWindow(KGGuiDockNode* node, KGGuiWindow* window, b Karma::DockNodeRemoveWindow(window->DockNode, window, 0); } KR_CORE_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL, ""); - KR_CORE_INFO("[docking] DockNodeAddWindow node 0x%08X window '{0}'\n", node->ID, window->Name); + KR_CORE_INFO("[docking] DockNodeAddWindow node 0x%08X window '{0}'", node->ID, window->Name); // If more than 2 windows appeared on the same frame leading to the creation of a new hosting window, // we'll hide windows until the host window is ready. Hide the 1st window after its been output (so it is not visible for one frame). @@ -14459,7 +14459,7 @@ void Karma::DockNodeRemoveWindow(KGGuiDockNode* node, KGGuiWindow* window, KGGui //KR_CORE_ASSERT(window->RootWindowDockTree == node->HostWindow); //KR_CORE_ASSERT(window->LastFrameActive < g.FrameCount); // We may call this from Begin() KR_CORE_ASSERT(save_dock_id == 0 || save_dock_id == node->ID, ""); - KR_CORE_INFO("[docking] DockNodeRemoveWindow node 0x%08X window '{0}'\n", node->ID, window->Name); + KR_CORE_INFO("[docking] DockNodeRemoveWindow node 0x%08X window '{0}'", node->ID, window->Name); window->DockNode = NULL; window->DockIsActive = window->DockTabWantClose = false; @@ -16704,7 +16704,7 @@ static KGGuiDockNode* DockBuilderCopyNodeRec(KGGuiDockNode* src_node, KGGuiID ds dst_node->ChildNodes[child_n]->ParentNode = dst_node; } - KR_CORE_INFO("[docking] Fork node {0} -> {1} ({2} childs)\n", src_node->ID, dst_node->ID, dst_node->IsSplitNode() ? 2 : 0); + KR_CORE_INFO("[docking] Fork node {0} -> {1} ({2} childs)", src_node->ID, dst_node->ID, dst_node->IsSplitNode() ? 2 : 0); return dst_node; } diff --git a/Karma/src/Karma/KarmaGui/KarmaGui.h b/Karma/src/Karma/KarmaGui/KarmaGui.h index eba8279c..87111380 100644 --- a/Karma/src/Karma/KarmaGui/KarmaGui.h +++ b/Karma/src/Karma/KarmaGui/KarmaGui.h @@ -25,9 +25,6 @@ #define KG_OFFSETOF(_TYPE,_MEMBER) offsetof(_TYPE, _MEMBER) // Offset of _MEMBER within _TYPE. Standardized as offsetof() in C++11 -// Includes -#include "krpch.h" - //----------------------------------------------------------------------------- // [SECTION] Forward declarations and basic types //----------------------------------------------------------------------------- @@ -174,6 +171,19 @@ struct KGVec4 namespace Karma { + /** + * @brief The chief class for our UI needs. The responsibility includes + * - Context creation and access + * - Main functions to start and end frames, render, and access draw data + * - Demo, Debug, Information windows + * - Styles + * - Windows and Child Windows management + * - Window Utilities and Manipulation + * - Content region access + * - Windows Scrolling + * + * @since Karma 1.0.0 + */ class KARMA_API KarmaGui { public: diff --git a/Karma/src/Karma/KarmaGui/KarmaGuiInternal.h b/Karma/src/Karma/KarmaGui/KarmaGuiInternal.h index dec72dff..f4ba02bb 100644 --- a/Karma/src/Karma/KarmaGui/KarmaGuiInternal.h +++ b/Karma/src/Karma/KarmaGui/KarmaGuiInternal.h @@ -43,7 +43,6 @@ Index of this file: #pragma once -#include "krpch.h" #include "KarmaGui.h" // Enable stb_truetype by default unless FreeType is enabled. @@ -1729,7 +1728,7 @@ struct KGGuiContextHook }; //----------------------------------------------------------------------------- -// [SECTION] KarmaGuiContext (main Dear ImGui context) +// [SECTION] KarmaGuiContext (main KarmaGui context) //----------------------------------------------------------------------------- struct KarmaGuiContext { diff --git a/Karma/src/Karma/KarmaGui/KarmaGuiLayer.cpp b/Karma/src/Karma/KarmaGui/KarmaGuiLayer.cpp index 509b7d35..14d864b3 100644 --- a/Karma/src/Karma/KarmaGui/KarmaGuiLayer.cpp +++ b/Karma/src/Karma/KarmaGui/KarmaGuiLayer.cpp @@ -5,6 +5,7 @@ #include "glm/glm.hpp" #include "Renderer/Renderer.h" #include "KarmaGuiRenderer.h" +#include "KarmaGui/KarmaGuizmo.h" namespace Karma { @@ -67,6 +68,8 @@ namespace Karma { KarmaGuiRenderer::OnKarmaGuiLayerBegin(); KarmaGui::NewFrame(); + + KarmaGuizmo::BeginFrame(); } void KarmaGuiLayer::KarmaGuiRender(float deltaTime) diff --git a/Karma/src/Karma/KarmaGui/KarmaGuiLayer.h b/Karma/src/Karma/KarmaGui/KarmaGuiLayer.h index 5edc26b0..a2734fbb 100644 --- a/Karma/src/Karma/KarmaGui/KarmaGuiLayer.h +++ b/Karma/src/Karma/KarmaGui/KarmaGuiLayer.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - #include "Layer.h" #include "Events/KeyEvent.h" #include "Events/MouseEvent.h" diff --git a/Karma/src/Karma/KarmaGui/KarmaGuiRenderer.cpp b/Karma/src/Karma/KarmaGui/KarmaGuiRenderer.cpp index 057efc7f..d27d8ba0 100644 --- a/Karma/src/Karma/KarmaGui/KarmaGuiRenderer.cpp +++ b/Karma/src/Karma/KarmaGui/KarmaGuiRenderer.cpp @@ -2,8 +2,16 @@ #include "Renderer/RendererAPI.h" #include "Vulkan/VulkanHolder.h" #include "Renderer/RenderCommand.h" - +#include "StaticMeshActor.h" #include "Platform/Vulkan/VulkanVertexArray.h" +#include "VulkanRHI/VulkanDynamicRHI.h" +#include "VulkanRHI/VulkanSwapChain.h" +#include "VulkanRHI/VulkanSynchronization.h" +#include "KarmaRHI/DynamicRHI.h" +#include "VulkanRHI/VulkanRenderPass.h" +#include "VulkanRHI/VulkanDescriptorSets.h" +#include "StaticMeshActor.h" +#include "PrimitiveComponent.h" // Emedded font #include "Karma/KarmaGui/Roboto-Regular.h" @@ -11,6 +19,7 @@ namespace Karma { VkDescriptorPool KarmaGuiRenderer::m_KarmaGuiDescriptorPool; + uint32_t KarmaGuiRenderer::m_SMCounter = 0; KarmaGui_ImplVulkanH_Window KarmaGuiRenderer::m_VulkanWindowData; bool KarmaGuiRenderer::m_SwapChainRebuild; GLFWwindow* KarmaGuiRenderer::m_GLFWwindow = nullptr; @@ -22,38 +31,38 @@ namespace Karma m_GLFWwindow = window; - if (RendererAPI::GetAPI() == RendererAPI::API::Vulkan) + if (GRHIInterfaceType == ERHIInterfaceType::Vulkan) { KarmaGui_ImplGlfw_InitForVulkan(window, true); KarmaGui_ImplVulkan_InitInfo initInfo = {}; - // An inter-class communication - initInfo.Instance = VulkanHolder::GetVulkanContext()->GetInstance(); - initInfo.PhysicalDevice = VulkanHolder::GetVulkanContext()->GetPhysicalDevice(); - initInfo.Device = VulkanHolder::GetVulkanContext()->GetLogicalDevice(); - initInfo.QueueFamily = VulkanHolder::GetVulkanContext()->FindQueueFamilies(initInfo.PhysicalDevice).graphicsFamily.value(); - initInfo.Queue = VulkanHolder::GetVulkanContext()->GetGraphicsQueue(); + initInfo.Instance = FVulkanDynamicRHI::Get().GetInstance(); + initInfo.PhysicalDevice = FVulkanDynamicRHI::Get().GetDevice()->GetGPU(); + initInfo.Device = FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(); + initInfo.QueueFamily = FVulkanDynamicRHI::Get().FindQueueFamilies(initInfo.PhysicalDevice).graphicsFamily.value(); + initInfo.Queue = FVulkanDynamicRHI::Get().GetDevice()->GetGraphicsQueue(); initInfo.PipelineCache = VK_NULL_HANDLE; - initInfo.MinImageCount = VulkanHolder::GetVulkanContext()->GetMinImageCount(); - initInfo.ImageCount = VulkanHolder::GetVulkanContext()->GetImageCount(); + initInfo.MinImageCount = FVulkanDynamicRHI::Get().GetGpuSwapChainSupportDetails().capabilities.minImageCount; + initInfo.ImageCount = FVulkanDynamicRHI::Get().SwapChainImageCount(); initInfo.MSAASamples = VK_SAMPLE_COUNT_1_BIT; // Stuff created and dedicated to KarmaGui - CreateDescriptorPool(); + CreateDescriptorPool(initInfo.Device); initInfo.DescriptorPool = m_KarmaGuiDescriptorPool; - - initInfo.RenderPass = VulkanHolder::GetVulkanContext()->GetRenderPass(); - - // Not sure about the use of Subpass so setting to 0 - initInfo.Subpass = 0; // Settingup backend in KarmaGui // KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_Init(&initInfo); KarmaGui_ImplVulkan_Init(&initInfo); // Fresh start with newly instantiated Vulkan data - // Since VulkanContext has already instantiated fresh swapchain and commandbuffers, we send that false - KarmaGuiVulkanHandler::ShareVulkanContextResourcesOfMainWindow(&m_VulkanWindowData, true); + KarmaGuiVulkanHandler::FillWindowData(&m_VulkanWindowData, true); + + // See if all the appropriate Vulkan resources have been instantiated + KarmaGuiVulkanHandler::CheckInitialization(); + + // Font, descriptor, and pipeline + KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreateDeviceObjects(); + FVulkanDynamicRHI::Get().GetDevice()->InitializeDefaultDescriptorSets(m_VulkanWindowData.RHIResources->VulkanSwapChain->GetMaxFramesInFlight()); // Load default font KGFontConfig fontConfig; @@ -117,11 +126,26 @@ namespace Karma } } + void KarmaGuiRenderer::OnAdditionOfStaticMesh(AStaticMeshActor* smActor) + { + if(GRHIInterfaceType == ERHIInterfaceType::Vulkan) + { + uint32_t maxFramesInFlight = GetWindowData().RHIResources->VulkanSwapChain->GetMaxFramesInFlight(); + + for (uint32_t counter = 0; counter < maxFramesInFlight; counter++) + { + FVulkanDynamicRHI::Get().GetDevice()->GetDefaultDescriptorSets()[counter]->UpdateUniformBufferDescriptorSet(static_cast(static_cast(smActor->GetRootComponent())->GetComponentTransformUniform().get()), 1, m_SMCounter, counter); + } + + m_SMCounter++; + } + } + void KarmaGuiRenderer::AddImageTexture(char const* fileName, const std::string& label) { - switch(RendererAPI::GetAPI()) + switch(GRHIInterfaceType) { - case RendererAPI::API::Vulkan: + case ERHIInterfaceType::Vulkan: { KarmaGuiIO& io = KarmaGui::GetIO(); KarmaGuiBackendRendererUserData* backendData = (KarmaGuiBackendRendererUserData*) io.BackendRendererUserData; @@ -155,12 +179,12 @@ namespace Karma KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to wait!"); } break; - case RendererAPI::API::OpenGL: + case ERHIInterfaceType::OpenGL: { KarmaGuiOpenGLHandler::KarmaGui_ImplOpenGL3_CreateTexture(fileName, label); } break; - case RendererAPI::API::None: + case ERHIInterfaceType::Null: KR_CORE_ASSERT(false, "RendererAPI::None is not supported"); break; default: @@ -171,16 +195,16 @@ namespace Karma void KarmaGuiRenderer::OnKarmaGuiLayerBegin() { - switch (RendererAPI::GetAPI()) + switch (GRHIInterfaceType) { - case RendererAPI::API::Vulkan: + case ERHIInterfaceType::Vulkan: GiveLoopBeginControlToVulkan(); break; - case RendererAPI::API::OpenGL: + case ERHIInterfaceType::OpenGL: KarmaGuiOpenGLHandler::KarmaGui_ImplOpenGL3_NewFrame(); KarmaGui_ImplGlfw_NewFrame(); break; - case RendererAPI::API::None: + case ERHIInterfaceType::Null: KR_CORE_ASSERT(false, "RendererAPI::None is not supported"); break; default: @@ -244,23 +268,15 @@ namespace Karma // Maybe chore for toofani mood! // io.BackendFlags |= KarmaGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) - KR_CORE_ASSERT(info->Instance != VK_NULL_HANDLE, "No instance found"); - KR_CORE_ASSERT(info->PhysicalDevice != VK_NULL_HANDLE, "No physical device found"); - KR_CORE_ASSERT(info->Device != VK_NULL_HANDLE, "No device found"); - KR_CORE_ASSERT(info->Queue != VK_NULL_HANDLE, "No queue assigned"); - KR_CORE_ASSERT(info->DescriptorPool != VK_NULL_HANDLE, "No descriptor pool found"); - KR_CORE_ASSERT(info->MinImageCount >= 2, "Minimum image count exceeding limit"); - KR_CORE_ASSERT(info->ImageCount >= info->MinImageCount, "Not enough pitch for ImageCount"); - KR_CORE_ASSERT(info->RenderPass != VK_NULL_HANDLE, "No renderpass assigned"); - backendData->VulkanInitInfo = *info; //backendData->VulkanInitInfo.Device = info->Device; - backendData->RenderPass = info->RenderPass; - backendData->Subpass = info->Subpass; + //backendData->RenderPass = info->RenderPass; + //backendData->Subpass = info->Subpass; - // Font, descriptor, and pipeline - KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreateDeviceObjects(); + // Font, descriptor, and pipeline (moved to the KarmaGuiRenderer::SetUpKarmaGuiRenderer, after KarmaGuiVulkanHandler::FillWindowData + // because RenderPass is required for pipeline creation) + // KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreateDeviceObjects(); // Our render function expect RendererUserData to be storing the window render buffer we need (for the main viewport we won't use ->Window) KarmaGuiViewport* mainViewport = KarmaGui::GetMainViewport(); @@ -285,23 +301,10 @@ namespace Karma if (width > 0 && height > 0) { - RendererAPI* rAPI = RenderCommand::GetRendererAPI(); - VulkanRendererAPI* vulkanAPI = nullptr; - - if (rAPI->GetAPI() == RendererAPI::API::Vulkan) - { - vulkanAPI = static_cast(rAPI); - } - else - { - KR_CORE_ASSERT(false, "How is this even possible?"); - } - - KR_CORE_ASSERT(vulkanAPI != nullptr, "Casting to VulkanAPI failed"); + //KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreateOrResizeWindow(&m_VulkanWindowData, true, true); + KarmaGuiVulkanHandler::ShivaSwapChainForRebuild(&m_VulkanWindowData); + KarmaGuiVulkanHandler::FillWindowData(&m_VulkanWindowData, false); - vulkanAPI->RecreateCommandBuffersAndSwapChain(); - - KarmaGuiVulkanHandler::ShareVulkanContextResourcesOfMainWindow(&m_VulkanWindowData, false); m_SwapChainRebuild = false; } } @@ -396,7 +399,7 @@ namespace Karma vkDestroyDescriptorPool(vulkanInfo->Device, m_KarmaGuiDescriptorPool, VK_NULL_HANDLE); } - void KarmaGuiRenderer::CreateDescriptorPool() + void KarmaGuiRenderer::CreateDescriptorPool(VkDevice VulkanDevice) { VkDescriptorPoolSize pool_sizes[] = { @@ -420,7 +423,7 @@ namespace Karma poolInfo.poolSizeCount = uint32_t(std::size(pool_sizes)); poolInfo.pPoolSizes = pool_sizes; - VkResult result = vkCreateDescriptorPool(VulkanHolder::GetVulkanContext()->GetLogicalDevice(), &poolInfo, nullptr, &m_KarmaGuiDescriptorPool); + VkResult result = vkCreateDescriptorPool(VulkanDevice, &poolInfo, nullptr, &m_KarmaGuiDescriptorPool); KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create descriptor pool for KarmaGui"); } @@ -431,18 +434,12 @@ namespace Karma KarmaGui_ImplVulkan_InitInfo* vulkanInfo = &backendData->VulkanInitInfo; // Pointer to the per frame data for instance fence, semaphores, and commandbuffer - // Remember windowData->SemaphoreIndex is m_CurrentFrame equivalent of VulkanRendererAPI KarmaGui_Vulkan_Frame_On_Flight* frameOnFlightData = &windowData->FramesOnFlight[windowData->SemaphoreIndex]; VkResult result; - // Pointer to the container of framebuffers (based on number of swapchain images) - KarmaGui_ImplVulkanH_ImageFrame* frameData = &windowData->ImageFrames[windowData->ImageFrameIndex]; - - result = vkWaitForFences(vulkanInfo->Device, 1, &frameOnFlightData->Fence, VK_TRUE, UINT64_MAX); - KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to wait"); + // Fence needs to be signaled to pass the vkWaitForFences. + FVulkanDynamicRHI::Get().GetDevice()->GetFenceManager().WaitForFence(frameOnFlightData->Fence); - - // ImageAcquiredSemaphore is m_ImageAvailableSemaphores equivalent VkSemaphore imageAcquiredSemaphore = frameOnFlightData->ImageAcquiredSemaphore; VkSemaphore renderCompleteSemaphore = frameOnFlightData->RenderCompleteSemaphore; @@ -450,10 +447,17 @@ namespace Karma if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { m_SwapChainRebuild = true; + vkDeviceWaitIdle(FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice()); + return; } + else + { + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to acquire next image"); + } - KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to acquire next image"); + // Pointer to the container of framebuffers (based on number of swapchain images) + KarmaGui_ImplVulkanH_ImageFrame* frameData = &windowData->ImageFrames[windowData->ImageFrameIndex]; vkResetCommandBuffer(frameOnFlightData->CommandBuffer, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); @@ -467,12 +471,18 @@ namespace Karma KR_CORE_ASSERT(result == VK_SUCCESS, "Couldn't begin commandbuffer recording"); } - for(auto it = backendData->Elements3DTo2D.begin(); it != backendData->Elements3DTo2D.end(); ++it) + FVulkanDynamicRHI::Get().UploadUniformBufferObjects(windowData->SemaphoreIndex); + + for (auto it = backendData->Elements3DTo2D.begin(); it != backendData->Elements3DTo2D.end(); ++it) { + std::shared_ptr scene3D = it->Scene3D; + + FVulkanDescriptorSets* descriptorSets = FVulkanDynamicRHI::Get().GetDevice()->GetDefaultDescriptorSets()[windowData->SemaphoreIndex]; + { VkRenderPassBeginInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderPassInfo.renderPass = backendData->OffScreenRR.RenderPass; + renderPassInfo.renderPass = backendData->OffScreenRR.RenderPass->GetHandle(); renderPassInfo.framebuffer = it->FrameBuffer; renderPassInfo.renderArea.offset = {0, 0}; renderPassInfo.renderArea.extent.width = it->Size.x; @@ -488,30 +498,38 @@ namespace Karma // The pass starts here and all commands until vkCmdEndRenderPass are recorded into it vkCmdBeginRenderPass(frameOnFlightData->CommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - std::shared_ptr vulkanVA = static_pointer_cast(it->Scene3D->GetRenderableVertexArray()); - vkCmdBindPipeline(frameOnFlightData->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vulkanVA->GetKarmaGuiGraphicsPipeline()); + + // ---- Bind Graphics Pipeline ---- + vkCmdBindPipeline(frameOnFlightData->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, backendData->OffScreenRR.OffscreenGraphicsPipeline); + + // === BIND GLOBAL DESCRIPTOR SETS (once per frame) === + // Set 0, Binding 0 :Camera UBO + vkCmdBindDescriptorSets(frameOnFlightData->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, backendData->OffScreenRR.OffscreenPipelineLayout, 0, 1, &descriptorSets->m_DescriptorSets[0][0], 0, nullptr); + // Set 0, Binding 1: Texture + vkCmdBindDescriptorSets(frameOnFlightData->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, backendData->OffScreenRR.OffscreenPipelineLayout, 0, 1, &descriptorSets->m_DescriptorSets[0][0], 0, nullptr); + + uint32_t objectIndex = 0; // ---- Bind 3D Vertex And Index Buffers ---- + for (const auto& smActor : scene3D->GetSMActors()) { - VkBuffer vertexBuffers[1] = { vulkanVA->GetVertexBuffer()->GetVertexBuffer() }; + std::shared_ptr mesh = smActor->GetStaticMeshComponent()->GetStaticMesh(); + + VkBuffer vertexBuffers[1] = { std::static_pointer_cast(mesh->GetVertexBuffer())->GetVertexBuffer() }; VkDeviceSize vertexOffset[1] = { 0 }; vkCmdBindVertexBuffers(frameOnFlightData->CommandBuffer, 0, 1, vertexBuffers, vertexOffset); - vkCmdBindIndexBuffer(frameOnFlightData->CommandBuffer, vulkanVA->GetIndexBuffer()->GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32); + vkCmdBindIndexBuffer(frameOnFlightData->CommandBuffer, std::static_pointer_cast(mesh->GetIndexBuffer())->GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32); + + // Set 2: Object UBO + vkCmdBindDescriptorSets(frameOnFlightData->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, backendData->OffScreenRR.OffscreenPipelineLayout, 1, 1, &descriptorSets->m_DescriptorSets[1][objectIndex], 0, nullptr); + + // ----Issue Draw Commands---- + // Draw 3D scene geometry on 2D rendertarget (it->FrameBuffers) + vkCmdDrawIndexed(frameOnFlightData->CommandBuffer, std::static_pointer_cast(mesh->GetIndexBuffer())->GetCount(), 1, 0, 0, 0); + + objectIndex++; } - vulkanVA->UpdateProcessAndSetReadyForSubmission(); - vulkanVA->Bind(); - - VulkanHolder::GetVulkanContext()->UploadUBO(windowData->SemaphoreIndex); - - // ---- Bind descriptor sets (e.g., uniforms for MVP matrices, lighting) ---- - // Assumes layout compatibility between pipeline and descriptor set - vkCmdBindDescriptorSets(frameOnFlightData->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vulkanVA->GetGraphicsPipelineLayout(), 0, 1, &vulkanVA->GetDescriptorSets()[windowData->SemaphoreIndex], 0, nullptr); - - // ---- Issue Draw Commands ---- - // Draw 3D scene geometry on 2D rendertarget (it->FrameBuffers) - vkCmdDrawIndexed(frameOnFlightData->CommandBuffer, vulkanVA->GetIndexBuffer()->GetCount(), 1, 0, 0, 0); - // ---- End the Offscreen Render Pass ---- vkCmdEndRenderPass(frameOnFlightData->CommandBuffer); } @@ -526,7 +544,7 @@ namespace Karma renderPassInfo.renderArea.extent = windowData->RenderArea.extent; std::array clearValues{}; - clearValues[0] = { windowData->ClearValue.color.float32[0], windowData->ClearValue.color.float32[1], windowData->ClearValue.color.float32[2], windowData->ClearValue.color.float32[3] }; + clearValues[0] = { windowData->ClearValue.color.float32[0], windowData->ClearValue.color.float32[1], windowData->ClearValue.color.float32[2], windowData->ClearValue.color.float32[3] }; clearValues[1].depthStencil = { 1.0f, 0 }; renderPassInfo.clearValueCount = static_cast(clearValues.size()); @@ -559,11 +577,13 @@ namespace Karma submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = &renderCompleteSemaphore; - // We reset the fence here or else the swapchain rebuild seemingly fails - result = vkResetFences(vulkanInfo->Device, 1, &frameOnFlightData->Fence); - KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to reset fence"); + // vkResetFences unsignals the Fence + // Fixing a deadlock: https://vulkan-tutorial.com/Drawing_a_triangle/Swap_chain_recreation#page_Fixing-a-deadlock + FVulkanDynamicRHI::Get().GetDevice()->GetFenceManager().ResetFence(frameOnFlightData->Fence); - result = vkQueueSubmit(vulkanInfo->Queue, 1, &submitInfo, frameOnFlightData->Fence); + VkFence fence = frameOnFlightData->Fence->GetHandle(); + // vkQueueSubmit signals the Fence once commandbuffers finish execution + result = vkQueueSubmit(vulkanInfo->Queue, 1, &submitInfo, fence); KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to submit queue"); } @@ -592,6 +612,9 @@ namespace Karma if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { m_SwapChainRebuild = true; + } + if (result == VK_ERROR_OUT_OF_DATE_KHR) + { return; } @@ -602,7 +625,7 @@ namespace Karma KGTextureID KarmaGuiRenderer::Add3DSceneFor2DRendering(std::shared_ptr scene, KGVec2 dimensions) { - if(RendererAPI::GetAPI() == RendererAPI::API::Vulkan) + if(GRHIInterfaceType == ERHIInterfaceType::Vulkan) { KarmaGui_ImplVulkan_Data* backendData = GetBackendRendererUserData(); KarmaGui_ImplVulkan_InitInfo* vulkanInfo = &backendData->VulkanInitInfo; diff --git a/Karma/src/Karma/KarmaGui/KarmaGuiRenderer.h b/Karma/src/Karma/KarmaGui/KarmaGuiRenderer.h index 7be5624e..f9b8eb7f 100644 --- a/Karma/src/Karma/KarmaGui/KarmaGuiRenderer.h +++ b/Karma/src/Karma/KarmaGui/KarmaGuiRenderer.h @@ -10,14 +10,15 @@ #pragma once -#include "krpch.h" - #include "imgui_impl_glfw.h" + #include "Platform/Vulkan/KarmaGuiVulkanHandler.h" #include "Platform/OpenGL/KarmaGuiOpenGLHandler.h" namespace Karma { + class AStaticMeshActor; + /** * @brief A multiply inherited class for supporting both OpenGL and Vulkan API's. * @@ -52,6 +53,8 @@ namespace Karma static void OnKarmaGuiLayerBegin(); static void OnKarmaGuiLayerEnd(); + static void OnAdditionOfStaticMesh(AStaticMeshActor* smActor); + /** * @brief Acessor function for KarmaGui's renderer backend (BackendRendererUserData). * @@ -70,7 +73,7 @@ namespace Karma * @since Karma 1.0.0 */ static void KarmaGui_ImplVulkan_Init(KarmaGui_ImplVulkan_InitInfo* initInfo); - static void CreateDescriptorPool(); + static void CreateDescriptorPool(VkDevice VulkanDevice); /** * @brief Calls CleanUpVulkanAndWindowData() and does the shutting of GLFW and KarmaGui (KarmaGui::DestroyContext) @@ -121,6 +124,11 @@ namespace Karma * @since Karma 1.0.0 */ static KGTextureID Add3DSceneFor2DRendering(std::shared_ptr scene, KGVec2 dimensions); + + /** + * @brief Getter for m_VulkanWindowData + */ + static const KarmaGui_ImplVulkanH_Window& GetWindowData() { return m_VulkanWindowData; } private: @@ -130,6 +138,8 @@ namespace Karma // Vulkan specific members static VkDescriptorPool m_KarmaGuiDescriptorPool; static KarmaGui_ImplVulkanH_Window m_VulkanWindowData; + + static uint32_t m_SMCounter; static bool m_SwapChainRebuild; }; diff --git a/Karma/src/Karma/KarmaGui/KarmaGuizmo.cpp b/Karma/src/Karma/KarmaGui/KarmaGuizmo.cpp new file mode 100644 index 00000000..44c29efa --- /dev/null +++ b/Karma/src/Karma/KarmaGui/KarmaGuizmo.cpp @@ -0,0 +1,3141 @@ +// https://github.com/CedricGuillemet/KarmaGuizmo +// v1.92.5 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2016-2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "KarmaGui.h" +#include "KarmaGuiInternal.h" + +#include "KarmaGuizmo.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif +#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) +#define _malloca(x) alloca(x) +#define _freea(x) +#endif + +// includes patches for multiview from +// https://github.com/CedricGuillemet/KarmaGuizmo/issues/15 + +namespace KARMAGUIZMO_NAMESPACE +{ + static const float ZPI = 3.14159265358979323846f; + static const float RAD2DEG = (180.f / ZPI); + static const float DEG2RAD = (ZPI / 180.f); + const float screenRotateSize = 0.06f; + // scale a bit so translate axis do not touch when in universal + const float rotationDisplayFactor = 1.2f; + + static OPERATION operator&(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + static bool operator!=(OPERATION lhs, int rhs) + { + return static_cast(lhs) != rhs; + } + + static bool Intersects(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) != 0; + } + + // True if lhs contains rhs + static bool Contains(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) == rhs; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // utility and math + + void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r) + { + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + } + + void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16) + { + float temp, temp2, temp3, temp4; + temp = 2.0f * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + m16[0] = temp / temp2; + m16[1] = 0.0; + m16[2] = 0.0; + m16[3] = 0.0; + m16[4] = 0.0; + m16[5] = temp / temp3; + m16[6] = 0.0; + m16[7] = 0.0; + m16[8] = (right + left) / temp2; + m16[9] = (top + bottom) / temp3; + m16[10] = (-zfar - znear) / temp4; + m16[11] = -1.0f; + m16[12] = 0.0; + m16[13] = 0.0; + m16[14] = (-temp * zfar) / temp4; + m16[15] = 0.0; + } + + void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16) + { + float ymax, xmax; + ymax = znear * tanf(fovyInDegrees * DEG2RAD); + xmax = ymax * aspectRatio; + Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16); + } + + void Cross(const float* a, const float* b, float* r) + { + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; + } + + float Dot(const float* a, const float* b) + { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + + void Normalize(const float* a, float* r) + { + float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON); + r[0] = a[0] * il; + r[1] = a[1] * il; + r[2] = a[2] * il; + } + + void LookAt(const float* eye, const float* at, const float* up, float* m16) + { + float X[3], Y[3], Z[3], tmp[3]; + + tmp[0] = eye[0] - at[0]; + tmp[1] = eye[1] - at[1]; + tmp[2] = eye[2] - at[2]; + Normalize(tmp, Z); + Normalize(up, Y); + Cross(Y, Z, tmp); + Normalize(tmp, X); + Cross(Z, X, tmp); + Normalize(tmp, Y); + + m16[0] = X[0]; + m16[1] = Y[0]; + m16[2] = Z[0]; + m16[3] = 0.0f; + m16[4] = X[1]; + m16[5] = Y[1]; + m16[6] = Z[1]; + m16[7] = 0.0f; + m16[8] = X[2]; + m16[9] = Y[2]; + m16[10] = Z[2]; + m16[11] = 0.0f; + m16[12] = -Dot(X, eye); + m16[13] = -Dot(Y, eye); + m16[14] = -Dot(Z, eye); + m16[15] = 1.0f; + } + + template T Clamp(T x, T y, T z) { return ((x < y) ? y : ((x > z) ? z : x)); } + template T max(T x, T y) { return (x > y) ? x : y; } + template T min(T x, T y) { return (x < y) ? x : y; } + template bool IsWithin(T x, T y, T z) { return (x >= y) && (x <= z); } + + struct matrix_t; + struct vec_t + { + public: + float x, y, z, w; + + void Lerp(const vec_t& v, float t) + { + x += (v.x - x) * t; + y += (v.y - y) * t; + z += (v.z - z) * t; + w += (v.w - w) * t; + } + + void Set(float v) { x = y = z = w = v; } + void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { x = _x; y = _y; z = _z; w = _w; } + + vec_t& operator -= (const vec_t& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } + vec_t& operator += (const vec_t& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } + vec_t& operator *= (const vec_t& v) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; } + vec_t& operator *= (float v) { x *= v; y *= v; z *= v; w *= v; return *this; } + + vec_t operator * (float f) const; + vec_t operator - () const; + vec_t operator - (const vec_t& v) const; + vec_t operator + (const vec_t& v) const; + vec_t operator * (const vec_t& v) const; + + const vec_t& operator + () const { return (*this); } + float Length() const { return sqrtf(x * x + y * y + z * z); }; + float LengthSq() const { return (x * x + y * y + z * z); }; + vec_t Normalize() { (*this) *= (1.f / ( Length() > FLT_EPSILON ? Length() : FLT_EPSILON ) ); return (*this); } + vec_t Normalize(const vec_t& v) { this->Set(v.x, v.y, v.z, v.w); this->Normalize(); return (*this); } + vec_t Abs() const; + + void Cross(const vec_t& v) + { + vec_t res; + res.x = y * v.z - z * v.y; + res.y = z * v.x - x * v.z; + res.z = x * v.y - y * v.x; + + x = res.x; + y = res.y; + z = res.z; + w = 0.f; + } + + void Cross(const vec_t& v1, const vec_t& v2) + { + x = v1.y * v2.z - v1.z * v2.y; + y = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + w = 0.f; + } + + float Dot(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); + } + + float Dot3(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z); + } + + void Transform(const matrix_t& matrix); + void Transform(const vec_t& s, const matrix_t& matrix); + + void TransformVector(const matrix_t& matrix); + void TransformPoint(const matrix_t& matrix); + void TransformVector(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformVector(matrix); } + void TransformPoint(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformPoint(matrix); } + + float& operator [] (size_t index) { return ((float*)&x)[index]; } + const float& operator [] (size_t index) const { return ((float*)&x)[index]; } + bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)) != 0; } + }; + + vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { vec_t res; res.x = _x; res.y = _y; res.z = _z; res.w = _w; return res; } + vec_t makeVect(KGVec2 v) { vec_t res; res.x = v.x; res.y = v.y; res.z = 0.f; res.w = 0.f; return res; } + vec_t vec_t::operator * (float f) const { return makeVect(x * f, y * f, z * f, w * f); } + vec_t vec_t::operator - () const { return makeVect(-x, -y, -z, -w); } + vec_t vec_t::operator - (const vec_t& v) const { return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); } + vec_t vec_t::operator + (const vec_t& v) const { return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); } + vec_t vec_t::operator * (const vec_t& v) const { return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); } + vec_t vec_t::Abs() const { return makeVect(fabsf(x), fabsf(y), fabsf(z)); } + + vec_t Normalized(const vec_t& v) { vec_t res; res = v; res.Normalize(); return res; } + vec_t Cross(const vec_t& v1, const vec_t& v2) + { + vec_t res; + res.x = v1.y * v2.z - v1.z * v2.y; + res.y = v1.z * v2.x - v1.x * v2.z; + res.z = v1.x * v2.y - v1.y * v2.x; + res.w = 0.f; + return res; + } + + float Dot(const vec_t& v1, const vec_t& v2) + { + return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); + } + + vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal) + { + vec_t normal, res; + normal.Normalize(p_normal); + res.w = normal.Dot(p_point1); + res.x = normal.x; + res.y = normal.y; + res.z = normal.z; + return res; + } + + struct matrix_t + { + public: + + union + { + float m[4][4]; + float m16[16]; + struct + { + vec_t right, up, dir, position; + } v; + vec_t component[4]; + }; + + operator float* () { return m16; } + operator const float* () const { return m16; } + void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } + + void Translation(const vec_t& vt) + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(vt.x, vt.y, vt.z, 1.f); + } + + void Scale(float _x, float _y, float _z) + { + v.right.Set(_x, 0.f, 0.f, 0.f); + v.up.Set(0.f, _y, 0.f, 0.f); + v.dir.Set(0.f, 0.f, _z, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } + + matrix_t& operator *= (const matrix_t& mat) + { + matrix_t tmpMat; + tmpMat = *this; + tmpMat.Multiply(mat); + *this = tmpMat; + return *this; + } + matrix_t operator * (const matrix_t& mat) const + { + matrix_t matT; + matT.Multiply(*this, mat); + return matT; + } + + void Multiply(const matrix_t& matrix) + { + matrix_t tmp; + tmp = *this; + + FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); + } + + void Multiply(const matrix_t& m1, const matrix_t& m2) + { + FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); + } + + float GetDeterminant() const + { + return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - + m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; + } + + float Inverse(const matrix_t& srcMatrix, bool affine = false); + void SetToIdentity() + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Transpose() + { + matrix_t tmpm; + for (int l = 0; l < 4; l++) + { + for (int c = 0; c < 4; c++) + { + tmpm.m[l][c] = m[c][l]; + } + } + (*this) = tmpm; + } + + void RotationAxis(const vec_t& axis, float angle); + + void OrthoNormalize() + { + v.right.Normalize(); + v.up.Normalize(); + v.dir.Normalize(); + } + }; + + void vec_t::Transform(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::Transform(const vec_t& s, const matrix_t& matrix) + { + *this = s; + Transform(matrix); + } + + void vec_t::TransformPoint(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::TransformVector(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine) + { + float det = 0; + + if (affine) + { + det = GetDeterminant(); + float s = 1 / det; + m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; + m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; + m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; + m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; + m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; + m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; + m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; + m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; + m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; + m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); + m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); + m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); + } + else + { + // transpose matrix + float src[16]; + for (int i = 0; i < 4; ++i) + { + src[i] = srcMatrix.m16[i * 4]; + src[i + 4] = srcMatrix.m16[i * 4 + 1]; + src[i + 8] = srcMatrix.m16[i * 4 + 2]; + src[i + 12] = srcMatrix.m16[i * 4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + float tmp[12]; // temp array for pairs + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); + m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); + m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); + m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); + m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); + m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); + m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); + m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + // calculate second 8 elements (cofactors) + m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); + m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); + m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); + m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); + m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); + m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); + m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); + m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); + + // calculate determinant + det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; + + // calculate matrix inverse + float invdet = 1 / det; + for (int j = 0; j < 16; ++j) + { + m16[j] *= invdet; + } + } + + return det; + } + + void matrix_t::RotationAxis(const vec_t& axis, float angle) + { + float length2 = axis.LengthSq(); + if (length2 < FLT_EPSILON) + { + SetToIdentity(); + return; + } + + vec_t n = axis * (1.f / sqrtf(length2)); + float s = sinf(angle); + float c = cosf(angle); + float k = 1.f - c; + + float xx = n.x * n.x * k + c; + float yy = n.y * n.y * k + c; + float zz = n.z * n.z * k + c; + float xy = n.x * n.y * k; + float yz = n.y * n.z * k; + float zx = n.z * n.x * k; + float xs = n.x * s; + float ys = n.y * s; + float zs = n.z * s; + + m[0][0] = xx; + m[0][1] = xy + zs; + m[0][2] = zx - ys; + m[0][3] = 0.f; + m[1][0] = xy - zs; + m[1][1] = yy; + m[1][2] = yz + xs; + m[1][3] = 0.f; + m[2][0] = zx + ys; + m[2][1] = yz - xs; + m[2][2] = zz; + m[2][3] = 0.f; + m[3][0] = 0.f; + m[3][1] = 0.f; + m[3][2] = 0.f; + m[3][3] = 1.f; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + enum MOVETYPE + { + MT_NONE, + MT_MOVE_X, + MT_MOVE_Y, + MT_MOVE_Z, + MT_MOVE_YZ, + MT_MOVE_ZX, + MT_MOVE_XY, + MT_MOVE_SCREEN, + MT_ROTATE_X, + MT_ROTATE_Y, + MT_ROTATE_Z, + MT_ROTATE_SCREEN, + MT_SCALE_X, + MT_SCALE_Y, + MT_SCALE_Z, + MT_SCALE_XYZ + }; + + static bool IsTranslateType(int type) + { + return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN; + } + + static bool IsRotateType(int type) + { + return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN; + } + + static bool IsScaleType(int type) + { + return type >= MT_SCALE_X && type <= MT_SCALE_XYZ; + } + + // Matches MT_MOVE_AB order + static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y }; + + Style::Style() + { + // default values + TranslationLineThickness = 3.0f; + TranslationLineArrowSize = 6.0f; + RotationLineThickness = 2.0f; + RotationOuterLineThickness = 3.0f; + ScaleLineThickness = 3.0f; + ScaleLineCircleSize = 6.0f; + HatchedAxisLineThickness = 6.0f; + CenterCircleSize = 6.0f; + + // initialize default colors + Colors[DIRECTION_X] = KGVec4(0.666f, 0.000f, 0.000f, 1.000f); + Colors[DIRECTION_Y] = KGVec4(0.000f, 0.666f, 0.000f, 1.000f); + Colors[DIRECTION_Z] = KGVec4(0.000f, 0.000f, 0.666f, 1.000f); + Colors[PLANE_X] = KGVec4(0.666f, 0.000f, 0.000f, 0.380f); + Colors[PLANE_Y] = KGVec4(0.000f, 0.666f, 0.000f, 0.380f); + Colors[PLANE_Z] = KGVec4(0.000f, 0.000f, 0.666f, 0.380f); + Colors[SELECTION] = KGVec4(1.000f, 0.500f, 0.062f, 0.541f); + Colors[INACTIVE] = KGVec4(0.600f, 0.600f, 0.600f, 0.600f); + Colors[TRANSLATION_LINE] = KGVec4(0.666f, 0.666f, 0.666f, 0.666f); + Colors[SCALE_LINE] = KGVec4(0.250f, 0.250f, 0.250f, 1.000f); + Colors[ROTATION_USING_BORDER] = KGVec4(1.000f, 0.500f, 0.062f, 1.000f); + Colors[ROTATION_USING_FILL] = KGVec4(1.000f, 0.500f, 0.062f, 0.500f); + Colors[HATCHED_AXIS_LINES] = KGVec4(0.000f, 0.000f, 0.000f, 0.500f); + Colors[TEXT] = KGVec4(1.000f, 1.000f, 1.000f, 1.000f); + Colors[TEXT_SHADOW] = KGVec4(0.000f, 0.000f, 0.000f, 1.000f); + } + + struct Context + { + Context() : mbUsing(false), mbUsingViewManipulate(false), mbEnable(true), mIsViewManipulatorHovered(false), mbUsingBounds(false) + { + } + + KGDrawList* mDrawList; + Style mStyle; + + MODE mMode; + matrix_t mViewMat; + matrix_t mProjectionMat; + matrix_t mModel; + matrix_t mModelLocal; // orthonormalized model + matrix_t mModelInverse; + matrix_t mModelSource; + matrix_t mModelSourceInverse; + matrix_t mMVP; + matrix_t mMVPLocal; // MVP with full model matrix whereas mMVP's model matrix might only be translation in case of World space edition + matrix_t mViewProjection; + + vec_t mModelScaleOrigin; + vec_t mCameraEye; + vec_t mCameraRight; + vec_t mCameraDir; + vec_t mCameraUp; + vec_t mRayOrigin; + vec_t mRayVector; + + float mRadiusSquareCenter; + KGVec2 mScreenSquareCenter; + KGVec2 mScreenSquareMin; + KGVec2 mScreenSquareMax; + + float mScreenFactor; + vec_t mRelativeOrigin; + + bool mbUsing; + bool mbUsingViewManipulate; + bool mbEnable; + bool mbMouseOver; + bool mReversed; // reversed projection matrix + bool mIsViewManipulatorHovered; + + // translation + vec_t mTranslationPlan; + vec_t mTranslationPlanOrigin; + vec_t mMatrixOrigin; + vec_t mTranslationLastDelta; + + // rotation + vec_t mRotationVectorSource; + float mRotationAngle; + float mRotationAngleOrigin; + //vec_t mWorldToLocalAxis; + + // scale + vec_t mScale; + vec_t mScaleValueOrigin; + vec_t mScaleLast; + float mSaveMousePosx; + + // save axis factor when using gizmo + bool mBelowAxisLimit[3]; + int mAxisMask = 0; + bool mBelowPlaneLimit[3]; + float mAxisFactor[3]; + + float mAxisLimit=0.0025f; + float mPlaneLimit=0.02f; + + // bounds stretching + vec_t mBoundsPivot; + vec_t mBoundsAnchor; + vec_t mBoundsPlan; + vec_t mBoundsLocalPivot; + int mBoundsBestAxis; + int mBoundsAxis[2]; + bool mbUsingBounds; + matrix_t mBoundsMatrix; + + // + int mCurrentOperation; + + float mX = 0.f; + float mY = 0.f; + float mWidth = 0.f; + float mHeight = 0.f; + float mXMax = 0.f; + float mYMax = 0.f; + float mDisplayRatio = 1.f; + + bool mIsOrthographic = false; + // check to not have multiple gizmo highlighted at the same time + bool mbOverGizmoHotspot = false; + + KGGuiWindow* mAlternativeWindow = nullptr; + KGVector mIDStack; + KGGuiID mEditingID = -1; + OPERATION mOperation = OPERATION(-1); + + bool mAllowAxisFlip = true; + float mGizmoSizeClipSpace = 0.1f; + + inline KGGuiID GetCurrentID() + { + if (mIDStack.empty()) + { + mIDStack.push_back(-1); + } + return mIDStack.back(); + } + }; + + static Context gContext; + + static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; + static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", + "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f", + "X : %5.3f Y : %5.3f Z : %5.3f" }; + static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; + static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; + static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 1,2,0, 0,2,0, 0,1,0, 0,1,2 }; + static const float quadMin = 0.5f; + static const float quadMax = 0.8f; + static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; + static const int halfCircleSegmentCount = 64; + static const float snapTension = 0.5f; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion); + static int GetRotateType(OPERATION op); + static int GetScaleType(OPERATION op); + + Style& GetStyle() + { + return gContext.mStyle; + } + + static KGU32 GetColorU32(int idx) + { + KR_CORE_ASSERT(idx < COLOR::COUNT, "Non color index maybe"); + return Karma::KarmaGui::ColorConvertFloat4ToU32(gContext.mStyle.Colors[idx]); + } + + static KGVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, KGVec2 position = KGVec2(gContext.mX, gContext.mY), KGVec2 size = KGVec2(gContext.mWidth, gContext.mHeight)) + { + vec_t trans; + trans.TransformPoint(worldPos, mat); + trans *= 0.5f / trans.w; + trans += makeVect(0.5f, 0.5f); + trans.y = 1.f - trans.y; + trans.x *= size.x; + trans.y *= size.y; + trans.x += position.x; + trans.y += position.y; + return KGVec2(trans.x, trans.y); + } + + static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, KGVec2 position = KGVec2(gContext.mX, gContext.mY), KGVec2 size = KGVec2(gContext.mWidth, gContext.mHeight)) + { + KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + + matrix_t mViewProjInverse; + mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); + + const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f; + const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f; + + const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f; + const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON); + + rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse); + rayOrigin *= 1.f / rayOrigin.w; + vec_t rayEnd; + rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse); + rayEnd *= 1.f / rayEnd.w; + rayDir = Normalized(rayEnd - rayOrigin); + } + + static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end, const bool localCoordinates = false) + { + vec_t startOfSegment = start; + const matrix_t& mvp = localCoordinates ? gContext.mMVPLocal : gContext.mMVP; + startOfSegment.TransformPoint(mvp); + if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + startOfSegment *= 1.f / startOfSegment.w; + } + + vec_t endOfSegment = end; + endOfSegment.TransformPoint(mvp); + if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + endOfSegment *= 1.f / endOfSegment.w; + } + + vec_t clipSpaceAxis = endOfSegment - startOfSegment; + if (gContext.mDisplayRatio < 1.0) + clipSpaceAxis.x *= gContext.mDisplayRatio; + else + clipSpaceAxis.y /= gContext.mDisplayRatio; + float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y); + return segmentLengthInClipSpace; + } + + static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB) + { + vec_t pts[] = { ptO, ptA, ptB }; + for (unsigned int i = 0; i < 3; i++) + { + pts[i].TransformPoint(gContext.mMVP); + if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction + { + pts[i] *= 1.f / pts[i].w; + } + } + vec_t segA = pts[1] - pts[0]; + vec_t segB = pts[2] - pts[0]; + segA.y /= gContext.mDisplayRatio; + segB.y /= gContext.mDisplayRatio; + vec_t segAOrtho = makeVect(-segA.y, segA.x); + segAOrtho.Normalize(); + float dt = segAOrtho.Dot3(segB); + float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt); + return surface; + } + + inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2) + { + vec_t c = point - vertPos1; + vec_t V; + + V.Normalize(vertPos2 - vertPos1); + float d = (vertPos2 - vertPos1).Length(); + float t = V.Dot3(c); + + if (t < 0.f) + { + return vertPos1; + } + + if (t > d) + { + return vertPos2; + } + + return vertPos1 + V * t; + } + + static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan) + { + const float numer = plan.Dot3(rOrigin) - plan.w; + const float denom = plan.Dot3(rVector); + + if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect + { + return -1.0f; + } + + return -(numer / denom); + } + + static float DistanceToPlane(const vec_t& point, const vec_t& plan) + { + return plan.Dot3(point) + plan.w; + } + + static bool IsInContextRect(KGVec2 p) + { + return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax); + } + + static bool IsHoveringWindow() + { + KarmaGuiContext& g = *Karma::KarmaGui::GetCurrentContext(); + KGGuiWindow* window = Karma::KarmaGuiInternal::FindWindowByName(gContext.mDrawList->_OwnerName); + if (g.HoveredWindow == window) // Mouse hovering drawlist window + return true; + if (gContext.mAlternativeWindow != nullptr && g.HoveredWindow == gContext.mAlternativeWindow) + return true; + if (g.HoveredWindow != NULL) // Any other window is hovered + return false; + if (Karma::KarmaGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max, false)) // Hovering drawlist window rect, while no other window is hovered (for _NoInputs windows) + return true; + return false; + } + + void SetRect(float x, float y, float width, float height) + { + gContext.mX = x; + gContext.mY = y; + gContext.mWidth = width; + gContext.mHeight = height; + gContext.mXMax = gContext.mX + gContext.mWidth; + gContext.mYMax = gContext.mY + gContext.mXMax; + gContext.mDisplayRatio = width / height; + } + + void SetOrthographic(bool isOrthographic) + { + gContext.mIsOrthographic = isOrthographic; + } + + void SetDrawlist(KGDrawList* drawlist) + { + gContext.mDrawList = drawlist ? drawlist : Karma::KarmaGui::GetWindowDrawList(); + } + + void SetKarmaGuiContext(KarmaGuiContext* ctx) + { + Karma::KarmaGui::SetCurrentContext(ctx); + } + + void BeginFrame() + { + const KGU32 flags = KGGuiWindowFlags_NoTitleBar | KGGuiWindowFlags_NoResize | KGGuiWindowFlags_NoScrollbar | KGGuiWindowFlags_NoInputs | KGGuiWindowFlags_NoSavedSettings | KGGuiWindowFlags_NoFocusOnAppearing | KGGuiWindowFlags_NoBringToFrontOnFocus; + +#ifdef KGGUI_HAS_VIEWPORT + KarmaGui::SetNextWindowSize(KarmaGui::GetMainViewport()->Size); + KarmaGui::SetNextWindowPos(KarmaGui::GetMainViewport()->Pos); +#else + KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + Karma::KarmaGui::SetNextWindowSize(io.DisplaySize); + Karma::KarmaGui::SetNextWindowPos(KGVec2(0, 0)); +#endif + + Karma::KarmaGui::PushStyleColor(KGGuiCol_WindowBg, 0); + Karma::KarmaGui::PushStyleColor(KGGuiCol_Border, 0); + Karma::KarmaGui::PushStyleVar(KGGuiStyleVar_WindowRounding, 0.0f); + + Karma::KarmaGui::Begin("gizmo", NULL, flags); + gContext.mDrawList = Karma::KarmaGui::GetWindowDrawList(); + gContext.mbOverGizmoHotspot = false; + Karma::KarmaGui::End(); + Karma::KarmaGui::PopStyleVar(); + Karma::KarmaGui::PopStyleColor(2); + } + + bool IsUsing() + { + return (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) || gContext.mbUsingBounds; + } + + bool IsUsingViewManipulate() + { + return gContext.mbUsingViewManipulate; + } + + bool IsViewManipulateHovered() + { + return gContext.mIsViewManipulatorHovered; + } + + bool IsUsingAny() + { + return gContext.mbUsing || gContext.mbUsingBounds; + } + + bool IsOver() + { + return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) || + (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) || + (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing(); + } + + bool IsOver(OPERATION op) + { + if(IsUsing()) + { + return true; + } + if(Intersects(op, SCALE) && GetScaleType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE) + { + return true; + } + return false; + } + + void Enable(bool enable) + { + gContext.mbEnable = enable; + if (!enable) + { + gContext.mbUsing = false; + gContext.mbUsingBounds = false; + } + } + + static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode) + { + gContext.mMode = mode; + gContext.mViewMat = *(matrix_t*)view; + gContext.mProjectionMat = *(matrix_t*)projection; + gContext.mbMouseOver = IsHoveringWindow(); + + gContext.mModelLocal = *(matrix_t*)matrix; + gContext.mModelLocal.OrthoNormalize(); + + if (mode == LOCAL) + { + gContext.mModel = gContext.mModelLocal; + } + else + { + gContext.mModel.Translation(((matrix_t*)matrix)->v.position); + } + gContext.mModelSource = *(matrix_t*)matrix; + gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + + gContext.mModelInverse.Inverse(gContext.mModel); + gContext.mModelSourceInverse.Inverse(gContext.mModelSource); + gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; + gContext.mMVP = gContext.mModel * gContext.mViewProjection; + gContext.mMVPLocal = gContext.mModelLocal * gContext.mViewProjection; + + matrix_t viewInverse; + viewInverse.Inverse(gContext.mViewMat); + gContext.mCameraDir = viewInverse.v.dir; + gContext.mCameraEye = viewInverse.v.position; + gContext.mCameraRight = viewInverse.v.right; + gContext.mCameraUp = viewInverse.v.up; + + // projection reverse + vec_t nearPos, farPos; + nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); + farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); + + gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w); + + // compute scale from the size of camera right vector projected on screen at the matrix position + vec_t pointRight = viewInverse.v.right; + pointRight.TransformPoint(gContext.mViewProjection); + + vec_t rightViewInverse = viewInverse.v.right; + rightViewInverse.TransformVector(gContext.mModelInverse); + float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + + KGVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); + gContext.mScreenSquareCenter = centerSSpace; + gContext.mScreenSquareMin = KGVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); + gContext.mScreenSquareMax = KGVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); + + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); + } + + static void ComputeColors(KGU32* colors, int type, OPERATION operation) + { + if (gContext.mbEnable) + { + KGU32 selectionColor = GetColorU32(SELECTION); + + switch (operation) + { + case TRANSLATE: + colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : KG_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : GetColorU32(PLANE_X + i); + colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4]; + } + break; + case ROTATE: + colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : KG_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + case SCALEU: + case SCALE: + colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : KG_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + // note: this internal function is only called with three possible values for operation + default: + break; + } + } + else + { + KGU32 inactiveColor = GetColorU32(INACTIVE); + for (int i = 0; i < 7; i++) + { + colors[i] = inactiveColor; + } + } + } + + static void ComputeTripodAxisAndVisibility(const int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit, const bool localCoordinates = false) + { + dirAxis = directionUnary[axisIndex]; + dirPlaneX = directionUnary[(axisIndex + 1) % 3]; + dirPlaneY = directionUnary[(axisIndex + 2) % 3]; + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) + { + // when using, use stored factors so the gizmo doesn't flip when we translate + + // Apply axis mask to axes and planes + belowAxisLimit = gContext.mBelowAxisLimit[axisIndex] && ((1< FLT_EPSILON) ? -1.f : 1.f; + float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX&& fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY&& fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f; + dirAxis *= mulAxis; + dirPlaneX *= mulAxisX; + dirPlaneY *= mulAxisY; + + // for axis + float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor, localCoordinates); + + float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor); + // Apply axis mask to axes and planes + belowPlaneLimit = (paraSurf > gContext.mAxisLimit) && (((1< gContext.mPlaneLimit) && !((1< (1.f - snapTension)) + { + *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f); + } + } + static void ComputeSnap(vec_t& value, const float* snap) + { + for (int i = 0; i < 3; i++) + { + ComputeSnap(&value[i], snap[i]); + } + } + + static float ComputeAngleOnPlan() + { + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); + + vec_t perpendicularVector; + perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); + perpendicularVector.Normalize(); + float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f); + float angle = acosf(acosAngle); + angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; + return angle; + } + + static void DrawRotationGizmo(OPERATION op, int type) + { + if(!Intersects(op, ROTATE)) + { + return; + } + KGDrawList* drawList = gContext.mDrawList; + + bool isMultipleAxesMasked = (gContext.mAxisMask & (gContext.mAxisMask - 1)) != 0; + bool isNoAxesMasked = !gContext.mAxisMask; + + // colors + KGU32 colors[7]; + ComputeColors(colors, type, ROTATE); + + vec_t viewDirNormalized; + if (gContext.mIsOrthographic) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat); + viewDirNormalized = -viewInverse.v.dir; + } + else + { + viewDirNormalized = Normalized(gContext.mCameraDir); + } + + viewDirNormalized.TransformVector(gContext.mModelInverse); + + gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; + + bool hasRSC = Intersects(op, ROTATE_SCREEN); + for (int axis = 0; axis < 3; axis++) + { + if(!Intersects(op, static_cast(ROTATE_Z >> axis))) + { + continue; + } + + bool isAxisMasked = ((1 << (2 - axis)) & gContext.mAxisMask) != 0; + + if ((!isAxisMasked || isMultipleAxesMasked) && !isNoAxesMasked) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_ROTATE_Z - axis); + const int circleMul = (hasRSC && !usingAxis) ? 1 : 2; + + KGVec2* circlePos = (KGVec2*)alloca(sizeof(KGVec2) * (circleMul * halfCircleSegmentCount + 1)); + + float angleStart = atan2f(viewDirNormalized[(4 - axis) % 3], viewDirNormalized[(3 - axis) % 3]) + (gContext.mIsOrthographic ? ZPI : -ZPI) * 0.5f; + + for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++) + { + float ng = angleStart + (float)circleMul * ZPI * ((float)i / (float)(circleMul * halfCircleSegmentCount)); + vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); + vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos, gContext.mMVP); + } + if (!gContext.mbUsing || usingAxis) + { + drawList->AddPolyline(circlePos, circleMul* halfCircleSegmentCount + 1, colors[3 - axis], false, gContext.mStyle.RotationLineThickness); + } + + float radiusAxis = sqrtf((KGLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]))); + if (radiusAxis > gContext.mRadiusSquareCenter) + { + gContext.mRadiusSquareCenter = radiusAxis; + } + } + if(hasRSC && (!gContext.mbUsing || type == MT_ROTATE_SCREEN) && (!isMultipleAxesMasked && isNoAxesMasked)) + { + drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, gContext.mStyle.RotationOuterLineThickness); + } + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsRotateType(type)) + { + KGVec2 circlePos[halfCircleSegmentCount + 1]; + + circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + for (unsigned int i = 1; i < halfCircleSegmentCount + 1; i++) + { + float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1)); + matrix_t rotateVectorMatrix; + rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); + vec_t pos; + pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); + pos *= gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); + } + drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_FILL)); + drawList->AddPolyline(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_BORDER), true, gContext.mStyle.RotationLineThickness); + + KGVec2 destinationPosOnScreen = circlePos[1]; + char tmps[512]; + KGFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle); + drawList->AddText(KGVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(KGVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawHatchedAxis(const vec_t& axis) + { + if (gContext.mStyle.HatchedAxisLineThickness <= 0.0f) + { + return; + } + + for (int j = 1; j < 10; j++) + { + KGVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); + KGVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); + gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, GetColorU32(HATCHED_AXIS_LINES), gContext.mStyle.HatchedAxisLineThickness); + } + } + + static void DrawScaleGizmo(OPERATION op, int type) + { + KGDrawList* drawList = gContext.mDrawList; + + if(!Intersects(op, SCALE)) + { + return; + } + + // colors + KGU32 colors[7]; + ComputeColors(colors, type, SCALE); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + KGVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + KGVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + KGVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) + { + KGU32 scaleLineColor = GetColorU32(SCALE_LINE); + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, scaleLineColor, gContext.mStyle.ScaleLineThickness); + drawList->AddCircleFilled(worldDirSSpaceNoScale, gContext.mStyle.ScaleLineCircleSize, scaleLineColor); + } + + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.ScaleLineThickness); + } + drawList->AddCircleFilled(worldDirSSpace, gContext.mStyle.ScaleLineCircleSize, colors[i + 1]); + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis * scaleDisplay[i]); + } + } + } + } + + // draw screen cirle + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsScaleType(type)) + { + //KGVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + KGVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(KGVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), KGVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + KGFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(KGVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(KGVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + + static void DrawScaleUniveralGizmo(OPERATION op, int type) + { + KGDrawList* drawList = gContext.mDrawList; + + if (!Intersects(op, SCALEU)) + { + return; + } + + // colors + KGU32 colors[7]; + ComputeColors(colors, type, SCALEU); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //KGVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //KGVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + KGVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVPLocal); + +#if 0 + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID)) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, KG_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, KG_COL32(0x40, 0x40, 0x40, 0xFF)); + } + /* + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + } + */ +#endif + drawList->AddCircleFilled(worldDirSSpace, 12.f, colors[i + 1]); + } + } + } + + // draw screen cirle + drawList->AddCircle(gContext.mScreenSquareCenter, 20.f, colors[0], 32, gContext.mStyle.CenterCircleSize); + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsScaleType(type)) + { + //KGVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + KGVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(KGVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), KGVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + KGFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(KGVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(KGVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawTranslationGizmo(OPERATION op, int type) + { + KGDrawList* drawList = gContext.mDrawList; + if (!drawList) + { + return; + } + + if(!Intersects(op, TRANSLATE)) + { + return; + } + + // colors + KGU32 colors[7]; + ComputeColors(colors, type, TRANSLATE); + + const KGVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + + // draw + bool belowAxisLimit = false; + bool belowPlaneLimit = false; + for (int i = 0; i < 3; ++i) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_X + i)) + { + // draw axis + if (belowAxisLimit && Intersects(op, static_cast(TRANSLATE_X << i))) + { + KGVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + KGVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP); + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.TranslationLineThickness); + + // Arrow head begin + KGVec2 dir(origin - worldDirSSpace); + + float d = sqrtf(KGLengthSqr(dir)); + dir /= d; // Normalize + dir *= gContext.mStyle.TranslationLineArrowSize; + + KGVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + KGVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); + // Arrow head end + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis); + } + } + } + // draw plane + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_YZ + i)) + { + if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i])) + { + KGVec2 screenQuadPts[4]; + for (int j = 0; j < 4; ++j) + { + vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; + screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); + } + drawList->AddPolyline(screenQuadPts, 4, GetColorU32(DIRECTION_X + i), true, 1.0f); + drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); + } + } + } + + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsTranslateType(type)) + { + KGU32 translationLineColor = GetColorU32(TRANSLATION_LINE); + + KGVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + KGVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(KGVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), KGVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + + char tmps[512]; + vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_MOVE_X) * 3; + KGFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); + drawList->AddText(KGVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(KGVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static bool CanActivate() + { + if (Karma::KarmaGui::IsMouseClicked(0) && !Karma::KarmaGui::IsAnyItemHovered() && !Karma::KarmaGui::IsAnyItemActive()) + { + return true; + } + return false; + } + + static bool HandleAndDrawLocalBounds(const float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation) + { + KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + KGDrawList* drawList = gContext.mDrawList; + + bool manipulated = false; + + // compute best projection axis + vec_t axesWorldDirections[3]; + vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; + int axes[3]; + unsigned int numAxes = 1; + axes[0] = gContext.mBoundsBestAxis; + int bestAxis = axes[0]; + if (!gContext.mbUsingBounds) + { + numAxes = 0; + float bestDot = 0.f; + for (int i = 0; i < 3; i++) + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); + dirPlaneNormalWorld.Normalize(); + + float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld)); + if (dt >= bestDot) + { + bestDot = dt; + bestAxis = i; + bestAxisWorldDirection = dirPlaneNormalWorld; + } + + if (dt >= 0.1f) + { + axes[numAxes] = i; + axesWorldDirections[numAxes] = dirPlaneNormalWorld; + ++numAxes; + } + } + } + + if (numAxes == 0) + { + axes[0] = bestAxis; + axesWorldDirections[0] = bestAxisWorldDirection; + numAxes = 1; + } + + else if (bestAxis != axes[0]) + { + unsigned int bestIndex = 0; + for (unsigned int i = 0; i < numAxes; i++) + { + if (axes[i] == bestAxis) + { + bestIndex = i; + break; + } + } + int tempAxis = axes[0]; + axes[0] = axes[bestIndex]; + axes[bestIndex] = tempAxis; + vec_t tempDirection = axesWorldDirections[0]; + axesWorldDirections[0] = axesWorldDirections[bestIndex]; + axesWorldDirections[bestIndex] = tempDirection; + } + + for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) + { + bestAxis = axes[axisIndex]; + bestAxisWorldDirection = axesWorldDirections[axisIndex]; + + // corners + vec_t aabb[4]; + + int secondAxis = (bestAxis + 1) % 3; + int thirdAxis = (bestAxis + 2) % 3; + + for (int i = 0; i < 4; i++) + { + aabb[i][3] = aabb[i][bestAxis] = 0.f; + aabb[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + } + + // draw bounds + unsigned int anchorAlpha = gContext.mbEnable ? KG_COL32_BLACK : KG_COL32(0, 0, 0, 0x80); + + matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; + for (int i = 0; i < 4; i++) + { + KGVec2 worldBound1 = worldToPos(aabb[i], boundsMVP); + KGVec2 worldBound2 = worldToPos(aabb[(i + 1) % 4], boundsMVP); + if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2)) + { + continue; + } + float boundDistance = sqrtf(KGLengthSqr(worldBound1 - worldBound2)); + int stepCount = (int)(boundDistance / 10.f); + stepCount = min(stepCount, 1000); + for (int j = 0; j < stepCount; j++) + { + float stepLength = 1.f / (float)stepCount; + float t1 = (float)j * stepLength; + float t2 = (float)j * stepLength + stepLength * 0.5f; + KGVec2 worldBoundSS1 = KGLerp(worldBound1, worldBound2, KGVec2(t1, t1)); + KGVec2 worldBoundSS2 = KGLerp(worldBound1, worldBound2, KGVec2(t2, t2)); + //drawList->AddLine(worldBoundSS1, worldBoundSS2, KG_COL32(0, 0, 0, 0) + anchorAlpha, 3.f); + drawList->AddLine(worldBoundSS1, worldBoundSS2, KG_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f); + } + vec_t midPoint = (aabb[i] + aabb[(i + 1) % 4]) * 0.5f; + KGVec2 midBound = worldToPos(midPoint, boundsMVP); + static const float AnchorBigRadius = 8.f; + static const float AnchorSmallRadius = 6.f; + bool overBigAnchor = KGLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + bool overSmallAnchor = KGLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + + int type = MT_NONE; + vec_t gizmoHitProportion; + + if(Intersects(operation, TRANSLATE)) + { + type = GetMoveType(operation, &gizmoHitProportion); + } + if(Intersects(operation, ROTATE) && type == MT_NONE) + { + type = GetRotateType(operation); + } + if(Intersects(operation, SCALE) && type == MT_NONE) + { + type = GetScaleType(operation); + } + + if (type != MT_NONE) + { + overBigAnchor = false; + overSmallAnchor = false; + } + + KGU32 selectionColor = GetColorU32(SELECTION); + + unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (KG_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (KG_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + + drawList->AddCircleFilled(worldBound1, AnchorBigRadius, KG_COL32_BLACK); + drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor); + + drawList->AddCircleFilled(midBound, AnchorSmallRadius, KG_COL32_BLACK); + drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor); + int oppositeIndex = (i + 2) % 4; + // big anchor on corners + if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) + { + gContext.mBoundsPivot.TransformPoint(aabb[(i + 2) % 4], gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(aabb[i], gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + gContext.mBoundsAxis[0] = secondAxis; + gContext.mBoundsAxis[1] = thirdAxis; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis]; + gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis]; + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.GetCurrentID(); + gContext.mBoundsMatrix = gContext.mModelSource; + } + // small anchor on middle of segment + if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) + { + vec_t midPointOpposite = (aabb[(i + 2) % 4] + aabb[(i + 3) % 4]) * 0.5f; + gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + int indices[] = { secondAxis , thirdAxis }; + gContext.mBoundsAxis[0] = indices[i % 2]; + gContext.mBoundsAxis[1] = -1; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.GetCurrentID(); + gContext.mBoundsMatrix = gContext.mModelSource; + } + } + + if (gContext.mbUsingBounds && (gContext.GetCurrentID() == gContext.mEditingID)) + { + matrix_t scale; + scale.SetToIdentity(); + + // compute projected mouse position on plan + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute a reference and delta vectors base on mouse move + vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); + vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); + + // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length + for (int i = 0; i < 2; i++) + { + int axisIndex1 = gContext.mBoundsAxis[i]; + if (axisIndex1 == -1) + { + continue; + } + + float ratioAxis = 1.f; + vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); + + float dtAxis = axisDir.Dot(referenceVector); + float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1]; + if (dtAxis > FLT_EPSILON) + { + ratioAxis = axisDir.Dot(deltaVector) / dtAxis; + } + + if (snapValues) + { + float length = boundSize * ratioAxis; + ComputeSnap(&length, snapValues[axisIndex1]); + if (boundSize > FLT_EPSILON) + { + ratioAxis = length / boundSize; + } + } + scale.component[axisIndex1] *= ratioAxis; + + if (fabsf(ratioAxis - 1.0f) > FLT_EPSILON) { + manipulated = true; + } + } + + // transform matrix + matrix_t preScale, postScale; + preScale.Translation(-gContext.mBoundsLocalPivot); + postScale.Translation(gContext.mBoundsLocalPivot); + matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix; + *matrix = res; + + // info text + char tmps[512]; + KGVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + KGFormatString(tmps, sizeof(tmps), "X: %.2f Y: %.2f Z: %.2f" + , (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length() + , (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length() + , (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length() + ); + drawList->AddText(KGVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(KGVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + + if (!io.MouseDown[0]) { + gContext.mbUsingBounds = false; + gContext.mEditingID = -1; + } + if (gContext.mbUsingBounds) + { + break; + } + } + + return manipulated; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + static int GetScaleType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, SCALE)) + { + type = MT_SCALE_XYZ; + } + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + bool isAxisMasked = ((1 << i) & gContext.mAxisMask) != 0; + + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + dirAxis.TransformVector(gContext.mModelLocal); + dirPlaneX.TransformVector(gContext.mModelLocal); + dirPlaneY.TransformVector(gContext.mModelLocal); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModelLocal.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float startOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.0f : 0.1f; + const float endOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.4f : 1.0f; + const KGVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection); + const KGVec2 axisStartOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection); + const KGVec2 axisEndOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection); + + vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + + if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size + { + if (!isAxisMasked) + type = MT_SCALE_X + i; + } + } + + // universal + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Contains(op, SCALEU) && dist >= 17.0f && dist < 23.0f) + { + type = MT_SCALE_XYZ; + } + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //KGVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //KGVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + KGVec2 worldDirSSpace = worldToPos((dirAxis * markerScale) * gContext.mScreenFactor, gContext.mMVPLocal); + + float distance = sqrtf(KGLengthSqr(worldDirSSpace - io.MousePos)); + if (distance < 12.f) + { + type = MT_SCALE_X + i; + } + } + } + return type; + } + + static int GetRotateType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + + bool isNoAxesMasked = !gContext.mAxisMask; + bool isMultipleAxesMasked = (gContext.mAxisMask & (gContext.mAxisMask - 1)) != 0; + + KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + int type = MT_NONE; + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 4.0f) && dist < (gContext.mRadiusSquareCenter + 4.0f)) + { + if (!isNoAxesMasked) + return MT_NONE; + type = MT_ROTATE_SCREEN; + } + + const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + + vec_t modelViewPos; + modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat); + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(ROTATE_X << i))) + { + continue; + } + bool isAxisMasked = ((1 << i) & gContext.mAxisMask) != 0; + // pickup plan + vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); + const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t intersectViewPos; + intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat); + + if (KGAbs(modelViewPos.z) - KGAbs(intersectViewPos.z) < -FLT_EPSILON) + { + continue; + } + + const vec_t localPos = intersectWorldPos - gContext.mModel.v.position; + vec_t idealPosOnCircle = Normalized(localPos); + idealPosOnCircle.TransformVector(gContext.mModelInverse); + const KGVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * rotationDisplayFactor * gContext.mScreenFactor, gContext.mMVP); + + //gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, KG_COL32_WHITE); + const KGVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos; + + const float distance = makeVect(distanceOnScreen).Length(); + if (distance < 8.f) // pixel size + { + if ((!isAxisMasked || isMultipleAxesMasked) && !isNoAxesMasked) + break; + type = MT_ROTATE_X + i; + } + } + + return type; + } + + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion) + { + if(!Intersects(op, TRANSLATE) || gContext.mbUsing || !gContext.mbMouseOver) + { + return MT_NONE; + } + + bool isNoAxesMasked = !gContext.mAxisMask; + bool isMultipleAxesMasked = (gContext.mAxisMask & (gContext.mAxisMask - 1)) != 0; + + KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, TRANSLATE)) + { + type = MT_MOVE_SCREEN; + } + + const vec_t screenCoord = makeVect(io.MousePos - KGVec2(gContext.mX, gContext.mY)); + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + bool isAxisMasked = ((1 << i) & gContext.mAxisMask) != 0; + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const KGVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - KGVec2(gContext.mX, gContext.mY); + const KGVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - KGVec2(gContext.mX, gContext.mY); + + vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast(TRANSLATE_X << i))) // pixel size + { + if (isAxisMasked) + break; + type = MT_MOVE_X + i; + } + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i])) + { + if ((!isAxisMasked || isMultipleAxesMasked) && !isNoAxesMasked) + break; + type = MT_MOVE_YZ + i; + } + + if (gizmoHitProportion) + { + *gizmoHitProportion = makeVect(dx, dy, 0.f); + } + } + return type; + } + + static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, TRANSLATE) || type != MT_NONE) + { + return false; + } + const KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + const bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN; + bool modified = false; + + // move + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation)) + { + + Karma::KarmaGui::SetNextFrameWantCaptureMouse(true); + + const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + const float len = fabsf(signedLength); // near plan + const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute delta + const vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z) + { + const int axisIndex = gContext.mCurrentOperation - MT_MOVE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + const float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + } + + // snap + if (snap) + { + vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; + if (applyRotationLocaly) + { + matrix_t modelSourceNormalized = gContext.mModelSource; + modelSourceNormalized.OrthoNormalize(); + matrix_t modelSourceNormalizedInverse; + modelSourceNormalizedInverse.Inverse(modelSourceNormalized); + cumulativeDelta.TransformVector(modelSourceNormalizedInverse); + ComputeSnap(cumulativeDelta, snap); + cumulativeDelta.TransformVector(modelSourceNormalized); + } + else + { + ComputeSnap(cumulativeDelta, snap); + } + delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; + + } + + if (delta != gContext.mTranslationLastDelta) + { + modified = true; + } + gContext.mTranslationLastDelta = delta; + + // compute matrix & delta + matrix_t deltaMatrixTranslation; + deltaMatrixTranslation.Translation(delta); + if (deltaMatrix) + { + memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); + } + + const matrix_t res = gContext.mModelSource * deltaMatrixTranslation; + *(matrix_t*)matrix = res; + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + } + + type = gContext.mCurrentOperation; + } + else + { + // find new possible way to move + vec_t gizmoHitProportion; + type = gContext.mbOverGizmoHotspot ? MT_NONE : GetMoveType(op, &gizmoHitProportion); + gContext.mbOverGizmoHotspot |= type != MT_NONE; + if (type != MT_NONE) + { + Karma::KarmaGui::SetNextFrameWantCaptureMouse(true); + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.GetCurrentID(); + gContext.mCurrentOperation = type; + vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + -gContext.mCameraDir }; + + vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + for (unsigned int i = 0; i < 3; i++) + { + vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); + movePlanNormal[i].Cross(orthoVector); + movePlanNormal[i].Normalize(); + } + // pickup plan + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + } + } + return modified; + } + + static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if((!Intersects(op, SCALE) && !Intersects(op, SCALEU)) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + bool modified = false; + + if (!gContext.mbUsing) + { + // find new possible way to scale + type = gContext.mbOverGizmoHotspot ? MT_NONE : GetScaleType(op); + gContext.mbOverGizmoHotspot |= type != MT_NONE; + + if (type != MT_NONE) + { + Karma::KarmaGui::SetNextFrameWantCaptureMouse(true); + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.GetCurrentID(); + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModelLocal.v.up, gContext.mModelLocal.v.dir, gContext.mModelLocal.v.right, gContext.mModelLocal.v.dir, gContext.mModelLocal.v.up, gContext.mModelLocal.v.right, -gContext.mCameraDir }; + // pickup plan + + gContext.mTranslationPlan = BuildPlan(gContext.mModelLocal.v.position, movePlanNormal[type - MT_SCALE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModelLocal.v.position; + gContext.mScale.Set(1.f, 1.f, 1.f); + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position) * (1.f / gContext.mScreenFactor); + gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + gContext.mSaveMousePosx = io.MousePos.x; + } + } + // scale + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation)) + { + Karma::KarmaGui::SetNextFrameWantCaptureMouse(true); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModelLocal.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_SCALE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModelLocal.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + + vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position; + float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); + + gContext.mScale[axisIndex] = max(ratio, 0.001f); + } + else + { + float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; + gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); + } + + // snap + if (snap) + { + float scaleSnap[] = { snap[0], snap[0], snap[0] }; + ComputeSnap(gContext.mScale, scaleSnap); + } + + // no 0 allowed + for (int i = 0; i < 3; i++) + gContext.mScale[i] = max(gContext.mScale[i], 0.001f); + + if (gContext.mScaleLast != gContext.mScale) + { + modified = true; + } + gContext.mScaleLast = gContext.mScale; + + // compute matrix & delta + matrix_t deltaMatrixScale; + deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); + + matrix_t res = deltaMatrixScale * gContext.mModelLocal; + *(matrix_t*)matrix = res; + + if (deltaMatrix) + { + vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin; + + vec_t originalScaleDivider; + originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x; + originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y; + originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z; + + deltaScale = deltaScale * originalScaleDivider; + + deltaMatrixScale.Scale(deltaScale); + memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mScale.Set(1.f, 1.f, 1.f); + } + + type = gContext.mCurrentOperation; + } + return modified; + } + + static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, ROTATE) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL; + bool modified = false; + + if (!gContext.mbUsing) + { + type = gContext.mbOverGizmoHotspot ? MT_NONE : GetRotateType(op); + gContext.mbOverGizmoHotspot |= type != MT_NONE; + + if (type != MT_NONE) + { + Karma::KarmaGui::SetNextFrameWantCaptureMouse(true); + } + + if (type == MT_ROTATE_SCREEN) + { + applyRotationLocaly = true; + } + + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.GetCurrentID(); + gContext.mCurrentOperation = type; + const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + // pickup plan + if (applyRotationLocaly) + { + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]); + } + else + { + gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]); + } + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + gContext.mRotationVectorSource = Normalized(localPos); + gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); + } + } + + // rotation + if (gContext.mbUsing && (gContext.GetCurrentID() == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation)) + { + Karma::KarmaGui::SetNextFrameWantCaptureMouse(true); + + gContext.mRotationAngle = ComputeAngleOnPlan(); + if (snap) + { + float snapInRadian = snap[0] * DEG2RAD; + ComputeSnap(&gContext.mRotationAngle, snapInRadian); + } + vec_t rotationAxisLocalSpace; + + rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); + rotationAxisLocalSpace.Normalize(); + + matrix_t deltaRotation; + deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); + if (gContext.mRotationAngle != gContext.mRotationAngleOrigin) + { + modified = true; + } + gContext.mRotationAngleOrigin = gContext.mRotationAngle; + + matrix_t scaleOrigin; + scaleOrigin.Scale(gContext.mModelScaleOrigin); + + if (applyRotationLocaly) + { + *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModelLocal; + } + else + { + matrix_t res = gContext.mModelSource; + res.v.position.Set(0.f); + + *(matrix_t*)matrix = res * deltaRotation; + ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; + } + + if (deltaMatrix) + { + *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mEditingID = -1; + } + type = gContext.mCurrentOperation; + } + return modified; + } + + void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale) + { + matrix_t mat = *(matrix_t*)matrix; + + scale[0] = mat.v.right.Length(); + scale[1] = mat.v.up.Length(); + scale[2] = mat.v.dir.Length(); + + mat.OrthoNormalize(); + + rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); + rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2])); + rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); + + translation[0] = mat.v.position.x; + translation[1] = mat.v.position.y; + translation[2] = mat.v.position.z; + } + + void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix) + { + matrix_t& mat = *(matrix_t*)matrix; + + matrix_t rot[3]; + for (int i = 0; i < 3; i++) + { + rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); + } + + mat = rot[0] * rot[1] * rot[2]; + + float validScale[3]; + for (int i = 0; i < 3; i++) + { + if (fabsf(scale[i]) < FLT_EPSILON) + { + validScale[i] = 0.001f; + } + else + { + validScale[i] = scale[i]; + } + } + mat.v.right *= validScale[0]; + mat.v.up *= validScale[1]; + mat.v.dir *= validScale[2]; + mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); + } + + void SetAlternativeWindow(KGGuiWindow* window) + { + gContext.mAlternativeWindow = window; + } + + void SetID(int id) + { + if (gContext.mIDStack.empty()) + { + gContext.mIDStack.push_back(-1); + } + gContext.mIDStack.back() = id; + } + + KGGuiID GetID(const char* str, const char* str_end) + { + KGGuiID seed = gContext.GetCurrentID(); + KGGuiID id = KGHashStr(str, str_end ? (str_end - str) : 0, seed); + return id; + } + + KGGuiID GetID(const char* str) + { + return GetID(str, nullptr); + } + + KGGuiID GetID(const void* ptr) + { + KGGuiID seed = gContext.GetCurrentID(); + KGGuiID id = KGHashData(&ptr, sizeof(void*), seed); + return id; + } + + KGGuiID GetID(int n) + { + KGGuiID seed = gContext.GetCurrentID(); + KGGuiID id = KGHashData(&n, sizeof(n), seed); + return id; + } + + void PushID(const char* str_id) + { + KGGuiID id = GetID(str_id); + gContext.mIDStack.push_back(id); + } + + void PushID(const char* str_id_begin, const char* str_id_end) + { + KGGuiID id = GetID(str_id_begin, str_id_end); + gContext.mIDStack.push_back(id); + } + + void PushID(const void* ptr_id) + { + KGGuiID id = GetID(ptr_id); + gContext.mIDStack.push_back(id); + } + + void PushID(int int_id) + { + KGGuiID id = GetID(int_id); + gContext.mIDStack.push_back(id); + } + + void PopID() + { + KR_CORE_ASSERT(gContext.mIDStack.Size > 1, "Too many PopID(), or could be popping in a wrong/different window?"); // Too many PopID(), or could be popping in a wrong/different window? + gContext.mIDStack.pop_back(); + if (gContext.mIDStack.empty()) + { + gContext.mIDStack.clear(); + } + } + + void AllowAxisFlip(bool value) + { + gContext.mAllowAxisFlip = value; + } + + void SetAxisLimit(float value) + { + gContext.mAxisLimit=value; + } + + void SetAxisMask(bool x, bool y, bool z) + { + gContext.mAxisMask = (x ? 1 : 0) + (y ? 2 : 0) + (z ? 4 : 0); + } + + void SetPlaneLimit(float value) + { + gContext.mPlaneLimit = value; + } + + bool IsOver(float* position, float pixelRadius) + { + const KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + + float radius = sqrtf((KGLengthSqr(worldToPos({ position[0], position[1], position[2], 0.0f }, gContext.mViewProjection) - io.MousePos))); + return radius < pixelRadius; + } + + bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, const float* localBounds, const float* boundsSnap) + { + gContext.mDrawList->PushClipRect (KGVec2 (gContext.mX, gContext.mY), KGVec2 (gContext.mX + gContext.mWidth, gContext.mY + gContext.mHeight), false); + + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + + // set delta to identity + if (deltaMatrix) + { + ((matrix_t*)deltaMatrix)->SetToIdentity(); + } + + // behind camera + vec_t camSpacePosition; + camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); + if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f && !gContext.mbUsing) + { + return false; + } + + // -- + int type = MT_NONE; + bool manipulated = false; + if (gContext.mbEnable) + { + if (!gContext.mbUsingBounds) + { + manipulated = HandleTranslation(matrix, deltaMatrix, operation, type, snap) || + HandleScale(matrix, deltaMatrix, operation, type, snap) || + HandleRotation(matrix, deltaMatrix, operation, type, snap); + } + } + + if (localBounds && !gContext.mbUsing) + { + manipulated |= HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation); + } + + gContext.mOperation = operation; + if (!gContext.mbUsingBounds) + { + DrawRotationGizmo(operation, type); + DrawTranslationGizmo(operation, type); + DrawScaleGizmo(operation, type); + DrawScaleUniveralGizmo(operation, type); + } + + gContext.mDrawList->PopClipRect (); + return manipulated; + } + + void SetGizmoSizeClipSpace(float value) + { + gContext.mGizmoSizeClipSpace = value; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////// + void ComputeFrustumPlanes(vec_t* frustum, const float* clip) + { + frustum[0].x = clip[3] - clip[0]; + frustum[0].y = clip[7] - clip[4]; + frustum[0].z = clip[11] - clip[8]; + frustum[0].w = clip[15] - clip[12]; + + frustum[1].x = clip[3] + clip[0]; + frustum[1].y = clip[7] + clip[4]; + frustum[1].z = clip[11] + clip[8]; + frustum[1].w = clip[15] + clip[12]; + + frustum[2].x = clip[3] + clip[1]; + frustum[2].y = clip[7] + clip[5]; + frustum[2].z = clip[11] + clip[9]; + frustum[2].w = clip[15] + clip[13]; + + frustum[3].x = clip[3] - clip[1]; + frustum[3].y = clip[7] - clip[5]; + frustum[3].z = clip[11] - clip[9]; + frustum[3].w = clip[15] - clip[13]; + + frustum[4].x = clip[3] - clip[2]; + frustum[4].y = clip[7] - clip[6]; + frustum[4].z = clip[11] - clip[10]; + frustum[4].w = clip[15] - clip[14]; + + frustum[5].x = clip[3] + clip[2]; + frustum[5].y = clip[7] + clip[6]; + frustum[5].z = clip[11] + clip[10]; + frustum[5].w = clip[15] + clip[14]; + + for (int i = 0; i < 6; i++) + { + frustum[i].Normalize(); + } + } + + void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + struct CubeFace + { + float z; + KGVec2 faceCoordsScreen[4]; + KGU32 color; + }; + CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6); + + if (!faces) + { + return; + } + + vec_t frustum[6]; + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + ComputeFrustumPlanes(frustum, viewProjection.m16); + + int cubeFaceCount = 0; + for (int cube = 0; cube < matrixCount; cube++) + { + const float* matrix = &matrices[cube * 16]; + + matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; + + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + + const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], + directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], + }; + + // clipping + /* + bool skipFace = false; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + vec_t camSpacePosition; + camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res); + if (camSpacePosition.z < 0.001f) + { + skipFace = true; + break; + } + } + if (skipFace) + { + continue; + } + */ + vec_t centerPosition, centerPositionVP; + centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix); + centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res); + + bool inFrustum = true; + for (int iFrustum = 0; iFrustum < 6; iFrustum++) + { + float dist = DistanceToPlane(centerPosition, frustum[iFrustum]); + if (dist < 0.f) + { + inFrustum = false; + break; + } + } + + if (!inFrustum) + { + continue; + } + CubeFace& cubeFace = faces[cubeFaceCount]; + + // 3D->2D + //KGVec2 faceCoordsScreen[4]; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); + } + + KGU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + cubeFace.color = directionColor | KG_COL32(0x80, 0x80, 0x80, 0); + + cubeFace.z = centerPositionVP.z / centerPositionVP.w; + cubeFaceCount++; + } + } + qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) { + CubeFace* a = (CubeFace*)_a; + CubeFace* b = (CubeFace*)_b; + if (a->z < b->z) + { + return 1; + } + return -1; + }); + // draw face with lighter color + for (int iFace = 0; iFace < cubeFaceCount; iFace++) + { + const CubeFace& cubeFace = faces[iFace]; + gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color); + } + + _freea(faces); + } + + void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize) + { + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + vec_t frustum[6]; + ComputeFrustumPlanes(frustum, viewProjection.m16); + matrix_t res = *(matrix_t*)matrix * viewProjection; + + for (float f = -gridSize; f <= gridSize; f += 1.f) + { + for (int dir = 0; dir < 2; dir++) + { + vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize); + vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize); + bool visible = true; + for (int i = 0; i < 6; i++) + { + float dA = DistanceToPlane(ptA, frustum[i]); + float dB = DistanceToPlane(ptB, frustum[i]); + if (dA < 0.f && dB < 0.f) + { + visible = false; + break; + } + if (dA > 0.f && dB > 0.f) + { + continue; + } + if (dA < 0.f) + { + float len = fabsf(dA - dB); + float t = fabsf(dA) / len; + ptA.Lerp(ptB, t); + } + if (dB < 0.f) + { + float len = fabsf(dB - dA); + float t = fabsf(dB) / len; + ptB.Lerp(ptA, t); + } + } + if (visible) + { + KGU32 col = KG_COL32(0x80, 0x80, 0x80, 0xFF); + col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? KG_COL32(0x90, 0x90, 0x90, 0xFF) : col; + col = (fabsf(f) < FLT_EPSILON) ? KG_COL32(0x40, 0x40, 0x40, 0xFF): col; + + float thickness = 1.f; + thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness; + thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness; + + gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness); + } + } + } + } + + void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, KGVec2 position, KGVec2 size, KGU32 backgroundColor) + { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + ViewManipulate(view, length, position, size, backgroundColor); + } + + void ViewManipulate(float* view, float length, KGVec2 position, KGVec2 size, KGU32 backgroundColor) + { + static bool isDraging = false; + static bool isClicking = false; + static vec_t interpolationUp; + static vec_t interpolationDir; + static int interpolationFrames = 0; + const vec_t referenceUp = makeVect(0.f, 1.f, 0.f); + + matrix_t svgView, svgProjection; + svgView = gContext.mViewMat; + svgProjection = gContext.mProjectionMat; + + KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor); + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length; + + // view/projection matrices + const float distance = 3.f; + matrix_t cubeProjection, cubeView; + float fov = acosf(distance / (sqrtf(distance * distance + 3.f))) * RAD2DEG; + Perspective(fov / sqrtf(2.f), size.x / size.y, 0.01f, 1000.f, cubeProjection.m16); + + vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]); + vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]); + vec_t eye = dir * distance; + vec_t zero = makeVect(0.f, 0.f); + LookAt(&eye.x, &zero.x, &up.x, cubeView.m16); + + // set context + gContext.mViewMat = cubeView; + gContext.mProjectionMat = cubeProjection; + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size); + + const matrix_t res = cubeView * cubeProjection; + + // panels + static const KGVec2 panelPosition[9] = { KGVec2(0.75f,0.75f), KGVec2(0.25f, 0.75f), KGVec2(0.f, 0.75f), + KGVec2(0.75f, 0.25f), KGVec2(0.25f, 0.25f), KGVec2(0.f, 0.25f), + KGVec2(0.75f, 0.f), KGVec2(0.25f, 0.f), KGVec2(0.f, 0.f) }; + + static const KGVec2 panelSize[9] = { KGVec2(0.25f,0.25f), KGVec2(0.5f, 0.25f), KGVec2(0.25f, 0.25f), + KGVec2(0.25f, 0.5f), KGVec2(0.5f, 0.5f), KGVec2(0.25f, 0.5f), + KGVec2(0.25f, 0.25f), KGVec2(0.5f, 0.25f), KGVec2(0.25f, 0.25f) }; + + // tag faces + bool boxes[27]{}; + static int overBox = -1; + for (int iPass = 0; iPass < 2; iPass++) + { + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + const vec_t indexVectorX = directionUnary[perpXIndex] * invert; + const vec_t indexVectorY = directionUnary[perpYIndex] * invert; + const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY; + + // plan local space + const vec_t n = directionUnary[normalIndex] * invert; + vec_t viewSpaceNormal = n; + vec_t viewSpacePoint = n * 0.5f; + viewSpaceNormal.TransformVector(cubeView); + viewSpaceNormal.Normalize(); + viewSpacePoint.TransformPoint(cubeView); + const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal); + + // back face culling + if (viewSpaceFacePlan.w > 0.f) + { + continue; + } + + const vec_t facePlan = BuildPlan(n * 0.5f, n); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f); + + float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f; + float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f; + + // panels + const vec_t dx = directionUnary[perpXIndex]; + const vec_t dy = directionUnary[perpYIndex]; + const vec_t origin = directionUnary[normalIndex] - dx - dy; + for (int iPanel = 0; iPanel < 9; iPanel++) + { + vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f); + const KGVec2 p = panelPosition[iPanel] * 2.f; + const KGVec2 s = panelSize[iPanel] * 2.f; + KGVec2 faceCoordsScreen[4]; + vec_t panelPos[4] = { dx * p.x + dy * p.y, + dx * p.x + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * p.y }; + + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size); + } + + const KGVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] }; + bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x && localy > panelCorners[0].y && localy < panelCorners[1].y; + int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z); + KR_CORE_ASSERT(boxCoordInt < 27, "Too much boxcoordint?"); + boxes[boxCoordInt] |= insidePanel && (!isDraging) && gContext.mbMouseOver; + + // draw face with lighter color + if (iPass) + { + KGU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, (directionColor | KG_COL32(0x80, 0x80, 0x80, 0x80)) | (gContext.mIsViewManipulatorHovered ? KG_COL32(0x08, 0x08, 0x08, 0) : 0)); + if (boxes[boxCoordInt]) + { + KGU32 selectionColor = GetColorU32(SELECTION); + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, selectionColor); + + if (io.MouseDown[0] && !isClicking && !isDraging && GKarmaGui->ActiveId == 0) { + overBox = boxCoordInt; + isClicking = true; + isDraging = true; + } + } + } + } + } + } + if (interpolationFrames) + { + interpolationFrames--; + vec_t newDir = viewInverse.v.dir; + newDir.Lerp(interpolationDir, 0.2f); + newDir.Normalize(); + + vec_t newUp = viewInverse.v.up; + newUp.Lerp(interpolationUp, 0.3f); + newUp.Normalize(); + newUp = interpolationUp; + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &newUp.x, view); + } + gContext.mIsViewManipulatorHovered = gContext.mbMouseOver && KGRect(position, position + size).Contains(io.MousePos); + + if (io.MouseDown[0] && (fabsf(io.MouseDelta[0]) || fabsf(io.MouseDelta[1])) && isClicking) + { + isClicking = false; + } + + if (!io.MouseDown[0]) + { + if (isClicking) + { + // apply new view direction + int cx = overBox / 9; + int cy = (overBox - cx * 9) / 3; + int cz = overBox % 3; + interpolationDir = makeVect(1.f - (float)cx, 1.f - (float)cy, 1.f - (float)cz); + interpolationDir.Normalize(); + + if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f) + { + vec_t right = viewInverse.v.right; + if (fabsf(right.x) > fabsf(right.z)) + { + right.z = 0.f; + } + else + { + right.x = 0.f; + } + right.Normalize(); + interpolationUp = Cross(interpolationDir, right); + interpolationUp.Normalize(); + } + else + { + interpolationUp = referenceUp; + } + interpolationFrames = 40; + + } + isClicking = false; + isDraging = false; + } + + + if (isDraging) + { + matrix_t rx, ry, roll; + + rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f); + ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f); + + roll = rx * ry; + + vec_t newDir = viewInverse.v.dir; + newDir.TransformVector(roll); + newDir.Normalize(); + + // clamp + vec_t planDir = Cross(viewInverse.v.right, referenceUp); + planDir.y = 0.f; + planDir.Normalize(); + float dt = Dot(planDir, newDir); + if (dt < 0.0f) + { + newDir += planDir * dt; + newDir.Normalize(); + } + + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view); + } + + gContext.mbUsingViewManipulate = (interpolationFrames != 0) || isDraging; + if (isClicking || gContext.mbUsingViewManipulate || gContext.mIsViewManipulatorHovered) { + Karma::KarmaGui::SetNextFrameWantCaptureMouse(true); + } + + // restore view/projection because it was used to compute ray + ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode); + } +}; diff --git a/Karma/src/Karma/KarmaGui/KarmaGuizmo.h b/Karma/src/Karma/KarmaGui/KarmaGuizmo.h new file mode 100644 index 00000000..ca3fe978 --- /dev/null +++ b/Karma/src/Karma/KarmaGui/KarmaGuizmo.h @@ -0,0 +1,305 @@ +// https://github.com/CedricGuillemet/KarmaGuizmo +// v1.92.5 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2016-2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ------------------------------------------------------------------------------------------- +// History : +// 2019/11/03 View gizmo +// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. +// 2016/09/09 Hatched negative axis. Snapping. Documentation update. +// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved +// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. +// 2016/08/31 First version +// +// ------------------------------------------------------------------------------------------- +// Future (no order): +// +// - Multi view +// - display rotation/translation/scale infos in local/world space and not only local +// - finish local/world matrix application +// - OPERATION as bitmask +// +// ------------------------------------------------------------------------------------------- +// Example +#if 0 +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static KarmaGuizmo::OPERATION mCurrentGizmoOperation(KarmaGuizmo::ROTATE); + static KarmaGuizmo::MODE mCurrentGizmoMode(KarmaGuizmo::WORLD); + if (KarmaGui::IsKeyPressed(90)) + mCurrentGizmoOperation = KarmaGuizmo::TRANSLATE; + if (KarmaGui::IsKeyPressed(69)) + mCurrentGizmoOperation = KarmaGuizmo::ROTATE; + if (KarmaGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = KarmaGuizmo::SCALE; + if (KarmaGui::RadioButton("Translate", mCurrentGizmoOperation == KarmaGuizmo::TRANSLATE)) + mCurrentGizmoOperation = KarmaGuizmo::TRANSLATE; + KarmaGui::SameLine(); + if (KarmaGui::RadioButton("Rotate", mCurrentGizmoOperation == KarmaGuizmo::ROTATE)) + mCurrentGizmoOperation = KarmaGuizmo::ROTATE; + KarmaGui::SameLine(); + if (KarmaGui::RadioButton("Scale", mCurrentGizmoOperation == KarmaGuizmo::SCALE)) + mCurrentGizmoOperation = KarmaGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + KarmaGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + KarmaGui::InputFloat3("Tr", matrixTranslation, 3); + KarmaGui::InputFloat3("Rt", matrixRotation, 3); + KarmaGui::InputFloat3("Sc", matrixScale, 3); + KarmaGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != KarmaGuizmo::SCALE) + { + if (KarmaGui::RadioButton("Local", mCurrentGizmoMode == KarmaGuizmo::LOCAL)) + mCurrentGizmoMode = KarmaGuizmo::LOCAL; + KarmaGui::SameLine(); + if (KarmaGui::RadioButton("World", mCurrentGizmoMode == KarmaGuizmo::WORLD)) + mCurrentGizmoMode = KarmaGuizmo::WORLD; + } + static bool useSnap(false); + if (KarmaGui::IsKeyPressed(83)) + useSnap = !useSnap; + KarmaGui::Checkbox("", &useSnap); + KarmaGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case KarmaGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + KarmaGui::InputFloat3("Snap", &snap.x); + break; + case KarmaGuizmo::ROTATE: + snap = config.mSnapRotation; + KarmaGui::InputFloat("Angle Snap", &snap.x); + break; + case KarmaGuizmo::SCALE: + snap = config.mSnapScale; + KarmaGui::InputFloat("Scale Snap", &snap.x); + break; + } + KarmaGuiIO& io = Karma::KarmaGui::GetIO(); + KarmaGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + KarmaGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +#endif +#pragma once + +#include "KarmaGui.h" + +#ifndef KARMAGUI_API +#define KARMAGUI_API KARMA_API +#endif + +#ifndef KARMAGUIZMO_NAMESPACE +#define KARMAGUIZMO_NAMESPACE KarmaGuizmo +#endif + +struct KGGuiWindow; + +namespace KARMAGUIZMO_NAMESPACE +{ + // call inside your own window and before Manipulate() in order to draw gizmo to that window. + // Or pass a specific KGDrawList to draw to (e.g. KarmaGui::GetForegroundDrawList()). + KARMAGUI_API void SetDrawlist(KGDrawList* drawlist = nullptr); + + // call BeginFrame right after KarmaGui_XXXX_NewFrame(); + KARMAGUI_API void BeginFrame(); + + // this is necessary because when imguizmo is compiled into a dll, and imgui into another + // globals are not shared between them. + // More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam + // expose method to set imgui context + KARMAGUI_API void SetKarmaGuiContext(KarmaGuiContext* ctx); + + // return true if mouse cursor is over any gizmo control (axis, plan or screen component) + KARMAGUI_API bool IsOver(); + + // return true if mouse IsOver or if the gizmo is in moving state + KARMAGUI_API bool IsUsing(); + + // return true if the view gizmo is in moving state + KARMAGUI_API bool IsUsingViewManipulate(); + // only check if your mouse is over the view manipulator - no matter whether it's active or not + KARMAGUI_API bool IsViewManipulateHovered(); + + // return true if any gizmo is in moving state + KARMAGUI_API bool IsUsingAny(); + + // enable/disable the gizmo. Stay in the state until next call to Enable. + // gizmo is rendered with gray half transparent color when disabled + KARMAGUI_API void Enable(bool enable); + + // helper functions for manualy editing translation/rotation/scale with an input float + // translation, rotation and scale float points to 3 floats each + // Angles are in degrees (more suitable for human editing) + // example: + // float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + // KarmaGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + // KarmaGui::InputFloat3("Tr", matrixTranslation, 3); + // KarmaGui::InputFloat3("Rt", matrixRotation, 3); + // KarmaGui::InputFloat3("Sc", matrixScale, 3); + // KarmaGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); + // + // These functions have some numerical stability issues for now. Use with caution. + KARMAGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale); + KARMAGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix); + + KARMAGUI_API void SetRect(float x, float y, float width, float height); + // default is false + KARMAGUI_API void SetOrthographic(bool isOrthographic); + + // Render a cube with face color corresponding to face normal. Usefull for debug/tests + KARMAGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount); + KARMAGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize); + + // call it when you want a gizmo + // Needs view and projection matrices. + // matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional + // translation is applied in world space + enum OPERATION + { + TRANSLATE_X = (1u << 0), + TRANSLATE_Y = (1u << 1), + TRANSLATE_Z = (1u << 2), + ROTATE_X = (1u << 3), + ROTATE_Y = (1u << 4), + ROTATE_Z = (1u << 5), + ROTATE_SCREEN = (1u << 6), + SCALE_X = (1u << 7), + SCALE_Y = (1u << 8), + SCALE_Z = (1u << 9), + BOUNDS = (1u << 10), + SCALE_XU = (1u << 11), + SCALE_YU = (1u << 12), + SCALE_ZU = (1u << 13), + + TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, + ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, + SCALE = SCALE_X | SCALE_Y | SCALE_Z, + SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal + UNIVERSAL = TRANSLATE | ROTATE | SCALEU + }; + + inline OPERATION operator|(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + enum MODE + { + LOCAL, + WORLD + }; + + KARMAGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL); + // + // Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en + // It seems to be a defensive patent in the US. I don't think it will bring troubles using it as + // other software are using the same mechanics. But just in case, you are now warned! + // + KARMAGUI_API void ViewManipulate(float* view, float length, KGVec2 position, KGVec2 size, KGU32 backgroundColor); + + // use this version if you did not call Manipulate before and you are just using ViewManipulate + KARMAGUI_API void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, KGVec2 position, KGVec2 size, KGU32 backgroundColor); + + KARMAGUI_API void SetAlternativeWindow(KGGuiWindow* window); + + [[deprecated("Use PushID/PopID instead.")]] + KARMAGUI_API void SetID(int id); + + // ID stack/scopes + // Read the FAQ (docs/FAQ.md or http://dearimgui.org/faq) for more details about how ID are handled in dear imgui. + // - Those questions are answered and impacted by understanding of the ID stack system: + // - "Q: Why is my widget not reacting when I click on it?" + // - "Q: How can I have widgets with an empty label?" + // - "Q: How can I have multiple widgets with the same label?" + // - Short version: ID are hashes of the entire ID stack. If you are creating widgets in a loop you most likely + // want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them. + // - You can also use the "Label##foobar" syntax within widget label to distinguish them from each others. + // - In this header file we use the "label"/"name" terminology to denote a string that will be displayed + used as an ID, + // whereas "str_id" denote a string that is only used as an ID and not normally displayed. + KARMAGUI_API void PushID(const char* str_id); // push string into the ID stack (will hash string). + KARMAGUI_API void PushID(const char* str_id_begin, const char* str_id_end); // push string into the ID stack (will hash string). + KARMAGUI_API void PushID(const void* ptr_id); // push pointer into the ID stack (will hash pointer). + KARMAGUI_API void PushID(int int_id); // push integer into the ID stack (will hash integer). + KARMAGUI_API void PopID(); // pop from the ID stack. + KARMAGUI_API KGGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself + KARMAGUI_API KGGuiID GetID(const char* str_id_begin, const char* str_id_end); + KARMAGUI_API KGGuiID GetID(const void* ptr_id); + + // return true if the cursor is over the operation's gizmo + KARMAGUI_API bool IsOver(OPERATION op); + KARMAGUI_API void SetGizmoSizeClipSpace(float value); + + // Allow axis to flip + // When true (default), the guizmo axis flip for better visibility + // When false, they always stay along the positive world/local axis + KARMAGUI_API void AllowAxisFlip(bool value); + + // Configure the limit where axis are hidden + KARMAGUI_API void SetAxisLimit(float value); + // Set an axis mask to permanently hide a given axis (true -> hidden, false -> shown) + KARMAGUI_API void SetAxisMask(bool x, bool y, bool z); + // Configure the limit where planes are hiden + KARMAGUI_API void SetPlaneLimit(float value); + // from a x,y,z point in space and using Manipulation view/projection matrix, check if mouse is in pixel radius distance of that projected point + KARMAGUI_API bool IsOver(float* position, float pixelRadius); + + enum COLOR + { + DIRECTION_X, // directionColor[0] + DIRECTION_Y, // directionColor[1] + DIRECTION_Z, // directionColor[2] + PLANE_X, // planeColor[0] + PLANE_Y, // planeColor[1] + PLANE_Z, // planeColor[2] + SELECTION, // selectionColor + INACTIVE, // inactiveColor + TRANSLATION_LINE, // translationLineColor + SCALE_LINE, + ROTATION_USING_BORDER, + ROTATION_USING_FILL, + HATCHED_AXIS_LINES, + TEXT, + TEXT_SHADOW, + COUNT + }; + + struct Style + { + KARMAGUI_API Style(); + + float TranslationLineThickness; // Thickness of lines for translation gizmo + float TranslationLineArrowSize; // Size of arrow at the end of lines for translation gizmo + float RotationLineThickness; // Thickness of lines for rotation gizmo + float RotationOuterLineThickness; // Thickness of line surrounding the rotation gizmo + float ScaleLineThickness; // Thickness of lines for scale gizmo + float ScaleLineCircleSize; // Size of circle at the end of lines for scale gizmo + float HatchedAxisLineThickness; // Thickness of hatched axis lines + float CenterCircleSize; // Size of circle at the center of the translate/scale gizmo + + KGVec4 Colors[COLOR::COUNT]; + }; + + KARMAGUI_API Style& GetStyle(); +} diff --git a/Karma/src/Karma/KarmaRHI/DynamicRHI.cpp b/Karma/src/Karma/KarmaRHI/DynamicRHI.cpp new file mode 100644 index 00000000..a8edcb60 --- /dev/null +++ b/Karma/src/Karma/KarmaRHI/DynamicRHI.cpp @@ -0,0 +1,51 @@ +#include "DynamicRHI.h" +#include "VulkanDynamicRHI.h" + +namespace Karma +{ + // Globals. + FDynamicRHI* GDynamicRHI = nullptr; + ERHIInterfaceType GRHIInterfaceType = ERHIInterfaceType::Vulkan; + + FDynamicRHI* FDynamicRHI::CreateRHI() + { + // if RHI is ERHIInterfaceType::Vulkan + if (GRHIInterfaceType == ERHIInterfaceType::Vulkan) + { + return static_cast(new FVulkanDynamicRHI()); + } + else + { + KR_CORE_WARN("RHI Interface Type not recognized!"); + return nullptr; + } + } + + void RHIInit() + { + if (!GDynamicRHI) + { + KR_CORE_INFO("Karma RHI initializing"); + + GDynamicRHI = PlatformCreateDynamicRHI(); + if (GDynamicRHI) + { + GDynamicRHI->Init(); + } + } + } + + void RHIExit() + { + if (GDynamicRHI != nullptr) + { + KR_CORE_INFO("Shutting down the RHI"); + GDynamicRHI->Shutdown(); + + delete GDynamicRHI; + GDynamicRHI = nullptr; + + KR_CORE_INFO("Karma RHI exodus"); + } + } +} \ No newline at end of file diff --git a/Karma/src/Karma/KarmaRHI/DynamicRHI.h b/Karma/src/Karma/KarmaRHI/DynamicRHI.h new file mode 100644 index 00000000..31e4defd --- /dev/null +++ b/Karma/src/Karma/KarmaRHI/DynamicRHI.h @@ -0,0 +1,81 @@ +/** + * @file DynamicRHI.h + * @brief + * @author Tim Sweeney + * @date December 15, 2025 + * + * @copyright Epic Games, Inc. All Rights Reserved. + */ + +#pragma once + +#include "KarmaRHI.h" + +namespace Karma +{ + /** + * @brief Abstract base class for Dynamic Rendering Hardware Interface (RHI). + * + * Provides an interface for dynamic rendering operations across different graphics APIs. + * + * @since Unreal Engine 6.0.0 + */ + class KARMA_API FDynamicRHI + { + public: + + static FDynamicRHI* CreateRHI(); + + /** + * @brief Virtual destructor for FDynamicRHI. + */ + virtual ~FDynamicRHI() = default; + + /** + * @brief Initializes the RHI. + * + * Sets up necessary resources and states for rendering. + * + * @return true if initialization was successful, false otherwise. + */ + virtual bool Init() = 0; + /** + * @brief Shuts down the RHI. + * + * Handles shutdown and resource destruction before the RHI's actual destructor is called (so that all resources of the RHI are still available for shutdown). + * + * @note Cleans up resources and states used by the RHI. + */ + virtual void Shutdown() = 0; + + /** + * @brief Presents the rendered frame to the display. + */ + virtual void Present() = 0; + // Additional methods for rendering operations can be added here + + virtual Karma::ERHIInterfaceType GetInterfaceType() const { return Karma::ERHIInterfaceType::Hidden; } + }; + + /** A global pointer to the dynamically bound RHI implementation. */ + extern KARMA_API FDynamicRHI* GDynamicRHI; + + /** + * @brief The current RHI interface type in use. + * + * @remark This variable indicates which graphics API is currently active. Later + * we can tie this to a configuration system (for instance loading configurable setting from file). + * + * @since Karma 1.0.0 + */ + extern KARMA_API ERHIInterfaceType GRHIInterfaceType; + + /** + * @brief Each platform that utilizes dynamic RHIs should implement this function + * + * Called to create the instance of the dynamic RHI. + * + * @since Karma 1.0.0 + */ + extern FDynamicRHI* PlatformCreateDynamicRHI(); +} diff --git a/Karma/src/Karma/KarmaRHI/KarmaRHI.cpp b/Karma/src/Karma/KarmaRHI/KarmaRHI.cpp new file mode 100644 index 00000000..e69de29b diff --git a/Karma/src/Karma/KarmaRHI/KarmaRHI.h b/Karma/src/Karma/KarmaRHI/KarmaRHI.h new file mode 100644 index 00000000..f7a74809 --- /dev/null +++ b/Karma/src/Karma/KarmaRHI/KarmaRHI.h @@ -0,0 +1,51 @@ +/** + * @file KarmaRHI.h + * @brief + * @author Ravi Mohan (the_cowboy) + * @date December 15, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +namespace Karma +{ + /** + * @brief Enumeration of supported RHI interface types. + */ + enum class ERHIInterfaceType + { + /** + * @brief Hidden or null RHI interface type. + */ + Hidden, + Null, + D3D11, + D3D12, + + /** + * @brief Vulkan RHI interface type. + */ + Vulkan, + Metal, + Agx, + OpenGL, + }; + + /** + * @brief Initializes the RHI. + * + * @see Application::Application() + * @since Karma 1.0.0 + */ + extern KARMA_API void RHIInit(); + + /** + * @brief Shuts down the RHI. + * + * @see Application::~Application() + * @since Karma 1.0.0 + */ + extern KARMA_API void RHIExit(); +} \ No newline at end of file diff --git a/Karma/src/Karma/KarmaUtilities.h b/Karma/src/Karma/KarmaUtilities.h index 111dda26..6b05e6ba 100644 --- a/Karma/src/Karma/KarmaUtilities.h +++ b/Karma/src/Karma/KarmaUtilities.h @@ -9,7 +9,6 @@ */ #pragma once -#include "krpch.h" #include "SubClassOf.h" #include "Actor.h" #include "stb_image.h" diff --git a/Karma/src/Karma/Layer.h b/Karma/src/Karma/Layer.h index 0eea1319..867e0510 100644 --- a/Karma/src/Karma/Layer.h +++ b/Karma/src/Karma/Layer.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Events/Event.h" #include "Renderer/Scene.h" diff --git a/Karma/src/Karma/LayerStack.cpp b/Karma/src/Karma/LayerStack.cpp index ecd2aaf9..a7ca1287 100644 --- a/Karma/src/Karma/LayerStack.cpp +++ b/Karma/src/Karma/LayerStack.cpp @@ -5,7 +5,7 @@ * In destructor, now those pointers were no longer there, that memory will never * be freed. Isn't this a memory leak. * - * The Cherno talk about this stuff here https://youtu.be/_Kj6BSfM6P4?list=PLlrATfBNZ98dC-V-N3m0Go4deliWHPFwT&t=717 + * The Cherno talks about this stuff here https://youtu.be/_Kj6BSfM6P4?list=PLlrATfBNZ98dC-V-N3m0Go4deliWHPFwT&t=717 * but the above issue doesn't seem to get resolved. */ diff --git a/Karma/src/Karma/LayerStack.h b/Karma/src/Karma/LayerStack.h index 8cfb96fa..cfd12bd2 100644 --- a/Karma/src/Karma/LayerStack.h +++ b/Karma/src/Karma/LayerStack.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Layer.h" namespace Karma diff --git a/Karma/src/Karma/Renderer/Buffer.cpp b/Karma/src/Karma/Renderer/Buffer.cpp index d449fde6..ff722974 100644 --- a/Karma/src/Karma/Renderer/Buffer.cpp +++ b/Karma/src/Karma/Renderer/Buffer.cpp @@ -1,5 +1,4 @@ #include "Buffer.h" -#include "Karma/Core.h" #include "Renderer.h" #include "Platform/OpenGL/OpenGLBuffer.h" #include "Platform/Vulkan/VulkanBuffer.h" diff --git a/Karma/src/Karma/Renderer/Buffer.h b/Karma/src/Karma/Renderer/Buffer.h index 536881f3..d60cbeaf 100644 --- a/Karma/src/Karma/Renderer/Buffer.h +++ b/Karma/src/Karma/Renderer/Buffer.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "glm/gtc/type_ptr.hpp" #include "stb_image.h" @@ -414,6 +412,8 @@ namespace Karma m_UniformList = { uniforms... }; } + virtual void UpdateCameraUniform() = 0; + /** * @brief An overridable function to upload the uniform buffer * diff --git a/Karma/src/Karma/Renderer/Camera/Camera.cpp b/Karma/src/Karma/Renderer/Camera/Camera.cpp index 438c8a9d..95b7195d 100644 --- a/Karma/src/Karma/Renderer/Camera/Camera.cpp +++ b/Karma/src/Karma/Renderer/Camera/Camera.cpp @@ -1,12 +1,19 @@ - #include "Camera.h" #include "glm/gtc/matrix_transform.hpp" #include "Karma/Input.h" +#include "Buffer.h" namespace Karma { Camera::Camera(const glm::vec3& initialCameraPosition) : m_Position(initialCameraPosition), m_LastMouseX(0.0f), m_LastMouseY(0.0f) - { + { + m_ViewProjectionUBO.reset(UniformBufferObject::Create({ ShaderDataType::Mat4, ShaderDataType::Mat4 }, 0)); + + UBODataPointer uProjection(&m_ProjectionMatrix); + UBODataPointer uView(&m_ViewMatrix); + + m_ViewProjectionUBO->UpdateUniforms(uProjection, uView); + m_ViewProjectionUBO->UpdateCameraUniform();// maybe move to Scene::AddCamera } Camera::~Camera() @@ -24,6 +31,10 @@ namespace Karma m_ViewMatrix = glm::lookAt(m_Position, m_Position + m_CameraFront, m_CameraUp); } + void Camera::OnUpdate(float deltaTime) + { + } + void Camera::MoveForward(float amount) { m_Position += amount * m_CameraFront; diff --git a/Karma/src/Karma/Renderer/Camera/Camera.h b/Karma/src/Karma/Renderer/Camera/Camera.h index 56330553..1af38d37 100644 --- a/Karma/src/Karma/Renderer/Camera/Camera.h +++ b/Karma/src/Karma/Renderer/Camera/Camera.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "glm/glm.hpp" namespace Karma @@ -38,6 +36,13 @@ namespace Karma */ virtual ~Camera(); + /** + * @brief A hook into the game loop for updating camera position and orientation in the level perhaps + * + * @since Karma 1.0.0 + */ + virtual void OnUpdate(float deltaTime); + /** * @brief Getter for current position of the Camers * @@ -171,6 +176,13 @@ namespace Karma */ const glm::mat4& GetViewMatirx() const { return m_ViewMatrix; } + /** + * @brief Getter for ViewProjection UBO + * + * @since Karma 1.0.0 + */ + std::shared_ptr& GetViewProjectionUBO() { return m_ViewProjectionUBO; } + private: void RecalculateViewMatrix(); @@ -192,5 +204,8 @@ namespace Karma const float m_Sensitivity = 0.1f; float m_Roll = 0.0f; float m_Pitch = 0.0f; + + private: + std::shared_ptr m_ViewProjectionUBO; }; } diff --git a/Karma/src/Karma/Renderer/Material.cpp b/Karma/src/Karma/Renderer/Material.cpp index c2ad6335..32059d82 100644 --- a/Karma/src/Karma/Renderer/Material.cpp +++ b/Karma/src/Karma/Renderer/Material.cpp @@ -8,7 +8,7 @@ namespace Karma void Material::OnUpdate() { - if (m_MainCamera != nullptr) + /*if (m_MainCamera != nullptr) { UBODataPointer uProjection(&m_MainCamera->GetProjectionMatrix()); UBODataPointer uView(&m_MainCamera->GetViewMatirx()); @@ -22,7 +22,7 @@ namespace Karma else { KR_CORE_WARN("No main camera specified for Material."); - } + }*/ } void Material::AttatchMainCamera(std::shared_ptr mCamera) diff --git a/Karma/src/Karma/Renderer/Material.h b/Karma/src/Karma/Renderer/Material.h index 91aa8fe0..1b8ab2e7 100644 --- a/Karma/src/Karma/Renderer/Material.h +++ b/Karma/src/Karma/Renderer/Material.h @@ -9,12 +9,10 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Renderer/Shader.h" - #include "Texture.h" #include "Camera/Camera.h" +#include namespace Karma { diff --git a/Karma/src/Karma/Renderer/Mesh.cpp b/Karma/src/Karma/Renderer/Mesh.cpp index 20f4f61d..9edebddb 100644 --- a/Karma/src/Karma/Renderer/Mesh.cpp +++ b/Karma/src/Karma/Renderer/Mesh.cpp @@ -1,5 +1,9 @@ #include "Mesh.h" #include "RenderCommand.h" +#include "Renderer/Material.h" + +// Experimental +#include "Platform/Vulkan/VulkanHolder.h" namespace Karma { @@ -57,6 +61,18 @@ namespace Karma } ProcessNode(scene->mRootNode, scene); + + // Set default static material + //m_StaticMaterial.reset(new Material()); + + // TODO: Need to abastract this shader addition process. ATM tied to vulkan only + // m_StaticMaterial->AddShader(std::static_pointer_cast(VulkanHolder::GetVulkanContext()->GetGeneralShader())); + + // We add default texture to the material + //std::shared_ptr defaultTexture; + //defaultTexture.reset(new Texture(TextureType::Image, "../Resources/Textures/UnrealGrid.png", "VikingTex", "texSampler")); + + //m_StaticMaterial->AddTexture(defaultTexture); } void Mesh::ProcessNode(aiNode* nodeToProcess, const aiScene* theScene) @@ -179,10 +195,12 @@ namespace Karma } } + // Checkout VulkanContext::CreateKarmaGuiGeneralGraphicsPipeline if you modify this function void Mesh::GaugeVertexDataLayout(aiMesh* meshToProcess, BufferLayout& buffLayout) { buffLayout.PushElement({ ShaderDataType::Float3, "v_Position" }); + // We check for optional attributes and add them to the layout if (meshToProcess->mTextureCoords[0] != nullptr) { buffLayout.PushElement({ ShaderDataType::Float2, "v_UV" }); diff --git a/Karma/src/Karma/Renderer/Mesh.h b/Karma/src/Karma/Renderer/Mesh.h index 1fc8a6ed..666df38b 100644 --- a/Karma/src/Karma/Renderer/Mesh.h +++ b/Karma/src/Karma/Renderer/Mesh.h @@ -10,8 +10,6 @@ #pragma once -#include "krpch.h" - #include "Buffer.h" #include #include @@ -123,5 +121,7 @@ namespace Karma MeshType m_MeshType; static std::shared_ptr> m_NameToAttributeDictionary; + + std::shared_ptr m_StaticMaterial; }; } diff --git a/Karma/src/Karma/Renderer/RenderCommand.h b/Karma/src/Karma/Renderer/RenderCommand.h index 21ea42fb..5268bfd4 100644 --- a/Karma/src/Karma/Renderer/RenderCommand.h +++ b/Karma/src/Karma/Renderer/RenderCommand.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "RendererAPI.h" namespace Karma diff --git a/Karma/src/Karma/Renderer/Renderer.cpp b/Karma/src/Karma/Renderer/Renderer.cpp index f4265004..11310b52 100644 --- a/Karma/src/Karma/Renderer/Renderer.cpp +++ b/Karma/src/Karma/Renderer/Renderer.cpp @@ -16,7 +16,7 @@ namespace Karma void Renderer::Submit(std::shared_ptr scene) { - RenderCommand::DrawIndexed(scene->GetRenderableVertexArray()); + //RenderCommand::DrawIndexed(scene->GetRenderableVertexArray()); } void Renderer::DeleteData() diff --git a/Karma/src/Karma/Renderer/Renderer.h b/Karma/src/Karma/Renderer/Renderer.h index c40148c6..19d854b4 100644 --- a/Karma/src/Karma/Renderer/Renderer.h +++ b/Karma/src/Karma/Renderer/Renderer.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "RenderCommand.h" #include "Karma/Renderer/Camera/Camera.h" #include "glm/glm.hpp" diff --git a/Karma/src/Karma/Renderer/RendererAPI.h b/Karma/src/Karma/Renderer/RendererAPI.h index 591c4a67..10327b69 100644 --- a/Karma/src/Karma/Renderer/RendererAPI.h +++ b/Karma/src/Karma/Renderer/RendererAPI.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "glm/glm.hpp" #include "VertexArray.h" diff --git a/Karma/src/Karma/Renderer/Scene.cpp b/Karma/src/Karma/Renderer/Scene.cpp index e80e5c29..9b4b55d6 100644 --- a/Karma/src/Karma/Renderer/Scene.cpp +++ b/Karma/src/Karma/Renderer/Scene.cpp @@ -1,4 +1,7 @@ #include "Scene.h" +#include "StaticMeshActor.h" + +#include "KarmaGui/KarmaGuiRenderer.h" namespace Karma { @@ -11,21 +14,16 @@ namespace Karma { } - std::shared_ptr Scene::GetRenderableVertexArray() const - { - // Get the first for now - return m_VertexArrays.at(0); - } - std::shared_ptr Scene::GetSceneCamera() const { // Get the first for now return m_Cameras.at(0); } - void Scene::AddVertexArray(std::shared_ptr vertexArray) + void Scene::AddStaticMeshActor(AStaticMeshActor* smActor) { - m_VertexArrays.push_back(vertexArray); + m_SMActors.push_back(smActor); + KarmaGuiRenderer::OnAdditionOfStaticMesh(smActor); } void Scene::AddCamera(std::shared_ptr camera) diff --git a/Karma/src/Karma/Renderer/Scene.h b/Karma/src/Karma/Renderer/Scene.h index cfcd8000..0c20e208 100644 --- a/Karma/src/Karma/Renderer/Scene.h +++ b/Karma/src/Karma/Renderer/Scene.h @@ -9,13 +9,14 @@ */ #pragma once -#include "krpch.h" - #include "Camera.h" #include "VertexArray.h" namespace Karma { + + class AStaticMeshActor; + /** * @brief Class for organizing and containing Scene relevant data */ @@ -44,6 +45,14 @@ namespace Karma */ void AddVertexArray(std::shared_ptr vertexArray); + /** + * @brief Add StaticMeshActor to the scene + * + * @param smActor The StaticMeshActor to be added + * @since Karma 1.0.0 + */ + void AddStaticMeshActor(AStaticMeshActor* smActor); + /** * @brief Add Camera * @@ -84,11 +93,11 @@ namespace Karma // Getters /** - * @brief Getter for the VertexArray + * @brief Get the list of StaticMeshActors in the scene * * @since Karma 1.0.0 */ - std::shared_ptr GetRenderableVertexArray() const; + const std::vector& GetSMActors() const { return m_SMActors; } /** * @brief Getter for the Camera being used for the scene @@ -104,13 +113,6 @@ namespace Karma */ inline const glm::vec4& GetClearColor() const { return m_ClearColor; } - /** - * @brief Get the list of VertexArrays - * - * @since Karma 1.0.0 - */ - inline const std::vector>& GetAllVertexArrays() const { return m_VertexArrays; } - /** * @brief Get the list of all Cameras * @@ -133,7 +135,7 @@ namespace Karma inline bool GetWindowToRenderWithinResizeStatus() const { return m_WindowResize; } private: - std::vector> m_VertexArrays; + std::vector m_SMActors; std::vector> m_Cameras; glm::vec4 m_ClearColor; diff --git a/Karma/src/Karma/Renderer/Shader.cpp b/Karma/src/Karma/Renderer/Shader.cpp index 7976a8b1..a507f171 100644 --- a/Karma/src/Karma/Renderer/Shader.cpp +++ b/Karma/src/Karma/Renderer/Shader.cpp @@ -1,6 +1,5 @@ #include "Shader.h" #include "Renderer.h" -#include "Karma/Core.h" #include "Platform/OpenGL/OpenGLShader.h" #include "Platform/Vulkan/VulkanShader.h" @@ -27,8 +26,7 @@ namespace Karma return nullptr; } - Shader* Shader::Create(const std::string& vertexSrcFile, const std::string& fragmentSrcFile, std::shared_ptr ubo, - const std::string& shaderName) + Shader* Shader::Create(const std::string& vertexSrcFile, const std::string& fragmentSrcFile, const std::string& shaderName) { switch (Renderer::GetAPI()) { @@ -36,9 +34,9 @@ namespace Karma KR_CORE_ASSERT(false, "RendererAPI::None is currently not supported"); return nullptr; case RendererAPI::API::OpenGL: - return new OpenGLShader(vertexSrcFile, fragmentSrcFile, ubo, shaderName); + return new OpenGLShader(vertexSrcFile, fragmentSrcFile, shaderName); case RendererAPI::API::Vulkan: - return new VulkanShader(vertexSrcFile, fragmentSrcFile, ubo); + return new VulkanShader(vertexSrcFile, fragmentSrcFile); } KR_CORE_ASSERT(false, "Unknown RendererAPI"); diff --git a/Karma/src/Karma/Renderer/Shader.h b/Karma/src/Karma/Renderer/Shader.h index 558e67f4..44d6f74b 100644 --- a/Karma/src/Karma/Renderer/Shader.h +++ b/Karma/src/Karma/Renderer/Shader.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Renderer/Buffer.h" #include "glm/glm.hpp" @@ -28,7 +26,7 @@ namespace Karma * @param ubo Uniform buffer object to a assigned * @since Karma 1.0.0 */ - Shader(std::shared_ptr ubo) : m_UniformBufferObject(ubo) + Shader() {} /** @@ -62,14 +60,6 @@ namespace Karma */ virtual void UnBind() const {} - /** - * @brief Getter for uniform buffer object - * - * @return pointer to UniformBufferObject present in the shader - * @since Karma 1.0.0 - */ - std::shared_ptr GetUniformBufferObject() const { return m_UniformBufferObject; } - /** * @brief Instantiating shader object according to the programmer chosen API * @@ -84,12 +74,10 @@ namespace Karma * @param vertexSrcFile Path to vertex shader (filename included). For instance "../Resources/Shaders/shader.vert", relative to * Engine's running directory * @param fragmentSrcFile Path to fragment shader (filename included). - * @param ubo UniformBufferObject to be used * @param shaderName Name of the shader to be supplied * @since Karma 1.0.0 */ - static Shader* Create(const std::string& vertexSrcFile, const std::string& fragmentSrcFile, std::shared_ptr ubo, - const std::string& shaderName = "NoNamedShader"); + static Shader* Create(const std::string& vertexSrcFile, const std::string& fragmentSrcFile, const std::string& shaderName = "NoNamedShader"); // Getters /** @@ -99,9 +87,6 @@ namespace Karma * @since Karma 1.0.0 */ const std::string& GetShaderName() const { return m_ShaderName; } - - private: - std::shared_ptr m_UniformBufferObject; protected: std::string m_ShaderName; diff --git a/Karma/src/Karma/Renderer/SkeletalMesh.h b/Karma/src/Karma/Renderer/SkeletalMesh.h index 50cdba54..a5a8f4fd 100644 --- a/Karma/src/Karma/Renderer/SkeletalMesh.h +++ b/Karma/src/Karma/Renderer/SkeletalMesh.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Mesh.h" namespace Karma diff --git a/Karma/src/Karma/Renderer/Texture.h b/Karma/src/Karma/Renderer/Texture.h index acc37bad..6942eede 100644 --- a/Karma/src/Karma/Renderer/Texture.h +++ b/Karma/src/Karma/Renderer/Texture.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - namespace Karma { /** diff --git a/Karma/src/Karma/Renderer/VertexArray.cpp b/Karma/src/Karma/Renderer/VertexArray.cpp index 9c81a96d..0d4ac753 100644 --- a/Karma/src/Karma/Renderer/VertexArray.cpp +++ b/Karma/src/Karma/Renderer/VertexArray.cpp @@ -1,6 +1,5 @@ #include "VertexArray.h" #include "Renderer.h" -#include "Karma/Core.h" #include "Platform/OpenGL/OpenGLVertexArray.h" #include "Platform/Vulkan/VulkanVertexArray.h" @@ -22,4 +21,4 @@ namespace Karma KR_CORE_ASSERT(false, "Unknown RendererAPI"); return nullptr; } -} \ No newline at end of file +} diff --git a/Karma/src/Karma/Renderer/VertexArray.h b/Karma/src/Karma/Renderer/VertexArray.h index 2bcb8c1e..7263a690 100644 --- a/Karma/src/Karma/Renderer/VertexArray.h +++ b/Karma/src/Karma/Renderer/VertexArray.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Renderer/Buffer.h" #include "Karma/Renderer/Shader.h" #include "Karma/Renderer/Mesh.h" diff --git a/Karma/src/Karma/Window.h b/Karma/src/Karma/Window.h index f844934b..6c15de45 100644 --- a/Karma/src/Karma/Window.h +++ b/Karma/src/Karma/Window.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Core.h" #include "Karma/Events/Event.h" diff --git a/Karma/src/Platform/Linux/Core/LinuxPlatformMemory.h b/Karma/src/Platform/Linux/Core/LinuxPlatformMemory.h index b595cac2..fa785977 100644 --- a/Karma/src/Platform/Linux/Core/LinuxPlatformMemory.h +++ b/Karma/src/Platform/Linux/Core/LinuxPlatformMemory.h @@ -8,8 +8,6 @@ */ #pragma once -#include "krpch.h" - #include "Core/TrueCore/GenericPlatformMemory.h" namespace Karma diff --git a/Karma/src/Platform/Linux/KarmaRHI/LinuxDynamicRHI.cpp b/Karma/src/Platform/Linux/KarmaRHI/LinuxDynamicRHI.cpp new file mode 100644 index 00000000..7cfd4d64 --- /dev/null +++ b/Karma/src/Platform/Linux/KarmaRHI/LinuxDynamicRHI.cpp @@ -0,0 +1,21 @@ +#include "LinuxDynamicRHI.h" +#include "VulkanDynamicRHI.h" + +#include "Log.h" + +namespace Karma +{ +#ifdef KR_LINUX_PLATFORM + FDynamicRHI* PlatformCreateDynamicRHI() + { + FDynamicRHI* DynamicRHI = FDynamicRHI::CreateRHI(); + + if (DynamicRHI) + { + KR_CORE_INFO("Created DynamicRHI for Linux OS"); + } + + return DynamicRHI; + } +#endif +} diff --git a/Karma/src/Platform/Linux/KarmaRHI/LinuxDynamicRHI.h b/Karma/src/Platform/Linux/KarmaRHI/LinuxDynamicRHI.h new file mode 100644 index 00000000..04d6aa51 --- /dev/null +++ b/Karma/src/Platform/Linux/KarmaRHI/LinuxDynamicRHI.h @@ -0,0 +1,18 @@ +/** + * @file LinuxDynamicRHI.h + * @brief + * @author Ravi Mohan (the_cowboy) + * @version 1.0 + * @date January 11, 2026 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include "KarmaRHI.h" + +namespace Karma +{ + +} diff --git a/Karma/src/Platform/Linux/LinuxWindow.cpp b/Karma/src/Platform/Linux/LinuxWindow.cpp index d88a8d46..265ba950 100644 --- a/Karma/src/Platform/Linux/LinuxWindow.cpp +++ b/Karma/src/Platform/Linux/LinuxWindow.cpp @@ -1,5 +1,4 @@ #include "LinuxWindow.h" -#include "Karma/Log.h" #include "Karma/Events/ApplicationEvent.h" #include "Karma/Events/KeyEvent.h" #include "Karma/Events/MouseEvent.h" @@ -71,7 +70,7 @@ namespace Karma m_Window = glfwCreateWindow((int)props.Width, (int)props.Height, m_Data.Title.c_str(), nullptr, nullptr); - switch (currentAPI) + /*switch (currentAPI) { case RendererAPI::API::None: KR_CORE_ASSERT(false, "RendererAPI::None is not supported"); @@ -82,10 +81,10 @@ namespace Karma case RendererAPI::API::Vulkan: m_Context = new VulkanContext(m_Window); break; - } + } m_Context->Init(); - SetVSync(true); + SetVSync(true);*/ // Used for event callbacks glfwSetWindowUserPointer(m_Window, &m_Data); @@ -102,7 +101,7 @@ namespace Karma bool LinuxWindow::OnResize(WindowResizeEvent& event) { - return m_Context->OnWindowResize(event); + return false;//m_Context->OnWindowResize(event); } void LinuxWindow::SetGLFWCallbacks(GLFWwindow* glfwWindow) @@ -216,17 +215,17 @@ namespace Karma { glfwDestroyWindow(m_Window); glfwTerminate(); - if (m_Context) + /*if (m_Context) { delete m_Context; m_Context = 0; - } + }*/ } void LinuxWindow::OnUpdate() { glfwPollEvents(); - m_Context->SwapBuffers(); + //m_Context->SwapBuffers(); } void LinuxWindow::SetVSync(bool enabled) diff --git a/Karma/src/Platform/Mac/Core/MacPlatformMemory.h b/Karma/src/Platform/Mac/Core/MacPlatformMemory.h index 49a9430a..066b42a9 100644 --- a/Karma/src/Platform/Mac/Core/MacPlatformMemory.h +++ b/Karma/src/Platform/Mac/Core/MacPlatformMemory.h @@ -9,8 +9,6 @@ #pragma once -#include "krpch.h" - #include "Core/TrueCore/GenericPlatformMemory.h" namespace Karma diff --git a/Karma/src/Platform/Mac/KarmaRHI/MacDynamicRHI.cpp b/Karma/src/Platform/Mac/KarmaRHI/MacDynamicRHI.cpp new file mode 100644 index 00000000..74e92f6a --- /dev/null +++ b/Karma/src/Platform/Mac/KarmaRHI/MacDynamicRHI.cpp @@ -0,0 +1,21 @@ +#include "MacDynamicRHI.h" +#include "VulkanDynamicRHI.h" + +#include "Log.h" + +namespace Karma +{ +#ifdef KR_MAC_PLATFORM + FDynamicRHI* PlatformCreateDynamicRHI() + { + FDynamicRHI* DynamicRHI = FDynamicRHI::CreateRHI(); + + if (DynamicRHI) + { + KR_CORE_INFO("Created DynamicRHI for MacOS"); + } + + return DynamicRHI; + } +#endif +} diff --git a/Karma/src/Platform/Mac/KarmaRHI/MacDynamicRHI.h b/Karma/src/Platform/Mac/KarmaRHI/MacDynamicRHI.h new file mode 100644 index 00000000..4190e56e --- /dev/null +++ b/Karma/src/Platform/Mac/KarmaRHI/MacDynamicRHI.h @@ -0,0 +1,18 @@ +/** + * @file MacDynamicRHI.h + * @brief + * @author Ravi Mohan (the_cowboy) + * @version 1.0 + * @date December 15, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include "KarmaRHI.h" + +namespace Karma +{ + +} diff --git a/Karma/src/Platform/Mac/MacWindow.cpp b/Karma/src/Platform/Mac/MacWindow.cpp index f7f34539..7f1d1181 100644 --- a/Karma/src/Platform/Mac/MacWindow.cpp +++ b/Karma/src/Platform/Mac/MacWindow.cpp @@ -1,5 +1,4 @@ #include "MacWindow.h" -#include "Karma/Log.h" #include "Karma/Events/ApplicationEvent.h" #include "Karma/Events/KeyEvent.h" #include "Karma/Events/MouseEvent.h" @@ -37,7 +36,7 @@ namespace Karma bool MacWindow::OnResize(WindowResizeEvent& event) { - return m_Context->OnWindowResize(event); + return false; //m_Context->OnWindowResize(event); } void MacWindow::Init(const WindowProps& props) @@ -82,7 +81,8 @@ namespace Karma m_Window = glfwCreateWindow((int)props.Width, (int)props.Height, m_Data.Title.c_str(), nullptr, nullptr); - switch (currentAPI) + // To be replaced with RHI + /*switch (currentAPI) { case RendererAPI::API::None: KR_CORE_ASSERT(false, "RendererAPI::None is not supported"); @@ -95,7 +95,7 @@ namespace Karma break; } - m_Context->Init(); + m_Context->Init();*/ SetVSync(true); // Used for event callbacks @@ -227,7 +227,7 @@ namespace Karma void MacWindow::OnUpdate() { glfwPollEvents(); - m_Context->SwapBuffers(); + //m_Context->SwapBuffers(); } void MacWindow::SetVSync(bool enabled) @@ -249,8 +249,8 @@ namespace Karma } case RendererAPI::API::Vulkan: { - VulkanContext* vContext = static_cast(m_Context); - vContext->SetVSync(enabled); + /*VulkanContext* vContext = static_cast(m_Context); + vContext->SetVSync(enabled);*/ break; } case RendererAPI::API::None: diff --git a/Karma/src/Platform/OpenGL/KarmaGuiOpenGLHandler.cpp b/Karma/src/Platform/OpenGL/KarmaGuiOpenGLHandler.cpp index 6e8ce1c5..13bf48a4 100644 --- a/Karma/src/Platform/OpenGL/KarmaGuiOpenGLHandler.cpp +++ b/Karma/src/Platform/OpenGL/KarmaGuiOpenGLHandler.cpp @@ -349,14 +349,14 @@ namespace Karma sceneToDraw = static_cast(pcmd->UserCallbackData); if (sceneToDraw) { - std::shared_ptr openGLVA = static_pointer_cast(sceneToDraw->GetRenderableVertexArray()); + /*std::shared_ptr openGLVA = static_pointer_cast(sceneToDraw->GetRenderableVertexArray()); openGLVA->UpdateProcessAndSetReadyForSubmission(); openGLVA->Bind(); // A very experimental hack OpenGLImageBuffer::BindTexture(); - glDrawElements(GL_TRIANGLES, openGLVA->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr); + glDrawElements(GL_TRIANGLES, openGLVA->GetIndexBuffer()->GetCount(), GL_UNSIGNED_INT, nullptr);*/ } KarmaGuiOpenGLHandler::KarmaGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); } diff --git a/Karma/src/Platform/OpenGL/OpenGLBuffer.h b/Karma/src/Platform/OpenGL/OpenGLBuffer.h index e220fa43..efd5372b 100644 --- a/Karma/src/Platform/OpenGL/OpenGLBuffer.h +++ b/Karma/src/Platform/OpenGL/OpenGLBuffer.h @@ -206,6 +206,8 @@ namespace Karma * @since Karma 1.0.0 */ virtual void UploadUniformBuffer(size_t frameIndex) override; + + virtual void UpdateCameraUniform() override {} private: /** diff --git a/Karma/src/Platform/OpenGL/OpenGLContext.cpp b/Karma/src/Platform/OpenGL/OpenGLContext.cpp index efa5adde..8ac847aa 100644 --- a/Karma/src/Platform/OpenGL/OpenGLContext.cpp +++ b/Karma/src/Platform/OpenGL/OpenGLContext.cpp @@ -1,7 +1,7 @@ #include "OpenGLContext.h" + #include "glad/glad.h" #include "GLFW/glfw3.h" -#include "Karma/Core.h" namespace Karma { diff --git a/Karma/src/Platform/OpenGL/OpenGLShader.cpp b/Karma/src/Platform/OpenGL/OpenGLShader.cpp index 8a9b261c..94712bd3 100644 --- a/Karma/src/Platform/OpenGL/OpenGLShader.cpp +++ b/Karma/src/Platform/OpenGL/OpenGLShader.cpp @@ -5,7 +5,7 @@ namespace Karma { - OpenGLShader::OpenGLShader(const std::string& vertexSrc, const std::string& fragmentSrc) : Shader(nullptr) + OpenGLShader::OpenGLShader(const std::string& vertexSrc, const std::string& fragmentSrc) : Shader() { // Create an empty vertex shader handle GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); @@ -109,8 +109,7 @@ namespace Karma glDetachShader(program, fragmentShader); } - OpenGLShader::OpenGLShader(const std::string& vertexSrcFile, const std::string& fragmentSrcFile, std::shared_ptr ubo, - const std::string& shaderName) : Shader(ubo) + OpenGLShader::OpenGLShader(const std::string& vertexSrcFile, const std::string& fragmentSrcFile, const std::string& shaderName) : Shader() { m_ShaderName = shaderName; @@ -120,12 +119,6 @@ namespace Karma shaderSources[GL_FRAGMENT_SHADER] = KarmaUtilities::ReadFileToSpitString(fragmentSrcFile); Compile(shaderSources); - - m_UniformBufferObject = std::static_pointer_cast(ubo); - if (m_UniformBufferObject == nullptr) - { - KR_CORE_WARN("Cast to OpenGLUniformBuffer failed."); - } } void OpenGLShader::Compile(const std::unordered_map& shaderSources) diff --git a/Karma/src/Platform/OpenGL/OpenGLShader.h b/Karma/src/Platform/OpenGL/OpenGLShader.h index 722cd83e..b9c44628 100644 --- a/Karma/src/Platform/OpenGL/OpenGLShader.h +++ b/Karma/src/Platform/OpenGL/OpenGLShader.h @@ -52,8 +52,7 @@ namespace Karma * * @since Karma 1.0.0 */ - OpenGLShader(const std::string& vertexSrcFile, const std::string& fragmentSrcFile, std::shared_ptr ubo, - const std::string& shaderName); + OpenGLShader(const std::string& vertexSrcFile, const std::string& fragmentSrcFile, const std::string& shaderName); /** * @brief Deletes the appropriate resources, no longer in use, and clean up @@ -111,6 +110,5 @@ namespace Karma private: // OpenGL's identification scheme uint32_t m_RendererID; - std::shared_ptr m_UniformBufferObject; }; } diff --git a/Karma/src/Platform/Vulkan/KarmaGuiVulkanHandler.cpp b/Karma/src/Platform/Vulkan/KarmaGuiVulkanHandler.cpp index 8b9ead70..ef24986b 100644 --- a/Karma/src/Platform/Vulkan/KarmaGuiVulkanHandler.cpp +++ b/Karma/src/Platform/Vulkan/KarmaGuiVulkanHandler.cpp @@ -4,6 +4,11 @@ #include "KarmaUtilities.h" #include "Platform/Vulkan/VulkanVertexArray.h" #include "Karma/KarmaGui/KarmaGuiRenderer.h" +#include "VulkanRHI/VulkanSwapChain.h" +#include "VulkanRHI/VulkanDynamicRHI.h" +#include "VulkanRHI/VulkanRenderPass.h" +#include "VulkanRHI/VulkanFramebuffer.h" +#include "VulkanRHI/VulkanDescriptorSets.h" // Visual Studio warnings /*#ifdef _MSC_VER @@ -236,23 +241,10 @@ namespace Karma if (windowRenderBuffers->FrameRenderBuffers == nullptr) { - RendererAPI* rAPI = RenderCommand::GetRendererAPI(); - VulkanRendererAPI* vulkanAPI = nullptr; - - if (rAPI->GetAPI() == RendererAPI::API::Vulkan) - { - vulkanAPI = static_cast(rAPI); - } - else - { - KR_CORE_ASSERT(false, "How is this even possible?"); - } - - KR_CORE_ASSERT(vulkanAPI != nullptr, "Casting to VulkanAPI failed"); - - windowRenderBuffers->Count = vulkanAPI->GetMaxFramesInFlight(); + windowRenderBuffers->Count = KarmaGuiRenderer::GetWindowData().RHIResources->VulkanSwapChain->GetMaxFramesInFlight(); // Caution: Need to think about the object instantiation and resource management + // Cowboy's Note: delete is done in KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_ShivaWindowRenderBuffers windowRenderBuffers->FrameRenderBuffers = new KarmaGui_ImplVulkanH_ImageFrameRenderBuffers[windowRenderBuffers->Count]; } @@ -426,7 +418,7 @@ namespace Karma VkImageCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; info.imageType = VK_IMAGE_TYPE_2D; - info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.format = VK_FORMAT_R8G8B8A8_SRGB;// VK_FORMAT_R8G8B8A8_UNORM; info.extent.width = width; info.extent.height = height; info.extent.depth = 1; @@ -461,10 +453,14 @@ namespace Karma info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; info.image = imageData->TextureImage; info.viewType = VK_IMAGE_VIEW_TYPE_2D; - info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.format = VK_FORMAT_R8G8B8A8_SRGB;//VK_FORMAT_R8G8B8A8_UNORM; info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; info.subresourceRange.levelCount = 1; info.subresourceRange.layerCount = 1; + info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; result = vkCreateImageView(vulkanInfo->Device, &info, vulkanInfo->Allocator, &imageData->TextureView); KR_CORE_ASSERT(result == VK_SUCCESS, "Couldn't create image view"); @@ -529,6 +525,7 @@ namespace Karma range[0].memory = imageData->UploadBufferMemory; range[0].size = uploadSize; + // Flush issue result = vkFlushMappedMemoryRanges(vulkanInfo->Device, 1, range); KR_CORE_ASSERT(result == VK_SUCCESS, "Couldn't flush memory range"); @@ -736,6 +733,24 @@ namespace Karma return true; } + VkShaderModule KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreateShaderModule(const std::vector& code) + { + KarmaGui_ImplVulkan_Data* backendData = KarmaGuiRenderer::GetBackendRendererUserData(); + KarmaGui_ImplVulkan_InitInfo* vulkanInfo = &backendData->VulkanInitInfo; + + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size() * sizeof(uint32_t); + createInfo.pCode = code.data(); + + VkShaderModule shaderModule; + VkResult result = vkCreateShaderModule(vulkanInfo->Device, &createInfo, nullptr, &shaderModule); + + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create shader module!"); + + return shaderModule; + } + void KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreateShaderModules(VkDevice device, const VkAllocationCallbacks* allocator) { // Create the shader modules @@ -837,7 +852,7 @@ namespace Karma KR_CORE_ASSERT(result == VK_SUCCESS, "Couldn't create pipeline layout"); } - void KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationCallbacks* allocator, VkPipelineCache pipelineCache, VkRenderPass renderPass, VkSampleCountFlagBits MSAASamples, VkPipeline* pipeline, uint32_t subpass) + void KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationCallbacks* allocator, VkPipelineCache pipelineCache, VkRenderPass renderPass, VkSampleCountFlagBits MSAASamples, VkPipeline* pipeline, uint32_t subpass) { KarmaGui_ImplVulkan_Data* backendData = KarmaGuiRenderer::GetBackendRendererUserData(); KarmaGui_ImplVulkan_CreateShaderModules(device, allocator); @@ -1303,107 +1318,295 @@ namespace Karma if(!backendData->OffScreenRR.bAllocationDoneOnce) { CreateOffScreenTextureRenderpassResource(); - - // This variable is also used to create sampler in KarmaGuiRenderer::Add3DSceneFor2DRendering once - backendData->OffScreenRR.bAllocationDoneOnce = true; } for(auto it = backendData->Elements3DTo2D.begin(); it != backendData->Elements3DTo2D.end(); ++it) { + if (!backendData->OffScreenRR.bAllocationDoneOnce) + { + // assuming that all 3D scenes use same pipeline layout. This may change later + CreateOffScreenTextureGraphicsPipelineResource(backendData->OffScreenRR.RenderPass->GetHandle(), it->Size.x, it->Size.y); + + // This variable is also used to create sampler in KarmaGuiRenderer::Add3DSceneFor2DRendering once + backendData->OffScreenRR.bAllocationDoneOnce = true; + } + CreateOffScreenTextureDepthResource(&(*it)); - //CreateOffScreenTextureRenderpassResource(&(*it)); - CreateOffScreenTextureFrameBufferResource(&(*it)); - - std::shared_ptr vulkanVA = static_pointer_cast(it->Scene3D->GetRenderableVertexArray()); - vulkanVA->CreateKarmaGuiGraphicsPipeline(backendData->OffScreenRR.RenderPass, it->Size.x, it->Size.y); } } + static VkFormat ShaderDataTypeToVulkanType(ShaderDataType type) + { + switch (type) + { + case ShaderDataType::Float: + return VK_FORMAT_R32_SFLOAT; + case ShaderDataType::Float2: + return VK_FORMAT_R32G32_SFLOAT; + case ShaderDataType::Float3: + return VK_FORMAT_R32G32B32_SFLOAT; + case ShaderDataType::Float4: + return VK_FORMAT_R32G32B32A32_SFLOAT; + case ShaderDataType::None: + case ShaderDataType::Mat3: + case ShaderDataType::Mat4: + case ShaderDataType::Int: + case ShaderDataType::Int2: + case ShaderDataType::Int3: + case ShaderDataType::Int4: + case ShaderDataType::Bool: + // Refer Mesh::GaugeVertexDataLayout for usual datatype + // to be used in the context of vertex buffer + KR_CORE_ASSERT(false, "Weird ShaderDataType is being used") + return VK_FORMAT_UNDEFINED; + break; + } + + KR_CORE_ASSERT(false, "Vulkan doesn't support this ShaderDatatype"); + return VK_FORMAT_UNDEFINED; + } + + void KarmaGuiVulkanHandler::CreateOffScreenTextureGraphicsPipelineResource(VkRenderPass renderPassKG, float windowKGWidth, float windowKGHeight) + { + KarmaGui_ImplVulkan_Data* backendData = KarmaGuiRenderer::GetBackendRendererUserData(); + KarmaGui_ImplVulkan_InitInfo* vulkanInfo = &backendData->VulkanInitInfo; + + const KarmaVector& setLayouts = FVulkanDynamicRHI::Get().GetDevice()->GetDefaultDescriptorSetLayout()->GetHandles(); + + VkPipelineLayoutCreateInfo plInfo{}; + plInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + plInfo.setLayoutCount = static_cast(setLayouts.Num()); + plInfo.pSetLayouts = setLayouts.GetData(); + plInfo.pushConstantRangeCount = 0; + plInfo.pPushConstantRanges = nullptr; + + VkResult result = vkCreatePipelineLayout(vulkanInfo->Device, &plInfo, nullptr, &backendData->OffScreenRR.OffscreenPipelineLayout); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create pipeline layout!"); + + VkShaderModule vertShaderModule = KarmaGui_ImplVulkan_CreateShaderModule(FVulkanDynamicRHI::Get().GetDevice()->GetDefaultShader()->GetVertSpirV()); + VkShaderModule fragShaderModule = KarmaGui_ImplVulkan_CreateShaderModule(FVulkanDynamicRHI::Get().GetDevice()->GetDefaultShader()->GetFragSpirV()); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo }; + + // See Mesh::GaugeVertexDataLayout to understand the layout of vertex data we are using + BufferLayout layout; + layout.PushElement(BufferElement(ShaderDataType::Float3, "v_Position")); + layout.PushElement(BufferElement(ShaderDataType::Float2, "v_UV")); + + layout.PushElement(BufferElement(ShaderDataType::Float3, "v_Normal")); + /*layout.PushElement(BufferElement(ShaderDataType::Float4, "v_Color")); + layout.PushElement(BufferElement(ShaderDataType::Float3, "v_Normal")); + layout.PushElement(BufferElement(ShaderDataType::Float3, "v_Tangent"));*/ + + VkVertexInputBindingDescription bindingDescription = {}; + std::vector attributeDescriptions; + + bindingDescription.binding = 0; + bindingDescription.stride = layout.GetStride(); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + // Telling vulkan what to expect from vertex data in terms of attributes and their rate of loading + uint32_t index = 0; + + for (const auto& element : layout) + { + VkVertexInputAttributeDescription elementAttributeDescription{}; + elementAttributeDescription.binding = 0; + elementAttributeDescription.location = index; + elementAttributeDescription.format = ShaderDataTypeToVulkanType(element.Type); + elementAttributeDescription.offset = static_cast(element.Offset); + + attributeDescriptions.push_back(elementAttributeDescription); + index++; + } + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = windowKGWidth; + viewport.height = windowKGHeight; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent.width = windowKGWidth; + scissor.extent.height = windowKGHeight; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + // Antialiasing + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.stencilTestEnable = VK_FALSE; + + VkBool32 bLogicalOperationsAllowed = FVulkanDynamicRHI::Get().GetGpuDeviceFeatures().logicOp; + + // Mix the old and new value to produce a final color + // finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor; + // finalColor.a = newAlpha.a; + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT + | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + if (!bLogicalOperationsAllowed) + { + colorBlendAttachment.blendEnable = VK_TRUE; + } + else + { + colorBlendAttachment.blendEnable = VK_FALSE; + } + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + + // Combine the old and new value using a bitwise operation + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + if (bLogicalOperationsAllowed) + { + colorBlending.logicOpEnable = VK_TRUE; + } + else + { + colorBlending.logicOpEnable = VK_FALSE; + } + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.layout = backendData->OffScreenRR.OffscreenPipelineLayout; + pipelineInfo.renderPass = renderPassKG; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineInfo.pDepthStencilState = &depthStencil; + + VkResult resultGP = vkCreateGraphicsPipelines(vulkanInfo->Device, VK_NULL_HANDLE, + 1, &pipelineInfo, nullptr, &backendData->OffScreenRR.OffscreenGraphicsPipeline); + + KR_CORE_ASSERT(resultGP == VK_SUCCESS, "Failed to create graphics pipeline!"); + + + vkDestroyShaderModule(vulkanInfo->Device, fragShaderModule, nullptr); + vkDestroyShaderModule(vulkanInfo->Device, vertShaderModule, nullptr); + } + void KarmaGuiVulkanHandler::CreateOffScreenTextureRenderpassResource() { KarmaGui_ImplVulkan_Data* backendData = KarmaGuiRenderer::GetBackendRendererUserData(); KarmaGui_ImplVulkan_InitInfo* vulkanInfo = &backendData->VulkanInitInfo; - + + KR_CORE_INFO("Attempting to Vulkan renderpass for offscreen texture rendering of 3D scene"); + FVulkanRenderPassInfo RPInfo; + // Color Attachment - VkAttachmentDescription colorAttachment{}; - colorAttachment.format = VK_FORMAT_R8G8B8A8_UNORM; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // Clear the image at start - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // Store the result for sampling later - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // Layout when render pass starts - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;//VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // Layout when render pass ends (we transition *after* the pass) - + FVulkanRenderPassInfo::FAttachmentInfo colorAttachmentInfo; + colorAttachmentInfo.AttachmentFlags = 0; + colorAttachmentInfo.AttachmentFormat = VK_FORMAT_R8G8B8A8_UNORM; + colorAttachmentInfo.AttachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; + colorAttachmentInfo.AttachmentLoadOperation = VK_ATTACHMENT_LOAD_OP_CLEAR; // Clear the image at start + colorAttachmentInfo.AttachmentStoreOperation = VK_ATTACHMENT_STORE_OP_STORE; // Store the result for sampling later + colorAttachmentInfo.AttachmentStencilLoadOperation = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentInfo.AttachmentStencilStoreOperation = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachmentInfo.AttachmentInitialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // Layout when render pass starts + colorAttachmentInfo.AttachmentFinalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;// Layout when render pass ends (we transition *after* the pass) + RPInfo.m_AttachmentsInfo.Add(colorAttachmentInfo); + // Depth Attachment - VkAttachmentDescription depthAttachment{}; - depthAttachment.format = VulkanHolder::GetVulkanContext()->FindDepthFormat(); - depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - // --- Subpass and References --- - - // Reference for the color attachment in the subpass - VkAttachmentReference colorAttachmentRef{}; - colorAttachmentRef.attachment = 0; // Index 0 in the pAttachments array - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - // Reference for the depth attachment in the subpass - VkAttachmentReference depthAttachmentRef{}; - depthAttachmentRef.attachment = 1; // Index 1 in the pAttachments array - depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - // The subpass itself - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - subpass.pDepthStencilAttachment = &depthAttachmentRef; - - // Subpass dependencies for layout transitions ########## seems IMPORTANT for layout transitions ################ - // https://github.com/1111mp/Vulkan/blob/7e65729c9782d00ee87e70fb25b711ccc0b71b64/src/Application.cpp#L1627 - std::array dependencies; - - dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; - dependencies[0].dstSubpass = 0; - dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; - dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; - dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; - - dependencies[1].srcSubpass = VK_SUBPASS_EXTERNAL; - dependencies[1].dstSubpass = 0; - dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; - dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; - dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + FVulkanRenderPassInfo::FAttachmentInfo depthAttachmentInfo; + depthAttachmentInfo.AttachmentFlags = 0; + depthAttachmentInfo.AttachmentFormat = FVulkanDynamicRHI::Get().FindDepthFormat(); + depthAttachmentInfo.AttachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; + depthAttachmentInfo.AttachmentLoadOperation = VK_ATTACHMENT_LOAD_OP_CLEAR; // Clear the image at start + depthAttachmentInfo.AttachmentStoreOperation = VK_ATTACHMENT_STORE_OP_STORE; // Store the result for sampling later + depthAttachmentInfo.AttachmentStencilLoadOperation = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachmentInfo.AttachmentStencilStoreOperation = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachmentInfo.AttachmentInitialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // Layout when render pass starts + depthAttachmentInfo.AttachmentFinalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;// Layout when render pass ends (we transition *after* the pass) + RPInfo.m_AttachmentsInfo.Add(depthAttachmentInfo); - - // --- Render Pass Creation --- - - std::vector attachments = {colorAttachment, depthAttachment}; - VkRenderPassCreateInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = static_cast(dependencies.size()); - renderPassInfo.pDependencies = dependencies.data(); - - // Subpass dependencies for synchronization could be added here, - // but the manual pipeline barrier after the pass is generally cleaner for this use case. + RPInfo.bHasDepthAttachment = true; + + FVulkanRenderPassInfo::FAttachmentRefInfo colorAttachmentReference; + colorAttachmentReference.attachment = 0; + colorAttachmentReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + RPInfo.m_ColorAttachmentsRefInfo.Add(colorAttachmentReference); + + RPInfo.m_DepthAttachmentReference.attachment = 1; + RPInfo.m_DepthAttachmentReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + FVulkanRenderTargetLayout RTLayout(RPInfo); + backendData->OffScreenRR.RenderPass = new FVulkanRenderPass(*FVulkanDynamicRHI::Get().GetDevice(), RTLayout); - VkResult result = vkCreateRenderPass(vulkanInfo->Device, &renderPassInfo, nullptr, &backendData->OffScreenRR.RenderPass); - KR_CORE_ASSERT(result == VK_SUCCESS, "Couldn't off screen render pass"); + // --- had couple of dependencies for layouts change --- } void KarmaGuiVulkanHandler::CreateOffScreenTextureFrameBufferResource(KarmaGui_3DScene_To_2DTexture_Data* textureData) @@ -1418,7 +1621,7 @@ namespace Karma VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = backendData->OffScreenRR.RenderPass; + framebufferInfo.renderPass = backendData->OffScreenRR.RenderPass->GetHandle(); framebufferInfo.attachmentCount = static_cast(attachments.size()); framebufferInfo.pAttachments = attachments.data(); framebufferInfo.width = textureData->Size.x; @@ -1443,7 +1646,7 @@ namespace Karma VkImageCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; info.imageType = VK_IMAGE_TYPE_2D; - info.format = VulkanHolder::GetVulkanContext()->FindDepthFormat(); + info.format = FVulkanDynamicRHI::Get().FindDepthFormat(); info.extent.width = textureData->Size.x; info.extent.height = textureData->Size.y; info.extent.depth = 1; @@ -1454,7 +1657,6 @@ namespace Karma info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; //info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - info.samples = VK_SAMPLE_COUNT_1_BIT; result = vkCreateImage(vulkanInfo->Device, &info, vulkanInfo->Allocator, &textureData->DepthImage); KR_CORE_ASSERT(result == VK_SUCCESS, "Couldn't create a image"); @@ -1464,7 +1666,7 @@ namespace Karma VkMemoryAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = req.size; - allocInfo.memoryTypeIndex = VulkanHolder::GetVulkanContext()->FindMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);//KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits); + allocInfo.memoryTypeIndex = FVulkanDynamicRHI::Get().FindMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); result = vkAllocateMemory(vulkanInfo->Device, &allocInfo, vulkanInfo->Allocator, &textureData->DepthDeviceMemory); KR_CORE_ASSERT(result == VK_SUCCESS, "Couldn't allocate memory"); @@ -1479,7 +1681,7 @@ namespace Karma info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; info.image = textureData->DepthImage; info.viewType = VK_IMAGE_VIEW_TYPE_2D; - info.format = VulkanHolder::GetVulkanContext()->FindDepthFormat(); + info.format = FVulkanDynamicRHI::Get().FindDepthFormat(); info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; info.subresourceRange.levelCount = 1; info.subresourceRange.layerCount = 1; @@ -1491,98 +1693,283 @@ namespace Karma } } - void KarmaGuiVulkanHandler::ShareVulkanContextResourcesOfMainWindow(KarmaGui_ImplVulkanH_Window* windowData, bool bCreateSyncronicity) + void KarmaGuiVulkanHandler::CheckInitialization() { KarmaGui_ImplVulkan_Data* backendData = KarmaGuiRenderer::GetBackendRendererUserData(); - KarmaGui_ImplVulkan_InitInfo* vulkanInfo = &backendData->VulkanInitInfo; + KarmaGui_ImplVulkan_InitInfo& vulkanInitInfo = backendData->VulkanInitInfo; + + KR_CORE_ASSERT(vulkanInitInfo.Instance != VK_NULL_HANDLE, "No instance found"); + KR_CORE_ASSERT(vulkanInitInfo.PhysicalDevice != VK_NULL_HANDLE, "No physical device found"); + KR_CORE_ASSERT(vulkanInitInfo.Device != VK_NULL_HANDLE, "No device found"); + KR_CORE_ASSERT(vulkanInitInfo.Queue != VK_NULL_HANDLE, "No queue assigned"); + KR_CORE_ASSERT(vulkanInitInfo.DescriptorPool != VK_NULL_HANDLE, "No descriptor pool found"); + KR_CORE_ASSERT(vulkanInitInfo.MinImageCount >= 2, "Minimum image count exceeding limit"); + KR_CORE_ASSERT(vulkanInitInfo.ImageCount >= vulkanInitInfo.MinImageCount, "Not enough pitch for ImageCount"); + KR_CORE_ASSERT(vulkanInitInfo.RenderPass != VK_NULL_HANDLE, "No renderpass assigned"); + } - // Clear the structure with now redundant information - ClearVulkanWindowData(windowData, bCreateSyncronicity); + void KarmaGuiVulkanHandler::FillWindowData(KarmaGui_ImplVulkanH_Window* windowData, bool bCreateSyncronicity) + { + windowData->RHIResources = new KarmaGui_ImplVulkanH_RHIResources(); - RendererAPI* rAPI = RenderCommand::GetRendererAPI(); - VulkanRendererAPI* vulkanAPI = nullptr; + windowData->RHIResources->VulkanSwapChain = FVulkanSwapChain::Create(FVulkanDynamicRHI::Get().GetDevice()); - if (rAPI->GetAPI() == RendererAPI::API::Vulkan) - { - vulkanAPI = static_cast(rAPI); - } - else - { - KR_CORE_ASSERT(false, "How is this even possible?"); - } + windowData->Swapchain = windowData->RHIResources->VulkanSwapChain->GetSwapChainHandle(); + + windowData->TotalImageCount = FVulkanDynamicRHI::Get().SwapChainImageCount(); + windowData->RenderArea.extent = windowData->RHIResources->VulkanSwapChain->GetSwapChainExtent(); + windowData->RenderArea.offset = { 0, 0 }; + windowData->MAX_FRAMES_IN_FLIGHT = windowData->RHIResources->VulkanSwapChain->GetMaxFramesInFlight(); - KR_CORE_ASSERT(vulkanAPI != nullptr, "Casting to VulkanAPI failed"); + KR_CORE_INFO("Attempting to create primary Vulkan renderpass for on-screen presentation"); + FVulkanRenderPassInfo RPInfo; + KarmaGuiVulkanHandler::MakeRenderPassInfo(windowData->RHIResources->VulkanSwapChain, RPInfo); - // Fill relevant information gathered from VulkanContext - // Assuming new swapchain and all that has been created in the KarmaGuiLayer::GiveLoopBeginControlToVulkan - windowData->Swapchain = VulkanHolder::GetVulkanContext()->GetSwapChain(); - windowData->TotalImageCount = (uint32_t)VulkanHolder::GetVulkanContext()->GetSwapChainImages().size(); - windowData->RenderArea.extent = VulkanHolder::GetVulkanContext()->GetSwapChainExtent(); - windowData->RenderArea.offset = { 0, 0 }; - windowData->RenderPass = VulkanHolder::GetVulkanContext()->GetRenderPass(); - windowData->MAX_FRAMES_IN_FLIGHT = vulkanAPI->GetMaxFramesInFlight(); - windowData->CommandPool = VulkanHolder::GetVulkanContext()->GetCommandPool(); - windowData->ImageFrameIndex = 0; + FVulkanRenderTargetLayout RTLayout(RPInfo); + windowData->RHIResources->VulkanRenderPass = new FVulkanRenderPass(*FVulkanDynamicRHI::Get().GetDevice(), RTLayout); + windowData->RenderPass = windowData->RHIResources->VulkanRenderPass->GetHandle(); + + KarmaGui_ImplVulkan_Data* backendData = KarmaGuiRenderer::GetBackendRendererUserData(); + + backendData->VulkanInitInfo.RenderPass = windowData->RenderPass; + backendData->RenderPass = windowData->RenderPass; + + backendData->Subpass = 0; + + windowData->CommandPool = FVulkanDynamicRHI::Get().GetDevice()->GetCommandPool(); KR_CORE_ASSERT(windowData->ImageFrames == nullptr, "Somehow frames are still occupied. Please clear them."); windowData->ImageFrames = new KarmaGui_ImplVulkanH_ImageFrame[windowData->TotalImageCount]; + FVulkanRenderTargetsInfo* rT = new FVulkanRenderTargetsInfo(); + windowData->RHIResources->RenderTargets.Add(rT); + + FVulkanRenderTargetsInfo& renderTargets = *rT; + CreateDepthRenderTarget(renderTargets, windowData->RHIResources->VulkanSwapChain); + + renderTargets.m_ColorRenderTargets.resize(windowData->TotalImageCount); + for (uint32_t counter = 0; counter < windowData->TotalImageCount; counter++) { KarmaGui_ImplVulkanH_ImageFrame* frameData = &windowData->ImageFrames[counter]; - // VulkanContext ImageView equivalent - frameData->BackbufferView = VulkanHolder::GetVulkanContext()->GetSwapChainImageViews()[counter]; + // Backbuffers are swapchain images + frameData->Backbuffer = windowData->RHIResources->VulkanSwapChain->GetSwapChainImages()[counter]; + // BackbufferViews are swapchain image views + frameData->BackbufferView = windowData->RHIResources->VulkanSwapChain->GetSwapChainImageViews()[counter]; + + GatherSwapChainColorRenderTargets(renderTargets, windowData->RHIResources->VulkanSwapChain, counter); + + FVulkanFramebuffer* frameBuffer = new FVulkanFramebuffer(*FVulkanDynamicRHI::Get().GetDevice(), renderTargets, RTLayout, *windowData->RHIResources->VulkanRenderPass, counter); + + windowData->RHIResources->VulkanFrameBuffers.Add(frameBuffer); + // Framebuffer - frameData->Framebuffer = VulkanHolder::GetVulkanContext()->GetSwapChainFrameBuffer()[counter]; + frameData->Framebuffer = frameBuffer->GetHandle(); + } - // Backbuffers could be VulkanContext m_swapChainImages equivalent - frameData->Backbuffer = VulkanHolder::GetVulkanContext()->GetSwapChainImages()[counter]; + if(windowData->FramesOnFlight == nullptr) + { + windowData->FramesOnFlight = new KarmaGui_Vulkan_Frame_On_Flight[windowData->MAX_FRAMES_IN_FLIGHT]; } + KR_CORE_ASSERT(windowData->FramesOnFlight != nullptr, "Commandbuffers are being assigned without enough resources"); + + std::vector commandBuffers; + commandBuffers.resize(windowData->MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = windowData->CommandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + VkResult result = vkAllocateCommandBuffers(FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(), &allocInfo, commandBuffers.data()); + + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create command buffers!"); + + for (uint32_t counter = 0; counter < windowData->MAX_FRAMES_IN_FLIGHT; counter++) + { + KarmaGui_Vulkan_Frame_On_Flight* frameOnFlight = &windowData->FramesOnFlight[counter]; + frameOnFlight->CommandBuffer = commandBuffers[counter]; + } + // We create seperate syncronicity resources for Dear KarmaGui if (bCreateSyncronicity) { - // For syncronicity, we instantiate FramesOnFlight with MAX_FRAMES_IN_FLIGHT number and label them the Real-Frames-On-Flight in our mind - KR_CORE_ASSERT(windowData->FramesOnFlight == nullptr, "Somehow frames-on-flight are still occupied. Please clear them."); - - windowData->FramesOnFlight = new KarmaGui_Vulkan_Frame_On_Flight[windowData->MAX_FRAMES_IN_FLIGHT]; - for (uint32_t counter = 0; counter < windowData->MAX_FRAMES_IN_FLIGHT; counter++) { KarmaGui_Vulkan_Frame_On_Flight* frameOnFlight = &windowData->FramesOnFlight[counter]; - VkFenceCreateInfo fenceInfo = {}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - VkResult result = vkCreateFence(vulkanInfo->Device, &fenceInfo, VK_NULL_HANDLE, &frameOnFlight->Fence); - - KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create fence"); + frameOnFlight->Fence = FVulkanDynamicRHI::Get().GetDevice()->GetFenceManager().AllocateFence(true); VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - result = vkCreateSemaphore(vulkanInfo->Device, &semaphoreInfo, VK_NULL_HANDLE, &frameOnFlight->ImageAcquiredSemaphore); + result = vkCreateSemaphore(FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(), &semaphoreInfo, VK_NULL_HANDLE, &frameOnFlight->ImageAcquiredSemaphore); KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create ImageAcquiredSemaphore"); - result = vkCreateSemaphore(vulkanInfo->Device, &semaphoreInfo, VK_NULL_HANDLE, &frameOnFlight->RenderCompleteSemaphore); + result = vkCreateSemaphore(FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(), &semaphoreInfo, VK_NULL_HANDLE, &frameOnFlight->RenderCompleteSemaphore); KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create RenderCompleteSemaphore"); } windowData->SemaphoreIndex = 0; } + } - KR_CORE_ASSERT(windowData->FramesOnFlight != nullptr, "Commandbuffers are being assigned without enough resources"); - vulkanAPI->AllocateCommandBuffers(); + void KarmaGuiVulkanHandler::CreateDepthRenderTarget(FVulkanRenderTargetsInfo &RTInfo, FVulkanSwapChain *SwapChain) + { + VkFormat depthFormat = FVulkanDynamicRHI::Get().FindDepthFormat(); + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = SwapChain->GetSwapChainExtent().width; + imageInfo.extent.height = SwapChain->GetSwapChainExtent().height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = depthFormat; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.flags = 0; + + VkDevice logicalDevice = FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(); + + VkResult result = vkCreateImage(logicalDevice, &imageInfo, nullptr, &RTInfo.m_DepthRenderTarget.m_DepthRTImage); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create depthimage!"); + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(logicalDevice, RTInfo.m_DepthRenderTarget.m_DepthRTImage, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = FVulkanDynamicRHI::Get().FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VkResult result1 = vkAllocateMemory(logicalDevice, &allocInfo, nullptr, &RTInfo.m_DepthRenderTarget.m_DepthRTDeviceMemory); + KR_CORE_ASSERT(result1 == VK_SUCCESS, "Failed to allocate depth image memeory"); + + vkBindImageMemory(logicalDevice, RTInfo.m_DepthRenderTarget.m_DepthRTImage, RTInfo.m_DepthRenderTarget.m_DepthRTDeviceMemory, 0); + + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = RTInfo.m_DepthRenderTarget.m_DepthRTImage; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = depthFormat; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + result = vkCreateImageView(logicalDevice, &viewInfo, nullptr, &RTInfo.m_DepthRenderTarget.m_DepthRTView); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create depth imageview"); + + RTInfo.bDepthRenderTarget = true; + } + + void KarmaGuiVulkanHandler::GatherSwapChainColorRenderTargets(class FVulkanRenderTargetsInfo &RTInfo, FVulkanSwapChain *SwapChain, uint32_t SwapChainImageIndex) + { + RTInfo.m_ColorRenderTargets[SwapChainImageIndex].m_ColorRTViews[0] = SwapChain->GetSwapChainImageViews()[SwapChainImageIndex]; + + RTInfo.m_ColorRenderTargets[SwapChainImageIndex].bSwapChainColorRenderTarget = true; + RTInfo.m_NumColorRenderTargets = 1; + } + + void KarmaGuiVulkanHandler::MakeRenderPassInfo(FVulkanSwapChain* SwapChain, FVulkanRenderPassInfo& RPInfo) + { + FVulkanRenderPassInfo::FAttachmentInfo colorAttachmentInfo; + colorAttachmentInfo.AttachmentFlags = 0; + colorAttachmentInfo.AttachmentFormat = SwapChain->GetSwapChainImageFormat(); + colorAttachmentInfo.AttachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; + colorAttachmentInfo.AttachmentLoadOperation = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachmentInfo.AttachmentStoreOperation = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachmentInfo.AttachmentStencilLoadOperation = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentInfo.AttachmentStencilStoreOperation = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachmentInfo.AttachmentInitialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachmentInfo.AttachmentFinalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + RPInfo.m_AttachmentsInfo.Add(colorAttachmentInfo); + + FVulkanRenderPassInfo::FAttachmentInfo depthAttachmentInfo; + depthAttachmentInfo.AttachmentFlags = 0; + depthAttachmentInfo.AttachmentFormat = FVulkanDynamicRHI::Get().FindDepthFormat(); + depthAttachmentInfo.AttachmentSampleCount = VK_SAMPLE_COUNT_1_BIT; + depthAttachmentInfo.AttachmentLoadOperation = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachmentInfo.AttachmentStoreOperation = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachmentInfo.AttachmentStencilLoadOperation = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachmentInfo.AttachmentStencilStoreOperation = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachmentInfo.AttachmentInitialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthAttachmentInfo.AttachmentFinalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + RPInfo.m_AttachmentsInfo.Add(depthAttachmentInfo); + + RPInfo.bHasDepthAttachment = true; + + FVulkanRenderPassInfo::FAttachmentRefInfo colorAttachmentReference; + colorAttachmentReference.attachment = 0; + colorAttachmentReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + RPInfo.m_ColorAttachmentsRefInfo.Add(colorAttachmentReference); + + RPInfo.m_DepthAttachmentReference.attachment = 1; + RPInfo.m_DepthAttachmentReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + RPInfo.m_RenderArea.extent = SwapChain->GetSwapChainExtent(); + } + void KarmaGuiVulkanHandler::ShivaSwapChainForRebuild(KarmaGui_ImplVulkanH_Window* windowData) + { + VkDevice logicalDevice = FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(); + + std::vector commandBuffers; + commandBuffers.resize(windowData->MAX_FRAMES_IN_FLIGHT); + for (uint32_t counter = 0; counter < windowData->MAX_FRAMES_IN_FLIGHT; counter++) { KarmaGui_Vulkan_Frame_On_Flight* frameOnFlight = &windowData->FramesOnFlight[counter]; - frameOnFlight->CommandBuffer = vulkanAPI->GetCommandBuffers()[counter]; + commandBuffers[counter] = frameOnFlight->CommandBuffer; + } + + vkDeviceWaitIdle(logicalDevice); + + vkFreeCommandBuffers(logicalDevice, windowData->CommandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + ClearVulkanWindowData(windowData, false); + + for(const auto& vulkanFramebuffer : windowData->RHIResources->VulkanFrameBuffers) + { + delete vulkanFramebuffer; } + windowData->RHIResources->VulkanFrameBuffers.Clear(); + + // DepthRenderTarget of the swapchain should be destroyed here. This assumes depth render target is only used for swapchain + for(const auto& renderTargets : windowData->RHIResources->RenderTargets) + { + if(renderTargets->bDepthRenderTarget) + { + vkDestroyImageView(logicalDevice, renderTargets->m_DepthRenderTarget.m_DepthRTView, nullptr); + vkDestroyImage(logicalDevice, renderTargets->m_DepthRenderTarget.m_DepthRTImage, nullptr); + vkFreeMemory(logicalDevice, renderTargets->m_DepthRenderTarget.m_DepthRTDeviceMemory, nullptr); + } + + delete renderTargets; + } + windowData->RHIResources->RenderTargets.Clear(); + + delete windowData->RHIResources->VulkanRenderPass; + + FVulkanSwapChainRecreateInfo RI{}; + windowData->RHIResources->VulkanSwapChain->Destroy(&RI); + delete windowData->RHIResources->VulkanSwapChain; + windowData->RHIResources->VulkanSwapChain = nullptr; + + delete windowData->RHIResources; } void KarmaGuiVulkanHandler::ClearVulkanWindowData(KarmaGui_ImplVulkanH_Window* vulkanWindowData, bool bDestroySyncronicity) @@ -1606,7 +1993,6 @@ namespace Karma { if (vulkanWindowData->FramesOnFlight != nullptr) { - // Remove syncronicity resources using Vulkan API DestroyFramesOnFlightData(&vulkanWindowData->FramesOnFlight[counter]); } } @@ -1652,8 +2038,9 @@ namespace Karma vkDeviceWaitIdle(vulkanInfo->Device); - vkDestroyFence(vulkanInfo->Device, frameSyncronicityData->Fence, VK_NULL_HANDLE); - frameSyncronicityData->Fence = VK_NULL_HANDLE; + //vkDestroyFence(vulkanInfo->Device, frameSyncronicityData->Fence, VK_NULL_HANDLE); + //frameSyncronicityData->Fence = VK_NULL_HANDLE; + FVulkanDynamicRHI::Get().GetDevice()->GetFenceManager().ReleaseFence(frameSyncronicityData->Fence); vkDestroySemaphore(vulkanInfo->Device, frameSyncronicityData->ImageAcquiredSemaphore, VK_NULL_HANDLE); vkDestroySemaphore(vulkanInfo->Device, frameSyncronicityData->RenderCompleteSemaphore, VK_NULL_HANDLE); @@ -1668,24 +2055,10 @@ namespace Karma { if (bRecreateSwapChainAndCommandBuffers) { - RendererAPI* rAPI = RenderCommand::GetRendererAPI(); - VulkanRendererAPI* vulkanAPI = nullptr; - - if (rAPI->GetAPI() == RendererAPI::API::Vulkan) - { - vulkanAPI = static_cast(rAPI); - } - else - { - KR_CORE_ASSERT(false, "How is this even possible?"); - } - - KR_CORE_ASSERT(vulkanAPI != nullptr, "Casting to VulkanAPI failed"); - - vulkanAPI->RecreateCommandBuffersAndSwapChain(); + ShivaSwapChainForRebuild(windowData); } - KarmaGuiVulkanHandler::ShareVulkanContextResourcesOfMainWindow(windowData, bCreateSyncronicity); + FillWindowData(windowData, bCreateSyncronicity); } void KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_DestroyWindow(KarmaGui_ImplVulkanH_Window* windowData) @@ -1693,32 +2066,90 @@ namespace Karma KarmaGui_ImplVulkan_Data* backendData = KarmaGuiRenderer::GetBackendRendererUserData(); KarmaGui_ImplVulkan_InitInfo* vulkanInfo = &backendData->VulkanInitInfo; - vkDestroySampler(vulkanInfo->Device, backendData->OffScreenRR.Sampler, nullptr); - vkDestroyRenderPass(vulkanInfo->Device, backendData->OffScreenRR.RenderPass, nullptr); - - // Clear vulkan resources from KarmaGui_3DScene_To_2DTexture_Data - for(auto it = backendData->Elements3DTo2D.begin(); it != backendData->Elements3DTo2D.end(); ++it) + if (backendData->OffScreenRR.bAllocationDoneOnce) { - // Order of destruction to be taken in account + vkDestroyPipeline(vulkanInfo->Device, backendData->OffScreenRR.OffscreenGraphicsPipeline, nullptr); + vkDestroyPipelineLayout(vulkanInfo->Device, backendData->OffScreenRR.OffscreenPipelineLayout, nullptr); - //vkDestroySampler(vulkanInfo->Device, it->Sampler, nullptr); - vkDestroyImageView(vulkanInfo->Device, it->DepthImage_View, nullptr); + delete backendData->OffScreenRR.RenderPass; + backendData->OffScreenRR.RenderPass = nullptr; + + vkDestroySampler(vulkanInfo->Device, backendData->OffScreenRR.Sampler, nullptr); + } + // Clear vulkan resources from KarmaGui_3DScene_To_2DTexture_Data + for (auto it = backendData->Elements3DTo2D.begin(); it != backendData->Elements3DTo2D.end(); ++it) + { + vkDestroyImageView(vulkanInfo->Device, it->DepthImage_View, nullptr); vkDestroyImage(vulkanInfo->Device, it->DepthImage, nullptr); vkFreeMemory(vulkanInfo->Device, it->DepthDeviceMemory, nullptr); - vkDestroyImageView(vulkanInfo->Device, it->Image_View, nullptr); vkDestroyImage(vulkanInfo->Device, it->Image, nullptr); vkFreeMemory(vulkanInfo->Device, it->DeviceMemory, nullptr); vkDestroyFramebuffer(vulkanInfo->Device, it->FrameBuffer, nullptr); + } - std::shared_ptr vulkanVA = static_pointer_cast(it->Scene3D->GetRenderableVertexArray()); - vulkanVA->CleanupKarmaGuiGraphicsPipeline(); + VkDevice logicalDevice = FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(); + + std::vector commandBuffers; + commandBuffers.resize(windowData->MAX_FRAMES_IN_FLIGHT); + + for (uint32_t counter = 0; counter < windowData->MAX_FRAMES_IN_FLIGHT; counter++) + { + KarmaGui_Vulkan_Frame_On_Flight* frameOnFlight = &windowData->FramesOnFlight[counter]; + commandBuffers[counter] = frameOnFlight->CommandBuffer; + } + + vkFreeCommandBuffers(logicalDevice, windowData->CommandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + // 1. Removes synchronicity + // 2. Deletes imageframes and frames on flight objects + ClearVulkanWindowData(windowData, true); - //vkDestroyRenderPass(vulkanInfo->Device, it->RenderPass, nullptr); + FVulkanDynamicRHI::Get().GetDevice()->GetFenceManager().Denit(); + + for(const auto& vulkanFramebuffer : windowData->RHIResources->VulkanFrameBuffers) + { + delete vulkanFramebuffer; + } + windowData->RHIResources->VulkanFrameBuffers.Clear(); + + // What to do with the rendertargets of swapchain which are destroyed in FVulkanSwapChain::Destroy + // A heuristic is used and swapchain rendertargets are not cleared here, they are cleared in ^^ + for(const auto& renderTargets : windowData->RHIResources->RenderTargets) + { + for(uint32_t counter = 0; counter < FVulkanDynamicRHI::Get().SwapChainImageCount(); counter++) + { + if(!renderTargets->m_ColorRenderTargets[counter].bSwapChainColorRenderTarget) + { + for(uint32_t colorRT = 0; colorRT < renderTargets->m_NumColorRenderTargets; colorRT++) + { + vkDestroyImageView(logicalDevice, renderTargets->m_ColorRenderTargets[counter].m_ColorRTViews[colorRT], nullptr); + vkDestroyImage(logicalDevice, renderTargets->m_ColorRenderTargets[counter].m_ColorRTImages[colorRT], nullptr); + vkFreeMemory(logicalDevice, renderTargets->m_ColorRenderTargets[counter].m_ColorRTDeviceMemory[colorRT], nullptr); + } + } + } + + if(renderTargets->bDepthRenderTarget) + { + vkDestroyImageView(logicalDevice, renderTargets->m_DepthRenderTarget.m_DepthRTView, nullptr); + vkDestroyImage(logicalDevice, renderTargets->m_DepthRenderTarget.m_DepthRTImage, nullptr); + vkFreeMemory(logicalDevice, renderTargets->m_DepthRenderTarget.m_DepthRTDeviceMemory, nullptr); + } + + delete renderTargets; } + windowData->RHIResources->RenderTargets.Clear(); + + delete windowData->RHIResources->VulkanRenderPass; - ClearVulkanWindowData(windowData, true); + FVulkanSwapChainRecreateInfo RI{}; + windowData->RHIResources->VulkanSwapChain->Destroy(&RI); + delete windowData->RHIResources->VulkanSwapChain; + windowData->RHIResources->VulkanSwapChain = nullptr; + + delete windowData->RHIResources; } //-------------------------------------------------------------------------------------------------------- @@ -1807,14 +2238,6 @@ namespace Karma frameData = &windowData->ImageFrames[windowData->ImageFrameIndex]; } - for (;;) - { - result = vkWaitForFences(vulkanInfo->Device, 1, &frameFlightData->Fence, VK_TRUE, 100); - if (result == VK_SUCCESS) break; - if (result == VK_TIMEOUT) continue; - - KR_CORE_ASSERT((result == VK_SUCCESS || VK_TIMEOUT), "Something not right"); - } { //result = vkResetCommandPool(vulkanInfo->Device, windowData->CommandPool, 0); @@ -1868,11 +2291,6 @@ namespace Karma result = vkEndCommandBuffer(frameFlightData->CommandBuffer); KR_CORE_ASSERT(result == VK_SUCCESS, "Coudn't finish the commandbuffer recording") - result = vkResetFences(vulkanInfo->Device, 1, &frameFlightData->Fence); - KR_CORE_ASSERT(result == VK_SUCCESS, "Couldn't reset the fences"); - - result = vkQueueSubmit(vulkanInfo->Queue, 1, &info, frameFlightData->Fence); - KR_CORE_ASSERT(result == VK_SUCCESS, "Couldn't submit the queue"); } } } @@ -1884,7 +2302,7 @@ namespace Karma KarmaGui_ImplVulkanH_Window* windowData = &viewportData->Window; KarmaGui_ImplVulkan_InitInfo* vulkanInfo = &backendData->VulkanInitInfo; - //KarmaGuiViewport* mainViewPort = KarmaGui::GetMainViewport(); + //KarmaGuiViewport* mainViewPort = KarmaGui::GetMainViewport(); VkResult result; uint32_t presentIndex = windowData->ImageFrameIndex; diff --git a/Karma/src/Platform/Vulkan/KarmaGuiVulkanHandler.h b/Karma/src/Platform/Vulkan/KarmaGuiVulkanHandler.h index 8de9a9bd..4ae8d6eb 100644 --- a/Karma/src/Platform/Vulkan/KarmaGuiVulkanHandler.h +++ b/Karma/src/Platform/Vulkan/KarmaGuiVulkanHandler.h @@ -17,15 +17,19 @@ #pragma once -#include "krpch.h" - #include "KarmaGui.h" #include "KarmaGuiInternal.h" #include "Karma/Renderer/Scene.h" +#include "KarmaTypes.h" #include namespace Karma { + class FVulkanSwapChain; + class FVulkanRenderPass; + class FVulkanRenderTargetsInfo; + class FVulkanFramebuffer; + class FVulkanFence; /** * @brief Macro for vulkan's alignment needs. @@ -97,11 +101,16 @@ namespace Karma VkDescriptorPool DescriptorPool; /** - * @brief A handle to render pass object which represents a collection of attachments (depth attachment or colo(u)r attachment ), subpasses, and dependencies between the subpasses, and describes how the attachments are used over the course of the subpasses. Taken from the one created in VulkanContext::CreateRenderPass(). + * @brief A handle to render pass object which represents a collection of attachments (depth attachment or colo(u)r attachment ), subpasses, and dependencies between the subpasses, and describes how the attachments are used over the course of the subpasses. + * + * Taken from the one created in VulkanContext::CreateRenderPass(). * + * @note Used to set KarmaGui_ImplVulkan_Data::RenderPass (backend data) in KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_Init() + * + * @see KarmaGuiRenderer::SetupKarmaGuiRenderer * @since Karma 1.0.0 */ - VkRenderPass RenderPass; // Very experimental here + VkRenderPass RenderPass; // begin Optional /** @@ -112,7 +121,7 @@ namespace Karma VkPipelineCache PipelineCache; /** - * @brief Not sure what the sue of this Subpass is. Seems vestigial? + * @brief Not sure what the use of this Subpass is. Seems vestigial? * * @since Karma 1.0.0 */ @@ -130,7 +139,6 @@ namespace Karma /** * @brief The number of elements in the pSwapchainImages array, i.e number of presentable swapchain images available (vkGetSwapchainImagesKHR). * - * @note Taken from VulkanContext where (VulkanContext::CreateSwapChain()) ImageCount is defined MinImageCount + 1. * @since Karma 1.0.0 */ uint32_t ImageCount; @@ -138,10 +146,24 @@ namespace Karma /** * @brief Is a VkSampleCountFlagBits value specifying the number of samples used in rasterization. This value is ignored for the purposes of setting the number of samples used in rasterization if the pipeline is created with the VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT dynamic state set, but if VK_DYNAMIC_STATE_SAMPLE_MASK_EXT dynamic state is not set, it is still used to define the size of the pSampleMask array. * - * @note >= VK_SAMPLE_COUNT_1_BIT (0 -> default to VK_SAMPLE_COUNT_1_BIT) + * This is using in the rasterization stage of the graphics pipeline to determine how many samples per pixel will be used when rendering. + * + * @note >= VK_SAMPLE_COUNT_1_BIT (0 -> default to VK_SAMPLE_COUNT_1_BIT). This setting disables multisampling, equivalent to no anti-aliasing, + * where each pixel has a single sample for color and depth processing during rasterization. Images created + * with this sample count cannot be used for multisample resolve operations or multisampled samplers, as they + * are treated as non-multisampled. Higher values like VK_SAMPLE_COUNT_2_BIT or VK_SAMPLE_COUNT_4_BIT enable + * multiple samples per pixel for improved anti-aliasing quality + * + * @note In rasterization stage the primitives are converted to fragments. Each fragment corresponds to a pixel in the framebuffer. When multisampling is enabled, multiple samples are taken within each pixel to improve image quality and reduce aliasing effects. + * @remark While using higher sample counts (for instance VK_SAMPLE_COUNT_4_BIT), I couldn't produce coherent 2D + * image here, despit of setting VK_SAMPLE_COUNT_4_BIT in KarmaGuiHandler::CreateOffScreenRenderResources() and + * appropriately setting image, depthimage, and pipeline to use that sample count. Perplexity isn't particularly + * able to help. So, I am leaving VK_SAMPLE_COUNT_1_BIT (no multisampling). + * + * @see KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreatePipeline * @since Karma 1.0.0 */ - VkSampleCountFlagBits MSAASamples; + VkSampleCountFlagBits MSAASamples; // Multi sample anti aliasing samples /** * @brief A pointer to a valid VkAllocationCallbacks structure. Structure containing callback function pointers for memory allocation @@ -193,7 +215,7 @@ namespace Karma * * @since Karma 1.0.0 */ - VkFence Fence; + FVulkanFence* Fence; /** * @brief Handle to semaphore object for next available presentable image (vkAcquireNextImageKHR). @@ -266,6 +288,31 @@ namespace Karma VkFramebuffer Framebuffer; }; + /** + * @brief Structure to hold references to Vulkan RHI resources + * + * @since Karma 1.0.0 + */ + struct KarmaGui_ImplVulkanH_RHIResources + { + /** + * @brief Primary swapchain created in KarmaGuiVulkanHandler::FillWindowData + */ + FVulkanSwapChain* VulkanSwapChain; + + /** + * @brief Primary renderpass for on-screen presentation + */ + FVulkanRenderPass* VulkanRenderPass; + KarmaVector VulkanFrameBuffers; + + /** + * @brief Collection of render targets organized in KarmaVector. Meaning each KarmaVector element contains + * a collection of rendertargets for a framebuffer + */ + KarmaVector RenderTargets; + }; + /** * @brief Helper structure to hold the data needed by entire KarmaGui primitive rendering including commandpools, renderpass, commandbuffers, * ImageFrameCount (swapchain), and SemaphoreIndex etc. @@ -336,6 +383,13 @@ namespace Karma * * * @note Taken from VulkanContext::CreateRenderPass() set in KarmaGuiVulkanHandler::ShareVulkanContextResourcesOfMainWindow() + * + * @note There are multiple RenderPass references for instance in KarmaGui_ImplVulkan_InitInfo and created in VulkanContext::CreateRenderPass(). Here, this + * RenderPass is again usually set to the one created in VulkanContext::CreateRenderPass(), meant for KarmaGui's primitive + * rendering. + * However the RenderPass in KarmaGui_OffScreenRenderResources (created in KarmaGuiVulkanHandler::CreateOffScreenRenderResources) + * is different, and meant for offscreen rendering purpose. + * * @since Karma 1.0.0 */ VkRenderPass RenderPass; @@ -388,11 +442,13 @@ namespace Karma * * @since Karma 1.0.0 */ - uint32_t MAX_FRAMES_IN_FLIGHT; + uint32_t MAX_FRAMES_IN_FLIGHT; /** - * @brief Just a container for buffers (framebuffers, images, and imageviews etc) and all those sizes depending on VulkanHolder::GetVulkanContext()->GetSwapChainImages().size(); + * @brief Just a container for rendertarget buffers (framebuffers, images, and imageviews etc) and all those sizes depending on number of swapchain images * + * @note Only SwapChain rendertargets are stored. The grand list of rendertargets (all rendertargets) is in KarmaGui_ImplVulkanH_RHIResources::RenderTargets + * @see KarmaGuiVulkanHandler::GatherSwapChainColorRenderTargets * @since Karma 1.0.0 */ KarmaGui_ImplVulkanH_ImageFrame* ImageFrames; @@ -415,6 +471,13 @@ namespace Karma */ VkRect2D RenderArea; + /** + * @brief Vulkan RHI resources containing pointers to FVulkanSwpaChain etc + * + * @since Karma 1.0.0 + */ + KarmaGui_ImplVulkanH_RHIResources* RHIResources; + /** * @brief Constructor for zero clearence and relevant stuff. * @@ -430,9 +493,13 @@ namespace Karma /** * @brief Reusable buffers used for rendering 1 current in-flight ImageFrame, for KarmaGui_ImplVulkan_RenderDrawData(). - * Seems like data structure with single instantiation for each of the FrameIndex. + * Seems like data structure with single instantiation for each of the FrameIndex (not ImageFrameIndex which is + * returned by vkGetSwapchainImagesKHR). * * @note Please zero-clear before use. + * + * @see KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_RenderDrawData + * @since Karma 1.0.0 */ struct KarmaGui_ImplVulkanH_ImageFrameRenderBuffers { @@ -566,7 +633,6 @@ namespace Karma * @since Karma 1.0.0 */ KarmaGui_ImplVulkanH_WindowRenderBuffers RenderBuffers; - //ImGui_ImplVulkanH_Window* ImGUILayerWindowData; /** * @brief A constructor for allocation and 0 initialization @@ -588,7 +654,7 @@ namespace Karma }; /** - * @brief Data structure containing the Mesa image texture relevant data. For instance, file and folder icons of Content Browser. + * @brief Data structure containing the Mesa image texture relevant data. For instance, file, and folder icons of Content Browser. * * Similar to MesaDecalData in KarmaGuiOpenGLHandler. * @@ -698,35 +764,104 @@ namespace Karma } }; + /** + * @brief Data structure for 3D scene to 2D texture rendering element(s) in KarmaGui. + * + * @see KarmaGuiRenderer::FrameRender + * @since Karma 1.0.0 + */ struct KarmaGui_3DScene_To_2DTexture_Data { - std::shared_ptr Scene3D; + /** + * @brief 3D scene to be rendered offscreen to 2D texture for display in KarmaGui element. + */ + std::shared_ptr Scene3D; + /** + * @brief Size of the 2D texture to be rendered offscreen from 3D scene. + */ KGVec2 Size; + + /** + * @brief Texture ID to be used in KarmaGui::Image() and related functions, for display of 2D rendered rendertarget + * in some KarmaGui window. + */ KGTextureID KarmaGui_Texture; + /** + * @brief Vulkan image for offscreen rendered 2D texture. + */ VkImage Image; + + /** + * @brief Device memory for the offscreen rendered 2D texture image. + */ VkDeviceMemory DeviceMemory; + + /** + * @brief Image view for the offscreen rendered 2D texture image. + */ VkImageView Image_View; - //VkSampler Sampler; - // Depth resources + /** + * @brief Depth image for offscreen rendered 2D texture. + */ VkImage DepthImage; + + /** + * @brief Device memory for the offscreen rendered 2D texture depth image. + */ VkDeviceMemory DepthDeviceMemory; + /** + * @brief Image view for the offscreen rendered 2D texture depth image. + */ VkImageView DepthImage_View; - //VkRenderPass RenderPass; + /** + * @brief Framebuffer for offscreen rendered 2D texture. + */ VkFramebuffer FrameBuffer; }; + /** + * @brief Resources common to screen rendering of 3D scene to 2D texture elements. + * + * @see KarmaGuiRenderer::FrameRender + * @since Karma 1.0.0 + */ struct KarmaGui_OffScreen_Render_Resources { + /** + * @brief Sampler for offscreen rendered 2D texture(s). + * + * A Vulkan sampler is an object that defines how to sample textures when they are accessed in shaders. + * It encapsulates various sampling parameters such as filtering modes, addressing modes, and mipmapping + * settings. + * + * @see KarmaGuiRenderer::Add3DSceneFor2DRendering + */ VkSampler Sampler; - VkRenderPass RenderPass; - // Since the resources in here require allocation only once + /** + * @brief Render pass for offscreen rendering of 3D scene to 2D texture(s). + * + * A render pass in Vulkan defines a sequence of rendering operations, including the attachments (color, depth, stencil) used during rendering, + * how they are used, and the order of subpasses. It encapsulates the rendering workflow and is essential for efficient rendering. + * + * @see KarmaGuiRenderer::Add3DSceneFor2DRendering + */ + FVulkanRenderPass* RenderPass; + + /** + * @brief Flag indicating whether the allocation of offscreen rendering resources has been done at least once. + * + * @see KarmaGuiRenderer::Add3DSceneFor2DRendering + */ bool bAllocationDoneOnce; + + VkPipelineLayout OffscreenPipelineLayout; + VkPipeline OffscreenGraphicsPipeline; KarmaGui_OffScreen_Render_Resources() : bAllocationDoneOnce(false) {} @@ -817,7 +952,7 @@ namespace Karma VkPipeline Pipeline; /** - * @brief Not sure what the use of this Subpass is. Seems vestigial? + * @brief This is the index of the subpass in the render pass where this pipeline (KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreatePipeline) will be used * * @since Karma 1.0.0 */ @@ -982,6 +1117,8 @@ namespace Karma */ static void KarmaGui_ImplVulkan_CreateShaderModules(VkDevice device, const VkAllocationCallbacks* allocator); + static VkShaderModule KarmaGui_ImplVulkan_CreateShaderModule(const std::vector& code); + /** * @brief Bilinear sampling object is created for font and stored in KarmaGui_ImplVulkan_Data.FontSampler if not done already in KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreateDeviceObjects(). * @@ -1124,21 +1261,64 @@ namespace Karma static void KarmaGui_ImplVulkan_SwapBuffers(KarmaGuiViewport* viewport, void*); /** - * @brief The purpose of the routine is two-fold + * @brief Checks whether KarmaGui_ImplVulkan_InitInfo has been initialized properly. Crashes if not. + * + * @see KarmaGuiRenderer::SetUpKarmaGuiRenderer + * @since Karma 1.0.0 + */ + static void CheckInitialization(); + + /** + * @brief Replacement for ShareVulkanContextResourcesOfMainWindow. Does the following * - * - Share the Vulkan resources, including Swapchain, Renderpass, CommandPool, Framebuffer, and all that, of MainWindow (stored in VulkanContext of VulkanHolder, initialized during VulkanContext::Init() which gets called - * on Platform(Windows, Mac, and Linux)Window during their specific creation implementation, for instance LinuxWindow::Init) to KarmaGui's backend, after appropriate clearence. - * - Ground work for establishing syncronicity, in GPU side functions' execution, by creating semaphores for (https://vulkan-tutorial.com/Drawing_a_triangle/Drawing/Rendering_and_presentation#page_Synchronization) - * -# to signal that image has been acquired from the swapchain and ready for rendering - * -# to signal that rendering has finished and presentation (the last step of drawing a frame is submitting the result back to the swap chain to have it eventually show up on the screen) can happen - * The set number of semaphores (we are using two types of them) are for number of "frames in flight" which represent the number of frames which can be processed on CPU whilst rendering is done on GPU + * - Creates Vulkan swapchain and sets the swapchain handle of windowData along with imagecount, MAX_FRAMES_IN_FLIGHT, + * and renderarea. + * - Creates Vulkan renderpass (see KarmaGuiVulkanHandler::MakeRenderPassInfo) for KarmaGui's Vulkan backend and sets + * backend->VulkanInitInfo.RenderPass with this renderpass (of which KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_CreatePipeline + * is created) + * - Creates depth render target and uses swapchain images for color render targets and packages them into FVulkanRenderTargetsInfo + * - Creates framebuffers for each swapchain image and sets windowData->Framebuffers + * - Sets windowData->CommandPool with the pool created in FVulkanDynamiRHI::m_Device (FVulkanDevice) + * - Creates semaphores and fences if bCreateSyncronicity is set to true * - * @param windowData The datastructure to hold all the data needed by one rendering Vulkan context. - * @param bCreateSyncronicity Should we allocate semaphores and fences. For instance true during KarmaGuiRenderer::SetUpKarmaGuiRenderer and false during KarmaGuiRenderer::GiveLoopBeginControlToVulkan * + * @note SwapChain and renderpass resources are cleared in KarmaGuiVulkanHandler::KarmaGui_ImplVulkan_DestroyWindow + * @since Karma 1.0.0 + */ + static void FillWindowData(KarmaGui_ImplVulkanH_Window* windowData, bool bCreateSyncronicity); + + /** + * @brief Prepares the renderpass information for KarmaGui's Vulkan backend + * + * Basically fills FVulkanRenderPassInfo struct with two attachments, one for color and one for depth. + * + * @todo Ponder upon the configurable subpass configuration. Currently done in FVulkanRenderPassBuilder::BuildCreateInfo with basic + * subpass description. + * @since Karma 1.0.0 + */ + static void MakeRenderPassInfo(FVulkanSwapChain* SwapChain, struct FVulkanRenderPassInfo& RPInfo); + + /** + * @brief Fills the depth render target information + * + * @since Karma 1.0.0 + */ + static void CreateDepthRenderTarget(FVulkanRenderTargetsInfo& RTInfo, FVulkanSwapChain* SwapChain); + + /** + * @brief Prepares the RTInfo for framebuffer creation + * * @since Karma 1.0.0 */ - static void ShareVulkanContextResourcesOfMainWindow(KarmaGui_ImplVulkanH_Window* windowData, bool bCreateSyncronicity = false); + static void GatherSwapChainColorRenderTargets(FVulkanRenderTargetsInfo& RTInfo, FVulkanSwapChain* SwapChain, uint32_t SwapChainImageIndex); + + /** + * @brief Destroys swapchain and related resources for KarmaGui's Vulkan backend + * + * @param windowData Container for information required for swapchain destruction + * @since Karma 1.0.0 + */ + static void ShivaSwapChainForRebuild(KarmaGui_ImplVulkanH_Window* windowData); /** * @brief Creates framebuffers, renderpass, and vertexarray (for KarmaGui) pipeline for 2D texture creation from 3D scene @@ -1170,6 +1350,8 @@ namespace Karma */ static void CreateOffScreenTextureFrameBufferResource(KarmaGui_3DScene_To_2DTexture_Data* textureData); + static void CreateOffScreenTextureGraphicsPipelineResource(VkRenderPass renderPassKG, float windowKGWidth, float windowKGHeight); + /** * @brief Clears appropriate buffers which are used for KarmaGui's rendering. They include: * diff --git a/Karma/src/Platform/Vulkan/VulkanBuffer.cpp b/Karma/src/Platform/Vulkan/VulkanBuffer.cpp index 50544bd7..e4299bf5 100644 --- a/Karma/src/Platform/Vulkan/VulkanBuffer.cpp +++ b/Karma/src/Platform/Vulkan/VulkanBuffer.cpp @@ -1,14 +1,18 @@ #include "VulkanBuffer.h" -#include "Platform/Vulkan/VulkanHolder.h" #include "Karma/Renderer/RenderCommand.h" #include "Karma/KarmaUtilities.h" +#include "VulkanRHI/VulkanDynamicRHI.h" +#include "VulkanRHI/VulkanDevice.h" +#include "VulkanRHI/VulkanSwapChain.h" +#include "KarmaGui/KarmaGuiRenderer.h" +#include "VulkanRHI/VulkanDescriptorSets.h" namespace Karma { // Vertex Buffer VulkanVertexBuffer::VulkanVertexBuffer(float* vertices, uint32_t size) { - m_Device = VulkanHolder::GetVulkanContext()->GetLogicalDevice(); + m_Device = FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(); VkDeviceSize bufferSize = size; m_BufferSize = size; @@ -35,6 +39,8 @@ namespace Karma VulkanVertexBuffer::~VulkanVertexBuffer() { + vkDeviceWaitIdle(m_Device); + vkDestroyBuffer(m_Device, m_VertexBuffer, nullptr); vkFreeMemory(m_Device, m_VertexBufferMemory, nullptr); } @@ -44,7 +50,7 @@ namespace Karma VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandPool = VulkanHolder::GetVulkanContext()->GetCommandPool(); + allocInfo.commandPool = FVulkanDynamicRHI::Get().GetDevice()->GetCommandPool(); allocInfo.commandBufferCount = 1; VkCommandBuffer commandBuffer; @@ -67,10 +73,10 @@ namespace Karma submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; - vkQueueSubmit(VulkanHolder::GetVulkanContext()->GetGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE); - vkQueueWaitIdle(VulkanHolder::GetVulkanContext()->GetGraphicsQueue()); + vkQueueSubmit(FVulkanDynamicRHI::Get().GetDevice()->GetGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(FVulkanDynamicRHI::Get().GetDevice()->GetGraphicsQueue()); - vkFreeCommandBuffers(m_Device, VulkanHolder::GetVulkanContext()->GetCommandPool(), 1, &commandBuffer); + vkFreeCommandBuffers(m_Device, allocInfo.commandPool, 1, &commandBuffer); } void VulkanVertexBuffer::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, @@ -92,7 +98,7 @@ namespace Karma VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, properties); + allocInfo.memoryTypeIndex = FVulkanDynamicRHI::Get().FindMemoryType(memRequirements.memoryTypeBits, properties); VkResult resultm = vkAllocateMemory(m_Device, &allocInfo, nullptr, &bufferMemory); @@ -100,23 +106,6 @@ namespace Karma vkBindBufferMemory(m_Device, buffer, bufferMemory, 0); } - uint32_t VulkanVertexBuffer::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) - { - VkPhysicalDeviceMemoryProperties memProperties; - vkGetPhysicalDeviceMemoryProperties(VulkanHolder::GetVulkanContext()->GetPhysicalDevice(), &memProperties); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) - { - if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) - { - return i; - } - } - - KR_CORE_ASSERT(false, "Failed to find suitable memory type for vertexbuffer"); - return 0; - } - void VulkanVertexBuffer::Bind() const { @@ -126,11 +115,10 @@ namespace Karma { } - // Index buffer VulkanIndexBuffer::VulkanIndexBuffer(uint32_t* indices, uint32_t count) : m_Count(count) { - m_Device = VulkanHolder::GetVulkanContext()->GetLogicalDevice(); + m_Device = FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(); VkDeviceSize bufferSize = sizeof(uint32_t) * count; m_BufferSize = bufferSize; @@ -157,6 +145,8 @@ namespace Karma VulkanIndexBuffer::~VulkanIndexBuffer() { + vkDeviceWaitIdle(m_Device); + vkDestroyBuffer(m_Device, m_IndexBuffer, nullptr); vkFreeMemory(m_Device, m_IndexBufferMemory, nullptr); } @@ -166,7 +156,7 @@ namespace Karma VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandPool = VulkanHolder::GetVulkanContext()->GetCommandPool(); + allocInfo.commandPool = FVulkanDynamicRHI::Get().GetDevice()->GetCommandPool(); allocInfo.commandBufferCount = 1; VkCommandBuffer commandBuffer; @@ -189,10 +179,10 @@ namespace Karma submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; - vkQueueSubmit(VulkanHolder::GetVulkanContext()->GetGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE); - vkQueueWaitIdle(VulkanHolder::GetVulkanContext()->GetGraphicsQueue()); + vkQueueSubmit(FVulkanDynamicRHI::Get().GetDevice()->GetGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(FVulkanDynamicRHI::Get().GetDevice()->GetGraphicsQueue()); - vkFreeCommandBuffers(m_Device, VulkanHolder::GetVulkanContext()->GetCommandPool(), 1, &commandBuffer); + vkFreeCommandBuffers(m_Device, allocInfo.commandPool, 1, &commandBuffer); } void VulkanIndexBuffer::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, @@ -214,7 +204,7 @@ namespace Karma VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, properties); + allocInfo.memoryTypeIndex = FVulkanDynamicRHI::Get().FindMemoryType(memRequirements.memoryTypeBits, properties); VkResult resultm = vkAllocateMemory(m_Device, &allocInfo, nullptr, &bufferMemory); @@ -222,23 +212,6 @@ namespace Karma vkBindBufferMemory(m_Device, buffer, bufferMemory, 0); } - uint32_t VulkanIndexBuffer::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) - { - VkPhysicalDeviceMemoryProperties memProperties; - vkGetPhysicalDeviceMemoryProperties(VulkanHolder::GetVulkanContext()->GetPhysicalDevice(), &memProperties); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) - { - if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) - { - return i; - } - } - - KR_CORE_ASSERT(false, "Failed to find suitable memory type for indexbuffer"); - return 0; - } - void VulkanIndexBuffer::Bind() const { } @@ -251,32 +224,35 @@ namespace Karma VulkanUniformBuffer::VulkanUniformBuffer(std::vector dataTypes, uint32_t bindingPointIndex) : UniformBufferObject(dataTypes, bindingPointIndex) { - m_Device = VulkanHolder::GetVulkanContext()->GetLogicalDevice(); + m_Device = FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(); BufferCreation(); + + //std::shared_ptr uboPtr(this); + //VulkanHolder::GetVulkanContext()->RegisterUBO(this); + + FVulkanDynamicRHI::Get().RegisterUniformBufferObject(this); } VulkanUniformBuffer::~VulkanUniformBuffer() { - //ClearBuffer(); + ClearBuffer(); } - void VulkanUniformBuffer::BufferCreation() + void VulkanUniformBuffer::UpdateCameraUniform() { - VkDeviceSize bufferSize = GetBufferSize(); + uint32_t maxFramesInFlight = KarmaGuiRenderer::GetWindowData().RHIResources->VulkanSwapChain->GetMaxFramesInFlight(); - RendererAPI* rAPI = RenderCommand::GetRendererAPI(); - VulkanRendererAPI* vulkanAPI = nullptr; - - if(rAPI->GetAPI() == RendererAPI::API::Vulkan) - { - vulkanAPI = static_cast(rAPI); - } - else + for(uint32_t counter = 0; counter < maxFramesInFlight; counter++) { - KR_CORE_ASSERT(false, "How is this even possible?"); + FVulkanDynamicRHI::Get().GetDevice()->GetDefaultDescriptorSets()[counter]->UpdateUniformBufferDescriptorSet(this, 0, 0, counter); } + } + + void VulkanUniformBuffer::BufferCreation() + { + VkDeviceSize bufferSize = GetBufferSize(); - int maxFramesInFlight = vulkanAPI->GetMaxFramesInFlight(); + int maxFramesInFlight = KarmaGuiRenderer::GetWindowData().RHIResources->VulkanSwapChain->GetMaxFramesInFlight(); m_UniformBuffers.resize(maxFramesInFlight); m_UniformBuffersMemory.resize(maxFramesInFlight); @@ -290,6 +266,8 @@ namespace Karma void VulkanUniformBuffer::ClearBuffer() { + vkDeviceWaitIdle(m_Device); + for (size_t i = 0; i < m_UniformBuffers.size(); i++) { vkDestroyBuffer(m_Device, m_UniformBuffers[i], nullptr); @@ -330,7 +308,7 @@ namespace Karma VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, properties); + allocInfo.memoryTypeIndex = FVulkanDynamicRHI::Get().FindMemoryType(memRequirements.memoryTypeBits, properties); VkResult resultm = vkAllocateMemory(m_Device, &allocInfo, nullptr, &bufferMemory); @@ -338,25 +316,27 @@ namespace Karma vkBindBufferMemory(m_Device, buffer, bufferMemory, 0); } - uint32_t VulkanUniformBuffer::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) + // ImageBuffer + VulkanImageBuffer::VulkanImageBuffer(const char* filename) { - VkPhysicalDeviceMemoryProperties memProperties; - vkGetPhysicalDeviceMemoryProperties(VulkanHolder::GetVulkanContext()->GetPhysicalDevice(), &memProperties); + stbi_uc* pixels = KarmaUtilities::GetImagePixelData(filename, &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) - { - if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) - { - return i; - } - } + // Need more consideration on image size + VkDeviceSize imageSize = texWidth * texHeight * 4; + + KR_CORE_ASSERT(pixels, "Failed to load textures image!"); + + m_Device = FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice(); + CreateBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_StagingBuffer, m_StagingBufferMemory); + void* data; + vkMapMemory(m_Device, m_StagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(m_Device, m_StagingBufferMemory); - KR_CORE_ASSERT(false, "Failed to find suitable memory type for uniformbuffer"); - return 0; + stbi_image_free(pixels); } - // ImageBuffer - VulkanImageBuffer::VulkanImageBuffer(const char* filename) + VulkanImageBuffer::VulkanImageBuffer(FVulkanDevice* InDevice, const char* filename) { stbi_uc* pixels = KarmaUtilities::GetImagePixelData(filename, &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); @@ -365,7 +345,9 @@ namespace Karma KR_CORE_ASSERT(pixels, "Failed to load textures image!"); - m_Device = VulkanHolder::GetVulkanContext()->GetLogicalDevice(); + m_Device = InDevice->GetLogicalDevice(); + m_PhysicalDevice = InDevice->GetGPU(); + CreateBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_StagingBuffer, m_StagingBufferMemory); void* data; vkMapMemory(m_Device, m_StagingBufferMemory, 0, imageSize, 0, &data); @@ -400,28 +382,11 @@ namespace Karma VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, properties); + allocInfo.memoryTypeIndex = FVulkanDynamicRHI::Get().FindMemoryType(memRequirements.memoryTypeBits, properties); VkResult resultm = vkAllocateMemory(m_Device, &allocInfo, nullptr, &bufferMemory); KR_CORE_ASSERT(resultm == VK_SUCCESS, "Failed to allocate imagebuffer memory"); vkBindBufferMemory(m_Device, buffer, bufferMemory, 0); } - - uint32_t VulkanImageBuffer::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) - { - VkPhysicalDeviceMemoryProperties memProperties; - vkGetPhysicalDeviceMemoryProperties(VulkanHolder::GetVulkanContext()->GetPhysicalDevice(), &memProperties); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) - { - if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) - { - return i; - } - } - - KR_CORE_ASSERT(false, "Failed to find suitable memory type for imagebuffer"); - return 0; - } } diff --git a/Karma/src/Platform/Vulkan/VulkanBuffer.h b/Karma/src/Platform/Vulkan/VulkanBuffer.h index f10a3725..ae6a8cec 100644 --- a/Karma/src/Platform/Vulkan/VulkanBuffer.h +++ b/Karma/src/Platform/Vulkan/VulkanBuffer.h @@ -14,6 +14,8 @@ namespace Karma { + class FVulkanDevice; + /** * @brief Vulkan's vertex buffer class. Vertex buffer is used in agnostic Mesh instance * @@ -118,22 +120,6 @@ namespace Karma */ void CopyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size); - /** - * @brief Finds appropriate memory type with demanded properties. Basically a loop is run from counter i = 0 to VkPhysicalDeviceMemoryProperties.memoryTypeCount - * (number of valid elements in the memoryTypes array) and memoryType[i] is queried for appropriate properties. On condition satisfaction, counter i is returned. - * - * Graphics cards can offer different types of memory to allocate from. Each type of memory varies in terms of allowed operations and performance characteristics. - * We need to combine the requirements of the buffer and our own application requirements to find the right type of memory to use. - * - * @param typeFilter A bitmask, and contains one bit set for every supported memory type for the resource. Bit i is set if and only if the memory type i in the VkPhysicalDeviceMemoryProperties structure for the physical device is supported for the resource. - * @param properties The demanded properties (https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBufferUsageFlagBits.html). - * - * @return Memory type index i - * @see VulkanUniformBuffer::CreateBuffer - * @since Karma 1.0.0 - */ - uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties); - /** * @brief Getter for vertex buffer. * @@ -231,18 +217,6 @@ namespace Karma */ void CopyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size); - /** - * @brief Finds appropriate memory type with demanded properties. Basically a loop is run from counter i = 0 to VkPhysicalDeviceMemoryProperties.memoryTypeCount - * (number of valid elements in the memoryTypes array) and memoryType[i] is queried for appropriate properties. On condition satisfaction, counter i is returned. - * - * @param typeFilter A bitmask, and contains one bit set for every supported memory type for the resource. Bit i is set if and only if the memory type i in the VkPhysicalDeviceMemoryProperties structure for the physical device is supported for the resource. - * @param properties The demanded properties (https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBufferUsageFlagBits.html). - * - * @return Memory type index i - * @since Karma 1.0.0 - */ - uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties); - /** * @brief Getter for the number of vertices to draw. * @@ -293,6 +267,8 @@ namespace Karma public: /** * @brief Constructor for Vulkan buffer. Calls VulkanUniformBuffer::BufferCreation(). + * + * Also registers the UBO with VulkanContext so that it can be updated appropriately during rendering. * * @param dataTypes List of data types for uniforms to be uploaded to GPU (like used in shaders), * for instance https://github.com/ravimohan1991/KarmaEngine/blob/138c172ccedf31acfab982af51ae130f9a37d3bb/Application/src/KarmaApp.cpp#L39 where Mat4 are for https://github.com/ravimohan1991/KarmaEngine/blob/138c172ccedf31acfab982af51ae130f9a37d3bb/Resources/Shaders/shader.vert#L9-L13 @@ -323,18 +299,6 @@ namespace Karma void CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory); - /** - * @brief Finds appropriate memory type with demanded properties. Basically a loop is run from counter i = 0 to VkPhysicalDeviceMemoryProperties.memoryTypeCount - * (number of valid elements in the memoryTypes array) and memoryType[i] is queried for appropriate properties. On condition satisfaction, counter i is returned. - * - * @param typeFilter A bitmask, and contains one bit set for every supported memory type for the resource. Bit i is set if and only if the memory type i in the VkPhysicalDeviceMemoryProperties structure for the physical device is supported for the resource. - * @param properties The demanded properties (https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBufferUsageFlagBits.html). - * - * @return Memory type index i - * @since Karma 1.0.0 - */ - uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties); - /** * @brief Getter for the uniform buffers * @@ -357,8 +321,10 @@ namespace Karma */ void BufferCreation(); + virtual void UpdateCameraUniform() override; + /** - * @brief Uploads (copies) the data from buffer memory (m_UniformBuffersMemory) to host-accessible (CPU) pointer, m_UniformList, to the beginning of the mapped range + * @brief Uploads (copies) the data to GPU buffer memory (m_UniformBuffersMemory) from host-accessible (CPU) pointer, m_UniformList, to the beginning of the mapped range * * Basically copies the data from CPU side (uniforms) to GPU side (m_UniformBuffersMemory). * @@ -367,7 +333,12 @@ namespace Karma * - The memory of the uniform buffer (a VkBuffer) is created with CPU-visible properties and mapped to a CPU pointer. * - The updateUniformBuffer function copies the CPU-side data (like transformation matrices) into the mapped GPU buffer memory via a memcpy call. * - This allows the GPU to read the fresh uniform data every frame during rendering without needing to re-record command buffers. - * + * + * Mechanics: + * vkMapMemory returns a CPU-accessible pointer (data) to a specific range within m_UniformBuffersMemory[frameIndex], which is VkDeviceMemory allocated for + * GPU use but with VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT (see VulkanUniformBuffer::BufferCreation()). This pointer points directly into GPU memory, so + * memcpy(data, it.GetDataPointer(), uniformSize) overlays CPU data bytes onto that GPU buffer region + * * @param frameIndex The m_CurrentFrame index representing index of MAX_FRAMES_IN_FLIGHT (number of images (to work upon (CPU side) whilst an image is being rendered (GPU side processing)) + 1) * * @see VulkanRendererAPI::SubmitCommandBuffers() @@ -395,6 +366,16 @@ namespace Karma */ VulkanImageBuffer(const char* filename); + /** + * @brief Creates GPU memory buffer for storing image texture with VulkanRHI support + * + * @param InDevice The FVulkanDevice object + * @param filename The path to the file, including filename, containing the image texture + * + * @since Karma 1.0.0 + */ + VulkanImageBuffer(FVulkanDevice* InDevice, const char* filename); + /** * @brief Frees up device resources * @@ -414,17 +395,6 @@ namespace Karma void CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory); - /** - * @brief Finds appropriate memory type with demanded properties. Basically a loop is run from counter i = 0 to VkPhysicalDeviceMemoryProperties.memoryTypeCount - * (number of valid elements in the memoryTypes array) and memoryType[i] is queried for appropriate properties. On condition satisfaction, counter i is returned. - * - * @param typeFilter A bitmask, and contains one bit set for every supported memory type for the resource. Bit i is set if and only if the memory type i in the VkPhysicalDeviceMemoryProperties structure for the physical device is supported for the resource. - * @param properties The demanded properties (https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBufferUsageFlagBits.html). - * - * @return Memory type index i - * @since Karma 1.0.0 - */ - uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties); const inline VkBuffer& GetBuffer() const { return m_StagingBuffer; } // Getters @@ -452,6 +422,7 @@ namespace Karma private: VkDevice m_Device; + VkPhysicalDevice m_PhysicalDevice; VkBuffer m_StagingBuffer; VkDeviceMemory m_StagingBufferMemory; diff --git a/Karma/src/Platform/Vulkan/VulkanContext.cpp b/Karma/src/Platform/Vulkan/VulkanContext.cpp index a7aed6c4..6a985729 100644 --- a/Karma/src/Platform/Vulkan/VulkanContext.cpp +++ b/Karma/src/Platform/Vulkan/VulkanContext.cpp @@ -6,12 +6,15 @@ #include "Karma/Renderer/RenderCommand.h" #include "Platform/Vulkan/VulkanVertexArray.h" #include "Platform/Vulkan/VulkanBuffer.h" +#include "Platform/Vulkan/VulkanTexture.h" +#include "Scene.h" +#include "Engine/StaticMeshActor.h" namespace Karma { - const std::vector validationLayers = { "VK_LAYER_KHRONOS_validation" }; + const std::vector ValidationLayers = { "VK_LAYER_KHRONOS_validation" }; // Subject to change based on available hardware scrutiny - std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; + std::vector DeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #ifdef KR_DEBUG bool VulkanContext::bEnableValidationLayers = true; @@ -19,6 +22,37 @@ namespace Karma bool VulkanContext::bEnableValidationLayers = false; #endif + static VkFormat ShaderDataTypeToVulkanType(ShaderDataType type) + { + switch (type) + { + case ShaderDataType::Float: + return VK_FORMAT_R32_SFLOAT; + case ShaderDataType::Float2: + return VK_FORMAT_R32G32_SFLOAT; + case ShaderDataType::Float3: + return VK_FORMAT_R32G32B32_SFLOAT; + case ShaderDataType::Float4: + return VK_FORMAT_R32G32B32A32_SFLOAT; + case ShaderDataType::None: + case ShaderDataType::Mat3: + case ShaderDataType::Mat4: + case ShaderDataType::Int: + case ShaderDataType::Int2: + case ShaderDataType::Int3: + case ShaderDataType::Int4: + case ShaderDataType::Bool: + // Refer Mesh::GaugeVertexDataLayout for usual datatype + // to be used in the context of vertex buffer + KR_CORE_ASSERT(false, "Weird ShaderDataType is being used") + return VK_FORMAT_UNDEFINED; + break; + } + + KR_CORE_ASSERT(false, "Vulkan doesn't support this ShaderDatatype"); + return VK_FORMAT_UNDEFINED; + } + VulkanContext::VulkanContext(GLFWwindow* windowHandle) : m_windowHandle(windowHandle) { @@ -28,8 +62,21 @@ namespace Karma VulkanContext::~VulkanContext() { + vkDestroyDescriptorSetLayout(m_device, m_ViewLayout, nullptr); + vkDestroyDescriptorSetLayout(m_device, m_TextureLayout, nullptr); + + vkDestroyDescriptorSetLayout(m_device, m_ObjectLayout, nullptr); + vkDestroyPipelineLayout(m_device, m_KarmaGuiGeneralPipelineLayout, nullptr); + + vkDestroyDescriptorPool(m_device, m_GeneralDescriptorPool, nullptr); + + // Tentative + static_pointer_cast(m_GeneralTexture)->~VulkanTexture(); + m_vulkanRendererAPI->ClearVulkanRendererAPI(); - ClearUBO(); + + // Tentative for the moment + // ClearUBO(); for (auto framebuffer : m_swapChainFrameBuffers) { @@ -60,7 +107,12 @@ namespace Karma glslang::FinalizeProcess(); } - void VulkanContext::RegisterUBO(const std::shared_ptr& ubo) + void VulkanContext::CleanUpKarmaGuiGeneralGraphicsPipeline() + { + vkDestroyPipeline(m_device, m_KarmaGuiGeneralGraphicsPipeline, nullptr); + } + + void VulkanContext::RegisterUBO(VulkanUniformBuffer* ubo) { m_VulkanUBO.insert(ubo); } @@ -109,6 +161,488 @@ namespace Karma // For glslang Initializeglslang(); + + CreateGeneralShader(); + CreateGeneralTexture(); + + CreateGeneralDescriptorSetLayouts(); + } + + void VulkanContext::CreateVulkanResourcesForScene(std::shared_ptr scene3D) + { + uint32_t smElementsNumber = 0; + + for (const auto element : scene3D->GetSMActors()) + { + smElementsNumber++; + } + + RendererAPI* rendererAPI = RenderCommand::GetRendererAPI(); + VulkanRendererAPI* vulkanRendererAPI = static_cast(rendererAPI); + + if (!vulkanRendererAPI) + { + KR_CORE_ASSERT(false, "VulkanRendererAPI is null!"); + return; + } + + uint32_t maxFramesInFlight = static_cast(vulkanRendererAPI->GetMaxFramesInFlight()); + + CreateGeneralDescriptorPool(smElementsNumber); + + CreateGeneralDescriptorSets(scene3D, smElementsNumber, maxFramesInFlight); + + for (uint32_t frameIndex = 0; frameIndex < maxFramesInFlight; frameIndex++) + { + UpdateGeneralDescriptorSets(scene3D, frameIndex); + } + } + + void VulkanContext::CreateGeneralShader() + { + // We are creating general shader here for the static material (material used as default for meshes) + // Ponder how this would look like in OpenGL + m_GeneralShader.reset(new VulkanShader("../Resources/Shaders/shader.vert", "../Resources/Shaders/shader.frag")); + } + + void VulkanContext::CreateGeneralTexture() + { + m_GeneralTexture.reset(new Texture(Karma::TextureType::Image, "../Resources/Textures/UnrealGrid.png", "VikingTex", "texSampler")); + } + + VkShaderModule VulkanContext::CreateShaderModule(const std::vector& code) + { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size() * sizeof(uint32_t); + createInfo.pCode = code.data(); + + VkShaderModule shaderModule; + VkResult result = vkCreateShaderModule(m_device, &createInfo, nullptr, &shaderModule); + + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create shader module!"); + + return shaderModule; + } + + void VulkanContext::CreateKarmaGuiGeneralGraphicsPipeline(VkRenderPass renderPassKG, float windowKGWidth, float windowKGHeight) + { + std::array setLayouts = { + m_ViewLayout, // set = 0 + m_TextureLayout, // set = 1 + m_ObjectLayout // set = 2 + }; + + VkPipelineLayoutCreateInfo plInfo{}; + plInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + plInfo.setLayoutCount = static_cast(setLayouts.size()); + plInfo.pSetLayouts = setLayouts.data(); + plInfo.pushConstantRangeCount = 0; + plInfo.pPushConstantRanges = nullptr; + + VkResult result = vkCreatePipelineLayout(m_device, &plInfo, nullptr, &m_KarmaGuiGeneralPipelineLayout); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create pipeline layout!"); + + VkShaderModule vertShaderModule = CreateShaderModule(m_GeneralShader->GetVertSpirV()); + VkShaderModule fragShaderModule = CreateShaderModule(m_GeneralShader->GetFragSpirV()); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo }; + + // Telling vulkan what to expect from vertex data in terms of attributes and their rate of loading + uint32_t index = 0; + + // See Mesh::GaugeVertexDataLayout to understand the layout of vertex data we are using + BufferLayout layout; + layout.PushElement(BufferElement(ShaderDataType::Float3, "v_Position")); + layout.PushElement(BufferElement(ShaderDataType::Float2, "v_UV")); + + layout.PushElement(BufferElement(ShaderDataType::Float3, "v_Normal")); + /*layout.PushElement(BufferElement(ShaderDataType::Float4, "v_Color")); + layout.PushElement(BufferElement(ShaderDataType::Float3, "v_Normal")); + layout.PushElement(BufferElement(ShaderDataType::Float3, "v_Tangent"));*/ + + VkVertexInputBindingDescription bindingDescription = {}; + std::vector attributeDescriptions; + + bindingDescription.binding = 0; + bindingDescription.stride = layout.GetStride(); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + for (const auto& element : layout) + { + VkVertexInputAttributeDescription elementAttributeDescription{}; + elementAttributeDescription.binding = 0; + elementAttributeDescription.location = index; + elementAttributeDescription.format = ShaderDataTypeToVulkanType(element.Type); + elementAttributeDescription.offset = static_cast(element.Offset); + + attributeDescriptions.push_back(elementAttributeDescription); + index++; + } + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = windowKGWidth; + viewport.height = windowKGHeight; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent.width = windowKGWidth; + scissor.extent.height = windowKGHeight; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + // Antialiasing + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.stencilTestEnable = VK_FALSE; + + VkBool32 bLogicalOperationsAllowed = m_SupportedDeviceFeatures.logicOp; + + // Mix the old and new value to produce a final color + // finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor; + // finalColor.a = newAlpha.a; + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT + | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + if (!bLogicalOperationsAllowed) + { + colorBlendAttachment.blendEnable = VK_TRUE; + } + else + { + colorBlendAttachment.blendEnable = VK_FALSE; + } + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + + // Combine the old and new value using a bitwise operation + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + if (bLogicalOperationsAllowed) + { + colorBlending.logicOpEnable = VK_TRUE; + } + else + { + colorBlending.logicOpEnable = VK_FALSE; + } + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.layout = m_KarmaGuiGeneralPipelineLayout; + pipelineInfo.renderPass = renderPassKG; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineInfo.pDepthStencilState = &depthStencil; + + VkResult resultGP = vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, + 1, &pipelineInfo, nullptr, &m_KarmaGuiGeneralGraphicsPipeline); + + KR_CORE_ASSERT(resultGP == VK_SUCCESS, "Failed to create graphics pipeline!"); + + + vkDestroyShaderModule(m_device, fragShaderModule, nullptr); + vkDestroyShaderModule(m_device, vertShaderModule, nullptr); + } + + void VulkanContext::CreateGeneralDescriptorPool(uint32_t smElementsNumber) + { + RendererAPI* rendererAPI = RenderCommand::GetRendererAPI(); + VulkanRendererAPI* vulkanRendererAPI = static_cast(rendererAPI); + + if (!vulkanRendererAPI) + { + KR_CORE_ASSERT(false, "VulkanRendererAPI is null!"); + return; + } + + uint32_t maxFramesInFlight = static_cast(vulkanRendererAPI->GetMaxFramesInFlight()); + + std::array poolSizes{}; + + // Uniform Buffers : Camera UBO + per object UBOs + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = (1 + smElementsNumber) * maxFramesInFlight; // 1 for camera UBO + 1 for object UBO + + // Combined Image Samplers : Texture + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = smElementsNumber * maxFramesInFlight; // 1 for each object texture + + VkDescriptorPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = static_cast(poolSizes.size()); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = 3 * maxFramesInFlight; // 3 sets per frame + + VkResult result = vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &m_GeneralDescriptorPool); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create general descriptor pool!"); + } + + void VulkanContext::CreateGeneralDescriptorSetLayouts() + { + // ===== Set 0: Camera UBO ===== + VkDescriptorSetLayoutBinding viewBinding{}; + viewBinding.binding = 0; + viewBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + viewBinding.descriptorCount = 1; + viewBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + viewBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo viewLayoutInfo{}; + viewLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + viewLayoutInfo.bindingCount = 1; + viewLayoutInfo.pBindings = &viewBinding; + VkResult result = vkCreateDescriptorSetLayout(m_device, &viewLayoutInfo, nullptr, &m_ViewLayout); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create view descriptor set layout!"); + + // === SET 1: MATERIAL UBO + 1 TEXTURE === + /*std::array materialBindings{}; + + // Binding 0: Material UBO + materialBindings[0].binding = 0; + materialBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + materialBindings[0].descriptorCount = 1; + materialBindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT; + + // Binding 1: SINGLE Texture + materialBindings[1].binding = 1; + materialBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + materialBindings[1].descriptorCount = 1; + materialBindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + VkDescriptorSetLayoutCreateInfo materialLayoutInfo{}; + materialLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + materialLayoutInfo.bindingCount = 2; // Only 2 bindings now + materialLayoutInfo.pBindings = materialBindings.data(); + vkCreateDescriptorSetLayout(device, &materialLayoutInfo, nullptr, &materialLayout); + + GLSLANG USAGE EXAMPLE: + // Set 1: Material UBO + 1 Texture + layout(set = 1, binding = 0) uniform MaterialUBO { + vec4 baseColor; + float metallic; + float roughness; + } material; + + layout(set = 1, binding = 1) uniform sampler2D albedoTexture; // Single texture! + */ + + // ===== Set 1: Single texture (combined image + sampler) ===== + VkDescriptorSetLayoutBinding texBinding{}; + texBinding.binding = 0; + texBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + texBinding.descriptorCount = 1; // <----------------------------------- Number of such descriptors, for per mesh should be number of meshes + texBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + texBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo texInfo{}; + texInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + texInfo.bindingCount = 1; + texInfo.pBindings = &texBinding; + + result = vkCreateDescriptorSetLayout(m_device, &texInfo, nullptr, &m_TextureLayout); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create texture descriptor set layout!"); + + // ===== Set 2: Per-mesh transform UBO ===== + VkDescriptorSetLayoutBinding objectBinding{}; + objectBinding.binding = 0; + objectBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + objectBinding.descriptorCount = 1;// <----------------------------------- number of uniform buffers , for per mesh should be number of meshes + objectBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + objectBinding.pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo objInfo{}; + objInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + objInfo.bindingCount = 1; + objInfo.pBindings = &objectBinding; + + result = vkCreateDescriptorSetLayout(m_device, &objInfo, nullptr, &m_ObjectLayout); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create object descriptor set layout!"); + } + + void VulkanContext::CreateGeneralDescriptorSets(std::shared_ptr scene3D, uint32_t smElementsNumber, uint32_t maxFramesInFlight) + { + m_GeneralDescriptorSets.resize(maxFramesInFlight); + + for (uint32_t frameIndex = 0; frameIndex < maxFramesInFlight; frameIndex++) + { + std::array frameSets; + + std::array frameLayouts = { + m_ViewLayout, // Set 0 + m_TextureLayout, // Set 1 + m_ObjectLayout // Set 2 + }; + + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = m_GeneralDescriptorPool; + allocInfo.descriptorSetCount = 3; + allocInfo.pSetLayouts = frameLayouts.data(); + + vkAllocateDescriptorSets(m_device, &allocInfo, frameSets.data()); + + // Copy to global struct + m_GeneralDescriptorSets[frameIndex].viewSet = frameSets[0]; + //m_GeneralDescriptorSets[frameIndex].textureSet = frameSets[1]; + //m_GeneralDescriptorSets[frameIndex].objectSet = frameSets[2]; + + m_GeneralDescriptorSets[frameIndex].textureSet.resize(smElementsNumber); + m_GeneralDescriptorSets[frameIndex].objectsSet.resize(smElementsNumber); + + uint32_t meshIndex = 0; + for (const auto element : scene3D->GetSMActors()) + { + m_GeneralDescriptorSets[frameIndex].textureSet[/*element->GetSMID()*/meshIndex] = frameSets[1]; + m_GeneralDescriptorSets[frameIndex].objectsSet[/*element->GetSMID()*/meshIndex] = frameSets[2]; + } + } + } + + void VulkanContext::UpdateGeneralDescriptorSets(std::shared_ptr scene3D, uint32_t frameIndex) + { + FrameDescriptorSets& frameDescriptorSets = m_GeneralDescriptorSets[frameIndex]; + + // Update Set 0: Camera UBO + { + std::shared_ptr vUBO = static_pointer_cast(scene3D->GetSceneCamera()->GetViewProjectionUBO()); + + VkDescriptorBufferInfo viewInfo{}; + viewInfo.buffer = vUBO->GetUniformBuffers()[frameIndex]; + viewInfo.offset = 0; + viewInfo.range = vUBO->GetBufferSize(); + + VkWriteDescriptorSet viewWrite{}; + viewWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + viewWrite.dstSet = frameDescriptorSets.viewSet; + viewWrite.dstBinding = 0; + viewWrite.dstArrayElement = 0; + viewWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + viewWrite.descriptorCount = 1; + viewWrite.pBufferInfo = &viewInfo; + + vkUpdateDescriptorSets(m_device, 1, &viewWrite, 0, nullptr); + } + + uint32_t smIndex = 0; + for (const auto smElement : scene3D->GetSMActors()) + { + // Update set 1: Texture + { + std::shared_ptr vTexture = m_GeneralTexture->GetVulkanTexture(); + + VkDescriptorImageInfo texInfo{}; + texInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + texInfo.sampler = vTexture->GetImageSampler(); + texInfo.imageView = vTexture->GetImageView(); + + VkWriteDescriptorSet texWrite{}; + texWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + texWrite.dstSet = frameDescriptorSets.textureSet[smIndex]; + texWrite.dstBinding = 0; + texWrite.dstArrayElement = 0; + texWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + texWrite.pImageInfo = &texInfo; + texWrite.descriptorCount = 1; + + vkUpdateDescriptorSets(m_device, 1, &texWrite, 0, nullptr); + } + + // Update set 2: Per-mesh UBO will be updated during mesh rendering + { + std::shared_ptr objectUBO = nullptr;//static_pointer_cast(smElement->GetMeshTransformUniform()); + + VkDescriptorBufferInfo objectInfo{}; + objectInfo.buffer = objectUBO->GetUniformBuffers()[frameIndex]; + objectInfo.offset = 0; + objectInfo.range = objectUBO->GetBufferSize(); + + VkWriteDescriptorSet objectWrite{}; + objectWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + objectWrite.dstSet = frameDescriptorSets.objectsSet[smIndex]; + objectWrite.dstBinding = 0; + objectWrite.dstArrayElement = 0; + objectWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + objectWrite.pBufferInfo = &objectInfo; + objectWrite.descriptorCount = 1; + + vkUpdateDescriptorSets(m_device, 1, &objectWrite, 0, nullptr); + } + + smIndex++; + } } void VulkanContext::Initializeglslang() @@ -323,26 +857,6 @@ namespace Karma vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer); } - /* - void VulkanContext::CreateTextureImageView() - { - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = m_TextureImage; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB; - viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - VkResult result = vkCreateImageView(m_device, &viewInfo, nullptr, &m_TextureImageView); - - KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create texture image view"); - } - */ - void VulkanContext::CopyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { VkCommandBufferAllocateInfo allocInfo{}; @@ -401,7 +915,7 @@ namespace Karma void VulkanContext::CreateCommandPool() { - QueueFamilyIndices queueFamilyIndices = FindQueueFamilies(m_physicalDevice); + QueueFamilyIndicesDepricated queueFamilyIndices = FindQueueFamilies(m_physicalDevice); VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; @@ -457,7 +971,7 @@ namespace Karma void VulkanContext::CreateSwapChain() { - SwapChainSupportDetails swapChainSupport = QuerySwapChainSupport(m_physicalDevice); + SwapChainSupportDetailsDepricated swapChainSupport = QuerySwapChainSupport(m_physicalDevice); // KarmaGui may have, MAY, different requirements. m_surfaceFormat = ChooseSwapSurfaceFormat(swapChainSupport.formats); @@ -483,7 +997,7 @@ namespace Karma createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - QueueFamilyIndices indices = FindQueueFamilies(m_physicalDevice); + QueueFamilyIndicesDepricated indices = FindQueueFamilies(m_physicalDevice); uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; @@ -554,7 +1068,7 @@ namespace Karma void VulkanContext::CreateLogicalDevice() { - QueueFamilyIndices indices = FindQueueFamilies(m_physicalDevice); + QueueFamilyIndicesDepricated indices = FindQueueFamilies(m_physicalDevice); std::vector queueCreateInfos; std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), @@ -595,13 +1109,13 @@ namespace Karma createInfo.pQueueCreateInfos = queueCreateInfos.data(); createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); - createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + createInfo.enabledExtensionCount = static_cast(DeviceExtensions.size()); + createInfo.ppEnabledExtensionNames = DeviceExtensions.data(); if (bEnableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); + createInfo.enabledLayerCount = static_cast(ValidationLayers.size()); + createInfo.ppEnabledLayerNames = ValidationLayers.data(); } else { @@ -665,14 +1179,14 @@ namespace Karma bool VulkanContext::IsDeviceSuitable(VkPhysicalDevice device) { - QueueFamilyIndices indices = FindQueueFamilies(device); + QueueFamilyIndicesDepricated indices = FindQueueFamilies(device); bool bExtensionsSupported = CheckDeviceExtensionSupport(device); bool swapChainAdequate = false; if (bExtensionsSupported) { - SwapChainSupportDetails swapChainSupport = QuerySwapChainSupport(device); + SwapChainSupportDetailsDepricated swapChainSupport = QuerySwapChainSupport(device); swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } @@ -697,13 +1211,13 @@ namespace Karma { if (strcmp(anExtention.extensionName, "VK_KHR_portability_subset") != 0) { - deviceExtensions.push_back("VK_KHR_portability_subset"); + DeviceExtensions.push_back("VK_KHR_portability_subset"); break; } } #endif - std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + std::set requiredExtensions(DeviceExtensions.begin(), DeviceExtensions.end()); if (bEnableValidationLayers) { @@ -733,9 +1247,9 @@ namespace Karma return requiredExtensions.empty(); } - QueueFamilyIndices VulkanContext::FindQueueFamilies(VkPhysicalDevice device) + QueueFamilyIndicesDepricated VulkanContext::FindQueueFamilies(VkPhysicalDevice device) { - QueueFamilyIndices indices; + QueueFamilyIndicesDepricated indices; uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); @@ -770,9 +1284,9 @@ namespace Karma return indices; } - SwapChainSupportDetails VulkanContext::QuerySwapChainSupport(VkPhysicalDevice device) + SwapChainSupportDetailsDepricated VulkanContext::QuerySwapChainSupport(VkPhysicalDevice device) { - SwapChainSupportDetails details; + SwapChainSupportDetailsDepricated details; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, m_surface, &details.capabilities); @@ -831,8 +1345,8 @@ namespace Karma VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo; if (bEnableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); + createInfo.enabledLayerCount = static_cast(ValidationLayers.size()); + createInfo.ppEnabledLayerNames = ValidationLayers.data(); PopulateDebugMessengerCreateInfo(debugCreateInfo); createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)(&debugCreateInfo); @@ -880,7 +1394,7 @@ namespace Karma std::vector availableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - for (const char* layerName : validationLayers) + for (const char* layerName : ValidationLayers) { bool layerFound = false; @@ -1079,10 +1593,10 @@ namespace Karma VkAttachmentDescription colorAttachment{}; colorAttachment.format = m_swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // what to do with attachment data before rendering: clear to clearcolor + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;// what to do with attachment data after rendering: store in the memory so we can read later + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;// this is a color buffer only not stencil data + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;// this is color buffer only not stencil data colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; diff --git a/Karma/src/Platform/Vulkan/VulkanContext.h b/Karma/src/Platform/Vulkan/VulkanContext.h index efb830dd..f4b818aa 100644 --- a/Karma/src/Platform/Vulkan/VulkanContext.h +++ b/Karma/src/Platform/Vulkan/VulkanContext.h @@ -10,7 +10,6 @@ #pragma once #define GLFW_INCLUDE_VULKAN -#include "krpch.h" #include "Karma/Renderer/GraphicsContext.h" #include "GLFW/glfw3.h" @@ -20,21 +19,29 @@ namespace Karma { - /** - * @brief Forward declaration - */ - class RendererAPI; + class VulkanShader; + + class Texture; + + class Scene; /** * @brief Forward declaration */ - class VulkanVertexArray; + class RendererAPI; /** * @brief Forward declaration */ struct VulkanUniformBuffer; + struct FrameDescriptorSets + { + VkDescriptorSet viewSet; // Set 0: Camera UBO + std::vector textureSet; // Set 1: Per-mesh Texture + std::vector objectsSet; // Set 2: Per-mesh UBO + }; + /** * @brief A structure for graphics and present queuefamilies * @@ -48,7 +55,7 @@ namespace Karma * @see VulkanContext::FindQueueFamilies() * @since Karma 1.0.0 */ - struct QueueFamilyIndices + struct QueueFamilyIndicesDepricated { /** * @brief The queues in this queue family support graphics operations. @@ -92,7 +99,7 @@ namespace Karma * * @since Karma 1.0.0 */ - struct SwapChainSupportDetails + struct SwapChainSupportDetailsDepricated { /** * @brief Basic surface capabilities (min/max number of images in swap chain, min/max width @@ -118,11 +125,16 @@ namespace Karma }; /** - * @brief Vulkan API has the following concepts - * 1. Physical Device (https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Physical_devices_and_queue_families): The software counterpart (VkPhysicalDevice) of a graphics card (GPU). Logical device is created from physical device. - * 2. Device (https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Logical_device_and_queues): The so called logical device for interfacing with the physical device. All the machinery (swapchain, graphicspipeline, and all that) are created from logical device. + * @brief A class for Vulkan specific graphics context. This class also contains all the common Vulkan resources shared by various + * elements that are rendered, for instance UniformBufferObjects, graphicspipeline etc. + * + * ATM this class also contains resources like swapchain, commandpool etc which are used by KarmaGui. * - * Host : is CPU the host? + * @note Vulkan API has the following concepts + * 1. Physical Device (https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Physical_devices_and_queue_families): The software counterpart (VkPhysicalDevice) of a graphics card (GPU). Logical device is created from physical device. + * 2. Device (https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Logical_device_and_queues): The so called logical device for interfacing with the physical device. All the machinery (swapchain, graphicspipeline, and all that) are created from logical device. + * @note CPU is always the host in Vulkan and GPU is the device. + * @since Karma 1.0.0 */ class KARMA_API VulkanContext : public GraphicsContext { @@ -272,7 +284,7 @@ namespace Karma * @param device The graphics card to be queired for queue family * @since Karma 1.0.0 */ - QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device); + QueueFamilyIndicesDepricated FindQueueFamilies(VkPhysicalDevice device); /** * @brief The so called logical device for interfacing with the physical device. All the machinery (swapchain, graphicspipeline, and all that) are created from logical device. Following is done: @@ -307,7 +319,7 @@ namespace Karma * Calls vkEnumerateDeviceExtensionProperties for list of supported extensions for instance VK_KHR_swapchain which is * required for, well, swapchain * - * @ + * @since Karma 1.0.0 */ bool CheckDeviceExtensionSupport(VkPhysicalDevice device); @@ -317,7 +329,7 @@ namespace Karma * @see VulkanContext::IsDeviceSuitable(), and VulkanContext::CreateSwapChain() * @since Karma 1.0.0 */ - SwapChainSupportDetails QuerySwapChainSupport(VkPhysicalDevice device); + SwapChainSupportDetailsDepricated QuerySwapChainSupport(VkPhysicalDevice device); /** * @brief Chooses the best surface format (pixel format and color space) for the swapchain from the available formats. @@ -510,7 +522,7 @@ namespace Karma * @see VulkanContext::m_VulkanUBO * @since Karma 1.0.0 */ - void RegisterUBO(const std::shared_ptr& ubo); + void RegisterUBO(VulkanUniformBuffer* ubo); /** * @brief Clears all registered VulkanUniformBuffers, freeing their resources. @@ -528,6 +540,106 @@ namespace Karma */ void RecreateUBO(); + /** + * @brief Creates a Vulkan shader module from SPIR-V bytecode. + * + * This is specifically used when creating the graphics pipeline to load vertex and fragment shaders. + * + * For instance while creating vertex shader stage info (pipeline creation): + * @code{.cpp} + * VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + * vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + * vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + * vertShaderStageInfo.module = vertShaderModule; + * vertShaderStageInfo.pName = "main"; + * @endcode + * + * @param code The SPIR-V bytecode as a vector of uint32_t + * + * @return The created VkShaderModule + * @since Karma 1.0.0 + */ + VkShaderModule CreateShaderModule(const std::vector& code); + + void CreateVulkanResourcesForScene(std::shared_ptr scene3D ); + + /** + * @brief Create a default vulkan shader + * + * @since Karma 1.0.0 + */ + void CreateGeneralShader(); + + /** + * @brief Creates a general texture used for default texturing in the KarmaGui exhibitor window. + * + * @since Karma 1.0.0 + */ + void CreateGeneralTexture(); + + /** + * @brief Creates general descriptor set layouts for common resources like camera UBO, texture sampler, and object UBO. + * + * These descriptor set layouts define how shader resources are organized and accessed in the rendering pipeline. + * + * @since Karma 1.0.0 + */ + void CreateGeneralDescriptorSetLayouts(); + + /** + * @brief Creates the graphics pipeline for rendering 3D objects in the KarmaGui's exhibitor window. + * + * @param renderPassKG The render pass to be used with the Karma GUI graphics pipeline + * @param windowKGWidth The width of the window for viewport and scissor setup + * @param windowKGHeight The height of the window for viewport and scissor setup + * + * @since Karma 1.0.0 + */ + void CreateKarmaGuiGeneralGraphicsPipeline(VkRenderPass renderPassKG, float windowKGWidth, float windowKGHeight); + + /** + * @brief Cleans up the resources associated with the KarmaGui general graphics pipeline. + * + * This includes destroying the pipeline and pipeline layout is destroyed in destructor. + * + * @since Karma 1.0.0 + */ + void CleanUpKarmaGuiGeneralGraphicsPipeline(); + + /** + * @brief Creates a general descriptor pool for allocating descriptor sets for common resources. + * + * The descriptor sets are like so + * 1. Camera UBO descriptor set + * 2. Texture sampler descriptor set + * 3. Object UBO descriptor set + * 4. Maybe add more later + * + * @since Karma 1.0.0 + */ + void CreateGeneralDescriptorPool(uint32_t smElementsNumber); + + /** + * @brief Allocates and writes to general descriptor sets for common resources like camera UBO, texture sampler, and object UBO. + * + * These descriptor sets are used in the graphics pipeline to bind the appropriate resources for rendering. + * + * @since Karma 1.0.0 + */ + void CreateGeneralDescriptorSets(std::shared_ptr scene3D, uint32_t smElementsNumber, uint32_t maxFramesInFlight); + + /** + * @brief Updates the general descriptor sets with the provided view buffer for the specified frame index. + * + * @note viewbuffer is the vkbuffer of the camera in scene + * + * @param frameIndex The index of the frame for which to update the descriptor sets + * @param viewBuffer The buffer containing view (camera) data + * + * @since Karma 1.0.0 + */ + void UpdateGeneralDescriptorSets(std::shared_ptr scene3D, uint32_t frameIndex); + /** * @brief Uploads data to the registered VulkanUniformBuffers for the specified frame index. * @@ -546,6 +658,13 @@ namespace Karma * * @param frameIndex The index of the frame for which to upload UBO data * + * @note The frameIndex is used to determine which uniform buffer to update, as multiple buffers + * may be used for double or triple buffering to avoid synchronization issues between CPU and GPU. + * + * @note The uniform buffers are resized automatically based on MAX_FRAMES_IN_FLIGHT (number of images (to work upon (CPU side) + * whilst an image is being rendered (GPU side processing)) + 1) + * @see VulkanUniformBuffer::BufferCreation() + * * @see KarmaGuiRenderer::FrameRender, VulkanBuffer::UploadUniformBuffer * @since Karma 1.0.0 */ @@ -572,6 +691,10 @@ namespace Karma uint32_t GetMinImageCount() const { return m_MinImageCount; } VkSurfaceKHR GetSurface() const { return m_surface; } VkPresentModeKHR GetPresentMode() const { return m_presentMode; } + std::shared_ptr GetGeneralShader() const { return m_GeneralShader; } + VkPipeline GetKarmaGuiGeneralGraphicsPipeline() const { return m_KarmaGuiGeneralGraphicsPipeline; } + VkPipelineLayout GetKarmaGuiGeneralPipelineLayout() const { return m_KarmaGuiGeneralPipelineLayout; } + const std::vector& GetGeneralDescriptorSets() const { return m_GeneralDescriptorSets; } private: // Apologies for little out-of-sync naming convention, was dealing with flood of lines when @@ -607,9 +730,20 @@ namespace Karma std::vector m_swapChainFrameBuffers; VkCommandPool m_commandPool; - std::set> m_VulkanUBO; + std::set m_VulkanUBO; + + // General vulkan resources + std::shared_ptr m_GeneralShader; + std::shared_ptr m_GeneralTexture; + VkPipelineLayout m_KarmaGuiGeneralPipelineLayout; + VkPipeline m_KarmaGuiGeneralGraphicsPipeline; + VkDescriptorSetLayout m_ViewLayout; // Camera UBO + VkDescriptorSetLayout m_TextureLayout; // Material UBO + texture sampler + VkDescriptorSetLayout m_ObjectLayout; // Object UBO + VkDescriptorPool m_GeneralDescriptorPool; + std::vector m_GeneralDescriptorSets; - bool bVSync = false; + bool bVSync = true; VkImage m_DepthImage; VkDeviceMemory m_DepthImageMemory; diff --git a/Karma/src/Platform/Vulkan/VulkanHolder.h b/Karma/src/Platform/Vulkan/VulkanHolder.h index 32dd0a46..4e332c3c 100644 --- a/Karma/src/Platform/Vulkan/VulkanHolder.h +++ b/Karma/src/Platform/Vulkan/VulkanHolder.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Platform/Vulkan/VulkanContext.h" namespace Karma @@ -54,4 +52,4 @@ namespace Karma private: static VulkanContext* m_VulkanContext; }; -} \ No newline at end of file +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDescriptorSets.cpp b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDescriptorSets.cpp new file mode 100644 index 00000000..7c54acb1 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDescriptorSets.cpp @@ -0,0 +1,212 @@ +#include "VulkanDescriptorSets.h" +#include "VulkanDevice.h" +#include "VulkanBuffer.h" +#include "VulkanTexture.h" + +namespace Karma +{ + void FVulkanDescriptorSetsLayoutInfo::AddDescriptor(int32_t DescriptorSetIndex, const VkDescriptorSetLayoutBinding& Descriptor) + { + if (m_LayoutTypes.Contains(Descriptor.descriptorType)) + { + m_LayoutTypes[Descriptor.descriptorType]++; + } + else + { + m_LayoutTypes.Add(Descriptor.descriptorType, 1); + } + + if (DescriptorSetIndex > m_SetLayouts.Num()) + { + m_SetLayouts.Resize(DescriptorSetIndex + 1); + } + + FSetLayout& layout = m_SetLayouts[DescriptorSetIndex]; + layout.m_LayoutBindings.Add(Descriptor); + } + + void FVulkanDescriptorSetsLayoutInfo::AddDescriptorSet(FSetLayout SetLayout) + { + m_SetLayouts.Add(SetLayout); + + for (const auto& layoutBindings : SetLayout.m_LayoutBindings) + { + if (m_LayoutTypes.Contains(layoutBindings.descriptorType)) + { + m_LayoutTypes[layoutBindings.descriptorType]++; + } + else + { + m_LayoutTypes.Add(layoutBindings.descriptorType, 1); + } + } + } + + FVulkanDescriptorSetsLayout::FVulkanDescriptorSetsLayout(FVulkanDevice* InDevice) : m_Device(InDevice), + m_DescriptorSetsAllocateInfo({}) + { + } + + FVulkanDescriptorSetsLayout::~FVulkanDescriptorSetsLayout() + { + for (const auto& handle : m_LayoutHandles) + { + vkDestroyDescriptorSetLayout(m_Device->GetLogicalDevice(), handle, nullptr); + } + + m_LayoutHandles.Clear(); + } + + void FVulkanDescriptorSetsLayout::Compile() + { + for (const auto& setLayout : m_SetLayouts) + { + VkDescriptorSetLayout handle = VK_NULL_HANDLE; + + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(setLayout.m_LayoutBindings.Num()); + layoutInfo.pBindings = setLayout.m_LayoutBindings.GetData(); // <------ contains the information of how many descriptors of each type are used in the layout, and their shader stage flags + + VkResult result = vkCreateDescriptorSetLayout(m_Device->GetLogicalDevice(), &layoutInfo, nullptr, &handle); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create descriptor set layout!"); + + m_LayoutHandles.Add(handle); + } + } + + FVulkanDescriptorPool::FVulkanDescriptorPool(FVulkanDevice* InDevice, const FVulkanDescriptorSetsLayout& Layout/*, uint32_t MaxSetsAllocations*/) + : m_Device(InDevice), m_Layout(Layout), + m_MaxDescriptorSets(0), m_DescriptorPool(VK_NULL_HANDLE) + { + // Descriptor sets number required to allocate max number of descriptor sets layout + //m_MaxDescriptorSets = MaxSetsAllocations * Layout.GetLayouts().Num(); + + KarmaMap descriptorTypeCounts; + + for (const auto& layout : Layout.GetLayouts()) + { + m_MaxDescriptorSets += layout.m_NumberOfDescriptorSets; + + for (const auto& layoutBindings : layout.m_LayoutBindings) + { + if (descriptorTypeCounts.Contains(layoutBindings.descriptorType)) + { + descriptorTypeCounts[layoutBindings.descriptorType] += layoutBindings.descriptorCount * layout.m_NumberOfDescriptorSets; + } + else + { + descriptorTypeCounts.Add(layoutBindings.descriptorType, layoutBindings.descriptorCount * layout.m_NumberOfDescriptorSets); + } + } + } + + KarmaVector types; + + for (const auto& layoutType : Layout.GetLayoutTypes()) + { + VkDescriptorPoolSize poolSize{}; + poolSize.type = VkDescriptorType(layoutType.first); + //poolSize.descriptorCount = Layout.GetTypesUsed(poolSize.type) * MaxSetsAllocations;// the number of descriptors of this type across all the descriptor sets + poolSize.descriptorCount = descriptorTypeCounts[layoutType.first];// the number of descriptors of this type across all the descriptor sets + types.Add(poolSize); + } + + // Note: A descriptor set may have multiple types of descriptors. + // For example, a descriptor set may have both uniform buffers and combined image samplers. + + VkDescriptorPoolCreateInfo PoolInfoP{}; + PoolInfoP.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + PoolInfoP.maxSets = m_MaxDescriptorSets;// Max number of descriptor sets that can be allocated from this pool (via vkAllocateDescriptorSets) + PoolInfoP.poolSizeCount = static_cast(types.Num()); + PoolInfoP.pPoolSizes = types.GetData(); + + VkResult result = vkCreateDescriptorPool(m_Device->GetLogicalDevice(), &PoolInfoP, nullptr, &m_DescriptorPool); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create descriptor pool!"); + } + + FVulkanDescriptorPool::~FVulkanDescriptorPool() + { + if (m_DescriptorPool != VK_NULL_HANDLE) + { + vkDestroyDescriptorPool(m_Device->GetLogicalDevice(), m_DescriptorPool, nullptr); + m_DescriptorPool = VK_NULL_HANDLE; + + //KR_CORE_INFO("Destroying descriptorpool"); + } + } + + void FVulkanDescriptorPool::AllocateDescriptorSets(const FVulkanDescriptorSetsLayout& InLayout, FVulkanDescriptorSets& InDSets) + { + uint32_t setIndex = 0; + for (const auto& layout : InLayout.GetLayouts()) + { + KarmaVector& dSets = InDSets.m_DescriptorSets[setIndex]; + + for (auto& descriptorSet : dSets) + { + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = m_DescriptorPool; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = &InLayout.GetHandles()[setIndex]; + + VkResult result = vkAllocateDescriptorSets(m_Device->GetLogicalDevice(), &allocInfo, &descriptorSet); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to allocate descriptor set!"); + } + + setIndex++; + } + } + + FVulkanDescriptorSets::FVulkanDescriptorSets(FVulkanDevice* InDevice, const FVulkanDescriptorSetsLayout& InLayout) + { + m_Device = InDevice; + + m_DescriptorSets.Resize(InLayout.GetLayouts().Num()); + + uint32_t setIndex = 0; + + for (const auto& layout : InLayout.GetLayouts()) + { + m_DescriptorSets[setIndex++].Resize(layout.m_NumberOfDescriptorSets); + } + } + + void FVulkanDescriptorSets::UpdateUniformBufferDescriptorSet(VulkanUniformBuffer* Uniform, uint32_t SetLayoutIndex, + uint32_t DescriptorSetIndex, uint32_t FrameIndex) + { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = Uniform->GetUniformBuffers()[FrameIndex]; + bufferInfo.offset = 0; + bufferInfo.range = Uniform->GetBufferSize(); + + VkWriteDescriptorSet descriptorWrite{}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = m_DescriptorSets[SetLayoutIndex][DescriptorSetIndex]; + descriptorWrite.dstBinding = 0; // Assuming the uniform buffer is bound to binding 0 in the shader + descriptorWrite.dstArrayElement = 0; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pBufferInfo = &bufferInfo; + vkUpdateDescriptorSets(m_Device->GetLogicalDevice(), 1, &descriptorWrite, 0, nullptr); + } + + void FVulkanDescriptorSets::UpdateTextureDescriptorSet(VulkanTexture* Texture, uint32_t SetLayoutIndex, uint32_t DescriptorSetIndex) + { + VkDescriptorImageInfo imageInfo{}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = Texture->GetImageView(); + imageInfo.sampler = Texture->GetImageSampler(); + + VkWriteDescriptorSet descriptorWrite{}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = m_DescriptorSets[SetLayoutIndex][DescriptorSetIndex]; + descriptorWrite.dstBinding = 1; // Assuming the texture is bound to binding 1 in the shader + descriptorWrite.dstArrayElement = 0; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pImageInfo = &imageInfo; + vkUpdateDescriptorSets(m_Device->GetLogicalDevice(), 1, &descriptorWrite, 0, nullptr); + } +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDescriptorSets.h b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDescriptorSets.h new file mode 100644 index 00000000..f27981e0 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDescriptorSets.h @@ -0,0 +1,247 @@ +/** + * @file VulkanDescriptorSets.h + * @brief Header file for Vulkan descriptor sets management in a Vulkan rendering context. + * @author Ravi Mohan (the_cowboy) + * @date January 30, 2026 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include "KarmaTypes.h" +#include + +namespace Karma +{ + class FVulkanDevice; + + class FVulkanDescriptorSetsLayoutInfo + { + public: + /** + * @brief Constructor + * + * Adds all the Vulkan descriptor types currently needed. + * + * @since Karma 1.0.0 + */ + FVulkanDescriptorSetsLayoutInfo() + { + } + + void SetLayoutTypes(const KarmaVector& NeededDescriptorTypes) + { + for (const VkDescriptorType& type : NeededDescriptorTypes) + { + m_LayoutTypes.Add(type, 1); + } + } + + /** + * @brief Returns the number of descriptors of a specific type used in the layout, across all descriptor sets + * + * @param Type The Vulkan descriptor type to query. + * @return The number of descriptors of the specified type used in the layout. + * + * @since Karma 1.0.0 + */ + inline uint32_t GetTypesUsed(VkDescriptorType Type) const + { + if (m_LayoutTypes.Contains(Type)) + { + return m_LayoutTypes[Type]; + } + else + { + return 0; + } + } + + /** + * @brief Structure representing a descriptor set layout. + */ + struct FSetLayout + { + /** + * @brief Layout bindings for this descriptor set, representing the individual + * descriptors and their configurations within the set. + */ + KarmaVector m_LayoutBindings; + uint32_t m_Hash = 0; + + /** + * @brief The number of descriptor sets that use this layout. This is used for calculating the total number of descriptors + * needed for the pool allocation and appropriating the number of descriptor sets to allocate (vkAllocateDescriptorSets), from + * the pool, using this layout. + */ + uint32_t m_NumberOfDescriptorSets = 0; + + inline void GenerateHash() + { + // Simple hash generation based on bindings + m_Hash = 0; + for (const auto& binding : m_LayoutBindings) + { + m_Hash ^= std::hash()(binding.binding) ^ + std::hash()(binding.descriptorType) ^ + std::hash()(binding.descriptorCount) ^ + std::hash()(binding.stageFlags); + } + } + + friend uint32_t GetTypeHash(const FSetLayout& Layout) + { + return Layout.m_Hash; + } + + }; + + inline const KarmaVector& GetLayouts() const + { + return m_SetLayouts; + } + + inline const KarmaMap& GetLayoutTypes() const + { + return m_LayoutTypes; + } + + /*protected:*/ + void AddDescriptor(int32_t DescriptorSetIndex, const VkDescriptorSetLayoutBinding& Descriptor); + void AddDescriptorSet(FSetLayout SetLayout); + + protected: + /** + * @brief Map storing the count of each Vulkan descriptor type used in the layout. + */ + KarmaMap m_LayoutTypes; + KarmaVector m_SetLayouts; + + uint32_t m_Hash = 0; + VkPipelineBindPoint m_BindPoint = VK_PIPELINE_BIND_POINT_MAX_ENUM; + }; + + class FVulkanDescriptorSetsLayout : public FVulkanDescriptorSetsLayoutInfo + { + public: + FVulkanDescriptorSetsLayout(FVulkanDevice* InDevice); + ~FVulkanDescriptorSetsLayout(); + + void Compile(); + + inline const KarmaVector& GetHandles() const + { + return m_LayoutHandles; + } + + inline const KarmaVector& GetHandleIds() const + { + return m_LayoutHandleIds; + } + + inline const VkDescriptorSetAllocateInfo& GetAllocateInfo() const + { + return m_DescriptorSetsAllocateInfo; + } + + inline uint32_t GetHash() const + { + return m_Hash; + } + + private: + FVulkanDevice* m_Device; + KarmaVector m_LayoutHandles; + KarmaVector m_LayoutHandleIds; + VkDescriptorSetAllocateInfo m_DescriptorSetsAllocateInfo; + }; + + struct FVulkanDescriptorSets + { + /** + * @brief Constructor for FVulkanDescriptorSets. + * + * Initializes the descriptor sets based on the provided Vulkan device and descriptor set layout. + * + * The descriptor sets are allocated based on the layout information, and are stored in a vector of vectors, where each inner + * vector corresponds to a specific descriptor set layout, and contains the allocated descriptor sets for that layout. + * + * @param InDevice The Vulkan device used for creating descriptor sets. + * @param InLayout The descriptor set layout defining the structure of the descriptor sets. + * + * @since Karma 1.0.0 + */ + FVulkanDescriptorSets(FVulkanDevice* InDevice, const FVulkanDescriptorSetsLayout& InLayout); + + /** + * @brief Assigns an Engine's uniform buffer to a specific descriptor set of a specific layout. + * + * @param Uniform The uniform buffer to be assigned to the descriptor set + * @param SetLayoutIndex The index of the descriptor set layout to which the descriptor set belongs + * @param DescriptorSetIndex The index of the descriptor set within the specified layout to which the uniform buffer will be assigned + * @param FrameIndex The index of the the frame for which the descriptor set will be updated with the uniform buffer. Uniform + * buffers are for each frame in flight, so the descriptor sets must be updated for each frame in flight, with the corresponding uniform buffer for that frame + * + * @since Karma 1.0.0 + */ + void UpdateUniformBufferDescriptorSet(class VulkanUniformBuffer* Uniform, uint32_t SetLayoutIndex, uint32_t DescriptorSetIndex, uint32_t FrameIndex); + + /** + * @brief Assigns an Engine's texture to a specific descriptor set of a specific layout. + * + * @param Texture The texture to be assigned to the descriptor set + * @param SetLayoutIndex The index of the descriptor set layout to which the descriptor set belongs + * + * @param DescriptorSetIndex The index of the descriptor set within the specified layout to which the texture will be assigned + * + * @since Karma 1.0.0 + */ + void UpdateTextureDescriptorSet(class VulkanTexture* Texture, uint32_t SetLayoutIndex, uint32_t DescriptorSetIndex); + + FVulkanDevice* m_Device; + KarmaVector> m_DescriptorSets; + }; + + class FVulkanDescriptorPool + { + public: + FVulkanDescriptorPool(FVulkanDevice* InDevice, const FVulkanDescriptorSetsLayout& InLayout/*, uint32_t MaxSetsAllocations*/); + ~FVulkanDescriptorPool(); + + /** + * @brief Retrieves the Vulkan descriptor pool handle. + * + * @return VkDescriptorPool The Vulkan descriptor pool handle. + * @since Karma 1.0.0 + */ + inline VkDescriptorPool GetHandle() const + { + return m_DescriptorPool; + } + + + /** + * @brief Allocates descriptor sets from the pool based on the provided allocation info. + * + * @param InDescriptorSetAllocateInfo Information about the descriptor set allocation + * @param OutSets Pointer to an array where allocated descriptor sets will be stored + * @return VkDescriptorSet The first allocated descriptor set handle + * @since Karma 1.0.0 + */ + void AllocateDescriptorSets(const FVulkanDescriptorSetsLayout& InLayout, FVulkanDescriptorSets& InDSets); + + private: + FVulkanDevice* m_Device; + uint32_t m_MaxDescriptorSets; + const FVulkanDescriptorSetsLayout& m_Layout; + VkDescriptorPool m_DescriptorPool; + }; + + class FVulkanDescriptorPoolsManager + { + private: + FVulkanDevice* m_Device = nullptr; + + }; +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDevice.cpp b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDevice.cpp new file mode 100644 index 00000000..c7aca176 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDevice.cpp @@ -0,0 +1,342 @@ +#include "VulkanDevice.h" +#include "VulkanDynamicRHI.h" +#include "Vulkan/VulkanTexture.h" +#include "Vulkan/VulkanShader.h" +#include "VulkanRHI/VulkanDescriptorSets.h" + +namespace Karma +{ + FVulkanDevice::FVulkanDevice(FVulkanDynamicRHI* InRHI, VkPhysicalDevice InGpu) : m_VulkanDynamicRHI(InRHI), + m_GPU(InGpu), m_FenceManager(*this), m_CommandPool(VK_NULL_HANDLE), m_DefaultTexture(nullptr), m_DefaultDescriptorSetLayout(nullptr), + m_LogicalDevice(VK_NULL_HANDLE), m_GraphicsQueue(VK_NULL_HANDLE), m_PresentQueue(VK_NULL_HANDLE) + { + // Implementation for creating a Vulkan logical device + } + + FVulkanDevice::~FVulkanDevice() + { + // Implementation for cleaning up Vulkan device resources + } + + void FVulkanDevice::Destroy() + { + // vkdestroy default buffers etc + for (uint32_t counter = 0; counter < m_MaxFramesInFlight; counter++) + { + delete m_DefaultDescriptorSets[counter]; + delete m_DescriptorPool[counter]; + } + + delete m_DefaultShader; + delete m_DefaultTexture; + delete m_DefaultDescriptorSetLayout; + vkDestroyCommandPool(m_LogicalDevice, m_CommandPool, nullptr); + vkDestroyDevice(m_LogicalDevice, nullptr); + } + + void FVulkanDevice::InitGPU() + { + QueueFamilyIndices indices = m_VulkanDynamicRHI->FindQueueFamilies(m_GPU); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), + indices.presentFamily.value() }; + + if (m_VulkanDynamicRHI->GetValidationLayersSetting()) + { + KR_CORE_INFO("+-------------------------------------------------"); + KR_CORE_INFO("| Available Unique Queue Family Indices (GPU):"); + uint32_t index = 1; + for (uint32_t queueFamily : uniqueQueueFamilies) + { + KR_CORE_INFO("| {0}. {1}", index++, queueFamily); + } + KR_CORE_INFO("+-------------------------------------------------"); + } + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) + { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + if (m_VulkanDynamicRHI->GetGpuDeviceFeatures().logicOp) + { + deviceFeatures.logicOp = VK_TRUE; + } + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.enabledExtensionCount = static_cast(FVulkanDynamicRHI::deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = FVulkanDynamicRHI::deviceExtensions.data(); + + if (m_VulkanDynamicRHI->GetValidationLayersSetting()) + { + createInfo.enabledLayerCount = static_cast(FVulkanDynamicRHI::validationLayers.size()); + createInfo.ppEnabledLayerNames = FVulkanDynamicRHI::validationLayers.data(); + } + else + { + createInfo.enabledLayerCount = 0; + } + + VkResult result = vkCreateDevice(m_GPU, &createInfo, nullptr, &m_LogicalDevice); + + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create logical device!"); + KR_CORE_INFO("Successfully created a Vulkan logical device"); + + vkGetDeviceQueue(m_LogicalDevice, indices.graphicsFamily.value(), 0, &m_GraphicsQueue); + vkGetDeviceQueue(m_LogicalDevice, indices.presentFamily.value(), 0, &m_PresentQueue); + + // CommandPool (UE has FVulkanDescriptorPoolsManager class for managing commandpools) + QueueFamilyIndices queueFamilyIndices = m_VulkanDynamicRHI->FindQueueFamilies(m_GPU); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + + result = vkCreateCommandPool(m_LogicalDevice, &poolInfo, nullptr, &m_CommandPool); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create command pool!"); + + KR_CORE_INFO("Created Vulkan commandpool"); + + // Default descriptorsets + m_DefaultDescriptorSetLayout = new FVulkanDescriptorSetsLayout(this); + + PopulateWithDescriptorSetsLayout(*m_DefaultDescriptorSetLayout); + m_DefaultDescriptorSetLayout->Compile(); + + + // Default texture (unreal grid) + m_DefaultTexture = new VulkanTexture(this, "../Resources/Textures/UnrealGrid.png"); + m_DefaultShader = new VulkanShader("../Resources/Shaders/shader.vert", "../Resources/Shaders/shader.frag"); + } + + void FVulkanDevice::InitializeDefaultDescriptorSets(uint32_t MaxFramesInFlight) + { + m_DescriptorPool.Resize(MaxFramesInFlight); + m_DefaultDescriptorSets.Resize(MaxFramesInFlight); + + for (uint32_t counter = 0; counter < MaxFramesInFlight; counter++) + { + m_DescriptorPool[counter] = new FVulkanDescriptorPool(this, *m_DefaultDescriptorSetLayout); + m_DefaultDescriptorSets[counter] = new FVulkanDescriptorSets(this, *m_DefaultDescriptorSetLayout); + + m_DescriptorPool[counter]->AllocateDescriptorSets(*m_DefaultDescriptorSetLayout, *m_DefaultDescriptorSets[counter]); + m_DefaultDescriptorSets[counter]->UpdateTextureDescriptorSet(m_DefaultTexture, 0, 0); + } + + m_MaxFramesInFlight = MaxFramesInFlight; + } + + void FVulkanDevice::PopulateWithDescriptorSetsLayout(FVulkanDescriptorSetsLayout& InLayout) + { + // Set 0: Global descriptors (camera UBO, texture sampler) + FVulkanDescriptorSetsLayoutInfo::FSetLayout setLayout; + + // Camera UBO at binding 0 (vertex shader) + setLayout.m_LayoutBindings.Add({ + 0, // binding + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 1, // descriptorCount + VK_SHADER_STAGE_VERTEX_BIT, + nullptr + }); + // Texture sampler at binding 1 (fragment shader) with default sampler and image view + setLayout.m_LayoutBindings.Add({ + 1, // binding + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + 1, // descriptorCount + VK_SHADER_STAGE_FRAGMENT_BIT, + nullptr + }); + + setLayout.m_NumberOfDescriptorSets = 1; + + InLayout.AddDescriptorSet(setLayout); + + // Set 1: Per-mesh descriptors (per-mesh UBO) + FVulkanDescriptorSetsLayoutInfo::FSetLayout meshSetLayout; + // Per mesh UBO at binding 0 (vertex shader) + meshSetLayout.m_LayoutBindings.Add({ + 0, // binding + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 1, // descriptorCount + VK_SHADER_STAGE_VERTEX_BIT, + }); + meshSetLayout.m_NumberOfDescriptorSets = 16; + + InLayout.AddDescriptorSet(meshSetLayout); + } + + void FVulkanDevice::WaitUntilIdle() + { + VkResult result = vkDeviceWaitIdle(m_LogicalDevice); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to wait"); + } + + void FVulkanDevice::TransitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) + { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = m_CommandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(m_LogicalDevice, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL && HasStencilComponent(format)) + { + barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + else + { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcAccessMask = 0; // TODO + barrier.dstAccessMask = 0; // TODO + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } + else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) + { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + } + else + { + KR_CORE_ASSERT(false, "Unsupported layout transition!"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(m_GraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(m_GraphicsQueue); + + vkFreeCommandBuffers(m_LogicalDevice, m_CommandPool, 1, &commandBuffer); + } + + void FVulkanDevice::CopyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) + { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = m_CommandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(m_LogicalDevice, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(m_GraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(m_GraphicsQueue); + + vkFreeCommandBuffers(m_LogicalDevice, m_CommandPool, 1, &commandBuffer); + } + + bool FVulkanDevice::HasStencilComponent(VkFormat format) + { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDevice.h b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDevice.h new file mode 100644 index 00000000..05003390 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDevice.h @@ -0,0 +1,217 @@ +/** + * @file VulkanDevice.h + * @brief Declaration of the FVulkanDevice class for managing Vulkan device resources. + * @author Ravi Mohan (the_cowboy) + * @version 1.0 + * @date December 17, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include +#include "VulkanSynchronization.h" + +namespace Karma +{ + class FVulkanDynamicRHI; + class VulkanTexture; + class FVulkanDescriptorSetsLayout; + class FVulkanDescriptorSets; + class FVulkanSwapChain; + class VulkanShader; + + /** + * @class FVulkanDevice + * @brief Manages Vulkan device resources and operations. + * + * This class encapsulates the functionality related to a Vulkan logical device, + * including initialization, resource management, and cleanup. + */ + class FVulkanDevice + { + public: + /** + * @brief Constructor for FVulkanDevice. + * + * Initializes the Vulkan device with the given physical device (GPU) and dynamic RHI instance. + * + * @param InRHI Pointer to the FVulkanDynamicRHI instance + * @param InGpu The Vulkan physical device (GPU) handle + * + * @since Karma 1.0.0 + */ + FVulkanDevice(FVulkanDynamicRHI* InRHI, VkPhysicalDevice InGpu); + + /** + * @brief Destructor for FVulkanDevice. + * + * Cleans up resources associated with the Vulkan device. + * + * @since Karma 1.0.0 + */ + ~FVulkanDevice(); + + /** + * @brief Creates the Vulkan logical device and default vulkan resources + * + * @note Called from FVulkanDynamicRHI::InitInstance() + * @since Karma 1.0.0 + */ + void InitGPU(); + + /** + * @brief Destroys the logical vkdevice by making Vulkan API call + * + * @since Karma 1.0.0 + */ + void Destroy(); + + /** + * @brief Waits on the host by blocking the calling CPU thread until the + * Vulkan logical device completes all pending GPU operations across all queues. + * + * @since Karma 1.0.0 + */ + void WaitUntilIdle(); + + /** + * @brief Retrieves the Vulkan logical device handle. + * + * @return VkDevice The Vulkan logical device. + */ + VkDevice GetLogicalDevice() const { return m_LogicalDevice; } + + /** + * @brief Retrieves the Vulkan physical device handle. + * + * @return VkPhysicalDevice The Vulkan physical device (GPU) + */ + VkPhysicalDevice GetGPU() const { return m_GPU; } + + /** + * @brief Retrives the Vulkan command pool for the commandbuffers + * + * @since Karma 1.0.0 + */ + VkCommandPool GetCommandPool() const { return m_CommandPool; } + + ///////////////// Utility Functions ///////////////// + /** + * @brief Transitions the layout of an image from oldLayout to newLayout. + * + * Image layout transitions are crucial in Vulkan to ensure that images are in the correct state for different operations, such as rendering, sampling, or transferring data. + * + * @param image The image to be transitioned + * @param format The format of the image + * @param oldLayout The current layout of the image + * @param newLayout The desired layout of the image + * + * @see VulkanTexture::CreateTextureImage() + * @since Karma 1.0.0 + */ + void TransitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout); + + /** + * @brief Copies data from a buffer to a Vulkan image. + * + * This is typically used for uploading texture data from a staging buffer to a Vulkan image. + * + * @param buffer The source buffer containing the data + * @param image The destination image + * @param width The width of the image + * @param height The height of the image + * + * @see VulkanTexture::CreateTextureImage() + * @since Karma 1.0.0 + */ + void CopyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height); + + /** + * @brief Checks if the given format has a stencil component. + * + * Sees if the format is VK_FORMAT_D32_SFLOAT_S8_UINT or VK_FORMAT_D24_UNORM_S8_UINT + * + * @param format The format to be checked + * + * @see VulkanContext::TransitionImageLayout() + * @since Karma 1.0.0 + */ + bool HasStencilComponent(VkFormat format); + + void InitializeDefaultDescriptorSets(uint32_t MaxFramesInFlight); + + ///////////////// Getters ///////////////// + + /** + * @brief Getter for the graphics queue created in FVulkanDevice::InitGPU() + * + * @since Karma 1.0.0 + */ + inline VkQueue GetGraphicsQueue() const { return m_GraphicsQueue; } + + /** + * @brief Getter for the present queue created in FVulkanDevice::InitGPU() + * + * @since Karma 1.0.0 + */ + inline FVulkanDynamicRHI* GetVulkanDynamicRHI() const { return m_VulkanDynamicRHI; } + + /** + * @brief Getter for the default texture (unreal grid) + * + * @since Karma 1.0.0 + */ + inline FVulkanFenceManager& GetFenceManager() { return m_FenceManager; } + + inline KarmaVector& GetDefaultDescriptorSets() { return m_DefaultDescriptorSets; } + + inline FVulkanDescriptorSetsLayout* GetDefaultDescriptorSetLayout() const { return m_DefaultDescriptorSetLayout; } + + inline VulkanTexture* GetDefaultTexture() const { return m_DefaultTexture; } + + inline VulkanShader* GetDefaultShader() const { return m_DefaultShader; } + + private: + /** + * @brief Populates the provided descriptor sets layout with the necessary bindings for the device. + * + * This function is responsible for defining the descriptor set layouts that will be used for resource binding in shaders. It + * adds the required bindings for global resources (like camera UBO and texture sampler) and per-mesh resources (like per-mesh UBO). + * + * From shader POV, the sets are like so + * set = 0, binding = 0: Camera UBO (vertex shader) + * set = 0, binding = 1: Texture sampler (fragment shader) + * + * set = 1, binding = 0: Per-mesh UBO (vertex and fragment shader) + * + * @param OutLayout The descriptor sets layout to be populated with the necessary bindings. + * + * @see FVulkanDescriptorSetsLayout + * @since Karma 1.0.0 + */ + void PopulateWithDescriptorSetsLayout(FVulkanDescriptorSetsLayout& OutLayout); + + private: + VkDevice m_LogicalDevice; ///< The Vulkan logical device handle. + FVulkanDynamicRHI* m_VulkanDynamicRHI; + + VkPhysicalDevice m_GPU; + VkQueue m_GraphicsQueue; + VkQueue m_PresentQueue; + VulkanTexture* m_DefaultTexture; + VulkanShader* m_DefaultShader; + + // Additional members for managing queues, command pools, etc. can be added here. + VkCommandPool m_CommandPool; + + // Default descriptor set layout for the device, which can be used for common resources like camera UBO and default texture sampler. + FVulkanDescriptorSetsLayout* m_DefaultDescriptorSetLayout; + KarmaVector m_DescriptorPool; + KarmaVector m_DefaultDescriptorSets; + uint32_t m_MaxFramesInFlight = 0; + + FVulkanFenceManager m_FenceManager; + }; +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDynamicRHI.cpp b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDynamicRHI.cpp new file mode 100644 index 00000000..e69de29b diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDynamicRHI.h b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDynamicRHI.h new file mode 100644 index 00000000..75976c6a --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanDynamicRHI.h @@ -0,0 +1,551 @@ +/** + * @file VulkanDynamicRHI.h + * @brief + * @author Ravi Mohan (the_cowboy) + * @version 1.0 + * @date December 15, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include "DynamicRHI.h" +#include "VulkanRHI.h" + +#include "VulkanDevice.h" + +struct GLFWwindow; + +namespace Karma +{ + /** + * @brief Forward declaration of VulkanUniformBuffer class. + */ + class VulkanUniformBuffer; + + /** + * @brief Template function to get the dynamic RHI instance casted to the specified type. + * + * @tparam TRHI The type to which the dynamic RHI instance should be casted. + * @return Pointer to the dynamic RHI instance of type TRHI. + * + * @since Karma 1.0.0 + */ + template + FORCEINLINE TRHI* GetDynamicRHI() + { + return static_cast(GDynamicRHI); + } + + /** + * @brief A structure for graphics and present queuefamilies + * + * Most operations performed with Vulkan, like draw commands and memory operations, are + * asynchronously executed by submitting them to a VkQueue. Queues are allocated from queue + * families, where each queue family supports a specific set of operations in its queues. For example, + * there could be separate queue families for graphics, compute, and memory transfer operations. + * + * Used for creating logical device, swapchain, and commandpool + * + * @see FVulkanDynamicRHI::FindQueueFamilies() + * @since Karma 1.0.0 + */ + struct QueueFamilyIndices + { + /** + * @brief The queues in this queue family support graphics operations. + * + * @note The optional is used to make the query of availibility easier + * @since Karma 1.0.0 + */ + std::optional graphicsFamily; + + /** + * @brief The queues in this queue family support image presentation + * + * The image is presented to the surface + * + * @note The optional is used to make the query of availibility easier + * @see VulkanContext::CreateSurface() + * + * @since Karma 1.0.0 + */ + std::optional presentFamily; + + /** + * @brief Routine for querying if appropriate queue families (graphicsFamily and presentFamily) are available. + * + * @see VulkanContext::IsDeviceSuitable + * @since Karma 1.0.0 + */ + bool IsComplete() + { + return graphicsFamily.has_value() && presentFamily.has_value(); + } + }; + + /** + * @brief Structure with data required for appropriate creation and working of swapchain. + * + * Vulkan does not have the concept of a "default framebuffer", hence it requires an infrastructure that will own + * the buffers we will render to before we visualize them on the screen. This infrastructure is known as the swap chain + * and must be created explicitly in Vulkan. The swap chain is essentially a queue of images that are waiting to be + * presented to the screen. + * + * @since Karma 1.0.0 + */ + struct SwapChainSupportDetails + { + /** + * @brief Basic surface capabilities (min/max number of images in swap chain, min/max width + * and height of images) + * + * @since Karma 1.0.0 + */ + VkSurfaceCapabilitiesKHR capabilities; + + /** + * @brief Surface formats (pixel format, color space) + * + * @since Karma 1.0.0 + */ + std::vector formats; + + /** + * @brief Available presentation modes + * + * @since Karma 1.0.0 + */ + std::vector presentModes; + }; + + /** + * @brief Vulkan implementation of the Dynamic RHI. + * + * Provides Vulkan-specific implementations for initializing/deinitializing RHI along with Vulkan resources and rendering operations. + * + * @since Karma 1.0.0 + */ + class FVulkanDynamicRHI : public IVulkanDynamicRHI + { + public: + /** + * @brief Getter for the FVulkanDynamicRHI instance + * + * @since Karma 1.0.0 + */ + static FVulkanDynamicRHI& Get() { return *GetDynamicRHI(); } + + /** + * @brief Initialization constructor. + * + * Does the following + * 1. Creates vkInstance + * 2. Sets up debug messenger (with Karma's logging callback function) + * 3. Creates a Vulkan surface to interface with the Engine's window (Editor window for instance) + * 4. Selects a physical device (GPU) based upon the availability of device extensions, swapchain support, + * and samplerAnisotropy + * 5. Creates Vulkan logical device + * + * @note Smapler anisotorpy is a technique that makes textures on surfaces viewed at steep angles (like roads or floors + * stretching into the distance) look clearer and sharper, preventing blurriness by taking multiple samples along the + * texture's axis of elongation rather than a single one. + * + * @since Karma 1.0.0 + */ + FVulkanDynamicRHI(); + + /** + * @brief For initializing VulkanDynamic RHI + * + * Called from RHIInit() declared in KarmaRHI.h and defined in DynamicRHI.cpp + * + * @see FVulkanDynamicRHI::InitInstance() + * + * @since Karma 1.0.0 + */ + virtual bool Init() override; + + /** + * @brief Shuts down the RHI. + * + * Called from RHIExit() declared in KarmaRHI.h and defined in DynamicRHI.cpp + * + * Cleans up resources and states used by the RHI. + * @since Karma 1.0.0 + */ + virtual void Shutdown() override; + + /** + * @brief Calls FVulkanDevice::InitGPU() to create Vulkan logical device from + * selected GPU, and created appropriate resources (commandpool, default texture etc). + * + * @see FVulkanDevice::InitGPU() + * @since Karma 1.0.0 + */ + void InitInstance(); + + /** + * @brief Presents the rendered frame to the display. + * + * @since Karma 1.0.0 + */ + virtual void Present() override {} + + /** + * @brief Uses Two-Pass Query to gather surface formats (physical device and surface paired color space or pixel format data) and present modes data. + * + * @see VulkanContext::IsDeviceSuitable(), and VulkanContext::CreateSwapChain() + * @since Karma 1.0.0 + */ + SwapChainSupportDetails QuerySwapChainSupport(VkPhysicalDevice device); + + /** + * @brief Getter for Vulkan instance + * + * @since Karma 1.0.0 + */ + inline VkInstance GetInstance() const + { + return m_VulkanInstance; + } + + /** + * @brief Getter for FVulkanDevice object created in FVulkanDynamicRHI::SelectDevice() + * + * @since Karma 1.0.0 + */ + inline FVulkanDevice* GetDevice() const + { + return m_Device; + } + + /** + * @brief Queries the graphics card for available queue families and compares against the availability of graphics and + * presentation queues + * + * @param device The graphics card to be queired for queue family + * @since Karma 1.0.0 + */ + QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device); + + /** + * @brief Getter for the boolean bEnableValidationLayers + * + * For Debug builds bEnableValidationLayers is set to true + * + * @since Karma 1.0.0 + */ + inline bool GetValidationLayersSetting() const { return bEnableValidationLayers; } + + /** + * @brief Getter for m_SupportedDeviceFeatures which contains boolean values for supported features like + * availability of geometry shader or sampler anisotropy + * + * @since Karma 1.0.0 + */ + inline const VkPhysicalDeviceFeatures& GetGpuDeviceFeatures() const{ return m_SupportedDeviceFeatures; } + + /** + * @brief Getter for m_GPUSwapChainSupport struct which gets initialized in FVulkanDynamicRHI::IsDeviceSuitable() + * + * @since Karma 1.0.0 + */ + inline const SwapChainSupportDetails& GetGpuSwapChainSupportDetails() const { return m_GPUSwapChainSupport; } + + /** + * @brief Getter for the number of supported swapchain images + * + * @since Karma 1.0.0 + */ + inline uint32_t SwapChainImageCount() const { return m_SwapChainImageCount; } + + /** + * @brief Finds a supported format from the list of candidate formats based on the desired tiling and features. + * Used in depth resource creation. + * + * @see KarmaGuiVulkanHandler::MakeRenderPassInfo + * @since Karma 1.0.0 + */ + VkFormat FindSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) const; + + /** + * @brief Finds an appropriate depth format for depth resources. + * + * @see KarmaGuiVulkanHandler::MakeRenderPassInfo + * @since Karma 1.0.0 + */ + VkFormat FindDepthFormat() const; + + /** + * @brief Finds appropriate memory type with demanded properties. Basically a loop is run from counter i = 0 to VkPhysicalDeviceMemoryProperties.memoryTypeCount + * (number of valid elements in the memoryTypes array) and memoryType[i] is queried for appropriate properties. On condition satisfaction, counter i is returned. + * + * Graphics cards can offer different types of memory to allocate from. Each type of memory varies in terms of allowed operations and performance characteristics. + * We need to combine the requirements of the buffer and our own application requirements to find the right type of memory to use. + * + * @param typeFilter A bitmask, and contains one bit set for every supported memory type for the resource. Bit i is set if and only if the memory type i in the VkPhysicalDeviceMemoryProperties structure for the physical device is supported for the resource. + * @param properties The demanded properties (https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBufferUsageFlagBits.html). + * + * @return Memory type index i + * @see VulkanUniformBuffer::CreateBuffer + * @since Karma 1.0.0 + */ + uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties); + + // These may be abstracted to FDynamicRHI + /** + * @brief Register a uniformbuffer object to curate an array or uniformbuffers + * + * @note May be abstracted to FDynamicRHI. + * @note In Unreal Engine, there is no single global “array of uniform buffers” that UpdateUniformBuffer walks; instead, each system owns its own uniform buffer(s) and explicitly passes them into UpdateUniformBuffer when something changes. + * Where the inputs come from + * Uniform buffers are created and owned by higher-level engine systems: + * Per-primitive: via FPrimitiveSceneInfo for object transforms, lighting, etc. + * Per-view / per-pass: view uniforms, fog, shadow parameters, etc. + * Per-material: FMaterialParameterCollectionInstance and other material parameter structs. + */ + void RegisterUniformBufferObject(VulkanUniformBuffer* ubo); + + /** + * @brief Uploads data to the registered VulkanUniformBuffers for the specified frame index. + * + * Typically called during rendering to update uniform buffer data for the current frame like so + * + * @code{.cpp} + * vkCmdBeginRenderPass + * vkCmdBindPipeline + * vkCmdBindVertexBuffers + * vkCmdBindIndexBuffer + * UploadUniformBufferObjects() + * vkCmdBindDescriptorSets // Descriptor sets include UBOs + * vkCmdDrawIndexed + * vkCmdEndRenderPass + * @endcode + * + * @param frameIndex The index of the frame for which to upload UBO data + * + * @note The frameIndex is used to determine which uniform buffer to update, as multiple buffers + * may be used for double or triple buffering to avoid synchronization issues between CPU and GPU. + * + * @note The uniform buffers are resized automatically based on MAX_FRAMES_IN_FLIGHT (number of images (to work upon (CPU side) + * whilst an image is being rendered (GPU side processing)) + 1) + * @see VulkanUniformBuffer::BufferCreation() + * + * @see KarmaGuiRenderer::FrameRender, VulkanBuffer::UploadUniformBuffer + * @since Karma 1.0.0 + */ + void UploadUniformBufferObjects(size_t frameIndex); + + ///////////////////////// Getters ///////////////////////// + inline GLFWwindow* GetSurfaceWindow() const { return m_WindowHandle; } + inline VkSurfaceKHR GetSurface() const { return m_Surface; } + + protected: + // Vulkan-specific members and methods can be added here + + /** + * @brief Creates a Vulkan instance. + * + * @since Karma 1.0.0 + */ + void CreateInstance(); + + /** + * @brief Sets up the debug messenger for Vulkan instance. + * + * This function initializes the debug messenger that will handle debug messages from the Vulkan API. + * It populates the debug messenger create info structure and creates the debug messenger using + * the CreateDebugUtilsMessengerEXT function. + * + * @see FVulkanDynamicRHI::CreateDebugUtilsMessengerEXT() + * @since Karma 1.0.0 + */ + void SetupDebugMessenger(); + + /** + * @brief Platform agnostic creation of surface to present rendered images to. Typically they are backed by the + * category of glfw windows (on Linux, MacOS, and Windows). + * + * @since Karma 1.0.0 + */ + void CreateSurface(); + + /** + * @brief Picks an Engine appropriate GPU and creates FVulkanDevice + * + * @since Karma 1.0.0 + */ + void SelectDevice(); + + private: + + /** + * @brief Computes the number of images supported by the GPU for swapchain creation + * + * Makes sure that the m_SwapChainImageCount remains is in the interval [capabilities.minImageCount + 1, + * capabilities.maxImageCount]. + * + * @since Karma 1.0.0 + */ + void ComputeNumberOfSwapchainImagesSupported(); + + /** + * @brief Looks for extension properties supported by the GPU + * + * Calls vkEnumerateDeviceExtensionProperties for list of supported extensions for instance VK_KHR_swapchain which is + * required for, well, swapchain + * + * @note These are different from instance extensions printed in PrintAvailableExtensions() + * + * @since Karma 1.0.0 + */ + bool CheckDeviceExtensionSupport(VkPhysicalDevice device); + + /** + * @brief Checks if the physical device (graphics card) is suitable for the Engine to use based on + * avaibality of required queue families (graphics and presentation), required device extensions (like VK_KHR_swapchain), sampler anisotropy support (which is + * anisotropic filtering support in samplers, allowing higher-quality texture sampling at oblique angles to reduce blurring and aliasing artifacts seen in standard bilinear filtering.) + * + * @param device The graphics card to be checked for suitability + * @since Karma 1.0.0 + */ + bool IsDeviceSuitable(VkPhysicalDevice device); + + /** + * @brief Prints all the available physical devices (graphics cards) supported by the system's Vulkan implementation. + * + * @param physicalDevices The list of GPUs detected + * + * @see FVulkanDynamicRHI::SelectDevice() + * @since Karma 1.0.0 + */ + void PrintAvailablePhysicalDevices(const std::vector& physicalDevices); + + /** + * @brief Creates a debug utils messenger for Vulkan instance. + * + * This function is used to set up a debug messenger that will handle debug messages from the Vulkan API. + * Basically looks for the vkCreateDebugUtilsMessengerEXT extension, and if found, uses it to create the debug messenger by + * creating function pointer to it using vkGetInstanceProcAddr. + * + * @param instance The Vulkan instance. + * @param pCreateInfo Pointer to the debug utils messenger create info structure. + * @param pAllocator Optional custom allocator. + * @param pDebugMessenger Pointer to the debug utils messenger handle to be created. + * + * @return VkResult indicating success or failure of the operation. + * + * @see FVulkanDynamicRHI::SetupDebugMessenger() + * @since Karma 1.0.0 + */ + VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pDebugMessenger); + + /** + * @brief Destroys the debug utils messenger for Vulkan instance. + * + * @param instance The Vulkan instance. + * @param debugMessenger The debug utils messenger to be destroyed. + * + * @param pAllocator Optional custom allocator. + * + * @since Karma 1.0.0 + */ + void DestroyDebugUtilsMessengerEXT(VkInstance instance, + VkDebugUtilsMessengerEXT debugMessenger, + const VkAllocationCallbacks* pAllocator); + + /** + * @brief Prints all the available instance extensions supported by the system's Vulkan implementation (VkInstance). + * + * Vulkan extensions provide additional functionality beyond the core Vulkan API. + * + * For instance VK_KHR_Surface (for window system integration) or VK_KHR_get_physical_device_properties2 (for querying extended physical device properties). + * + * @see VulkanContext::CreateInstance() + * @since Karma 1.0.0 + */ + void PrintAvailableExtensions(); + + /** + * @brief Retrieves the required Vulkan instance extensions based on the platform and validation layer settings. + * + * 1. For window system integration, queries GLFW for required extensions. + * 2. For MacOS, adds VK_KHR_portability_enumeration extension if available. + * + * @param flagsToBeSet Reference to VkInstanceCreateFlags to be set based on required extensions. + * + * @return A vector of required extension names. + * + * @see VulkanContext::CreateInstance() + * @since Karma 1.0.0 + */ + std::vector GetRequiredExtensions(VkInstanceCreateFlags& flagsToBeSet); + + /** + * @brief Checks if the requested validation layers are supported by the Vulkan implementation. + * + * Validation layers are used for debugging and development purposes to catch errors and provide additional information during Vulkan API usage. + * + * @return true if all requested validation layers are supported, false otherwise. + * + * @since Karma 1.0.0 + */ + bool CheckValidationLayerSupport(); + + /** + * @brief Populates the debug messenger create info structure for setting up validation layers. + * + * Basically for choosing which type of debug messages we want to be displayed + * + * @param createInfo Reference to the VkDebugUtilsMessengerCreateInfoEXT structure to be populated. + * + * @note This function sets DebugCallback as the callback function for handling (printing, for instance) debug messages. + * @see FVulkanDynamicRHI::SetupDebugMessenger(), FVulkanDynamicRHI::CreateInstance() + * + * @since Karma 1.0.0 + */ + void PopulateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo); + + /** + * @brief Callback function for Vulkan debug messages. + * + * Here we define the actions to be taken, by the Engine, when a debug message is received from the Vulkan API. + * + * @param messageSeverity Severity of the debug message. + * @param messageType Type of the debug message. + * @param pCallbackData Pointer to the callback data containing the debug message. + * @param pUserData User-defined data passed to the callback. + * + * @since Karma 1.0.0 + */ + static VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData); + + public: + // To be set manually by coder + static std::vector deviceExtensions; + static const std::vector validationLayers; + + protected: + uint32_t m_APIVersion; + VkInstance m_VulkanInstance; + FVulkanDevice* m_Device; + static bool bEnableValidationLayers; + VkDebugUtilsMessengerEXT m_DebugMessenger; + GLFWwindow* m_WindowHandle; + VkSurfaceKHR m_Surface; + VkPhysicalDeviceFeatures m_SupportedDeviceFeatures; + VkPhysicalDevice m_PhysicalDevice = VK_NULL_HANDLE;// GPU + SwapChainSupportDetails m_GPUSwapChainSupport; + uint32_t m_SwapChainImageCount; + + std::set m_VulkanUBO; + }; +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanFramebuffer.cpp b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanFramebuffer.cpp new file mode 100644 index 00000000..7f2282c7 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanFramebuffer.cpp @@ -0,0 +1,42 @@ +#include "VulkanFramebuffer.h" + +#include "VulkanDevice.h" +#include "VulkanRenderPass.h" + +namespace Karma +{ + FVulkanFramebuffer::FVulkanFramebuffer(FVulkanDevice& Device, const FVulkanRenderTargetsInfo& InRTInfo, const FVulkanRenderTargetLayout& RTLayout, const FVulkanRenderPass& RenderPass, uint32_t SwapchainImageIndex) : m_Device(Device) + { + for (const auto& renderTarget : InRTInfo.m_ColorRenderTargets[SwapchainImageIndex].m_ColorRTViews) + { + if(renderTarget != 0) + { + m_ImageViews.Add(renderTarget); + } + } + + if (InRTInfo.bDepthRenderTarget) + { + m_ImageViews.Add(InRTInfo.m_DepthRenderTarget.m_DepthRTView); + } + + VkFramebufferCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + createInfo.renderPass = RenderPass.GetHandle(); + createInfo.attachmentCount = m_ImageViews.Num(); + createInfo.pAttachments = m_ImageViews.GetData(); + createInfo.width = RTLayout.GetRenderArea().extent.width; + createInfo.height = RTLayout.GetRenderArea().extent.height; + createInfo.layers = 1; + + VkResult result = vkCreateFramebuffer(Device.GetLogicalDevice(), &createInfo, nullptr, &m_Framebuffer); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create frame buffer"); + + m_RenderArea = RTLayout.GetRenderArea(); + } + + FVulkanFramebuffer::~FVulkanFramebuffer() + { + vkDestroyFramebuffer(m_Device.GetLogicalDevice(), m_Framebuffer, nullptr); + } +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanFramebuffer.h b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanFramebuffer.h new file mode 100644 index 00000000..4aa79e25 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanFramebuffer.h @@ -0,0 +1,88 @@ +/** + * @file VulkanFramebuffer.h + * @brief Contains the Vulkan framebuffer class to hold pixel color/depth data + * @author Ravi Mohan (the_cowboy) + * @version 1.0 + * @date December 30, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include +#include "VulkanRenderPass.h" + +namespace Karma +{ + // Forward declaration + class FVulkanDevice; + class FVulkanRenderTargetLayout; + + class FVulkanRenderPass; + + /** + * @brief May move to more abstract since any graphic api should use such render targets' information + * + * @note UE uses FRHISetRenderTargetsInfo + */ + class FVulkanRenderTargetsInfo + { + public: + struct ColorRenderTarget + { + VkImage m_ColorRTImages[MaxSimultaneousRenderTargets]; + + VkDeviceMemory m_ColorRTDeviceMemory[MaxSimultaneousRenderTargets]; + VkImageView m_ColorRTViews[MaxSimultaneousRenderTargets]; + + bool bSwapChainColorRenderTarget = false; + }; + + struct DepthRenderTarget + { + VkImage m_DepthRTImage; + + VkDeviceMemory m_DepthRTDeviceMemory; + VkImageView m_DepthRTView; + }; + + /** + * @brief Array of of color rendertargets for each swapchain image. + * + * @note number of swapchain images (FVulkanDynamicRHI::m_SwapChainImageCount) is size of this vector + */ + std::vector m_ColorRenderTargets; + uint32_t m_NumColorRenderTargets; + + // Depth render target information + bool bDepthRenderTarget = false; + DepthRenderTarget m_DepthRenderTarget; + }; + + /** + * @brief Actual Vulkan framebuffer class + * + * Framebuffers consists of rendertargets which are filled with per pixel information (color/depth). + * Specifically framebuffer consists of VkImages with appropriate VkImageViews which receive fragment shader output + * data. In terms of graphics pipeline stages, this output is stored in framebuffer imageviews after fragment shader + * stage and before color blending stage. + */ + class FVulkanFramebuffer + { + public: + FVulkanFramebuffer(FVulkanDevice& Device, const FVulkanRenderTargetsInfo& InRTInfo, const FVulkanRenderTargetLayout& RTLayout, const FVulkanRenderPass& RenderPass, uint32_t SwapchainImageIndex); + + VkFramebuffer GetHandle() const { return m_Framebuffer; } + + ~FVulkanFramebuffer(); + + private: + class FVulkanDevice& m_Device; + + VkFramebuffer m_Framebuffer; + VkRect2D m_RenderArea; + + KarmaVector m_ImageViews; + }; +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanPipeline.cpp b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanPipeline.cpp new file mode 100644 index 00000000..966ac0a9 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanPipeline.cpp @@ -0,0 +1,32 @@ +#include "VulkanPipeline.h" +#include "VulkanDevice.h" + +namespace Karma +{ + FVulkanPipelineStateCacheManager::FVulkanPipelineStateCacheManager(FVulkanDevice* InDevice) : m_Device(InDevice) + { + } + + FVulkanPipelineStateCacheManager::~FVulkanPipelineStateCacheManager() + { + } + + bool FVulkanPipelineStateCacheManager::CreateGfxPipeline() + { + return false; + } + + FVulkanPipeline::FVulkanPipeline(FVulkanDevice* InDevice) : + m_Device(InDevice), m_Pipeline(VK_NULL_HANDLE) + { + } + + FVulkanPipeline::~FVulkanPipeline() + { + if (m_Pipeline != VK_NULL_HANDLE) + { + vkDestroyPipeline(m_Device->GetLogicalDevice(), m_Pipeline, nullptr); + m_Pipeline = VK_NULL_HANDLE; + } + } +} \ No newline at end of file diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanPipeline.h b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanPipeline.h new file mode 100644 index 00000000..0d1b8e76 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanPipeline.h @@ -0,0 +1,46 @@ +/** + * @file VulkanPipeline.h + * @author Ravi Mohan (the_cowboy) + * @brief This file contains FVulkanPipeline class and relevant data structures. + * @version 1.0 + * @date February 6, 2026 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include + +namespace Karma +{ + class FVulkanDevice; + + class FVulkanPipelineStateCacheManager + { + public: + FVulkanPipelineStateCacheManager(FVulkanDevice* InDevice); + ~FVulkanPipelineStateCacheManager(); + + private: + bool CreateGfxPipeline(); + + private: + FVulkanDevice* m_Device; + + }; + + class FVulkanPipeline + { + public: + FVulkanPipeline(FVulkanDevice* InDevice); + + /*virtual*/ ~FVulkanPipeline(); + + protected: + FVulkanDevice* m_Device; + VkPipeline m_Pipeline; + + //FVulkanLayout* m_Layout; + }; +} \ No newline at end of file diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRHI.cpp b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRHI.cpp new file mode 100644 index 00000000..70350abc --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRHI.cpp @@ -0,0 +1,570 @@ +#include "VulkanDynamicRHI.h" +#include "GLFW/glfw3.h" +#include "Application.h" +#include "VulkanBuffer.h" + +namespace Karma +{ + const std::vector FVulkanDynamicRHI::validationLayers = {"VK_LAYER_KHRONOS_validation"}; + std::vector FVulkanDynamicRHI:: deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; + +#ifdef KR_DEBUG + bool FVulkanDynamicRHI::bEnableValidationLayers = true; +#else + bool FVulkanDynamicRHI::bEnableValidationLayers = false; +#endif + + FVulkanDynamicRHI::FVulkanDynamicRHI() + { + KR_CORE_INFO("Initializing Vulkan RHI.."); + + CreateInstance(); + SetupDebugMessenger(); + CreateSurface(); + SelectDevice();// physical device selection and logical device creation + } + + bool FVulkanDynamicRHI::Init() + { + // initialize device + InitInstance(); + + KR_CORE_INFO("Vulkan RHI initialized successfully."); + + return true; + } + + void FVulkanDynamicRHI::InitInstance() + { + m_Device->InitGPU(); + } + + void FVulkanDynamicRHI::Shutdown() + { + // How to destroy swapchain + /*FVulkanSwapChainRecreateInfo RI{}; + experimentalSwapChain->Destroy(&RI); + delete experimentalSwapChain; + experimentalSwapChain = nullptr;*/ + + m_Device->Destroy(); + vkDestroySurfaceKHR(m_VulkanInstance, m_Surface, nullptr); + DestroyDebugUtilsMessengerEXT(m_VulkanInstance, m_DebugMessenger, nullptr); + vkDestroyInstance(m_VulkanInstance, nullptr); + + delete m_Device; + m_Device = nullptr; + + KR_CORE_INFO("Vulkan RHI shutdown complete"); + } + + void FVulkanDynamicRHI::CreateInstance() + { + // Implementation for creating a Vulkan instance + if (bEnableValidationLayers) + { + PrintAvailableExtensions(); + } + + if (bEnableValidationLayers && !CheckValidationLayerSupport()) + { + KR_CORE_WARN("Validation layers requested, but not available"); + } + + // Optional information about the application (or Engine in our case) + // TODO: Fill this info from Engine config (if/when we have the config) + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; +#ifdef KR_APPLICATION_NAME + appInfo.pApplicationName = KR_APPLICATION_NAME; +#else + appInfo.pApplicationName = "No Name"; +#endif + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "Karma"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_2; + + // Tell Vulkan which global extensions and validation layers we want to use + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + // Validation layers + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo; + if (bEnableValidationLayers) + { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + PopulateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)(&debugCreateInfo); + } + else + { + createInfo.enabledLayerCount = 0; + createInfo.pNext = nullptr; + } + + VkInstanceCreateFlags flagsToBeSet{}; + auto extensions = GetRequiredExtensions(flagsToBeSet); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + createInfo.flags |= flagsToBeSet; + + VkResult result = vkCreateInstance(&createInfo, nullptr, &m_VulkanInstance); + + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create Vulkan m_Instance."); + + KR_CORE_INFO("Vulkan Instance created successfully."); + } + + void FVulkanDynamicRHI::SetupDebugMessenger() + { + if (!bEnableValidationLayers) + { + return; + } + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + PopulateDebugMessengerCreateInfo(createInfo); + + VkResult result = CreateDebugUtilsMessengerEXT(m_VulkanInstance, &createInfo, nullptr, &m_DebugMessenger); + + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to set up debug messenger!"); + KR_CORE_INFO("Vulkan RHI finished setting up Vulkan debug messenger with appropriate logging callback"); + } + + void FVulkanDynamicRHI::CreateSurface() + { + m_WindowHandle = static_cast(Application::Get().GetWindow().GetNativeWindow()); + VkResult result; + + if (m_WindowHandle != nullptr) + { + result = glfwCreateWindowSurface(m_VulkanInstance, m_WindowHandle, nullptr, &m_Surface); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create window surface"); + + KR_CORE_INFO("Vulkan RHI successfully created abstract surface to present rendered images"); + } + else + { + KR_CORE_ASSERT(false, "No GLFW window handle found for surface creation"); + } + } + + void FVulkanDynamicRHI::ComputeNumberOfSwapchainImagesSupported() + { + m_SwapChainImageCount = m_GPUSwapChainSupport.capabilities.minImageCount + 1; + + if (m_GPUSwapChainSupport.capabilities.maxImageCount > 0 && m_SwapChainImageCount > m_GPUSwapChainSupport.capabilities.maxImageCount) + { + m_SwapChainImageCount = m_GPUSwapChainSupport.capabilities.maxImageCount; + } + } + + uint32_t FVulkanDynamicRHI::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) + { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(m_PhysicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + KR_CORE_ASSERT(false, "Failed to find suitable memory type for imagebuffer"); + return 0; + } + + VkFormat FVulkanDynamicRHI::FindSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) const + { + for (VkFormat format : candidates) + { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(m_PhysicalDevice, format, &props); + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) + { + return format; + } + else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + KR_CORE_ASSERT(false, "Failed to find supported format!"); + return VkFormat{}; + } + + VkFormat FVulkanDynamicRHI::FindDepthFormat() const + { + return FindSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + void FVulkanDynamicRHI::SelectDevice()// Pick physical device (GPU) and create Vulkan logical device + { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(m_VulkanInstance, &deviceCount, nullptr); + + if (deviceCount == 0) + { + KR_CORE_ASSERT(false, "Failed to load GPU with Vulkan support"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(m_VulkanInstance, &deviceCount, devices.data()); + + if (bEnableValidationLayers) + { + PrintAvailablePhysicalDevices(devices); + } + + for (const auto& device : devices) + { + if (IsDeviceSuitable(device)) + { + m_PhysicalDevice = device; + ComputeNumberOfSwapchainImagesSupported(); + + break; + } + } + + if (m_PhysicalDevice == VK_NULL_HANDLE) + { + KR_CORE_ASSERT(false, "Failed to find a suitable GPU!"); + } + else + { + VkPhysicalDeviceProperties deviceProperties; + vkGetPhysicalDeviceProperties(m_PhysicalDevice, &deviceProperties); + + KR_CORE_INFO("Physical Device (GPU) {0} has appropriate support for Karma Engine", deviceProperties.deviceName); + } + + m_Device = new FVulkanDevice(this, m_PhysicalDevice); + } + + bool FVulkanDynamicRHI::IsDeviceSuitable(VkPhysicalDevice device) + { + QueueFamilyIndices indices = FindQueueFamilies(device); + + bool bExtensionsSupported = CheckDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (bExtensionsSupported) + { + m_GPUSwapChainSupport = QuerySwapChainSupport(device); + swapChainAdequate = !m_GPUSwapChainSupport.formats.empty() && !m_GPUSwapChainSupport.presentModes.empty(); + } + + vkGetPhysicalDeviceFeatures(device, &m_SupportedDeviceFeatures); + + return indices.IsComplete() && bExtensionsSupported && swapChainAdequate && m_SupportedDeviceFeatures.samplerAnisotropy; + } + + SwapChainSupportDetails FVulkanDynamicRHI::QuerySwapChainSupport(VkPhysicalDevice device) + { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, m_Surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, m_Surface, &formatCount, nullptr); + + if (formatCount != 0) + { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, m_Surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, m_Surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) + { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, m_Surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + // Check if all the required extensions are there + bool FVulkanDynamicRHI::CheckDeviceExtensionSupport(VkPhysicalDevice device) + { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + +#ifdef KR_MAC_PLATFORM + // Case by case query for required extensions + // One for MacOS: VK_KHR_portability_subset + for (auto anExtention : availableExtensions) + { + if (strcmp(anExtention.extensionName, "VK_KHR_portability_subset") != 0) + { + deviceExtensions.push_back("VK_KHR_portability_subset"); + break; + } + } +#endif + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + if (bEnableValidationLayers) + { + KR_CORE_INFO("+-------------------------------------------------"); + KR_CORE_INFO("| Available Device (GPU) Extensions:"); + uint32_t index = 1; + for (auto anExtension : availableExtensions) + { + KR_CORE_INFO("| {0}. {1}", index++, anExtension.extensionName); + } + KR_CORE_INFO("+-------------------------------------------------"); + KR_CORE_INFO("+-------------------------------------------------"); + KR_CORE_INFO("| Required Extensions (shall be enabled...):"); + index = 1; + for (auto swapchainExtension : requiredExtensions) + { + KR_CORE_INFO("| {0}. {1}", index++, swapchainExtension); + } + KR_CORE_INFO("+-------------------------------------------------"); + } + + for (const auto& extension : availableExtensions) + { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices FVulkanDynamicRHI::FindQueueFamilies(VkPhysicalDevice device) + { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) + { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) + { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, m_Surface, &presentSupport); + + if (presentSupport) + { + indices.presentFamily = i; + } + + if (indices.IsComplete()) + { + break; + } + + i++; + } + + return indices; + } + + void FVulkanDynamicRHI::PrintAvailablePhysicalDevices(const std::vector& physicalDevices) + { + uint32_t index = 1; + KR_CORE_INFO("+-------------------------------------------------"); + KR_CORE_INFO("| Available Graphics cards:"); + for (auto physicalDevice : physicalDevices) + { + VkPhysicalDeviceProperties deviceProperties; + vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); + KR_CORE_INFO("| {0}. Device Name: {1}", index++, deviceProperties.deviceName); + } + KR_CORE_INFO("+-------------------------------------------------"); + } + + void FVulkanDynamicRHI::PopulateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) + { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = DebugCallback; + } + + VKAPI_ATTR VkBool32 VKAPI_CALL FVulkanDynamicRHI::DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) + { + switch (messageSeverity) + { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + KR_CORE_INFO("Validation Layer: {0}", pCallbackData->pMessage); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + KR_CORE_WARN("Validation Layer: {0}", pCallbackData->pMessage); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + KR_CORE_ERROR("Validation Layer: {0}", pCallbackData->pMessage); + break; + default: + //KR_CORE_TRACE("Validation Layer: {0}", pCallbackData->pMessage); + break; + } + + return VK_FALSE; + } + + // Return the required list of instance extensions based on whether validation layers are + // enabled or not + std::vector FVulkanDynamicRHI::GetRequiredExtensions(VkInstanceCreateFlags& flagsToBeSet) + { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + +#ifdef KR_MAC_PLATFORM + // Case by case query for required instance extensions + // One for MacOS: VK_KHR_portability_enumeration + uint32_t extensionCount = 0; + + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + std::vector vulkanExtensions(extensionCount); + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, vulkanExtensions.data()); + + uint32_t index = 1; + for (auto anExtension : vulkanExtensions) + { + if (strcmp(anExtension.extensionName, "VK_KHR_portability_enumeration")) + { + extensions.push_back("VK_KHR_portability_enumeration"); + flagsToBeSet = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + break; + } + } +#endif + if (bEnableValidationLayers)// Enable printing of extensions when validation layers are enabled (debug mode) + { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + uint32_t index = 1; + KR_CORE_INFO("+-------------------------------------------------"); + KR_CORE_INFO("| GLFW and other required instance extensions:"); + for (auto extension : extensions) + { + KR_CORE_INFO("| {0}. {1}", index++, extension); + } + KR_CORE_INFO("+-------------------------------------------------"); + } + + return extensions; + } + + void FVulkanDynamicRHI::PrintAvailableExtensions() + { + uint32_t extensionCount = 0; + + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + std::vector vulkanExtensions(extensionCount); + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, vulkanExtensions.data()); + + uint32_t index = 1; + KR_CORE_INFO("+-------------------------------------------------"); + KR_CORE_INFO("| Available Vulkan instance extensions:"); + for (auto extension : vulkanExtensions) + { + KR_CORE_INFO("| {0}. {1}", index++, extension.extensionName); + } + KR_CORE_INFO("+-------------------------------------------------"); + } + + bool FVulkanDynamicRHI::CheckValidationLayerSupport() + { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) + { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) + { + if (strcmp(layerName, layerProperties.layerName) == 0) + { + layerFound = true; + break; + } + } + + if (!layerFound) + { + return false; + } + } + + return true; + } + + VkResult FVulkanDynamicRHI::CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) + { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func) + { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else + { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + } + + void FVulkanDynamicRHI::DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) + { + if (!bEnableValidationLayers) + { + return; + } + + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) + { + func(instance, debugMessenger, pAllocator); + } + } + + void FVulkanDynamicRHI::RegisterUniformBufferObject(VulkanUniformBuffer* ubo) + { + m_VulkanUBO.insert(ubo); + } + + void FVulkanDynamicRHI::UploadUniformBufferObjects(size_t frameIndex) + { + for (auto ubo : m_VulkanUBO) + { + ubo->UploadUniformBuffer(frameIndex); + } + } +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRHI.h b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRHI.h new file mode 100644 index 00000000..8dd43da6 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRHI.h @@ -0,0 +1,22 @@ +/** + * @file VulkanRHI.h + * @brief + * @author Ravi Mohan (the_cowboy) + * @version 1.0 + * @date December 15, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include "DynamicRHI.h" + +namespace Karma +{ + struct IVulkanDynamicRHI : public FDynamicRHI + { + virtual ERHIInterfaceType GetInterfaceType() const override { return ERHIInterfaceType::Vulkan; } + }; + +} \ No newline at end of file diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRHIPrivate.cpp b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRHIPrivate.cpp new file mode 100644 index 00000000..e69de29b diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRHIPrivate.h b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRHIPrivate.h new file mode 100644 index 00000000..e69de29b diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRenderPass.cpp b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRenderPass.cpp new file mode 100644 index 00000000..04be5429 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRenderPass.cpp @@ -0,0 +1,68 @@ +#include "VulkanRenderPass.h" + +namespace Karma +{ + VkRenderPass CreateVulkanRenderPass(FVulkanDevice& InDevice, const FVulkanRenderTargetLayout& RTLayout) + { + VkRenderPass OutRenderpass; + + FVulkanRenderPassBuilder, FVulkanSubpassDependency, FVulkanAttachmentReference, FVulkanAttachmentDescription, FVulkanRenderPassCreateInfo> Creator(InDevice); + OutRenderpass = Creator.Create(RTLayout); + + return OutRenderpass; + } + + FVulkanRenderTargetLayout::FVulkanRenderTargetLayout(FVulkanRenderPassInfo& VRPInfo) : m_NumAttachmentDescriptions(0), m_NumColorAttachments(0) + { + // Color + depth attachment descriptions + for (const auto& AttachmentInfo : VRPInfo.m_AttachmentsInfo) + { + VkAttachmentDescription& CurrentDescription = m_AttachmentDescriptions[m_NumAttachmentDescriptions++]; + + CurrentDescription.flags = AttachmentInfo.AttachmentFlags; + CurrentDescription.format = AttachmentInfo.AttachmentFormat; + CurrentDescription.samples = AttachmentInfo.AttachmentSampleCount; + CurrentDescription.loadOp = AttachmentInfo.AttachmentLoadOperation; + CurrentDescription.storeOp = AttachmentInfo.AttachmentStoreOperation; + CurrentDescription.stencilLoadOp = AttachmentInfo.AttachmentStencilLoadOperation; + CurrentDescription.stencilStoreOp = AttachmentInfo.AttachmentStencilStoreOperation; + CurrentDescription.initialLayout = AttachmentInfo.AttachmentInitialLayout; + CurrentDescription.finalLayout = AttachmentInfo.AttachmentFinalLayout; + } + + // Color attachment reference + for (const auto& AttachmentRefInfo : VRPInfo.m_ColorAttachmentsRefInfo) + { + VkAttachmentReference& CurrentReference = m_ColorReferences[m_NumColorAttachments++]; + + CurrentReference.attachment = AttachmentRefInfo.attachment; + CurrentReference.layout = AttachmentRefInfo.layout; + } + + // Depth attachment reference + if (VRPInfo.bHasDepthAttachment) + { + bHasDepthStencil = true; + + m_DepthReference.attachment = VRPInfo.m_DepthAttachmentReference.attachment; + m_DepthReference.layout = VRPInfo.m_DepthAttachmentReference.layout; + } + else + { + bHasDepthStencil = false; + } + + m_RenderArea = VRPInfo.m_RenderArea; + } + + FVulkanRenderPass::FVulkanRenderPass(FVulkanDevice& Device, const FVulkanRenderTargetLayout& RTLayout) : m_Device(Device) + { + m_RenderPass = CreateVulkanRenderPass(m_Device, RTLayout); + m_Layout = RTLayout; + } + + FVulkanRenderPass::~FVulkanRenderPass() + { + vkDestroyRenderPass(m_Device.GetLogicalDevice(), m_RenderPass, nullptr); + } +} \ No newline at end of file diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRenderPass.h b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRenderPass.h new file mode 100644 index 00000000..76da1876 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanRenderPass.h @@ -0,0 +1,744 @@ +/** + * @file VulkanRenderPass.h + * @brief Karma Engine Vulkan RHI Render Pass definitions + * @author Ravi Mohan (the_cowboy) + * @version 1.0 + * @date December 21, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include "VulkanRHI/VulkanDevice.h" +#include "Core/KarmaTypes.h" +#include "KarmaMemory.h" +#include "Core.h" + +namespace Karma +{ + /** + * @brief Template definition of FVulkanAttachmentReference with specializations done later + * + * This is primary struct template for attachment references like color, depth, and input attachments + * + * See + * VkRenderPass CreateVulkanRenderPass() + * { + * .. + * FVulkanRenderPassBuilder<.., FVulkanAttachmentReference, ..> Creator(InDevice); + * Creator.Create(RTLayout); + * .. + * } + * + * where FVulkanRenderPassBuilder::m_ColorAttachmentReferences and FVulkanRenderPassBuilder::m_DepthStencilAttachmentReference are called + * + * m_ColorAttachmentReferences.Add(TAttachmentReferenceClass(RTLayout.GetColorAttachmentReferences()[ColorAttachment], 0)); + * m_DepthStencilAttachmentReference.SetDepthStencilAttachment + * + * triggering class template specializations out-of-class definitions of SetAttachment and SetDepthStencilAttachment + * + * @since Karma 1.0.0 + */ + template + struct FVulkanAttachmentReference : public TAttachmentReferenceType + { + FVulkanAttachmentReference() + { + ZeroStruct(); + } + + /** + * @brief Constructor to copy from VkAttachmentReference + * + * @since Karma 1.0.0 + */ + FVulkanAttachmentReference(const VkAttachmentReference& AttachmentReferenceIn, VkImageAspectFlags AspectMask) + { + SetAttachment(AttachmentReferenceIn, AspectMask); + } + + /** + * @brief Disable method enforced here (checkNoEntry()) + * + * This "poison" is to ensure correct use (or trap the misuse) at call site + * + * ❌ Input attachments: mutation blocked (checkNoEntry()) + * FVulkanAttachmentReference inputRef; + * inputRef.SetAttachment(...); // Asserts/crashes - prevents bugs + * + * ✅ Color/depth attachments: mutation allowed + * FVulkanAttachmentReference colorRef; + * colorRef.SetAttachment(ref, mask); // Copies attachment/layout safely + * + * This selectively enables mutation only for standard attachments. Or this discourages certain uses + * + * @since Karma 1.0.0 + */ + inline void SetAttachment(const VkAttachmentReference& AttachmentReferenceIn, VkImageAspectFlags AspectMask) { checkNoEntry(); } + + /** + * @brief Template definition of SetAttachment (specialized out-of-class definition exists) + * + * @since Karma 1.0.0 + */ + inline void SetAttachment(const FVulkanAttachmentReference& AttachmentReferenceIn, VkImageAspectFlags AspectMask) { *this = AttachmentReferenceIn; } + + /** + * @brief Disable method enforced here (checkNoEntry()). + * + * This is to prevent misuse for non-depth-stencil attachments + * + * @note Specialized out-of-class definition exists for VkAttachmentReference + * + * @since Karma 1.0.0 + */ + inline void SetDepthStencilAttachment(const VkAttachmentReference& AttachmentReferenceIn, const VkAttachmentReferenceStencilLayout* StencilReference, VkImageAspectFlags AspectMask, bool bSupportsParallelRendering) { checkNoEntry(); } + + /** + * @brief Template definition for zero initialization. Specialized out-of-class definition exists + * + * @since Karma 1.0.0 + */ + inline void ZeroStruct() {} + //inline void SetAspect(uint32_t Aspect) {} + }; + + /** + * @brief This is template specialization out-of-class definition triggered by + * m_ColorAttachmentReferences.Add(TAttachmentReferenceClass(RTLayout.GetColorAttachmentReferences()[ColorAttachment], 0)); + * + * @since Karma 1.0.0 + */ + template <> + inline void FVulkanAttachmentReference::SetAttachment(const VkAttachmentReference& AttachmentReferenceIn, VkImageAspectFlags AspectMask) + { + attachment = AttachmentReferenceIn.attachment; + layout = AttachmentReferenceIn.layout; + } + + /** + * @brief This is template specialization out-of-class definition triggered by + * m_DepthStencilAttachmentReference.SetDepthStencilAttachment + * + * @since Karma 1.0.0 + */ + template <> + inline void FVulkanAttachmentReference::SetDepthStencilAttachment(const VkAttachmentReference& AttachmentReferenceIn, + const VkAttachmentReferenceStencilLayout* StencilReference, VkImageAspectFlags AspectMask, bool bSupportsParallelRendering) + { + attachment = AttachmentReferenceIn.attachment; + //const VkImageLayout StencilLayout = StencilReference ? StencilReference->stencilLayout : VK_IMAGE_LAYOUT_UNDEFINED; + + layout = AttachmentReferenceIn.layout; //GetMergedDepthStencilLayout(AttachmentReferenceIn.layout, StencilLayout); + } + + /** + * @brief Template specialization out-of-class definition for zero initialization + * + * This function is triggered by FVulkanAttachmentReference constructor + * + * @since Karma 1.0.0 + */ + template<> + inline void FVulkanAttachmentReference::ZeroStruct() + { + attachment = 0; + layout = VK_IMAGE_LAYOUT_UNDEFINED; + } + + /** + * @brief Template (for VkAttachmentDescription(2)) definition of FVulkanAttachmentDescription + * + * This is primary struct template for attachment descriptions like color, depth, and input attachments. These + * attachments (FVulkanRenderPassBuilder::m_AttachmentDescriptions) are used in FVulkanRenderPassBuilder, to create + * VkRenderPassCreateInfo, structure like so + * + * FVulkanRenderPassBuilder<.., FVulkanAttachmentDescription, ..> Creator(InDevice); + * + * + * @since Karma 1.0.0 + */ + template + struct FVulkanAttachmentDescription + { + }; + + /** + * @brief Specializing FVulkanAttachmentDescription for VkAttachmentDescription + * + * @since Karma 1.0.0 + */ + template<> + struct FVulkanAttachmentDescription + : public VkAttachmentDescription + { + /** + * @brief Default constructor to zero initialize the structure + * + * @since Karma 1.0.0 + */ + FVulkanAttachmentDescription() + { + FMemory::Memzero(this, sizeof(VkAttachmentDescription)); + } + + /** + * @brief Constructor to copy from VkAttachmentDescription + * + * Triggered by m_AttachmentDescriptions.Add(TAttachmentDescriptionClass(RTLayout.GetAttachmentDescriptions()[Attachment])); + * + * @since Karma 1.0.0 + */ + FVulkanAttachmentDescription(const VkAttachmentDescription& InDesc) + { + flags = InDesc.flags; + format = InDesc.format; + samples = InDesc.samples; + loadOp = InDesc.loadOp; + storeOp = InDesc.storeOp; + stencilLoadOp = InDesc.stencilLoadOp; + stencilStoreOp = InDesc.stencilStoreOp; + initialLayout = InDesc.initialLayout; + finalLayout = InDesc.finalLayout; + } + + /** + * @brief Seems like we are not supporting VkAttachmentDescriptionStencilLayout + * + * For understanding purposes only atm + * + * @since Karma 1.0.0 + */ + FVulkanAttachmentDescription(const VkAttachmentDescription& InDesc, const VkAttachmentDescriptionStencilLayout* InStencilDesc, bool bSupportsParallelRendering) + { + //flags = InDesc.flags; + format = InDesc.format; + samples = InDesc.samples; + loadOp = InDesc.loadOp; + storeOp = InDesc.storeOp; + stencilLoadOp = InDesc.stencilLoadOp; + stencilStoreOp = InDesc.stencilStoreOp; + + /*const bool bHasStencilLayout = VulkanFormatHasStencil(InDesc.format) && (InStencilDesc != nullptr); + const VkImageLayout StencilInitialLayout = bHasStencilLayout ? InStencilDesc->stencilInitialLayout : VK_IMAGE_LAYOUT_UNDEFINED; + initialLayout = GetMergedDepthStencilLayout(InDesc.initialLayout, StencilInitialLayout); + const VkImageLayout StencilFinalLayout = bHasStencilLayout ? InStencilDesc->stencilFinalLayout : VK_IMAGE_LAYOUT_UNDEFINED; + finalLayout = GetMergedDepthStencilLayout(InDesc.finalLayout, StencilFinalLayout); + */ + } + }; + + /** + * @brief Template (for VkSubpassDescription(2)) definition of FVulkanSubpassDescription + * + * This is primary class template for subpasses. + * + * Subpasses in Vulkan are passes in RenderPass that define how attachments are used. There is one main subpass + * which is used for standard rendering, which we are going to implement in FVulkanRenderPassBuilder::BuildCreateInfo() + * + * Unreal Engine has multiple subpasses for various effects. For instance color write / depth read subpass for deferred shading + * optimization etc. We will keep this simple. + * + * @since Karma 1.0.0 + */ + template + class FVulkanSubpassDescription + { + }; + + /** + * @brief Specialized definition of FVulkanSubpassDescription for VkSubpassDescription + */ + template<> + struct FVulkanSubpassDescription + : public VkSubpassDescription + { + /** + * @brief Default constructor to zero initialize the structure + * + * Triggered by the FVulkanRenderPassBuilder constructor for variable m_SubpassDescriptions + * + * @since Karma 1.0.0 + */ + FVulkanSubpassDescription() + { + FMemory::Memzero(this, sizeof(VkSubpassDescription)); + pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + } + + /** + * @brief Set color attachments for the subpass + * + * @param ColorAttachmentReferences Array of color attachment references for the subpass + * @param OverrideCount Optional override for number of color attachments + * + * @since Karma 1.0.0 + */ + void SetColorAttachments(const KarmaVector>& ColorAttachmentReferences, int OverrideCount = -1) + { + colorAttachmentCount = (OverrideCount == -1) ? ColorAttachmentReferences.Num() : OverrideCount; + pColorAttachments = ColorAttachmentReferences.GetData(); + } + + /*void SetResolveAttachments(const KarmaVector>& ResolveAttachmentReferences) + { + if (ResolveAttachmentReferences.Num() > 0) + { + check(colorAttachmentCount == ResolveAttachmentReferences.Num()); + pResolveAttachments = ResolveAttachmentReferences.GetData(); + } + }*/ + + /** + * @brief Set depth-stencil attachment for the subpass + * + * @param DepthStencilAttachmentReference Depth-stencil attachment reference for the subpass + * + * @since Karma 1.0.0 + */ + void SetDepthStencilAttachment(FVulkanAttachmentReference* DepthStencilAttachmentReference) + { + pDepthStencilAttachment = static_cast(DepthStencilAttachmentReference); + } + + /*void SetInputAttachments(FVulkanAttachmentReference* InputAttachmentReferences, uint32 NumInputAttachmentReferences) + { + pInputAttachments = static_cast(InputAttachmentReferences); + inputAttachmentCount = NumInputAttachmentReferences; + } + + void SetShadingRateAttachment(void* /* ShadingRateAttachmentInfo) + { + // No-op without VK_KHR_create_renderpass2 + } + + void SetMultiViewMask(uint32_t Mask) + { + // No-op without VK_KHR_create_renderpass2 + }*/ + }; + + /** + * @brief Template definition of FVulkanSubpassDependency + * + * This is primary struct template for subpass dependency. + * + * SubpassDependencies define memory and execution dependencies between subpasses in a renderpass. These are + * configured according to the subpasses defined in the renderpass. Since we are using only one subpass, we will + * use single dependency in FVulkanRenderPassBuilder::BuildCreateInfo() + * + * @since Karma 1.0.0 + */ + template + struct FVulkanSubpassDependency + : public TSubpassDependencyType + { + }; + + /** + * @brief Specialized definition of FVulkanSubpassDependency for VkSubpassDependency(2) + * + * @since Karma 1.0.0 + */ + template<> + struct FVulkanSubpassDependency + : public VkSubpassDependency + { + /** + * @brief Default constructor to zero initialize the structure + * + * Triggered by FVulkanRenderPassBuilder constructor for variable m_SubpassDependencies + * + * @since Karma 1.0.0 + */ + FVulkanSubpassDependency() + { + FMemory::Memzero(this, sizeof(VkSubpassDependency)); + } + }; + + /** + * @brief Template definition of FVulkanRenderPassCreateInfo for VkRenderPassCreateInfo(2) + * + * This is primary struct template for renderpass create info. + * + * @since Karma 1.0.0 + */ + template + struct FVulkanRenderPassCreateInfo + { + }; + + /** + * @brief Specialized definition of FVulkanRenderPassCreateInfo + */ + template<> + struct FVulkanRenderPassCreateInfo + : public VkRenderPassCreateInfo + { + /** + * @brief Constructor to initialize the renderpass create info structure + * + * The constructor basically sets sType and zero initializes the rest of the structure + * + * @since Karma 1.0.0 + */ + FVulkanRenderPassCreateInfo() + { + //ZeroVulkanStruct(*this, VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO); + this->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + FMemory::Memzero(((uint8_t*)&(*this)) + sizeof(VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO), sizeof(VkRenderPassCreateInfo) - sizeof(VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO)); + } + + /** + * @brief Actual renderpass creation function + * + * @since Karma 1.0.0 + */ + VkRenderPass Create(FVulkanDevice& Device) + { + VkRenderPass Handle = VK_NULL_HANDLE; + //VERIFYVULKANRESULT_EXPANDED(VulkanRHI::vkCreateRenderPass(Device.GetInstanceHandle(), this, VULKAN_CPU_ALLOCATOR, &Handle)); + VkResult result = vkCreateRenderPass(Device.GetLogicalDevice(), this, nullptr, &Handle); + + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create render pass"); + + KR_CORE_INFO("Renderpass created successfully"); + + return Handle; + } + }; + + /** + * @brief The number of render-targets that may be simultaneously written to. + * + * @note May move to different header file with appropriate definitions + */ + enum + { + MaxSimultaneousRenderTargets = 8, + MaxSimultaneousRenderTargets_NumBits = 3, + }; + static_assert(MaxSimultaneousRenderTargets <= (1 << MaxSimultaneousRenderTargets_NumBits), "MaxSimultaneousRenderTargets will not fit on MaxSimultaneousRenderTargets_NumBits"); + + /** + * @brief Data structure to hold information about Vulkan renderpass attachments (like color and depth attachments). + * + * This is used to create FVulkanRenderTargetLayout which in turn is used to create Vulkan renderpasses. + * + * @since Karma 1.0.0 + */ + struct FVulkanRenderPassInfo + { + /** + * @brief Information about a single attachment in the renderpass + */ + struct FAttachmentInfo + { + /** + * @brief Attachment description flags + */ + VkAttachmentDescriptionFlags AttachmentFlags; + + /** + * @brief Format of the attachment (color format VK_FORMAT_R8G8B8A8_UNORM for instance) + */ + VkFormat AttachmentFormat; + + /** + * @brief Attachment sample count + */ + VkSampleCountFlagBits AttachmentSampleCount; + + /** + * @brief what to do with attachment data before rendering + */ + VkAttachmentLoadOp AttachmentLoadOperation; + + /** + * @brief what to do with attachment data after rendering + */ + VkAttachmentStoreOp AttachmentStoreOperation; + + /** + * @brief what to do with stencil data before rendering + */ + VkAttachmentLoadOp AttachmentStencilLoadOperation; + + /** + * @brief what to do with stencil data after rendering + */ + VkAttachmentStoreOp AttachmentStencilStoreOperation; + + /** + * @brief Attachment layout before renderpass begins + * + * Image State Timeline: + * ┌─────────────────────-┐ + * │ PRE-RENDER PASS │ ← initialLayout (what you provide) + * ├─────────────────────-┤ + * │ vkCmdBeginRenderPass │ + * │ ↓ Implicit transition│ + * │ FIRST subpass layout │ (e.g. COLOR_ATTACHMENT_OPTIMAL) + * ├─ subpass transitions─┤ + * │ LAST subpass layout │ + * │ ↑ Implicit transition│ + * │ POST-RENDER PASS │ → finalLayout (what you get) + * └─────────────────────-┘ + * │ vkCmdEndRenderPass │ + * + * @since Karma 1.0.0 + */ + VkImageLayout AttachmentInitialLayout; + + /** + * @brief Attachment layout at the end of renderpass + */ + VkImageLayout AttachmentFinalLayout; + }; + + /** + * @brief Reference to an attachment in the renderpass + */ + struct FAttachmentRefInfo + { + /** + * @brief The parameter specifies which attachment to reference by its index in the attachment descriptions array. + */ + uint32_t attachment; + + /** + * @brief The variable specifies which layout we would like the attachment to have during a subpass that uses this + * reference. Vulkan will automatically transition the attachment to this layout when the subpass is started. + * + * Examples: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL + * + * @note This is the layout inside a subpass while shader accesses the attachment + */ + VkImageLayout layout; + }; + + KarmaVector m_AttachmentsInfo;// color + depth + KarmaVector m_ColorAttachmentsRefInfo; + bool bHasDepthAttachment; + FAttachmentRefInfo m_DepthAttachmentReference; + VkRect2D m_RenderArea; + }; + + /** + * @brief Data structure for Vulkan's render targets (color buffers and depth buffer) + * + * @since Karma 1.0.0 + */ + class FVulkanRenderTargetLayout + { + public: + /** + * @brief Default constructor provided for FVulkanRenderPass class constructor + * + * @since Karma 1.0.0 + */ + FVulkanRenderTargetLayout() = default; + //FVulkanRenderTargetLayout(const FGraphicsPipelineStateInitializer& Initializer); + + // Experimental layout constructor (layout as in VulkanContext). We need to make this generic somehow + FVulkanRenderTargetLayout(FVulkanRenderPassInfo& VRPInfo); + + //FVulkanRenderTargetLayout(FVulkanDevice& InDevice, const FRHISetRenderTargetsInfo& RTInfo); + //FVulkanRenderTargetLayout(FVulkanDevice& InDevice, const FRHIRenderPassInfo& RPInfo, VkImageLayout CurrentDepthLayout, VkImageLayout CurrentStencilLayout); + + // Getters + inline const VkAttachmentReference* GetColorAttachmentReferences() const { return m_NumColorAttachments > 0 ? m_ColorReferences : nullptr; } + inline const VkAttachmentReference* GetDepthAttachmentReference() const { return bHasDepthStencil ? &m_DepthReference : nullptr; } + inline const VkAttachmentReferenceStencilLayout* GetStencilAttachmentReference() const { return bHasDepthStencil ? &m_StencilReference : nullptr; } + inline uint32_t GetNumColorAttachments() const { return m_NumColorAttachments; } + inline uint32_t GetNumAttachmentDescriptions() const { return m_NumAttachmentDescriptions; } + inline const VkAttachmentDescription* GetAttachmentDescriptions() const { return m_AttachmentDescriptions; } + inline const VkRect2D& GetRenderArea() const { return m_RenderArea; } + + private: + VkAttachmentReference m_ColorReferences[MaxSimultaneousRenderTargets]; + + // Depth attachment ref + VkAttachmentReference m_DepthReference; + + // Probabbly not required + VkAttachmentReferenceStencilLayout m_StencilReference; + + uint8_t m_NumColorAttachments; + uint8_t m_NumAttachmentDescriptions; + + /** + * @brief Vulkan attachment descriptions for the rendertargets + * + * @note Just MaxSimultaneousRenderTargets should be fine. Try doing this + */ + VkAttachmentDescription m_AttachmentDescriptions[MaxSimultaneousRenderTargets * 2 + 2]; + + // Do we have a depth stencil + uint8_t bHasDepthStencil; + + VkRect2D m_RenderArea; + }; + + /** + * @brief The actual Vulkan renderpass Engine class + * + * @since Karma 1.0.0 + */ + class FVulkanRenderPass + { + public: + inline VkRenderPass GetHandle() const { return m_RenderPass; } + + inline const FVulkanRenderTargetLayout& GetLayout() const { return m_Layout; } + + // May become private once we have FVulkanRenderPassManager + + //FVulkanRenderPass() = default; + + /** + * @brief Constructor to create a renderpass with supplied rendertarget layout + * + * @since Karma 1.0.0 + */ + FVulkanRenderPass(FVulkanDevice& Device, const FVulkanRenderTargetLayout& RTLayout); + + /** + * @brief Destroys renderpass + */ + ~FVulkanRenderPass(); + + private: + VkRenderPass m_RenderPass; + FVulkanRenderTargetLayout m_Layout; + + FVulkanDevice& m_Device; + }; + + /** + * @brief Builder class to create Vulkan renderpass create info structure and generate renderpass + * + * @since Karma 1.0.0 + */ + template + class FVulkanRenderPassBuilder + { + public: + /** + * @brief Constructor + * + * @since Karma 1.0.0 + */ + FVulkanRenderPassBuilder(FVulkanDevice& InDevice) + : m_Device(InDevice) + { + } + + /** + * @brief Build the VkRenderPassCreateInfo structure from the supplied FVulkanRenderTargetLayout + * + * @since Karma 1.0.0 + */ + void BuildCreateInfo(const FVulkanRenderTargetLayout& RTLayout) + { + uint32_t NumSubpasses = 0; + uint32_t NumDependencies = 0; + + // Grab (and optionally convert) attachment references. + uint32_t NumColorAttachments = RTLayout.GetNumColorAttachments(); + + // Do we have a depth attachment + const bool bHasDepthStencilAttachmentReference = (RTLayout.GetDepthAttachmentReference() != nullptr); + + // VkAttachmentDescription for color/depth attachments + for (uint32_t Attachment = 0; Attachment < RTLayout.GetNumAttachmentDescriptions(); ++Attachment) + { + /*if (bHasDepthStencilAttachmentReference && (Attachment == m_DepthStencilAttachmentReference.attachment)) + { + //m_AttachmentDescriptions.Add(TAttachmentDescriptionClass(RTLayout.GetAttachmentDescriptions()[Attachment], RTLayout.GetStencilDescription(), false/*Device.SupportsParallelRendering())); + } + else*/ + //{ + m_AttachmentDescriptions.Add(TAttachmentDescriptionClass(RTLayout.GetAttachmentDescriptions()[Attachment])); + //} + } + + // VkAttachmentReference for color attachments + for (uint32_t ColorAttachment = 0; ColorAttachment < NumColorAttachments; ++ColorAttachment) + { + m_ColorAttachmentReferences.Add(TAttachmentReferenceClass(RTLayout.GetColorAttachmentReferences()[ColorAttachment], 0)); + } + + // VkAttachmentReference for depth attachment + if (bHasDepthStencilAttachmentReference) + { + // For depth attachment reference, attachment and layout should also be given by GetDepthAttachmentReference only + m_DepthStencilAttachmentReference.SetDepthStencilAttachment(*RTLayout.GetDepthAttachmentReference(), RTLayout.GetStencilAttachmentReference(), 0, false/*m_Device.SupportsParallelRendering()*/); + + // Why are these needed when attachment and layout are assigned in m_DepthStencilAttachmentReference + //m_DepthStencilAttachment.attachment = RTLayout.GetDepthAttachmentReference()->attachment; + //m_DepthStencilAttachment.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + } + + // main subpass (using only single pass) + { + TSubpassDescriptionClass& SubpassDesc = m_SubpassDescriptions[NumSubpasses++]; + + SubpassDesc.SetColorAttachments(m_ColorAttachmentReferences, NumColorAttachments); + + if (bHasDepthStencilAttachmentReference) + { + SubpassDesc.SetDepthStencilAttachment(&m_DepthStencilAttachmentReference); + } + } + + // using single dependency (vulkancontext experimental) + TSubpassDependencyClass& SubpassDep = m_SubpassDependencies[NumDependencies++]; + SubpassDep.srcSubpass = VK_SUBPASS_EXTERNAL; + SubpassDep.dstSubpass = 0; + SubpassDep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + SubpassDep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + SubpassDep.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + //SubpassDep.dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT; + SubpassDep.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + + m_CreateInfo.attachmentCount = m_AttachmentDescriptions.Num(); + m_CreateInfo.pAttachments = m_AttachmentDescriptions.GetData(); + m_CreateInfo.subpassCount = NumSubpasses; + m_CreateInfo.pSubpasses = m_SubpassDescriptions; + m_CreateInfo.dependencyCount = NumDependencies; + m_CreateInfo.pDependencies = m_SubpassDependencies; + } + + /** + * @brief Create the Vulkan renderpass + * + * @see Template definition of FVulkanRenderPassCreateInfo + */ + VkRenderPass Create(const FVulkanRenderTargetLayout& RTLayout) + { + BuildCreateInfo(RTLayout); + + return m_CreateInfo.Create(m_Device); + } + + private: + TSubpassDescriptionClass m_SubpassDescriptions[8]; + TSubpassDependencyClass m_SubpassDependencies[8]; + + KarmaVector m_ColorAttachmentReferences; + KarmaVector m_ResolveAttachmentReferences; + + KarmaVector m_AttachmentDescriptions; + + TAttachmentReferenceClass m_DepthStencilAttachmentReference; + TAttachmentReferenceClass m_DepthStencilAttachment; + + TRenderPassCreateInfoClass m_CreateInfo; + FVulkanDevice& m_Device; + }; + + VkRenderPass CreateVulkanRenderPass(FVulkanDevice& Device, const FVulkanRenderTargetLayout& RTLayout); +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSwapChain.cpp b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSwapChain.cpp new file mode 100644 index 00000000..0b2facf9 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSwapChain.cpp @@ -0,0 +1,166 @@ +#include "VulkanSwapChain.h" +#include "VulkanRHI/VulkanDynamicRHI.h" + +#include "GLFW/glfw3.h" + +namespace Karma +{ + FVulkanSwapChain* FVulkanSwapChain::Create(FVulkanDevice* InDevice) + { + FVulkanSwapChain* swapChain = new FVulkanSwapChain(InDevice); + + return swapChain; + } + + FVulkanSwapChain::FVulkanSwapChain(FVulkanDevice* InDevice) : m_Instance(InDevice->GetVulkanDynamicRHI()->GetInstance()), + m_Device(InDevice) + { + m_WindowHandle = InDevice->GetVulkanDynamicRHI()->GetSurfaceWindow(); + + SwapChainSupportDetails swapChainSupport = InDevice->GetVulkanDynamicRHI()->QuerySwapChainSupport(InDevice->GetGPU()); + + // KarmaGui may have, MAY, different requirements. + m_SurfaceFormat = ChooseSwapSurfaceFormat(swapChainSupport.formats); + m_PresentMode = ChooseSwapPresentMode(swapChainSupport.presentModes);// Analogous to v-sync + + VkExtent2D extent = ChooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = InDevice->GetVulkanDynamicRHI()->SwapChainImageCount(); + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = InDevice->GetVulkanDynamicRHI()->GetSurface(); + createInfo.minImageCount = imageCount; + createInfo.imageFormat = m_SurfaceFormat.format; + createInfo.imageColorSpace = m_SurfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = InDevice->GetVulkanDynamicRHI()->FindQueueFamilies(InDevice->GetGPU()); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), + indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) + { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else + { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; + createInfo.pQueueFamilyIndices = nullptr; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = m_PresentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = VK_NULL_HANDLE; + + VkResult result = vkCreateSwapchainKHR(InDevice->GetLogicalDevice(), &createInfo, nullptr, &m_SwapChain); + + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create swapchain!"); + KR_CORE_INFO("Created a Vulkan swapchain"); + + vkGetSwapchainImagesKHR(InDevice->GetLogicalDevice(), m_SwapChain, &imageCount, nullptr); + m_SwapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(InDevice->GetLogicalDevice(), m_SwapChain, &imageCount, m_SwapChainImages.data()); + + m_SwapChainImageFormat = m_SurfaceFormat.format; + m_SwapChainExtent = extent; + + m_SwapChainImageViews.resize(m_SwapChainImages.size()); + + for (size_t i = 0; i < m_SwapChainImages.size(); i++) + { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = m_SwapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = m_SwapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + VkResult result = vkCreateImageView(InDevice->GetLogicalDevice(), &createInfo, nullptr, &m_SwapChainImageViews[i]); + + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create image views!"); + } + } + + void FVulkanSwapChain::Destroy(FVulkanSwapChainRecreateInfo * RecreateInfo) + { + m_Device->WaitUntilIdle(); + for (auto imageView : m_SwapChainImageViews) + { + vkDestroyImageView(m_Device->GetLogicalDevice(), imageView, nullptr); + } + + vkDestroySwapchainKHR(m_Device->GetLogicalDevice(), m_SwapChain, nullptr); + } + + VkSurfaceFormatKHR FVulkanSwapChain::ChooseSwapSurfaceFormat(const std::vector& availableFormats) + { + for (const auto& availableFormat : availableFormats) + { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB + && availableFormat.colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR) + { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR FVulkanSwapChain::ChooseSwapPresentMode(const std::vector& availablePresentModes) + { + for (const auto& availablePresentMode : availablePresentModes) + { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) + { + KR_CORE_INFO("Vulkan found surface present mode: VK_PRESENT_MODE_MAILBOX_KHR"); + KR_CORE_INFO("Using VK_PRESENT_MODE_MAILBOX_KHR for swapchain present mode"); + + return availablePresentMode; + } + } + + KR_CORE_INFO("Using the fallback VK_PRESENT_MODE_FIFO_KHR for swapchain"); + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D FVulkanSwapChain::ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) + { + if (capabilities.currentExtent.width != UINT32_MAX) + { + return capabilities.currentExtent; + } + else + { + int width, height; + glfwGetFramebufferSize(m_WindowHandle, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::max(capabilities.minImageExtent.width, + std::min(capabilities.maxImageExtent.width, actualExtent.width)); + actualExtent.height = std::max(capabilities.minImageExtent.height, + std::min(capabilities.maxImageExtent.height, actualExtent.height)); + + return actualExtent; + } + } +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSwapChain.h b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSwapChain.h new file mode 100644 index 00000000..de4cc016 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSwapChain.h @@ -0,0 +1,159 @@ +/** + * @file VulkanSwapChain.h + * @brief + * @author Ravi Mohan (the_cowboy) + * @version 1.0 + * @date 20 December, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include "VulkanRHI/VulkanDevice.h" +#include + +struct GLFWwindow; + +namespace Karma +{ + /** + * @brief Information required to recreate a Vulkan swapchain. + * + * @note To be battle tested during swapchain recreation on window resize + * @since Karma 1.0.0 + */ + struct FVulkanSwapChainRecreateInfo + { + /** + * @brief + */ + VkSwapchainKHR SwapChain; + + /** + * @brief + */ + VkSurfaceKHR Surface; + }; + + /** + * @brief Represents a Vulkan swapchain, managing the images used for rendering and presentation. + * + * A swapchain is a series of images that are presented to the screen in a specific order. It is a crucial component in Vulkan for rendering graphics to a window. + * Since there is not default framebuffer in Vulkan, the swapchain provides the images that will be used as the framebuffer for rendering. + * + * + * @since Karma 1.0.0 + */ + class FVulkanSwapChain + { + public: + /** + * @brief Creates a Vulkan swapchain based on the provided device. + * + * @param InDevice The FVulkanDevice containing the LogicalDevice and GPU + * + * @see KarmaGuiVulkanHandler::FillWindowData + * @since Karma 1.0.0 + */ + static FVulkanSwapChain* Create(FVulkanDevice* InDevice); + + /** + * @brief Destroys the swapchain appropriately + * + * @param RecreateInfo Information required to recreate the swapchain + * + * @note m_SwapChainImages are cleared automatically when vkDestroySwapchainKHR is called + * @since Karma 1.0.0 + */ + void Destroy(FVulkanSwapChainRecreateInfo* RecreateInfo); + + ///////////////// Getters ///////////////// + inline VkSwapchainKHR GetSwapChainHandle() const { return m_SwapChain; } + + inline VkExtent2D GetSwapChainExtent() const { return m_SwapChainExtent; } + inline uint32_t GetMaxFramesInFlight() const { return MAX_FRAMES_IN_FLIGHT; } + inline const std::vector& GetSwapChainImages() const { return m_SwapChainImages; } + inline const std::vector& GetSwapChainImageViews() const { return m_SwapChainImageViews; } + inline VkFormat GetSwapChainImageFormat() const { return m_SwapChainImageFormat; } + + private: + + /** + * @brief Creates Vulkan swapchain based upon the surface format and present mode + * + * @param InDevice The FVulkanDevice containing the LogicalDevice and GPU + * @since Karma 1.0.0 + */ + FVulkanSwapChain(FVulkanDevice* InDevice); + + /** + * @brief Chooses the best surface format (pixel format and color space) for the swapchain from the available formats. + * + * Basically looks for VK_FORMAT_B8G8R8A8_SRGB and VK_COLOR_SPACE_SRGB_NONLINEAR_KHR combination. + * + * VK_FORMAT_B8G8R8A8_SRGB : represents a 32-bit format with 8 bits for each of the blue, green, red, and alpha channels in sRGB color space. + * This format is widely used for swapchain images and color attachments. + * + * VK_COLOR_SPACE_SRGB_NONLINEAR_KHR : represents the sRGB color space with a nonlinear gamma curve. This color space is commonly used for displaying images on standard monitors. + * + * @note If this combination is not found, returns the first available format. + * + * @param availableFormats The available surface formats (from QuerySwapChainSupport()) + * + * @since Karma 1.0.0 + */ + static VkSurfaceFormatKHR ChooseSwapSurfaceFormat(const std::vector& availableFormats); + + /** + * @brief Chooses the best presentation mode for the swapchain from the available present modes. + * + * Basically looks for VK_PRESENT_MODE_MAILBOX_KHR (triple buffering) first, then VK_PRESENT_MODE_IMMEDIATE_KHR (tearing possible), and finally defaults to VK_PRESENT_MODE_FIFO_KHR (always available, v-sync) + * + * @param availablePresentModes The available presentation modes (from QuerySwapChainSupport()) + * + * @since Karma 1.0.0 + */ + VkPresentModeKHR ChooseSwapPresentMode(const std::vector& availablePresentModes); + + /** + * @brief Chooses the swap extent (resolution of the swapchain images) based on the capabilities of the surface and the actual window size. + * + * @param capabilities The surface capabilities (from QuerySwapChainSupport()) + * + * @since Karma 1.0.0 + */ + VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities); + + protected: + + const VkInstance m_Instance; + FVulkanDevice* m_Device; + + VkSurfaceKHR m_Surface = VK_NULL_HANDLE; + VkSwapchainKHR m_SwapChain = VK_NULL_HANDLE; + + std::vector m_SwapChainImages; + std::vector m_SwapChainImageViews; + + VkFormat m_SwapChainImageFormat; + VkExtent2D m_SwapChainExtent; + + VkSurfaceFormatKHR m_SurfaceFormat; + VkPresentModeKHR m_PresentMode; + + GLFWwindow* m_WindowHandle; + + uint32_t m_InternalWidth = 0; + uint32_t m_InternalHeight = 0; + + bool bInternalFullScreen = false; + + uint32_t m_CurrentImageIndex; + uint32_t m_SemaphoreIndex; + + // Number of images (to work upon (CPU side) whilst an image is being rendered (GPU side processing)) + 1 + // Clearly, m_SwapChainImages.size() shouldn't exceed MAX_FRAMES_IN_FLIGHT + const uint32_t MAX_FRAMES_IN_FLIGHT = 4; + }; +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSynchronization.cpp b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSynchronization.cpp new file mode 100644 index 00000000..2f42875d --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSynchronization.cpp @@ -0,0 +1,126 @@ +#include "VulkanSynchronization.h" +#include "VulkanDevice.h" + +namespace Karma +{ + FVulkanFence::FVulkanFence(FVulkanDevice& InDevice, FVulkanFenceManager& InOwner, bool bCreateSignaled) : + m_Owner(InOwner), m_State(bCreateSignaled ? EState::Signaled : EState::NotReady) + { + VkFenceCreateInfo fenceInfo = {}; + + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = bCreateSignaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0; + + VkResult result = vkCreateFence(InDevice.GetLogicalDevice(), &fenceInfo, nullptr, &m_Handle); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create fence"); + + } + + FVulkanFence::~FVulkanFence() + { + KR_CORE_ASSERT(m_Handle == VK_NULL_HANDLE, "Fence must be destroyed by owner before being deleted"); + } + + void FVulkanFenceManager::DestroyFence(FVulkanFence* Fence) + { + vkDestroyFence(m_Device.GetLogicalDevice(), Fence->m_Handle, nullptr); + + Fence->m_Handle = VK_NULL_HANDLE; + delete Fence; + } + + FVulkanFenceManager::~FVulkanFenceManager() + { + KR_CORE_ASSERT(m_UsedFences.Num() == 0, "All used fences must be released before destroying the manager"); + } + + FVulkanFence* FVulkanFenceManager::AllocateFence(bool bCreateSignaled) + { + if (m_FreeFences.Num() != 0) + { + FVulkanFence* Fence = m_FreeFences.IndexToObject(0); + + m_FreeFences.RemoveAtSwap(0/*, EAllowShrinking::No*/); + m_UsedFences.Add(Fence); + + if (bCreateSignaled) + { + Fence->m_State = FVulkanFence::EState::Signaled; + } + else + { + Fence->m_State = FVulkanFence::EState::NotReady; + } + + return Fence; + } + + FVulkanFence* NewFence = new FVulkanFence(m_Device, *this, bCreateSignaled); + m_UsedFences.Add(NewFence); + + return NewFence; + } + + void FVulkanFenceManager::Denit() + { + KR_CORE_ASSERT(m_UsedFences.Num() == 0, "Not all fences are done") + + for(FVulkanFence* fence : m_FreeFences) + { + DestroyFence(fence); + } + } + + bool FVulkanFenceManager::WaitForFence(FVulkanFence* Fence) + { + KR_CORE_ASSERT(m_UsedFences.Contains(Fence), "Not a usable fence"); + + VkResult result = vkWaitForFences(m_Device.GetLogicalDevice(), 1, &Fence->m_Handle, true, UINT64_MAX); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to wait"); + + switch (result) + { + case VK_SUCCESS: + Fence->m_State = FVulkanFence::EState::Signaled; + return true; + case VK_TIMEOUT: + break; + default: + break; + } + + return false; + } + + void FVulkanFenceManager::ResetFence(FVulkanFence* Fence) + { + if (Fence->m_State != FVulkanFence::EState::NotReady) + { + VkResult result = vkResetFences(m_Device.GetLogicalDevice(), 1, &Fence->m_Handle); + Fence->m_State = FVulkanFence::EState::NotReady; + } + } + + void FVulkanFenceManager::ReleaseFence(FVulkanFence*& Fence) + { + ResetFence(Fence); + m_UsedFences.RemoveSingleSwap(Fence/*, EAllowShrinking::No*/); + + m_FreeFences.Add(Fence);// add copy of the pointer to free list + Fence = nullptr;// nullify the caller's pointer + } + + FVulkanSemaphore::FVulkanSemaphore(FVulkanDevice& InDevice) : m_Device(InDevice) + { + VkSemaphoreCreateInfo semaphoreInfo = {}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkResult result = vkCreateSemaphore(m_Device.GetLogicalDevice(), &semaphoreInfo, VK_NULL_HANDLE, &m_Handle); + KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create Semaphore"); + } + + FVulkanSemaphore::~FVulkanSemaphore() + { + vkDestroySemaphore(m_Device.GetLogicalDevice(), m_Handle, VK_NULL_HANDLE); + } +} diff --git a/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSynchronization.h b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSynchronization.h new file mode 100644 index 00000000..f61f11a0 --- /dev/null +++ b/Karma/src/Platform/Vulkan/VulkanRHI/VulkanSynchronization.h @@ -0,0 +1,221 @@ +/** + * @file VulkanSynchronization.h + * @brief Header file for Vulkan synchronization primitives. + * + * This file declares functions and types related to synchronization in Vulkan, + * including fences, semaphores, and barriers. + * + * @author Ravi Mohan (the_cowboy) + * @version 1.0 + * + * @date 22 January, 2026 + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include +#include "KarmaTypes.h" + +namespace Karma +{ + class FVulkanFence; + class FVulkanDevice; + + class FVulkanFenceManager + { + public: + /** + * @brief Constructor + * + * @param InDevice Reference to the owning FVulkanDevice + * @since Karma 1.0.0 + */ + FVulkanFenceManager(FVulkanDevice& InDevice) + : m_Device(InDevice) + { + } + + /** + * @brief Makes sure m_UsedFences are zero i.e no fences are currently in use by + * GPU or inflight-work + * + * @since Karma 1.0.0 + */ + ~FVulkanFenceManager(); + + /** + * @brief Purpose is two-fold + * - 1. Makes sure no fences are currently in use (m_UsedFences.Num() == 0) + * - 2. Clear off m_FreeFences + * + * @since Karma 1.0.0 + */ + void Denit(); + + /** + * @brief Allocates fence for use + * + * Looks in m_FreeFences, if array is not empty, uses the first fence and fills m_UsedFence. + * If m_FreeFences is empty, creates new FVulkanFence and fills m_UsedFence. + * + * @param bCreateSignaled Should the fence be in Signaled state + * @see KarmaGuiVulkanHandler::FillWindowData + * + * @since Karma 1.0.0 + */ + FVulkanFence* AllocateFence(bool bCreateSignaled = false); + + /** + * @brief Waits for the given fence to be signaled. + * + * Makes sure the fence is in m_UsedFences before waiting. + * + * @param Fence The fence to wait for + * + * @since Karma 1.0.0 + */ + bool WaitForFence(FVulkanFence* Fence); + + /** + * @brief Resets the given fence to the unsignaled state. + * + * @note Both CPU and GPU side states are reset. + * + * @param Fence The fence to be reset + * @since Karma 1.0.0 + */ + void ResetFence(FVulkanFence* Fence); + + /** + * @brief Releases the given fence back to the manager. + * + * Moves the fence from m_UsedFences to m_FreeFences for future reuse. + * + * @note This is kind of reverse of AllocateFence + * @param Fence The fence to be released + * + * @see KarmaGuiVulkanHandler::DestroyFramesOnFlightData + * + * @since Karma 1.0.0 + */ + void ReleaseFence(FVulkanFence*& Fence); + + protected: + /** + * @brief Destroys the given fence and releases its resources. + * + * @param InFence The fence to be destroyed + * @since Karma 1.0.0 + */ + void DestroyFence(FVulkanFence* InFence); + + protected: + FVulkanDevice& m_Device; + + /** + * @brief Array of free fences to be used. + * + * @note This gets filled mainly by ReleaseFence and WaitAndReleaseFence once + * work is finished. + */ + KarmaVector m_FreeFences; + + /** + * @brief Array of fences currently in use by GPU or in-flight + * work. + */ + KarmaVector m_UsedFences; + }; + + /** + * @brief Represents a Vulkan fence used for synchronization between the CPU and GPU. + * + * A fence is a synchronization primitive that can be used to coordinate operations between the CPU and GPU. + * It allows the CPU to wait for the completion of GPU operations, ensuring that resources are not accessed + * prematurely. + * + * @since Karma 1.0.0 + */ + class FVulkanFence + { + public: + /** + * @brief Constructor for FVulkanFence. + * + * @param InDevice Reference to the owning FVulkanDevice + * @param InOwner Reference to the owning FVulkanFenceManager + * + * @param bCreateSignaled Whether to create the fence in a signaled state + * + * @since Karma 1.0.0 + */ + FVulkanFence(FVulkanDevice& InDevice, FVulkanFenceManager& InOwner, bool bCreateSignaled); + + /** + * @brief Checks if the fence is currently signaled. + * + * @return true if the fence is in the signaled state, false otherwise + * + * @since Karma 1.0.0 + */ + inline bool IsSignaled() const + { + return m_State == EState::Signaled; + } + + /** + * @brief Retrieves the Vulkan fence handle. + * + * @return VkFence The Vulkan fence handle. + * + * @since Karma 1.0.0 + */ + inline VkFence GetHandle() const + { + return m_Handle; + } + + protected: + VkFence m_Handle; + + enum class EState + { + /** + * @brief The fence is not signaled, initial state after reset + * + * @note Fence must be signaled when created + * https://vulkan-tutorial.com/Drawing_a_triangle/Drawing/Rendering_and_presentation#page_Waiting-for-the-previous-frame + */ + NotReady, + + /** + * @brief The fence is signaled, indicating that the associated operations have completed + * + * @note vkWaitForFences will pass when fence is in this state + */ + Signaled, + }; + + EState m_State; + + FVulkanFenceManager& m_Owner; + + // Only owner can create and manage fences + ~FVulkanFence(); + + friend FVulkanFenceManager; + }; + + class FVulkanSemaphore + { + public: + FVulkanSemaphore(FVulkanDevice& InDevice); + + ~FVulkanSemaphore(); + + protected: + VkSemaphore m_Handle; + FVulkanDevice& m_Device; + }; +} diff --git a/Karma/src/Platform/Vulkan/VulkanRendererAPI.h b/Karma/src/Platform/Vulkan/VulkanRendererAPI.h index 50a7347a..42852282 100644 --- a/Karma/src/Platform/Vulkan/VulkanRendererAPI.h +++ b/Karma/src/Platform/Vulkan/VulkanRendererAPI.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Renderer/RendererAPI.h" #include "vulkan/vulkan.h" diff --git a/Karma/src/Platform/Vulkan/VulkanShader.cpp b/Karma/src/Platform/Vulkan/VulkanShader.cpp index 170de2a4..77f04fe3 100644 --- a/Karma/src/Platform/Vulkan/VulkanShader.cpp +++ b/Karma/src/Platform/Vulkan/VulkanShader.cpp @@ -5,15 +5,13 @@ namespace Karma { - VulkanShader::VulkanShader(const std::string& vertexSrc, const std::string& fragmentSrc, std::shared_ptr ubo) : Shader(ubo) + VulkanShader::VulkanShader(const std::string& vertexSrc, const std::string& fragmentSrc) { std::string vString = KarmaUtilities::ReadFileToSpitString(vertexSrc); vertSpirV = Compile(vertexSrc, vString, EShLangVertex);// vertex shader vString = KarmaUtilities::ReadFileToSpitString(fragmentSrc); fragSpirV = Compile(fragmentSrc, vString, EShLangFragment);// fragment shader - - m_UniformBufferObject = std::static_pointer_cast(ubo); } VulkanShader::~VulkanShader() @@ -23,7 +21,7 @@ namespace Karma std::vector VulkanShader::Compile(const std::string& src, const std::string& source, EShLanguage lang) { - KR_CORE_INFO("Compiling {0} {1} for Vulkan ...", lang == EShLangVertex ? "vertex shader" : "fragment shader", src); + KR_CORE_INFO("Compiling {0} {1} for Vulkan ..", lang == EShLangVertex ? "vertex shader" : "fragment shader", src); const char* sString = source.c_str(); diff --git a/Karma/src/Platform/Vulkan/VulkanShader.h b/Karma/src/Platform/Vulkan/VulkanShader.h index 8e9bde53..e6c6115b 100644 --- a/Karma/src/Platform/Vulkan/VulkanShader.h +++ b/Karma/src/Platform/Vulkan/VulkanShader.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Karma/Renderer/Shader.h" #include "glslang/Public/ShaderLang.h" #include "Karma/KarmaUtilities.h" @@ -58,7 +56,7 @@ namespace Karma * @see Shader::Create() * @since Karma 1.0.0 */ - VulkanShader(const std::string& vertexSrc, const std::string& fragmentSrc, std::shared_ptr ubo); + VulkanShader(const std::string& vertexSrc, const std::string& fragmentSrc); /** * @brief Destructor @@ -96,12 +94,10 @@ namespace Karma //Getters const std::vector& GetVertSpirV() const { return vertSpirV; } const std::vector& GetFragSpirV() const { return fragSpirV; } - std::shared_ptr GetUniformBufferObject() const { return m_UniformBufferObject; } private: std::vector vertSpirV; std::vector fragSpirV; - std::shared_ptr m_UniformBufferObject; }; } diff --git a/Karma/src/Platform/Vulkan/VulkanTexture.cpp b/Karma/src/Platform/Vulkan/VulkanTexture.cpp index 5d8f8325..cdee5960 100644 --- a/Karma/src/Platform/Vulkan/VulkanTexture.cpp +++ b/Karma/src/Platform/Vulkan/VulkanTexture.cpp @@ -1,20 +1,42 @@ #include "VulkanTexture.h" #include "VulkanHolder.h" +#include "VulkanRHI/VulkanDevice.h" +#include "VulkanRHI/VulkanDynamicRHI.h" namespace Karma { VulkanTexture::VulkanTexture() { - m_Device = VulkanHolder::GetVulkanContext()->GetLogicalDevice(); - m_PhysicalDevice = VulkanHolder::GetVulkanContext()->GetPhysicalDevice(); + m_LogicalDevice = VulkanHolder::GetVulkanContext()->GetLogicalDevice(); + m_GPU = VulkanHolder::GetVulkanContext()->GetPhysicalDevice(); + } + + VulkanTexture::VulkanTexture(FVulkanDevice* InDevice, const char* filename) : m_TextureImage(VK_NULL_HANDLE), + m_TextureImageMemory(VK_NULL_HANDLE), m_TextureImageView(VK_NULL_HANDLE), m_TextureSampler(VK_NULL_HANDLE) + { + m_Device = InDevice; + + m_LogicalDevice = InDevice->GetLogicalDevice(); + m_GPU = InDevice->GetGPU(); + + VulkanImageBuffer* vImageBuffer = new VulkanImageBuffer(InDevice, filename); + if (vImageBuffer != nullptr) + { + GenerateVulkanTexture(vImageBuffer); + } + else + { + KR_CORE_WARN("Couldn't load the image texture file {0}", filename); + } + delete vImageBuffer; } VulkanTexture::~VulkanTexture() { - vkDestroySampler(m_Device, m_TextureSampler, nullptr); - vkDestroyImageView(m_Device, m_TextureImageView, nullptr); - vkDestroyImage(m_Device, m_TextureImage, nullptr); - vkFreeMemory(m_Device, m_TextureImageMemory, nullptr); + vkDestroySampler(m_LogicalDevice, m_TextureSampler, nullptr); + vkDestroyImageView(m_LogicalDevice, m_TextureImageView, nullptr); + vkDestroyImage(m_LogicalDevice, m_TextureImage, nullptr); + vkFreeMemory(m_LogicalDevice, m_TextureImageMemory, nullptr); } void VulkanTexture::GenerateVulkanTexture(VulkanImageBuffer* vImageBuffer) @@ -41,24 +63,24 @@ namespace Karma imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.flags = 0; - VkResult result = vkCreateImage(m_Device, &imageInfo, nullptr, &m_TextureImage); + VkResult result = vkCreateImage(m_LogicalDevice, &imageInfo, nullptr, &m_TextureImage); KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create image!"); VkMemoryRequirements memRequirements; - vkGetImageMemoryRequirements(m_Device, m_TextureImage, &memRequirements); + vkGetImageMemoryRequirements(m_LogicalDevice, m_TextureImage, &memRequirements); VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = VulkanHolder::GetVulkanContext()->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - VkResult result1 = vkAllocateMemory(m_Device, &allocInfo, nullptr, &m_TextureImageMemory); + allocInfo.memoryTypeIndex = FVulkanDynamicRHI::Get().FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VkResult result1 = vkAllocateMemory(m_LogicalDevice, &allocInfo, nullptr, &m_TextureImageMemory); KR_CORE_ASSERT(result1 == VK_SUCCESS, "Failed to allocate image memeory"); - vkBindImageMemory(m_Device, m_TextureImage, m_TextureImageMemory, 0); + vkBindImageMemory(m_LogicalDevice, m_TextureImage, m_TextureImageMemory, 0); - VulkanHolder::GetVulkanContext()->TransitionImageLayout(m_TextureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - VulkanHolder::GetVulkanContext()->CopyBufferToImage(vImageBuffer->GetBuffer(), m_TextureImage, static_cast(vImageBuffer->GetTextureWidth()), static_cast(vImageBuffer->GetTextureHeight())); - VulkanHolder::GetVulkanContext()->TransitionImageLayout(m_TextureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + m_Device->TransitionImageLayout(m_TextureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + m_Device->CopyBufferToImage(vImageBuffer->GetBuffer(), m_TextureImage, static_cast(vImageBuffer->GetTextureWidth()), static_cast(vImageBuffer->GetTextureHeight())); + m_Device->TransitionImageLayout(m_TextureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } void VulkanTexture::CreateTextureImageView() @@ -74,7 +96,7 @@ namespace Karma viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; - VkResult result = vkCreateImageView(m_Device, &viewInfo, nullptr, &m_TextureImageView); + VkResult result = vkCreateImageView(m_LogicalDevice, &viewInfo, nullptr, &m_TextureImageView); KR_CORE_ASSERT(result == VK_SUCCESS, "Failed to create texture image view"); } @@ -91,7 +113,7 @@ namespace Karma samplerInfo.anisotropyEnable = VK_TRUE; VkPhysicalDeviceProperties properties{}; - vkGetPhysicalDeviceProperties(m_PhysicalDevice, &properties); + vkGetPhysicalDeviceProperties(m_GPU, &properties); samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; @@ -103,8 +125,8 @@ namespace Karma samplerInfo.minLod = 0.0f; samplerInfo.maxLod = 0.0f; - VkResult result = vkCreateSampler(m_Device, &samplerInfo, nullptr, &m_TextureSampler); + VkResult result = vkCreateSampler(m_LogicalDevice, &samplerInfo, nullptr, &m_TextureSampler); KR_CORE_ASSERT(result == false, "Failed to create texture sampler!"); } -} \ No newline at end of file +} diff --git a/Karma/src/Platform/Vulkan/VulkanTexture.h b/Karma/src/Platform/Vulkan/VulkanTexture.h index d2e47202..0cf48825 100644 --- a/Karma/src/Platform/Vulkan/VulkanTexture.h +++ b/Karma/src/Platform/Vulkan/VulkanTexture.h @@ -14,6 +14,8 @@ namespace Karma { + class FVulkanDevice; + /** * @brief Vulkan specific implementation of Texture class. * @@ -34,6 +36,17 @@ namespace Karma */ VulkanTexture(); + /** + * @brief Constructor with VulkanRHI support + * + * @brief InDevice The FVulkanDevice object + * @brief filename The location of the texture file along with location + * + * @see FVulkanDevice::InitGPU() + * @since Karma 1.0.0 + */ + VulkanTexture(FVulkanDevice* InDevice, const char* filename); + /** * @brief A destructor * @@ -98,9 +111,12 @@ namespace Karma VkSampler GetImageSampler() const { return m_TextureSampler; } private: + // VulkanRHI stuff + FVulkanDevice* m_Device; + // Vulkan context relevant stuff - VkDevice m_Device; - VkPhysicalDevice m_PhysicalDevice; + VkDevice m_LogicalDevice; + VkPhysicalDevice m_GPU; // Texture relevant stuff VkImage m_TextureImage; diff --git a/Karma/src/Platform/Vulkan/VulkanVertexArray.cpp b/Karma/src/Platform/Vulkan/VulkanVertexArray.cpp index eb797342..ba45009c 100644 --- a/Karma/src/Platform/Vulkan/VulkanVertexArray.cpp +++ b/Karma/src/Platform/Vulkan/VulkanVertexArray.cpp @@ -2,18 +2,20 @@ #include "Platform/Vulkan/VulkanHolder.h" #include "Platform/Vulkan/VulkanTexture.h" #include "Karma/Renderer/RenderCommand.h" +#include "VulkanRHI/VulkanDynamicRHI.h" +#include "VulkanRHI/VulkanDevice.h" namespace Karma { VulkanVertexArray::VulkanVertexArray() : m_SupportedDeviceFeatures(VulkanHolder::GetVulkanContext()->GetSupportedDeviceFeatures()), - m_device(VulkanHolder::GetVulkanContext()->GetLogicalDevice()) + m_device(FVulkanDynamicRHI::Get().GetDevice()->GetLogicalDevice()) { } VulkanVertexArray::~VulkanVertexArray() { vkDeviceWaitIdle(m_device); - CleanupPipeline(); + //CleanupPipeline(); } void VulkanVertexArray::Bind() const @@ -45,7 +47,7 @@ namespace Karma void VulkanVertexArray::SetShader(std::shared_ptr shader) { m_Shader = std::static_pointer_cast(shader); - VulkanHolder::GetVulkanContext()->RegisterUBO(m_Shader->GetUniformBufferObject()); + //VulkanHolder::GetVulkanContext()->RegisterUBO(m_Shader->GetUniformBufferObject()); GenerateVulkanVA(); } @@ -365,7 +367,7 @@ namespace Karma m_Materials.push_back(material); m_Shader = std::static_pointer_cast(material->GetShader(0)); - VulkanHolder::GetVulkanContext()->RegisterUBO(m_Shader->GetUniformBufferObject()); + //VulkanHolder::GetVulkanContext()->RegisterUBO(m_Shader->GetUniformBufferObject()); GenerateVulkanVA(); } @@ -499,7 +501,7 @@ namespace Karma void VulkanVertexArray::CreateDescriptorSetLayout() { VkDescriptorSetLayoutBinding uboLayoutBinding{}; - uboLayoutBinding.binding = m_Shader->GetUniformBufferObject()->GetBindingPointIndex(); + uboLayoutBinding.binding = 0;//m_Shader->GetUniformBufferObject()->GetBindingPointIndex(); uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; uboLayoutBinding.descriptorCount = 1; uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; @@ -553,9 +555,9 @@ namespace Karma for (size_t i = 0; i < maxFramesInFlight; i++) { VkDescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = m_Shader->GetUniformBufferObject()->GetUniformBuffers()[i]; + //bufferInfo.buffer = m_Shader->GetUniformBufferObject()->GetUniformBuffers()[i]; bufferInfo.offset = 0; - bufferInfo.range = m_Shader->GetUniformBufferObject()->GetBufferSize(); + //bufferInfo.range = m_Shader->GetUniformBufferObject()->GetBufferSize(); // Fetch right texture pointer first whose image is to be considered. // Caution: GetTexture index is with temporary assumption that needs addressing. @@ -570,7 +572,7 @@ namespace Karma descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrites[0].dstSet = m_descriptorSets[i]; - descriptorWrites[0].dstBinding = m_Shader->GetUniformBufferObject()->GetBindingPointIndex(); + descriptorWrites[0].dstBinding = 0;//m_Shader->GetUniformBufferObject()->GetBindingPointIndex(); descriptorWrites[0].dstArrayElement = 0; descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; descriptorWrites[0].descriptorCount = 1; diff --git a/Karma/src/Platform/Windows/Core/WindowsPlatformMemory.h b/Karma/src/Platform/Windows/Core/WindowsPlatformMemory.h index ed999651..32a73819 100644 --- a/Karma/src/Platform/Windows/Core/WindowsPlatformMemory.h +++ b/Karma/src/Platform/Windows/Core/WindowsPlatformMemory.h @@ -9,8 +9,6 @@ */ #pragma once -#include "krpch.h" - #include "Core/TrueCore/GenericPlatformMemory.h" namespace Karma @@ -37,4 +35,4 @@ namespace Karma }; typedef FWindowsPlatformMemory FPlatformMemory; -} \ No newline at end of file +} diff --git a/Karma/src/Platform/Windows/KarmaRHI/WindowsDynamicRHI.cpp b/Karma/src/Platform/Windows/KarmaRHI/WindowsDynamicRHI.cpp new file mode 100644 index 00000000..35f40bcb --- /dev/null +++ b/Karma/src/Platform/Windows/KarmaRHI/WindowsDynamicRHI.cpp @@ -0,0 +1,22 @@ +#include "WindowsDynamicRHI.h" +#include "VulkanDynamicRHI.h" + +#include "Log.h" + +namespace Karma +{ + +#ifdef KR_WINDOWS_PLATFORM + FDynamicRHI* PlatformCreateDynamicRHI() + { + FDynamicRHI* DynamicRHI = FDynamicRHI::CreateRHI(); + + if (DynamicRHI) + { + KR_CORE_INFO("Created DynamicRHI for Windows OS"); + } + + return DynamicRHI; + } +#endif +} diff --git a/Karma/src/Platform/Windows/KarmaRHI/WindowsDynamicRHI.h b/Karma/src/Platform/Windows/KarmaRHI/WindowsDynamicRHI.h new file mode 100644 index 00000000..e8255156 --- /dev/null +++ b/Karma/src/Platform/Windows/KarmaRHI/WindowsDynamicRHI.h @@ -0,0 +1,18 @@ +/** + * @file WindowsDynamicRHI.h + * @brief + * @author Ravi Mohan (the_cowboy) + * @version 1.0 + * @date December 15, 2025 + * + * @copyright Karma Engine copyright(c) People of India + */ + +#pragma once + +#include "KarmaRHI.h" + +namespace Karma +{ + +} \ No newline at end of file diff --git a/Karma/src/Platform/Windows/WindowsWindow.cpp b/Karma/src/Platform/Windows/WindowsWindow.cpp index 75d7b977..efc41185 100644 --- a/Karma/src/Platform/Windows/WindowsWindow.cpp +++ b/Karma/src/Platform/Windows/WindowsWindow.cpp @@ -1,5 +1,4 @@ #include "WindowsWindow.h" -#include "Karma/Log.h" #include "Karma/Events/ApplicationEvent.h" #include "Karma/Events/KeyEvent.h" #include "Karma/Events/MouseEvent.h" @@ -64,7 +63,8 @@ namespace Karma m_Window = glfwCreateWindow((int)m_Data.Width, (int)m_Data.Height, m_Data.Title.c_str(), nullptr, nullptr); - switch (currentAPI) + // To be uncommented when we have RHI + /*switch (currentAPI) { case RendererAPI::API::None: KR_CORE_ASSERT(false, "RendererAPI::None is not supported"); @@ -75,10 +75,9 @@ namespace Karma case RendererAPI::API::Vulkan: m_Context = new VulkanContext(m_Window); break; - } + }*/ - m_Context->Init(); - SetVSync(true); + //SetVSync(true); // Used for event callbacks glfwSetWindowUserPointer(m_Window, &m_Data); @@ -95,7 +94,7 @@ namespace Karma bool WindowsWindow::OnResize(WindowResizeEvent& event) { - return m_Context->OnWindowResize(event); + return true;//m_Context->OnWindowResize(event); <-- to be removed when RHI is in place } void WindowsWindow::SetGLFWCallbacks(GLFWwindow* glfwWindow) @@ -202,17 +201,17 @@ namespace Karma { glfwDestroyWindow(m_Window); glfwTerminate(); - if (m_Context) + /*if (m_Context) { delete m_Context; m_Context = 0; - } + }*/ } void WindowsWindow::OnUpdate() { glfwPollEvents(); - m_Context->SwapBuffers(); + //m_Context->SwapBuffers(); } void WindowsWindow::SetVSync(bool enabled) @@ -251,4 +250,4 @@ namespace Karma { return m_Data.VSync; } -} \ No newline at end of file +} diff --git a/Karma/src/krpch.h b/Karma/src/krpch.h index 383f070b..91cd60be 100644 --- a/Karma/src/krpch.h +++ b/Karma/src/krpch.h @@ -32,4 +32,4 @@ #include "Karma/Core.h" #include "Karma/Log.h" -#include "Karma/Core/KarmaTypes.h" +//#include "Karma/Core/KarmaTypes.h" diff --git a/Pranjal/Source/Private/EditorLayer.cpp b/Pranjal/Source/Private/EditorLayer.cpp index f05b0967..9c68027d 100644 --- a/Pranjal/Source/Private/EditorLayer.cpp +++ b/Pranjal/Source/Private/EditorLayer.cpp @@ -10,6 +10,8 @@ #include "UObjectIterator.h" #include "Engine.h" #include "GameInstance.h" +#include "Engine/StaticMeshActor.h" +#include namespace Karma { @@ -29,41 +31,41 @@ namespace Karma // First intantiate VertexArray m_ModelVertexArray.reset(Karma::VertexArray::Create()); - { + /* { // Get hold of model std::shared_ptr modelMesh; modelMesh.reset(new Karma::Mesh("../Resources/Models/BonedCylinder.obj")); // Set the mesh in vertex array m_ModelVertexArray->SetMesh(modelMesh); - } + }*/ // Next, instantiate material - m_ModelMaterial.reset(new Karma::Material()); + // m_ModelMaterial.reset(new Karma::Material()); { // Setting shader // Uniforms for regular transform uploads - std::shared_ptr shaderUniform; + /*std::shared_ptr shaderUniform; shaderUniform.reset(Karma::UniformBufferObject::Create({ Karma::ShaderDataType::Mat4, Karma::ShaderDataType::Mat4 }, 0)); m_ModelShader.reset(Karma::Shader::Create("../Resources/Shaders/shader.vert", "../Resources/Shaders/shader.frag", shaderUniform, "CylinderShader")); - m_ModelMaterial->AddShader(m_ModelShader); + m_ModelMaterial->AddShader(m_ModelShader);*/ } // Then we set texture - m_ModelTexture.reset(new Karma::Texture(Karma::TextureType::Image, "../Resources/Textures/UnrealGrid.png", "VikingTex", "texSampler")); + /*m_ModelTexture.reset(new Karma::Texture(Karma::TextureType::Image, "../Resources/Textures/UnrealGrid.png", "VikingTex", "texSampler")); m_ModelMaterial->AddTexture(m_ModelTexture); m_ModelMaterial->AttatchMainCamera(m_EditorCamera); //Is this needed? - m_ModelVertexArray->SetMaterial(m_ModelMaterial); + m_ModelVertexArray->SetMaterial(m_ModelMaterial);*/ m_EditorScene.reset(new Karma::Scene()); m_EditorScene->AddCamera(m_EditorCamera); - m_EditorScene->AddVertexArray(m_ModelVertexArray); + //m_EditorScene->AddVertexArray(m_ModelVertexArray); m_EditorScene->SetClearColor({ 0.0f, 0.0f, 0.0f, 1 }); } @@ -107,6 +109,9 @@ namespace Karma void EditorLayer::OnUpdate(float deltaTime) { + // Hacky, need to see if ubos are updated in correct manner (wrt to input etc) + m_EditorCamera->OnUpdate(deltaTime); + InputPolling(deltaTime); } @@ -200,6 +205,14 @@ namespace Karma { IterateActors(); } + else if (e.GetKeyCode() == GLFW_KEY_V) + { + SpawnStaticMeshActor(); + } + else if (e.GetKeyCode() == GLFW_KEY_B) + { + SpawnStaticMeshActor2(); + } return false; } @@ -340,6 +353,66 @@ namespace Karma // delete testWorld; } + void EditorLayer::SpawnStaticMeshActor() + { + // We are attempting to spawn StaticMeshActor here which will hold the model + + UWorld* currentWorld = GEngine->GetCurrentGameInstance()->GetWorldContext()->World(); + + if (currentWorld) + { + KR_INFO("Current World is : {0}", currentWorld->GetName()); + + FTransform smActorTransform = FTransform::m_Identity; + smActorTransform.SetTranslation(glm::vec3(1.f, 0, 1.f)); + + FActorSpawnParameters smActorParams; + smActorParams.m_Owner = nullptr; + smActorParams.m_Name = "StaticMeshActor"; + smActorParams.m_OverrideLevel = currentWorld->GetCurrentLevel(); + + + AStaticMeshActor* staticMeshActor = currentWorld->SpawnActor(AStaticMeshActor::StaticClass(), &smActorTransform, smActorParams); + + if (staticMeshActor) + { + KR_INFO("Spawned Actor: {0}", staticMeshActor->GetName()); + + staticMeshActor->LoadMeshFromFile("../Resources/Models/BonedCylinder.obj"); + m_EditorScene->AddStaticMeshActor(staticMeshActor); + } + } + } + + void EditorLayer::SpawnStaticMeshActor2() + { + UWorld* currentWorld = GEngine->GetCurrentGameInstance()->GetWorldContext()->World(); + + if (currentWorld) + { + KR_INFO("Current World is : {0}", currentWorld->GetName()); + + FTransform smActorTransform = FTransform::m_Identity; + smActorTransform.SetTranslation(glm::vec3(0.f, 0.f, 1.f)); + + FActorSpawnParameters smActorParams; + smActorParams.m_Owner = nullptr; + smActorParams.m_Name = "StaticMeshActor2"; + smActorParams.m_OverrideLevel = currentWorld->GetCurrentLevel(); + + + AStaticMeshActor* staticMeshActor = currentWorld->SpawnActor(AStaticMeshActor::StaticClass(), &smActorTransform, smActorParams); + + if (staticMeshActor) + { + KR_INFO("Spawned Actor: {0}", staticMeshActor->GetName()); + + staticMeshActor->LoadMeshFromFile("../Resources/Models/FORZombie.obj"); + m_EditorScene->AddStaticMeshActor(staticMeshActor); + } + } + } + void EditorLayer::IterateActors() { // Iterate over all actors, can also supply a different derived class diff --git a/Pranjal/Source/Private/KarmaGuiMesa.cpp b/Pranjal/Source/Private/KarmaGuiMesa.cpp index 5c226206..b95c3525 100644 --- a/Pranjal/Source/Private/KarmaGuiMesa.cpp +++ b/Pranjal/Source/Private/KarmaGuiMesa.cpp @@ -14,6 +14,8 @@ #include "hwinfo/hwinfo.h" #include "hwinfo/utils/unit.h" #include "spdlog/sinks/callback_sink.h" +#include "KarmaGui/KarmaGuizmo.h" +#include "StaticMeshActor.h" // Experimental //#include "Karma/Core/UObjectAllocator.h" @@ -36,6 +38,8 @@ namespace Karma std::shared_ptr s_MesaLogFormatter = nullptr; bool KarmaGuiMesa::m_EditorInitialized = false; bool KarmaGuiMesa::m_RefreshRenderingResources = false; + AStaticMeshActor* KarmaGuiMesa::m_SelectedSMActor = nullptr; + TransformCache KarmaGuiMesa::m_SelectedSMActorTransformCache; WindowManipulationGaugeData KarmaGuiMesa::m_3DExhibitor; WindowManipulationGaugeData KarmaGuiMesa::m_MemoryExhibitor; @@ -50,6 +54,70 @@ namespace Karma DropRectsDraw[n] = KGRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); } } + + void KarmaGuiMesa::EditTransform(std::shared_ptr scene) + { + if(m_SelectedSMActor == nullptr) + { + return; + } + + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + FTransform transform = m_SelectedSMActor->GetTransform(); + + matrixTranslation[0] = m_SelectedSMActorTransformCache.translation[0] = transform.GetTranslation().x; + matrixTranslation[1] = m_SelectedSMActorTransformCache.translation[1] = transform.GetTranslation().y; + matrixTranslation[2] = m_SelectedSMActorTransformCache.translation[2] = transform.GetTranslation().z; + + matrixRotation[0] = m_SelectedSMActorTransformCache.rotation[0] = transform.GetRotation().m_Roll; + matrixRotation[1] = m_SelectedSMActorTransformCache.rotation[1] = transform.GetRotation().m_Pitch; + matrixRotation[2] = m_SelectedSMActorTransformCache.rotation[2] = transform.GetRotation().m_Yaw; + + matrixScale[0] = m_SelectedSMActorTransformCache.scale[0] = transform.GetScale3D().x; + matrixScale[1] = m_SelectedSMActorTransformCache.scale[1] = transform.GetScale3D().y; + matrixScale[2] = m_SelectedSMActorTransformCache.scale[2] = transform.GetScale3D().z; + + KarmaGui::InputFloat3("Tr", matrixTranslation, "%.2f"); + KarmaGui::InputFloat3("Rt", matrixRotation, "%.2f"); + + KarmaGui::InputFloat3("Sc", matrixScale, "%.2f"); + + if (matrixTranslation[0] != m_SelectedSMActorTransformCache.translation[0] || matrixTranslation[1] != m_SelectedSMActorTransformCache.translation[1] || matrixTranslation[2] != m_SelectedSMActorTransformCache.translation[2] || + matrixRotation[0] != m_SelectedSMActorTransformCache.rotation[0] || matrixRotation[1] != m_SelectedSMActorTransformCache.rotation[1] || matrixRotation[2] != m_SelectedSMActorTransformCache.rotation[2] || + matrixScale[0] != m_SelectedSMActorTransformCache.scale[0] || matrixScale[1] != m_SelectedSMActorTransformCache.scale[1] || matrixScale[2] != m_SelectedSMActorTransformCache.scale[2]) + { + m_SelectedSMActorTransformCache.translation[0] = matrixTranslation[0]; + m_SelectedSMActorTransformCache.translation[1] = matrixTranslation[1]; + m_SelectedSMActorTransformCache.translation[2] = matrixTranslation[2]; + + m_SelectedSMActorTransformCache.rotation[0] = matrixRotation[0]; + m_SelectedSMActorTransformCache.rotation[1] = matrixRotation[1]; + m_SelectedSMActorTransformCache.rotation[2] = matrixRotation[2]; + + m_SelectedSMActorTransformCache.scale[0] = matrixScale[0]; + m_SelectedSMActorTransformCache.scale[1] = matrixScale[1]; + m_SelectedSMActorTransformCache.scale[2] = matrixScale[2]; + + m_SelectedSMActorTransformCache.bIsDirty = true; + } + + if (m_SelectedSMActorTransformCache.bIsDirty) + { + glm::vec3 translation(matrixTranslation[0], matrixTranslation[1], matrixTranslation[2]); + transform.SetTranslation(translation); + + glm::vec3 euler(matrixRotation[0], matrixRotation[1], matrixRotation[2]); + TRotator rotation(euler); + + transform.SetRotation(rotation); + + glm::vec3 scale(matrixScale[0], matrixScale[1], matrixScale[2]); + transform.SetScale3D(scale); + + m_SelectedSMActor->SetActorTransform(transform); + m_SelectedSMActorTransformCache.bIsDirty = false; + } + } void KarmaGuiMesa::RevealMainFrame(KGGuiID mainMesaDockID, std::shared_ptr scene, const CallbacksFromEditor& editorCallbacks) { @@ -58,15 +126,16 @@ namespace Karma // 2. Show a simple sampling and experiment window { - static bool show = true; + static bool show1 = true; + static bool show2 = true; static float fValue = 0.0f; static int counter = 0; KarmaGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and appeninto it KarmaGui::Text("This is some useful text."); // Display some text (you can use a format strings too) - KarmaGui::Checkbox("Demo Window", &show); // Edit bools storing our window open/close state - KarmaGui::Checkbox("Another Window", &show); + KarmaGui::Checkbox("Demo Window", &show1); // Edit bools storing our window open/close state + KarmaGui::Checkbox("Another Window", &show2); KarmaGui::SliderFloat("float", &fValue, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f //ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a colo @@ -86,6 +155,8 @@ namespace Karma node = nullptr;//ImGui::FindAppropriateNode(window, payloadWindow, boxNumber); KarmaGui::Text("Node ID = %d at position x = %f, y = %f on docking box %d", node != nullptr ? node->ID : 0, KarmaGui::GetMousePos().x, KarmaGui::GetMousePos().y, boxNumber); + + EditTransform(scene); if (payloadWindow) KarmaGui::Text("Karma: Log window is of dimension width = %f und height = %f", payloadWindow->Size.x, payloadWindow->Size.y); @@ -100,7 +171,7 @@ namespace Karma // 4. A panel for scene hierarchy and whatnot { - DrawKarmaSceneHierarchyPanelMesa(); + DrawKarmaSceneHierarchyPanelMesa(scene); } // 5. A window for 3D rendering part @@ -512,8 +583,16 @@ namespace Karma bShouldRefresh = true; } - m_ViewportFocused = KarmaGui::IsWindowFocused(); - m_ViewportHovered = KarmaGui::IsWindowHovered() && !((window->Pos.y + window->TitleBarHeight()) * KarmaGui::GetIO().DisplayFramebufferScale.y > KarmaGui::GetMousePos().y); + if (!KarmaGuizmo::IsUsing()) + { + m_ViewportFocused = KarmaGui::IsWindowFocused(); + m_ViewportHovered = KarmaGui::IsWindowHovered() && !((window->Pos.y + window->TitleBarHeight()) * KarmaGui::GetIO().DisplayFramebufferScale.y > KarmaGui::GetMousePos().y); + } + else + { + m_ViewportFocused = false; + m_ViewportHovered = false; + } KarmaGuiIO& io = KarmaGui::GetIO(); @@ -524,14 +603,49 @@ namespace Karma KarmaGuiBackendRendererUserData* backendData = KarmaGuiRenderer::GetBackendRendererUserData(); backgroundImageTextureID = backendData->GetTextureIDAtIndex(1); - - KGTextureID textureID3D = KarmaGuiRenderer::Add3DSceneFor2DRendering(scene, KGVec2(window->Size.x, window->Size.y)); + KGTextureID textureID3D = nullptr; + if (scene->GetSMActors().size() > 0) + { + textureID3D = KarmaGuiRenderer::Add3DSceneFor2DRendering(scene, KGVec2(window->Size.x, window->Size.y)); + KarmaGui::SetItemAllowOverlap(); + } + + KGVec2 pos = KarmaGui::GetWindowPos(); KGDrawList* drawList = KarmaGui::GetWindowDrawList(); - KGVec2 pos = KarmaGui::GetCursorScreenPos(); drawList->AddImage((void*)backgroundImageTextureID, pos, KGVec2(pos.x + window->Size.x, pos.y + window->Size.y)); - drawList->AddImage((void*)textureID3D, pos, KGVec2(pos.x + window->Size.x, pos.y + window->Size.y)); + if (scene->GetSMActors().size() > 0 && textureID3D != nullptr) + { + drawList->AddImage((void*)textureID3D, pos, KGVec2(pos.x + window->Size.x, pos.y + window->Size.y)); + } + + KarmaGuizmo::SetDrawlist(); + KarmaGuizmo::SetRect(pos.x, pos.y + window->TitleBarHeight(), window->Size.x, window->Size.y - window->TitleBarHeight()); + + if(scene->GetSMActors().size() > 0 && m_SelectedSMActor != nullptr) + { + std::shared_ptr sceneCamera = scene->GetSceneCamera(); + glm::mat4 viewMatrix = sceneCamera->GetViewMatirx(); + + glm::mat4 projectionMatrix = sceneCamera->GetProjectionMatrix(); + + float* projectionPtr = glm::value_ptr(projectionMatrix); + projectionPtr[5] *= -1.f; + + FTransform operationalTransform = m_SelectedSMActor->GetTransform(); + + glm::mat4 objectMatrix = operationalTransform.ToMatrixWithScale(); + bool bManipulate = KarmaGuizmo::Manipulate(glm::value_ptr(viewMatrix), projectionPtr, KarmaGuizmo::UNIVERSAL, KarmaGuizmo::WORLD, glm::value_ptr(objectMatrix)); + + if(KarmaGuizmo::IsUsing() && bManipulate) + { + FTransform transform; + transform.ToTransform(objectMatrix); + + m_SelectedSMActor->SetActorTransform(transform); + } + } scene->SetRenderWindow(window); @@ -571,14 +685,30 @@ namespace Karma KarmaGui::End(); } - void KarmaGuiMesa::DrawKarmaSceneHierarchyPanelMesa() + void KarmaGuiMesa::DrawKarmaSceneHierarchyPanelMesa(std::shared_ptr scene) { KarmaGui::SetNextWindowSize(KGVec2(500, 400), KGGuiCond_FirstUseEver); KarmaGui::Begin("Scene Hierarchy"); - KarmaGui::Text("Some Stuff 1"); - KarmaGui::Text("Some stuff 2"); - KarmaGui::Text("lumbdaa"); + + for(const auto& smActor : scene->GetSMActors()) + { + const char* sceneElement = smActor->GetName().c_str(); + + KGGuiTreeNodeFlags_ flags = (smActor == m_SelectedSMActor) ? KGGuiTreeNodeFlags_Selected : KGGuiTreeNodeFlags_Bullet; + bool opened = KarmaGui::TreeNodeEx(sceneElement, flags); + + if(KarmaGui::IsItemClicked()) + { + m_SelectedSMActor = smActor; + } + + if(opened) + { + KarmaGui::TreePop(); + } + } + KarmaGui::End(); } diff --git a/Pranjal/Source/Public/EditorLayer.h b/Pranjal/Source/Public/EditorLayer.h index 988364f5..a1e20473 100644 --- a/Pranjal/Source/Public/EditorLayer.h +++ b/Pranjal/Source/Public/EditorLayer.h @@ -26,6 +26,11 @@ namespace Karma void TentativeTrigger(); void IterateActors(); + // Tentative routine for incorporating spawning of full fledged StaticMeshActor (SM actor) + // with model + void SpawnStaticMeshActor(); + void SpawnStaticMeshActor2(); + private: std::shared_ptr m_ModelShader; std::shared_ptr m_ModelVertexArray; diff --git a/Pranjal/Source/Public/KarmaGuiMesa.h b/Pranjal/Source/Public/KarmaGuiMesa.h index f156e232..580b9b80 100644 --- a/Pranjal/Source/Public/KarmaGuiMesa.h +++ b/Pranjal/Source/Public/KarmaGuiMesa.h @@ -272,15 +272,25 @@ namespace Karma } }; + struct TransformCache + { + float translation[3]; + float rotation[3]; + float scale[3]; + + bool bIsDirty = false; + }; + class KarmaGuiMesa { public: // Showtime! static void RevealMainFrame(KGGuiID mainMesaDockID, std::shared_ptr scene, const CallbacksFromEditor& editorCallbacks); + static void EditTransform(std::shared_ptr scene); static void DrawKarmaMainMenuBarMesa(); static void DrawMainMenuFileListMesa(); static void DrawKarmaLogMesa(KGGuiID mainMesaDockID); - static void DrawKarmaSceneHierarchyPanelMesa(); + static void DrawKarmaSceneHierarchyPanelMesa(std::shared_ptr scene); static void Draw3DModelExhibitorMesa(std::shared_ptr scene); static void DrawContentBrowser(const std::function< void(std::string) >& openSceneCallback); static void DrawMemoryExhibitor(); @@ -320,6 +330,9 @@ namespace Karma static WindowManipulationGaugeData m_MemoryExhibitor; static bool m_EditorInitialized; static bool m_RefreshRenderingResources; + static class AStaticMeshActor* m_SelectedSMActor; + static TransformCache m_SelectedSMActorTransformCache; + // Content browser static std::filesystem::path m_CurrentDirectory; diff --git a/Resources/Shaders/shader.frag b/Resources/Shaders/shader.frag index a6d8701f..83b09aa8 100644 --- a/Resources/Shaders/shader.frag +++ b/Resources/Shaders/shader.frag @@ -6,7 +6,8 @@ layout(location = 1) in vec2 fragUVs; layout(location = 0) out vec4 outColor; -layout(binding = 1) uniform sampler2D texSampler; +// set 1: texture +layout(set = 0, binding = 1) uniform sampler2D texSampler; void main() { diff --git a/Resources/Shaders/shader.vert b/Resources/Shaders/shader.vert index ab96f219..f1872f88 100644 --- a/Resources/Shaders/shader.vert +++ b/Resources/Shaders/shader.vert @@ -5,22 +5,29 @@ layout(location = 0) in vec3 inPosition; layout(location = 1) in vec2 inUV; -layout(location = 2) in vec4 inColor; +layout(location = 2) in vec3 inNormal; layout(location = 0) out vec4 fragColor; layout(location = 1) out vec2 fragUVs; -layout(std140, binding = 0) uniform MVPUniformBufferObject +// set 0: camera +layout(set = 0, binding = 0) uniform VPUniformBufferObject { mat4 u_Projection; mat4 u_View; }; +// set 2: per-mesh transform +layout(set = 1, binding = 0) uniform ModelUniformBufferObject +{ + mat4 u_Model; +}; + void main() { - gl_Position = u_Projection * u_View * vec4(inPosition, 1.0); + gl_Position = u_Projection * u_View * u_Model * vec4(inPosition, 1.0); fragColor = vec4(1.0f, 1.0f, 1.0f, 1.0f); fragUVs = inUV; }