Add Vulkan RHI minimal backend path
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
71
engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h
Normal file
71
engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h
Normal file
@@ -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
|
||||
43
engine/include/XCEngine/RHI/Vulkan/VulkanCommandQueue.h
Normal file
43
engine/include/XCEngine/RHI/Vulkan/VulkanCommandQueue.h
Normal file
@@ -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
|
||||
85
engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h
Normal file
85
engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef VK_USE_PLATFORM_WIN32_KHR
|
||||
#define VK_USE_PLATFORM_WIN32_KHR
|
||||
#endif
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include "XCEngine/RHI/RHIEnums.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
||||
74
engine/include/XCEngine/RHI/Vulkan/VulkanDevice.h
Normal file
74
engine/include/XCEngine/RHI/Vulkan/VulkanDevice.h
Normal file
@@ -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
|
||||
25
engine/include/XCEngine/RHI/Vulkan/VulkanFence.h
Normal file
25
engine/include/XCEngine/RHI/Vulkan/VulkanFence.h
Normal file
@@ -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
|
||||
38
engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h
Normal file
38
engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h
Normal file
@@ -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
|
||||
15
engine/include/XCEngine/RHI/Vulkan/VulkanScreenshot.h
Normal file
15
engine/include/XCEngine/RHI/Vulkan/VulkanScreenshot.h
Normal file
@@ -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
|
||||
59
engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h
Normal file
59
engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCEngine/RHI/RHISwapChain.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanCommon.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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<std::unique_ptr<VulkanTexture>> m_backBuffers;
|
||||
};
|
||||
|
||||
} // namespace RHI
|
||||
} // namespace XCEngine
|
||||
57
engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h
Normal file
57
engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCEngine/RHI/RHITexture.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanCommon.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
} // namespace XCEngine
|
||||
|
||||
326
engine/src/RHI/Vulkan/VulkanCommandList.cpp
Normal file
326
engine/src/RHI/Vulkan/VulkanCommandList.cpp
Normal file
@@ -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<VulkanResourceView*>(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<VulkanResourceView*>(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
|
||||
80
engine/src/RHI/Vulkan/VulkanCommandQueue.cpp
Normal file
80
engine/src/RHI/Vulkan/VulkanCommandQueue.cpp
Normal file
@@ -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 <vector>
|
||||
|
||||
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<VkCommandBuffer> commandBuffers;
|
||||
commandBuffers.reserve(count);
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
auto* commandList = static_cast<VulkanCommandList*>(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<uint32_t>(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
|
||||
342
engine/src/RHI/Vulkan/VulkanDevice.cpp
Normal file
342
engine/src/RHI/Vulkan/VulkanDevice.cpp
Normal file
@@ -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 <algorithm>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
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<uint32_t>(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<VkPhysicalDevice> devices(deviceCount);
|
||||
vkEnumeratePhysicalDevices(m_instance, &deviceCount, devices.data());
|
||||
|
||||
for (VkPhysicalDevice physicalDevice : devices) {
|
||||
uint32_t queueFamilyCount = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
|
||||
std::vector<VkQueueFamilyProperties> 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<uint32_t>(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<uint32_t>(properties.limits.maxSamplerAnisotropy);
|
||||
m_capabilities.majorVersion = static_cast<int>(m_deviceInfo.majorVersion);
|
||||
m_capabilities.minorVersion = static_cast<int>(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<VulkanCommandQueue*>(presentQueue), static_cast<HWND__*>(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<CommandQueueType>(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<VulkanTexture*>(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
|
||||
47
engine/src/RHI/Vulkan/VulkanResourceView.cpp
Normal file
47
engine/src/RHI/Vulkan/VulkanResourceView.cpp
Normal file
@@ -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<Format>(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
|
||||
187
engine/src/RHI/Vulkan/VulkanScreenshot.cpp
Normal file
187
engine/src/RHI/Vulkan/VulkanScreenshot.cpp
Normal file
@@ -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 <cstdio>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
bool VulkanScreenshot::Capture(RHIDevice* device, RHISwapChain* swapChain, const char* filename) {
|
||||
auto* vkDevice = static_cast<VulkanDevice*>(device);
|
||||
auto* texture = static_cast<VulkanTexture*>(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<VkDeviceSize>(width) * static_cast<VkDeviceSize>(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<const uint8_t*>(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<size_t>(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
|
||||
234
engine/src/RHI/Vulkan/VulkanSwapChain.cpp
Normal file
234
engine/src/RHI/Vulkan/VulkanSwapChain.cpp
Normal file
@@ -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 <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
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<VkSurfaceFormatKHR> 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<VkPresentModeKHR> 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<uint32_t>)(2, capabilities.minImageCount);
|
||||
const uint32_t imageCount = capabilities.maxImageCount > 0
|
||||
? std::min<uint32_t>(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<VkImage> 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<VulkanTexture>();
|
||||
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
|
||||
39
engine/src/RHI/Vulkan/VulkanTexture.cpp
Normal file
39
engine/src/RHI/Vulkan/VulkanTexture.cpp
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user