├── Luna ├── Source │ ├── Vulkan │ │ ├── Enums.cpp │ │ ├── Cookie.cpp │ │ ├── Common.cpp │ │ ├── CMakeLists.txt │ │ ├── Format.cpp │ │ ├── Sampler.cpp │ │ ├── Fence.cpp │ │ ├── CommandPool.cpp │ │ ├── QueryPool.cpp │ │ ├── Semaphore.cpp │ │ ├── DescriptorSet.cpp │ │ └── Buffer.cpp │ ├── Core │ │ ├── Windows │ │ │ └── CMakeLists.txt │ │ ├── CMakeLists.txt │ │ ├── Log.cpp │ │ ├── Input.cpp │ │ ├── Engine.cpp │ │ ├── Window.cpp │ │ └── WindowManager.cpp │ ├── Utility │ │ ├── CMakeLists.txt │ │ ├── String.cpp │ │ ├── Timer.cpp │ │ └── Memory.cpp │ ├── CMakeLists.txt │ └── Renderer │ │ ├── CMakeLists.txt │ │ ├── Camera.cpp │ │ ├── ShaderCompiler.cpp │ │ └── Swapchain.cpp ├── Include │ └── Luna │ │ ├── Utility │ │ ├── Math.hpp │ │ ├── Memory.hpp │ │ ├── Timer.hpp │ │ ├── StackAllocator.hpp │ │ ├── String.hpp │ │ ├── ObjectPool.hpp │ │ ├── Hash.hpp │ │ ├── SpinLock.hpp │ │ ├── BitOps.hpp │ │ ├── TemporaryHashMap.hpp │ │ ├── Path.hpp │ │ ├── IntrusiveList.hpp │ │ ├── Bitmask.hpp │ │ └── Time.hpp │ │ ├── Renderer │ │ ├── Renderer.hpp │ │ ├── UIManager.hpp │ │ ├── ShaderCompiler.hpp │ │ ├── Enums.hpp │ │ ├── Swapchain.hpp │ │ ├── Camera.hpp │ │ ├── ShaderManager.hpp │ │ └── Scene.hpp │ │ ├── Vulkan │ │ ├── InternalSync.hpp │ │ ├── Cookie.hpp │ │ ├── CommandPool.hpp │ │ ├── Fence.hpp │ │ ├── Context.hpp │ │ ├── Format.hpp │ │ ├── QueryPool.hpp │ │ ├── DescriptorSet.hpp │ │ ├── Buffer.hpp │ │ ├── Semaphore.hpp │ │ ├── Sampler.hpp │ │ ├── TextureFormat.hpp │ │ ├── RenderPass.hpp │ │ ├── Shader.hpp │ │ └── Enums.hpp │ │ ├── Core │ │ ├── Engine.hpp │ │ ├── WindowManager.hpp │ │ ├── OSFilesystem.hpp │ │ ├── Window.hpp │ │ ├── Log.hpp │ │ ├── Threading.hpp │ │ ├── Input.hpp │ │ └── Filesystem.hpp │ │ └── Common.hpp └── CMakeLists.txt ├── Resources ├── Fonts │ ├── NotoSansJP-Medium.otf │ ├── Roboto-SemiMedium.ttf │ ├── FontAwesome6Free-Solid-900.otf │ └── FontAwesome6Free-Regular-400.otf └── Shaders │ ├── DebugLines.frag.glsl │ ├── Luna.glsli │ ├── HzbCopy.comp.glsl │ ├── ImGui.vert.glsl │ ├── DebugLines.vert.glsl │ ├── ImGui.frag.glsl │ ├── Common.glsli │ ├── VisBuffer.glsli │ ├── HzbReduce.comp.glsl │ ├── StaticMesh.frag.glsl │ ├── StaticMesh.vert.glsl │ ├── CullMeshlets.comp.glsl │ └── CullTriangles.comp.glsl ├── Launcher ├── CrashHandler.hpp ├── CMakeLists.txt └── Launcher.cpp ├── .gitignore ├── TestShader.comp.glsl ├── CMakeLists.txt ├── LICENSE ├── .clang-format └── README.md /Luna/Source/Vulkan/Enums.cpp: -------------------------------------------------------------------------------- 1 | #include "Enums.h" 2 | -------------------------------------------------------------------------------- /Luna/Source/Core/Windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(Luna PRIVATE 2 | OSFilesystem.cpp) 3 | -------------------------------------------------------------------------------- /Resources/Fonts/NotoSansJP-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eearslya/Luna/HEAD/Resources/Fonts/NotoSansJP-Medium.otf -------------------------------------------------------------------------------- /Resources/Fonts/Roboto-SemiMedium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eearslya/Luna/HEAD/Resources/Fonts/Roboto-SemiMedium.ttf -------------------------------------------------------------------------------- /Luna/Source/Utility/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(Luna PRIVATE 2 | Memory.cpp 3 | Path.cpp 4 | String.cpp 5 | Timer.cpp) 6 | -------------------------------------------------------------------------------- /Resources/Fonts/FontAwesome6Free-Solid-900.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eearslya/Luna/HEAD/Resources/Fonts/FontAwesome6Free-Solid-900.otf -------------------------------------------------------------------------------- /Luna/Source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(Core) 2 | add_subdirectory(Renderer) 3 | add_subdirectory(Utility) 4 | add_subdirectory(Vulkan) 5 | -------------------------------------------------------------------------------- /Resources/Fonts/FontAwesome6Free-Regular-400.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eearslya/Luna/HEAD/Resources/Fonts/FontAwesome6Free-Regular-400.otf -------------------------------------------------------------------------------- /Launcher/CrashHandler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace CrashHandler { 4 | bool Initialize() noexcept; 5 | void Shutdown() noexcept; 6 | } // namespace CrashHandler 7 | -------------------------------------------------------------------------------- /Resources/Shaders/DebugLines.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) out vec4 outColor; 4 | 5 | void main() { 6 | outColor = vec4(1, 1, 1, 1); 7 | } 8 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/Math.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Luna { 4 | inline constexpr uint32_t PreviousPowerOfTwo(uint32_t value) { 5 | uint32_t v = 1; 6 | while ((v << 1) < value) { v <<= 1; } 7 | 8 | return v; 9 | } 10 | } // namespace Luna 11 | -------------------------------------------------------------------------------- /Luna/Source/Renderer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(Luna PRIVATE 2 | Camera.cpp 3 | Renderer.cpp 4 | #RenderGraph.cpp 5 | #RenderPass.cpp 6 | Scene.cpp 7 | ShaderCompiler.cpp 8 | ShaderManager.cpp 9 | Swapchain.cpp 10 | UIManager.cpp) 11 | -------------------------------------------------------------------------------- /Luna/Source/Core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(Luna PRIVATE 2 | Engine.cpp 3 | Filesystem.cpp 4 | Input.cpp 5 | Log.cpp 6 | Threading.cpp 7 | Window.cpp 8 | WindowManager.cpp) 9 | 10 | if(WIN32) 11 | add_subdirectory(Windows) 12 | endif(WIN32) 13 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/Cookie.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace Luna { 5 | namespace Vulkan { 6 | Cookie::Cookie(Device& device) : _cookie(device.AllocateCookie()) {} 7 | } // namespace Vulkan 8 | } // namespace Luna 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | .idea/ 3 | .vs/ 4 | .vscode/ 5 | /[Aa]ssets/ 6 | /[Bb]uild/ 7 | /[Cc]ache/ 8 | /Examples/ 9 | /[Pp]roject/ 10 | /Resources/Models/ 11 | 12 | *~ 13 | .dir-locals.el 14 | .DS_Store 15 | *.bak 16 | *.cap 17 | *.dll 18 | *.exe 19 | *.lib 20 | *.log 21 | *.o 22 | *.pdb 23 | *.spv 24 | imgui.ini 25 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/Memory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | void* AllocateAligned(std::size_t size, std::size_t alignment); 7 | void FreeAligned(void* ptr); 8 | 9 | struct AlignedDeleter { 10 | void operator()(void* ptr); 11 | }; 12 | } // namespace Luna 13 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/Timer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | int64_t GetCurrentTimeNanoseconds(); 7 | 8 | class Timer { 9 | public: 10 | void Start(); 11 | double End() const; 12 | 13 | private: 14 | int64_t _t = 0; 15 | }; 16 | } // namespace Luna 17 | -------------------------------------------------------------------------------- /Resources/Shaders/Luna.glsli: -------------------------------------------------------------------------------- 1 | #extension GL_EXT_nonuniform_qualifier : enable 2 | #extension GL_EXT_buffer_reference : enable 3 | #extension GL_EXT_scalar_block_layout : enable 4 | 5 | #define LunaStorageBufferBinding 0 6 | #define LunaStorageImageBinding 1 7 | #define LunaSampledImageBinding 2 8 | #define LunaSamplerBinding 3 9 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Renderer/Renderer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | class Renderer final { 7 | public: 8 | static bool Initialize(); 9 | static void Shutdown(); 10 | 11 | static Vulkan::Device& GetDevice(); 12 | static void Render(); 13 | }; 14 | } // namespace Luna 15 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/InternalSync.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Luna { 4 | namespace Vulkan { 5 | class InternalSyncEnabled { 6 | public: 7 | void SetInternalSync() { 8 | _internalSync = true; 9 | } 10 | 11 | protected: 12 | bool _internalSync = false; 13 | }; 14 | } // namespace Vulkan 15 | } // namespace Luna 16 | -------------------------------------------------------------------------------- /TestShader.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(set = 0, binding = 0) buffer InBuffer { 4 | float In; 5 | }; 6 | 7 | layout(set = 0, binding = 1) buffer OutBuffer { 8 | float Out; 9 | }; 10 | 11 | layout(push_constant) uniform PushConstant { 12 | float Data; 13 | }; 14 | 15 | void main() { 16 | Out = In * Data; 17 | } 18 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Core/Engine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | class Window; 7 | 8 | class Engine final { 9 | public: 10 | static bool Initialize(); 11 | static int Run(); 12 | static void Shutdown(); 13 | 14 | static double GetTime(); 15 | static Window* GetMainWindow(); 16 | }; 17 | } // namespace Luna 18 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/Common.cpp: -------------------------------------------------------------------------------- 1 | #define VMA_IMPLEMENTATION 2 | 3 | #ifdef __clang__ 4 | # pragma clang diagnostic push 5 | # pragma clang diagnostic ignored "-Wnullability-completeness" 6 | #endif 7 | #include 8 | #ifdef __clang__ 9 | # pragma clang diagnostic pop 10 | #endif 11 | 12 | namespace Luna { 13 | namespace Vulkan {} // namespace Vulkan 14 | } // namespace Luna 15 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(Luna PRIVATE 2 | Buffer.cpp 3 | CommandBuffer.cpp 4 | CommandPool.cpp 5 | Common.cpp 6 | Context.cpp 7 | Cookie.cpp 8 | DescriptorSet.cpp 9 | Device.cpp 10 | Fence.cpp 11 | Format.cpp 12 | Image.cpp 13 | QueryPool.cpp 14 | RenderPass.cpp 15 | Sampler.cpp 16 | Semaphore.cpp 17 | Shader.cpp 18 | TextureFormat.cpp) 19 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/Cookie.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | class Device; 8 | 9 | class Cookie { 10 | public: 11 | Cookie(Device& device); 12 | 13 | [[nodiscard]] uint64_t GetCookie() const noexcept { 14 | return _cookie; 15 | } 16 | 17 | private: 18 | const uint64_t _cookie; 19 | }; 20 | } // namespace Vulkan 21 | } // namespace Luna 22 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Renderer/UIManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | class CommandBuffer; 8 | } 9 | 10 | class UIManager { 11 | public: 12 | static bool Initialize(); 13 | static void Shutdown(); 14 | 15 | static void BeginFrame(); 16 | static void Render(Vulkan::CommandBuffer& cmd); 17 | static void UpdateFontAtlas(); 18 | }; 19 | } // namespace Luna 20 | -------------------------------------------------------------------------------- /Launcher/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project(Luna-Launcher LANGUAGES CXX) 3 | 4 | add_executable(Luna-Launcher CrashHandler.cpp Launcher.cpp) 5 | target_link_libraries(Luna-Launcher PRIVATE Luna) 6 | set_target_properties(Luna-Launcher PROPERTIES 7 | OUTPUT_NAME "Luna" 8 | VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") 9 | 10 | add_custom_target(Run 11 | COMMAND Luna-Launcher 12 | DEPENDS Luna-Launcher 13 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") 14 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project(Luna LANGUAGES CXX) 3 | 4 | if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") 6 | endif() 7 | set(CMAKE_CXX_STANDARD 20) 8 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Bin") 9 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 10 | 11 | set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY VS_STARTUP_PROJECT "Luna-Launcher") 12 | 13 | add_subdirectory(Launcher) 14 | add_subdirectory(Luna) 15 | -------------------------------------------------------------------------------- /Resources/Shaders/HzbCopy.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #include "Common.glsli" 4 | 5 | layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; 6 | 7 | layout(set = 0, binding = 0) uniform sampler2D In; 8 | layout(set = 0, binding = 1, r32f) uniform restrict writeonly image2D Out; 9 | 10 | void main() { 11 | const vec2 position = vec2(gl_GlobalInvocationID.xy); 12 | const vec2 hzbSize = vec2(imageSize(Out)); 13 | const vec2 uv = (position + 0.5) / hzbSize; 14 | const float depthSample = texture(In, uv).r; 15 | imageStore(Out, ivec2(position), vec4(depthSample)); 16 | } 17 | -------------------------------------------------------------------------------- /Resources/Shaders/ImGui.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) in vec2 inPosition; 4 | layout(location = 1) in vec2 inTexcoord; 5 | layout(location = 2) in vec4 inColor; 6 | 7 | layout(push_constant) uniform PushConstant { 8 | vec2 Scale; 9 | vec2 Translate; 10 | uint SampleMode; 11 | }; 12 | 13 | layout(location = 0) out vec2 outTexcoord; 14 | layout(location = 1) out vec4 outColor; 15 | 16 | void main() { 17 | outTexcoord = inTexcoord; 18 | outColor = inColor; 19 | 20 | vec2 position = inPosition * Scale + Translate; 21 | gl_Position = vec4(vec2(position.x, -1.0 * position.y), 0, 1); 22 | } 23 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Core/WindowManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct GLFWcursor; 6 | 7 | namespace Luna { 8 | enum class MouseCursor : uint8_t { 9 | Arrow, 10 | IBeam, 11 | Crosshair, 12 | Hand, 13 | ResizeNS, 14 | ResizeEW, 15 | ResizeNESW, 16 | ResizeNWSE, 17 | ResizeAll 18 | }; 19 | 20 | class WindowManager final { 21 | public: 22 | static bool Initialize(); 23 | static void Update(); 24 | static void Shutdown(); 25 | 26 | static GLFWcursor* GetCursor(MouseCursor cursor); 27 | static std::vector GetRequiredInstanceExtensions(); 28 | static double GetTime(); 29 | }; 30 | }; // namespace Luna 31 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Standard Library Includes 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | // Third-Party Libraries 23 | #include 24 | #include 25 | 26 | // Core Engine Functionality 27 | #include 28 | #include 29 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/StackAllocator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | template 7 | class StackAllocator { 8 | public: 9 | T* Allocate(size_t count) { 10 | if (count == 0 || (_index + count) > N) { return nullptr; } 11 | 12 | T* ret = &_buffer[_index]; 13 | _index += count; 14 | 15 | return ret; 16 | } 17 | 18 | T* AllocateCleared(size_t count) { 19 | T* ret = Allocate(count); 20 | if (ret) { std::fill(ret, ret + count, T()); } 21 | 22 | return ret; 23 | } 24 | 25 | void Reset() { 26 | _index = 0; 27 | } 28 | 29 | private: 30 | T _buffer[N]; 31 | size_t _index = 0; 32 | }; 33 | } // namespace Luna 34 | -------------------------------------------------------------------------------- /Resources/Shaders/DebugLines.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_EXT_scalar_block_layout : require 3 | 4 | #include "Common.glsli" 5 | 6 | struct DebugLine { 7 | vec3 Start; 8 | vec3 End; 9 | vec3 Color; 10 | }; 11 | 12 | layout(set = 0, binding = 0, scalar) uniform SceneBuffer { 13 | SceneData Scene; 14 | }; 15 | 16 | layout(set = 1, binding = 0, scalar) restrict readonly buffer DebugLinesBuffer { 17 | DebugLine Lines[]; 18 | }; 19 | 20 | void main() { 21 | const DebugLine line = Lines[gl_VertexIndex / 2]; 22 | const vec3 position = ((gl_VertexIndex & 1) == 1) ? line.End : line.Start; 23 | gl_Position = /*Scene.ViewProjection **/ vec4(position.xy * 2.0 - 1.0, 0.0, 1.0); 24 | } 25 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/String.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | template 7 | std::string StringJoin(const Range& elements, const char* const delimiter) { 8 | std::ostringstream oss; 9 | auto b = std::begin(elements); 10 | auto e = std::end(elements); 11 | 12 | if (b != e) { 13 | std::copy(b, std::prev(e), std::ostream_iterator(oss, delimiter)); 14 | b = std::prev(e); 15 | } 16 | if (b != e) { 17 | oss << *b; 18 | } 19 | 20 | return oss.str(); 21 | } 22 | 23 | std::vector StringSplit(std::string_view str, std::string_view delim, bool keepEmpty = true); 24 | } 25 | -------------------------------------------------------------------------------- /Resources/Shaders/ImGui.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) in vec2 inTexcoord; 4 | layout(location = 1) in vec4 inColor; 5 | 6 | layout(set = 0, binding = 0) uniform sampler2D Texture; 7 | 8 | layout(push_constant) uniform PushConstant { 9 | vec2 Scale; 10 | vec2 Translate; 11 | uint SampleMode; 12 | }; 13 | 14 | layout(location = 0) out vec4 outColor; 15 | 16 | void main() { 17 | vec4 texSample = texture(Texture, inTexcoord); 18 | 19 | switch(SampleMode) { 20 | case 1: // ImGui Font 21 | texSample = vec4(1, 1, 1, texSample.r); 22 | break; 23 | case 2: // Grayscale 24 | texSample = texSample.rrra; 25 | break; 26 | default: // Standard 27 | break; 28 | } 29 | 30 | outColor = inColor * texSample; 31 | } 32 | -------------------------------------------------------------------------------- /Luna/Source/Utility/String.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace Luna { 4 | std::vector StringSplit(std::string_view str, std::string_view delim, bool keepEmpty) { 5 | if (str.empty()) { return {}; } 6 | 7 | std::vector ret; 8 | 9 | std::string::size_type startIndex = 0; 10 | std::string::size_type index = 0; 11 | while ((index = str.find_first_of(delim, startIndex)) != std::string::npos) { 12 | if (keepEmpty || index > startIndex) { ret.emplace_back(str.substr(startIndex, index - startIndex)); } 13 | startIndex = index + 1; 14 | if (keepEmpty && (index == str.size() - 1)) { ret.emplace_back(); } 15 | } 16 | 17 | if (startIndex < str.size()) { ret.emplace_back(str.substr(startIndex)); } 18 | 19 | return ret; 20 | } 21 | } // namespace Luna 22 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/CommandPool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | class CommandPool { 8 | public: 9 | CommandPool(Device& device, uint32_t familyIndex, const std::string& debugName = ""); 10 | CommandPool(const CommandPool&) = delete; 11 | CommandPool(CommandPool&& other) noexcept; 12 | CommandPool& operator=(const CommandPool&) = delete; 13 | CommandPool& operator=(CommandPool&& other) noexcept; 14 | ~CommandPool() noexcept; 15 | 16 | void Begin(); 17 | vk::CommandBuffer RequestCommandBuffer(); 18 | void Trim(); 19 | 20 | private: 21 | Device& _device; 22 | vk::CommandPool _commandPool; 23 | std::string _debugName; 24 | std::vector _commandBuffers; 25 | uint32_t _commandBufferIndex = 0; 26 | }; 27 | } // namespace Vulkan 28 | } // namespace Luna 29 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/Fence.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | struct FenceDeleter { 8 | void operator()(Fence* fence); 9 | }; 10 | 11 | class Fence : public VulkanObject, public InternalSyncEnabled { 12 | friend struct FenceDeleter; 13 | friend class ObjectPool; 14 | 15 | public: 16 | ~Fence() noexcept; 17 | 18 | void Wait(); 19 | bool TryWait(uint64_t timeout); 20 | 21 | private: 22 | Fence(Device& device, vk::Fence fence); 23 | Fence(Device& device, vk::Semaphore timelineSemaphore, uint64_t timelineValue); 24 | 25 | Device& _device; 26 | vk::Fence _fence; 27 | vk::Semaphore _timelineSemaphore; 28 | uint64_t _timelineValue; 29 | bool _observedWait = false; 30 | std::mutex _mutex; 31 | }; 32 | } // namespace Vulkan 33 | } // namespace Luna 34 | -------------------------------------------------------------------------------- /Resources/Shaders/Common.glsli: -------------------------------------------------------------------------------- 1 | struct DispatchIndirectCommand { 2 | uint x; 3 | uint y; 4 | uint z; 5 | }; 6 | 7 | struct DrawIndexedIndirectCommand { 8 | uint IndexCount; 9 | uint InstanceCount; 10 | uint FirstIndex; 11 | int VertexOffset; 12 | uint FirstInstance; 13 | }; 14 | 15 | struct Vertex { 16 | vec3 Normal; 17 | vec4 Tangent; 18 | vec2 Texcoord0; 19 | vec2 Texcoord1; 20 | vec4 Color0; 21 | uvec4 Joints0; 22 | vec4 Weights0; 23 | }; 24 | 25 | struct SceneData { 26 | mat4 Projection; 27 | mat4 View; 28 | mat4 ViewProjection; 29 | vec4 CameraPosition; 30 | vec4 FrustumPlanes[6]; 31 | vec2 ViewportExtent; 32 | }; 33 | 34 | bool RectIntersectRect(vec2 bottomLeft0, vec2 topRight0, vec2 bottomLeft1, vec2 topRight1) { 35 | return !(any(lessThan(topRight0, bottomLeft1)) || any(greaterThan(bottomLeft0, topRight1))); 36 | } 37 | -------------------------------------------------------------------------------- /Resources/Shaders/VisBuffer.glsli: -------------------------------------------------------------------------------- 1 | #extension GL_EXT_shader_explicit_arithmetic_types : require 2 | 3 | #define MeshletIdBits 24u 4 | #define MeshletPrimitiveBits 8u 5 | #define MeshletIdMask ((1u << MeshletIdBits) - 1u) 6 | #define MeshletPrimitiveMask ((1u << MeshletPrimitiveBits) - 1u) 7 | 8 | #define CullMeshletFrustumBit (1 << 0) 9 | #define CullMeshletHiZBit (1 << 1) 10 | #define CullTriangleBackfaceBit (1 << 2) 11 | 12 | struct CullUniforms { 13 | uint Flags; 14 | uint MeshletCount; 15 | uint MeshletsPerBatch; 16 | uint IndicesPerBatch; 17 | }; 18 | 19 | struct Meshlet { 20 | uint VertexOffset; 21 | uint IndexOffset; 22 | uint TriangleOffset; 23 | uint IndexCount; 24 | uint TriangleCount; 25 | uint InstanceID; 26 | vec3 AABBMin; 27 | vec3 AABBMax; 28 | }; 29 | 30 | struct VisBufferStats { 31 | uint VisibleMeshlets; 32 | uint VisibleTriangles; 33 | }; 34 | -------------------------------------------------------------------------------- /Resources/Shaders/HzbReduce.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #include "Common.glsli" 4 | 5 | layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; 6 | 7 | layout(set = 0, binding = 0, r32f) uniform restrict readonly image2D HZBSrc; 8 | layout(set = 0, binding = 1, r32f) uniform restrict writeonly image2D HZBDst; 9 | 10 | void main() { 11 | const vec2 position = vec2(gl_GlobalInvocationID.xy); 12 | const vec2 hzbSrcSize = vec2(imageSize(HZBSrc)); 13 | const vec2 hzbDstSize = vec2(imageSize(HZBDst)); 14 | const vec2 scaledPos = position * (hzbSrcSize / hzbDstSize); 15 | const float[] depths = float[]( 16 | imageLoad(HZBSrc, ivec2(scaledPos + vec2(0.0, 0.0) + 0.5)).r, 17 | imageLoad(HZBSrc, ivec2(scaledPos + vec2(1.0, 0.0) + 0.5)).r, 18 | imageLoad(HZBSrc, ivec2(scaledPos + vec2(0.0, 1.0) + 0.5)).r, 19 | imageLoad(HZBSrc, ivec2(scaledPos + vec2(1.0, 1.0) + 0.5)).r 20 | ); 21 | const float depth = min(min(min(depths[0], depths[1]), depths[2]), depths[3]); 22 | imageStore(HZBDst, ivec2(position), vec4(depth)); 23 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Eearslya Sleiarion 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /Launcher/Launcher.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "CrashHandler.hpp" 4 | 5 | #ifdef _WIN32 6 | # define NOMINMAX 7 | # define WIN32_LEAN_AND_MEAN 8 | # include 9 | 10 | extern "C" { 11 | __declspec(dllexport) DWORD NvOptimusEnablement = 1; 12 | __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; 13 | } 14 | #endif 15 | 16 | int main(int argc, const char** argv) { 17 | if (!CrashHandler::Initialize()) { return -1; } 18 | 19 | int returnValue = 0; 20 | try { 21 | if (!Luna::Engine::Initialize()) { return -1; } 22 | returnValue = Luna::Engine::Run(); 23 | Luna::Engine::Shutdown(); 24 | } catch (const std::exception& e) { 25 | fprintf(stderr, "[Luna] =================================\n"); 26 | fprintf(stderr, "[Luna] === FATAL UNHANDLED EXCEPTION ===\n"); 27 | fprintf(stderr, "[Luna] =================================\n"); 28 | fprintf(stderr, "[Luna] Exception Message: %s\n", e.what()); 29 | 30 | fflush(stderr); 31 | 32 | returnValue = -1; 33 | } 34 | 35 | CrashHandler::Shutdown(); 36 | 37 | return returnValue; 38 | } 39 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Core/OSFilesystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | class OSFilesystem : public FilesystemBackend { 7 | public: 8 | OSFilesystem(const Path& base); 9 | ~OSFilesystem() noexcept; 10 | 11 | virtual std::filesystem::path GetFilesystemPath(const Path& path) const override; 12 | virtual bool MoveReplace(const Path& dst, const Path& src) override; 13 | virtual bool MoveYield(const Path& dst, const Path& src) override; 14 | virtual bool Remove(const Path& path) override; 15 | 16 | virtual int GetWatchFD() const override; 17 | virtual std::vector List(const Path& path) override; 18 | virtual FileHandle Open(const Path& path, FileMode mode = FileMode::ReadOnly) override; 19 | virtual bool Stat(const Path& path, FileStat& stat) const override; 20 | virtual void UnwatchFile(FileNotifyHandle handle) override; 21 | virtual void Update() override; 22 | virtual FileNotifyHandle WatchFile(const Path& path, std::function func) override; 23 | 24 | protected: 25 | std::filesystem::path _basePath; 26 | std::unique_ptr _data; 27 | }; 28 | } // namespace Luna 29 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Renderer/ShaderCompiler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace Luna { 9 | class ShaderCompiler { 10 | public: 11 | const std::vector& GetDependencies() const { 12 | return _dependencies; 13 | } 14 | Hash GetSourceHash() const { 15 | return _sourceHash; 16 | } 17 | 18 | std::vector Compile(std::string& error, const std::vector>& defines = {}); 19 | bool Preprocess(); 20 | void SetIncludeDirectories(const std::vector& includeDirs); 21 | void SetSourceFromFile(const Path& path, Vulkan::ShaderStage stage); 22 | 23 | private: 24 | bool Parse(const Path& sourcePath, const std::string& source); 25 | std::pair ResolveInclude(const Path& sourcePath, const std::string& includePath); 26 | 27 | std::string _source; 28 | Path _sourcePath; 29 | Hash _sourceHash; 30 | Vulkan::ShaderStage _stage; 31 | std::vector _includeDirs; 32 | 33 | std::vector _dependencies; 34 | std::string _processedSource; 35 | }; 36 | } // namespace Luna 37 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/Context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | class Context : public VulkanObject { 8 | friend class Device; 9 | 10 | public: 11 | Context(const std::vector& instanceExtensions = {}, 12 | const std::vector& deviceExtensions = {}); 13 | ~Context() noexcept; 14 | 15 | private: 16 | static constexpr uint32_t TargetVulkanVersion = VK_API_VERSION_1_3; 17 | 18 | void CreateInstance(const std::vector& requiredExtensions); 19 | void SelectPhysicalDevice(const std::vector& requiredExtensions); 20 | void CreateDevice(const std::vector& requiredExtensions); 21 | 22 | #ifdef LUNA_VULKAN_DEBUG 23 | void DumpInstanceInfo() const; 24 | void DumpDeviceInfo() const; 25 | #endif 26 | 27 | vk::DynamicLoader _loader; 28 | Extensions _extensions; 29 | vk::Instance _instance; 30 | #ifdef LUNA_VULKAN_DEBUG 31 | vk::DebugUtilsMessengerEXT _debugMessenger; 32 | #endif 33 | DeviceInfo _deviceInfo; 34 | QueueInfo _queueInfo; 35 | vk::Device _device; 36 | }; 37 | } // namespace Vulkan 38 | } // namespace Luna 39 | -------------------------------------------------------------------------------- /Resources/Shaders/StaticMesh.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) flat in uint inMeshlet; 4 | layout(location = 1) in vec3 inNormal; 5 | 6 | layout(location = 0) out vec4 outColor; 7 | 8 | vec3 hueShift(vec3 color, float hueAdjust) { 9 | const vec3 kRGBToYPrime = vec3(0.299, 0.587, 0.114); 10 | const vec3 kRGBToI = vec3(0.596, -0.275, -0.321); 11 | const vec3 kRGBToQ = vec3(0.212, -0.523, 0.311); 12 | 13 | const vec3 kYIQToR = vec3(1.0, 0.956, 0.621); 14 | const vec3 kYIQToG = vec3(1.0, -0.272, -0.647); 15 | const vec3 kYIQToB = vec3(1.0, -1.107, 1.704); 16 | 17 | float YPrime = dot(color, kRGBToYPrime); 18 | float I = dot(color, kRGBToI); 19 | float Q = dot(color, kRGBToQ); 20 | float hue = atan(Q, I); 21 | float chroma = sqrt(I * I + Q * Q); 22 | 23 | hue += hueAdjust; 24 | 25 | Q = chroma * sin(hue); 26 | I = chroma * cos(hue); 27 | 28 | vec3 yIQ = vec3(YPrime, I, Q); 29 | 30 | return vec3(dot(yIQ, kYIQToR), dot(yIQ, kYIQToG), dot(yIQ, kYIQToB)); 31 | } 32 | 33 | void main() { 34 | vec3 color = hueShift(vec3(0.9, 0.0, 0.0), 0.84 * inMeshlet); 35 | color = inNormal * 0.5 + 0.5; 36 | outColor = vec4(color, 1.0); 37 | } 38 | -------------------------------------------------------------------------------- /Luna/Source/Utility/Timer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef _WIN32 4 | # define WIN32_LEAN_AND_MEAN 5 | # define NOMINMAX 6 | # include 7 | #else 8 | # include 9 | #endif 10 | 11 | namespace Luna { 12 | #ifdef _WIN32 13 | static struct QPCFrequency { 14 | QPCFrequency() { 15 | LARGE_INTEGER frequency; 16 | ::QueryPerformanceFrequency(&frequency); 17 | InverseFrequency = 1e9 / double(frequency.QuadPart); 18 | } 19 | 20 | double InverseFrequency = 0.0; 21 | } StaticQPCFrequency; 22 | #endif 23 | 24 | int64_t GetCurrentTimeNanoseconds() { 25 | #ifdef _WIN32 26 | LARGE_INTEGER li; 27 | if (!::QueryPerformanceCounter(&li)) { return 0; } 28 | 29 | return int64_t(double(li.QuadPart) * StaticQPCFrequency.InverseFrequency); 30 | #else 31 | struct timespec ts = {}; 32 | constexpr auto timeBase = CLOCK_MONOTONIC_RAW; 33 | if (clock_gettime(timebase, &ts) < 0) { return 0; } 34 | 35 | return ts.tv_sec * 1000000000ll + ts.tv_nsec; 36 | #endif 37 | } 38 | 39 | void Timer::Start() { 40 | _t = GetCurrentTimeNanoseconds(); 41 | } 42 | 43 | double Timer::End() const { 44 | const auto now = GetCurrentTimeNanoseconds(); 45 | 46 | return double(now - _t) * 1e-9; 47 | } 48 | } // namespace Luna 49 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/Format.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace Luna { 5 | namespace Vulkan { 6 | void FormatAlignDimensions(vk::Format format, uint32_t& width, uint32_t& height) {} 7 | 8 | vk::ImageAspectFlags FormatAspectFlags(vk::Format format) { 9 | switch (format) { 10 | case vk::Format::eUndefined: 11 | return {}; 12 | 13 | case vk::Format::eS8Uint: 14 | return vk::ImageAspectFlagBits::eStencil; 15 | 16 | case vk::Format::eD16Unorm: 17 | case vk::Format::eD32Sfloat: 18 | case vk::Format::eX8D24UnormPack32: 19 | return vk::ImageAspectFlagBits::eDepth; 20 | 21 | case vk::Format::eD16UnormS8Uint: 22 | case vk::Format::eD24UnormS8Uint: 23 | case vk::Format::eD32SfloatS8Uint: 24 | return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil; 25 | 26 | default: 27 | return vk::ImageAspectFlagBits::eColor; 28 | } 29 | } 30 | 31 | void FormatBlockCount(vk::Format& format, uint32_t& width, uint32_t& height) {} 32 | 33 | int FormatChannelCount(vk::Format format) { 34 | return vk::componentCount(format); 35 | } 36 | 37 | bool FormatIsSrgb(vk::Format format) { 38 | const char* comp = vk::componentName(format, 0); 39 | 40 | return strcmp(comp, "SRGB") == 0; 41 | } 42 | } // namespace Vulkan 43 | } // namespace Luna 44 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/Format.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | void FormatAlignDimensions(vk::Format format, uint32_t& width, uint32_t& height); 8 | vk::ImageAspectFlags FormatAspectFlags(vk::Format format); 9 | void FormatBlockCount(vk::Format& format, uint32_t& width, uint32_t& height); 10 | int FormatChannelCount(vk::Format format); 11 | bool FormatIsSrgb(vk::Format format); 12 | 13 | constexpr bool FormatHasDepth(vk::Format format) { 14 | switch (format) { 15 | case vk::Format::eD16Unorm: 16 | case vk::Format::eD16UnormS8Uint: 17 | case vk::Format::eD24UnormS8Uint: 18 | case vk::Format::eD32Sfloat: 19 | case vk::Format::eX8D24UnormPack32: 20 | case vk::Format::eD32SfloatS8Uint: 21 | return true; 22 | 23 | default: 24 | return false; 25 | } 26 | } 27 | 28 | constexpr bool FormatHasStencil(vk::Format format) { 29 | switch (format) { 30 | case vk::Format::eS8Uint: 31 | case vk::Format::eD16UnormS8Uint: 32 | case vk::Format::eD24UnormS8Uint: 33 | case vk::Format::eD32SfloatS8Uint: 34 | return true; 35 | 36 | default: 37 | return false; 38 | } 39 | } 40 | 41 | constexpr bool FormatHasDepthOrStencil(vk::Format format) { 42 | return FormatHasDepth(format) || FormatHasStencil(format); 43 | } 44 | } // namespace Vulkan 45 | } // namespace Luna 46 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Renderer/Enums.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Luna { 7 | enum class AttachmentInfoFlagBits { 8 | Persistent = 1 << 0, 9 | UnormSrgbAlias = 1 << 1, 10 | SupportsPrerotate = 1 << 2, 11 | GenerateMips = 1 << 3, 12 | CreateMipViews = 1 << 4, 13 | InternalTransient = 1 << 16, 14 | InternalProxy = 1 << 17, 15 | }; 16 | using AttachmentInfoFlags = Bitmask; 17 | 18 | enum class RenderGraphQueueFlagBits { 19 | Graphics = 1 << 0, 20 | Compute = 1 << 1, 21 | AsyncCompute = 1 << 2, 22 | AsyncGraphics = 1 << 3 23 | }; 24 | using RenderGraphQueueFlags = Bitmask; 25 | 26 | /** 27 | * Used to describe the size of an image attachment. 28 | */ 29 | enum class SizeClass { 30 | Absolute, /** Size is taken as an absolute pixel size. */ 31 | SwapchainRelative, /** Size is taken as a multiplier to swapchain size, such that 1.0 is equal to swapchain size. */ 32 | InputRelative /** Size is taken as a multiplier to another named image attachment's size. */ 33 | }; 34 | } // namespace Luna 35 | 36 | template <> 37 | struct Luna::EnableBitmaskOperators : std::true_type {}; 38 | template <> 39 | struct Luna::EnableBitmaskOperators : std::true_type {}; 40 | -------------------------------------------------------------------------------- /Luna/Source/Utility/Memory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #ifdef _WIN32 6 | # include 7 | #endif 8 | 9 | namespace Luna { 10 | void* AllocateAligned(std::size_t size, std::size_t alignment) { 11 | #if defined(_WIN32) 12 | return _aligned_malloc(size, alignment); 13 | #elif defined(_ISOC11_SOURCE) 14 | return aligned_alloc(alignment, (size + alignment - 1) & ~(alignment - 1)); 15 | #elif (_POSIX_C_SOURCE >= 200112L) || (_XOPEN_SOURCE >= 600) 16 | void* ptr = nullptr; 17 | if (posix_memalign(&ptr, alignment, size) < 0) { return nullptr; } 18 | return ptr; 19 | #else 20 | void** place; 21 | uintptr_t addr = 0; 22 | void* ptr = malloc(alignment + size + sizeof(uintptr_t)); 23 | 24 | if (ptr == nullptr) { return nullptr; } 25 | 26 | addr = (reinterpret_cast(ptr) + sizeof(uintptr_t) + alignment) & ~(alignment - 1); 27 | place = reinterpret_cast(addr); 28 | place[-1] = ptr; 29 | 30 | return reinterpret_cast(addr); 31 | #endif 32 | } 33 | 34 | void FreeAligned(void* ptr) { 35 | #if defined(_WIN32) 36 | _aligned_free(ptr); 37 | #elif !defined(_ISOC11_SOURCE) && !((_POSIX_C_SOURCE >= 200112L) || (_XOPEN_SOURCE >= 600)) 38 | if (ptr != nullptr) { 39 | void** p = reinterpret_cast(ptr); 40 | free(p[-1]); 41 | } 42 | #else 43 | free(ptr); 44 | #endif 45 | } 46 | 47 | void AlignedDeleter::operator()(void* ptr) { 48 | FreeAligned(ptr); 49 | } 50 | } // namespace Luna 51 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | Language: Cpp 3 | 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: true 6 | AlignConsecutiveMacros: true 7 | AlignEscapedNewlines: Left 8 | AlignOperands: Align 9 | AlignTrailingComments: true 10 | AllowShortBlocksOnASingleLine: Always 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortEnumsOnASingleLine: true 13 | AllowShortFunctionsOnASingleLine: Empty 14 | AllowShortIfStatementsOnASingleLine: WithoutElse 15 | AllowShortLoopsOnASingleLine: true 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: false 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BreakBeforeBraces: Attach 21 | BreakBeforeTernaryOperators: true 22 | BreakStringLiterals: true 23 | ColumnLimit: 120 24 | ContinuationIndentWidth: 2 25 | DeriveLineEnding: false 26 | DerivePointerAlignment: false 27 | IndentCaseBlocks: false 28 | IndentCaseLabels: true 29 | IndentGotoLabels: false 30 | IndentPPDirectives: AfterHash 31 | IndentWidth: 2 32 | MaxEmptyLinesToKeep: 1 33 | PenaltyReturnTypeOnItsOwnLine: 10000 34 | PointerAlignment: Left 35 | ReflowComments: true 36 | SortIncludes: true 37 | SpaceAfterCStyleCast: true 38 | SpaceAfterLogicalNot: false 39 | SpaceBeforeAssignmentOperators: true 40 | SpaceBeforeParens: ControlStatementsExceptForEachMacros 41 | SpaceBeforeSquareBrackets: false 42 | SpaceInEmptyBlock: false 43 | SpaceInEmptyParentheses: false 44 | SpacesBeforeTrailingComments: 2 45 | SpacesInCStyleCastParentheses: false 46 | SpacesInConditionalStatement: false 47 | SpacesInContainerLiterals: false 48 | SpacesInParentheses: false 49 | SpacesInSquareBrackets: false 50 | TabWidth: 2 51 | UseCRLF: false 52 | UseTab: AlignWithSpaces 53 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Renderer/Swapchain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | class Window; 7 | class Swapchain : public IntrusivePtrEnabled { 8 | friend class Vulkan::Device; 9 | 10 | public: 11 | Swapchain(Window& window); 12 | ~Swapchain() noexcept; 13 | 14 | [[nodiscard]] vk::ColorSpaceKHR GetColorSpace() const noexcept { 15 | return _format.colorSpace; 16 | } 17 | [[nodiscard]] const vk::Extent2D GetExtent() const noexcept { 18 | return _extent; 19 | } 20 | [[nodiscard]] vk::Format GetFormat() const noexcept { 21 | return _format.format; 22 | } 23 | [[nodiscard]] Hash GetHash() const noexcept { 24 | return _swapchainHash; 25 | } 26 | [[nodiscard]] uint32_t GetImageCount() const noexcept { 27 | return uint32_t(_images.size()); 28 | } 29 | [[nodiscard]] vk::PresentModeKHR GetPresentMode() const noexcept { 30 | return _presentMode; 31 | } 32 | [[nodiscard]] const vk::SurfaceFormatKHR& GetSurfaceFormat() const noexcept { 33 | return _format; 34 | } 35 | 36 | bool Acquire(); 37 | void Present(); 38 | 39 | private: 40 | static constexpr uint32_t NotAcquired = std::numeric_limits::max(); 41 | 42 | void Recreate(); 43 | 44 | Window* _window = nullptr; 45 | vk::SurfaceKHR _surface; 46 | vk::SwapchainKHR _swapchain; 47 | vk::Extent2D _extent; 48 | vk::SurfaceFormatKHR _format; 49 | vk::PresentModeKHR _presentMode; 50 | uint32_t _acquired = NotAcquired; 51 | std::vector _images; 52 | std::vector _release; 53 | bool _suboptimal = false; 54 | 55 | Hash _swapchainHash; 56 | }; 57 | } // namespace Luna 58 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/ObjectPool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Luna { 7 | template 8 | class ObjectPool { 9 | public: 10 | template 11 | T* Allocate(Args&&... args) { 12 | if (_available.empty()) { 13 | std::size_t newObjects = std::size_t(64) << _memory.size(); 14 | T* ptr = static_cast(AllocateAligned(newObjects * sizeof(T), std::max(64u, alignof(T)))); 15 | if (!ptr) { return nullptr; } 16 | 17 | for (std::size_t i = 0; i < newObjects; ++i) { _available.push_back(&ptr[i]); } 18 | _memory.emplace_back(ptr); 19 | } 20 | 21 | T* ptr = _available.back(); 22 | _available.pop_back(); 23 | try { 24 | new (ptr) T(std::forward(args)...); 25 | } catch (const std::exception& e) { 26 | _available.push_back(ptr); 27 | throw; 28 | } 29 | 30 | return ptr; 31 | } 32 | 33 | void Clear() { 34 | _available.clear(); 35 | _memory.clear(); 36 | } 37 | 38 | void Free(T* ptr) { 39 | ptr->~T(); 40 | _available.push_back(ptr); 41 | } 42 | 43 | protected: 44 | std::vector _available; 45 | std::vector> _memory; 46 | }; 47 | 48 | template 49 | class ThreadSafeObjectPool : private ObjectPool { 50 | public: 51 | template 52 | T* Allocate(Args&&... args) { 53 | std::lock_guard lock(_mutex); 54 | return ObjectPool::Allocate(std::forward(args)...); 55 | } 56 | 57 | void Clear() { 58 | std::lock_guard lock(_mutex); 59 | ObjectPool::Clear(); 60 | } 61 | 62 | void Free(T* ptr) { 63 | ptr->~T(); 64 | std::lock_guard lock(_mutex); 65 | this->_available.push_back(ptr); 66 | } 67 | 68 | private: 69 | std::mutex _mutex; 70 | }; 71 | } // namespace Luna 72 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Core/Window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct GLFWwindow; 8 | using VkInstance = struct VkInstance_T*; 9 | using VkSurfaceKHR = struct VkSurfaceKHR_T*; 10 | 11 | namespace Luna { 12 | class Swapchain; 13 | 14 | class Window final { 15 | public: 16 | Window(const std::string& title, int width, int height, bool show = true); 17 | Window(const Window&) = delete; 18 | Window& operator=(const Window&) noexcept = delete; 19 | ~Window() noexcept; 20 | 21 | [[nodiscard]] VkSurfaceKHR CreateSurface(VkInstance instance) const; 22 | [[nodiscard]] glm::ivec2 GetFramebufferSize() const; 23 | [[nodiscard]] GLFWwindow* GetHandle() const; 24 | [[nodiscard]] glm::ivec2 GetPosition() const; 25 | [[nodiscard]] Swapchain& GetSwapchain(); 26 | [[nodiscard]] const Swapchain& GetSwapchain() const; 27 | [[nodiscard]] Hash GetSwapchainHash() const noexcept; 28 | [[nodiscard]] glm::ivec2 GetWindowSize() const; 29 | [[nodiscard]] bool IsCloseRequested() const; 30 | [[nodiscard]] bool IsFocused() const; 31 | [[nodiscard]] bool IsMaximized() const; 32 | [[nodiscard]] bool IsMinimized() const; 33 | 34 | void CenterPosition(); 35 | void Close(); 36 | void Hide(); 37 | void Maximize(); 38 | void Minimize(); 39 | void Restore(); 40 | void SetCursor(MouseCursor cursor); 41 | void SetPosition(int x, int y); 42 | void SetPosition(const glm::ivec2& pos); 43 | void SetSize(int w, int h); 44 | void SetSize(const glm::ivec2& size); 45 | void SetTitle(const std::string& title); 46 | void Show(); 47 | 48 | private: 49 | GLFWwindow* _window = nullptr; 50 | MouseCursor _cursor = MouseCursor::Arrow; 51 | IntrusivePtr _swapchain; 52 | }; 53 | } // namespace Luna 54 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/Hash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace vk { 6 | template 7 | class Flags; 8 | } 9 | 10 | namespace Luna { 11 | using Hash = std::uint64_t; 12 | 13 | /** 14 | * Helper class to generate a hash using any hashable data type. 15 | */ 16 | class Hasher { 17 | public: 18 | Hasher() = default; 19 | explicit Hasher(Hash h) : _hash(h) {} 20 | template 21 | Hasher(const T& data) { 22 | operator()(data); 23 | } 24 | 25 | /** Get the computed hash. */ 26 | Hash Get() const { 27 | return _hash; 28 | } 29 | 30 | /** 31 | * Hash the given block of data. 32 | * 33 | * Hash is computed by splitting the data into 64-bit chunks and hashing each chunk as if it were a 64-bit integer, 34 | * followed by hashing the remaining bytes individually. 35 | */ 36 | void Data(size_t size, const void* bytes) { 37 | const size_t chunks = size / sizeof(uint64_t); 38 | 39 | const uint64_t* p64 = reinterpret_cast(bytes); 40 | for (size_t i = 0; i < chunks; ++i) { operator()(p64[i]); } 41 | 42 | const uint8_t* p8 = reinterpret_cast(bytes); 43 | for (size_t i = (chunks * sizeof(uint64_t)); i < size; ++i) { operator()(p8[i]); } 44 | } 45 | 46 | /** Hash the given object. Must have an std::hash specialization. */ 47 | template 48 | void operator()(const T& data) { 49 | const std::size_t hash = std::hash{}(data); 50 | _hash = (_hash * 0x100000001b3ull) ^ (hash & 0xffffffffu); 51 | _hash = (_hash * 0x100000001b3ull) ^ (hash >> 32); 52 | } 53 | 54 | /** Hash the given Vulkan flags. */ 55 | template 56 | void operator()(const vk::Flags& flags) { 57 | operator()(static_cast::MaskType>(flags)); 58 | } 59 | 60 | private: 61 | Hash _hash = 0xcbf29ce484222325ull; 62 | }; 63 | } // namespace Luna 64 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Renderer/Camera.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | class Camera { 7 | public: 8 | Camera() noexcept; 9 | 10 | [[nodiscard]] float GetFovDegrees() const noexcept { 11 | return _fovDegrees; 12 | } 13 | [[nodiscard]] const glm::mat4& GetProjection() const noexcept { 14 | return _projection; 15 | } 16 | [[nodiscard]] float GetZFar() const noexcept { 17 | return _zFar; 18 | } 19 | [[nodiscard]] float GetZNear() const noexcept { 20 | return _zNear; 21 | } 22 | 23 | void SetPerspective(float fovDegrees, float zNear, float zFar) noexcept; 24 | void SetViewport(float width, float height) noexcept; 25 | 26 | private: 27 | void UpdateProjection() noexcept; 28 | 29 | float _aspectRatio = 1.0f; 30 | float _fovDegrees = 60.0f; 31 | glm::mat4 _projection = glm::mat4(1.0f); 32 | float _zNear = 0.01f; 33 | float _zFar = 100.0f; 34 | }; 35 | 36 | class EditorCamera : public Camera { 37 | public: 38 | [[nodiscard]] float GetPitch() const noexcept { 39 | return _pitch; 40 | } 41 | [[nodiscard]] const glm::vec3& GetPosition() const noexcept { 42 | return _position; 43 | } 44 | [[nodiscard]] float GetYaw() const noexcept { 45 | return _yaw; 46 | } 47 | 48 | [[nodiscard]] glm::vec3 GetForward() const noexcept; 49 | [[nodiscard]] glm::vec3 GetRight() const noexcept; 50 | [[nodiscard]] glm::vec3 GetUp() const noexcept; 51 | [[nodiscard]] glm::mat4 GetView() const noexcept; 52 | 53 | void Move(const glm::vec3& direction) noexcept; 54 | void Rotate(float pitchDelta, float yawDelta) noexcept; 55 | void SetPosition(const glm::vec3& position) noexcept; 56 | void SetRotation(float pitch, float yaw) noexcept; 57 | void Translate(const glm::vec3& translate) noexcept; 58 | 59 | private: 60 | glm::vec3 _position = glm::vec3(0.0f); 61 | float _pitch = 0.0f; 62 | float _yaw = -90.0f; 63 | }; 64 | } // namespace Luna 65 | -------------------------------------------------------------------------------- /Resources/Shaders/StaticMesh.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_EXT_scalar_block_layout : require 3 | 4 | #include "Common.glsli" 5 | #include "VisBuffer.glsli" 6 | 7 | layout(set = 0, binding = 0, scalar) uniform SceneBuffer { 8 | SceneData Scene; 9 | }; 10 | 11 | layout(set = 1, binding = 1, scalar) restrict readonly buffer MeshletBuffer { 12 | Meshlet Meshlets[]; 13 | }; 14 | layout(set = 1, binding = 2, scalar) readonly buffer VertexPositions { 15 | vec3 Positions[]; 16 | }; 17 | layout(set = 1, binding = 3, scalar) readonly buffer VertexAttributes { 18 | Vertex Attributes[]; 19 | }; 20 | layout(set = 1, binding = 4, scalar) readonly buffer MeshletIndices { 21 | uint Indices[]; 22 | }; 23 | layout(set = 1, binding = 5, scalar) readonly buffer MeshletPrimitives { 24 | uint8_t Triangles[]; 25 | }; 26 | layout(set = 1, binding = 6, scalar) readonly buffer TransformBuffer { 27 | mat4 Transforms[]; 28 | }; 29 | 30 | layout(location = 0) flat out uint outMeshlet; 31 | layout(location = 1) out vec3 outNormal; 32 | 33 | void main() { 34 | const uint meshletId = (uint(gl_VertexIndex) >> MeshletPrimitiveBits) & MeshletIdMask; 35 | const uint primitiveId = uint(gl_VertexIndex) & MeshletPrimitiveMask; 36 | 37 | const uint vertexOffset = Meshlets[meshletId].VertexOffset; 38 | const uint indexOffset = Meshlets[meshletId].IndexOffset; 39 | const uint triangleOffset = Meshlets[meshletId].TriangleOffset; 40 | const uint instanceId = Meshlets[meshletId].InstanceID; 41 | 42 | const uint primitive = uint(Triangles[triangleOffset + primitiveId]); 43 | const uint index = Indices[indexOffset + primitive]; 44 | const vec3 position = Positions[vertexOffset + index]; 45 | const Vertex vertex = Attributes[vertexOffset + index]; 46 | const mat4 transform = Transforms[instanceId]; 47 | 48 | outMeshlet = meshletId; 49 | outNormal = vertex.Normal; 50 | gl_Position = Scene.ViewProjection * transform * vec4(position, 1.0); 51 | } 52 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/Sampler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace Luna { 5 | namespace Vulkan { 6 | void SamplerDeleter::operator()(Sampler* sampler) { 7 | sampler->_device._samplerPool.Free(sampler); 8 | } 9 | 10 | Sampler::Sampler(Device& device, const SamplerCreateInfo& info, bool immutable) 11 | : Cookie(device), _device(device), _createInfo(info), _immutable(immutable) { 12 | const vk::SamplerReductionModeCreateInfo reduction(info.ReductionMode); 13 | 14 | const vk::SamplerCreateInfo samplerCI({}, 15 | info.MagFilter, 16 | info.MinFilter, 17 | info.MipmapMode, 18 | info.AddressModeU, 19 | info.AddressModeV, 20 | info.AddressModeW, 21 | info.MipLodBias, 22 | info.AnisotropyEnable, 23 | info.MaxAnisotropy, 24 | info.CompareEnable, 25 | info.CompareOp, 26 | info.MinLod, 27 | info.MaxLod, 28 | info.BorderColor, 29 | info.UnnormalizedCoordinates); 30 | 31 | const vk::StructureChain chain(samplerCI, reduction); 32 | _sampler = _device.GetDevice().createSampler(chain.get()); 33 | 34 | Log::Trace("Vulkan", "{} created.", _immutable ? "Immutable Sampler" : "Sampler"); 35 | } 36 | 37 | Sampler::~Sampler() noexcept { 38 | if (_sampler) { 39 | if (_immutable) { 40 | _device.GetDevice().destroySampler(_sampler); 41 | } else if (_internalSync) { 42 | _device.DestroySamplerNoLock(_sampler); 43 | } else { 44 | _device.DestroySampler(_sampler); 45 | } 46 | } 47 | } 48 | 49 | ImmutableSampler::ImmutableSampler(Hash hash, Device& device, const SamplerCreateInfo& samplerCI) 50 | : HashedObject(hash), _device(device) { 51 | _sampler = _device.CreateSampler(samplerCI); 52 | } 53 | } // namespace Vulkan 54 | } // namespace Luna 55 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/QueryPool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | struct QueryResultDeleter { 8 | void operator()(QueryResult* result); 9 | }; 10 | 11 | class QueryResult : public VulkanObject { 12 | friend class ObjectPool; 13 | friend struct QueryResultDeleter; 14 | 15 | public: 16 | [[nodiscard]] uint64_t GetTimestampTicks() const noexcept { 17 | return _timestampTicks; 18 | } 19 | [[nodiscard]] bool IsDeviceTimebase() const noexcept { 20 | return _deviceTimebase; 21 | } 22 | [[nodiscard]] bool IsSignalled() const noexcept { 23 | return _hasTimestamp; 24 | } 25 | 26 | void SignalTimestampTicks(uint64_t ticks) noexcept; 27 | 28 | private: 29 | QueryResult(Device& device, bool deviceTimebase); 30 | 31 | Device& _device; 32 | uint64_t _timestampTicks = 0; 33 | bool _hasTimestamp = false; 34 | bool _deviceTimebase = false; 35 | }; 36 | 37 | class QueryPool { 38 | public: 39 | QueryPool(Device& device); 40 | ~QueryPool() noexcept; 41 | 42 | void Begin(); 43 | QueryResultHandle WriteTimestamp(vk::CommandBuffer cmd, vk::PipelineStageFlags2 stages); 44 | 45 | private: 46 | struct Pool { 47 | vk::QueryPool Pool; 48 | std::vector Results; 49 | std::vector Cookies; 50 | uint32_t Index = 0; 51 | uint32_t Size = 0; 52 | }; 53 | 54 | void AddPool(); 55 | 56 | Device& _device; 57 | std::vector _pools; 58 | uint32_t _poolIndex = 0; 59 | bool _supportsTimestamp = false; 60 | }; 61 | 62 | class TimestampInterval : public IntrusiveHashMapEnabled { 63 | public: 64 | TimestampInterval(const std::string& name); 65 | 66 | [[nodiscard]] const std::string& GetName() const noexcept { 67 | return _name; 68 | } 69 | [[nodiscard]] uint64_t GetTotalAccumulations() const noexcept { 70 | return _totalAccumulations; 71 | } 72 | [[nodiscard]] double GetTotalTime() const noexcept { 73 | return _totalTime; 74 | } 75 | 76 | [[nodiscard]] double GetTimePerAccumulation() const noexcept; 77 | 78 | void AccumulateTime(double t) noexcept; 79 | void Reset() noexcept; 80 | 81 | private: 82 | std::string _name; 83 | double _totalTime = 0.0; 84 | uint64_t _totalAccumulations = 0; 85 | }; 86 | } // namespace Vulkan 87 | } // namespace Luna 88 | -------------------------------------------------------------------------------- /Luna/Source/Core/Log.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | namespace Luna { 8 | bool Log::Initialize() { 9 | spdlog::init_thread_pool(8192, 1); 10 | 11 | const std::filesystem::path logsDirectory = "Logs"; 12 | if (!std::filesystem::exists(logsDirectory)) { std::filesystem::create_directories(logsDirectory); } 13 | const auto logFile = logsDirectory / "Luna.log"; 14 | 15 | auto consoleSink = std::make_shared(); 16 | auto fileSink = std::make_shared(logFile.string(), true); 17 | 18 | consoleSink->set_pattern("%^[%T] %n-%L: %v%$"); 19 | fileSink->set_pattern("[%T] %n-%L: %v"); 20 | 21 | std::vector sinks{consoleSink, fileSink}; 22 | auto logger = std::make_shared( 23 | "Luna", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block); 24 | spdlog::set_default_logger(logger); 25 | 26 | return true; 27 | } 28 | 29 | void Log::Shutdown() { 30 | spdlog::shutdown(); 31 | } 32 | 33 | Log::Level Log::GetLevel() { 34 | return FromSpdlog(spdlog::get_level()); 35 | } 36 | 37 | void Log::SetLevel(Level level) { 38 | spdlog::set_level(ToSpdlog(level)); 39 | } 40 | 41 | Log::Level Log::FromSpdlog(spdlog::level::level_enum level) { 42 | switch (level) { 43 | case spdlog::level::critical: 44 | return Level::Fatal; 45 | case spdlog::level::err: 46 | return Level::Error; 47 | case spdlog::level::warn: 48 | return Level::Warning; 49 | case spdlog::level::info: 50 | return Level::Info; 51 | case spdlog::level::debug: 52 | return Level::Debug; 53 | case spdlog::level::trace: 54 | return Level::Trace; 55 | default: 56 | return Level::Info; 57 | } 58 | } 59 | 60 | spdlog::level::level_enum Log::ToSpdlog(Level level) { 61 | switch (level) { 62 | case Level::Fatal: 63 | return spdlog::level::critical; 64 | case Level::Error: 65 | return spdlog::level::err; 66 | case Level::Warning: 67 | return spdlog::level::warn; 68 | case Level::Info: 69 | return spdlog::level::info; 70 | case Level::Debug: 71 | return spdlog::level::debug; 72 | case Level::Trace: 73 | return spdlog::level::trace; 74 | } 75 | 76 | return spdlog::level::info; 77 | } 78 | } // namespace Luna 79 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Core/Log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Luna { 8 | class Log { 9 | public: 10 | enum class Level { Fatal, Error, Warning, Info, Debug, Trace }; 11 | 12 | static bool Initialize(); 13 | static void Shutdown(); 14 | 15 | static Level GetLevel(); 16 | static void SetLevel(Level level); 17 | 18 | template 19 | static void Assert(bool condition, std::string_view tag, const std::format_string& format, Args&&... args) { 20 | if (!condition) { 21 | Output(Level::Fatal, tag, format, std::forward(args)...); 22 | Shutdown(); 23 | std::exit(-1); 24 | } 25 | } 26 | template 27 | static void Fatal(std::string_view tag, const std::format_string& format, Args&&... args) { 28 | Output(Level::Fatal, tag, format, std::forward(args)...); 29 | } 30 | template 31 | static void Error(std::string_view tag, const std::format_string& format, Args&&... args) { 32 | Output(Level::Error, tag, format, std::forward(args)...); 33 | } 34 | template 35 | static void Warning(std::string_view tag, const std::format_string& format, Args&&... args) { 36 | Output(Level::Warning, tag, format, std::forward(args)...); 37 | } 38 | template 39 | static void Info(std::string_view tag, const std::format_string& format, Args&&... args) { 40 | Output(Level::Info, tag, format, std::forward(args)...); 41 | } 42 | template 43 | static void Debug(std::string_view tag, const std::format_string& format, Args&&... args) { 44 | Output(Level::Debug, tag, format, std::forward(args)...); 45 | } 46 | template 47 | static void Trace(std::string_view tag, const std::format_string& format, Args&&... args) { 48 | Output(Level::Trace, tag, format, std::forward(args)...); 49 | } 50 | 51 | private: 52 | static Level FromSpdlog(spdlog::level::level_enum level); 53 | static spdlog::level::level_enum ToSpdlog(Level level); 54 | 55 | template 56 | static void Output(Level level, std::string_view tag, const std::format_string& format, Args&&... args) { 57 | const auto logMessage = std::format(format, std::forward(args)...); 58 | spdlog::log(ToSpdlog(level), "[{}] {}", tag, logMessage); 59 | } 60 | }; 61 | } // namespace Luna 62 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/Fence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace Luna { 5 | namespace Vulkan { 6 | void FenceDeleter::operator()(Fence* fence) { 7 | fence->_device._fencePool.Free(fence); 8 | } 9 | 10 | Fence::Fence(Device& device, vk::Fence fence) 11 | : _device(device), _fence(fence), _timelineSemaphore(nullptr), _timelineValue(0) {} 12 | 13 | Fence::Fence(Device& device, vk::Semaphore timelineSemaphore, uint64_t timelineValue) 14 | : _device(device), _fence(nullptr), _timelineSemaphore(timelineSemaphore), _timelineValue(timelineValue) {} 15 | 16 | Fence::~Fence() noexcept { 17 | if (_fence) { 18 | if (_internalSync) { 19 | _device.ResetFenceNoLock(_fence, _observedWait); 20 | } else { 21 | _device.ResetFence(_fence, _observedWait); 22 | } 23 | } 24 | } 25 | 26 | void Fence::Wait() { 27 | if (_observedWait) { return; } 28 | 29 | std::lock_guard lock(_mutex); 30 | if (_timelineValue) { 31 | const vk::SemaphoreWaitInfo waitInfo({}, _timelineSemaphore, _timelineValue); 32 | const auto waitResult = _device._device.waitSemaphores(waitInfo, std::numeric_limits::max()); 33 | if (waitResult == vk::Result::eSuccess) { 34 | _observedWait = true; 35 | } else { 36 | Log::Error("Vulkan", "Failed to wait on Timeline Semaphore"); 37 | } 38 | } else { 39 | const auto waitResult = _device._device.waitForFences(_fence, VK_TRUE, std::numeric_limits::max()); 40 | if (waitResult == vk::Result::eSuccess) { 41 | _observedWait = true; 42 | } else { 43 | Log::Error("Vulkan", "Failed to wait on Fence"); 44 | } 45 | } 46 | } 47 | 48 | bool Fence::TryWait(uint64_t timeout) { 49 | if (_observedWait) { return true; } 50 | 51 | std::lock_guard lock(_mutex); 52 | if (_timelineValue) { 53 | const vk::SemaphoreWaitInfo waitInfo({}, _timelineSemaphore, _timelineValue); 54 | const auto waitResult = _device._device.waitSemaphores(waitInfo, std::numeric_limits::max()); 55 | if (waitResult == vk::Result::eSuccess) { 56 | _observedWait = true; 57 | 58 | return true; 59 | } else { 60 | return false; 61 | } 62 | } else { 63 | const auto waitResult = _device._device.waitForFences(_fence, VK_TRUE, std::numeric_limits::max()); 64 | if (waitResult == vk::Result::eSuccess) { 65 | _observedWait = true; 66 | 67 | return true; 68 | } else { 69 | return false; 70 | } 71 | } 72 | } 73 | } // namespace Vulkan 74 | } // namespace Luna 75 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/CommandPool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace Luna { 5 | namespace Vulkan { 6 | CommandPool::CommandPool(Device& device, uint32_t familyIndex, const std::string& debugName) 7 | : _device(device), _debugName(debugName) { 8 | const auto vkDevice = _device._device; 9 | const vk::CommandPoolCreateInfo poolCI(vk::CommandPoolCreateFlagBits::eTransient, familyIndex); 10 | _commandPool = vkDevice.createCommandPool(poolCI); 11 | Log::Trace("Vulkan", "Command Pool created."); 12 | _device.SetObjectName(_commandPool, debugName); 13 | } 14 | 15 | CommandPool::CommandPool(CommandPool&& other) noexcept : _device(other._device) { 16 | *this = std::move(other); 17 | } 18 | 19 | CommandPool& CommandPool::operator=(CommandPool&& other) noexcept { 20 | if (this != &other) { 21 | _commandPool = other._commandPool; 22 | _debugName = std::move(other._debugName); 23 | _commandBuffers = std::move(other._commandBuffers); 24 | _commandBufferIndex = other._commandBufferIndex; 25 | 26 | other._commandPool = nullptr; 27 | other._debugName.clear(); 28 | other._commandBuffers.clear(); 29 | other._commandBufferIndex = 0; 30 | } 31 | 32 | return *this; 33 | } 34 | 35 | CommandPool::~CommandPool() noexcept { 36 | if (_commandPool) { 37 | if (!_commandBuffers.empty()) { _device._device.freeCommandBuffers(_commandPool, _commandBuffers); } 38 | _device._device.destroyCommandPool(_commandPool); 39 | } 40 | } 41 | 42 | void CommandPool::Begin() { 43 | if (_commandBufferIndex > 0) { _device._device.resetCommandPool(_commandPool); } 44 | _commandBufferIndex = 0; 45 | } 46 | 47 | vk::CommandBuffer CommandPool::RequestCommandBuffer() { 48 | if (_commandBufferIndex >= _commandBuffers.size()) { 49 | const vk::CommandBufferAllocateInfo bufferAI(_commandPool, vk::CommandBufferLevel::ePrimary, 1); 50 | const auto buffers = _device._device.allocateCommandBuffers(bufferAI); 51 | _commandBuffers.push_back(buffers[0]); 52 | ++_commandBufferIndex; 53 | 54 | return buffers[0]; 55 | } 56 | 57 | return _commandBuffers[_commandBufferIndex++]; 58 | } 59 | 60 | void CommandPool::Trim() { 61 | _device._device.resetCommandPool(_commandPool); 62 | if (!_commandBuffers.empty()) { _device._device.freeCommandBuffers(_commandPool, _commandBuffers); } 63 | _commandBuffers.clear(); 64 | _commandBufferIndex = 0; 65 | _device._device.trimCommandPool(_commandPool, {}); 66 | } 67 | } // namespace Vulkan 68 | } // namespace Luna 69 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/SpinLock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef __SSE2__ 6 | # include 7 | #endif 8 | 9 | namespace Luna { 10 | class RWSpinLock { 11 | public: 12 | RWSpinLock() { 13 | _counter.store(0); 14 | } 15 | 16 | void LockRead() { 17 | uint32_t v = _counter.fetch_add(Reader, std::memory_order_acquire); 18 | while ((v & Writer) != 0) { 19 | #ifdef __SSE2__ 20 | _mm_pause(); 21 | #endif 22 | v = _counter.load(std::memory_order_acquire); 23 | } 24 | } 25 | 26 | void LockWrite() { 27 | uint32_t expected = 0; 28 | while (!_counter.compare_exchange_weak(expected, Writer, std::memory_order_acquire, std::memory_order_relaxed)) { 29 | #ifdef __SSE2__ 30 | _mm_pause(); 31 | #endif 32 | expected = 0; 33 | } 34 | } 35 | 36 | bool TryLockRead() { 37 | uint32_t v = _counter.fetch_add(Reader, std::memory_order_acquire); 38 | if ((v & Writer) != 0) { 39 | UnlockRead(); 40 | 41 | return false; 42 | } 43 | 44 | return true; 45 | } 46 | 47 | bool TryLockWrite() { 48 | uint32_t expected = 0; 49 | 50 | return _counter.compare_exchange_strong(expected, Writer, std::memory_order_acquire, std::memory_order_relaxed); 51 | } 52 | 53 | void UnlockRead() { 54 | _counter.fetch_sub(Reader, std::memory_order_release); 55 | } 56 | 57 | void UnlockWrite() { 58 | _counter.fetch_and(~Writer, std::memory_order_release); 59 | } 60 | 61 | void PromoteReaderToWriter() { 62 | uint32_t expected = Reader; 63 | if (!_counter.compare_exchange_strong(expected, Writer, std::memory_order_acquire, std::memory_order_relaxed)) { 64 | UnlockRead(); 65 | LockWrite(); 66 | } 67 | } 68 | 69 | private: 70 | constexpr static int Writer = 1; 71 | constexpr static int Reader = 2; 72 | 73 | std::atomic_uint32_t _counter; 74 | }; 75 | 76 | class RWSpinLockReadHolder { 77 | public: 78 | explicit RWSpinLockReadHolder(RWSpinLock& lock) : _lock(lock) { 79 | _lock.LockRead(); 80 | } 81 | 82 | ~RWSpinLockReadHolder() noexcept { 83 | _lock.UnlockRead(); 84 | } 85 | 86 | private: 87 | RWSpinLock& _lock; 88 | }; 89 | 90 | class RWSpinLockWriteHolder { 91 | public: 92 | explicit RWSpinLockWriteHolder(RWSpinLock& lock) : _lock(lock) { 93 | _lock.LockWrite(); 94 | } 95 | 96 | ~RWSpinLockWriteHolder() noexcept { 97 | _lock.UnlockWrite(); 98 | } 99 | 100 | private: 101 | RWSpinLock& _lock; 102 | }; 103 | } // namespace Luna 104 | -------------------------------------------------------------------------------- /Luna/Source/Core/Input.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Luna { 8 | static struct InputState { 9 | bool CursorHidden = false; 10 | glm::dvec2 LastPosition = {0, 0}; 11 | glm::dvec2 Position = {0, 0}; 12 | } State; 13 | 14 | static GLFWwindow* GetWindow() { 15 | return Engine::GetMainWindow()->GetHandle(); 16 | } 17 | 18 | InputAction Input::GetButton(MouseButton button) { 19 | return static_cast(glfwGetMouseButton(GetWindow(), static_cast(button))); 20 | } 21 | 22 | bool Input::GetCursorHidden() { 23 | return State.CursorHidden; 24 | } 25 | 26 | glm::dvec2 Input::GetCursorPosition() { 27 | return State.Position; 28 | } 29 | 30 | InputAction Input::GetKey(Key key) { 31 | return static_cast(glfwGetKey(GetWindow(), static_cast(key))); 32 | } 33 | 34 | void Input::SetCursorShape(MouseCursor cursor) { 35 | Engine::GetMainWindow()->SetCursor(cursor); 36 | } 37 | 38 | void Input::SetCursorHidden(bool hidden) { 39 | if (State.CursorHidden != hidden) { 40 | State.CursorHidden = hidden; 41 | glfwSetInputMode(GetWindow(), GLFW_CURSOR, State.CursorHidden ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); 42 | if (State.CursorHidden) { 43 | glfwGetCursorPos(GetWindow(), &State.LastPosition.x, &State.LastPosition.y); 44 | State.Position = glm::dvec2(0); 45 | } 46 | } 47 | } 48 | 49 | void Input::SetCursorPosition(const glm::dvec2& position) { 50 | glfwSetCursorPos(GetWindow(), position.x, position.y); 51 | glfwGetCursorPos(GetWindow(), &State.Position.x, &State.Position.y); 52 | } 53 | 54 | void Input::CharEvent(int c) { 55 | OnChar(c); 56 | } 57 | 58 | void Input::DropEvent(const std::vector& paths) { 59 | OnFilesDropped(paths); 60 | } 61 | 62 | void Input::KeyEvent(Key key, InputAction action, InputMods mods) { 63 | OnKey(key, action, mods); 64 | } 65 | 66 | void Input::MouseButtonEvent(MouseButton button, InputAction action, InputMods mods) { 67 | OnMouseButton(button, action, mods); 68 | } 69 | 70 | void Input::MouseMovedEvent(const glm::dvec2& position) { 71 | if (State.CursorHidden) { 72 | State.Position = {State.LastPosition - position}; 73 | State.LastPosition = position; 74 | } else { 75 | State.Position = position; 76 | } 77 | 78 | OnMouseMoved(State.Position); 79 | } 80 | 81 | void Input::MouseScrolledEvent(const glm::dvec2& wheelDelta) { 82 | OnMouseScrolled(wheelDelta); 83 | } 84 | } // namespace Luna 85 | -------------------------------------------------------------------------------- /Luna/Source/Core/Engine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace Luna { 15 | static struct EngineState { 16 | bool Initialized = false; 17 | bool Running = false; 18 | std::unique_ptr Window; 19 | 20 | uint64_t FrameCount = 0; 21 | } State; 22 | 23 | static void Update() { 24 | Filesystem::Update(); 25 | ShaderManager::Update(); 26 | WindowManager::Update(); 27 | if (State.Window->IsCloseRequested()) { State.Running = false; } 28 | 29 | UIManager::BeginFrame(); 30 | ImGui::ShowDemoWindow(); 31 | if (!State.Window->IsMinimized()) { Renderer::Render(); } 32 | 33 | ++State.FrameCount; 34 | } 35 | 36 | bool Engine::Initialize() { 37 | if (!Log::Initialize()) { return false; } 38 | Log::SetLevel(Log::Level::Debug); 39 | Log::Info("Luna", "Luna Engine initializing..."); 40 | 41 | if (!Threading::Initialize()) { return false; } 42 | if (!Filesystem::Initialize()) { return false; } 43 | Filesystem::RegisterProtocol("res", std::unique_ptr(new OSFilesystem("Resources"))); 44 | if (!WindowManager::Initialize()) { return false; } 45 | if (!Renderer::Initialize()) { return false; } 46 | if (!ShaderManager::Initialize()) { return false; } 47 | if (!UIManager::Initialize()) { return false; } 48 | 49 | State.Window = std::make_unique("Luna", 1600, 900, false); 50 | if (!State.Window) { return false; } 51 | State.Window->Maximize(); 52 | 53 | State.Window->Show(); 54 | 55 | State.Initialized = true; 56 | 57 | return true; 58 | } 59 | 60 | int Engine::Run() { 61 | if (!State.Initialized) { return -1; } 62 | 63 | State.FrameCount = 0; 64 | State.Running = true; 65 | while (State.Running) { Update(); } 66 | 67 | return 0; 68 | } 69 | 70 | void Engine::Shutdown() { 71 | State.Window.reset(); 72 | UIManager::Shutdown(); 73 | ShaderManager::Shutdown(); 74 | Renderer::Shutdown(); 75 | WindowManager::Shutdown(); 76 | Filesystem::Shutdown(); 77 | Threading::Shutdown(); 78 | Log::Shutdown(); 79 | } 80 | 81 | double Engine::GetTime() { 82 | return WindowManager::GetTime(); 83 | } 84 | 85 | Window* Engine::GetMainWindow() { 86 | return State.Window.get(); 87 | } 88 | } // namespace Luna 89 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/BitOps.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef _MSC_VER 6 | # include 7 | #endif 8 | 9 | namespace Luna { 10 | #ifdef __GNUC__ 11 | # define LeadingZeroes(x) ((x) == 0 ? 32 : __builtin_clz(x)) 12 | # define TrailingZeroes(x) ((x) == 0 ? 32 : __builtin_ctz(x)) 13 | # define TrailingOnes(x) __builtin_ctz(~uint32_t(x)) 14 | # define LeadingZeroes64(x) ((x) == 0 ? 64 : __builtin_clzll(x)) 15 | # define TrailingZeroes64(x) ((x) == 0 ? 64 : __builtin_ctzll(x)) 16 | # define TrailingOnes64(x) __builtin_ctzll(~uint64_t(x)) 17 | #elif defined(_MSC_VER) 18 | static inline uint32_t CountLeadingZeroes(uint32_t x) { 19 | unsigned long result; 20 | if (_BitScanReverse(&result, x)) { return 31 - result; } 21 | return 32; 22 | } 23 | 24 | static inline uint32_t CountTrailingZeroes(uint32_t x) { 25 | unsigned long result; 26 | if (_BitScanForward(&result, x)) { return result; } 27 | return 32; 28 | } 29 | 30 | static inline uint32_t CountLeadingZeroes64(uint64_t x) { 31 | unsigned long result; 32 | if (_BitScanReverse64(&result, x)) { return 63 - result; } 33 | return 64; 34 | } 35 | 36 | static inline uint32_t CountTrailingZeroes64(uint64_t x) { 37 | unsigned long result; 38 | if (_BitScanForward64(&result, x)) { return result; } 39 | return 64; 40 | } 41 | 42 | # define LeadingZeroes(x) ::Luna::CountLeadingZeroes(x) 43 | # define TrailingZeroes(x) ::Luna::CountTrailingZeroes(x) 44 | # define TrailingOnes(x) ::Luna::CountTrailingZeroes(~uint32_t(x)) 45 | # define LeadingZeroes64(x) ::Luna::CountLeadingZeroes64(x) 46 | # define TrailingZeroes64(x) ::Luna::CountTrailingZeroes64(x) 47 | # define TrailingOnes64(x) ::Luna::CountTrailingZeroes64(~uint64_t(x)) 48 | #endif 49 | 50 | template 51 | inline void ForEachBit(uint32_t value, const T& func) { 52 | while (value) { 53 | const uint32_t bit = TrailingZeroes(value); 54 | func(bit); 55 | value &= ~(1u << bit); 56 | } 57 | } 58 | 59 | template 60 | inline void ForEachBit64(uint64_t value, const T& func) { 61 | while (value) { 62 | const uint32_t bit = TrailingZeroes64(value); 63 | func(bit); 64 | value &= ~(1llu << bit); 65 | } 66 | } 67 | 68 | template 69 | inline void ForEachBitRange(uint32_t value, const T& func) { 70 | if (value == ~0u) { 71 | func(0, 32); 72 | return; 73 | } 74 | 75 | uint32_t bitOffset = 0; 76 | while (value) { 77 | uint32_t bit = TrailingZeroes(value); 78 | bitOffset += bit; 79 | value >>= bit; 80 | uint32_t range = TrailingOnes(value); 81 | func(bitOffset, range); 82 | value &= ~((1u << range) - 1); 83 | } 84 | } 85 | } // namespace Luna 86 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/DescriptorSet.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | struct DescriptorSetLayout { 8 | uint8_t ArraySizes[MaxDescriptorBindings] = {}; 9 | uint32_t FloatMask = 0; 10 | uint32_t InputAttachmentMask = 0; 11 | uint32_t SampledImageMask = 0; 12 | uint32_t SampledTexelBufferMask = 0; 13 | uint32_t SamplerMask = 0; 14 | uint32_t SeparateImageMask = 0; 15 | uint32_t StorageBufferMask = 0; 16 | uint32_t StorageImageMask = 0; 17 | uint32_t StorageTexelBufferMask = 0; 18 | uint32_t UniformBufferMask = 0; 19 | 20 | constexpr static const uint8_t UnsizedArray = 0xff; 21 | }; 22 | 23 | struct BindlessDescriptorPoolDeleter { 24 | void operator()(BindlessDescriptorPool* pool); 25 | }; 26 | 27 | class BindlessDescriptorPool : public VulkanObject, 28 | public InternalSyncEnabled { 29 | friend struct BindlessDescriptorPoolDeleter; 30 | 31 | public: 32 | private: 33 | }; 34 | 35 | class BindlessAllocator { 36 | public: 37 | private: 38 | }; 39 | 40 | class DescriptorSetAllocator : public HashedObject { 41 | constexpr static const int DescriptorSetRingSize = 8; 42 | 43 | public: 44 | DescriptorSetAllocator(Hash hash, 45 | Device& device, 46 | const DescriptorSetLayout& layout, 47 | const vk::ShaderStageFlags* stagesForBindings); 48 | ~DescriptorSetAllocator() noexcept; 49 | 50 | [[nodiscard]] vk::DescriptorSetLayout GetSetLayout() const { 51 | return _setLayout; 52 | } 53 | [[nodiscard]] bool IsBindless() const { 54 | return _bindless; 55 | } 56 | 57 | void BeginFrame(); 58 | void Clear(); 59 | std::pair Find(uint32_t threadIndex, Hash hash); 60 | 61 | private: 62 | struct DescriptorSetNode : TemporaryHashMapEnabled, IntrusiveListEnabled { 63 | explicit DescriptorSetNode(vk::DescriptorSet set); 64 | vk::DescriptorSet Set; 65 | }; 66 | 67 | struct PerThread { 68 | std::vector Pools; 69 | TemporaryHashMap SetNodes; 70 | bool ShouldBegin = true; 71 | }; 72 | 73 | Device& _device; 74 | vk::DescriptorSetLayout _setLayout; 75 | std::vector> _perThread; 76 | std::vector _poolSizes; 77 | bool _bindless = false; 78 | }; 79 | } // namespace Vulkan 80 | } // namespace Luna 81 | -------------------------------------------------------------------------------- /Luna/Source/Renderer/Camera.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace Luna { 5 | Camera::Camera() noexcept { 6 | UpdateProjection(); 7 | } 8 | 9 | void Camera::SetPerspective(float fovDegrees, float zNear, float zFar) noexcept { 10 | Log::Assert(zNear > 0.0f, "Camera", "Near Plane must be greater than 0"); 11 | 12 | _fovDegrees = fovDegrees; 13 | _zNear = zNear; 14 | _zFar = zFar; 15 | UpdateProjection(); 16 | } 17 | 18 | void Camera::SetViewport(float width, float height) noexcept { 19 | Log::Assert(width > 0.0f && height > 0.0f, "Camera", "Viewport size must be greater than 0"); 20 | 21 | _aspectRatio = width / height; 22 | UpdateProjection(); 23 | } 24 | 25 | void Camera::UpdateProjection() noexcept { 26 | const float fovy = glm::radians(_fovDegrees); 27 | const float tanHalfFovy = glm::tan(fovy / 2.0f); 28 | const float tanHalfFovx = _aspectRatio * tanHalfFovy; 29 | 30 | _projection = glm::mat4(0.0f); 31 | _projection[0][0] = 1.0f / tanHalfFovx; 32 | _projection[1][1] = 1.0f / tanHalfFovy; 33 | _projection[2][3] = -1.0f; 34 | _projection[3][2] = _zNear; 35 | } 36 | 37 | glm::vec3 EditorCamera::GetForward() const noexcept { 38 | const float pitch = glm::radians(_pitch); 39 | const float yaw = glm::radians(_yaw); 40 | 41 | return glm::normalize(glm::vec3(glm::cos(yaw) * glm::cos(pitch), glm::sin(pitch), glm::sin(yaw) * glm::cos(pitch))); 42 | } 43 | 44 | glm::vec3 EditorCamera::GetRight() const noexcept { 45 | return glm::normalize(glm::cross(GetForward(), glm::vec3(0, 1, 0))); 46 | } 47 | 48 | glm::vec3 EditorCamera::GetUp() const noexcept { 49 | return glm::normalize(glm::cross(GetRight(), GetForward())); 50 | } 51 | 52 | glm::mat4 EditorCamera::GetView() const noexcept { 53 | return glm::lookAt(_position, _position + GetForward(), glm::vec3(0, 1, 0)); 54 | } 55 | 56 | void EditorCamera::Move(const glm::vec3& direction) noexcept { 57 | Translate(GetForward() * direction.z); 58 | Translate(GetRight() * direction.x); 59 | Translate(GetUp() * direction.y); 60 | } 61 | 62 | void EditorCamera::Rotate(float pitchDelta, float yawDelta) noexcept { 63 | SetRotation(_pitch + pitchDelta, _yaw + yawDelta); 64 | } 65 | 66 | void EditorCamera::SetPosition(const glm::vec3& position) noexcept { 67 | _position = position; 68 | } 69 | 70 | void EditorCamera::SetRotation(float pitch, float yaw) noexcept { 71 | _pitch = glm::clamp(pitch, -89.0f, 89.0f); 72 | _yaw = yaw; 73 | while (_yaw < 0.0f) { _yaw += 360.0f; } 74 | while (_yaw >= 360.0f) { _yaw -= 360.0f; } 75 | } 76 | 77 | void EditorCamera::Translate(const glm::vec3& translate) noexcept { 78 | SetPosition(_position + translate); 79 | } 80 | } // namespace Luna 81 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/Buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | struct BufferCreateInfo { 8 | constexpr BufferCreateInfo() noexcept = default; 9 | constexpr BufferCreateInfo(BufferDomain domain, vk::DeviceSize size) noexcept : Domain(domain), Size(size) {} 10 | 11 | constexpr BufferCreateInfo& SetDomain(BufferDomain domain) noexcept { 12 | Domain = domain; 13 | 14 | return *this; 15 | } 16 | constexpr BufferCreateInfo& SetSize(vk::DeviceSize size) noexcept { 17 | Size = size; 18 | 19 | return *this; 20 | } 21 | constexpr BufferCreateInfo& SetFlags(BufferCreateFlags flags) noexcept { 22 | Flags = flags; 23 | 24 | return *this; 25 | } 26 | constexpr BufferCreateInfo& AddFlags(BufferCreateFlags flags) noexcept { 27 | Flags |= flags; 28 | 29 | return *this; 30 | } 31 | constexpr BufferCreateInfo& ZeroInitialize() noexcept { 32 | Flags |= BufferCreateFlagBits::ZeroInitialize; 33 | 34 | return *this; 35 | } 36 | 37 | BufferDomain Domain = BufferDomain::Device; 38 | vk::DeviceSize Size = 0; 39 | BufferCreateFlags Flags; 40 | }; 41 | 42 | struct BufferDeleter { 43 | void operator()(Buffer* buffer); 44 | }; 45 | 46 | class Buffer : public VulkanObject, public Cookie, public InternalSyncEnabled { 47 | friend class ObjectPool; 48 | friend struct BufferDeleter; 49 | 50 | public: 51 | ~Buffer() noexcept; 52 | 53 | [[nodiscard]] const VmaAllocation& GetAllocation() const noexcept { 54 | return _allocation; 55 | } 56 | [[nodiscard]] vk::Buffer GetBuffer() const noexcept { 57 | return _buffer; 58 | } 59 | [[nodiscard]] const BufferCreateInfo& GetCreateInfo() const noexcept { 60 | return _createInfo; 61 | } 62 | [[nodiscard]] vk::DeviceAddress GetDeviceAddress() const noexcept { 63 | return _deviceAddress; 64 | } 65 | [[nodiscard]] vk::DeviceSize GetSize() const noexcept { 66 | return _createInfo.Size; 67 | } 68 | 69 | template 70 | [[nodiscard]] T* Map() const { 71 | return reinterpret_cast(_mappedMemory); 72 | } 73 | 74 | void FillData(uint8_t data, vk::DeviceSize dataSize, vk::DeviceSize offset = 0); 75 | void WriteData(const void* data, vk::DeviceSize dataSize, vk::DeviceSize offset = 0); 76 | 77 | private: 78 | Buffer(Device& device, const BufferCreateInfo& createInfo, const void* initialData, const std::string& debugName); 79 | 80 | Device& _device; 81 | BufferCreateInfo _createInfo; 82 | std::string _debugName; 83 | 84 | vk::Buffer _buffer; 85 | VmaAllocation _allocation; 86 | vk::DeviceAddress _deviceAddress = 0; 87 | void* _mappedMemory = nullptr; 88 | }; 89 | } // namespace Vulkan 90 | } // namespace Luna 91 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/Semaphore.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | struct SemaphoreDeleter { 8 | void operator()(Semaphore* semaphore); 9 | }; 10 | 11 | class Semaphore : public VulkanObject, public InternalSyncEnabled { 12 | friend class ObjectPool; 13 | friend struct SemaphoreDeleter; 14 | 15 | public: 16 | ~Semaphore() noexcept; 17 | 18 | vk::Semaphore GetSemaphore() const { 19 | return _semaphore; 20 | } 21 | vk::SemaphoreType GetSemaphoreType() const { 22 | return _semaphoreType; 23 | } 24 | uint64_t GetTimelineValue() const { 25 | return _timelineValue; 26 | } 27 | bool IsPendingWait() const { 28 | return _pendingWait; 29 | } 30 | bool IsSignalled() const { 31 | return _signalled; 32 | } 33 | 34 | /** 35 | * Consume the Semaphore handle. 36 | * Semaphore handle must exist and have been signalled. 37 | * 38 | * @return The vk::Semaphore handle. 39 | */ 40 | vk::Semaphore Consume(); 41 | 42 | /** 43 | * Release the Semaphore handle. 44 | * 45 | * @return The vk::Semaphore handle. 46 | */ 47 | vk::Semaphore Release(); 48 | 49 | /** 50 | * Signal that this Semaphore will receive its signal from a "foreign" queue, usually the presentation engine. 51 | */ 52 | void SetForeignQueue(); 53 | 54 | /** 55 | * Signal that this Semaphore has been submitted for waiting. 56 | */ 57 | void SetPendingWait(); 58 | 59 | /** 60 | * Signal that this Semaphore has been submitted for signalling. 61 | */ 62 | void SignalExternal(); 63 | 64 | /** 65 | * Signal that this Semaphore has been waited on and is no longer signalled. 66 | */ 67 | void WaitExternal(); 68 | 69 | private: 70 | explicit Semaphore(Device& device); 71 | Semaphore(Device& device, vk::Semaphore semaphore, bool signalled, bool owned, const std::string& debugName = ""); 72 | Semaphore( 73 | Device& device, vk::Semaphore semaphore, uint64_t timelineValue, bool owned, const std::string& debugName = ""); 74 | 75 | Device& _device; /**< The Device this Semaphore belongs to. */ 76 | std::string _debugName; /**< The name assigned to this Semaphore. */ 77 | vk::Semaphore _semaphore; /**< The Semaphore handle. */ 78 | uint64_t _timelineValue = 0; /**< The current timeline value of this Semaphore. */ 79 | bool _owned = false; /**< Specifies whether this class owns the Semaphore handle and should destroy it. */ 80 | bool _isForeignQueue = false; 81 | bool _pendingWait = false; /**< Specifies whether this Semaphore has been submitted for waiting on. */ 82 | vk::SemaphoreType _semaphoreType = vk::SemaphoreType::eBinary; /**< The type of the Semaphore handle. */ 83 | bool _signalled = false; /**< Specifies whether this Semaphore has been submitted for signalling. */ 84 | }; 85 | } // namespace Vulkan 86 | } // namespace Luna 87 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/Sampler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | struct SamplerCreateInfo { 8 | vk::Filter MagFilter = vk::Filter::eNearest; 9 | vk::Filter MinFilter = vk::Filter::eNearest; 10 | vk::SamplerMipmapMode MipmapMode = vk::SamplerMipmapMode::eNearest; 11 | vk::SamplerAddressMode AddressModeU = vk::SamplerAddressMode::eRepeat; 12 | vk::SamplerAddressMode AddressModeV = vk::SamplerAddressMode::eRepeat; 13 | vk::SamplerAddressMode AddressModeW = vk::SamplerAddressMode::eRepeat; 14 | float MipLodBias = 0.0f; 15 | vk::Bool32 AnisotropyEnable = VK_FALSE; 16 | float MaxAnisotropy = 0.0f; 17 | vk::Bool32 CompareEnable = VK_FALSE; 18 | vk::CompareOp CompareOp = vk::CompareOp::eNever; 19 | float MinLod = 0.0f; 20 | float MaxLod = 0.0f; 21 | vk::BorderColor BorderColor = vk::BorderColor::eFloatTransparentBlack; 22 | vk::Bool32 UnnormalizedCoordinates = VK_FALSE; 23 | vk::SamplerReductionMode ReductionMode = vk::SamplerReductionMode::eWeightedAverage; 24 | }; 25 | 26 | struct SamplerDeleter { 27 | void operator()(Sampler* sampler); 28 | }; 29 | 30 | class Sampler : public VulkanObject, public Cookie, public InternalSyncEnabled { 31 | friend class ObjectPool; 32 | friend struct SamplerDeleter; 33 | 34 | public: 35 | ~Sampler() noexcept; 36 | 37 | const SamplerCreateInfo& GetCreateInfo() const { 38 | return _createInfo; 39 | } 40 | vk::Sampler GetSampler() const { 41 | return _sampler; 42 | } 43 | 44 | private: 45 | Sampler(Device& device, const SamplerCreateInfo& info, bool immutable); 46 | 47 | Device& _device; 48 | vk::Sampler _sampler; 49 | SamplerCreateInfo _createInfo; 50 | bool _immutable; 51 | }; 52 | 53 | class ImmutableSampler : public HashedObject { 54 | public: 55 | ImmutableSampler(Hash hash, Device& device, const SamplerCreateInfo& samplerCI); 56 | ImmutableSampler(const ImmutableSampler&) = delete; 57 | void operator=(const ImmutableSampler&) = delete; 58 | 59 | const Sampler& GetSampler() const { 60 | return *_sampler; 61 | } 62 | 63 | private: 64 | Device& _device; 65 | SamplerHandle _sampler; 66 | }; 67 | } // namespace Vulkan 68 | } // namespace Luna 69 | 70 | template <> 71 | struct std::hash { 72 | size_t operator()(const Luna::Vulkan::SamplerCreateInfo& info) const { 73 | Luna::Hasher h; 74 | h(info.MagFilter); 75 | h(info.MinFilter); 76 | h(info.MipmapMode); 77 | h(info.AddressModeU); 78 | h(info.AddressModeV); 79 | h(info.AddressModeW); 80 | h(info.MipLodBias); 81 | h(info.AnisotropyEnable); 82 | h(info.MaxAnisotropy); 83 | h(info.CompareEnable); 84 | h(info.CompareOp); 85 | h(info.MinLod); 86 | h(info.MaxLod); 87 | h(info.BorderColor); 88 | h(info.UnnormalizedCoordinates); 89 | h(info.ReductionMode); 90 | 91 | return static_cast(h.Get()); 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Core/Threading.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Luna { 8 | struct Task; 9 | struct TaskDependencies; 10 | struct TaskGroup; 11 | 12 | struct TaskDependenciesDeleter { 13 | void operator()(TaskDependencies* deps); 14 | }; 15 | 16 | struct TaskDependencies : public IntrusivePtrEnabled { 17 | TaskDependencies(); 18 | 19 | void DependencySatisfied(); 20 | void NotifyDependees(); 21 | void TaskCompleted(); 22 | 23 | std::vector> Pending; 24 | std::atomic_uint PendingCount; 25 | 26 | std::atomic_uint DependencyCount; 27 | std::vector PendingTasks; 28 | 29 | std::condition_variable Condition; 30 | std::mutex Mutex; 31 | bool Done = false; 32 | }; 33 | using TaskDependenciesHandle = IntrusivePtr; 34 | 35 | struct Task { 36 | Task() = default; 37 | Task(TaskDependenciesHandle dependencies, std::function&& function); 38 | 39 | TaskDependenciesHandle Dependencies; 40 | std::function Function; 41 | }; 42 | 43 | struct TaskGroupDeleter { 44 | void operator()(TaskGroup* group); 45 | }; 46 | 47 | struct TaskGroup : public IntrusivePtrEnabled { 48 | TaskGroup() = default; 49 | ~TaskGroup() noexcept; 50 | 51 | void AddFlushDependency(); 52 | void DependOn(TaskGroup& dependency); 53 | void Enqueue(std::function&& function); 54 | void Flush(); 55 | void ReleaseFlushDependency(); 56 | void Wait(); 57 | 58 | TaskDependenciesHandle Dependencies; 59 | bool Flushed = false; 60 | }; 61 | using TaskGroupHandle = IntrusivePtr; 62 | 63 | class TaskComposer { 64 | public: 65 | void AddOutgoingDependency(TaskGroup& task); 66 | TaskGroup& BeginPipelineStage(); 67 | TaskGroupHandle GetDeferredEnqueueHandle(); 68 | TaskGroup& GetGroup(); 69 | TaskGroupHandle GetOutgoingTask(); 70 | TaskGroupHandle GetPipelineStageDependency(); 71 | void SetIncomingTask(TaskGroupHandle group); 72 | 73 | private: 74 | TaskGroupHandle _current; 75 | TaskGroupHandle _incomingDependencies; 76 | TaskGroupHandle _nextStageDependencies; 77 | }; 78 | 79 | class Threading final { 80 | friend struct TaskDependenciesDeleter; 81 | friend struct TaskGroup; 82 | friend struct TaskGroupDeleter; 83 | 84 | public: 85 | static bool Initialize(); 86 | static void Shutdown(); 87 | 88 | static void AddDependency(TaskGroup& dependee, TaskGroup& dependency); 89 | static TaskGroupHandle CreateTaskGroup(); 90 | static std::uint32_t GetThreadCount(); 91 | static void Sleep(uint32_t milliseconds); 92 | static void Submit(TaskGroupHandle& group); 93 | static void SubmitTasks(const std::vector& tasks); 94 | static void WaitIdle(); 95 | 96 | static void SetThreadID(std::uint32_t thread); 97 | static std::uint32_t GetThreadID(); 98 | static std::uint32_t GetThreadIDFromSys(const std::string& idStr); 99 | 100 | private: 101 | static void FreeTaskDependencies(TaskDependencies* dependencies); 102 | static void WorkerThread(int threadID); 103 | }; 104 | } // namespace Luna 105 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/TemporaryHashMap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Luna { 7 | template 8 | class TemporaryHashMapEnabled { 9 | public: 10 | [[nodiscard]] Hash GetHash() const noexcept { 11 | return _hash; 12 | } 13 | [[nodiscard]] uint32_t GetIndex() const noexcept { 14 | return _index; 15 | } 16 | 17 | void SetHash(Hash hash) noexcept { 18 | _hash = hash; 19 | } 20 | void SetIndex(uint32_t index) noexcept { 21 | _index = index; 22 | } 23 | 24 | private: 25 | Hash _hash = 0; 26 | uint32_t _index = 0; 27 | }; 28 | 29 | template 30 | class TemporaryHashMap { 31 | public: 32 | ~TemporaryHashMap() noexcept { 33 | Clear(); 34 | } 35 | 36 | void BeginFrame() { 37 | _index = (_index + 1) & (RingSize - 1); 38 | for (auto& node : _rings[_index]) { 39 | _hashMap.Erase(node.GetHash()); 40 | FreeObject(&node, ReuseTag()); 41 | } 42 | _rings[_index].Clear(); 43 | } 44 | 45 | void Clear() { 46 | for (auto& ring : _rings) { 47 | for (auto& node : ring) { _pool.Free(static_cast(&node)); } 48 | ring.Clear(); 49 | } 50 | _hashMap.Clear(); 51 | 52 | for (auto& vacant : _vacants) { _pool.Free(static_cast(&*vacant)); } 53 | _vacants.clear(); 54 | _pool.Clear(); 55 | } 56 | 57 | template 58 | T* Emplace(Hash hash, Args&&... args) { 59 | auto* node = _pool.Allocate(std::forward(args)...); 60 | node->SetIndex(_index); 61 | node->SetHash(hash); 62 | _hashMap.EmplaceReplace(hash, node); 63 | _rings[_index].InsertFront(node); 64 | 65 | return node; 66 | } 67 | 68 | template 69 | void MakeVacant(Args&&... args) { 70 | _vacants.push_back(_pool.Allocate(std::forward(args)...)); 71 | } 72 | 73 | T* Request(Hash hash) { 74 | auto* v = _hashMap.Find(hash); 75 | if (v) { 76 | auto node = v->Value; 77 | if (node->GetIndex() != _index) { 78 | _rings[_index].MoveToFront(_rings[node->GetIndex()], node); 79 | node->SetIndex(_index); 80 | } 81 | 82 | return &*node; 83 | } else { 84 | return nullptr; 85 | } 86 | } 87 | 88 | T* RequestVacant(Hash hash) { 89 | if (_vacants.empty()) { return nullptr; } 90 | 91 | auto top = _vacants.back(); 92 | _vacants.pop_back(); 93 | top->SetIndex(_index); 94 | top->SetHash(hash); 95 | _hashMap.EmplaceReplace(hash, top); 96 | _rings[_index].InsertFront(top); 97 | 98 | return &*top; 99 | } 100 | 101 | private: 102 | template 103 | struct ReuseTag {}; 104 | 105 | void FreeObject(T* object, const ReuseTag&) { 106 | _pool.Free(object); 107 | } 108 | void FreeObject(T* object, const ReuseTag&) { 109 | _vacants.push_back(object); 110 | } 111 | 112 | IntrusiveHashMap::Iterator>> _hashMap; 113 | uint32_t _index = 0; 114 | ObjectPool _pool; 115 | IntrusiveList _rings[RingSize]; 116 | std::vector::Iterator> _vacants; 117 | }; 118 | } // namespace Luna 119 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/Path.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | class PathIterator; 7 | 8 | class Path { 9 | friend class PathIterator; 10 | 11 | public: 12 | Path(); 13 | Path(const char* pathStr); 14 | Path(const std::string& pathStr); 15 | Path(const std::string_view pathStr); 16 | Path(const std::filesystem::path& fsPath); 17 | 18 | [[nodiscard]] bool Empty() const; 19 | [[nodiscard]] bool IsAbsolute() const; 20 | [[nodiscard]] bool IsRelative() const; 21 | [[nodiscard]] bool IsRoot() const; 22 | [[nodiscard]] bool ValidateBounds() const; 23 | 24 | [[nodiscard]] Path Normalized() const; 25 | [[nodiscard]] const std::string& String() const; 26 | 27 | [[nodiscard]] std::string_view Extension() const; 28 | [[nodiscard]] std::string_view Filename() const; 29 | [[nodiscard]] std::string_view FilePath() const; 30 | [[nodiscard]] std::string_view ParentPath() const; 31 | [[nodiscard]] std::string_view Protocol() const; 32 | [[nodiscard]] std::string_view Stem() const; 33 | 34 | [[nodiscard]] Path Relative(const Path& other) const; 35 | 36 | [[nodiscard]] operator std::string() const; 37 | [[nodiscard]] bool operator==(const Path& other) const; 38 | [[nodiscard]] bool operator!=(const Path& other) const; 39 | 40 | [[nodiscard]] Path operator/(const char* path) const; 41 | [[nodiscard]] Path operator/(std::string_view path) const; 42 | [[nodiscard]] Path operator/(const std::string& other) const; 43 | [[nodiscard]] Path operator/(const Path& other) const; 44 | Path& operator/=(const char* path); 45 | Path& operator/=(std::string_view path); 46 | Path& operator/=(const std::string& other); 47 | Path& operator/=(const Path& other); 48 | 49 | PathIterator begin() const; 50 | PathIterator end() const noexcept; 51 | 52 | private: 53 | std::string _pathStr; 54 | }; 55 | 56 | class PathIterator { 57 | using BaseIteratorT = std::string::const_iterator; 58 | 59 | public: 60 | PathIterator(); 61 | PathIterator(const BaseIteratorT& position, const Path* parent); 62 | PathIterator(const BaseIteratorT& position, std::string_view element, const Path* parent); 63 | PathIterator(const PathIterator&) = default; 64 | PathIterator(PathIterator&&) = default; 65 | PathIterator& operator=(const PathIterator&) = default; 66 | PathIterator& operator=(PathIterator&&) = default; 67 | 68 | PathIterator& operator++(); 69 | [[nodiscard]] const std::string_view* operator->() const noexcept; 70 | [[nodiscard]] const std::string_view& operator*() const noexcept; 71 | [[nodiscard]] bool operator==(const PathIterator& other) const; 72 | [[nodiscard]] bool operator!=(const PathIterator& other) const; 73 | 74 | private: 75 | std::string::const_iterator _position = {}; 76 | std::string_view _element = {}; 77 | const Path* _parent = nullptr; 78 | }; 79 | } // namespace Luna 80 | 81 | namespace std { 82 | template <> 83 | struct hash { 84 | size_t operator()(const Luna::Path& path) const { 85 | return std::hash{}(path.String()); 86 | } 87 | }; 88 | } // namespace std 89 | 90 | template <> 91 | struct std::formatter : std::formatter { 92 | auto format(const Luna::Path& path, format_context& ctx) const -> decltype(ctx.out()) { 93 | return format_to(ctx.out(), "{}", std::string(path)); 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/QueryPool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace Luna { 5 | namespace Vulkan { 6 | void QueryResultDeleter::operator()(QueryResult* result) { 7 | result->_device._queryResultPool.Free(result); 8 | } 9 | 10 | QueryResult::QueryResult(Device& device, bool deviceTimebase) : _device(device), _deviceTimebase(deviceTimebase) {} 11 | 12 | void QueryResult::SignalTimestampTicks(uint64_t ticks) noexcept { 13 | _timestampTicks = ticks; 14 | _hasTimestamp = true; 15 | } 16 | 17 | QueryPool::QueryPool(Device& device) : _device(device) { 18 | _supportsTimestamp = _device.GetDeviceInfo().Properties.Core.limits.timestampComputeAndGraphics && 19 | _device.GetDeviceInfo().EnabledFeatures.Vulkan12.hostQueryReset; 20 | 21 | if (_supportsTimestamp) { AddPool(); } 22 | } 23 | 24 | QueryPool::~QueryPool() noexcept { 25 | for (auto& pool : _pools) { _device.GetDevice().destroyQueryPool(pool.Pool); } 26 | } 27 | 28 | void QueryPool::Begin() { 29 | for (uint32_t i = 0; i <= _poolIndex; ++i) { 30 | if (i >= _pools.size()) { continue; } 31 | 32 | auto& pool = _pools[i]; 33 | if (pool.Index == 0) { continue; } 34 | 35 | const auto queryResult = 36 | _device.GetDevice().getQueryPoolResults(pool.Pool, 37 | 0, 38 | pool.Index, 39 | pool.Index * sizeof(uint64_t), 40 | pool.Results.data(), 41 | sizeof(uint64_t), 42 | vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait); 43 | for (uint32_t j = 0; j < pool.Index; ++j) { pool.Cookies[j]->SignalTimestampTicks(pool.Results[j]); } 44 | 45 | _device.GetDevice().resetQueryPool(pool.Pool, 0, pool.Index); 46 | } 47 | 48 | _poolIndex = 0; 49 | for (auto& pool : _pools) { pool.Index = 0; } 50 | } 51 | 52 | QueryResultHandle QueryPool::WriteTimestamp(vk::CommandBuffer cmd, vk::PipelineStageFlags2 stages) { 53 | if (!_supportsTimestamp) { return {}; } 54 | 55 | if (_pools[_poolIndex].Index >= _pools[_poolIndex].Size) { _poolIndex++; } 56 | if (_poolIndex >= _pools.size()) { AddPool(); } 57 | 58 | auto& pool = _pools[_poolIndex]; 59 | auto cookie = QueryResultHandle(_device._queryResultPool.Allocate(_device, true)); 60 | pool.Cookies[pool.Index] = cookie; 61 | 62 | cmd.writeTimestamp2(stages, pool.Pool, pool.Index); 63 | pool.Index++; 64 | 65 | return cookie; 66 | } 67 | 68 | void QueryPool::AddPool() { 69 | const vk::QueryPoolCreateInfo poolCI({}, vk::QueryType::eTimestamp, 64); 70 | auto queryPool = _device.GetDevice().createQueryPool(poolCI); 71 | _device.GetDevice().resetQueryPool(queryPool, 0, poolCI.queryCount); 72 | 73 | auto& pool = _pools.emplace_back(); 74 | pool.Pool = queryPool; 75 | pool.Size = poolCI.queryCount; 76 | pool.Index = 0; 77 | pool.Results.resize(pool.Size); 78 | pool.Cookies.resize(pool.Size); 79 | } 80 | 81 | TimestampInterval::TimestampInterval(const std::string& name) : _name(name) {} 82 | 83 | double TimestampInterval::GetTimePerAccumulation() const noexcept { 84 | if (_totalAccumulations) { return _totalTime / double(_totalAccumulations); } 85 | 86 | return 0.0; 87 | } 88 | 89 | void TimestampInterval::AccumulateTime(double t) noexcept { 90 | _totalTime += t; 91 | _totalAccumulations++; 92 | } 93 | 94 | void TimestampInterval::Reset() noexcept { 95 | _totalTime = 0.0; 96 | _totalAccumulations = 0; 97 | } 98 | } // namespace Vulkan 99 | } // namespace Luna 100 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/Semaphore.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace Luna { 5 | namespace Vulkan { 6 | void SemaphoreDeleter::operator()(Semaphore* semaphore) { 7 | semaphore->_device._semaphorePool.Free(semaphore); 8 | } 9 | 10 | Semaphore::Semaphore(Device& device) : _device(device) {} 11 | 12 | Semaphore::Semaphore(Device& device, vk::Semaphore semaphore, bool signalled, bool owned, const std::string& debugName) 13 | : _device(device), 14 | _semaphore(semaphore), 15 | _signalled(signalled), 16 | _owned(owned), 17 | _semaphoreType(vk::SemaphoreType::eBinary), 18 | _debugName(debugName) {} 19 | 20 | Semaphore::Semaphore( 21 | Device& device, vk::Semaphore semaphore, uint64_t timelineValue, bool owned, const std::string& debugName) 22 | : _device(device), 23 | _semaphore(semaphore), 24 | _timelineValue(timelineValue), 25 | _owned(owned), 26 | _semaphoreType(vk::SemaphoreType::eTimeline), 27 | _debugName(debugName) {} 28 | 29 | Semaphore::~Semaphore() noexcept { 30 | if (!_owned) { return; } 31 | 32 | // "Destroying" a semaphore can mean one of three things depending on the current state of the semaphore. 33 | // 34 | // Our implementation tries to recycle semaphores whenever possible, meaning we do not call vkDestroySemaphore and 35 | // simply mark the semaphore handle as available for any function that needs a semaphore later. 36 | // However, we cannot recycle timeline semaphores, so those are always destroyed immediately. 37 | // 38 | // If the semaphore has already been submitted for signalling, but this handle is being destroyed, it means nobody is 39 | // left to wait on it, so the semaphore is destroyed. 40 | // If the semaphore belongs to a "foreign" queue, such as the presentation engine, we cannot destroy it immediately. 41 | // We must first submit the semaphore to be waited on, then it will be recycled. 42 | // 43 | // Finally, if none of the above apply, the semaphore is submitted for recycling. 44 | 45 | if (_internalSync) { 46 | if (_semaphoreType == vk::SemaphoreType::eTimeline) { 47 | _device.DestroySemaphoreNoLock(_semaphore); 48 | } else if (_signalled) { 49 | if (_isForeignQueue) { 50 | _device.ConsumeSemaphoreNoLock(_semaphore); 51 | } else { 52 | _device.DestroySemaphoreNoLock(_semaphore); 53 | } 54 | } else { 55 | _device.RecycleSemaphoreNoLock(_semaphore); 56 | } 57 | } else { 58 | if (_semaphoreType == vk::SemaphoreType::eTimeline) { 59 | _device.DestroySemaphore(_semaphore); 60 | } else if (_signalled) { 61 | if (_isForeignQueue) { 62 | _device.ConsumeSemaphore(_semaphore); 63 | } else { 64 | _device.DestroySemaphore(_semaphore); 65 | } 66 | } else { 67 | _device.RecycleSemaphore(_semaphore); 68 | } 69 | } 70 | } 71 | 72 | vk::Semaphore Semaphore::Consume() { 73 | Log::Assert(_semaphore && _signalled, "Vulkan", "Attempting to Consume an invalid or unsignalled Semaphore"); 74 | 75 | return Release(); 76 | } 77 | 78 | vk::Semaphore Semaphore::Release() { 79 | auto sem = _semaphore; 80 | _semaphore = nullptr; 81 | _signalled = false; 82 | _owned = false; 83 | 84 | return sem; 85 | } 86 | 87 | void Semaphore::SetForeignQueue() { 88 | _isForeignQueue = true; 89 | } 90 | 91 | void Semaphore::SetPendingWait() { 92 | _pendingWait = true; 93 | } 94 | 95 | void Semaphore::SignalExternal() { 96 | _signalled = true; 97 | } 98 | 99 | void Semaphore::WaitExternal() { 100 | _signalled = false; 101 | } 102 | } // namespace Vulkan 103 | } // namespace Luna 104 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/IntrusiveList.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Luna { 4 | template 5 | class IntrusiveListEnabled { 6 | public: 7 | IntrusiveListEnabled* Prev = nullptr; 8 | IntrusiveListEnabled* Next = nullptr; 9 | }; 10 | 11 | /** 12 | * Contains a doubly-linked list of objects. This class does not own the objects in the list. 13 | * 14 | * Objects used in this list must inherit from IntrusiveListEnabled. 15 | */ 16 | template 17 | class IntrusiveList { 18 | public: 19 | class Iterator { 20 | public: 21 | Iterator() noexcept = default; 22 | Iterator(IntrusiveListEnabled* node) noexcept : _node(node) {} 23 | 24 | [[nodiscard]] T* Get() noexcept { 25 | return static_cast(_node); 26 | } 27 | [[nodiscard]] const T* Get() const noexcept { 28 | return static_cast(_node); 29 | } 30 | 31 | Iterator& operator++() noexcept { 32 | _node = _node->Next; 33 | return *this; 34 | } 35 | Iterator& operator--() noexcept { 36 | _node = _node->Prev; 37 | return *this; 38 | } 39 | 40 | [[nodiscard]] T& operator*() noexcept { 41 | return *static_cast(_node); 42 | } 43 | [[nodiscard]] const T& operator*() const noexcept { 44 | return *static_cast(_node); 45 | } 46 | [[nodiscard]] T* operator->() noexcept { 47 | return static_cast(_node); 48 | } 49 | [[nodiscard]] const T* operator->() const noexcept { 50 | return static_cast(_node); 51 | } 52 | 53 | [[nodiscard]] explicit operator bool() const noexcept { 54 | return _node != nullptr; 55 | } 56 | auto operator<=>(const Iterator& other) const noexcept = default; 57 | 58 | private: 59 | IntrusiveListEnabled* _node = nullptr; 60 | }; 61 | 62 | [[nodiscard]] bool Empty() const noexcept { 63 | return _head = nullptr; 64 | } 65 | 66 | void Clear() noexcept { 67 | _head = nullptr; 68 | _tail = nullptr; 69 | } 70 | 71 | Iterator Erase(Iterator it) noexcept { 72 | auto* node = it.Get(); 73 | auto* next = node->Next; 74 | auto* prev = node->Prev; 75 | 76 | if (prev) { 77 | prev->Next = next; 78 | } else { 79 | _head = next; 80 | } 81 | 82 | if (next) { 83 | next->Prev = prev; 84 | } else { 85 | _tail = prev; 86 | } 87 | 88 | return next; 89 | } 90 | void InsertBack(Iterator it) noexcept { 91 | auto* node = it.Get(); 92 | 93 | if (_tail) { 94 | _tail->Next = node; 95 | } else { 96 | _head = node; 97 | } 98 | 99 | node->Prev = _tail; 100 | node->Next = nullptr; 101 | _tail = node; 102 | } 103 | void InsertFront(Iterator it) noexcept { 104 | auto* node = it.Get(); 105 | 106 | if (_head) { 107 | _head->Prev = node; 108 | } else { 109 | _tail = node; 110 | } 111 | 112 | node->Next = _head; 113 | node->Prev = nullptr; 114 | _head = node; 115 | } 116 | 117 | void MoveToBack(IntrusiveList& other, Iterator it) noexcept { 118 | other.Erase(it); 119 | InsertBack(it); 120 | } 121 | void MoveToFront(IntrusiveList& other, Iterator it) noexcept { 122 | other.Erase(it); 123 | InsertFront(it); 124 | } 125 | 126 | [[nodiscard]] Iterator begin() const noexcept { 127 | return Iterator(_head); 128 | } 129 | [[nodiscard]] Iterator end() const noexcept { 130 | return Iterator(); 131 | } 132 | [[nodiscard]] Iterator rbegin() const noexcept { 133 | return Iterator(_tail); 134 | } 135 | [[nodiscard]] Iterator rend() const noexcept { 136 | return Iterator(); 137 | } 138 | 139 | private: 140 | IntrusiveListEnabled* _head = nullptr; 141 | IntrusiveListEnabled* _tail = nullptr; 142 | }; 143 | } // namespace Luna 144 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Renderer/ShaderManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Luna { 7 | struct MetaCache; 8 | class ShaderCompiler; 9 | 10 | struct ShaderTemplateVariant : public IntrusiveHashMapEnabled { 11 | Hash VariantHash = 0; 12 | Hash SpirvHash = 0; 13 | std::vector Spirv; 14 | std::vector> Defines; 15 | uint32_t Instance = 0; 16 | Vulkan::Shader* PrecompiledShader = nullptr; 17 | 18 | Vulkan::Shader* Resolve() const; 19 | }; 20 | 21 | class ShaderTemplate : public IntrusiveHashMapEnabled { 22 | public: 23 | ShaderTemplate(const Path& shaderPath, 24 | Vulkan::ShaderStage stage, 25 | Hash pathHash, 26 | const std::vector& includeDirs); 27 | ~ShaderTemplate() noexcept; 28 | 29 | [[nodiscard]] const Path& GetPath() const noexcept { 30 | return _path; 31 | } 32 | [[nodiscard]] Hash GetPathHash() const noexcept { 33 | return _pathHash; 34 | } 35 | 36 | void Recompile(); 37 | void RegisterDependencies(); 38 | const ShaderTemplateVariant* RegisterVariant(const std::vector>& defines = {}, 39 | Vulkan::Shader* precompiledShader = nullptr); 40 | 41 | private: 42 | void RecompileVariant(ShaderTemplateVariant& variant); 43 | void UpdateVariantCache(const ShaderTemplateVariant& variant); 44 | 45 | Path _path; 46 | Hash _pathHash = 0; 47 | Vulkan::ShaderStage _stage; 48 | Vulkan::VulkanCache _variants; 49 | 50 | // Used when loading raw SPIR-V shaders. 51 | std::vector _staticShader; 52 | 53 | // Used when loading shaders from GLSL source. 54 | std::unique_ptr _compiler; 55 | std::vector _includeDirs; 56 | Hash _sourceHash = 0; 57 | }; 58 | 59 | class ShaderProgramVariant : public IntrusiveHashMapEnabled { 60 | friend class ShaderProgram; 61 | 62 | public: 63 | ShaderProgramVariant(); 64 | ~ShaderProgramVariant() noexcept; 65 | 66 | Vulkan::Program* GetProgram(); 67 | 68 | private: 69 | Vulkan::Program* GetCompute(); 70 | Vulkan::Program* GetGraphics(); 71 | 72 | RWSpinLock _instanceLock; 73 | std::atomic _program; 74 | std::array _shaderInstance; 75 | std::array _stages; 76 | }; 77 | 78 | class ShaderProgram : public IntrusiveHashMapEnabled { 79 | public: 80 | ShaderProgram(ShaderTemplate& compute); 81 | ShaderProgram(ShaderTemplate& vertex, ShaderTemplate& fragment); 82 | ~ShaderProgram() noexcept; 83 | 84 | ShaderProgramVariant* RegisterVariant(const std::vector>& defines = {}); 85 | void SetStage(Vulkan::ShaderStage stage, ShaderTemplate* shader); 86 | 87 | private: 88 | std::array _stages; 89 | Vulkan::VulkanCacheReadWrite _variantCache; 90 | }; 91 | 92 | class ShaderManager { 93 | public: 94 | static bool Initialize(); 95 | static void Update(); 96 | static void Shutdown(); 97 | 98 | static Vulkan::Program* GetGraphics(const Path& vertex, 99 | const Path& fragment, 100 | const std::vector>& defines = {}); 101 | static ShaderProgram* RegisterCompute(const Path& compute); 102 | static ShaderProgram* RegisterGraphics(const Path& vertex, const Path& fragment); 103 | }; 104 | } // namespace Luna 105 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Renderer/Scene.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Luna { 8 | struct GltfContext; 9 | 10 | struct Vertex { 11 | glm::vec3 Normal = glm::vec3(0.0f); 12 | glm::vec4 Tangent = glm::vec4(0.0f); 13 | glm::vec2 Texcoord0 = glm::vec2(0.0f); 14 | glm::vec2 Texcoord1 = glm::vec2(0.0f); 15 | glm::vec4 Color0 = glm::vec4(0.0f); 16 | glm::uvec4 Joints0 = glm::uvec4(0); 17 | glm::vec4 Weights0 = glm::vec4(0.0f); 18 | 19 | bool operator==(const Vertex& other) const { 20 | return Normal == other.Normal && Tangent == other.Tangent && Texcoord0 == other.Texcoord0 && 21 | Texcoord1 == other.Texcoord1 && Color0 == other.Color0 && Joints0 == other.Joints0 && 22 | Weights0 == other.Weights0; 23 | } 24 | }; 25 | 26 | struct CombinedVertex { 27 | glm::vec3 Position; 28 | Vertex Attributes; 29 | 30 | bool operator==(const CombinedVertex& other) const { 31 | return Position == other.Position && Attributes == other.Attributes; 32 | } 33 | }; 34 | 35 | struct Meshlet { 36 | uint32_t VertexOffset; 37 | uint32_t IndexOffset; 38 | uint32_t TriangleOffset; 39 | uint32_t IndexCount; 40 | uint32_t TriangleCount; 41 | uint32_t InstanceID; 42 | glm::vec3 AABBMin; 43 | glm::vec3 AABBMax; 44 | }; 45 | 46 | struct Mesh { 47 | std::vector Meshlets; 48 | }; 49 | 50 | struct RenderScene { 51 | std::vector Meshlets; 52 | std::vector Transforms; 53 | uint64_t TriangleCount = 0; 54 | }; 55 | 56 | class Scene { 57 | public: 58 | struct Node { 59 | [[nodiscard]] glm::mat4 GetGlobalTransform() const noexcept; 60 | 61 | Node* Parent = nullptr; 62 | std::vector Children; 63 | glm::mat4 Transform = glm::mat4(1.0f); 64 | 65 | Mesh* Mesh = nullptr; 66 | }; 67 | 68 | [[nodiscard]] Vulkan::Buffer& GetPositionBuffer() { 69 | return *_positionBuffer; 70 | } 71 | [[nodiscard]] Vulkan::Buffer& GetVertexBuffer() { 72 | return *_vertexBuffer; 73 | } 74 | [[nodiscard]] Vulkan::Buffer& GetIndexBuffer() { 75 | return *_indexBuffer; 76 | } 77 | [[nodiscard]] Vulkan::Buffer& GetTriangleBuffer() { 78 | return *_triangleBuffer; 79 | } 80 | 81 | void Clear(); 82 | RenderScene Flatten() const; 83 | void LoadModel(const Path& gltfFile); 84 | 85 | private: 86 | bool ParseGltf(GltfContext& context); 87 | 88 | std::vector _meshes; 89 | std::vector _nodes; 90 | std::vector _rootNodes; 91 | 92 | std::vector _positions; 93 | std::vector _vertices; 94 | std::vector _indices; 95 | std::vector _triangles; 96 | 97 | Vulkan::BufferHandle _positionBuffer; 98 | Vulkan::BufferHandle _vertexBuffer; 99 | Vulkan::BufferHandle _indexBuffer; 100 | Vulkan::BufferHandle _triangleBuffer; 101 | }; 102 | } // namespace Luna 103 | 104 | template <> 105 | struct std::hash { 106 | size_t operator()(const Luna::Vertex& v) { 107 | Luna::Hasher h; 108 | h.Data(sizeof(v.Normal), glm::value_ptr(v.Normal)); 109 | h.Data(sizeof(v.Tangent), glm::value_ptr(v.Tangent)); 110 | h.Data(sizeof(v.Texcoord0), glm::value_ptr(v.Texcoord0)); 111 | h.Data(sizeof(v.Texcoord1), glm::value_ptr(v.Texcoord1)); 112 | h.Data(sizeof(v.Color0), glm::value_ptr(v.Color0)); 113 | h.Data(sizeof(v.Joints0), glm::value_ptr(v.Joints0)); 114 | h.Data(sizeof(v.Weights0), glm::value_ptr(v.Weights0)); 115 | 116 | return static_cast(h.Get()); 117 | } 118 | }; 119 | 120 | template <> 121 | struct std::hash { 122 | size_t operator()(const Luna::CombinedVertex& v) const { 123 | Luna::Hasher h; 124 | h.Data(sizeof(v.Position), glm::value_ptr(v.Position)); 125 | h(v.Attributes); 126 | 127 | return static_cast(h.Get()); 128 | } 129 | }; 130 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/Bitmask.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | template 7 | struct EnableBitmaskOperators : std::false_type {}; 8 | 9 | template 10 | concept IsBitmaskType = EnableBitmaskOperators::value; 11 | 12 | template 13 | struct Bitmask { 14 | using UnderlyingT = typename std::underlying_type_t; 15 | 16 | UnderlyingT Value; 17 | 18 | constexpr Bitmask() noexcept : Value(static_cast(0)) {} 19 | constexpr Bitmask(Bits value) noexcept : Value(static_cast(value)) {} 20 | constexpr explicit Bitmask(UnderlyingT value) noexcept : Value(value) {} 21 | constexpr Bitmask(const Bitmask& other) noexcept = default; 22 | 23 | constexpr Bitmask& operator=(const Bitmask&) noexcept = default; 24 | 25 | [[nodiscard]] explicit constexpr operator UnderlyingT() const noexcept { 26 | return Value; 27 | } 28 | [[nodiscard]] constexpr operator bool() const noexcept { 29 | return !!Value; 30 | } 31 | [[nodiscard]] bool operator!() const noexcept { 32 | return !Value; 33 | } 34 | [[nodiscard]] Bitmask operator~() const noexcept { 35 | return Bitmask(~Value); 36 | } 37 | 38 | [[nodiscard]] auto operator<=>(const Bitmask&) const = default; 39 | [[nodiscard]] constexpr Bitmask operator&(const Bitmask& other) const noexcept { 40 | return Bitmask(Value & other.Value); 41 | } 42 | [[nodiscard]] constexpr Bitmask operator|(const Bitmask& other) const noexcept { 43 | return Bitmask(Value | other.Value); 44 | } 45 | [[nodiscard]] constexpr Bitmask operator^(const Bitmask& other) const noexcept { 46 | return Bitmask(Value ^ other.Value); 47 | } 48 | 49 | constexpr Bitmask& operator&=(const Bitmask& other) noexcept { 50 | Value &= other.Value; 51 | return *this; 52 | } 53 | constexpr Bitmask& operator|=(const Bitmask& other) noexcept { 54 | Value |= other.Value; 55 | return *this; 56 | } 57 | constexpr Bitmask& operator^=(const Bitmask& other) noexcept { 58 | Value ^= other.Value; 59 | return *this; 60 | } 61 | }; 62 | 63 | template 64 | [[nodiscard]] constexpr Bitmask operator&(Bits bit, const Bitmask& flags) noexcept { 65 | return flags.operator&(bit); 66 | } 67 | template 68 | [[nodiscard]] constexpr Bitmask operator|(Bits bit, const Bitmask& flags) noexcept { 69 | return flags.operator|(bit); 70 | } 71 | template 72 | [[nodiscard]] constexpr Bitmask operator^(Bits bit, const Bitmask& flags) noexcept { 73 | return flags.operator^(bit); 74 | } 75 | 76 | template 77 | requires(IsBitmaskType) 78 | [[nodiscard]] inline constexpr Bitmask operator&(Bits a, Bits b) noexcept { 79 | return Bitmask(a) & b; 80 | } 81 | template 82 | requires(IsBitmaskType) 83 | [[nodiscard]] inline constexpr Bitmask operator|(Bits a, Bits b) noexcept { 84 | return Bitmask(a) | b; 85 | } 86 | template 87 | requires(IsBitmaskType) 88 | [[nodiscard]] inline constexpr Bitmask operator^(Bits a, Bits b) noexcept { 89 | return Bitmask(a) ^ b; 90 | } 91 | 92 | template 93 | requires(IsBitmaskType) 94 | [[nodiscard]] inline constexpr Bitmask operator~(Bits bit) noexcept { 95 | return ~(Bitmask(bit)); 96 | } 97 | } // namespace Luna 98 | 99 | namespace std { 100 | template 101 | struct hash> { 102 | size_t operator()(const Luna::Bitmask bitmask) const { 103 | return hash::UnderlyingT>{}(bitmask); 104 | } 105 | }; 106 | } // namespace std 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Luna 2 | 3 | Luna is the latest (and hopefully greatest) pet project of mine, in my efforts to understand how modern game engines work. 4 | I've been an avid gamer for most of my life, and I've always been fascinated by how graphics and computing technology has come together to make such incredible experiences. 5 | 6 | I'm also curious at heart, and I would love nothing more than to take those game engines and tear them apart to see how they tick. 7 | But more than that, I want to understand these programs on a more fundamental level, and the best way to do that is to create my own. 8 | 9 | This repository contains the combined efforts of the past few years of study, experimentation, and tribulations I've gone through on my journey. 10 | The code contained within is not going to be optimal, but I leave it here in the hopes that someone like myself may see it and find some amount of inspiration, or even learn from it. 11 | 12 | Luna is purely written as an educational experiment, and I have no intentions of ever releasing it commercially. 13 | 14 | ## Building 15 | 16 | Luna has been built and tested using Clang and Windows. While I endeavor to keep things platform-agnostic and compliant, other compilers have not been tested and may produce errors. 17 | 18 | ### Prerequisites 19 | 20 | - [CMake](https://cmake.org/) (Verion 3.21 or greater) 21 | - [Vulkan SDK](https://vulkan.lunarg.com/sdk/home) (Version 1.3.xxx.x or greater) 22 | 23 | ### Build Steps 24 | 25 | - `cmake -S . -B Build` 26 | - `cmake --build Build --parallel 8` 27 | 28 | ### Running 29 | 30 | - The final executable can be found in the `Build/Bin` folder. 31 | - The editor expects to be run from the root of the repository. Otherwise it will fail to load assets. 32 | 33 | ## Credits 34 | 35 | My journey has been long and hard, and it would not be possible without the inspiration and help of others in the game development community. Below, in no particular order, are just a few of the people and projects I would like to extend thanks to, or credit for code I've used along the way in this project. 36 | 37 | * [Hans-Kristian Arntzen (Themaister)](https://github.com/Themaister) - For his work on the [Granite](https://github.com/Themaister/Granite) engine, and several helpful [blog posts](http://themaister.net/blog/). A lot of the way I use and abstract Vulkan has been inspired from him. 38 | * [Travis Vroman (travisvroman)](https://github.com/travisvroman) - For his work on the [Kohi](https://github.com/travisvroman/kohi) engine. His return to the basics of C and hand-rolled libraries was inspiring and informative, and while I've chosen to stick with C++ for my project, I have learned a lot about low-level implementations from this series. 39 | * [Flax Engine](https://github.com/FlaxEngine/FlaxEngine) - For providing a high-quality, open-source engine that I can look to as an example of how to architect the systems needed to make a game engine run. 40 | * [Yan Chernikov (TheCherno)](https://github.com/TheCherno/) - For his work on the [Hazel](https://github.com/TheCherno/Hazel) engine (and the private development branch of Hazel). I was able to learn a lot about engine architecture and asset management from this example. 41 | * [Matthew Albrecht (mattparks)](https://github.com/mattparks) - For his work on the [Acid](https://github.com/EQMG/Acid) engine. The clean, modern C++ infrastructure he developed was instrumental in helping me work out this engine's structure. 42 | * [Panos Karabelas (PanosK92)](https://github.com/PanosK92) - For his work on the [Spartan](https://github.com/PanosK92/SpartanEngine) engine. The beautiful lighting and rendering techniques have driven me to strive for more, in the hopes I can one day match them. 43 | * [The Khronos Group (KhronosGroup)](https://github.com/KhronosGroup) - For their work on the Vulkan API itself, as well as the [glTF](https://www.khronos.org/gltf/) model format, [KTX](https://www.khronos.org/ktx/) texture format, and [sample models](https://github.com/KhronosGroup/glTF-Sample-Models). 44 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Utility/Time.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | using namespace std::chrono_literals; 7 | 8 | class Time { 9 | public: 10 | Time() = default; 11 | template 12 | constexpr Time(const std::chrono::duration& duration) 13 | : _value(std::chrono::duration_cast(duration).count()) {} 14 | 15 | [[nodiscard]] static Time Now() { 16 | static const auto epoch = std::chrono::high_resolution_clock::now(); 17 | 18 | return std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - epoch); 19 | } 20 | template 21 | [[nodiscard]] static constexpr Time Hours(const Rep hours) { 22 | return Minutes(hours * static_cast(60)); 23 | } 24 | template 25 | [[nodiscard]] static constexpr Time Minutes(const Rep minutes) { 26 | return Seconds(minutes * static_cast(60)); 27 | } 28 | template 29 | [[nodiscard]] static constexpr Time Seconds(const Rep seconds) { 30 | return Time(std::chrono::duration(seconds)); 31 | } 32 | template 33 | [[nodiscard]] static constexpr Time Milliseconds(const Rep milliseconds) { 34 | return Time(std::chrono::duration(milliseconds)); 35 | } 36 | template 37 | [[nodiscard]] static constexpr Time Microseconds(const Rep microseconds) { 38 | return Time(std::chrono::duration(microseconds)); 39 | } 40 | 41 | template 42 | [[nodiscard]] constexpr T AsSeconds() const { 43 | return static_cast(_value.count()) / static_cast(1'000'000); 44 | } 45 | template 46 | [[nodiscard]] constexpr T AsMilliseconds() const { 47 | return static_cast(_value.count()) / static_cast(1'000); 48 | } 49 | template 50 | [[nodiscard]] constexpr T AsMicroseconds() const { 51 | return static_cast(_value.count()); 52 | } 53 | 54 | template 55 | [[nodiscard]] constexpr operator std::chrono::duration() const { 56 | return std::chrono::duration_cast>(_value); 57 | } 58 | 59 | [[nodiscard]] constexpr auto operator<=>(const Time& other) const { 60 | return _value <=> other._value; 61 | } 62 | 63 | [[nodiscard]] friend constexpr Time operator+(const Time& a, const Time& b) { 64 | return a._value + b._value; 65 | } 66 | [[nodiscard]] friend constexpr Time operator-(const Time& a, const Time& b) { 67 | return a._value - b._value; 68 | } 69 | [[nodiscard]] friend constexpr Time operator*(const Time& a, float b) { 70 | return a._value * b; 71 | } 72 | [[nodiscard]] friend constexpr Time operator*(const Time& a, int64_t b) { 73 | return a._value * b; 74 | } 75 | [[nodiscard]] friend constexpr Time operator*(float a, const Time& b) { 76 | return a * b._value; 77 | } 78 | [[nodiscard]] friend constexpr Time operator*(int64_t a, const Time& b) { 79 | return a * b._value; 80 | } 81 | [[nodiscard]] friend constexpr Time operator/(const Time& a, float b) { 82 | return a._value / b; 83 | } 84 | [[nodiscard]] friend constexpr Time operator/(const Time& a, int64_t b) { 85 | return a._value / b; 86 | } 87 | [[nodiscard]] friend constexpr double operator/(const Time& a, const Time& b) { 88 | return static_cast(a._value.count()) / static_cast(b._value.count()); 89 | } 90 | 91 | private: 92 | std::chrono::microseconds _value = {}; 93 | }; 94 | 95 | class ElapsedTime { 96 | public: 97 | [[nodiscard]] const Time& Get() const { 98 | return _delta; 99 | } 100 | 101 | void Update() { 102 | _startTime = Time::Now(); 103 | _delta = _startTime - _lastTime; 104 | _lastTime = _startTime; 105 | } 106 | 107 | private: 108 | Time _delta; 109 | Time _lastTime; 110 | Time _startTime; 111 | }; 112 | } // namespace Luna 113 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/TextureFormat.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | class TextureFormatLayout { 8 | public: 9 | struct MipInfo { 10 | size_t Offset = 0; 11 | uint32_t Width = 1; 12 | uint32_t Height = 1; 13 | uint32_t Depth = 1; 14 | 15 | uint32_t BlockImageHeight = 0; 16 | uint32_t BlockRowLength = 0; 17 | uint32_t ImageHeight = 0; 18 | uint32_t RowLength = 0; 19 | }; 20 | 21 | [[nodiscard]] uint32_t GetArrayLayers() const noexcept { 22 | return _arrayLayers; 23 | } 24 | [[nodiscard]] uint32_t GetBlockDimX() const noexcept { 25 | return _blockDimX; 26 | } 27 | [[nodiscard]] uint32_t GetBlockDimY() const noexcept { 28 | return _blockDimY; 29 | } 30 | [[nodiscard]] uint32_t GetBlockStride() const noexcept { 31 | return _blockStride; 32 | } 33 | [[nodiscard]] uint32_t GetDepth(uint32_t mip = 0) const noexcept { 34 | return _mipInfos[mip].Depth; 35 | } 36 | [[nodiscard]] vk::Format GetFormat() const noexcept { 37 | return _format; 38 | } 39 | [[nodiscard]] uint32_t GetHeight(uint32_t mip = 0) const noexcept { 40 | return _mipInfos[mip].Height; 41 | } 42 | [[nodiscard]] size_t GetLayerSize(uint32_t mip) const noexcept { 43 | return _mipInfos[mip].BlockImageHeight * GetRowSize(mip); 44 | } 45 | [[nodiscard]] const MipInfo& GetMipInfo(uint32_t mip) const noexcept { 46 | return _mipInfos[mip]; 47 | } 48 | [[nodiscard]] uint32_t GetMipLevels() const noexcept { 49 | return _mipLevels; 50 | } 51 | [[nodiscard]] vk::ImageType GetImageType() const noexcept { 52 | return _imageType; 53 | } 54 | [[nodiscard]] size_t GetRequiredSize() const noexcept { 55 | return _requiredSize; 56 | } 57 | [[nodiscard]] size_t GetRowSize(uint32_t mip) const noexcept { 58 | return _mipInfos[mip].BlockRowLength * _blockStride; 59 | } 60 | [[nodiscard]] uint32_t GetWidth(uint32_t mip = 0) const noexcept { 61 | return _mipInfos[mip].Width; 62 | } 63 | [[nodiscard]] size_t LayerByteStride(uint32_t imageHeight, size_t rowByteStride) const noexcept { 64 | return ((imageHeight + _blockDimY - 1) / _blockDimY) * rowByteStride; 65 | } 66 | [[nodiscard]] size_t RowByteStride(uint32_t rowLength) const noexcept { 67 | return ((rowLength + _blockDimX - 1) / _blockDimX) * _blockStride; 68 | } 69 | 70 | [[nodiscard]] void* GetBuffer() noexcept { 71 | return _buffer; 72 | } 73 | 74 | [[nodiscard]] std::vector BuildBufferImageCopies() const; 75 | void Set1D(vk::Format format, uint32_t width, uint32_t arrayLayers = 1, uint32_t mipLevels = 1); 76 | void Set2D(vk::Format format, uint32_t width, uint32_t height, uint32_t arrayLayers = 1, uint32_t mipLevels = 1); 77 | void Set3D(vk::Format format, uint32_t width, uint32_t height, uint32_t depth, uint32_t mipLevels = 1); 78 | void SetBuffer(size_t size, void* buffer); 79 | 80 | [[nodiscard]] void* Data(uint32_t layer = 0, uint32_t mip = 0) const { 81 | const auto& mipInfo = _mipInfos[mip]; 82 | 83 | return _buffer + mipInfo.Offset + _blockStride * layer * mipInfo.BlockRowLength * mipInfo.BlockImageHeight; 84 | } 85 | template 86 | [[nodiscard]] T* DataGeneric(uint32_t x = 0, uint32_t y = 0, uint32_t sliceIndex = 0, uint32_t mip = 0) const { 87 | const auto& mipInfo = _mipInfos[mip]; 88 | 89 | return reinterpret_cast(_buffer + mipInfo.Offset) + 90 | (sliceIndex * mipInfo.BlockRowLength * mipInfo.BlockImageHeight) + (y * mipInfo.BlockRowLength) + x; 91 | } 92 | [[nodiscard]] void* DataOpaque(uint32_t x, uint32_t y, uint32_t sliceIndex, uint32_t mip = 0) const { 93 | return DataGeneric(x, y, sliceIndex, mip); 94 | } 95 | template 96 | [[nodiscard]] T* Data1D(uint32_t x, uint32_t layer = 0, uint32_t mip = 0) const { 97 | return DataGeneric(x, 0, layer, mip); 98 | } 99 | template 100 | [[nodiscard]] T* Data2D(uint32_t x, uint32_t y, uint32_t layer = 0, uint32_t mip = 0) const { 101 | return DataGeneric(x, y, layer, mip); 102 | } 103 | template 104 | [[nodiscard]] T* Data3D(uint32_t x, uint32_t y, uint32_t z, uint32_t mip = 0) const { 105 | return DataGeneric(x, y, z, mip); 106 | } 107 | 108 | static void FormatBlockDim(vk::Format format, uint32_t& width, uint32_t& height); 109 | [[nodiscard]] static uint32_t FormatBlockSize(vk::Format format, vk::ImageAspectFlags aspect); 110 | 111 | private: 112 | void FillMipInfo(uint32_t width, uint32_t height, uint32_t depth); 113 | 114 | uint8_t* _buffer = nullptr; 115 | size_t _bufferSize = 0; 116 | 117 | vk::Format _format = vk::Format::eUndefined; 118 | vk::ImageType _imageType = vk::ImageType::e2D; 119 | size_t _requiredSize = 0; 120 | 121 | uint32_t _arrayLayers = 1; 122 | uint32_t _blockDimX = 1; 123 | uint32_t _blockDimY = 1; 124 | uint32_t _blockStride = 1; 125 | uint32_t _mipLevels = 1; 126 | 127 | std::array _mipInfos; 128 | }; 129 | } // namespace Vulkan 130 | } // namespace Luna 131 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/RenderPass.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Luna { 8 | namespace Vulkan { 9 | struct RenderPassInfo { 10 | struct Subpass { 11 | uint32_t ColorAttachmentCount = 0; 12 | std::array ColorAttachments; 13 | uint32_t InputAttachmentCount = 0; 14 | std::array InputAttachments; 15 | uint32_t ResolveAttachmentCount = 0; 16 | std::array ResolveAttachments; 17 | DepthStencilUsage DepthStencil = DepthStencilUsage::ReadWrite; 18 | }; 19 | 20 | RenderPassFlags Flags = {}; 21 | 22 | uint32_t ColorAttachmentCount = 0; 23 | std::array ColorAttachments; 24 | std::array ClearColors; 25 | const ImageView* DepthStencilAttachment = nullptr; 26 | vk::ClearDepthStencilValue ClearDepthStencil = vk::ClearDepthStencilValue(1.0f, 0); 27 | 28 | uint32_t ClearAttachmentMask = 0; 29 | uint32_t LoadAttachmentMask = 0; 30 | uint32_t StoreAttachmentMask = 0; 31 | 32 | uint32_t BaseLayer = 0; 33 | uint32_t ArrayLayers = 1; 34 | 35 | vk::Rect2D RenderArea = vk::Rect2D( 36 | vk::Offset2D(0, 0), vk::Extent2D(std::numeric_limits::max(), std::numeric_limits::max())); 37 | 38 | std::vector Subpasses; 39 | }; 40 | 41 | class RenderPass : public HashedObject { 42 | public: 43 | RenderPass(Hash hash, Device& device, const RenderPassInfo& rpInfo); 44 | RenderPass(Hash hash, Device& device, const vk::RenderPassCreateInfo2& rpCI); 45 | RenderPass(const RenderPass&) = delete; 46 | RenderPass(RenderPass&&) = delete; 47 | RenderPass& operator=(const RenderPass&) = delete; 48 | RenderPass& operator=(RenderPass&&) = delete; 49 | ~RenderPass() noexcept; 50 | 51 | [[nodiscard]] uint32_t GetColorAttachmentCount(uint32_t subpass) const noexcept { 52 | return _subpasses[subpass].ColorAttachmentCount; 53 | } 54 | [[nodiscard]] const vk::AttachmentReference2& GetColorAttachment(uint32_t subpass, uint32_t att) const noexcept { 55 | return _subpasses[subpass].ColorAttachments[att]; 56 | } 57 | [[nodiscard]] uint32_t GetInputAttachmentCount(uint32_t subpass) const noexcept { 58 | return _subpasses[subpass].InputAttachmentCount; 59 | } 60 | [[nodiscard]] const vk::AttachmentReference2& GetInputAttachment(uint32_t subpass, uint32_t att) const noexcept { 61 | return _subpasses[subpass].InputAttachments[att]; 62 | } 63 | [[nodiscard]] vk::RenderPass GetRenderPass() const noexcept { 64 | return _renderPass; 65 | } 66 | [[nodiscard]] vk::SampleCountFlagBits GetSampleCount(uint32_t subpass) const noexcept { 67 | return _subpasses[subpass].SampleCount; 68 | } 69 | [[nodiscard]] size_t GetSubpassCount() const noexcept { 70 | return _subpasses.size(); 71 | } 72 | [[nodiscard]] bool HasDepth(uint32_t subpass) const noexcept { 73 | return _subpasses[subpass].DepthStencilAttachment.attachment != VK_ATTACHMENT_UNUSED && 74 | FormatHasDepth(_depthStencilFormat); 75 | } 76 | [[nodiscard]] bool HasStencil(uint32_t subpass) const noexcept { 77 | return _subpasses[subpass].DepthStencilAttachment.attachment != VK_ATTACHMENT_UNUSED && 78 | FormatHasStencil(_depthStencilFormat); 79 | } 80 | 81 | private: 82 | struct SubpassInfo { 83 | uint32_t ColorAttachmentCount = 0; 84 | std::array ColorAttachments; 85 | uint32_t InputAttachmentCount = 0; 86 | std::array InputAttachments; 87 | vk::AttachmentReference2 DepthStencilAttachment; 88 | vk::SampleCountFlagBits SampleCount = {}; 89 | }; 90 | 91 | void SetupSubpasses(const vk::RenderPassCreateInfo2& rpCI); 92 | 93 | Device& _device; 94 | vk::RenderPass _renderPass; 95 | std::array _colorAttachmentFormats; 96 | vk::Format _depthStencilFormat = vk::Format::eUndefined; 97 | std::vector _subpasses; 98 | }; 99 | 100 | class Framebuffer : public Cookie, public InternalSyncEnabled { 101 | public: 102 | Framebuffer(Device& device, const RenderPass& renderPass, const RenderPassInfo& rpInfo); 103 | ~Framebuffer() noexcept; 104 | 105 | const RenderPass& GetCompatibleRenderPass() const { 106 | return _renderPass; 107 | } 108 | const vk::Extent2D& GetExtent() const { 109 | return _extent; 110 | } 111 | vk::Framebuffer GetFramebuffer() const { 112 | return _framebuffer; 113 | } 114 | 115 | private: 116 | Device& _device; 117 | vk::Framebuffer _framebuffer; 118 | const RenderPass& _renderPass; 119 | RenderPassInfo _renderPassInfo; 120 | vk::Extent2D _extent = {0, 0}; 121 | }; 122 | 123 | struct FramebufferNode : TemporaryHashMapEnabled, IntrusiveListEnabled, Framebuffer { 124 | FramebufferNode(Device& device, const RenderPass& renderPass, const RenderPassInfo& rpInfo); 125 | }; 126 | 127 | struct TransientAttachmentNode : TemporaryHashMapEnabled, 128 | IntrusiveListEnabled { 129 | TransientAttachmentNode(ImageHandle image); 130 | 131 | ImageHandle Image; 132 | }; 133 | } // namespace Vulkan 134 | } // namespace Luna 135 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/Shader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Luna { 7 | namespace Vulkan { 8 | struct ShaderResourceLayout { 9 | std::array SetLayouts = {}; 10 | 11 | uint32_t BindlessSetMask = 0; 12 | uint32_t InputMask = 0; 13 | uint32_t OutputMask = 0; 14 | uint32_t SpecConstantMask = 0; 15 | uint32_t PushConstantSize = 0; 16 | }; 17 | 18 | struct ProgramResourceLayout { 19 | std::array SetLayouts = {}; 20 | 21 | std::array, MaxDescriptorSets> StagesForBindings = {}; 22 | std::array StagesForSets = {}; 23 | 24 | uint32_t AttributeMask = 0; 25 | uint32_t BindlessDescriptorSetMask = 0; 26 | uint32_t CombinedSpecConstantMask = 0; 27 | uint32_t DescriptorSetMask = 0; 28 | uint32_t RenderTargetMask = 0; 29 | std::array SpecConstantMask = {}; 30 | 31 | vk::PushConstantRange PushConstantRange = {}; 32 | Hash PushConstantLayoutHash = {}; 33 | }; 34 | 35 | class PipelineLayout : public HashedObject { 36 | public: 37 | PipelineLayout(Hash hash, Device& device, const ProgramResourceLayout& resourceLayout); 38 | ~PipelineLayout() noexcept; 39 | 40 | [[nodiscard]] DescriptorSetAllocator* GetAllocator(uint32_t set) const noexcept { 41 | return _setAllocators[set]; 42 | } 43 | [[nodiscard]] vk::PipelineLayout GetPipelineLayout() const noexcept { 44 | return _pipelineLayout; 45 | } 46 | [[nodiscard]] const ProgramResourceLayout& GetResourceLayout() const noexcept { 47 | return _resourceLayout; 48 | } 49 | [[nodiscard]] vk::DescriptorUpdateTemplate GetUpdateTemplate(uint32_t set) const noexcept { 50 | return _updateTemplates[set]; 51 | } 52 | 53 | private: 54 | Device& _device; 55 | vk::PipelineLayout _pipelineLayout; 56 | ProgramResourceLayout _resourceLayout; 57 | std::array _setAllocators; 58 | std::array _updateTemplates; 59 | }; 60 | 61 | class Shader : public HashedObject { 62 | public: 63 | Shader(Hash hash, Device& device, size_t codeSize, const void* code); 64 | ~Shader() noexcept; 65 | 66 | [[nodiscard]] ShaderResourceLayout GetResourceLayout() const noexcept { 67 | return _resourceLayout; 68 | } 69 | [[nodiscard]] vk::ShaderModule GetShader() const noexcept { 70 | return _shaderModule; 71 | } 72 | 73 | [[nodiscard]] static ShaderResourceLayout Reflect(size_t codeSize, const void* code); 74 | 75 | private: 76 | Device& _device; 77 | vk::ShaderModule _shaderModule; 78 | ShaderResourceLayout _resourceLayout; 79 | }; 80 | 81 | class ProgramBuilder { 82 | public: 83 | ProgramBuilder(Device& device); 84 | 85 | ProgramBuilder& Compute(Shader* compute); 86 | ProgramBuilder& Fragment(Shader* compute); 87 | ProgramBuilder& Vertex(Shader* compute); 88 | 89 | Program* Build() const; 90 | void Reset(); 91 | 92 | private: 93 | Device& _device; 94 | std::array _shaders; 95 | }; 96 | 97 | class Program : public HashedObject { 98 | public: 99 | Program(Hash hash, Device& device, const std::array& shaders); 100 | ~Program() noexcept; 101 | 102 | [[nodiscard]] const PipelineLayout* GetPipelineLayout() const noexcept { 103 | return _pipelineLayout; 104 | } 105 | [[nodiscard]] const Shader* GetShader(ShaderStage stage) const noexcept { 106 | return _shaders[int(stage)]; 107 | } 108 | 109 | Pipeline AddPipeline(Hash hash, const Pipeline& pipeline); 110 | Pipeline GetPipeline(Hash hash) const; 111 | void PromoteReadWriteToReadOnly(); 112 | 113 | private: 114 | void Bake(); 115 | 116 | Device& _device; 117 | std::array _shaders = {}; 118 | const PipelineLayout* _pipelineLayout = nullptr; 119 | VulkanCache> _pipelines; 120 | }; 121 | } // namespace Vulkan 122 | } // namespace Luna 123 | 124 | template <> 125 | struct std::hash { 126 | size_t operator()(const Luna::Vulkan::ProgramResourceLayout& layout) const { 127 | Luna::Hasher h; 128 | 129 | for (uint32_t i = 0; i < Luna::Vulkan::MaxDescriptorSets; ++i) { 130 | auto& set = layout.SetLayouts[i]; 131 | h(set.FloatMask); 132 | h(set.InputAttachmentMask); 133 | h(set.SampledTexelBufferMask); 134 | h(set.SampledImageMask); 135 | h(set.SamplerMask); 136 | h(set.SeparateImageMask); 137 | h(set.StorageBufferMask); 138 | h(set.StorageImageMask); 139 | h(set.StorageTexelBufferMask); 140 | h(set.UniformBufferMask); 141 | 142 | for (uint32_t j = 0; j < Luna::Vulkan::MaxDescriptorBindings; ++j) { 143 | h(set.ArraySizes[j]); 144 | h(layout.StagesForBindings[i][j]); 145 | } 146 | } 147 | 148 | h.Data(sizeof(layout.SpecConstantMask), layout.SpecConstantMask.data()); 149 | h(layout.PushConstantRange.stageFlags); 150 | h(layout.PushConstantRange.size); 151 | h(layout.AttributeMask); 152 | h(layout.RenderTargetMask); 153 | 154 | return static_cast(h.Get()); 155 | } 156 | }; 157 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Core/Input.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace Luna { 9 | enum class Key : int16_t { 10 | Unknown = -1, 11 | Space = 32, 12 | Apostrophe = 39, 13 | Comma = 44, 14 | Minus = 45, 15 | Period = 46, 16 | Slash = 47, 17 | _0 = 48, 18 | _1 = 49, 19 | _2 = 50, 20 | _3 = 51, 21 | _4 = 52, 22 | _5 = 53, 23 | _6 = 54, 24 | _7 = 55, 25 | _8 = 56, 26 | _9 = 57, 27 | Semicolon = 59, 28 | Equal = 61, 29 | A = 65, 30 | B = 66, 31 | C = 67, 32 | D = 68, 33 | E = 69, 34 | F = 70, 35 | G = 71, 36 | H = 72, 37 | I = 73, 38 | J = 74, 39 | K = 75, 40 | L = 76, 41 | M = 77, 42 | N = 78, 43 | O = 79, 44 | P = 80, 45 | Q = 81, 46 | R = 82, 47 | S = 83, 48 | T = 84, 49 | U = 85, 50 | V = 86, 51 | W = 87, 52 | X = 88, 53 | Y = 89, 54 | Z = 90, 55 | LeftBracket = 91, 56 | Backslash = 92, 57 | RightBracket = 93, 58 | GraveAccent = 96, 59 | World1 = 161, 60 | World2 = 162, 61 | Escape = 256, 62 | Enter = 257, 63 | Tab = 258, 64 | Backspace = 259, 65 | Insert = 260, 66 | Delete = 261, 67 | Right = 262, 68 | Left = 263, 69 | Down = 264, 70 | Up = 265, 71 | PageUp = 266, 72 | PageDown = 267, 73 | Home = 268, 74 | End = 269, 75 | CapsLock = 280, 76 | ScrollLock = 281, 77 | NumLock = 282, 78 | PrintScreen = 283, 79 | Pause = 284, 80 | F1 = 290, 81 | F2 = 291, 82 | F3 = 292, 83 | F4 = 293, 84 | F5 = 294, 85 | F6 = 295, 86 | F7 = 296, 87 | F8 = 297, 88 | F9 = 298, 89 | F10 = 299, 90 | F11 = 300, 91 | F12 = 301, 92 | F13 = 302, 93 | F14 = 303, 94 | F15 = 304, 95 | F16 = 305, 96 | F17 = 306, 97 | F18 = 307, 98 | F19 = 308, 99 | F20 = 309, 100 | F21 = 310, 101 | F22 = 311, 102 | F23 = 312, 103 | F24 = 313, 104 | F25 = 314, 105 | Numpad0 = 320, 106 | Numpad1 = 321, 107 | Numpad2 = 322, 108 | Numpad3 = 323, 109 | Numpad4 = 324, 110 | Numpad5 = 325, 111 | Numpad6 = 326, 112 | Numpad7 = 327, 113 | Numpad8 = 328, 114 | Numpad9 = 329, 115 | NumpadDecimal = 330, 116 | NumpadDivide = 331, 117 | NumpadMultiply = 332, 118 | NumpadSubtract = 333, 119 | NumpadAdd = 334, 120 | NumpadEnter = 335, 121 | NumpadEqual = 336, 122 | ShiftLeft = 340, 123 | ControlLeft = 341, 124 | AltLeft = 342, 125 | SuperLeft = 343, 126 | ShiftRight = 344, 127 | ControlRight = 345, 128 | AltRight = 346, 129 | SuperRight = 347, 130 | Menu = 348 131 | }; 132 | enum class InputAction : int32_t { Release = 0, Press = 1, Repeat = 2 }; 133 | enum class InputModBits : uint32_t { None = 0, Shift = 1 << 0, Control = 1 << 1, Alt = 1 << 2, Super = 1 << 3 }; 134 | using InputMods = Bitmask; 135 | enum class MouseButton : uint8_t { 136 | Button1 = 0, 137 | Button2 = 1, 138 | Button3 = 2, 139 | Button4 = 3, 140 | Button5 = 4, 141 | Button6 = 5, 142 | Button7 = 6, 143 | Button8 = 7, 144 | Left = Button1, 145 | Right = Button2, 146 | Middle = Button3 147 | }; 148 | 149 | class Input final { 150 | public: 151 | static InputAction GetButton(MouseButton button); 152 | static bool GetCursorHidden(); 153 | static glm::dvec2 GetCursorPosition(); 154 | static InputAction GetKey(Key key); 155 | static void SetCursorShape(MouseCursor cursor); 156 | static void SetCursorHidden(bool hidden); 157 | static void SetCursorPosition(const glm::dvec2& position); 158 | 159 | static void CharEvent(int c); 160 | static void DropEvent(const std::vector& paths); 161 | static void KeyEvent(Key key, InputAction action, InputMods mods); 162 | static void MouseButtonEvent(MouseButton button, InputAction action, InputMods mods); 163 | static void MouseMovedEvent(const glm::dvec2& position); 164 | static void MouseScrolledEvent(const glm::dvec2& wheelDelta); 165 | 166 | inline static Delegate OnChar; 167 | inline static Delegate&)> OnFilesDropped; 168 | inline static Delegate OnKey; 169 | inline static Delegate OnMouseButton; 170 | inline static Delegate OnMouseMoved; 171 | inline static Delegate OnMouseScrolled; 172 | }; 173 | } // namespace Luna 174 | 175 | template <> 176 | struct Luna::EnableBitmaskOperators : std::true_type {}; 177 | -------------------------------------------------------------------------------- /Luna/Source/Core/Window.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Luna { 8 | Window::Window(const std::string& title, int width, int height, bool show) { 9 | GLFWmonitor* monitor = glfwGetPrimaryMonitor(); 10 | const GLFWvidmode* videoMode = glfwGetVideoMode(monitor); 11 | width = std::clamp(width, 1, videoMode->width); 12 | height = std::clamp(height, 1, videoMode->height); 13 | 14 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 15 | glfwWindowHint(GLFW_MAXIMIZED, width == videoMode->width && height == videoMode->height ? GLFW_TRUE : GLFW_FALSE); 16 | glfwWindowHint(GLFW_VISIBLE, show ? GLFW_TRUE : GLFW_FALSE); 17 | 18 | _window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); 19 | if (!_window) { throw std::runtime_error("Failed to create GLFW window!"); } 20 | glfwSetWindowUserPointer(_window, this); 21 | 22 | if (glfwGetWindowAttrib(_window, GLFW_MAXIMIZED) == GLFW_FALSE) { CenterPosition(); } 23 | 24 | glfwSetCharCallback(_window, [](GLFWwindow* window, unsigned int c) { Input::CharEvent(c); }); 25 | glfwSetCursorPosCallback(_window, [](GLFWwindow* window, double x, double y) { Input::MouseMovedEvent({x, y}); }); 26 | glfwSetDropCallback(_window, [](GLFWwindow* window, int pathCount, const char** paths) { 27 | std::vector files; 28 | for (int i = 0; i < pathCount; ++i) { files.emplace_back(paths[i]); } 29 | Input::DropEvent(files); 30 | }); 31 | glfwSetKeyCallback(_window, [](GLFWwindow* window, int key, int scancode, int action, int mods) { 32 | Input::KeyEvent(Key(key), InputAction(action), InputMods(mods)); 33 | }); 34 | glfwSetMouseButtonCallback(_window, [](GLFWwindow* window, int button, int action, int mods) { 35 | Input::MouseButtonEvent(MouseButton(button), InputAction(action), InputMods(mods)); 36 | }); 37 | glfwSetScrollCallback(_window, [](GLFWwindow* window, double x, double y) { Input::MouseScrolledEvent({x, y}); }); 38 | 39 | _swapchain = MakeHandle(*this); 40 | 41 | if (show) { 42 | glfwShowWindow(_window); 43 | glfwFocusWindow(_window); 44 | } 45 | } 46 | 47 | Window::~Window() noexcept { 48 | _swapchain.Reset(); 49 | glfwDestroyWindow(_window); 50 | } 51 | 52 | VkSurfaceKHR Window::CreateSurface(VkInstance instance) const { 53 | VkSurfaceKHR surface = VK_NULL_HANDLE; 54 | const VkResult result = glfwCreateWindowSurface(instance, _window, nullptr, &surface); 55 | if (result != VK_SUCCESS) { return VK_NULL_HANDLE; } 56 | 57 | return surface; 58 | } 59 | 60 | glm::ivec2 Window::GetFramebufferSize() const { 61 | glm::ivec2 fbSize(0, 0); 62 | glfwGetFramebufferSize(_window, &fbSize.x, &fbSize.y); 63 | 64 | return fbSize; 65 | } 66 | 67 | GLFWwindow* Window::GetHandle() const { 68 | return _window; 69 | } 70 | 71 | glm::ivec2 Window::GetPosition() const { 72 | glm::ivec2 pos(0, 0); 73 | glfwGetWindowPos(_window, &pos.x, &pos.y); 74 | 75 | return pos; 76 | } 77 | 78 | Swapchain& Window::GetSwapchain() { 79 | return *_swapchain; 80 | } 81 | 82 | const Swapchain& Window::GetSwapchain() const { 83 | return *_swapchain; 84 | } 85 | 86 | Hash Window::GetSwapchainHash() const noexcept { 87 | return _swapchain->GetHash(); 88 | } 89 | 90 | glm::ivec2 Window::GetWindowSize() const { 91 | glm::ivec2 size(0, 0); 92 | glfwGetWindowSize(_window, &size.x, &size.y); 93 | 94 | return size; 95 | } 96 | 97 | bool Window::IsCloseRequested() const { 98 | return glfwWindowShouldClose(_window); 99 | } 100 | 101 | bool Window::IsFocused() const { 102 | return glfwGetWindowAttrib(_window, GLFW_FOCUSED) == GLFW_TRUE; 103 | } 104 | 105 | bool Window::IsMaximized() const { 106 | return glfwGetWindowAttrib(_window, GLFW_MAXIMIZED) == GLFW_TRUE; 107 | } 108 | 109 | bool Window::IsMinimized() const { 110 | return glfwGetWindowAttrib(_window, GLFW_ICONIFIED) == GLFW_TRUE; 111 | } 112 | 113 | void Window::CenterPosition() { 114 | GLFWmonitor* monitor = glfwGetPrimaryMonitor(); 115 | const GLFWvidmode* videoMode = glfwGetVideoMode(monitor); 116 | 117 | const auto size = GetWindowSize(); 118 | SetPosition((videoMode->width - size.x) / 2, (videoMode->height - size.y) / 2); 119 | } 120 | 121 | void Window::Close() { 122 | glfwSetWindowShouldClose(_window, GLFW_TRUE); 123 | } 124 | 125 | void Window::Hide() { 126 | glfwHideWindow(_window); 127 | } 128 | 129 | void Window::Maximize() { 130 | glfwMaximizeWindow(_window); 131 | } 132 | 133 | void Window::Minimize() { 134 | glfwIconifyWindow(_window); 135 | } 136 | 137 | void Window::Restore() { 138 | glfwRestoreWindow(_window); 139 | } 140 | 141 | void Window::SetCursor(MouseCursor cursor) { 142 | if (_cursor != cursor) { 143 | glfwSetCursor(_window, WindowManager::GetCursor(cursor)); 144 | _cursor = cursor; 145 | } 146 | } 147 | 148 | void Window::SetPosition(int x, int y) { 149 | glfwSetWindowPos(_window, x, y); 150 | } 151 | 152 | void Window::SetPosition(const glm::ivec2& pos) { 153 | SetPosition(pos.x, pos.y); 154 | } 155 | 156 | void Window::SetSize(int w, int h) { 157 | if (w <= 0 || h <= 0) { return; } 158 | 159 | glfwSetWindowSize(_window, w, h); 160 | } 161 | 162 | void Window::SetSize(const glm::ivec2& size) { 163 | SetSize(size.x, size.y); 164 | } 165 | 166 | void Window::SetTitle(const std::string& title) { 167 | glfwSetWindowTitle(_window, title.c_str()); 168 | } 169 | 170 | void Window::Show() { 171 | glfwShowWindow(_window); 172 | } 173 | } // namespace Luna 174 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Core/Filesystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Luna { 8 | class FileMapping; 9 | 10 | enum class FileMode { ReadOnly, WriteOnly, ReadWrite, WriteOnlyTransactional }; 11 | enum class FileNotifyType { FileChanged, FileDeleted, FileCreated }; 12 | enum class PathType { File, Directory, Special }; 13 | 14 | using FileNotifyHandle = int; 15 | 16 | struct FileNotifyInfo { 17 | Path Path; 18 | FileNotifyType Type; 19 | FileNotifyHandle Handle; 20 | }; 21 | 22 | struct FileStat { 23 | uint64_t Size; 24 | PathType Type; 25 | uint64_t LastModified; 26 | }; 27 | 28 | struct ListEntry { 29 | Path Path; 30 | PathType Type; 31 | }; 32 | 33 | class File : public ThreadSafeIntrusivePtrEnabled { 34 | public: 35 | virtual ~File() noexcept = default; 36 | 37 | IntrusivePtr Map(); 38 | 39 | virtual IntrusivePtr MapSubset(uint64_t offset, size_t range) = 0; 40 | virtual IntrusivePtr MapWrite(size_t range) = 0; 41 | virtual uint64_t GetSize() = 0; 42 | virtual void Unmap(void* mapped, size_t range) = 0; 43 | }; 44 | using FileHandle = IntrusivePtr; 45 | 46 | class FileMapping : public ThreadSafeIntrusivePtrEnabled { 47 | public: 48 | FileMapping( 49 | FileHandle handle, uint64_t fileOffset, void* mapped, size_t mappedSize, size_t mapOffset, size_t accessibleSize); 50 | ~FileMapping() noexcept; 51 | 52 | template 53 | inline const T* Data() const { 54 | void* ptr = static_cast(_mapped) + _mapOffset; 55 | 56 | return static_cast(ptr); 57 | } 58 | 59 | template 60 | inline T* MutableData() { 61 | void* ptr = static_cast(_mapped) + _mapOffset; 62 | 63 | return static_cast(ptr); 64 | } 65 | 66 | uint64_t GetFileOffset() const; 67 | uint64_t GetSize() const; 68 | 69 | private: 70 | FileHandle _file; 71 | uint64_t _fileOffset = 0; 72 | void* _mapped = nullptr; 73 | size_t _mappedSize = 0; 74 | size_t _mapOffset = 0; 75 | size_t _accessibleSize = 0; 76 | }; 77 | using FileMappingHandle = IntrusivePtr; 78 | 79 | class FilesystemBackend { 80 | public: 81 | virtual ~FilesystemBackend() noexcept = default; 82 | 83 | virtual std::filesystem::path GetFilesystemPath(const Path& path) const; 84 | virtual bool MoveReplace(const Path& dst, const Path& src); 85 | virtual bool MoveYield(const Path& dst, const Path& src); 86 | void SetProtocol(std::string_view proto); 87 | std::vector Walk(const Path& path); 88 | virtual bool Remove(const Path& path); 89 | 90 | virtual int GetWatchFD() const = 0; 91 | virtual std::vector List(const Path& path) = 0; 92 | virtual FileHandle Open(const Path& path, FileMode mode = FileMode::ReadOnly) = 0; 93 | virtual bool Stat(const Path& path, FileStat& stat) const = 0; 94 | virtual void UnwatchFile(FileNotifyHandle handle) = 0; 95 | virtual void Update() = 0; 96 | virtual FileNotifyHandle WatchFile(const Path& path, std::function func) = 0; 97 | 98 | protected: 99 | std::string _protocol; 100 | }; 101 | 102 | class Filesystem final { 103 | public: 104 | static bool Initialize(); 105 | static void Shutdown(); 106 | 107 | static FilesystemBackend* GetBackend(std::string_view proto = "file"); 108 | static void RegisterProtocol(std::string_view proto, std::unique_ptr&& backend); 109 | static void UnregisterProtocol(std::string_view proto); 110 | 111 | static bool Exists(const Path& path); 112 | static std::filesystem::path GetFilesystemPath(const Path& path); 113 | static std::vector List(const Path& path); 114 | static bool MoveReplace(const Path& dst, const Path& src); 115 | static bool MoveYield(const Path& dst, const Path& src); 116 | static FileHandle Open(const Path& path, FileMode mode = FileMode::ReadOnly); 117 | static FileMappingHandle OpenReadOnlyMapping(const Path& path); 118 | static FileMappingHandle OpenTransactionalMapping(const Path& path, size_t size); 119 | static FileMappingHandle OpenWriteOnlyMapping(const Path& path); 120 | static bool ReadFileToString(const Path& path, std::string& outStr); 121 | static bool Remove(const Path& path); 122 | static bool Stat(const Path& path, FileStat& outStat); 123 | static void Update(); 124 | static std::vector Walk(const Path& path); 125 | static bool WriteDataToFile(const Path& path, size_t size, const void* data); 126 | static bool WriteStringToFile(const Path& path, std::string_view str); 127 | }; 128 | 129 | class ScratchFilesystem : public FilesystemBackend { 130 | public: 131 | virtual int GetWatchFD() const override; 132 | virtual std::vector List(const Path& path) override; 133 | virtual FileHandle Open(const Path& path, FileMode mode = FileMode::ReadOnly) override; 134 | virtual bool Stat(const Path& path, FileStat& stat) const override; 135 | virtual void UnwatchFile(FileNotifyHandle handle) override; 136 | virtual void Update() override; 137 | virtual FileNotifyHandle WatchFile(const Path& path, std::function func) override; 138 | 139 | private: 140 | using ScratchFile = std::vector; 141 | 142 | std::unordered_map> _files; 143 | }; 144 | } // namespace Luna 145 | -------------------------------------------------------------------------------- /Luna/Source/Renderer/ShaderCompiler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace Luna { 7 | std::vector ShaderCompiler::Compile(std::string& error, 8 | const std::vector>& defines) { 9 | shaderc::Compiler compiler; 10 | shaderc::CompileOptions options; 11 | if (_processedSource.empty()) { 12 | error = "Must call Preprocess() before compiling!"; 13 | return {}; 14 | } 15 | 16 | for (const auto& def : defines) { options.AddMacroDefinition(def.first, std::to_string(def.second)); } 17 | options.SetOptimizationLevel(shaderc_optimization_level_performance); 18 | options.SetGenerateDebugInfo(); 19 | options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_3); 20 | options.SetSourceLanguage(shaderc_source_language_glsl); 21 | 22 | shaderc_shader_kind shaderKind; 23 | switch (_stage) { 24 | case Vulkan::ShaderStage::Vertex: 25 | shaderKind = shaderc_glsl_vertex_shader; 26 | break; 27 | case Vulkan::ShaderStage::TessellationControl: 28 | shaderKind = shaderc_glsl_tess_control_shader; 29 | break; 30 | case Vulkan::ShaderStage::TessellationEvaluation: 31 | shaderKind = shaderc_glsl_tess_evaluation_shader; 32 | break; 33 | case Vulkan::ShaderStage::Geometry: 34 | shaderKind = shaderc_glsl_geometry_shader; 35 | break; 36 | case Vulkan::ShaderStage::Fragment: 37 | shaderKind = shaderc_glsl_fragment_shader; 38 | break; 39 | case Vulkan::ShaderStage::Compute: 40 | shaderKind = shaderc_glsl_compute_shader; 41 | break; 42 | default: 43 | error = "Invalid shader stage."; 44 | return {}; 45 | } 46 | 47 | error.clear(); 48 | const auto result = 49 | compiler.CompileGlslToSpv(_processedSource.c_str(), shaderKind, _sourcePath.String().c_str(), options); 50 | if (result.GetCompilationStatus() != shaderc_compilation_status_success) { 51 | error = result.GetErrorMessage(); 52 | return {}; 53 | } 54 | 55 | return std::vector(result.cbegin(), result.cend()); 56 | } 57 | 58 | bool ShaderCompiler::Preprocess() { 59 | _dependencies.clear(); 60 | _processedSource.clear(); 61 | 62 | if (_source.empty()) { return false; } 63 | 64 | try { 65 | if (!Parse(_sourcePath, _source)) { return false; } 66 | } catch (const std::exception& e) { 67 | Log::Error("ShaderCompiler", "Failed to preprocess {} shader '{}': {}", _stage, _sourcePath, e.what()); 68 | 69 | return false; 70 | } 71 | _sourceHash = Hasher(_processedSource).Get(); 72 | 73 | return true; 74 | } 75 | 76 | void ShaderCompiler::SetIncludeDirectories(const std::vector& includeDirs) { 77 | _includeDirs = includeDirs; 78 | } 79 | 80 | void ShaderCompiler::SetSourceFromFile(const Path& path, Vulkan::ShaderStage stage) { 81 | if (!Filesystem::ReadFileToString(path, _source)) { return; } 82 | _sourcePath = path; 83 | _sourceHash = Hasher(_source).Get(); 84 | _stage = stage; 85 | } 86 | 87 | bool ShaderCompiler::Parse(const Path& sourcePath, const std::string& source) { 88 | auto lines = StringSplit(source, "\n"); 89 | uint32_t lineIndex = 1; 90 | size_t offset = 0; 91 | 92 | for (auto& line : lines) { 93 | // Strip comments from the code. (TODO: Handle block comments.) 94 | if ((offset = line.find("//")) != std::string::npos) { line = line.substr(0, offset); } 95 | 96 | // Handle include directives. 97 | if ((offset = line.find("#include \"")) != std::string::npos) { 98 | // Find the path of our included file. 99 | auto includePath = line.substr(offset + 10); 100 | if (!includePath.empty() && includePath.back() == '"') { includePath.pop_back(); } 101 | 102 | // Load the actual path and source code for the included file. 103 | const auto [includedPath, includedSource] = ResolveInclude(sourcePath, includePath); 104 | 105 | // Prevent including the same file twice. 106 | const auto it = std::find(_dependencies.begin(), _dependencies.end(), includedPath); 107 | if (it == _dependencies.end()) { 108 | // Use a #line directive to tell the compiler we're starting at line 1 of this included file. 109 | _processedSource += std::format("#line 1 \"{}\"\n", includedPath.String()); 110 | 111 | // Append the included file's source. 112 | if (!Parse(includedPath, includedSource)) { return false; } 113 | 114 | // Use another #line directive to tell the compiler to go back to where we were in this file. 115 | _processedSource += std::format("#line {} \"{}\"\n", lineIndex + 1, sourcePath.String()); 116 | 117 | // Add the included file to our list of dependencies. 118 | _dependencies.push_back(includedPath); 119 | } 120 | } else { 121 | _processedSource += line; 122 | _processedSource += '\n'; 123 | } 124 | 125 | ++lineIndex; 126 | } 127 | 128 | return true; 129 | } 130 | 131 | std::pair ShaderCompiler::ResolveInclude(const Path& sourcePath, const std::string& includePath) { 132 | // First try and load the include path as if it were relative to the source file. 133 | auto includedPath = sourcePath.Relative(includePath); 134 | std::string includedSource; 135 | if (Filesystem::ReadFileToString(includedPath, includedSource)) { return {includedPath, includedSource}; } 136 | 137 | // If that doesn't work, try loading it relative to each include directory. 138 | for (const auto& dir : _includeDirs) { 139 | includedPath = dir / includePath; 140 | if (Filesystem::ReadFileToString(includedPath, includedSource)) { return {includedPath, includedSource}; } 141 | } 142 | 143 | Log::Error("ShaderCompiler", "Failed to resolve included file '{}', included from '{}'.", includePath, sourcePath); 144 | 145 | throw std::runtime_error("Failed to resolve GLSL include!"); 146 | } 147 | } // namespace Luna 148 | -------------------------------------------------------------------------------- /Luna/Source/Core/WindowManager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace Luna { 6 | struct CursorShape { 7 | const char* const Pixels; 8 | const int Width; 9 | const int Height; 10 | const int X; 11 | const int Y; 12 | }; 13 | 14 | // clang-format off 15 | static constexpr const CursorShape ResizeNWSE = { 16 | .Pixels = "XXXXXXX " 17 | "X.....X " 18 | "X....X " 19 | "X...X " 20 | "X..X.X " 21 | "X.X X.X " 22 | "XX X.X " 23 | " X.X " 24 | " X.X " 25 | " X.X " 26 | " X.X XX" 27 | " X.X X.X" 28 | " X.X..X" 29 | " X...X" 30 | " X....X" 31 | " X.....X" 32 | " XXXXXXX", 33 | .Width = 17, 34 | .Height = 17, 35 | .X = 8, 36 | .Y = 8 37 | }; 38 | static constexpr const CursorShape ResizeNESW = { 39 | .Pixels = " XXXXXXX" 40 | " X.....X" 41 | " X....X" 42 | " X...X" 43 | " X.X..X" 44 | " X.X X.X" 45 | " X.X XX" 46 | " X.X " 47 | " X.X " 48 | " X.X " 49 | "XX X.X " 50 | "X.X X.X " 51 | "X..X.X " 52 | "X...X " 53 | "X....X " 54 | "X.....X " 55 | "XXXXXXX ", 56 | .Width = 17, 57 | .Height = 17, 58 | .X = 8, 59 | .Y = 8 60 | }; 61 | static constexpr const CursorShape ResizeAll = { 62 | .Pixels = " X " 63 | " X.X " 64 | " X...X " 65 | " X.....X " 66 | " X.......X " 67 | " XXXX.XXXX " 68 | " X.X " 69 | " XX X.X XX " 70 | " X.X X.X X.X " 71 | " X..X X.X X..X " 72 | " X...XXXXXX.XXXXXX...X " 73 | "X.....................X" 74 | " X...XXXXXX.XXXXXX...X " 75 | " X..X X.X X..X " 76 | " X.X X.X X.X " 77 | " XX X.X XX " 78 | " X.X " 79 | " XXXX.XXXX " 80 | " X.......X " 81 | " X.....X " 82 | " X...X " 83 | " X.X " 84 | " X ", 85 | .Width = 23, 86 | .Height = 23, 87 | .X = 11, 88 | .Y = 11 89 | }; 90 | // clang-format on 91 | 92 | static struct WindowManagerState { 93 | std::array Cursors; 94 | } State; 95 | 96 | static void GlfwErrorCallback(int errorCode, const char* errorDescription) { 97 | Log::Error("WindowManager", "GLFW Error {}: {}", errorCode, errorDescription); 98 | } 99 | 100 | bool WindowManager::Initialize() { 101 | glfwSetErrorCallback(GlfwErrorCallback); 102 | 103 | if (!glfwInit()) { return false; } 104 | if (!glfwVulkanSupported()) { return false; } 105 | 106 | State.Cursors[int(MouseCursor::Arrow)] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); 107 | State.Cursors[int(MouseCursor::IBeam)] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); 108 | State.Cursors[int(MouseCursor::Crosshair)] = glfwCreateStandardCursor(GLFW_CROSSHAIR_CURSOR); 109 | State.Cursors[int(MouseCursor::Hand)] = glfwCreateStandardCursor(GLFW_HAND_CURSOR); 110 | State.Cursors[int(MouseCursor::ResizeEW)] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); 111 | State.Cursors[int(MouseCursor::ResizeNS)] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); 112 | 113 | const auto AddCursor = [](const CursorShape& shape) -> GLFWcursor* { 114 | const int pixelCount = shape.Width * shape.Height; 115 | std::vector cursorPixels(pixelCount * 4); 116 | for (int i = 0; i < pixelCount; ++i) { 117 | if (shape.Pixels[i] == 'X') { 118 | cursorPixels[i * 4 + 0] = 0; 119 | cursorPixels[i * 4 + 1] = 0; 120 | cursorPixels[i * 4 + 2] = 0; 121 | cursorPixels[i * 4 + 3] = 255; 122 | } else if (shape.Pixels[i] == '.') { 123 | cursorPixels[i * 4 + 0] = 255; 124 | cursorPixels[i * 4 + 1] = 255; 125 | cursorPixels[i * 4 + 2] = 255; 126 | cursorPixels[i * 4 + 3] = 255; 127 | } else { 128 | cursorPixels[i * 4 + 0] = 0; 129 | cursorPixels[i * 4 + 1] = 0; 130 | cursorPixels[i * 4 + 2] = 0; 131 | cursorPixels[i * 4 + 3] = 0; 132 | } 133 | } 134 | const GLFWimage cursorImage{.width = shape.Width, .height = shape.Height, .pixels = cursorPixels.data()}; 135 | 136 | return glfwCreateCursor(&cursorImage, shape.X, shape.Y); 137 | }; 138 | 139 | State.Cursors[int(MouseCursor::ResizeNESW)] = AddCursor(ResizeNESW); 140 | State.Cursors[int(MouseCursor::ResizeNWSE)] = AddCursor(ResizeNWSE); 141 | State.Cursors[int(MouseCursor::ResizeAll)] = AddCursor(ResizeAll); 142 | 143 | return true; 144 | } 145 | 146 | void WindowManager::Update() { 147 | glfwPollEvents(); 148 | } 149 | 150 | void WindowManager::Shutdown() { 151 | for (auto cursor : State.Cursors) { glfwDestroyCursor(cursor); } 152 | 153 | glfwTerminate(); 154 | } 155 | 156 | GLFWcursor* WindowManager::GetCursor(MouseCursor cursor) { 157 | return State.Cursors[int(cursor)]; 158 | } 159 | 160 | std::vector WindowManager::GetRequiredInstanceExtensions() { 161 | uint32_t extensionCount = 0; 162 | const char** extensionNames = glfwGetRequiredInstanceExtensions(&extensionCount); 163 | 164 | return std::vector(extensionNames, extensionNames + extensionCount); 165 | } 166 | 167 | double WindowManager::GetTime() { 168 | return glfwGetTime(); 169 | } 170 | } // namespace Luna 171 | -------------------------------------------------------------------------------- /Luna/Source/Renderer/Swapchain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Luna { 8 | Swapchain::Swapchain(Window& window) : _window(&window) { 9 | const auto& device = Renderer::GetDevice(); 10 | _surface = window.CreateSurface(device.GetInstance()); 11 | 12 | const auto& deviceInfo = device.GetDeviceInfo(); 13 | const auto capabilities = deviceInfo.PhysicalDevice.getSurfaceCapabilitiesKHR(_surface); 14 | const auto formats = deviceInfo.PhysicalDevice.getSurfaceFormatsKHR(_surface); 15 | const auto presentModes = deviceInfo.PhysicalDevice.getSurfacePresentModesKHR(_surface); 16 | 17 | _format.format = vk::Format::eUndefined; 18 | for (const auto& format : formats) { 19 | if (format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear && 20 | (format.format == vk::Format::eR8G8B8A8Srgb || format.format == vk::Format::eB8G8R8A8Srgb || 21 | format.format == vk::Format::eA8B8G8R8SrgbPack32)) { 22 | _format = format; 23 | break; 24 | } 25 | } 26 | if (_format.format == vk::Format::eUndefined) { _format = formats[0]; } 27 | 28 | _presentMode = vk::PresentModeKHR::eFifo; 29 | for (const auto& presentMode : presentModes) { 30 | if (presentMode == vk::PresentModeKHR::eMailbox) { 31 | _presentMode = presentMode; 32 | break; 33 | } 34 | } 35 | } 36 | 37 | Swapchain::~Swapchain() noexcept { 38 | auto& device = Renderer::GetDevice(); 39 | 40 | _release.clear(); 41 | device.SetAcquireSemaphore(0, Vulkan::SemaphoreHandle{}); 42 | device.ConsumeReleaseSemaphore(); 43 | device.WaitIdle(); 44 | 45 | if (_swapchain) { device.GetDevice().destroySwapchainKHR(_swapchain); } 46 | if (_surface) { device.GetInstance().destroySurfaceKHR(_surface); } 47 | } 48 | 49 | bool Swapchain::Acquire() { 50 | if (!_swapchain || _suboptimal) { Recreate(); } 51 | if (_acquired != NotAcquired) { return true; } 52 | 53 | auto device = Renderer::GetDevice().GetDevice(); 54 | 55 | int retry = 0; 56 | while (retry < 3) { 57 | auto acquire = Renderer::GetDevice().RequestSemaphore("Swapchain Acquire"); 58 | 59 | try { 60 | const auto acquireResult = 61 | device.acquireNextImageKHR(_swapchain, std::numeric_limits::max(), acquire->GetSemaphore(), nullptr); 62 | 63 | if (acquireResult.result == vk::Result::eSuboptimalKHR) { _suboptimal = true; } 64 | 65 | acquire->SignalExternal(); 66 | acquire->SetForeignQueue(); 67 | _acquired = acquireResult.value; 68 | _release[_acquired].Reset(); 69 | Renderer::GetDevice().SetAcquireSemaphore(_acquired, acquire); 70 | 71 | break; 72 | } catch (const vk::OutOfDateKHRError& e) { 73 | Recreate(); 74 | ++retry; 75 | 76 | continue; 77 | } 78 | } 79 | 80 | return _acquired != NotAcquired; 81 | } 82 | 83 | void Swapchain::Present() { 84 | if (_acquired == NotAcquired) { return; } 85 | 86 | const auto& queues = Renderer::GetDevice().GetQueueInfo(); 87 | auto queue = queues.Queue(Vulkan::QueueType::Graphics); 88 | 89 | Renderer::GetDevice().EndFrame(); 90 | if (!Renderer::GetDevice().SwapchainAcquired()) { return; } 91 | 92 | auto release = Renderer::GetDevice().ConsumeReleaseSemaphore(); 93 | auto releaseSemaphore = release->GetSemaphore(); 94 | const vk::PresentInfoKHR presentInfo(releaseSemaphore, _swapchain, _acquired); 95 | vk::Result presentResult = vk::Result::eSuccess; 96 | try { 97 | presentResult = queue.presentKHR(presentInfo); 98 | if (presentResult == vk::Result::eSuboptimalKHR) { _suboptimal = true; } 99 | release->WaitExternal(); 100 | _release[_acquired] = std::move(release); 101 | } catch (const vk::OutOfDateKHRError& e) { Recreate(); } 102 | 103 | _acquired = NotAcquired; 104 | } 105 | 106 | void Swapchain::Recreate() { 107 | auto physicalDevice = Renderer::GetDevice().GetDeviceInfo().PhysicalDevice; 108 | auto device = Renderer::GetDevice().GetDevice(); 109 | const auto capabilities = physicalDevice.getSurfaceCapabilitiesKHR(_surface); 110 | const auto fbSize = _window->GetFramebufferSize(); 111 | 112 | if (fbSize.x == 0 || fbSize.y == 0 || capabilities.maxImageExtent.width == 0 || 113 | capabilities.maxImageExtent.height == 0) { 114 | return; 115 | } 116 | 117 | _extent = vk::Extent2D( 118 | glm::clamp(fbSize.x, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), 119 | glm::clamp(fbSize.y, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)); 120 | 121 | uint32_t imageCount = glm::max(3u, capabilities.minImageCount); 122 | if (capabilities.maxImageCount > 0) { imageCount = glm::min(imageCount, capabilities.maxImageCount); } 123 | 124 | const vk::SwapchainCreateInfoKHR swapchainCI( 125 | {}, 126 | _surface, 127 | imageCount, 128 | _format.format, 129 | _format.colorSpace, 130 | _extent, 131 | 1, 132 | vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst, 133 | vk::SharingMode::eExclusive, 134 | nullptr, 135 | vk::SurfaceTransformFlagBitsKHR::eIdentity, 136 | vk::CompositeAlphaFlagBitsKHR::eOpaque, 137 | _presentMode, 138 | VK_TRUE, 139 | _swapchain); 140 | auto newSwapchain = device.createSwapchainKHR(swapchainCI); 141 | 142 | if (_swapchain) { device.destroySwapchainKHR(_swapchain); } 143 | _swapchain = newSwapchain; 144 | 145 | _acquired = NotAcquired; 146 | _images = device.getSwapchainImagesKHR(_swapchain); 147 | _release.clear(); 148 | _release.resize(_images.size()); 149 | _suboptimal = false; 150 | 151 | Hasher h; 152 | h(_extent.width); 153 | h(_extent.height); 154 | h(_format.format); 155 | h(_format.colorSpace); 156 | h(_images.size()); 157 | _swapchainHash = h.Get(); 158 | 159 | Renderer::GetDevice().SetupSwapchain(_extent, _format, _images); 160 | } 161 | } // namespace Luna 162 | -------------------------------------------------------------------------------- /Resources/Shaders/CullMeshlets.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_EXT_shader_atomic_float : require 3 | #extension GL_EXT_scalar_block_layout : require 4 | 5 | #include "Common.glsli" 6 | #include "VisBuffer.glsli" 7 | 8 | struct GetMeshletUvBoundsParams { 9 | uint MeshletID; 10 | mat4 ViewProj; 11 | bool ClampNDC; 12 | bool ReverseZ; 13 | }; 14 | 15 | // layout(local_size_x = 64) in; 16 | 17 | layout(set = 0, binding = 0, scalar) uniform SceneBuffer { 18 | SceneData Scene; 19 | }; 20 | 21 | layout(set = 1, binding = 0) uniform ComputeUniforms { 22 | CullUniforms Uniforms; 23 | }; 24 | layout(set = 1, binding = 1, scalar) restrict readonly buffer MeshletBuffer { 25 | Meshlet Meshlets[]; 26 | }; 27 | layout(set = 1, binding = 2, scalar) restrict readonly buffer TransformBuffer { 28 | mat4 Transforms[]; 29 | }; 30 | layout(set = 1, binding = 3) uniform sampler2D HZB; 31 | 32 | layout(set = 1, binding = 4, scalar) restrict writeonly buffer VisibleMeshletsBuffer { 33 | uint VisibleMeshlets[]; 34 | }; 35 | layout(set = 1, binding = 5, scalar) restrict buffer CullTriangleDispatchBuffer { 36 | DispatchIndirectCommand CullTriangleDispatches[]; 37 | }; 38 | layout(set = 1, binding = 6, scalar) restrict buffer VisBufferStatsBuffer { 39 | VisBufferStats Stats; 40 | }; 41 | 42 | void GetMeshletUvBounds(GetMeshletUvBoundsParams params, out vec2 minXY, out vec2 maxXY, out float nearestZ, out bool intersectsNearPlane) { 43 | uint meshletId = params.MeshletID; 44 | uint instanceId = Meshlets[meshletId].InstanceID; 45 | mat4 transform = Transforms[instanceId]; 46 | vec3 aabbMin = Meshlets[meshletId].AABBMin; 47 | vec3 aabbMax = Meshlets[meshletId].AABBMax; 48 | vec4 aabbSize = vec4(aabbMax - aabbMin, 0.0); 49 | vec3[] aabbCorners = vec3[]( 50 | aabbMin, 51 | aabbMin + aabbSize.xww, 52 | aabbMin + aabbSize.wyw, 53 | aabbMin + aabbSize.wwz, 54 | aabbMin + aabbSize.xyw, 55 | aabbMin + aabbSize.wyz, 56 | aabbMin + aabbSize.xwz, 57 | aabbMin + aabbSize.xyz 58 | ); 59 | nearestZ = 0; 60 | 61 | minXY = vec2(1e20); 62 | maxXY = vec2(-1e20); 63 | mat4 mvp = params.ViewProj * transform; 64 | for (uint i = 0; i < 8; ++i) { 65 | vec4 clip = mvp * vec4(aabbCorners[i], 1.0); 66 | 67 | if (clip.w <= 0) { 68 | intersectsNearPlane = true; 69 | return; 70 | } 71 | 72 | clip.z = max(clip.z, 0.0); 73 | clip /= clip.w; 74 | if (params.ClampNDC) { 75 | clip.xy = clamp(clip.xy, -1.0, 1.0); 76 | } 77 | clip.xy = clip.xy * 0.5 + 0.5; 78 | minXY = min(minXY, clip.xy); 79 | maxXY = max(maxXY, clip.xy); 80 | nearestZ = clamp(max(nearestZ, clip.z), 0.0, 1.0); 81 | } 82 | 83 | intersectsNearPlane = false; 84 | } 85 | 86 | bool IsAABBInsidePlane(vec3 center, vec3 extent, vec4 plane) { 87 | vec3 normal = plane.xyz; 88 | float radius = dot(extent, abs(normal)); 89 | 90 | return (dot(normal, center) - plane.w) >= -radius; 91 | } 92 | 93 | bool CullMeshletFrustum(uint meshletId) { 94 | if ((Uniforms.Flags & CullMeshletFrustumBit) == 0) { return true; } 95 | 96 | uint instanceId = Meshlets[meshletId].InstanceID; 97 | mat4 transform = Transforms[instanceId]; 98 | vec3 aabbMin = Meshlets[meshletId].AABBMin; 99 | vec3 aabbMax = Meshlets[meshletId].AABBMax; 100 | vec3 aabbCenter = (aabbMin + aabbMax) / 2.0; 101 | vec3 aabbExtent = aabbMax - aabbCenter; 102 | vec3 worldAABBCenter = vec3(transform * vec4(aabbCenter, 1.0)); 103 | vec3 right = vec3(transform[0]) * aabbExtent.x; 104 | vec3 up = vec3(transform[1]) * aabbExtent.y; 105 | vec3 forward = vec3(-transform[2]) * aabbExtent.z; 106 | 107 | vec3 worldExtent = vec3( 108 | abs(dot(vec3(1, 0, 0), right)) + 109 | abs(dot(vec3(1, 0, 0), up)) + 110 | abs(dot(vec3(1, 0, 0), forward)), 111 | abs(dot(vec3(0, 1, 0), right)) + 112 | abs(dot(vec3(0, 1, 0), up)) + 113 | abs(dot(vec3(0, 1, 0), forward)), 114 | abs(dot(vec3(0, 0, 1), right)) + 115 | abs(dot(vec3(0, 0, 1), up)) + 116 | abs(dot(vec3(0, 0, 1), forward)) 117 | ); 118 | for (uint i = 0; i < 6; ++i) { 119 | if (!IsAABBInsidePlane(worldAABBCenter, worldExtent, Scene.FrustumPlanes[i])) { return false; } 120 | } 121 | 122 | return true; 123 | } 124 | 125 | bool CullQuadHiZ(vec2 minXY, vec2 maxXY, float nearestZ) { 126 | vec4 boxUvs = vec4(minXY, maxXY); 127 | boxUvs.y = 1.0 - boxUvs.y; 128 | boxUvs.w = 1.0 - boxUvs.w; 129 | vec2 hzbSize = vec2(textureSize(HZB, 0)); 130 | float width = (boxUvs.z - boxUvs.x) * hzbSize.x; 131 | float height = (boxUvs.w - boxUvs.y) * hzbSize.y; 132 | 133 | float level = ceil(log2(max(width, height))) + 1; 134 | float depth = textureLod(HZB, (boxUvs.xy + boxUvs.zw) * 0.5, level).r; 135 | 136 | if (nearestZ < depth) { return false; } 137 | 138 | return true; 139 | } 140 | 141 | void main() { 142 | uint meshletsPerBatch = gl_NumWorkGroups.x; 143 | uint batchId = gl_GlobalInvocationID.y; 144 | uint meshletOffset = batchId * meshletsPerBatch; 145 | uint meshletId = gl_WorkGroupID.x + meshletOffset; 146 | if (meshletId >= Uniforms.MeshletCount) { return; } 147 | 148 | bool isVisible = false; 149 | if (CullMeshletFrustum(meshletId)) { 150 | isVisible = true; 151 | 152 | if ((Uniforms.Flags & CullMeshletHiZBit) != 0) { 153 | GetMeshletUvBoundsParams params; 154 | params.MeshletID = meshletId; 155 | params.ViewProj = Scene.ViewProjection; 156 | params.ClampNDC = true; 157 | params.ReverseZ = true; 158 | 159 | vec2 minXY; 160 | vec2 maxXY; 161 | float nearestZ; 162 | bool intersectsNearPlane; 163 | GetMeshletUvBounds(params, minXY, maxXY, nearestZ, intersectsNearPlane); 164 | isVisible = intersectsNearPlane; 165 | if (!isVisible) { 166 | isVisible = CullQuadHiZ(minXY, maxXY, nearestZ + 0.0001); 167 | } 168 | } 169 | } 170 | 171 | if (isVisible) { 172 | uint index = atomicAdd(CullTriangleDispatches[batchId].x, 1); 173 | VisibleMeshlets[index + meshletOffset] = meshletId; 174 | atomicAdd(Stats.VisibleMeshlets, 1); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/DescriptorSet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | DescriptorSetAllocator::DescriptorSetNode::DescriptorSetNode(vk::DescriptorSet set) : Set(set) {} 8 | 9 | DescriptorSetAllocator::DescriptorSetAllocator(Hash hash, 10 | Device& device, 11 | const DescriptorSetLayout& layout, 12 | const vk::ShaderStageFlags* stagesForBindings) 13 | : HashedObject(hash), _device(device) { 14 | _bindless = layout.ArraySizes[0] == DescriptorSetLayout::UnsizedArray; 15 | 16 | const vk::DescriptorBindingFlags bindingFlags = vk::DescriptorBindingFlagBits::ePartiallyBound | 17 | vk::DescriptorBindingFlagBits::eUpdateAfterBind | 18 | vk::DescriptorBindingFlagBits::eVariableDescriptorCount; 19 | const vk::DescriptorSetLayoutBindingFlagsCreateInfo setBindingFlags(bindingFlags); 20 | vk::DescriptorSetLayoutCreateInfo layoutCI; 21 | 22 | if (!_bindless) { 23 | const uint32_t threadCount = Threading::GetThreadCount() + 1; 24 | for (uint32_t i = 0; i < threadCount; ++i) { _perThread.emplace_back(new PerThread()); } 25 | } 26 | 27 | std::vector bindings; 28 | 29 | if (_bindless) { 30 | layoutCI.flags |= vk::DescriptorSetLayoutCreateFlagBits::eUpdateAfterBindPool; 31 | layoutCI.pNext = &setBindingFlags; 32 | } 33 | 34 | for (uint32_t binding = 0; binding < MaxDescriptorBindings; ++binding) { 35 | const auto stages = stagesForBindings[binding]; 36 | if (!stages) { continue; } 37 | 38 | const uint32_t bindingMask = 1u << binding; 39 | const uint32_t arraySize = _bindless ? MaxBindlessDescriptors : layout.ArraySizes[binding]; 40 | const uint32_t poolArraySize = _bindless ? arraySize : arraySize * DescriptorSetsPerPool; 41 | 42 | if (layout.InputAttachmentMask & bindingMask) { 43 | bindings.push_back({binding, vk::DescriptorType::eInputAttachment, arraySize, stages, nullptr}); 44 | _poolSizes.push_back({vk::DescriptorType::eInputAttachment, poolArraySize}); 45 | } else if (layout.SampledImageMask & bindingMask) { 46 | bindings.push_back({binding, vk::DescriptorType::eCombinedImageSampler, arraySize, stages, nullptr}); 47 | _poolSizes.push_back({vk::DescriptorType::eCombinedImageSampler, poolArraySize}); 48 | } else if (layout.SampledTexelBufferMask & bindingMask) { 49 | bindings.push_back({binding, vk::DescriptorType::eUniformTexelBuffer, arraySize, stages, nullptr}); 50 | _poolSizes.push_back({vk::DescriptorType::eUniformTexelBuffer, poolArraySize}); 51 | } else if (layout.SamplerMask & bindingMask) { 52 | bindings.push_back({binding, vk::DescriptorType::eSampler, arraySize, stages, nullptr}); 53 | _poolSizes.push_back({vk::DescriptorType::eSampler, poolArraySize}); 54 | } else if (layout.SeparateImageMask & bindingMask) { 55 | bindings.push_back({binding, vk::DescriptorType::eSampledImage, arraySize, stages, nullptr}); 56 | _poolSizes.push_back({vk::DescriptorType::eSampledImage, poolArraySize}); 57 | } else if (layout.StorageBufferMask & bindingMask) { 58 | bindings.push_back({binding, vk::DescriptorType::eStorageBuffer, arraySize, stages, nullptr}); 59 | _poolSizes.push_back({vk::DescriptorType::eStorageBuffer, poolArraySize}); 60 | } else if (layout.StorageImageMask & bindingMask) { 61 | bindings.push_back({binding, vk::DescriptorType::eStorageImage, arraySize, stages, nullptr}); 62 | _poolSizes.push_back({vk::DescriptorType::eStorageImage, poolArraySize}); 63 | } else if (layout.StorageTexelBufferMask & bindingMask) { 64 | bindings.push_back({binding, vk::DescriptorType::eStorageTexelBuffer, arraySize, stages, nullptr}); 65 | _poolSizes.push_back({vk::DescriptorType::eStorageTexelBuffer, poolArraySize}); 66 | } else if (layout.UniformBufferMask & bindingMask) { 67 | bindings.push_back({binding, vk::DescriptorType::eUniformBufferDynamic, arraySize, stages, nullptr}); 68 | _poolSizes.push_back({vk::DescriptorType::eUniformBufferDynamic, poolArraySize}); 69 | } 70 | } 71 | 72 | layoutCI.setBindings(bindings); 73 | _setLayout = _device.GetDevice().createDescriptorSetLayout(layoutCI); 74 | Log::Trace("Vulkan", "Descriptor Set Layout created."); 75 | } 76 | 77 | DescriptorSetAllocator::~DescriptorSetAllocator() noexcept { 78 | if (_setLayout) { _device.GetDevice().destroyDescriptorSetLayout(_setLayout); } 79 | Clear(); 80 | } 81 | 82 | void DescriptorSetAllocator::BeginFrame() { 83 | if (!_bindless) { 84 | for (auto& t : _perThread) { t->ShouldBegin = true; } 85 | } 86 | } 87 | 88 | void DescriptorSetAllocator::Clear() { 89 | for (auto& t : _perThread) { 90 | t->SetNodes.Clear(); 91 | for (auto& pool : t->Pools) { 92 | _device.GetDevice().resetDescriptorPool(pool); 93 | _device.GetDevice().destroyDescriptorPool(pool); 94 | } 95 | t->Pools.clear(); 96 | } 97 | } 98 | 99 | std::pair DescriptorSetAllocator::Find(uint32_t threadIndex, Hash hash) { 100 | auto& state = *_perThread[threadIndex]; 101 | 102 | if (state.ShouldBegin) { 103 | state.SetNodes.BeginFrame(); 104 | state.ShouldBegin = false; 105 | } 106 | 107 | auto* node = state.SetNodes.Request(hash); 108 | if (node) { return {node->Set, true}; } 109 | 110 | node = state.SetNodes.RequestVacant(hash); 111 | if (node) { return {node->Set, false}; } 112 | 113 | const vk::DescriptorPoolCreateInfo poolCI({}, DescriptorSetsPerPool, _poolSizes); 114 | auto pool = _device.GetDevice().createDescriptorPool(poolCI); 115 | state.Pools.push_back(pool); 116 | Log::Trace("Vulkan", "Descriptor Pool created."); 117 | 118 | std::array layouts; 119 | std::fill(layouts.begin(), layouts.end(), _setLayout); 120 | const vk::DescriptorSetAllocateInfo setAI(pool, layouts); 121 | auto sets = _device.GetDevice().allocateDescriptorSets(setAI); 122 | for (auto set : sets) { state.SetNodes.MakeVacant(set); } 123 | 124 | return {state.SetNodes.RequestVacant(hash)->Set, false}; 125 | } 126 | } // namespace Vulkan 127 | } // namespace Luna 128 | -------------------------------------------------------------------------------- /Luna/Source/Vulkan/Buffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | void BufferDeleter::operator()(Buffer* buffer) { 8 | buffer->_device._bufferPool.Free(buffer); 9 | } 10 | 11 | Buffer::Buffer(Device& device, 12 | const BufferCreateInfo& createInfo, 13 | const void* initialData, 14 | const std::string& debugName) 15 | : Cookie(device), _device(device), _createInfo(createInfo), _debugName(debugName) { 16 | // First perform a few sanity checks. 17 | const bool zeroInitialize = createInfo.Flags & BufferCreateFlagBits::ZeroInitialize; 18 | 19 | // Convert our create info to Vulkan's create info 20 | const auto queueFamilies = _device._queueInfo.UniqueFamilies(); 21 | VkBufferCreateInfo bufferCI = {}; 22 | bufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; 23 | bufferCI.size = createInfo.Size; 24 | bufferCI.usage = static_cast( 25 | vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst | 26 | vk::BufferUsageFlagBits::eUniformTexelBuffer | vk::BufferUsageFlagBits::eStorageTexelBuffer | 27 | vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eStorageBuffer | 28 | vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eVertexBuffer | 29 | vk::BufferUsageFlagBits::eIndirectBuffer | vk::BufferUsageFlagBits::eShaderDeviceAddress); 30 | if (queueFamilies.size() > 1) { 31 | bufferCI.sharingMode = VK_SHARING_MODE_CONCURRENT; 32 | bufferCI.queueFamilyIndexCount = queueFamilies.size(); 33 | bufferCI.pQueueFamilyIndices = queueFamilies.data(); 34 | } else { 35 | bufferCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 36 | } 37 | 38 | // Set up Allocation info 39 | VmaAllocationCreateInfo bufferAI = {}; 40 | bufferAI.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; 41 | bufferAI.usage = VMA_MEMORY_USAGE_AUTO; 42 | if (createInfo.Domain == BufferDomain::Host) { 43 | bufferAI.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; 44 | } 45 | 46 | // Create and allocate our buffer 47 | VmaAllocationInfo allocationInfo = {}; 48 | { 49 | std::lock_guard lock(_device._lock.MemoryLock); 50 | 51 | VkBuffer buffer = VK_NULL_HANDLE; 52 | const VkResult createResult = 53 | vmaCreateBuffer(_device._allocator, &bufferCI, &bufferAI, &buffer, &_allocation, &allocationInfo); 54 | if (createResult != VK_SUCCESS) { 55 | Log::Error("Vulkan", "Failed to create buffer: {}", vk::to_string(vk::Result(createResult))); 56 | 57 | throw std::runtime_error("Failed to create buffer"); 58 | } 59 | _buffer = buffer; 60 | } 61 | 62 | if (_debugName.empty()) { 63 | Log::Trace("Vulkan", "Buffer created. ({})", Size(createInfo.Size)); 64 | } else { 65 | _device.SetObjectName(_buffer, _debugName); 66 | vmaSetAllocationName(_device._allocator, _allocation, _debugName.c_str()); 67 | Log::Trace("Vulkan", "Buffer \"{}\" created. ({})", _debugName, Size(createInfo.Size)); 68 | } 69 | 70 | const vk::BufferDeviceAddressInfo addressInfo(_buffer); 71 | _deviceAddress = _device.GetDevice().getBufferAddress(addressInfo); 72 | 73 | // If able, map the buffer memory 74 | const auto& memoryType = _device._deviceInfo.Memory.memoryTypes[allocationInfo.memoryType]; 75 | const bool memoryIsHostVisible(memoryType.propertyFlags & vk::MemoryPropertyFlagBits::eHostVisible); 76 | if (memoryIsHostVisible) { 77 | const VkResult mapResult = vmaMapMemory(_device._allocator, _allocation, &_mappedMemory); 78 | if (mapResult != VK_SUCCESS) { 79 | Log::Error("Vulkan", "Failed to map host-visible buffer: {}", vk::to_string(vk::Result(mapResult))); 80 | } 81 | } 82 | } 83 | 84 | Buffer::~Buffer() noexcept { 85 | if (_internalSync) { 86 | _device.DestroyBufferNoLock(_buffer); 87 | _device.FreeAllocationNoLock(_allocation, _mappedMemory != nullptr); 88 | } else { 89 | _device.DestroyBuffer(_buffer); 90 | _device.FreeAllocation(_allocation, _mappedMemory != nullptr); 91 | } 92 | } 93 | 94 | void Buffer::FillData(uint8_t data, vk::DeviceSize dataSize, vk::DeviceSize offset) { 95 | Log::Assert(dataSize + offset <= _createInfo.Size, 96 | "Vulkan::Buffer", 97 | "FillData: dataSize ({}) + offset ({}) is greater than buffer size ({})", 98 | dataSize, 99 | offset, 100 | _createInfo.Size); 101 | 102 | if (_mappedMemory) { 103 | uint8_t* mappedData = reinterpret_cast(_mappedMemory); 104 | std::memset(mappedData + offset, data, dataSize); 105 | } else { 106 | const std::string commandBufferName = std::format("{} Fill", _debugName.empty() ? "Buffer" : _debugName); 107 | CommandBufferHandle cmd = _device.RequestCommandBuffer(CommandBufferType::AsyncTransfer, commandBufferName); 108 | cmd->FillBuffer(*this, data, offset, dataSize); 109 | 110 | _device.SubmitStaging(cmd, {}, true); 111 | } 112 | } 113 | 114 | void Buffer::WriteData(const void* data, vk::DeviceSize dataSize, vk::DeviceSize offset) { 115 | Log::Assert(dataSize + offset <= _createInfo.Size, 116 | "Vulkan::Buffer", 117 | "WriteData: dataSize ({}) + offset ({}) is greater than buffer size ({})", 118 | dataSize, 119 | offset, 120 | _createInfo.Size); 121 | 122 | if (!data) { return; } 123 | 124 | if (_mappedMemory) { 125 | uint8_t* mappedData = reinterpret_cast(_mappedMemory); 126 | std::memcpy(mappedData + offset, data, dataSize); 127 | } else { 128 | CommandBufferHandle cmd; 129 | 130 | auto stagingCreateInfo = _createInfo; 131 | stagingCreateInfo.SetDomain(BufferDomain::Host); 132 | 133 | const std::string stagingBufferName = 134 | _debugName.empty() ? "Staging Buffer" : std::format("{} [Staging]", _debugName); 135 | const std::string commandBufferName = std::format("{} Copy", stagingBufferName); 136 | 137 | auto stagingBuffer = _device.CreateBuffer(stagingCreateInfo, data, stagingBufferName); 138 | 139 | cmd = _device.RequestCommandBuffer(CommandBufferType::AsyncTransfer, commandBufferName); 140 | cmd->CopyBuffer(*this, *stagingBuffer); 141 | 142 | _device.SubmitStaging(cmd, {}, true); 143 | } 144 | } 145 | } // namespace Vulkan 146 | } // namespace Luna 147 | -------------------------------------------------------------------------------- /Luna/Include/Luna/Vulkan/Enums.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Luna { 6 | namespace Vulkan { 7 | template 8 | struct VulkanEnumEnabled : public std::false_type {}; 9 | 10 | template 11 | concept IsVulkanEnum = VulkanEnumEnabled::value; 12 | 13 | template 14 | static const char* VulkanEnumToString(const T value) { 15 | static_assert(IsVulkanEnum, "VulkanEnumToString can only be used on Luna::Vulkan enums"); 16 | } 17 | 18 | #define BeginVulkanEnum(Name, ValueCount, ...) \ 19 | enum class Name { __VA_ARGS__ }; \ 20 | constexpr static const int Name##Count = ValueCount; \ 21 | template <> \ 22 | struct VulkanEnumEnabled : std::true_type {}; \ 23 | template <> \ 24 | const char* VulkanEnumToString(const Name value) { \ 25 | switch (value) { 26 | #define EnumCase(Name, Value) \ 27 | case Name::Value: \ 28 | return #Value; 29 | #define EndVulkanEnum() \ 30 | } \ 31 | return "Unknown"; \ 32 | } 33 | 34 | BeginVulkanEnum(QueueType, 3, Graphics, Transfer, Compute); 35 | EnumCase(QueueType, Graphics); 36 | EnumCase(QueueType, Transfer); 37 | EnumCase(QueueType, Compute); 38 | EndVulkanEnum(); 39 | 40 | BeginVulkanEnum(BufferDomain, 2, Device, Host); 41 | EnumCase(BufferDomain, Device); 42 | EnumCase(BufferDomain, Host); 43 | EndVulkanEnum(); 44 | 45 | BeginVulkanEnum(CommandBufferType, 46 | 4, 47 | Generic = int(QueueType::Graphics), 48 | AsyncCompute = int(QueueType::Compute), 49 | AsyncTransfer = int(QueueType::Transfer), 50 | AsyncGraphics = int(QueueTypeCount)); 51 | EnumCase(CommandBufferType, Generic); 52 | EnumCase(CommandBufferType, AsyncCompute); 53 | EnumCase(CommandBufferType, AsyncTransfer); 54 | EnumCase(CommandBufferType, AsyncGraphics); 55 | EndVulkanEnum(); 56 | 57 | BeginVulkanEnum(DepthStencilUsage, 3, None, ReadOnly, ReadWrite); 58 | EnumCase(DepthStencilUsage, None); 59 | EnumCase(DepthStencilUsage, ReadOnly); 60 | EnumCase(DepthStencilUsage, ReadWrite); 61 | EndVulkanEnum(); 62 | 63 | BeginVulkanEnum(ImageDomain, 2, Physical, Transient); 64 | EnumCase(ImageDomain, Physical); 65 | EnumCase(ImageDomain, Transient); 66 | EndVulkanEnum(); 67 | 68 | BeginVulkanEnum(ImageLayoutType, 2, Optimal, General); 69 | EnumCase(ImageLayoutType, Optimal); 70 | EnumCase(ImageLayoutType, General); 71 | EndVulkanEnum(); 72 | 73 | // Enum values MUST be in the same order as vk::ShaderStageFlagBits 74 | BeginVulkanEnum(ShaderStage, 12, Vertex, TessellationControl, TessellationEvaluation, Geometry, Fragment, Compute); 75 | EnumCase(ShaderStage, Vertex); 76 | EnumCase(ShaderStage, TessellationControl); 77 | EnumCase(ShaderStage, TessellationEvaluation); 78 | EnumCase(ShaderStage, Geometry); 79 | EnumCase(ShaderStage, Fragment); 80 | EnumCase(ShaderStage, Compute); 81 | EndVulkanEnum(); 82 | 83 | BeginVulkanEnum(StockSampler, 84 | 12, 85 | NearestClamp, 86 | LinearClamp, 87 | TrilinearClamp, 88 | NearestWrap, 89 | LinearWrap, 90 | TrilinearWrap, 91 | NearestShadow, 92 | LinearShadow, 93 | DefaultGeometryFilterClamp, 94 | DefaultGeometryFilterWrap, 95 | LinearMin, 96 | LinearMax); 97 | EnumCase(StockSampler, NearestClamp); 98 | EnumCase(StockSampler, LinearClamp); 99 | EnumCase(StockSampler, TrilinearClamp); 100 | EnumCase(StockSampler, NearestWrap); 101 | EnumCase(StockSampler, LinearWrap); 102 | EnumCase(StockSampler, TrilinearWrap); 103 | EnumCase(StockSampler, NearestShadow); 104 | EnumCase(StockSampler, LinearShadow); 105 | EnumCase(StockSampler, DefaultGeometryFilterClamp); 106 | EnumCase(StockSampler, DefaultGeometryFilterWrap); 107 | EnumCase(StockSampler, LinearMin); 108 | EnumCase(StockSampler, LinearMax); 109 | EndVulkanEnum(); 110 | 111 | BeginVulkanEnum(SwapchainRenderPassType, 3, ColorOnly, Depth, DepthStencil); 112 | EnumCase(SwapchainRenderPassType, ColorOnly); 113 | EnumCase(SwapchainRenderPassType, Depth); 114 | EnumCase(SwapchainRenderPassType, DepthStencil); 115 | EndVulkanEnum(); 116 | 117 | #undef EndVulkanEnum 118 | #undef EnumCase 119 | #undef BeginVulkanEnum 120 | 121 | enum class BufferCreateFlagBits : uint32_t { ZeroInitialize = 1 << 0 }; 122 | using BufferCreateFlags = Bitmask; 123 | 124 | enum class CommandBufferDirtyFlagBits { 125 | StaticState = 1 << 0, 126 | Pipeline = 1 << 1, 127 | Viewport = 1 << 2, 128 | Scissor = 1 << 3, 129 | DepthBias = 1 << 4, 130 | StencilReference = 1 << 5, 131 | StaticVertex = 1 << 6, 132 | PushConstants = 1 << 7, 133 | Dynamic = Viewport | Scissor | DepthBias | StencilReference 134 | }; 135 | using CommandBufferDirtyFlags = Bitmask; 136 | 137 | enum class ImageCreateFlagBits : uint32_t { 138 | GenerateMipmaps = 1 << 0, 139 | ForceArray = 1 << 1, 140 | MutableSrgb = 1 << 2, 141 | CubeCompatible = 1 << 3, 142 | ConcurrentQueueGraphics = 1 << 4, 143 | ConcurrentQueueAsyncCompute = 1 << 5, 144 | ConcurrentQueueAsyncGraphics = 1 << 6, 145 | ConcurrentQueueAsyncTransfer = 1 << 7 146 | }; 147 | using ImageCreateFlags = Bitmask; 148 | 149 | enum class ImageViewCreateFlagBits : uint32_t { ForceArray = 1 << 0 }; 150 | using ImageViewCreateFlags = Bitmask; 151 | 152 | enum class RenderPassFlagBits : uint32_t { 153 | ClearDepthStencil = 1 << 0, 154 | LoadDepthStencil = 1 << 1, 155 | StoreDepthStencil = 1 << 2, 156 | DepthStencilReadOnly = 1 << 3, 157 | EnableTransientStore = 1 << 4, 158 | EnableTransientLoad = 1 << 5 159 | }; 160 | using RenderPassFlags = Bitmask; 161 | } // namespace Vulkan 162 | } // namespace Luna 163 | 164 | template 165 | struct std::formatter : std::formatter { 166 | auto format(const T value, format_context& ctx) const -> decltype(ctx.out()) { 167 | return format_to(ctx.out(), "{}", Luna::Vulkan::VulkanEnumToString(value)); 168 | } 169 | }; 170 | 171 | template <> 172 | struct Luna::EnableBitmaskOperators : public std::true_type {}; 173 | template <> 174 | struct Luna::EnableBitmaskOperators : public std::true_type {}; 175 | template <> 176 | struct Luna::EnableBitmaskOperators : public std::true_type {}; 177 | template <> 178 | struct Luna::EnableBitmaskOperators : public std::true_type {}; 179 | template <> 180 | struct Luna::EnableBitmaskOperators : public std::true_type {}; 181 | -------------------------------------------------------------------------------- /Luna/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(Luna-Engine LANGUAGES CXX C) 2 | 3 | include(FetchContent) 4 | include(FindVulkan) 5 | 6 | set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) 7 | 8 | FetchContent_Declare(fastgltf 9 | GIT_REPOSITORY https://github.com/spnda/fastgltf.git 10 | GIT_TAG cf8c9fa7f73c5f1e3a7dec229e18245b1536d096) 11 | 12 | FetchContent_Declare(glfw 13 | GIT_REPOSITORY https://github.com/glfw/glfw.git 14 | GIT_TAG 3eaf1255b29fdf5c2895856c7be7d7185ef2b241) 15 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) 16 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 17 | set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) 18 | set(GLFW_INSTALL OFF CACHE BOOL "" FORCE) 19 | set(GLFW_VULKAN_STATIC OFF CACHE BOOL "" FORCE) 20 | 21 | FetchContent_Declare(glm 22 | GIT_REPOSITORY https://github.com/g-truc/glm.git 23 | GIT_TAG 586a402397dd35d66d7a079049856d1e2cbab300) 24 | 25 | FetchContent_Declare(glslang 26 | GIT_REPOSITORY https://github.com/KhronosGroup/glslang.git 27 | GIT_TAG a8d39f97cdf08cf7eac4ae40dfe7b41420a3be30) 28 | set(ENABLE_CTEST OFF CACHE BOOL "" FORCE) 29 | set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "" FORCE) 30 | set(ENABLE_HLSL ON CACHE BOOL "" FORCE) 31 | set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "" FORCE) 32 | 33 | FetchContent_Declare(imgui 34 | GIT_REPOSITORY https://github.com/ocornut/imgui 35 | GIT_TAG 96b5b1724b677aa03ca4a3d720367beba9a980a3) 36 | 37 | FetchContent_Declare(ktx 38 | GIT_REPOSITORY https://github.com/KhronosGroup/KTX-Software.git 39 | GIT_TAG d3ef5ed8b4d443804fc07b348cc503b30f7da938) 40 | set(KTX_FEATURE_DOC OFF CACHE BOOL "" FORCE) 41 | set(KTX_FEATURE_GL_UPLOAD OFF CACHE BOOL "" FORCE) 42 | set(KTX_FEATURE_JNI OFF CACHE BOOL "" FORCE) 43 | set(KTX_FEATURE_PY OFF CACHE BOOL "" FORCE) 44 | set(KTX_FEATURE_STATIC_LIBRARY ON CACHE BOOL "" FORCE) 45 | set(KTX_FEATURE_TOOLS OFF CACHE BOOL "" FORCE) 46 | set(KTX_FEATURE_TESTS OFF CACHE BOOL "" FORCE) 47 | 48 | FetchContent_Declare(meshopt 49 | GIT_REPOSITORY https://github.com/zeux/meshoptimizer.git 50 | GIT_TAG f7982fca28a8238180a7a6f77fa41cf736054b73) 51 | 52 | FetchContent_Declare(shaderc 53 | GIT_REPOSITORY https://github.com/google/shaderc.git 54 | GIT_TAG 0d6f72f3ec57fe68377363d9e810385e6b6e37e1) 55 | set(SHADERC_ENABLE_WERROR_COMPILE OFF CACHE BOOL "" FORCE) 56 | set(SHADERC_SKIP_COPYRIGHT_CHECK ON CACHE BOOL "" FORCE) 57 | set(SHADERC_SKIP_EXAMPLES ON CACHE BOOL "" FORCE) 58 | set(SHADERC_SKIP_INSTALL ON CACHE BOOL "" FORCE) 59 | set(SHADERC_SKIP_TESTS ON CACHE BOOL "" FORCE) 60 | 61 | FetchContent_Declare(spdlog 62 | GIT_REPOSITORY https://github.com/gabime/spdlog.git 63 | GIT_TAG ac55e60488032b9acde8940a5de099541c4515da) 64 | set(SPDLOG_USE_STD_FORMAT ON CACHE BOOL "" FORCE) 65 | 66 | FetchContent_Declare(spirv-cross 67 | GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Cross.git 68 | GIT_TAG 4818f7e7ef7b7078a3a7a5a52c4a338e0dda22f4) 69 | set(SPIRV_CROSS_CLI OFF CACHE BOOL "" FORCE) 70 | set(SPIRV_CROSS_ENABLE_GLSL ON CACHE BOOL "" FORCE) 71 | set(SPIRV_CROSS_ENABLE_HLSL OFF CACHE BOOL "" FORCE) 72 | set(SPIRV_CROSS_ENABLE_MSL OFF CACHE BOOL "" FORCE) 73 | set(SPIRV_CROSS_ENABLE_TESTS OFF CACHE BOOL "" FORCE) 74 | 75 | FetchContent_Declare(spirv-headers 76 | GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Headers 77 | GIT_TAG 38f39dae5baaa24431b24ac659054ebe972fa1e6) 78 | set(SPIRV_HEADERS_SKIP_EXAMPLES ON CACHE BOOL "" FORCE) 79 | set(SPIRV_HEADERS_SKIP_INSTALL ON CACHE BOOL "" FORCE) 80 | 81 | FetchContent_Declare(spirv-tools 82 | GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Tools 83 | GIT_TAG fbf047cc8b51d396d66c747f1eee472feb1b2441) 84 | set(SKIP_SPIRV_TOOLS_INSTALL ON CACHE BOOL "" FORCE) 85 | set(SPIRV_BUILD_FUZZER OFF CACHE BOOL "" FORCE) 86 | set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "" FORCE) 87 | set(SPIRV_SKIP_TESTS ON CACHE BOOL "" FORCE) 88 | set(SPIRV_WARN_EVERYTHING OFF CACHE BOOL "" FORCE) 89 | set(SPIRV_WERROR OFF CACHE BOOL "" FORCE) 90 | 91 | FetchContent_Declare(vma 92 | GIT_REPOSITORY https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git 93 | GIT_TAG 5e43c795daf43dd09398d8307212e85025215052) 94 | set(VMA_DYNAMIC_VULKAN_FUNCTIONS OFF CACHE BOOL "" FORCE) 95 | set(VMA_STATIC_VULKAN_FUNCTIONS OFF CACHE BOOL "" FORCE) 96 | 97 | FetchContent_MakeAvailable(fastgltf glfw glm imgui ktx meshopt spdlog spirv-headers spirv-tools spirv-cross glslang shaderc vma) 98 | 99 | set_target_properties(glslc PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 100 | set_target_properties(glslc_exe PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 101 | set_target_properties(shaderc_combined_genfile PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 102 | set_target_properties(shaderc_combined-pkg-config PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 103 | set_target_properties(shaderc_shared PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 104 | set_target_properties(shaderc_static-pkg-config PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 105 | set_target_properties(shaderc-pkg-config PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 106 | set_target_properties(spirv-cross-c PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 107 | set_target_properties(spirv-cross-reflect PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 108 | set_target_properties(spirv-cross-util PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 109 | set_target_properties(SPIRV-Tools-shared PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 110 | set_target_properties(SPVRemapper PROPERTIES EXCLUDE_FROM_ALL ON EXCLUDE_FROM_DEFAULT_BUILD ON) 111 | 112 | target_compile_options(ktx 113 | PRIVATE 114 | $<$,$,$>: 115 | -Wno-everything> 116 | $<$: 117 | -w> 118 | ) 119 | 120 | add_library(imgui STATIC) 121 | target_compile_definitions(imgui PUBLIC IMGUI_DISABLE_OBSOLETE_FUNCTIONS) 122 | target_include_directories(imgui PUBLIC "${imgui_SOURCE_DIR}") 123 | target_sources(imgui PRIVATE 124 | "${imgui_SOURCE_DIR}/imgui.cpp" 125 | "${imgui_SOURCE_DIR}/imgui_demo.cpp" 126 | "${imgui_SOURCE_DIR}/imgui_draw.cpp" 127 | "${imgui_SOURCE_DIR}/imgui_tables.cpp" 128 | "${imgui_SOURCE_DIR}/imgui_widgets.cpp") 129 | 130 | add_library(Luna STATIC) 131 | target_compile_definitions(Luna 132 | PUBLIC GLM_FORCE_DEPTH_ZERO_TO_ONE VK_ENABLE_BETA_EXTENSIONS VK_NO_PROTOTYPES 133 | PRIVATE GLFW_INCLUDE_VULKAN VULKAN_HPP_DISPATCH_LOADER_DYNAMIC) 134 | target_include_directories(Luna PUBLIC "Include") 135 | target_link_libraries(Luna 136 | PUBLIC imgui glm spdlog Vulkan::Headers VulkanMemoryAllocator 137 | PRIVATE fastgltf glfw ktx meshoptimizer shaderc spirv-cross-cpp) 138 | target_precompile_headers(Luna PRIVATE "Include/Luna/Common.hpp") 139 | 140 | add_subdirectory(Source) 141 | -------------------------------------------------------------------------------- /Resources/Shaders/CullTriangles.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_EXT_shader_atomic_float : require 3 | #extension GL_EXT_scalar_block_layout : require 4 | 5 | #include "Common.glsli" 6 | #include "VisBuffer.glsli" 7 | 8 | layout(local_size_x = 64) in; 9 | 10 | layout(push_constant) uniform PushConstant { 11 | uint BatchID; 12 | }; 13 | 14 | layout(set = 0, binding = 0, scalar) uniform SceneBuffer { 15 | SceneData Scene; 16 | }; 17 | 18 | layout(set = 1, binding = 0) uniform ComputeUniforms { 19 | CullUniforms Uniforms; 20 | }; 21 | layout(set = 1, binding = 1, scalar) restrict readonly buffer MeshletBuffer { 22 | Meshlet Meshlets[]; 23 | }; 24 | layout(set = 1, binding = 2, scalar) restrict readonly buffer ScenePositions { 25 | vec3 Positions[]; 26 | }; 27 | layout(set = 1, binding = 3, scalar) restrict readonly buffer SceneAttributes { 28 | Vertex Attributes[]; 29 | }; 30 | layout(set = 1, binding = 4, scalar) restrict readonly buffer SceneIndices { 31 | uint Indices[]; 32 | }; 33 | layout(set = 1, binding = 5, scalar) restrict readonly buffer SceneTriangles { 34 | uint8_t Triangles[]; 35 | }; 36 | layout(set = 1, binding = 6, scalar) restrict readonly buffer TransformBuffer { 37 | mat4 Transforms[]; 38 | }; 39 | layout(set = 1, binding = 7) uniform sampler2D HZB; 40 | layout(set = 1, binding = 8, scalar) restrict readonly buffer VisibleMeshletsBuffer { 41 | uint VisibleMeshlets[]; 42 | }; 43 | 44 | layout(set = 1, binding = 9, scalar) restrict writeonly buffer DrawIndirect { 45 | DrawIndexedIndirectCommand DrawCommands[]; 46 | }; 47 | layout(set = 1, binding = 10, scalar) restrict writeonly buffer MeshletIndicesBuffer { 48 | uint MeshletIndices[]; 49 | }; 50 | layout(set = 1, binding = 11, scalar) restrict buffer VisBufferStatsBuffer { 51 | VisBufferStats Stats; 52 | }; 53 | 54 | shared uint sBaseIndex; 55 | shared uint sPrimitivesPassed; 56 | shared mat4 sMVP; 57 | 58 | bool CullSmallTriangle(vec2 vertices[3]) { 59 | const uint SubpixelBits = 8; 60 | const uint SubpixelMask = 0xff; 61 | const uint SubpixelSamples = 1 << SubpixelBits; 62 | 63 | ivec2 minBB = ivec2(1 << 30, 1 << 30); 64 | ivec2 maxBB = ivec2(-(1 << 30), -(1 << 30)); 65 | 66 | for (uint i = 0; i < 3; ++i) { 67 | vec2 screenSpacePositionFP = vertices[i].xy * Scene.ViewportExtent; 68 | if (screenSpacePositionFP.x < -(1 << 23) || screenSpacePositionFP.x > (1 << 23) || screenSpacePositionFP.y < -(1 << 23) || screenSpacePositionFP.y > (1 << 23)) { return true; } 69 | 70 | ivec2 screenSpacePosition = ivec2(screenSpacePositionFP * SubpixelSamples); 71 | minBB = min(screenSpacePosition, minBB); 72 | maxBB = max(screenSpacePosition, maxBB); 73 | } 74 | 75 | return !( 76 | ( 77 | ((minBB.x & SubpixelMask) > SubpixelSamples / 2) 78 | && ((maxBB.x - ((minBB.x & ~SubpixelMask) + SubpixelSamples / 2)) < (SubpixelSamples - 1))) 79 | || ( 80 | ((minBB.y & SubpixelMask) > SubpixelSamples / 2) 81 | && ((maxBB.y - ((minBB.y & ~SubpixelMask) + SubpixelSamples / 2)) < (SubpixelSamples - 1)))); 82 | 83 | return true; 84 | } 85 | 86 | bool CullTriangle(uint meshletId, uint localId) { 87 | uint primitiveId = localId * 3; 88 | 89 | uint vertexOffset = Meshlets[meshletId].VertexOffset; 90 | uint indexOffset = Meshlets[meshletId].IndexOffset; 91 | uint triangleOffset = Meshlets[meshletId].TriangleOffset; 92 | 93 | uint triangle0 = uint(Triangles[triangleOffset + primitiveId + 0]); 94 | uint triangle1 = uint(Triangles[triangleOffset + primitiveId + 1]); 95 | uint triangle2 = uint(Triangles[triangleOffset + primitiveId + 2]); 96 | 97 | uint index0 = Indices[indexOffset + triangle0]; 98 | uint index1 = Indices[indexOffset + triangle1]; 99 | uint index2 = Indices[indexOffset + triangle2]; 100 | 101 | vec3 position0 = Positions[vertexOffset + index0]; 102 | vec3 position1 = Positions[vertexOffset + index1]; 103 | vec3 position2 = Positions[vertexOffset + index2]; 104 | 105 | Vertex vertex0 = Attributes[vertexOffset + index0]; 106 | Vertex vertex1 = Attributes[vertexOffset + index1]; 107 | Vertex vertex2 = Attributes[vertexOffset + index2]; 108 | 109 | vec4 posClip0 = sMVP * vec4(position0, 1.0); 110 | vec4 posClip1 = sMVP * vec4(position1, 1.0); 111 | vec4 posClip2 = sMVP * vec4(position2, 1.0); 112 | 113 | if ((Uniforms.Flags & CullTriangleBackfaceBit) != 0) { 114 | float det = determinant(mat3(posClip0.xyw, posClip1.xyw, posClip2.xyw)); 115 | if (det <= 0) { return false; } 116 | } 117 | 118 | vec3 posNdc0 = posClip0.xyz / posClip0.w; 119 | vec3 posNdc1 = posClip1.xyz / posClip1.w; 120 | vec3 posNdc2 = posClip2.xyz / posClip2.w; 121 | 122 | vec2 bboxNdcMin = min(posNdc0.xy, min(posNdc1.xy, posNdc2.xy)); 123 | vec2 bboxNdcMax = max(posNdc0.xy, max(posNdc1.xy, posNdc2.xy)); 124 | 125 | bool allBehind = posNdc0.z < 0 && posNdc1.z < 0 && posNdc2.z < 0; 126 | if (allBehind) { return false; } 127 | 128 | bool anyBehind = posNdc0.z < 0 || posNdc1.z < 0 || posNdc2.z < 0; 129 | if (anyBehind) { return true; } 130 | 131 | if (!RectIntersectRect(bboxNdcMin, bboxNdcMax, vec2(-1.0), vec2(1.0))) { return false; } 132 | 133 | vec2 posUv0 = posNdc0.xy * 0.5 + 0.5; 134 | vec2 posUv1 = posNdc1.xy * 0.5 + 0.5; 135 | vec2 posUv2 = posNdc2.xy * 0.5 + 0.5; 136 | if (!CullSmallTriangle(vec2[3](posUv0, posUv1, posUv2))) { return false; } 137 | 138 | return true; 139 | } 140 | 141 | void main() { 142 | uint meshletsPerBatch = gl_NumWorkGroups.x; 143 | uint batchId = BatchID; 144 | uint meshletOffset = batchId * Uniforms.MeshletsPerBatch; 145 | uint meshletId = VisibleMeshlets[gl_WorkGroupID.x + meshletOffset]; 146 | if (meshletId >= Uniforms.MeshletCount) { return; } 147 | 148 | uint localId = gl_LocalInvocationID.x; 149 | if (localId == 0) { 150 | uint instanceId = Meshlets[meshletId].InstanceID; 151 | sPrimitivesPassed = 0; 152 | sMVP = Scene.ViewProjection * Transforms[instanceId]; 153 | } 154 | 155 | barrier(); 156 | 157 | uint triangleCount = Meshlets[meshletId].TriangleCount; 158 | bool primitivePassed = false; 159 | uint activePrimitiveId = 0; 160 | if (localId < triangleCount) { 161 | primitivePassed = CullTriangle(meshletId, localId); 162 | if (primitivePassed) { 163 | activePrimitiveId = atomicAdd(sPrimitivesPassed, 1); 164 | } 165 | } 166 | 167 | barrier(); 168 | 169 | if (localId == 0) { 170 | sBaseIndex = atomicAdd(DrawCommands[batchId].IndexCount, sPrimitivesPassed * 3); 171 | atomicAdd(Stats.VisibleTriangles, sPrimitivesPassed); 172 | } 173 | 174 | barrier(); 175 | 176 | if (primitivePassed) { 177 | uint triangleId = localId * 3; 178 | uint indexOffset = sBaseIndex + (batchId * Uniforms.IndicesPerBatch) + activePrimitiveId * 3; 179 | MeshletIndices[indexOffset + 0] = (meshletId << MeshletPrimitiveBits) | ((triangleId + 0) & MeshletPrimitiveMask); 180 | MeshletIndices[indexOffset + 1] = (meshletId << MeshletPrimitiveBits) | ((triangleId + 1) & MeshletPrimitiveMask); 181 | MeshletIndices[indexOffset + 2] = (meshletId << MeshletPrimitiveBits) | ((triangleId + 2) & MeshletPrimitiveMask); 182 | } 183 | } 184 | --------------------------------------------------------------------------------