diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 741f8a2c..d42e5b89 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -2,6 +2,22 @@ cmake_minimum_required(VERSION 3.15) project(XCEngineLib) set(CMAKE_CXX_STANDARD 17) +set(XCENGINE_VULKAN_SDK_HINT "$ENV{VULKAN_SDK}") +if(NOT EXISTS "${XCENGINE_VULKAN_SDK_HINT}/Lib/vulkan-1.lib") + file(GLOB XCENGINE_VULKAN_SDK_DIRS "D:/VulkanSDK/*") + if(XCENGINE_VULKAN_SDK_DIRS) + list(SORT XCENGINE_VULKAN_SDK_DIRS COMPARE NATURAL ORDER DESCENDING) + list(GET XCENGINE_VULKAN_SDK_DIRS 0 XCENGINE_VULKAN_SDK_HINT) + endif() +endif() + +if(EXISTS "${XCENGINE_VULKAN_SDK_HINT}/Lib/vulkan-1.lib") + set(Vulkan_ROOT "${XCENGINE_VULKAN_SDK_HINT}") + list(APPEND CMAKE_PREFIX_PATH "${XCENGINE_VULKAN_SDK_HINT}") +endif() + +find_package(Vulkan REQUIRED) + set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(XCEngine STATIC @@ -133,6 +149,24 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/D3D12/D3D12ResourceView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/D3D12/D3D12QueryHeap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/D3D12/D3D12RenderPass.cpp + + # Vulkan RHI + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanCommon.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanTexture.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanResourceView.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanFence.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanCommandQueue.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanCommandList.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanDevice.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanScreenshot.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanTexture.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanResourceView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanCommandQueue.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanCommandList.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanSwapChain.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanDevice.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanScreenshot.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/D3D12/D3D12Framebuffer.cpp # OpenGL RHI @@ -372,6 +406,7 @@ target_include_directories(XCEngine PUBLIC target_link_libraries(XCEngine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/third_party/assimp/lib/assimp-vc143-mt.lib + Vulkan::Vulkan ) if(MSVC) @@ -380,4 +415,7 @@ else() target_compile_options(XCEngine PRIVATE -Wall) endif() -target_compile_definitions(XCEngine PRIVATE XCENGINE_SUPPORT_OPENGL) +target_compile_definitions(XCEngine PRIVATE + XCENGINE_SUPPORT_OPENGL + XCENGINE_SUPPORT_VULKAN +) diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h b/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h new file mode 100644 index 00000000..cdf91e3d --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h @@ -0,0 +1,71 @@ +#pragma once + +#include "XCEngine/RHI/RHICommandList.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +namespace XCEngine { +namespace RHI { + +class VulkanDevice; +class VulkanTexture; + +class VulkanCommandList : public RHICommandList { +public: + VulkanCommandList() = default; + ~VulkanCommandList() override; + + bool Initialize(VulkanDevice* device); + + void Shutdown() override; + void Reset() override; + void Close() override; + + void TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) override; + + void BeginRenderPass(class RHIRenderPass* renderPass, class RHIFramebuffer* framebuffer, + const Rect& renderArea, uint32_t clearValueCount, const ClearValue* clearValues) override; + void EndRenderPass() override; + + void SetShader(RHIShader* shader) override; + void SetPipelineState(RHIPipelineState* pso) override; + void SetGraphicsDescriptorSets(uint32_t firstSet, uint32_t count, RHIDescriptorSet** descriptorSets, RHIPipelineLayout* pipelineLayout) override; + void SetComputeDescriptorSets(uint32_t firstSet, uint32_t count, RHIDescriptorSet** descriptorSets, RHIPipelineLayout* pipelineLayout) override; + void SetPrimitiveTopology(PrimitiveTopology topology) override; + void SetViewport(const Viewport& viewport) override; + void SetViewports(uint32_t count, const Viewport* viewports) override; + void SetScissorRect(const Rect& rect) override; + void SetScissorRects(uint32_t count, const Rect* rects) override; + void SetRenderTargets(uint32_t count, RHIResourceView** renderTargets, RHIResourceView* depthStencil = nullptr) override; + + void SetStencilRef(uint8_t ref) override; + void SetBlendFactor(const float factor[4]) override; + + void SetVertexBuffers(uint32_t startSlot, uint32_t count, RHIResourceView** buffers, const uint64_t* offsets, const uint32_t* strides) override; + void SetIndexBuffer(RHIResourceView* buffer, uint64_t offset) override; + + void Draw(uint32_t vertexCount, uint32_t instanceCount = 1, uint32_t startVertex = 0, uint32_t startInstance = 0) override; + void DrawIndexed(uint32_t indexCount, uint32_t instanceCount = 1, uint32_t startIndex = 0, int32_t baseVertex = 0, uint32_t startInstance = 0) override; + + void Clear(float r, float g, float b, float a, uint32_t buffers) override; + void ClearRenderTarget(RHIResourceView* renderTarget, const float color[4]) override; + void ClearDepthStencil(RHIResourceView* depthStencil, float depth, uint8_t stencil) override; + + void CopyResource(RHIResourceView* dst, RHIResourceView* src) override; + void Dispatch(uint32_t x, uint32_t y, uint32_t z) override; + + void* GetNativeHandle() override { return m_commandBuffer; } + + VkCommandBuffer GetCommandBuffer() const { return m_commandBuffer; } + +private: + void TransitionTexture(VulkanTexture* texture, ResourceStates newState); + + VulkanDevice* m_device = nullptr; + VkCommandPool m_commandPool = VK_NULL_HANDLE; + VkCommandBuffer m_commandBuffer = VK_NULL_HANDLE; + RHIResourceView* m_currentColorTarget = nullptr; + RHIResourceView* m_currentDepthTarget = nullptr; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanCommandQueue.h b/engine/include/XCEngine/RHI/Vulkan/VulkanCommandQueue.h new file mode 100644 index 00000000..6bb7bc7f --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanCommandQueue.h @@ -0,0 +1,43 @@ +#pragma once + +#include "XCEngine/RHI/RHICommandQueue.h" +#include "XCEngine/RHI/RHIFence.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +namespace XCEngine { +namespace RHI { + +class VulkanDevice; + +class VulkanCommandQueue : public RHICommandQueue { +public: + VulkanCommandQueue() = default; + ~VulkanCommandQueue() override = default; + + bool Initialize(VulkanDevice* device, CommandQueueType type); + + void Shutdown() override; + void ExecuteCommandLists(uint32_t count, void** lists) override; + void Signal(RHIFence* fence, uint64_t value) override; + void Wait(RHIFence* fence, uint64_t value) override; + uint64_t GetCompletedValue() override; + void WaitForIdle() override; + + CommandQueueType GetType() const override { return m_type; } + uint64_t GetTimestampFrequency() const override { return 1000000000ull; } + + void* GetNativeHandle() override { return m_queue; } + void WaitForPreviousFrame() override {} + uint64_t GetCurrentFrame() const override { return m_currentFrame; } + + VkQueue GetQueue() const { return m_queue; } + +private: + VulkanDevice* m_device = nullptr; + VkQueue m_queue = VK_NULL_HANDLE; + CommandQueueType m_type = CommandQueueType::Direct; + uint64_t m_currentFrame = 0; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h b/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h new file mode 100644 index 00000000..7cf03681 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h @@ -0,0 +1,85 @@ +#pragma once + +#ifndef VK_USE_PLATFORM_WIN32_KHR +#define VK_USE_PLATFORM_WIN32_KHR +#endif + +#include + +#include "XCEngine/RHI/RHIEnums.h" + +#include + +namespace XCEngine { +namespace RHI { + +inline std::wstring WidenAscii(const char* value) { + if (value == nullptr) { + return {}; + } + + const std::string ascii(value); + return std::wstring(ascii.begin(), ascii.end()); +} + +inline VkFormat ToVulkanFormat(Format format) { + switch (format) { + case Format::R8G8B8A8_UNorm: + return VK_FORMAT_R8G8B8A8_UNORM; + case Format::D24_UNorm_S8_UInt: + return VK_FORMAT_D24_UNORM_S8_UINT; + case Format::D32_Float: + return VK_FORMAT_D32_SFLOAT; + case Format::R32_UInt: + return VK_FORMAT_R32_UINT; + case Format::R32G32_Float: + return VK_FORMAT_R32G32_SFLOAT; + case Format::R32G32B32A32_Float: + return VK_FORMAT_R32G32B32A32_SFLOAT; + default: + return VK_FORMAT_UNDEFINED; + } +} + +inline Format ToRHIFormat(VkFormat format) { + switch (format) { + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_B8G8R8A8_UNORM: + return Format::R8G8B8A8_UNorm; + case VK_FORMAT_D24_UNORM_S8_UINT: + return Format::D24_UNorm_S8_UInt; + case VK_FORMAT_D32_SFLOAT: + return Format::D32_Float; + case VK_FORMAT_R32_UINT: + return Format::R32_UInt; + case VK_FORMAT_R32G32_SFLOAT: + return Format::R32G32_Float; + case VK_FORMAT_R32G32B32A32_SFLOAT: + return Format::R32G32B32A32_Float; + default: + return Format::Unknown; + } +} + +inline VkImageAspectFlags GetImageAspectMask(Format format) { + switch (format) { + case Format::D24_UNorm_S8_UInt: + return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + case Format::D32_Float: + case Format::D16_UNorm: + return VK_IMAGE_ASPECT_DEPTH_BIT; + default: + return VK_IMAGE_ASPECT_COLOR_BIT; + } +} + +inline uint32_t ResolveVulkanApiMajor(uint32_t apiVersion) { + return VK_API_VERSION_MAJOR(apiVersion); +} + +inline uint32_t ResolveVulkanApiMinor(uint32_t apiVersion) { + return VK_API_VERSION_MINOR(apiVersion); +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanDevice.h b/engine/include/XCEngine/RHI/Vulkan/VulkanDevice.h new file mode 100644 index 00000000..c90721d5 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanDevice.h @@ -0,0 +1,74 @@ +#pragma once + +#include "XCEngine/RHI/RHIDevice.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +namespace XCEngine { +namespace RHI { + +class VulkanCommandQueue; +class VulkanCommandList; + +class VulkanDevice : public RHIDevice { +public: + VulkanDevice() = default; + ~VulkanDevice() override; + + bool Initialize(const RHIDeviceDesc& desc) override; + void Shutdown() override; + + RHIBuffer* CreateBuffer(const BufferDesc& desc) override; + RHITexture* CreateTexture(const TextureDesc& desc) override; + RHITexture* CreateTexture(const TextureDesc& desc, const void* initialData, size_t initialDataSize, uint32_t rowPitch = 0) override; + RHISwapChain* CreateSwapChain(const SwapChainDesc& desc, RHICommandQueue* presentQueue) override; + RHICommandList* CreateCommandList(const CommandListDesc& desc) override; + RHICommandQueue* CreateCommandQueue(const CommandQueueDesc& desc) override; + RHIShader* CreateShader(const ShaderCompileDesc& desc) override; + RHIPipelineState* CreatePipelineState(const GraphicsPipelineDesc& desc) override; + RHIPipelineLayout* CreatePipelineLayout(const RHIPipelineLayoutDesc& desc) override; + RHIFence* CreateFence(const FenceDesc& desc) override; + RHISampler* CreateSampler(const SamplerDesc& desc) override; + + RHIRenderPass* CreateRenderPass(uint32_t colorAttachmentCount, const AttachmentDesc* colorAttachments, const AttachmentDesc* depthStencilAttachment) override; + RHIFramebuffer* CreateFramebuffer(class RHIRenderPass* renderPass, uint32_t width, uint32_t height, uint32_t colorAttachmentCount, RHIResourceView** colorAttachments, RHIResourceView* depthStencilAttachment) override; + + RHIDescriptorPool* CreateDescriptorPool(const DescriptorPoolDesc& desc) override; + RHIDescriptorSet* CreateDescriptorSet(RHIDescriptorPool* pool, const DescriptorSetLayoutDesc& layout) override; + + RHIResourceView* CreateVertexBufferView(RHIBuffer* buffer, const ResourceViewDesc& desc) override; + RHIResourceView* CreateIndexBufferView(RHIBuffer* buffer, const ResourceViewDesc& desc) override; + RHIResourceView* CreateRenderTargetView(RHITexture* texture, const ResourceViewDesc& desc) override; + RHIResourceView* CreateDepthStencilView(RHITexture* texture, const ResourceViewDesc& desc) override; + RHIResourceView* CreateShaderResourceView(RHITexture* texture, const ResourceViewDesc& desc) override; + RHIResourceView* CreateUnorderedAccessView(RHITexture* texture, const ResourceViewDesc& desc) override; + + const RHICapabilities& GetCapabilities() const override { return m_capabilities; } + const RHIDeviceInfo& GetDeviceInfo() const override { return m_deviceInfo; } + void* GetNativeDevice() override { return m_device; } + + VkInstance GetInstance() const { return m_instance; } + VkPhysicalDevice GetPhysicalDevice() const { return m_physicalDevice; } + VkDevice GetDevice() const { return m_device; } + VkQueue GetGraphicsQueue() const { return m_graphicsQueue; } + uint32_t GetGraphicsQueueFamilyIndex() const { return m_graphicsQueueFamilyIndex; } + uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const; + +private: + bool CreateInstance(); + bool PickPhysicalDevice(); + bool CreateLogicalDevice(); + void QueryDeviceInfo(); + + RHIDeviceDesc m_deviceDesc = {}; + VkInstance m_instance = VK_NULL_HANDLE; + VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; + VkDevice m_device = VK_NULL_HANDLE; + VkQueue m_graphicsQueue = VK_NULL_HANDLE; + uint32_t m_graphicsQueueFamilyIndex = UINT32_MAX; + RHICapabilities m_capabilities = {}; + RHIDeviceInfo m_deviceInfo = {}; + bool m_initialized = false; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanFence.h b/engine/include/XCEngine/RHI/Vulkan/VulkanFence.h new file mode 100644 index 00000000..c15af9f4 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanFence.h @@ -0,0 +1,25 @@ +#pragma once + +#include "XCEngine/RHI/RHIFence.h" + +namespace XCEngine { +namespace RHI { + +class VulkanFence : public RHIFence { +public: + explicit VulkanFence(uint64_t initialValue = 0) + : m_value(initialValue) {} + + void Shutdown() override {} + void Signal() override { ++m_value; } + void Signal(uint64_t value) override { m_value = value; } + void Wait(uint64_t value) override { if (m_value < value) m_value = value; } + uint64_t GetCompletedValue() const override { return m_value; } + void* GetNativeHandle() override { return nullptr; } + +private: + uint64_t m_value = 0; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h b/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h new file mode 100644 index 00000000..01f85183 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h @@ -0,0 +1,38 @@ +#pragma once + +#include "XCEngine/RHI/RHIResourceView.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" +#include "XCEngine/RHI/Vulkan/VulkanTexture.h" + +namespace XCEngine { +namespace RHI { + +class VulkanResourceView : public RHIResourceView { +public: + VulkanResourceView() = default; + ~VulkanResourceView() override; + + bool InitializeAsRenderTarget(VkDevice device, VulkanTexture* texture, const ResourceViewDesc& desc); + + void Shutdown() override; + void* GetNativeHandle() override { return m_imageView; } + bool IsValid() const override { return m_imageView != VK_NULL_HANDLE; } + + ResourceViewType GetViewType() const override { return m_viewType; } + ResourceViewDimension GetDimension() const override { return m_dimension; } + Format GetFormat() const override { return m_format; } + + VulkanTexture* GetTexture() const { return m_texture; } + VkImageView GetImageView() const { return m_imageView; } + +private: + VkDevice m_device = VK_NULL_HANDLE; + VkImageView m_imageView = VK_NULL_HANDLE; + VulkanTexture* m_texture = nullptr; + ResourceViewType m_viewType = ResourceViewType::RenderTarget; + ResourceViewDimension m_dimension = ResourceViewDimension::Texture2D; + Format m_format = Format::Unknown; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanScreenshot.h b/engine/include/XCEngine/RHI/Vulkan/VulkanScreenshot.h new file mode 100644 index 00000000..87adf821 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanScreenshot.h @@ -0,0 +1,15 @@ +#pragma once + +#include "XCEngine/RHI/RHIScreenshot.h" + +namespace XCEngine { +namespace RHI { + +class VulkanScreenshot : public RHIScreenshot { +public: + bool Capture(RHIDevice* device, RHISwapChain* swapChain, const char* filename) override; + void Shutdown() override {} +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h b/engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h new file mode 100644 index 00000000..2fef7615 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h @@ -0,0 +1,59 @@ +#pragma once + +#include "XCEngine/RHI/RHISwapChain.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +#include +#include + +struct HWND__; + +namespace XCEngine { +namespace RHI { + +class VulkanDevice; +class VulkanCommandQueue; +class VulkanTexture; + +class VulkanSwapChain : public RHISwapChain { +public: + VulkanSwapChain() = default; + ~VulkanSwapChain() override; + + bool Initialize(VulkanDevice* device, VulkanCommandQueue* presentQueue, HWND__* window, uint32_t width, uint32_t height); + bool AcquireNextImage(); + + void Shutdown() override; + uint32_t GetCurrentBackBufferIndex() const override { return m_currentImageIndex; } + RHITexture* GetCurrentBackBuffer() override; + void Present(uint32_t syncInterval = 1, uint32_t flags = 0) override; + void Resize(uint32_t width, uint32_t height) override; + + void* GetNativeHandle() override { return m_swapChain; } + + uint32_t GetWidth() const { return m_width; } + uint32_t GetHeight() const { return m_height; } + VkSwapchainKHR GetSwapChain() const { return m_swapChain; } + VkSurfaceKHR GetSurface() const { return m_surface; } + VkFormat GetVkFormat() const { return m_surfaceFormat; } + VulkanDevice* GetDevice() const { return m_device; } + +private: + bool CreateSurface(HWND__* window); + bool CreateSwapChainResources(); + void DestroySwapChainResources(); + + VulkanDevice* m_device = nullptr; + VulkanCommandQueue* m_presentQueue = nullptr; + HWND__* m_window = nullptr; + VkSurfaceKHR m_surface = VK_NULL_HANDLE; + VkSwapchainKHR m_swapChain = VK_NULL_HANDLE; + VkFormat m_surfaceFormat = VK_FORMAT_UNDEFINED; + uint32_t m_width = 0; + uint32_t m_height = 0; + uint32_t m_currentImageIndex = 0; + std::vector> m_backBuffers; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h b/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h new file mode 100644 index 00000000..74cc8eab --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h @@ -0,0 +1,57 @@ +#pragma once + +#include "XCEngine/RHI/RHITexture.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +#include + +namespace XCEngine { +namespace RHI { + +class VulkanTexture : public RHITexture { +public: + VulkanTexture() = default; + ~VulkanTexture() override; + + bool InitializeSwapChainImage( + VkDevice device, + VkImage image, + uint32_t width, + uint32_t height, + Format format, + VkFormat vkFormat); + + void Shutdown() override; + + uint32_t GetWidth() const override { return m_width; } + uint32_t GetHeight() const override { return m_height; } + uint32_t GetDepth() const override { return 1; } + uint32_t GetMipLevels() const override { return 1; } + Format GetFormat() const override { return m_format; } + TextureType GetTextureType() const override { return m_textureType; } + + ResourceStates GetState() const override { return m_state; } + void SetState(ResourceStates state) override { m_state = state; } + + void* GetNativeHandle() override { return m_image; } + + const std::string& GetName() const override { return m_name; } + void SetName(const std::string& name) override { m_name = name; } + + VkImage GetImage() const { return m_image; } + VkFormat GetVkFormat() const { return m_vkFormat; } + +private: + VkDevice m_device = VK_NULL_HANDLE; + VkImage m_image = VK_NULL_HANDLE; + uint32_t m_width = 0; + uint32_t m_height = 0; + Format m_format = Format::Unknown; + TextureType m_textureType = TextureType::Texture2D; + ResourceStates m_state = ResourceStates::Common; + VkFormat m_vkFormat = VK_FORMAT_UNDEFINED; + std::string m_name; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/RHIFactory.cpp b/engine/src/RHI/RHIFactory.cpp index a2293b08..02d0682b 100644 --- a/engine/src/RHI/RHIFactory.cpp +++ b/engine/src/RHI/RHIFactory.cpp @@ -3,6 +3,9 @@ #ifdef XCENGINE_SUPPORT_OPENGL #include "XCEngine/RHI/OpenGL/OpenGLDevice.h" #endif +#ifdef XCENGINE_SUPPORT_VULKAN +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#endif namespace XCEngine { namespace RHI { @@ -15,7 +18,10 @@ RHIDevice* RHIFactory::CreateRHIDevice(RHIType type) { case RHIType::OpenGL: return new OpenGLDevice(); #endif +#ifdef XCENGINE_SUPPORT_VULKAN case RHIType::Vulkan: + return new VulkanDevice(); +#endif case RHIType::Metal: default: return nullptr; @@ -28,6 +34,10 @@ RHIDevice* RHIFactory::CreateRHIDevice(const std::string& typeName) { #ifdef XCENGINE_SUPPORT_OPENGL } else if (typeName == "OpenGL" || typeName == "opengl" || typeName == "GL") { return new OpenGLDevice(); +#endif +#ifdef XCENGINE_SUPPORT_VULKAN + } else if (typeName == "Vulkan" || typeName == "vulkan" || typeName == "VK" || typeName == "vk") { + return new VulkanDevice(); #endif } return nullptr; diff --git a/engine/src/RHI/RHIScreenshot.cpp b/engine/src/RHI/RHIScreenshot.cpp index 459a3cb5..2f91e8ad 100644 --- a/engine/src/RHI/RHIScreenshot.cpp +++ b/engine/src/RHI/RHIScreenshot.cpp @@ -4,6 +4,9 @@ #ifdef XCENGINE_SUPPORT_OPENGL #include "XCEngine/RHI/OpenGL/OpenGLScreenshot.h" #endif +#ifdef XCENGINE_SUPPORT_VULKAN +#include "XCEngine/RHI/Vulkan/VulkanScreenshot.h" +#endif namespace XCEngine { namespace RHI { @@ -15,6 +18,10 @@ RHIScreenshot* RHIScreenshot::Create(RHIType type) { #ifdef XCENGINE_SUPPORT_OPENGL case RHIType::OpenGL: return new OpenGLScreenshot(); +#endif +#ifdef XCENGINE_SUPPORT_VULKAN + case RHIType::Vulkan: + return new VulkanScreenshot(); #endif default: return nullptr; @@ -22,4 +29,4 @@ RHIScreenshot* RHIScreenshot::Create(RHIType type) { } } // namespace RHI -} // namespace XCEngine \ No newline at end of file +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanCommandList.cpp b/engine/src/RHI/Vulkan/VulkanCommandList.cpp new file mode 100644 index 00000000..73f216c9 --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanCommandList.cpp @@ -0,0 +1,326 @@ +#include "XCEngine/RHI/Vulkan/VulkanCommandList.h" + +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#include "XCEngine/RHI/Vulkan/VulkanResourceView.h" +#include "XCEngine/RHI/Vulkan/VulkanTexture.h" + +namespace XCEngine { +namespace RHI { + +namespace { + +VkImageLayout ToVulkanImageLayout(ResourceStates state) { + switch (state) { + case ResourceStates::RenderTarget: + return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + case ResourceStates::CopySrc: + return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + case ResourceStates::CopyDst: + return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + case ResourceStates::Present: + return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + case ResourceStates::PixelShaderResource: + return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + case ResourceStates::DepthWrite: + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + case ResourceStates::Common: + default: + return VK_IMAGE_LAYOUT_UNDEFINED; + } +} + +VkAccessFlags ToVulkanAccessMask(ResourceStates state) { + switch (state) { + case ResourceStates::RenderTarget: + return VK_ACCESS_TRANSFER_WRITE_BIT; + case ResourceStates::CopySrc: + return VK_ACCESS_TRANSFER_READ_BIT; + case ResourceStates::CopyDst: + return VK_ACCESS_TRANSFER_WRITE_BIT; + case ResourceStates::PixelShaderResource: + return VK_ACCESS_SHADER_READ_BIT; + case ResourceStates::DepthWrite: + return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + case ResourceStates::Present: + case ResourceStates::Common: + default: + return 0; + } +} + +VkPipelineStageFlags ToVulkanStageMask(ResourceStates state) { + switch (state) { + case ResourceStates::RenderTarget: + case ResourceStates::CopySrc: + case ResourceStates::CopyDst: + return VK_PIPELINE_STAGE_TRANSFER_BIT; + case ResourceStates::PixelShaderResource: + return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + case ResourceStates::DepthWrite: + return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + case ResourceStates::Present: + return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + case ResourceStates::Common: + default: + return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + } +} + +} // namespace + +VulkanCommandList::~VulkanCommandList() { + Shutdown(); +} + +bool VulkanCommandList::Initialize(VulkanDevice* device) { + if (device == nullptr || device->GetDevice() == VK_NULL_HANDLE) { + return false; + } + + m_device = device; + + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = device->GetGraphicsQueueFamilyIndex(); + if (vkCreateCommandPool(device->GetDevice(), &poolInfo, nullptr, &m_commandPool) != VK_SUCCESS) { + return false; + } + + VkCommandBufferAllocateInfo allocateInfo = {}; + allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocateInfo.commandPool = m_commandPool; + allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocateInfo.commandBufferCount = 1; + return vkAllocateCommandBuffers(device->GetDevice(), &allocateInfo, &m_commandBuffer) == VK_SUCCESS; +} + +void VulkanCommandList::Shutdown() { + if (m_commandPool != VK_NULL_HANDLE && m_device != nullptr) { + if (m_commandBuffer != VK_NULL_HANDLE) { + vkFreeCommandBuffers(m_device->GetDevice(), m_commandPool, 1, &m_commandBuffer); + } + vkDestroyCommandPool(m_device->GetDevice(), m_commandPool, nullptr); + } + + m_commandBuffer = VK_NULL_HANDLE; + m_commandPool = VK_NULL_HANDLE; + m_currentColorTarget = nullptr; + m_currentDepthTarget = nullptr; + m_device = nullptr; +} + +void VulkanCommandList::Reset() { + if (m_device == nullptr || m_commandPool == VK_NULL_HANDLE || m_commandBuffer == VK_NULL_HANDLE) { + return; + } + + vkResetCommandPool(m_device->GetDevice(), m_commandPool, 0); + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(m_commandBuffer, &beginInfo); +} + +void VulkanCommandList::Close() { + if (m_commandBuffer != VK_NULL_HANDLE) { + vkEndCommandBuffer(m_commandBuffer); + } +} + +void VulkanCommandList::TransitionTexture(VulkanTexture* texture, ResourceStates newState) { + if (texture == nullptr || m_commandBuffer == VK_NULL_HANDLE) { + return; + } + + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = ToVulkanImageLayout(texture->GetState()); + barrier.newLayout = ToVulkanImageLayout(newState); + barrier.srcAccessMask = ToVulkanAccessMask(texture->GetState()); + barrier.dstAccessMask = ToVulkanAccessMask(newState); + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = texture->GetImage(); + barrier.subresourceRange.aspectMask = GetImageAspectMask(texture->GetFormat()); + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + vkCmdPipelineBarrier( + m_commandBuffer, + ToVulkanStageMask(texture->GetState()), + ToVulkanStageMask(newState), + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + texture->SetState(newState); +} + +void VulkanCommandList::TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) { + (void)stateBefore; + auto* view = static_cast(resource); + if (view != nullptr) { + TransitionTexture(view->GetTexture(), stateAfter); + } +} + +void VulkanCommandList::BeginRenderPass(class RHIRenderPass* renderPass, class RHIFramebuffer* framebuffer, const Rect& renderArea, uint32_t clearValueCount, const ClearValue* clearValues) { + (void)renderPass; + (void)framebuffer; + (void)renderArea; + (void)clearValueCount; + (void)clearValues; +} + +void VulkanCommandList::EndRenderPass() { +} + +void VulkanCommandList::SetShader(RHIShader* shader) { + (void)shader; +} + +void VulkanCommandList::SetPipelineState(RHIPipelineState* pso) { + (void)pso; +} + +void VulkanCommandList::SetGraphicsDescriptorSets(uint32_t firstSet, uint32_t count, RHIDescriptorSet** descriptorSets, RHIPipelineLayout* pipelineLayout) { + (void)firstSet; + (void)count; + (void)descriptorSets; + (void)pipelineLayout; +} + +void VulkanCommandList::SetComputeDescriptorSets(uint32_t firstSet, uint32_t count, RHIDescriptorSet** descriptorSets, RHIPipelineLayout* pipelineLayout) { + (void)firstSet; + (void)count; + (void)descriptorSets; + (void)pipelineLayout; +} + +void VulkanCommandList::SetPrimitiveTopology(PrimitiveTopology topology) { + (void)topology; +} + +void VulkanCommandList::SetViewport(const Viewport& viewport) { + (void)viewport; +} + +void VulkanCommandList::SetViewports(uint32_t count, const Viewport* viewports) { + (void)count; + (void)viewports; +} + +void VulkanCommandList::SetScissorRect(const Rect& rect) { + (void)rect; +} + +void VulkanCommandList::SetScissorRects(uint32_t count, const Rect* rects) { + (void)count; + (void)rects; +} + +void VulkanCommandList::SetRenderTargets(uint32_t count, RHIResourceView** renderTargets, RHIResourceView* depthStencil) { + m_currentColorTarget = (count > 0 && renderTargets != nullptr) ? renderTargets[0] : nullptr; + m_currentDepthTarget = depthStencil; +} + +void VulkanCommandList::SetStencilRef(uint8_t ref) { + (void)ref; +} + +void VulkanCommandList::SetBlendFactor(const float factor[4]) { + (void)factor; +} + +void VulkanCommandList::SetVertexBuffers(uint32_t startSlot, uint32_t count, RHIResourceView** buffers, const uint64_t* offsets, const uint32_t* strides) { + (void)startSlot; + (void)count; + (void)buffers; + (void)offsets; + (void)strides; +} + +void VulkanCommandList::SetIndexBuffer(RHIResourceView* buffer, uint64_t offset) { + (void)buffer; + (void)offset; +} + +void VulkanCommandList::Draw(uint32_t vertexCount, uint32_t instanceCount, uint32_t startVertex, uint32_t startInstance) { + (void)vertexCount; + (void)instanceCount; + (void)startVertex; + (void)startInstance; +} + +void VulkanCommandList::DrawIndexed(uint32_t indexCount, uint32_t instanceCount, uint32_t startIndex, int32_t baseVertex, uint32_t startInstance) { + (void)indexCount; + (void)instanceCount; + (void)startIndex; + (void)baseVertex; + (void)startInstance; +} + +void VulkanCommandList::Clear(float r, float g, float b, float a, uint32_t buffers) { + (void)buffers; + + auto* colorView = static_cast(m_currentColorTarget); + if (colorView == nullptr || colorView->GetTexture() == nullptr) { + return; + } + + VulkanTexture* texture = colorView->GetTexture(); + TransitionTexture(texture, ResourceStates::RenderTarget); + + VkClearColorValue clearColor = {}; + clearColor.float32[0] = r; + clearColor.float32[1] = g; + clearColor.float32[2] = b; + clearColor.float32[3] = a; + + VkImageSubresourceRange range = {}; + range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + range.baseMipLevel = 0; + range.levelCount = 1; + range.baseArrayLayer = 0; + range.layerCount = 1; + + vkCmdClearColorImage( + m_commandBuffer, + texture->GetImage(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + &clearColor, + 1, + &range); + + TransitionTexture(texture, ResourceStates::Present); +} + +void VulkanCommandList::ClearRenderTarget(RHIResourceView* renderTarget, const float color[4]) { + m_currentColorTarget = renderTarget; + Clear(color[0], color[1], color[2], color[3], 1); +} + +void VulkanCommandList::ClearDepthStencil(RHIResourceView* depthStencil, float depth, uint8_t stencil) { + (void)depthStencil; + (void)depth; + (void)stencil; +} + +void VulkanCommandList::CopyResource(RHIResourceView* dst, RHIResourceView* src) { + (void)dst; + (void)src; +} + +void VulkanCommandList::Dispatch(uint32_t x, uint32_t y, uint32_t z) { + (void)x; + (void)y; + (void)z; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanCommandQueue.cpp b/engine/src/RHI/Vulkan/VulkanCommandQueue.cpp new file mode 100644 index 00000000..6c895ce5 --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanCommandQueue.cpp @@ -0,0 +1,80 @@ +#include "XCEngine/RHI/Vulkan/VulkanCommandQueue.h" + +#include "XCEngine/RHI/RHIFence.h" +#include "XCEngine/RHI/Vulkan/VulkanCommandList.h" +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" + +#include + +namespace XCEngine { +namespace RHI { + +bool VulkanCommandQueue::Initialize(VulkanDevice* device, CommandQueueType type) { + if (device == nullptr || device->GetGraphicsQueue() == VK_NULL_HANDLE) { + return false; + } + + m_device = device; + m_queue = device->GetGraphicsQueue(); + m_type = type; + return true; +} + +void VulkanCommandQueue::Shutdown() { + m_queue = VK_NULL_HANDLE; + m_device = nullptr; + m_currentFrame = 0; +} + +void VulkanCommandQueue::ExecuteCommandLists(uint32_t count, void** lists) { + if (m_queue == VK_NULL_HANDLE || count == 0 || lists == nullptr) { + return; + } + + std::vector commandBuffers; + commandBuffers.reserve(count); + for (uint32_t i = 0; i < count; ++i) { + auto* commandList = static_cast(lists[i]); + if (commandList != nullptr && commandList->GetCommandBuffer() != VK_NULL_HANDLE) { + commandBuffers.push_back(commandList->GetCommandBuffer()); + } + } + + if (commandBuffers.empty()) { + return; + } + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = static_cast(commandBuffers.size()); + submitInfo.pCommandBuffers = commandBuffers.data(); + + vkQueueSubmit(m_queue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(m_queue); + ++m_currentFrame; +} + +void VulkanCommandQueue::Signal(RHIFence* fence, uint64_t value) { + if (fence != nullptr) { + fence->Signal(value); + } +} + +void VulkanCommandQueue::Wait(RHIFence* fence, uint64_t value) { + if (fence != nullptr) { + fence->Wait(value); + } +} + +uint64_t VulkanCommandQueue::GetCompletedValue() { + return m_currentFrame; +} + +void VulkanCommandQueue::WaitForIdle() { + if (m_queue != VK_NULL_HANDLE) { + vkQueueWaitIdle(m_queue); + } +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanDevice.cpp b/engine/src/RHI/Vulkan/VulkanDevice.cpp new file mode 100644 index 00000000..3ad6aaa6 --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanDevice.cpp @@ -0,0 +1,342 @@ +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" + +#include "XCEngine/RHI/Vulkan/VulkanCommandList.h" +#include "XCEngine/RHI/Vulkan/VulkanCommandQueue.h" +#include "XCEngine/RHI/Vulkan/VulkanFence.h" +#include "XCEngine/RHI/Vulkan/VulkanResourceView.h" +#include "XCEngine/RHI/Vulkan/VulkanSwapChain.h" + +#include +#include +#include + +namespace XCEngine { +namespace RHI { + +namespace { + +std::wstring ResolveVendorName(uint32_t vendorId) { + switch (vendorId) { + case 0x10DE: return L"NVIDIA"; + case 0x1002: return L"AMD"; + case 0x8086: return L"Intel"; + case 0x13B5: return L"ARM"; + case 0x5143: return L"Qualcomm"; + default: return L"Unknown"; + } +} + +} // namespace + +VulkanDevice::~VulkanDevice() { + Shutdown(); +} + +bool VulkanDevice::Initialize(const RHIDeviceDesc& desc) { + if (m_initialized) { + return true; + } + + m_deviceDesc = desc; + if (!CreateInstance()) { + return false; + } + if (!PickPhysicalDevice()) { + Shutdown(); + return false; + } + if (!CreateLogicalDevice()) { + Shutdown(); + return false; + } + + QueryDeviceInfo(); + m_initialized = true; + return true; +} + +bool VulkanDevice::CreateInstance() { + VkApplicationInfo appInfo = {}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "XCEngine"; + appInfo.pEngineName = "XCEngine"; + appInfo.apiVersion = VK_API_VERSION_1_1; + + const char* extensions[] = { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_WIN32_SURFACE_EXTENSION_NAME + }; + + VkInstanceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + createInfo.enabledExtensionCount = static_cast(std::size(extensions)); + createInfo.ppEnabledExtensionNames = extensions; + return vkCreateInstance(&createInfo, nullptr, &m_instance) == VK_SUCCESS; +} + +bool VulkanDevice::PickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(m_instance, &deviceCount, nullptr); + if (deviceCount == 0) { + return false; + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(m_instance, &deviceCount, devices.data()); + + for (VkPhysicalDevice physicalDevice : devices) { + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr); + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data()); + + for (uint32_t index = 0; index < queueFamilyCount; ++index) { + if ((queueFamilies[index].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { + m_physicalDevice = physicalDevice; + m_graphicsQueueFamilyIndex = index; + return true; + } + } + } + + return false; +} + +bool VulkanDevice::CreateLogicalDevice() { + const float queuePriority = 1.0f; + + VkDeviceQueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = m_graphicsQueueFamilyIndex; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + + VkPhysicalDeviceFeatures features = {}; + + const char* deviceExtensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME + }; + + VkDeviceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = 1; + createInfo.pQueueCreateInfos = &queueCreateInfo; + createInfo.pEnabledFeatures = &features; + createInfo.enabledExtensionCount = static_cast(std::size(deviceExtensions)); + createInfo.ppEnabledExtensionNames = deviceExtensions; + + if (vkCreateDevice(m_physicalDevice, &createInfo, nullptr, &m_device) != VK_SUCCESS) { + return false; + } + + vkGetDeviceQueue(m_device, m_graphicsQueueFamilyIndex, 0, &m_graphicsQueue); + return m_graphicsQueue != VK_NULL_HANDLE; +} + +void VulkanDevice::QueryDeviceInfo() { + VkPhysicalDeviceProperties properties = {}; + VkPhysicalDeviceFeatures features = {}; + vkGetPhysicalDeviceProperties(m_physicalDevice, &properties); + vkGetPhysicalDeviceFeatures(m_physicalDevice, &features); + + m_deviceInfo.description = WidenAscii(properties.deviceName); + m_deviceInfo.vendor = ResolveVendorName(properties.vendorID); + m_deviceInfo.renderer = WidenAscii(properties.deviceName); + m_deviceInfo.version = L"Vulkan"; + m_deviceInfo.majorVersion = ResolveVulkanApiMajor(properties.apiVersion); + m_deviceInfo.minorVersion = ResolveVulkanApiMinor(properties.apiVersion); + m_deviceInfo.vendorId = properties.vendorID; + m_deviceInfo.deviceId = properties.deviceID; + m_deviceInfo.isSoftware = properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU; + + m_capabilities.bSupportsGeometryShaders = features.geometryShader == VK_TRUE; + m_capabilities.bSupportsTessellation = features.tessellationShader == VK_TRUE; + m_capabilities.bSupportsComputeShaders = true; + m_capabilities.bSupportsMultiViewport = features.multiViewport == VK_TRUE; + m_capabilities.bSupportsExplicitMultiThreading = true; + m_capabilities.maxTexture2DSize = properties.limits.maxImageDimension2D; + m_capabilities.maxTexture3DSize = properties.limits.maxImageDimension3D; + m_capabilities.maxTextureCubeSize = properties.limits.maxImageDimensionCube; + m_capabilities.maxRenderTargets = properties.limits.maxColorAttachments; + m_capabilities.maxColorAttachments = properties.limits.maxColorAttachments; + m_capabilities.maxViewports = properties.limits.maxViewports; + m_capabilities.maxVertexAttribs = properties.limits.maxVertexInputAttributes; + m_capabilities.maxConstantBufferSize = properties.limits.maxUniformBufferRange; + m_capabilities.maxAnisotropy = static_cast(properties.limits.maxSamplerAnisotropy); + m_capabilities.majorVersion = static_cast(m_deviceInfo.majorVersion); + m_capabilities.minorVersion = static_cast(m_deviceInfo.minorVersion); + m_capabilities.shaderModel = "SPIR-V"; +} + +void VulkanDevice::Shutdown() { + if (m_device != VK_NULL_HANDLE) { + vkDeviceWaitIdle(m_device); + vkDestroyDevice(m_device, nullptr); + m_device = VK_NULL_HANDLE; + } + + if (m_instance != VK_NULL_HANDLE) { + vkDestroyInstance(m_instance, nullptr); + m_instance = VK_NULL_HANDLE; + } + + m_physicalDevice = VK_NULL_HANDLE; + m_graphicsQueue = VK_NULL_HANDLE; + m_graphicsQueueFamilyIndex = UINT32_MAX; + m_initialized = false; +} + +uint32_t VulkanDevice::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const { + VkPhysicalDeviceMemoryProperties memoryProperties = {}; + vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties); + + for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; ++i) { + if ((typeFilter & (1u << i)) != 0 && + (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + return UINT32_MAX; +} + +RHIBuffer* VulkanDevice::CreateBuffer(const BufferDesc& desc) { + (void)desc; + return nullptr; +} + +RHITexture* VulkanDevice::CreateTexture(const TextureDesc& desc) { + (void)desc; + return nullptr; +} + +RHITexture* VulkanDevice::CreateTexture(const TextureDesc& desc, const void* initialData, size_t initialDataSize, uint32_t rowPitch) { + (void)desc; + (void)initialData; + (void)initialDataSize; + (void)rowPitch; + return nullptr; +} + +RHISwapChain* VulkanDevice::CreateSwapChain(const SwapChainDesc& desc, RHICommandQueue* presentQueue) { + auto* swapChain = new VulkanSwapChain(); + if (swapChain->Initialize(this, static_cast(presentQueue), static_cast(desc.windowHandle), desc.width, desc.height)) { + return swapChain; + } + delete swapChain; + return nullptr; +} + +RHICommandList* VulkanDevice::CreateCommandList(const CommandListDesc& desc) { + (void)desc; + auto* commandList = new VulkanCommandList(); + if (commandList->Initialize(this)) { + return commandList; + } + delete commandList; + return nullptr; +} + +RHICommandQueue* VulkanDevice::CreateCommandQueue(const CommandQueueDesc& desc) { + auto* queue = new VulkanCommandQueue(); + if (queue->Initialize(this, static_cast(desc.queueType))) { + return queue; + } + delete queue; + return nullptr; +} + +RHIShader* VulkanDevice::CreateShader(const ShaderCompileDesc& desc) { + (void)desc; + return nullptr; +} + +RHIPipelineState* VulkanDevice::CreatePipelineState(const GraphicsPipelineDesc& desc) { + (void)desc; + return nullptr; +} + +RHIPipelineLayout* VulkanDevice::CreatePipelineLayout(const RHIPipelineLayoutDesc& desc) { + (void)desc; + return nullptr; +} + +RHIFence* VulkanDevice::CreateFence(const FenceDesc& desc) { + return new VulkanFence(desc.initialValue); +} + +RHISampler* VulkanDevice::CreateSampler(const SamplerDesc& desc) { + (void)desc; + return nullptr; +} + +RHIRenderPass* VulkanDevice::CreateRenderPass(uint32_t colorAttachmentCount, const AttachmentDesc* colorAttachments, const AttachmentDesc* depthStencilAttachment) { + (void)colorAttachmentCount; + (void)colorAttachments; + (void)depthStencilAttachment; + return nullptr; +} + +RHIFramebuffer* VulkanDevice::CreateFramebuffer(class RHIRenderPass* renderPass, uint32_t width, uint32_t height, uint32_t colorAttachmentCount, RHIResourceView** colorAttachments, RHIResourceView* depthStencilAttachment) { + (void)renderPass; + (void)width; + (void)height; + (void)colorAttachmentCount; + (void)colorAttachments; + (void)depthStencilAttachment; + return nullptr; +} + +RHIDescriptorPool* VulkanDevice::CreateDescriptorPool(const DescriptorPoolDesc& desc) { + (void)desc; + return nullptr; +} + +RHIDescriptorSet* VulkanDevice::CreateDescriptorSet(RHIDescriptorPool* pool, const DescriptorSetLayoutDesc& layout) { + (void)pool; + (void)layout; + return nullptr; +} + +RHIResourceView* VulkanDevice::CreateVertexBufferView(RHIBuffer* buffer, const ResourceViewDesc& desc) { + (void)buffer; + (void)desc; + return nullptr; +} + +RHIResourceView* VulkanDevice::CreateIndexBufferView(RHIBuffer* buffer, const ResourceViewDesc& desc) { + (void)buffer; + (void)desc; + return nullptr; +} + +RHIResourceView* VulkanDevice::CreateRenderTargetView(RHITexture* texture, const ResourceViewDesc& desc) { + auto* view = new VulkanResourceView(); + if (view->InitializeAsRenderTarget(m_device, static_cast(texture), desc)) { + return view; + } + delete view; + return nullptr; +} + +RHIResourceView* VulkanDevice::CreateDepthStencilView(RHITexture* texture, const ResourceViewDesc& desc) { + (void)texture; + (void)desc; + return nullptr; +} + +RHIResourceView* VulkanDevice::CreateShaderResourceView(RHITexture* texture, const ResourceViewDesc& desc) { + (void)texture; + (void)desc; + return nullptr; +} + +RHIResourceView* VulkanDevice::CreateUnorderedAccessView(RHITexture* texture, const ResourceViewDesc& desc) { + (void)texture; + (void)desc; + return nullptr; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanResourceView.cpp b/engine/src/RHI/Vulkan/VulkanResourceView.cpp new file mode 100644 index 00000000..d5628664 --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanResourceView.cpp @@ -0,0 +1,47 @@ +#include "XCEngine/RHI/Vulkan/VulkanResourceView.h" + +namespace XCEngine { +namespace RHI { + +VulkanResourceView::~VulkanResourceView() { + Shutdown(); +} + +bool VulkanResourceView::InitializeAsRenderTarget(VkDevice device, VulkanTexture* texture, const ResourceViewDesc& desc) { + if (device == VK_NULL_HANDLE || texture == nullptr || texture->GetImage() == VK_NULL_HANDLE) { + return false; + } + + m_device = device; + m_texture = texture; + m_viewType = ResourceViewType::RenderTarget; + m_dimension = desc.dimension != ResourceViewDimension::Unknown ? desc.dimension : ResourceViewDimension::Texture2D; + m_format = desc.format != 0 ? static_cast(desc.format) : texture->GetFormat(); + + VkImageViewCreateInfo viewInfo = {}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = texture->GetImage(); + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = texture->GetVkFormat(); + viewInfo.subresourceRange.aspectMask = GetImageAspectMask(texture->GetFormat()); + viewInfo.subresourceRange.baseMipLevel = desc.mipLevel; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = desc.firstArraySlice; + viewInfo.subresourceRange.layerCount = 1; + + return vkCreateImageView(device, &viewInfo, nullptr, &m_imageView) == VK_SUCCESS; +} + +void VulkanResourceView::Shutdown() { + if (m_imageView != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyImageView(m_device, m_imageView, nullptr); + } + m_imageView = VK_NULL_HANDLE; + m_device = VK_NULL_HANDLE; + m_texture = nullptr; + m_format = Format::Unknown; + m_dimension = ResourceViewDimension::Unknown; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanScreenshot.cpp b/engine/src/RHI/Vulkan/VulkanScreenshot.cpp new file mode 100644 index 00000000..5e7608ad --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanScreenshot.cpp @@ -0,0 +1,187 @@ +#include "XCEngine/RHI/Vulkan/VulkanScreenshot.h" + +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#include "XCEngine/RHI/Vulkan/VulkanSwapChain.h" +#include "XCEngine/RHI/Vulkan/VulkanTexture.h" + +#include +#include + +namespace XCEngine { +namespace RHI { + +bool VulkanScreenshot::Capture(RHIDevice* device, RHISwapChain* swapChain, const char* filename) { + auto* vkDevice = static_cast(device); + auto* texture = static_cast(swapChain->GetCurrentBackBuffer()); + if (vkDevice == nullptr || texture == nullptr) { + return false; + } + + const uint32_t width = texture->GetWidth(); + const uint32_t height = texture->GetHeight(); + const VkDevice logicalDevice = vkDevice->GetDevice(); + + VkBuffer stagingBuffer = VK_NULL_HANDLE; + VkDeviceMemory stagingMemory = VK_NULL_HANDLE; + const VkDeviceSize bufferSize = static_cast(width) * static_cast(height) * 4; + + VkBufferCreateInfo bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = bufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + if (vkCreateBuffer(logicalDevice, &bufferInfo, nullptr, &stagingBuffer) != VK_SUCCESS) { + return false; + } + + VkMemoryRequirements memoryRequirements = {}; + vkGetBufferMemoryRequirements(logicalDevice, stagingBuffer, &memoryRequirements); + + VkMemoryAllocateInfo allocateInfo = {}; + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = memoryRequirements.size; + allocateInfo.memoryTypeIndex = vkDevice->FindMemoryType( + memoryRequirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + if (allocateInfo.memoryTypeIndex == UINT32_MAX || + vkAllocateMemory(logicalDevice, &allocateInfo, nullptr, &stagingMemory) != VK_SUCCESS) { + vkDestroyBuffer(logicalDevice, stagingBuffer, nullptr); + return false; + } + + vkBindBufferMemory(logicalDevice, stagingBuffer, stagingMemory, 0); + + VkCommandPool commandPool = VK_NULL_HANDLE; + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = vkDevice->GetGraphicsQueueFamilyIndex(); + if (vkCreateCommandPool(logicalDevice, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + vkFreeMemory(logicalDevice, stagingMemory, nullptr); + vkDestroyBuffer(logicalDevice, stagingBuffer, nullptr); + return false; + } + + VkCommandBufferAllocateInfo commandBufferInfo = {}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + commandBufferInfo.commandPool = commandPool; + commandBufferInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + commandBufferInfo.commandBufferCount = 1; + vkAllocateCommandBuffers(logicalDevice, &commandBufferInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkImageMemoryBarrier toCopySource = {}; + toCopySource.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + toCopySource.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + toCopySource.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + toCopySource.srcAccessMask = 0; + toCopySource.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + toCopySource.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toCopySource.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toCopySource.image = texture->GetImage(); + toCopySource.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + toCopySource.subresourceRange.baseMipLevel = 0; + toCopySource.subresourceRange.levelCount = 1; + toCopySource.subresourceRange.baseArrayLayer = 0; + toCopySource.subresourceRange.layerCount = 1; + + vkCmdPipelineBarrier( + commandBuffer, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &toCopySource); + + VkBufferImageCopy copyRegion = {}; + copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.imageSubresource.mipLevel = 0; + copyRegion.imageSubresource.baseArrayLayer = 0; + copyRegion.imageSubresource.layerCount = 1; + copyRegion.imageExtent.width = width; + copyRegion.imageExtent.height = height; + copyRegion.imageExtent.depth = 1; + + vkCmdCopyImageToBuffer( + commandBuffer, + texture->GetImage(), + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + stagingBuffer, + 1, + ©Region); + + VkImageMemoryBarrier backToPresent = toCopySource; + backToPresent.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + backToPresent.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + backToPresent.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + backToPresent.dstAccessMask = 0; + + vkCmdPipelineBarrier( + commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &backToPresent); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(vkDevice->GetGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(vkDevice->GetGraphicsQueue()); + + void* mappedData = nullptr; + if (vkMapMemory(logicalDevice, stagingMemory, 0, bufferSize, 0, &mappedData) != VK_SUCCESS) { + vkDestroyCommandPool(logicalDevice, commandPool, nullptr); + vkFreeMemory(logicalDevice, stagingMemory, nullptr); + vkDestroyBuffer(logicalDevice, stagingBuffer, nullptr); + return false; + } + + FILE* fp = fopen(filename, "wb"); + if (fp == nullptr) { + vkUnmapMemory(logicalDevice, stagingMemory); + vkDestroyCommandPool(logicalDevice, commandPool, nullptr); + vkFreeMemory(logicalDevice, stagingMemory, nullptr); + vkDestroyBuffer(logicalDevice, stagingBuffer, nullptr); + return false; + } + + fprintf(fp, "P6\n%d %d\n255\n", width, height); + + const uint8_t* bytes = static_cast(mappedData); + const bool isBGRA = texture->GetVkFormat() == VK_FORMAT_B8G8R8A8_UNORM; + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + const size_t index = (static_cast(y) * width + x) * 4; + const uint8_t r = isBGRA ? bytes[index + 2] : bytes[index + 0]; + const uint8_t g = bytes[index + 1]; + const uint8_t b = isBGRA ? bytes[index + 0] : bytes[index + 2]; + fwrite(&r, 1, 1, fp); + fwrite(&g, 1, 1, fp); + fwrite(&b, 1, 1, fp); + } + } + + fclose(fp); + vkUnmapMemory(logicalDevice, stagingMemory); + vkDestroyCommandPool(logicalDevice, commandPool, nullptr); + vkFreeMemory(logicalDevice, stagingMemory, nullptr); + vkDestroyBuffer(logicalDevice, stagingBuffer, nullptr); + + return true; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanSwapChain.cpp b/engine/src/RHI/Vulkan/VulkanSwapChain.cpp new file mode 100644 index 00000000..106dab27 --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanSwapChain.cpp @@ -0,0 +1,234 @@ +#include "XCEngine/RHI/Vulkan/VulkanSwapChain.h" + +#include "XCEngine/RHI/Vulkan/VulkanCommandQueue.h" +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#include "XCEngine/RHI/Vulkan/VulkanTexture.h" + +#include +#include + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include + +namespace XCEngine { +namespace RHI { + +VulkanSwapChain::~VulkanSwapChain() { + Shutdown(); +} + +bool VulkanSwapChain::Initialize(VulkanDevice* device, VulkanCommandQueue* presentQueue, HWND__* window, uint32_t width, uint32_t height) { + if (device == nullptr || presentQueue == nullptr || window == nullptr) { + return false; + } + + m_device = device; + m_presentQueue = presentQueue; + m_window = window; + m_width = width; + m_height = height; + + if (!CreateSurface(window)) { + return false; + } + + return CreateSwapChainResources(); +} + +bool VulkanSwapChain::CreateSurface(HWND__* window) { + VkWin32SurfaceCreateInfoKHR surfaceInfo = {}; + surfaceInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + surfaceInfo.hinstance = GetModuleHandle(nullptr); + surfaceInfo.hwnd = window; + return vkCreateWin32SurfaceKHR(m_device->GetInstance(), &surfaceInfo, nullptr, &m_surface) == VK_SUCCESS; +} + +bool VulkanSwapChain::CreateSwapChainResources() { + VkSurfaceCapabilitiesKHR capabilities = {}; + if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_device->GetPhysicalDevice(), m_surface, &capabilities) != VK_SUCCESS) { + return false; + } + + uint32_t formatCount = 0; + vkGetPhysicalDeviceSurfaceFormatsKHR(m_device->GetPhysicalDevice(), m_surface, &formatCount, nullptr); + if (formatCount == 0) { + return false; + } + + std::vector formats(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(m_device->GetPhysicalDevice(), m_surface, &formatCount, formats.data()); + + VkSurfaceFormatKHR selectedFormat = formats[0]; + for (const VkSurfaceFormatKHR& candidate : formats) { + if (candidate.format == VK_FORMAT_B8G8R8A8_UNORM) { + selectedFormat = candidate; + break; + } + } + m_surfaceFormat = selectedFormat.format; + + if ((capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0 || + (capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) == 0) { + return false; + } + + uint32_t presentModeCount = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(m_device->GetPhysicalDevice(), m_surface, &presentModeCount, nullptr); + std::vector presentModes(presentModeCount); + if (presentModeCount > 0) { + vkGetPhysicalDeviceSurfacePresentModesKHR(m_device->GetPhysicalDevice(), m_surface, &presentModeCount, presentModes.data()); + } + + VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; + for (VkPresentModeKHR candidate : presentModes) { + if (candidate == VK_PRESENT_MODE_MAILBOX_KHR) { + presentMode = candidate; + break; + } + } + + const uint32_t requestedImageCount = (std::max)(2, capabilities.minImageCount); + const uint32_t imageCount = capabilities.maxImageCount > 0 + ? std::min(requestedImageCount, capabilities.maxImageCount) + : requestedImageCount; + + VkExtent2D extent = capabilities.currentExtent; + if (extent.width == UINT32_MAX) { + extent.width = m_width; + extent.height = m_height; + } + m_width = extent.width; + m_height = extent.height; + + VkSwapchainCreateInfoKHR createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = m_surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = selectedFormat.format; + createInfo.imageColorSpace = selectedFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.preTransform = capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(m_device->GetDevice(), &createInfo, nullptr, &m_swapChain) != VK_SUCCESS) { + return false; + } + + uint32_t swapChainImageCount = 0; + vkGetSwapchainImagesKHR(m_device->GetDevice(), m_swapChain, &swapChainImageCount, nullptr); + std::vector images(swapChainImageCount); + vkGetSwapchainImagesKHR(m_device->GetDevice(), m_swapChain, &swapChainImageCount, images.data()); + + m_backBuffers.clear(); + m_backBuffers.reserve(swapChainImageCount); + for (VkImage image : images) { + auto texture = std::make_unique(); + texture->InitializeSwapChainImage( + m_device->GetDevice(), + image, + m_width, + m_height, + ToRHIFormat(selectedFormat.format), + selectedFormat.format); + m_backBuffers.push_back(std::move(texture)); + } + + return !m_backBuffers.empty(); +} + +void VulkanSwapChain::DestroySwapChainResources() { + m_backBuffers.clear(); + if (m_swapChain != VK_NULL_HANDLE && m_device != nullptr) { + vkDestroySwapchainKHR(m_device->GetDevice(), m_swapChain, nullptr); + m_swapChain = VK_NULL_HANDLE; + } +} + +bool VulkanSwapChain::AcquireNextImage() { + if (m_device == nullptr || m_swapChain == VK_NULL_HANDLE) { + return false; + } + + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + + VkFence acquireFence = VK_NULL_HANDLE; + if (vkCreateFence(m_device->GetDevice(), &fenceInfo, nullptr, &acquireFence) != VK_SUCCESS) { + return false; + } + + const VkResult result = vkAcquireNextImageKHR( + m_device->GetDevice(), + m_swapChain, + UINT64_MAX, + VK_NULL_HANDLE, + acquireFence, + &m_currentImageIndex); + + if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) { + vkWaitForFences(m_device->GetDevice(), 1, &acquireFence, VK_TRUE, UINT64_MAX); + vkDestroyFence(m_device->GetDevice(), acquireFence, nullptr); + return true; + } + + vkDestroyFence(m_device->GetDevice(), acquireFence, nullptr); + return false; +} + +void VulkanSwapChain::Shutdown() { + DestroySwapChainResources(); + + if (m_surface != VK_NULL_HANDLE && m_device != nullptr) { + vkDestroySurfaceKHR(m_device->GetInstance(), m_surface, nullptr); + m_surface = VK_NULL_HANDLE; + } + + m_presentQueue = nullptr; + m_window = nullptr; + m_device = nullptr; + m_width = 0; + m_height = 0; + m_currentImageIndex = 0; +} + +RHITexture* VulkanSwapChain::GetCurrentBackBuffer() { + if (m_currentImageIndex >= m_backBuffers.size()) { + return nullptr; + } + return m_backBuffers[m_currentImageIndex].get(); +} + +void VulkanSwapChain::Present(uint32_t syncInterval, uint32_t flags) { + (void)syncInterval; + (void)flags; + + if (m_presentQueue == nullptr || m_swapChain == VK_NULL_HANDLE) { + return; + } + + VkPresentInfoKHR presentInfo = {}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &m_swapChain; + presentInfo.pImageIndices = &m_currentImageIndex; + + vkQueuePresentKHR(m_presentQueue->GetQueue(), &presentInfo); +} + +void VulkanSwapChain::Resize(uint32_t width, uint32_t height) { + m_width = width; + m_height = height; + DestroySwapChainResources(); + CreateSwapChainResources(); +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanTexture.cpp b/engine/src/RHI/Vulkan/VulkanTexture.cpp new file mode 100644 index 00000000..6a3e78a1 --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanTexture.cpp @@ -0,0 +1,39 @@ +#include "XCEngine/RHI/Vulkan/VulkanTexture.h" + +namespace XCEngine { +namespace RHI { + +VulkanTexture::~VulkanTexture() { + Shutdown(); +} + +bool VulkanTexture::InitializeSwapChainImage( + VkDevice device, + VkImage image, + uint32_t width, + uint32_t height, + Format format, + VkFormat vkFormat) { + m_device = device; + m_image = image; + m_width = width; + m_height = height; + m_format = format; + m_vkFormat = vkFormat; + m_textureType = TextureType::Texture2D; + m_state = ResourceStates::Common; + return m_image != VK_NULL_HANDLE; +} + +void VulkanTexture::Shutdown() { + m_image = VK_NULL_HANDLE; + m_device = VK_NULL_HANDLE; + m_width = 0; + m_height = 0; + m_format = Format::Unknown; + m_vkFormat = VK_FORMAT_UNDEFINED; + m_state = ResourceStates::Common; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp b/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp index c77b4b88..f508140d 100644 --- a/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp +++ b/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp @@ -14,6 +14,10 @@ #include "XCEngine/RHI/RHIScreenshot.h" #include "XCEngine/RHI/RHIEnums.h" +#if defined(XCENGINE_SUPPORT_VULKAN) +#include "XCEngine/RHI/Vulkan/VulkanSwapChain.h" +#endif + using namespace XCEngine::Debug; using namespace XCEngine::Containers; @@ -83,7 +87,7 @@ void RHIIntegrationFixture::SetUp() { ASSERT_NE(mDevice, nullptr); bool initResult = false; - if (GetParam() == RHIType::D3D12) { + if (GetParam() == RHIType::D3D12 || GetParam() == RHIType::Vulkan) { RHIDeviceDesc desc = {}; desc.enableDebugLayer = false; desc.enableGPUValidation = false; @@ -132,7 +136,7 @@ void RHIIntegrationFixture::SetUp() { auto* rtv = new D3D12ResourceView(); rtv->InitializeAsRenderTarget(device, backBuffer.GetResource(), &rtvDesc, mRTVHeap, i); - mRTVs.push_back(rtv); + mBackBufferViews.push_back(rtv); } mDepthStencilTexture = new D3D12Texture(); @@ -152,26 +156,55 @@ void RHIIntegrationFixture::SetUp() { } void RHIIntegrationFixture::BeginRender() { + if (GetParam() == RHIType::Vulkan) { +#if defined(XCENGINE_SUPPORT_VULKAN) + auto* vulkanSwapChain = static_cast(mSwapChain); + ASSERT_NE(vulkanSwapChain, nullptr); + ASSERT_TRUE(vulkanSwapChain->AcquireNextImage()); +#endif + } + mCurrentBackBufferIndex = mSwapChain->GetCurrentBackBufferIndex(); Log("[TEST] BeginRender: backBufferIndex=%d", mCurrentBackBufferIndex); } void RHIIntegrationFixture::SetRenderTargetForClear(bool includeDepthStencil) { if (GetParam() == RHIType::D3D12) { - Log("[TEST] SetRenderTargetForClear: D3D12 branch, mRTVs.size=%d, index=%d", - (int)mRTVs.size(), mCurrentBackBufferIndex); - if (!mRTVs.empty() && mCurrentBackBufferIndex < mRTVs.size()) { + Log("[TEST] SetRenderTargetForClear: D3D12 branch, mBackBufferViews.size=%d, index=%d", + (int)mBackBufferViews.size(), mCurrentBackBufferIndex); + if (!mBackBufferViews.empty() && mCurrentBackBufferIndex < mBackBufferViews.size()) { D3D12Texture* backBuffer = static_cast(mSwapChain->GetCurrentBackBuffer()); D3D12CommandList* d3d12CmdList = static_cast(mCommandList); Log("[TEST] SetRenderTargetForClear: calling TransitionBarrier"); d3d12CmdList->TransitionBarrier(backBuffer->GetResource(), ResourceStates::Present, ResourceStates::RenderTarget); - RHIResourceView* rtv = mRTVs[mCurrentBackBufferIndex]; + RHIResourceView* rtv = mBackBufferViews[mCurrentBackBufferIndex]; Log("[TEST] SetRenderTargetForClear: calling SetRenderTargets, rtv=%p", (void*)rtv); mCommandList->SetRenderTargets(1, &rtv, includeDepthStencil ? mDSV : nullptr); Log("[TEST] SetRenderTargetForClear: done"); } else { Log("[TEST] SetRenderTargetForClear: skipped - condition failed"); } + } else if (GetParam() == RHIType::Vulkan) { + if (mCurrentBackBufferIndex < 0) { + return; + } + + const size_t backBufferIndex = static_cast(mCurrentBackBufferIndex); + if (mBackBufferViews.size() <= backBufferIndex) { + mBackBufferViews.resize(backBufferIndex + 1, nullptr); + } + + if (mBackBufferViews[backBufferIndex] == nullptr) { + ResourceViewDesc viewDesc = {}; + viewDesc.dimension = ResourceViewDimension::Texture2D; + viewDesc.format = static_cast(Format::R8G8B8A8_UNorm); + viewDesc.arraySize = 1; + mBackBufferViews[backBufferIndex] = mDevice->CreateRenderTargetView(GetCurrentBackBuffer(), viewDesc); + } + + ASSERT_NE(mBackBufferViews[backBufferIndex], nullptr); + RHIResourceView* rtv = mBackBufferViews[backBufferIndex]; + mCommandList->SetRenderTargets(1, &rtv, nullptr); } } @@ -191,13 +224,17 @@ void RHIIntegrationFixture::TearDown() { mScreenshot = nullptr; } - if (GetParam() == RHIType::D3D12) { - for (auto* rtv : mRTVs) { - delete rtv; + for (auto* backBufferView : mBackBufferViews) { + if (backBufferView) { + backBufferView->Shutdown(); + delete backBufferView; } - mRTVs.clear(); + } + mBackBufferViews.clear(); + if (GetParam() == RHIType::D3D12) { if (mDSV) { + mDSV->Shutdown(); delete mDSV; mDSV = nullptr; } @@ -276,6 +313,8 @@ void RHIIntegrationFixture::WaitForGPU() { delete fence; } Sleep(100); + } else if (GetParam() == RHIType::Vulkan) { + mCommandQueue->WaitForIdle(); } } diff --git a/tests/RHI/integration/fixtures/RHIIntegrationFixture.h b/tests/RHI/integration/fixtures/RHIIntegrationFixture.h index 7b91a5d7..b490f5c6 100644 --- a/tests/RHI/integration/fixtures/RHIIntegrationFixture.h +++ b/tests/RHI/integration/fixtures/RHIIntegrationFixture.h @@ -2,6 +2,7 @@ #include #include +#include #include #include "XCEngine/RHI/RHIFactory.h" @@ -23,6 +24,10 @@ #include "XCEngine/RHI/D3D12/D3D12DescriptorHeap.h" #endif +#if defined(XCENGINE_SUPPORT_VULKAN) +#include "XCEngine/RHI/Vulkan/VulkanSwapChain.h" +#endif + namespace XCEngine { namespace RHI { namespace Integration { @@ -62,12 +67,12 @@ private: RHIScreenshot* mScreenshot = nullptr; HWND mWindow = nullptr; int mCurrentBackBufferIndex = 0; + std::vector mBackBufferViews; #if defined(XCENGINE_SUPPORT_D3D12) D3D12DescriptorHeap* mRTVHeap = nullptr; D3D12DescriptorHeap* mDSVHeap = nullptr; D3D12Texture* mDepthStencilTexture = nullptr; - std::vector mRTVs; RHIResourceView* mDSV = nullptr; #endif }; diff --git a/tests/RHI/integration/minimal/CMakeLists.txt b/tests/RHI/integration/minimal/CMakeLists.txt index 7f0ee22a..4b5de1e1 100644 --- a/tests/RHI/integration/minimal/CMakeLists.txt +++ b/tests/RHI/integration/minimal/CMakeLists.txt @@ -33,11 +33,16 @@ target_link_libraries(rhi_integration_minimal PRIVATE GTest::gtest ) +if(TARGET Vulkan::Vulkan) + target_link_libraries(rhi_integration_minimal PRIVATE Vulkan::Vulkan) +endif() + target_compile_definitions(rhi_integration_minimal PRIVATE UNICODE _UNICODE XCENGINE_SUPPORT_OPENGL XCENGINE_SUPPORT_D3D12 + XCENGINE_SUPPORT_VULKAN ) add_custom_command(TARGET rhi_integration_minimal POST_BUILD diff --git a/tests/RHI/integration/minimal/main.cpp b/tests/RHI/integration/minimal/main.cpp index 3f8b17c3..18bd672a 100644 --- a/tests/RHI/integration/minimal/main.cpp +++ b/tests/RHI/integration/minimal/main.cpp @@ -20,7 +20,16 @@ protected: }; const char* GetScreenshotFilename(RHIType type) { - return type == RHIType::D3D12 ? "minimal_d3d12.ppm" : "minimal_opengl.ppm"; + switch (type) { + case RHIType::D3D12: + return "minimal_d3d12.ppm"; + case RHIType::OpenGL: + return "minimal_opengl.ppm"; + case RHIType::Vulkan: + return "minimal_vulkan.ppm"; + default: + return "minimal_unknown.ppm"; + } } int GetComparisonThreshold(RHIType type) { @@ -94,6 +103,9 @@ TEST_P(MinimalTest, RenderClear) { INSTANTIATE_TEST_SUITE_P(D3D12, MinimalTest, ::testing::Values(RHIType::D3D12)); INSTANTIATE_TEST_SUITE_P(OpenGL, MinimalTest, ::testing::Values(RHIType::OpenGL)); +#if defined(XCENGINE_SUPPORT_VULKAN) +INSTANTIATE_TEST_SUITE_P(Vulkan, MinimalTest, ::testing::Values(RHIType::Vulkan)); +#endif GTEST_API_ int main(int argc, char** argv) { Logger::Get().Initialize(); diff --git a/tests/RHI/unit/CMakeLists.txt b/tests/RHI/unit/CMakeLists.txt index 17b43d71..0beead3c 100644 --- a/tests/RHI/unit/CMakeLists.txt +++ b/tests/RHI/unit/CMakeLists.txt @@ -6,6 +6,7 @@ find_package(GTest REQUIRED) set(TEST_SOURCES fixtures/RHITestFixture.cpp + test_factory.cpp test_device.cpp test_buffer.cpp test_texture.cpp @@ -30,7 +31,10 @@ set(TEST_SOURCES add_executable(rhi_unit_tests ${TEST_SOURCES}) -target_compile_definitions(rhi_unit_tests PRIVATE XCENGINE_SUPPORT_OPENGL) +target_compile_definitions(rhi_unit_tests PRIVATE + XCENGINE_SUPPORT_OPENGL + XCENGINE_SUPPORT_VULKAN +) target_link_libraries(rhi_unit_tests PRIVATE d3d12 @@ -42,6 +46,10 @@ target_link_libraries(rhi_unit_tests PRIVATE GTest::gtest_main ) +if(TARGET Vulkan::Vulkan) + target_link_libraries(rhi_unit_tests PRIVATE Vulkan::Vulkan) +endif() + target_include_directories(rhi_unit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/fixtures ${PROJECT_ROOT_DIR}/engine/include diff --git a/tests/RHI/unit/test_factory.cpp b/tests/RHI/unit/test_factory.cpp index f6016cd8..97fd8070 100644 --- a/tests/RHI/unit/test_factory.cpp +++ b/tests/RHI/unit/test_factory.cpp @@ -5,6 +5,10 @@ #include "XCEngine/RHI/RHIFactory.h" #include "XCEngine/RHI/D3D12/D3D12Device.h" +#if defined(XCENGINE_SUPPORT_VULKAN) +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#endif + using namespace XCEngine::RHI; TEST(RHIFactory, CreateD3D12Device_ReturnsValidPointer) { @@ -30,11 +34,29 @@ TEST(RHIFactory, CreateInvalidDevice_ReturnsNullptr) { } TEST(RHIFactory, CreateInvalidType_ReturnsNullptr) { - RHIDevice* device = RHIFactory::CreateRHIDevice(RHIType::Vulkan); + RHIDevice* device = RHIFactory::CreateRHIDevice(RHIType::Metal); ASSERT_EQ(device, nullptr); } +#if defined(XCENGINE_SUPPORT_VULKAN) +TEST(RHIFactory, CreateVulkanDevice_ReturnsValidPointer) { + RHIDevice* device = RHIFactory::CreateRHIDevice(RHIType::Vulkan); + + ASSERT_NE(device, nullptr); + + delete device; +} + +TEST(RHIFactory, CreateVulkanDeviceByName_ReturnsValidPointer) { + RHIDevice* device = RHIFactory::CreateRHIDevice("Vulkan"); + + ASSERT_NE(device, nullptr); + + delete device; +} +#endif + TEST(D3D12DeviceCreation, DirectCreation_Success) { D3D12Device* device = new D3D12Device(); @@ -42,3 +64,13 @@ TEST(D3D12DeviceCreation, DirectCreation_Success) { delete device; } + +#if defined(XCENGINE_SUPPORT_VULKAN) +TEST(VulkanDeviceCreation, DirectCreation_Success) { + VulkanDevice* device = new VulkanDevice(); + + ASSERT_NE(device, nullptr); + + delete device; +} +#endif