diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-09-13 15:59:15 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-09-13 15:59:15 -0400 |
| commit | c2e5d2468ad6a38cdb8a067da0678302f6cc6066 (patch) | |
| tree | 97c448d28e54068d84c422e9f172996b7a95f1ed /tools/gfx/vulkan | |
| parent | 0b6321b3f08c48e37e6b8256d420f05d9727fb5a (diff) | |
Refactor render-test to make cross platform (#1053)
* First pass of render-test refactor.
* Make window construction a function that can choose an implementation.
* Remove OpenGL as currently has windows dependency.
* Disable Vulkan as Renderer impl has dependency on windows.
* Pass Window in as parameter of 'update'.
* Add win-window.cpp as was missing.
* Fix warning on windows about signs during comparison.
Diffstat (limited to 'tools/gfx/vulkan')
| -rw-r--r-- | tools/gfx/vulkan/render-vk.cpp | 2793 | ||||
| -rw-r--r-- | tools/gfx/vulkan/render-vk.h | 10 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-api.cpp | 142 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-api.h | 203 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-device-queue.cpp | 199 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-device-queue.h | 94 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-module.cpp | 76 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-module.h | 40 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-swap-chain.cpp | 421 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-swap-chain.h | 141 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-util.cpp | 59 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-util.h | 41 |
12 files changed, 4219 insertions, 0 deletions
diff --git a/tools/gfx/vulkan/render-vk.cpp b/tools/gfx/vulkan/render-vk.cpp new file mode 100644 index 000000000..2221187ed --- /dev/null +++ b/tools/gfx/vulkan/render-vk.cpp @@ -0,0 +1,2793 @@ +// render-vk.cpp +#include "render-vk.h" + +//WORKING:#include "options.h" +#include "../render.h" + +#include "../../source/core/slang-smart-pointer.h" + +#include "vk-api.h" +#include "vk-util.h" +#include "vk-device-queue.h" +#include "vk-swap-chain.h" + +#include "../surface.h" + +// Vulkan has a different coordinate system to ogl +// http://anki3d.org/vulkan-coordinate-system/ + +#define ENABLE_VALIDATION_LAYER 1 + +#ifdef _MSC_VER +# include <stddef.h> +# pragma warning(disable: 4996) +# if (_MSC_VER < 1900) +# define snprintf sprintf_s +# endif +#endif + +namespace gfx { +using namespace Slang; + +class VKRenderer : public Renderer +{ +public: + enum + { + kMaxRenderTargets = 8, + kMaxAttachments = kMaxRenderTargets + 1, + + kMaxDescriptorSets = 4, + }; + + // Renderer implementation + virtual SlangResult initialize(const Desc& desc, void* inWindowHandle) override; + virtual const List<String>& getFeatures() override { return m_features; } + virtual void setClearColor(const float color[4]) override; + virtual void clearFrame() override; + virtual void presentFrame() override; + TextureResource::Desc getSwapChainTextureDesc() override; + + Result createTextureResource(Resource::Usage initialUsage, const TextureResource::Desc& desc, const TextureResource::Data* initData, TextureResource** outResource) override; + Result createBufferResource(Resource::Usage initialUsage, const BufferResource::Desc& desc, const void* initData, BufferResource** outResource) override; + Result createSamplerState(SamplerState::Desc const& desc, SamplerState** outSampler) override; + + Result createTextureView(TextureResource* texture, ResourceView::Desc const& desc, ResourceView** outView) override; + Result createBufferView(BufferResource* buffer, ResourceView::Desc const& desc, ResourceView** outView) override; + + Result createInputLayout(const InputElementDesc* inputElements, UInt inputElementCount, InputLayout** outLayout) override; + + Result createDescriptorSetLayout(const DescriptorSetLayout::Desc& desc, DescriptorSetLayout** outLayout) override; + Result createPipelineLayout(const PipelineLayout::Desc& desc, PipelineLayout** outLayout) override; + Result createDescriptorSet(DescriptorSetLayout* layout, DescriptorSet** outDescriptorSet) override; + + Result createProgram(const ShaderProgram::Desc& desc, ShaderProgram** outProgram) override; + Result createGraphicsPipelineState(const GraphicsPipelineStateDesc& desc, PipelineState** outState) override; + Result createComputePipelineState(const ComputePipelineStateDesc& desc, PipelineState** outState) override; + + virtual SlangResult captureScreenSurface(Surface& surface) override; + + virtual void* map(BufferResource* buffer, MapFlavor flavor) override; + virtual void unmap(BufferResource* buffer) override; + virtual void setPrimitiveTopology(PrimitiveTopology topology) override; + + virtual void setDescriptorSet(PipelineType pipelineType, PipelineLayout* layout, UInt index, DescriptorSet* descriptorSet) override; + + virtual void setVertexBuffers(UInt startSlot, UInt slotCount, BufferResource*const* buffers, const UInt* strides, const UInt* offsets) override; + virtual void setIndexBuffer(BufferResource* buffer, Format indexFormat, UInt offset) override; + virtual void setDepthStencilTarget(ResourceView* depthStencilView) override; + void setViewports(UInt count, Viewport const* viewports) override; + void setScissorRects(UInt count, ScissorRect const* rects) override; + virtual void setPipelineState(PipelineType pipelineType, PipelineState* state) override; + virtual void draw(UInt vertexCount, UInt startVertex) override; + virtual void drawIndexed(UInt indexCount, UInt startIndex, UInt baseVertex) override; + virtual void dispatchCompute(int x, int y, int z) override; + virtual void submitGpuWork() override; + virtual void waitForGpu() override; + virtual RendererType getRendererType() const override { return RendererType::Vulkan; } + + /// Dtor + ~VKRenderer(); + + protected: + + class Buffer + { + public: + /// Initialize a buffer with specified size, and memory props + Result init(const VulkanApi& api, size_t bufferSize, VkBufferUsageFlags usage, VkMemoryPropertyFlags reqMemoryProperties); + + /// Returns true if has been initialized + bool isInitialized() const { return m_api != nullptr; } + + // Default Ctor + Buffer(): + m_api(nullptr) + {} + + /// Dtor + ~Buffer() + { + if (m_api) + { + m_api->vkDestroyBuffer(m_api->m_device, m_buffer, nullptr); + m_api->vkFreeMemory(m_api->m_device, m_memory, nullptr); + } + } + + VkBuffer m_buffer; + VkDeviceMemory m_memory; + const VulkanApi* m_api; + }; + + class InputLayoutImpl : public InputLayout + { + public: + List<VkVertexInputAttributeDescription> m_vertexDescs; + int m_vertexSize; + }; + + class BufferResourceImpl: public BufferResource + { + public: + typedef BufferResource Parent; + + BufferResourceImpl(Resource::Usage initialUsage, const BufferResource::Desc& desc, VKRenderer* renderer): + Parent(desc), + m_renderer(renderer), + m_initialUsage(initialUsage) + { + assert(renderer); + } + + Resource::Usage m_initialUsage; + VKRenderer* m_renderer; + Buffer m_buffer; + Buffer m_uploadBuffer; + List<uint8_t> m_readBuffer; ///< Stores the contents when a map read is performed + + MapFlavor m_mapFlavor = MapFlavor::Unknown; ///< If resource is mapped, records what kind of mapping else Unknown (if not mapped) + }; + + class TextureResourceImpl : public TextureResource + { + public: + typedef TextureResource Parent; + + TextureResourceImpl(const Desc& desc, Usage initialUsage, const VulkanApi* api) : + Parent(desc), + m_initialUsage(initialUsage), + m_api(api) + { + } + ~TextureResourceImpl() + { + if (m_api) + { + if (m_imageMemory != VK_NULL_HANDLE) + { + m_api->vkFreeMemory(m_api->m_device, m_imageMemory, nullptr); + } + if (m_image != VK_NULL_HANDLE) + { + m_api->vkDestroyImage(m_api->m_device, m_image, nullptr); + } + } + } + + Usage m_initialUsage; + + VkImage m_image = VK_NULL_HANDLE; + VkDeviceMemory m_imageMemory = VK_NULL_HANDLE; + + const VulkanApi* m_api; + }; + + class SamplerStateImpl : public SamplerState + { + public: + VkSampler m_sampler; + }; + + class ResourceViewImpl : public ResourceView + { + public: + enum class ViewType + { + Texture, + TexelBuffer, + PlainBuffer, + }; + ViewType m_type; + }; + + class TextureResourceViewImpl : public ResourceViewImpl + { + public: + TextureResourceViewImpl() + { + m_type = ViewType::Texture; + } + + RefPtr<TextureResourceImpl> m_texture; + VkImageView m_view; + VkImageLayout m_layout; + }; + + class TexelBufferResourceViewImpl : public ResourceViewImpl + { + public: + TexelBufferResourceViewImpl() + { + m_type = ViewType::TexelBuffer; + } + + RefPtr<BufferResourceImpl> m_buffer; + VkBufferView m_view; + }; + + class PlainBufferResourceViewImpl : public ResourceViewImpl + { + public: + PlainBufferResourceViewImpl() + { + m_type = ViewType::PlainBuffer; + } + + RefPtr<BufferResourceImpl> m_buffer; + VkDeviceSize offset; + VkDeviceSize size; + }; + + class ShaderProgramImpl: public ShaderProgram + { + public: + + ShaderProgramImpl(PipelineType pipelineType): + m_pipelineType(pipelineType) + {} + + PipelineType m_pipelineType; + + VkPipelineShaderStageCreateInfo m_compute; + VkPipelineShaderStageCreateInfo m_vertex; + VkPipelineShaderStageCreateInfo m_fragment; + + List<char> m_buffers[2]; //< To keep storage of code in scope + }; + + class DescriptorSetLayoutImpl : public DescriptorSetLayout + { + public: + DescriptorSetLayoutImpl(const VulkanApi& api) + : m_api(&api) + { + } + + ~DescriptorSetLayoutImpl() + { + if(m_descriptorSetLayout != VK_NULL_HANDLE) + { + m_api->vkDestroyDescriptorSetLayout(m_api->m_device, m_descriptorSetLayout, nullptr); + } + if (m_descriptorPool != VK_NULL_HANDLE) + { + m_api->vkDestroyDescriptorPool(m_api->m_device, m_descriptorPool, nullptr); + } + } + + VulkanApi const* m_api; + VkDescriptorSetLayout m_descriptorSetLayout = VK_NULL_HANDLE; + VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE; + + struct RangeInfo + { + VkDescriptorType descriptorType; + }; + List<RangeInfo> m_ranges; + }; + + class PipelineLayoutImpl : public PipelineLayout + { + public: + PipelineLayoutImpl(const VulkanApi& api) + : m_api(&api) + { + } + + ~PipelineLayoutImpl() + { + if (m_pipelineLayout != VK_NULL_HANDLE) + { + m_api->vkDestroyPipelineLayout(m_api->m_device, m_pipelineLayout, nullptr); + } + } + + VulkanApi const* m_api; + VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; + UInt m_descriptorSetCount = 0; + }; + + class DescriptorSetImpl : public DescriptorSet + { + public: + // Record the view binding + struct Binding + { + enum class Type : uint8_t + { + Unknown, + ResourceView, + SamplerState, + BufferResource, + CountOf, + }; + Type type; + uint32_t range; + uint32_t index; + RefPtr<RefObject> obj; + }; + + DescriptorSetImpl(VKRenderer* renderer) + : m_renderer(renderer) + { + } + + ~DescriptorSetImpl() + { + } + + virtual void setConstantBuffer(UInt range, UInt index, BufferResource* buffer) override; + virtual void setResource(UInt range, UInt index, ResourceView* view) override; + virtual void setSampler(UInt range, UInt index, SamplerState* sampler) override; + virtual void setCombinedTextureSampler( + UInt range, + UInt index, + ResourceView* textureView, + SamplerState* sampler) override; + + static Binding::Type _getBindingType(RefObject* ptr); + void _setBinding(Binding::Type type, UInt range, UInt index, RefObject* ptr); + + VKRenderer* m_renderer = nullptr; ///< Weak pointer, can't be strong, because if set will become circular reference + RefPtr<DescriptorSetLayoutImpl> m_layout; + VkDescriptorSet m_descriptorSet = VK_NULL_HANDLE; + + List<Binding> m_bindings; ///< Records entities are bound to this descriptor set, and keeps the associated resources/views/state in scope + }; + +#if 0 + struct BindingDetail + { + VkImageView m_srv = VK_NULL_HANDLE; + VkBufferView m_uav = VK_NULL_HANDLE; + VkSampler m_sampler = VK_NULL_HANDLE; + }; + + class BindingStateImpl: public BindingState + { + public: + typedef BindingState Parent; + + BindingStateImpl(const Desc& desc, const VulkanApi* api): + Parent(desc), + m_api(api) + { + } + ~BindingStateImpl() + { + for (int i = 0; i < int(m_bindingDetails.Count()); ++i) + { + BindingDetail& detail = m_bindingDetails[i]; + if (detail.m_sampler != VK_NULL_HANDLE) + { + m_api->vkDestroySampler(m_api->m_device, detail.m_sampler, nullptr); + } + if (detail.m_srv != VK_NULL_HANDLE) + { + m_api->vkDestroyImageView(m_api->m_device, detail.m_srv, nullptr); + } + if (detail.m_uav != VK_NULL_HANDLE) + { + m_api->vkDestroyBufferView(m_api->m_device, detail.m_uav, nullptr); + } + } + } + + const VulkanApi* m_api; + List<BindingDetail> m_bindingDetails; + }; +#endif + + struct BoundVertexBuffer + { + RefPtr<BufferResourceImpl> m_buffer; + int m_stride; + int m_offset; + }; + + class PipelineStateImpl : public PipelineState + { + public: + PipelineStateImpl(const VulkanApi& api): + m_api(&api) + { + } + ~PipelineStateImpl() + { + if (m_pipeline != VK_NULL_HANDLE) + { + m_api->vkDestroyPipeline(m_api->m_device, m_pipeline, nullptr); + } + } + + const VulkanApi* m_api; + +// VkPrimitiveTopology m_primitiveTopology; + + RefPtr<PipelineLayoutImpl> m_pipelineLayout; + +// RefPtr<InputLayoutImpl> m_inputLayout; + RefPtr<ShaderProgramImpl> m_shaderProgram; + + VkPipeline m_pipeline = VK_NULL_HANDLE; + }; + + VkBool32 handleDebugMessage(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject, + size_t location, int32_t msgCode, const char* pLayerPrefix, const char* pMsg); + + VkPipelineShaderStageCreateInfo compileEntryPoint( + ShaderProgram::KernelDesc const& kernelDesc, + VkShaderStageFlagBits stage, + List<char>& bufferOut); + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject, + size_t location, int32_t msgCode, const char* pLayerPrefix, const char* pMsg, void* pUserData); + + /// Returns true if m_currentPipeline matches the current configuration +// Pipeline* _getPipeline(); +// bool _isEqual(const Pipeline& pipeline) const; +// Slang::Result _createPipeline(RefPtr<Pipeline>& pipelineOut); + void _beginRender(); + void _endRender(); + + Slang::Result _beginPass(); + void _endPass(); + void _transitionImageLayout(VkImage image, VkFormat format, const TextureResource::Desc& desc, VkImageLayout oldLayout, VkImageLayout newLayout); + + VkDebugReportCallbackEXT m_debugReportCallback; + +// RefPtr<InputLayoutImpl> m_currentInputLayout; + +// RefPtr<BindingStateImpl> m_currentBindingState; + RefPtr<PipelineLayoutImpl> m_currentPipelineLayout; + + RefPtr<DescriptorSetImpl> m_currentDescriptorSetImpls [kMaxDescriptorSets]; + VkDescriptorSet m_currentDescriptorSets [kMaxDescriptorSets]; + +// RefPtr<ShaderProgramImpl> m_currentProgram; + +// List<RefPtr<Pipeline> > m_pipelineCache; + RefPtr<PipelineStateImpl> m_currentPipeline; + + List<BoundVertexBuffer> m_boundVertexBuffers; + + VkPrimitiveTopology m_primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + VkDevice m_device = VK_NULL_HANDLE; + + VulkanModule m_module; + VulkanApi m_api; + + VulkanDeviceQueue m_deviceQueue; + VulkanSwapChain m_swapChain; + + VkRenderPass m_renderPass = VK_NULL_HANDLE; + + int m_swapChainImageIndex = -1; + + float m_clearColor[4] = { 0, 0, 0, 0 }; + + Desc m_desc; + List<String> m_features; +}; + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! VkRenderer::Buffer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +Result VKRenderer::Buffer::init(const VulkanApi& api, size_t bufferSize, VkBufferUsageFlags usage, VkMemoryPropertyFlags reqMemoryProperties) +{ + assert(!isInitialized()); + + m_api = &api; + m_memory = VK_NULL_HANDLE; + m_buffer = VK_NULL_HANDLE; + + VkBufferCreateInfo bufferCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferCreateInfo.size = bufferSize; + bufferCreateInfo.usage = usage; + + SLANG_VK_CHECK(api.vkCreateBuffer(api.m_device, &bufferCreateInfo, nullptr, &m_buffer)); + + VkMemoryRequirements memoryReqs = {}; + api.vkGetBufferMemoryRequirements(api.m_device, m_buffer, &memoryReqs); + + int memoryTypeIndex = api.findMemoryTypeIndex(memoryReqs.memoryTypeBits, reqMemoryProperties); + assert(memoryTypeIndex >= 0); + + VkMemoryPropertyFlags actualMemoryProperites = api.m_deviceMemoryProperties.memoryTypes[memoryTypeIndex].propertyFlags; + + VkMemoryAllocateInfo allocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + allocateInfo.allocationSize = memoryReqs.size; + allocateInfo.memoryTypeIndex = memoryTypeIndex; + + SLANG_VK_CHECK(api.vkAllocateMemory(api.m_device, &allocateInfo, nullptr, &m_memory)); + SLANG_VK_CHECK(api.vkBindBufferMemory(api.m_device, m_buffer, m_memory, 0)); + + return SLANG_OK; +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! VkRenderer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +#if 0 +bool VKRenderer::_isEqual(const Pipeline& pipeline) const +{ + return + pipeline.m_pipelineLayout == m_currentPipelineLayout && + pipeline.m_primitiveTopology == m_primitiveTopology && + pipeline.m_inputLayout == m_currentInputLayout && + pipeline.m_shaderProgram == m_currentProgram; +} + +VKRenderer::Pipeline* VKRenderer::_getPipeline() +{ + if (m_currentPipeline && _isEqual(*m_currentPipeline)) + { + return m_currentPipeline; + } + + // Look for a match in the cache + for (int i = 0; i < int(m_pipelineCache.Count()); ++i) + { + Pipeline* pipeline = m_pipelineCache[i]; + if (_isEqual(*pipeline)) + { + m_currentPipeline = pipeline; + return pipeline; + } + } + + RefPtr<Pipeline> pipeline; + SLANG_RETURN_NULL_ON_FAIL(_createPipeline(pipeline)); + m_pipelineCache.Add(pipeline); + m_currentPipeline = pipeline; + return pipeline; +} + +Slang::Result VKRenderer::_createPipeline(RefPtr<Pipeline>& pipelineOut) +{ + RefPtr<Pipeline> pipeline(new Pipeline(m_api)); + + // Initialize the state + pipeline->m_primitiveTopology = m_primitiveTopology; + pipeline->m_pipelineLayout = m_currentPipelineLayout; + pipeline->m_shaderProgram = m_currentProgram; + pipeline->m_inputLayout = m_currentInputLayout; + + // Must be equal at this point if all the items are correctly set in pipeline + assert(_isEqual(*pipeline)); + + VkPipelineCache pipelineCache = VK_NULL_HANDLE; + + if (m_currentProgram->m_pipelineType == PipelineType::Compute) + { + // Then create a pipeline to use that layout + + VkComputePipelineCreateInfo computePipelineInfo = { VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO }; + computePipelineInfo.stage = m_currentProgram->m_compute; + computePipelineInfo.layout = pipeline->m_pipelineLayout->m_pipelineLayout; + + SLANG_VK_CHECK(m_api.vkCreateComputePipelines(m_device, pipelineCache, 1, &computePipelineInfo, nullptr, &pipeline->m_pipeline)); + } + else if (m_currentProgram->m_pipelineType == PipelineType::Graphics) + { + // Create the graphics pipeline + + const int width = m_swapChain.getWidth(); + const int height = m_swapChain.getHeight(); + + VkPipelineShaderStageCreateInfo shaderStages[] = { m_currentProgram->m_vertex, m_currentProgram->m_fragment }; + + // VertexBuffer/s + // Currently only handles one + + VkPipelineVertexInputStateCreateInfo vertexInputInfo = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkVertexInputBindingDescription vertexInputBindingDescription; + + if (m_currentInputLayout) + { + vertexInputBindingDescription.binding = 0; + vertexInputBindingDescription.stride = m_currentInputLayout->m_vertexSize; + vertexInputBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + const auto& srcAttributeDescs = m_currentInputLayout->m_vertexDescs; + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &vertexInputBindingDescription; + + vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(srcAttributeDescs.Count()); + vertexInputInfo.pVertexAttributeDescriptions = srcAttributeDescs.getBuffer(); + } + + // + + VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)width; + viewport.height = (float)height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = { 0, 0 }; + scissor.extent = { uint32_t(width), uint32_t(height) }; + + VkPipelineViewportStateCreateInfo viewportState = {}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending = {}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + VkGraphicsPipelineCreateInfo pipelineInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.layout = pipeline->m_pipelineLayout->m_pipelineLayout; + pipelineInfo.renderPass = m_renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + SLANG_VK_CHECK(m_api.vkCreateGraphicsPipelines(m_device, pipelineCache, 1, &pipelineInfo, nullptr, &pipeline->m_pipeline)); + } + else + { + assert(!"Unhandled program type"); + return SLANG_FAIL; + } + + pipelineOut = pipeline; + return SLANG_OK; +} +#endif + +Result VKRenderer::_beginPass() +{ + if (m_swapChainImageIndex < 0) + { + return SLANG_FAIL; + } + + const int numRenderTargets = 1; + + const VulkanSwapChain::Image& image = m_swapChain.getImages()[m_swapChainImageIndex]; + + int numAttachments = 0; + + // Start render pass + VkClearValue clearValues[kMaxAttachments]; + clearValues[numAttachments++] = VkClearValue{ m_clearColor[0], m_clearColor[1], m_clearColor[2], m_clearColor[3] }; + + bool hasDepthBuffer = false; + if (hasDepthBuffer) + { + VkClearValue& clearValue = clearValues[numAttachments++]; + + clearValue.depthStencil.depth = 1.0f; + clearValue.depthStencil.stencil = 0; + } + + const int width = m_swapChain.getWidth(); + const int height = m_swapChain.getHeight(); + + VkCommandBuffer cmdBuffer = m_deviceQueue.getCommandBuffer(); + + VkRenderPassBeginInfo renderPassBegin = {}; + renderPassBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassBegin.renderPass = m_renderPass; + renderPassBegin.framebuffer = image.m_frameBuffer; + renderPassBegin.renderArea.offset.x = 0; + renderPassBegin.renderArea.offset.y = 0; + renderPassBegin.renderArea.extent.width = width; + renderPassBegin.renderArea.extent.height = height; + renderPassBegin.clearValueCount = numAttachments; + renderPassBegin.pClearValues = clearValues; + + m_api.vkCmdBeginRenderPass(cmdBuffer, &renderPassBegin, VK_SUBPASS_CONTENTS_INLINE); + + // Set up scissor and viewport + { + VkRect2D rects[kMaxRenderTargets] = {}; + VkViewport viewports[kMaxRenderTargets] = {}; + for (int i = 0; i < numRenderTargets; ++i) + { + rects[i] = VkRect2D{ 0, 0, uint32_t(width), uint32_t(height) }; + + VkViewport& dstViewport = viewports[i]; + + dstViewport.x = 0.0f; + dstViewport.y = 0.0f; + dstViewport.width = float(width); + dstViewport.height = float(height); + dstViewport.minDepth = 0.0f; + dstViewport.maxDepth = 1.0f; + } + + m_api.vkCmdSetScissor(cmdBuffer, 0, numRenderTargets, rects); + m_api.vkCmdSetViewport(cmdBuffer, 0, numRenderTargets, viewports); + } + + return SLANG_OK; +} + +void VKRenderer::_endPass() +{ + VkCommandBuffer cmdBuffer = m_deviceQueue.getCommandBuffer(); + m_api.vkCmdEndRenderPass(cmdBuffer); +} + +void VKRenderer::_beginRender() +{ + m_swapChainImageIndex = m_swapChain.nextFrontImageIndex(); + + if (m_swapChainImageIndex < 0) + { + return; + } +} + +void VKRenderer::_endRender() +{ + m_deviceQueue.flush(); +} + +Renderer* createVKRenderer() +{ + return new VKRenderer; +} + +VKRenderer::~VKRenderer() +{ + if (m_renderPass != VK_NULL_HANDLE) + { + m_api.vkDestroyRenderPass(m_device, m_renderPass, nullptr); + m_renderPass = VK_NULL_HANDLE; + } +} + + +VkBool32 VKRenderer::handleDebugMessage(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject, + size_t location, int32_t msgCode, const char* pLayerPrefix, const char* pMsg) +{ + char const* severity = "message"; + if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) + severity = "warning"; + if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) + severity = "error"; + + // pMsg can be really big (it can be assembler dump for example) + // Use a dynamic buffer to store + size_t bufferSize = strlen(pMsg) + 1 + 1024; + List<char> bufferArray; + bufferArray.setCount(bufferSize); + char* buffer = bufferArray.getBuffer(); + + sprintf_s(buffer, + bufferSize, + "%s: %s %d: %s\n", + pLayerPrefix, + severity, + msgCode, + pMsg); + + fprintf(stderr, "%s", buffer); + fflush(stderr); + + OutputDebugStringA(buffer); + + return VK_FALSE; +} + +/* static */VKAPI_ATTR VkBool32 VKAPI_CALL VKRenderer::debugMessageCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject, + size_t location, int32_t msgCode, const char* pLayerPrefix, const char* pMsg, void* pUserData) +{ + return ((VKRenderer*)pUserData)->handleDebugMessage(flags, objType, srcObject, location, msgCode, pLayerPrefix, pMsg); +} + +VkPipelineShaderStageCreateInfo VKRenderer::compileEntryPoint( + ShaderProgram::KernelDesc const& kernelDesc, + VkShaderStageFlagBits stage, + List<char>& bufferOut) +{ + char const* dataBegin = (char const*) kernelDesc.codeBegin; + char const* dataEnd = (char const*) kernelDesc.codeEnd; + + // We need to make a copy of the code, since the Slang compiler + // will free the memory after a compile request is closed. + size_t codeSize = dataEnd - dataBegin; + + bufferOut.insertRange(0, dataBegin, codeSize); + + char* codeBegin = bufferOut.getBuffer(); + + VkShaderModuleCreateInfo moduleCreateInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; + moduleCreateInfo.pCode = (uint32_t*)codeBegin; + moduleCreateInfo.codeSize = codeSize; + + VkShaderModule module; + SLANG_VK_CHECK(m_api.vkCreateShaderModule(m_device, &moduleCreateInfo, nullptr, &module)); + + VkPipelineShaderStageCreateInfo shaderStageCreateInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; + shaderStageCreateInfo.stage = stage; + + shaderStageCreateInfo.module = module; + shaderStageCreateInfo.pName = "main"; + + return shaderStageCreateInfo; +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!! Renderer interface !!!!!!!!!!!!!!!!!!!!!!!!!! + +SlangResult VKRenderer::initialize(const Desc& desc, void* inWindowHandle) +{ + SLANG_RETURN_ON_FAIL(m_module.init()); + SLANG_RETURN_ON_FAIL(m_api.initGlobalProcs(m_module)); + + m_desc = desc; + + VkApplicationInfo applicationInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO }; + applicationInfo.pApplicationName = "slang-render-test"; + applicationInfo.pEngineName = "slang-render-test"; + applicationInfo.apiVersion = VK_API_VERSION_1_0; + + char const* instanceExtensions[] = + { + VK_KHR_SURFACE_EXTENSION_NAME, + + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + +#if SLANG_WINDOWS_FAMILY + VK_KHR_WIN32_SURFACE_EXTENSION_NAME, +#else + VK_KHR_XLIB_SURFACE_EXTENSION_NAME +#endif + +#if ENABLE_VALIDATION_LAYER + VK_EXT_DEBUG_REPORT_EXTENSION_NAME, +#endif + }; + + VkInstance instance = VK_NULL_HANDLE; + + VkInstanceCreateInfo instanceCreateInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; + instanceCreateInfo.pApplicationInfo = &applicationInfo; + + instanceCreateInfo.enabledExtensionCount = SLANG_COUNT_OF(instanceExtensions); + instanceCreateInfo.ppEnabledExtensionNames = &instanceExtensions[0]; + +#if ENABLE_VALIDATION_LAYER + const char* layerNames[] = { "VK_LAYER_LUNARG_standard_validation" }; + instanceCreateInfo.enabledLayerCount = SLANG_COUNT_OF(layerNames); + instanceCreateInfo.ppEnabledLayerNames = layerNames; +#endif + + SLANG_VK_RETURN_ON_FAIL(m_api.vkCreateInstance(&instanceCreateInfo, nullptr, &instance)); + SLANG_RETURN_ON_FAIL(m_api.initInstanceProcs(instance)); + +#if ENABLE_VALIDATION_LAYER + VkDebugReportFlagsEXT debugFlags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; + + VkDebugReportCallbackCreateInfoEXT debugCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT }; + debugCreateInfo.pfnCallback = &debugMessageCallback; + debugCreateInfo.pUserData = this; + debugCreateInfo.flags = debugFlags; + + SLANG_VK_RETURN_ON_FAIL(m_api.vkCreateDebugReportCallbackEXT(instance, &debugCreateInfo, nullptr, &m_debugReportCallback)); +#endif + + uint32_t numPhysicalDevices = 0; + SLANG_VK_RETURN_ON_FAIL(m_api.vkEnumeratePhysicalDevices(instance, &numPhysicalDevices, nullptr)); + + List<VkPhysicalDevice> physicalDevices; + physicalDevices.setCount(numPhysicalDevices); + SLANG_VK_RETURN_ON_FAIL(m_api.vkEnumeratePhysicalDevices(instance, &numPhysicalDevices, physicalDevices.getBuffer())); + + Index selectedDeviceIndex = 0; + + if (desc.adapter.getLength()) + { + selectedDeviceIndex = -1; + + String lowerAdapter = desc.adapter.toLower(); + + for (Index i = 0; i < physicalDevices.getCount(); ++i) + { + auto physicalDevice = physicalDevices[i]; + + VkPhysicalDeviceProperties basicProps = {}; + m_api.vkGetPhysicalDeviceProperties(physicalDevice, &basicProps); + + String lowerName = String(basicProps.deviceName).toLower(); + + if (lowerName.indexOf(lowerAdapter) != Index(-1)) + { + selectedDeviceIndex = i; + break; + } + } + if (selectedDeviceIndex < 0) + { + // Device not found + return SLANG_FAIL; + } + } + + SLANG_RETURN_ON_FAIL(m_api.initPhysicalDevice(physicalDevices[selectedDeviceIndex])); + + List<const char*> deviceExtensions; + deviceExtensions.add(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + + VkDeviceCreateInfo deviceCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; + deviceCreateInfo.queueCreateInfoCount = 1; + + deviceCreateInfo.pEnabledFeatures = &m_api.m_deviceFeatures; + + // Get the device features (doesn't use, but useful when debugging) + if (m_api.vkGetPhysicalDeviceFeatures2) + { + VkPhysicalDeviceFeatures2 deviceFeatures2 = {}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + m_api.vkGetPhysicalDeviceFeatures2(m_api.m_physicalDevice, &deviceFeatures2); + } + + VkPhysicalDeviceProperties basicProps = {}; + m_api.vkGetPhysicalDeviceProperties(m_api.m_physicalDevice, &basicProps); + + // Get the API version + const uint32_t majorVersion = VK_VERSION_MAJOR(basicProps.apiVersion); + const uint32_t minorVersion = VK_VERSION_MINOR(basicProps.apiVersion); + + // Float16 features + // Need in this scope because it will be linked into the device creation (if it is available) + VkPhysicalDeviceFloat16Int8FeaturesKHR float16Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR }; + + // API version check, can't use vkGetPhysicalDeviceProperties2 yet since this device might not support it + if (VK_MAKE_VERSION(majorVersion, minorVersion, 0) >= VK_API_VERSION_1_1 && + m_api.vkGetPhysicalDeviceProperties2 && + m_api.vkGetPhysicalDeviceFeatures2) + { + VkPhysicalDeviceProperties2 physicalDeviceProps2; + + physicalDeviceProps2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + physicalDeviceProps2.pNext = nullptr; + physicalDeviceProps2.properties = {}; + + m_api.vkGetPhysicalDeviceProperties2(m_api.m_physicalDevice, &physicalDeviceProps2); + + // Get device features + VkPhysicalDeviceFeatures2 deviceFeatures2 = {}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + + // Link together for lookup + float16Features.pNext = deviceFeatures2.pNext; + deviceFeatures2.pNext = &float16Features; + + m_api.vkGetPhysicalDeviceFeatures2(m_api.m_physicalDevice, &deviceFeatures2); + + // If we have float16 features then enable + if (float16Features.shaderFloat16) + { + // Link into the creation features + float16Features.pNext = (void*)deviceCreateInfo.pNext; + deviceCreateInfo.pNext = &float16Features; + + // Add the Float16 extension + deviceExtensions.add(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME); + + // We have half support + m_features.add("half"); + } + } + + int queueFamilyIndex = m_api.findQueue(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT); + assert(queueFamilyIndex >= 0); + + float queuePriority = 0.0f; + VkDeviceQueueCreateInfo queueCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; + queueCreateInfo.queueFamilyIndex = queueFamilyIndex; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + + deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; + + deviceCreateInfo.enabledExtensionCount = uint32_t(deviceExtensions.getCount()); + deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.getBuffer(); + + SLANG_VK_RETURN_ON_FAIL(m_api.vkCreateDevice(m_api.m_physicalDevice, &deviceCreateInfo, nullptr, &m_device)); + SLANG_RETURN_ON_FAIL(m_api.initDeviceProcs(m_device)); + + { + VkQueue queue; + m_api.vkGetDeviceQueue(m_device, queueFamilyIndex, 0, &queue); + SLANG_RETURN_ON_FAIL(m_deviceQueue.init(m_api, queue, queueFamilyIndex)); + } + + // set up swap chain + + { + VulkanSwapChain::Desc desc; + VulkanSwapChain::PlatformDesc* platformDesc = nullptr; + + desc.init(); + desc.m_format = Format::RGBA_Unorm_UInt8; + +#if SLANG_WINDOWS_FAMILY + VulkanSwapChain::WinPlatformDesc winPlatformDesc; + winPlatformDesc.m_hinstance = ::GetModuleHandle(nullptr); + winPlatformDesc.m_hwnd = (HWND)inWindowHandle; + platformDesc = &winPlatformDesc; +#endif + + SLANG_RETURN_ON_FAIL(m_swapChain.init(&m_deviceQueue, desc, platformDesc)); + } + + // depth/stencil? + + // render pass? + + { + const int numRenderTargets = 1; + bool shouldClear = true; + bool shouldClearDepth = false; + bool shouldClearStencil = false; + bool hasDepthBuffer = false; + + Format depthFormat = Format::Unknown; + VkFormat colorFormat = m_swapChain.getVkFormat(); + + int numAttachments = 0; + // We need extra space if we have depth buffer + VkAttachmentDescription attachmentDesc[kMaxRenderTargets + 1] = {}; + for (int i = 0; i < numRenderTargets; ++i) + { + VkAttachmentDescription& dst = attachmentDesc[numAttachments ++]; + + dst.flags = 0; + dst.format = colorFormat; + dst.samples = VK_SAMPLE_COUNT_1_BIT; + dst.loadOp = shouldClear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + dst.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + dst.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + dst.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + dst.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + dst.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + } + if (hasDepthBuffer) + { + VkAttachmentDescription& dst = attachmentDesc[numAttachments++]; + + dst.flags = 0; + dst.format = VulkanUtil::getVkFormat(depthFormat); + dst.samples = VK_SAMPLE_COUNT_1_BIT; + dst.loadOp = shouldClearDepth ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + dst.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + dst.stencilLoadOp = shouldClearStencil ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + dst.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + dst.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + dst.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + } + + VkAttachmentReference colorAttachments[kMaxRenderTargets] = {}; + for (int i = 0; i < numRenderTargets; ++i) + { + VkAttachmentReference& dst = colorAttachments[i]; + dst.attachment = i; + dst.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + } + + VkAttachmentReference depthAttachment = {}; + depthAttachment.attachment = numRenderTargets; + depthAttachment.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpassDesc = {}; + subpassDesc.flags = 0; + subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpassDesc.inputAttachmentCount = 0u; + subpassDesc.pInputAttachments = nullptr; + subpassDesc.colorAttachmentCount = numRenderTargets; + subpassDesc.pColorAttachments = colorAttachments; + subpassDesc.pResolveAttachments = nullptr; + subpassDesc.pDepthStencilAttachment = hasDepthBuffer ? &depthAttachment : nullptr; + subpassDesc.preserveAttachmentCount = 0u; + subpassDesc.pPreserveAttachments = nullptr; + + VkRenderPassCreateInfo renderPassCreateInfo = {}; + renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassCreateInfo.attachmentCount = numAttachments; + renderPassCreateInfo.pAttachments = attachmentDesc; + renderPassCreateInfo.subpassCount = 1; + renderPassCreateInfo.pSubpasses = &subpassDesc; + SLANG_VK_RETURN_ON_FAIL(m_api.vkCreateRenderPass(m_device, &renderPassCreateInfo, nullptr, &m_renderPass)); + } + + // frame buffer + SLANG_RETURN_ON_FAIL(m_swapChain.createFrameBuffers(m_renderPass)); + + _beginRender(); + + return SLANG_OK; +} + +void VKRenderer::submitGpuWork() +{ + m_deviceQueue.flush(); +} + +void VKRenderer::waitForGpu() +{ + m_deviceQueue.flushAndWait(); +} + +void VKRenderer::setClearColor(const float color[4]) +{ + for (int ii = 0; ii < 4; ++ii) + m_clearColor[ii] = color[ii]; +} + +void VKRenderer::clearFrame() +{ +} + +void VKRenderer::presentFrame() +{ + _endRender(); + + const bool vsync = true; + m_swapChain.present(vsync); + + _beginRender(); +} + +TextureResource::Desc VKRenderer::getSwapChainTextureDesc() +{ + TextureResource::Desc desc; + desc.init2D(Resource::Type::Texture2D, Format::Unknown, m_desc.width, m_desc.height, 1); + return desc; +} + +SlangResult VKRenderer::captureScreenSurface(Surface& surfaceOut) +{ + return SLANG_FAIL; +} + +static VkBufferUsageFlagBits _calcBufferUsageFlags(Resource::BindFlag::Enum bind) +{ + typedef Resource::BindFlag BindFlag; + + switch (bind) + { + case BindFlag::VertexBuffer: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + case BindFlag::IndexBuffer: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + case BindFlag::ConstantBuffer: return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + case BindFlag::StreamOutput: + case BindFlag::RenderTarget: + case BindFlag::DepthStencil: + { + assert(!"Not supported yet"); + return VkBufferUsageFlagBits(0); + } + case BindFlag::UnorderedAccess: return VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT; + case BindFlag::PixelShaderResource: return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + case BindFlag::NonPixelShaderResource: return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + default: return VkBufferUsageFlagBits(0); + } +} + +static VkBufferUsageFlagBits _calcBufferUsageFlags(int bindFlags) +{ + int dstFlags = 0; + while (bindFlags) + { + int lsb = bindFlags & -bindFlags; + dstFlags |= _calcBufferUsageFlags(Resource::BindFlag::Enum(lsb)); + bindFlags &= ~lsb; + } + return VkBufferUsageFlagBits(dstFlags); +} + +static VkBufferUsageFlags _calcBufferUsageFlags(int bindFlags, int cpuAccessFlags, const void* initData) +{ + VkBufferUsageFlags usage = _calcBufferUsageFlags(bindFlags); + + if (cpuAccessFlags & Resource::AccessFlag::Read) + { + // If it can be read from, set this + usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + } + if ((cpuAccessFlags & Resource::AccessFlag::Write) || initData) + { + usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + } + + return usage; +} + +static VkImageUsageFlagBits _calcImageUsageFlags(Resource::BindFlag::Enum bind) +{ + typedef Resource::BindFlag BindFlag; + + switch (bind) + { + case BindFlag::RenderTarget: return VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + case BindFlag::DepthStencil: return VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + case BindFlag::NonPixelShaderResource: + case BindFlag::PixelShaderResource: + { + // Ignore + return VkImageUsageFlagBits(0); + } + default: + { + assert(!"Unsupported"); + return VkImageUsageFlagBits(0); + } + } +} + +static VkImageUsageFlagBits _calcImageUsageFlags(int bindFlags) +{ + int dstFlags = 0; + while (bindFlags) + { + int lsb = bindFlags & -bindFlags; + dstFlags |= _calcImageUsageFlags(Resource::BindFlag::Enum(lsb)); + bindFlags &= ~lsb; + } + return VkImageUsageFlagBits(dstFlags); +} + +static VkImageUsageFlags _calcImageUsageFlags(int bindFlags, int cpuAccessFlags, const void* initData) +{ + VkImageUsageFlags usage = _calcImageUsageFlags(bindFlags); + + usage |= VK_IMAGE_USAGE_SAMPLED_BIT; + + if (cpuAccessFlags & Resource::AccessFlag::Read) + { + // If it can be read from, set this + usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + } + if ((cpuAccessFlags & Resource::AccessFlag::Write) || initData) + { + usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + } + + return usage; +} + +void VKRenderer::_transitionImageLayout(VkImage image, VkFormat format, const TextureResource::Desc& desc, VkImageLayout oldLayout, VkImageLayout newLayout) +{ + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = desc.numMipLevels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } + else + { + assert(!"unsupported layout transition!"); + return; + } + + VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); + + m_api.vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier); +} + +Result VKRenderer::createTextureResource(Resource::Usage initialUsage, const TextureResource::Desc& descIn, const TextureResource::Data* initData, TextureResource** outResource) +{ + TextureResource::Desc desc(descIn); + desc.setDefaults(initialUsage); + + const VkFormat format = VulkanUtil::getVkFormat(desc.format); + if (format == VK_FORMAT_UNDEFINED) + { + assert(!"Unhandled image format"); + return SLANG_FAIL; + } + + const int arraySize = desc.calcEffectiveArraySize(); + + RefPtr<TextureResourceImpl> texture(new TextureResourceImpl(desc, initialUsage, &m_api)); + + // Create the image + { + VkImageCreateInfo imageInfo = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + + switch (desc.type) + { + case Resource::Type::Texture1D: + { + imageInfo.imageType = VK_IMAGE_TYPE_1D; + imageInfo.extent = VkExtent3D{ uint32_t(descIn.size.width), 1, 1 }; + break; + } + case Resource::Type::Texture2D: + { + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent = VkExtent3D{ uint32_t(descIn.size.width), uint32_t(descIn.size.height), 1 }; + break; + } + case Resource::Type::TextureCube: + { + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent = VkExtent3D{ uint32_t(descIn.size.width), uint32_t(descIn.size.height), 1 }; + break; + } + case Resource::Type::Texture3D: + { + // Can't have an array and 3d texture + assert(desc.arraySize <= 1); + + imageInfo.imageType = VK_IMAGE_TYPE_3D; + imageInfo.extent = VkExtent3D{ uint32_t(descIn.size.width), uint32_t(descIn.size.height), uint32_t(descIn.size.depth) }; + break; + } + default: + { + assert(!"Unhandled type"); + return SLANG_FAIL; + } + } + + imageInfo.mipLevels = desc.numMipLevels; + imageInfo.arrayLayers = arraySize; + + imageInfo.format = format; + + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.usage = _calcImageUsageFlags(desc.bindFlags, desc.cpuAccessFlags, initData); + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.flags = 0; // Optional + + SLANG_VK_RETURN_ON_FAIL(m_api.vkCreateImage(m_device, &imageInfo, nullptr, &texture->m_image)); + } + + VkMemoryRequirements memRequirements; + m_api.vkGetImageMemoryRequirements(m_device, texture->m_image, &memRequirements); + + // Allocate the memory + { + VkMemoryPropertyFlags reqMemoryProperties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + + VkMemoryAllocateInfo allocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + + int memoryTypeIndex = m_api.findMemoryTypeIndex(memRequirements.memoryTypeBits, reqMemoryProperties); + assert(memoryTypeIndex >= 0); + + VkMemoryPropertyFlags actualMemoryProperites = m_api.m_deviceMemoryProperties.memoryTypes[memoryTypeIndex].propertyFlags; + + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = memoryTypeIndex; + + SLANG_VK_RETURN_ON_FAIL(m_api.vkAllocateMemory(m_device, &allocInfo, nullptr, &texture->m_imageMemory)); + } + + // Bind the memory to the image + m_api.vkBindImageMemory(m_device, texture->m_image, texture->m_imageMemory, 0); + + if (initData) + { + List<TextureResource::Size> mipSizes; + + VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); + + const int numMipMaps = desc.numMipLevels; + assert(initData->numMips == numMipMaps); + + // Calculate how large the buffer has to be + size_t bufferSize = 0; + // Calculate how large an array entry is + for (int j = 0; j < numMipMaps; ++j) + { + const TextureResource::Size mipSize = desc.size.calcMipSize(j); + + const int rowSizeInBytes = Surface::calcRowSize(desc.format, mipSize.width); + const int numRows = Surface::calcNumRows(desc.format, mipSize.height); + + mipSizes.add(mipSize); + + bufferSize += (rowSizeInBytes * numRows) * mipSize.depth; + } + + + // Calculate the total size taking into account the array + bufferSize *= arraySize; + + Buffer uploadBuffer; + SLANG_RETURN_ON_FAIL(uploadBuffer.init(m_api, bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)); + + assert(mipSizes.getCount() == numMipMaps); + + // Copy into upload buffer + { + int subResourceIndex = 0; + + uint8_t* dstData; + m_api.vkMapMemory(m_device, uploadBuffer.m_memory, 0, bufferSize, 0, (void**)&dstData); + + for (int i = 0; i < arraySize; ++i) + { + for (Index j = 0; j < mipSizes.getCount(); ++j) + { + const auto& mipSize = mipSizes[j]; + + const ptrdiff_t srcRowStride = initData->mipRowStrides[j]; + const int dstRowSizeInBytes = Surface::calcRowSize(desc.format, mipSize.width); + const int numRows = Surface::calcNumRows(desc.format, mipSize.height); + + for (int k = 0; k < mipSize.depth; k++) + { + const uint8_t* srcData = (const uint8_t*)(initData->subResources[subResourceIndex]); + + for (int l = 0; l < numRows; l++) + { + ::memcpy(dstData, srcData, dstRowSizeInBytes); + + dstData += dstRowSizeInBytes; + srcData += srcRowStride; + } + + subResourceIndex++; + } + } + } + + m_api.vkUnmapMemory(m_device, uploadBuffer.m_memory); + } + + _transitionImageLayout(texture->m_image, format, texture->getDesc(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + { + size_t srcOffset = 0; + for (int i = 0; i < arraySize; ++i) + { + for (Index j = 0; j < mipSizes.getCount(); ++j) + { + const auto& mipSize = mipSizes[j]; + + const int rowSizeInBytes = Surface::calcRowSize(desc.format, mipSize.width); + const int numRows = Surface::calcNumRows(desc.format, mipSize.height); + + // https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkBufferImageCopy.html + // bufferRowLength and bufferImageHeight specify the data in buffer memory as a subregion of a larger two- or three-dimensional image, + // and control the addressing calculations of data in buffer memory. If either of these values is zero, that aspect of the buffer memory + // is considered to be tightly packed according to the imageExtent. + + VkBufferImageCopy region = {}; + + region.bufferOffset = srcOffset; + region.bufferRowLength = 0; //rowSizeInBytes; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = uint32_t(j); + region.imageSubresource.baseArrayLayer = i; + region.imageSubresource.layerCount = 1; + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { uint32_t(mipSize.width), uint32_t(mipSize.height), uint32_t(mipSize.depth) }; + + // Do the copy (do all depths in a single go) + m_api.vkCmdCopyBufferToImage(commandBuffer, uploadBuffer.m_buffer, texture->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + // Next + srcOffset += rowSizeInBytes * numRows * mipSize.depth; + } + } + } + + _transitionImageLayout(texture->m_image, format, texture->getDesc(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + m_deviceQueue.flushAndWait(); + } + + *outResource = texture.detach(); + return SLANG_OK; +} + +Result VKRenderer::createBufferResource(Resource::Usage initialUsage, const BufferResource::Desc& descIn, const void* initData, BufferResource** outResource) +{ + BufferResource::Desc desc(descIn); + desc.setDefaults(initialUsage); + + const size_t bufferSize = desc.sizeInBytes; + + VkMemoryPropertyFlags reqMemoryProperties = 0; + + VkBufferUsageFlags usage = _calcBufferUsageFlags(desc.bindFlags, desc.cpuAccessFlags, initData); + + switch (initialUsage) + { + case Resource::Usage::ConstantBuffer: + { + reqMemoryProperties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + break; + } + default: break; + } + + RefPtr<BufferResourceImpl> buffer(new BufferResourceImpl(initialUsage, desc, this)); + SLANG_RETURN_ON_FAIL(buffer->m_buffer.init(m_api, desc.sizeInBytes, usage, reqMemoryProperties)); + + if ((desc.cpuAccessFlags & Resource::AccessFlag::Write) || initData) + { + SLANG_RETURN_ON_FAIL(buffer->m_uploadBuffer.init(m_api, bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)); + } + + if (initData) + { + // TODO: only create staging buffer if the memory type + // used for the buffer doesn't let us fill things in + // directly. + // Copy into staging buffer + void* mappedData = nullptr; + SLANG_VK_CHECK(m_api.vkMapMemory(m_device, buffer->m_uploadBuffer.m_memory, 0, bufferSize, 0, &mappedData)); + ::memcpy(mappedData, initData, bufferSize); + m_api.vkUnmapMemory(m_device, buffer->m_uploadBuffer.m_memory); + + // Copy from staging buffer to real buffer + VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); + + VkBufferCopy copyInfo = {}; + copyInfo.size = bufferSize; + m_api.vkCmdCopyBuffer(commandBuffer, buffer->m_uploadBuffer.m_buffer, buffer->m_buffer.m_buffer, 1, ©Info); + + //flushCommandBuffer(commandBuffer); + } + + *outResource = buffer.detach(); + return SLANG_OK; +} + + +VkFilter translateFilterMode(TextureFilteringMode mode) +{ + switch (mode) + { + default: + return VkFilter(0); + +#define CASE(SRC, DST) \ + case TextureFilteringMode::SRC: return VK_FILTER_##DST + + CASE(Point, NEAREST); + CASE(Linear, LINEAR); + +#undef CASE + } +} + +VkSamplerMipmapMode translateMipFilterMode(TextureFilteringMode mode) +{ + switch (mode) + { + default: + return VkSamplerMipmapMode(0); + +#define CASE(SRC, DST) \ + case TextureFilteringMode::SRC: return VK_SAMPLER_MIPMAP_MODE_##DST + + CASE(Point, NEAREST); + CASE(Linear, LINEAR); + +#undef CASE + } +} + +VkSamplerAddressMode translateAddressingMode(TextureAddressingMode mode) +{ + switch (mode) + { + default: + return VkSamplerAddressMode(0); + +#define CASE(SRC, DST) \ + case TextureAddressingMode::SRC: return VK_SAMPLER_ADDRESS_MODE_##DST + + CASE(Wrap, REPEAT); + CASE(ClampToEdge, CLAMP_TO_EDGE); + CASE(ClampToBorder, CLAMP_TO_BORDER); + CASE(MirrorRepeat, MIRRORED_REPEAT); + CASE(MirrorOnce, MIRROR_CLAMP_TO_EDGE); + +#undef CASE + } +} + +static VkCompareOp translateComparisonFunc(ComparisonFunc func) +{ + switch (func) + { + default: + // TODO: need to report failures + return VK_COMPARE_OP_ALWAYS; + +#define CASE(FROM, TO) \ + case ComparisonFunc::FROM: return VK_COMPARE_OP_##TO + + CASE(Never, NEVER); + CASE(Less, LESS); + CASE(Equal, EQUAL); + CASE(LessEqual, LESS_OR_EQUAL); + CASE(Greater, GREATER); + CASE(NotEqual, NOT_EQUAL); + CASE(GreaterEqual, GREATER_OR_EQUAL); + CASE(Always, ALWAYS); +#undef CASE + } +} + +Result VKRenderer::createSamplerState(SamplerState::Desc const& desc, SamplerState** outSampler) +{ + VkSamplerCreateInfo samplerInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; + + samplerInfo.magFilter = translateFilterMode(desc.minFilter); + samplerInfo.minFilter = translateFilterMode(desc.magFilter); + + samplerInfo.addressModeU = translateAddressingMode(desc.addressU); + samplerInfo.addressModeV = translateAddressingMode(desc.addressV); + samplerInfo.addressModeW = translateAddressingMode(desc.addressW); + + samplerInfo.anisotropyEnable = desc.maxAnisotropy > 1; + samplerInfo.maxAnisotropy = (float) desc.maxAnisotropy; + + // TODO: support translation of border color... + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = desc.reductionOp == TextureReductionOp::Comparison; + samplerInfo.compareOp = translateComparisonFunc(desc.comparisonFunc); + samplerInfo.mipmapMode = translateMipFilterMode(desc.mipFilter); + + VkSampler sampler; + SLANG_VK_RETURN_ON_FAIL(m_api.vkCreateSampler(m_device, &samplerInfo, nullptr, &sampler)); + + RefPtr<SamplerStateImpl> samplerImpl = new SamplerStateImpl(); + samplerImpl->m_sampler = sampler; + *outSampler = samplerImpl.detach(); + return SLANG_OK; +} + +Result VKRenderer::createTextureView(TextureResource* texture, ResourceView::Desc const& desc, ResourceView** outView) +{ + assert(!"unimplemented"); + return SLANG_FAIL; +} + +Result VKRenderer::createBufferView(BufferResource* buffer, ResourceView::Desc const& desc, ResourceView** outView) +{ + auto resourceImpl = (BufferResourceImpl*) buffer; + + // TODO: These should come from the `ResourceView::Desc` + VkDeviceSize offset = 0; + VkDeviceSize size = resourceImpl->getDesc().sizeInBytes; + + // There are two different cases we need to think about for buffers. + // + // One is when we have a "uniform texel buffer" or "storage texel buffer," + // in which case we need to construct a `VkBufferView` to represent the + // formatting that is applied to the buffer. This case would correspond + // to a `textureBuffer` or `imageBuffer` in GLSL, and more or less to + // `Buffer<..>` or `RWBuffer<...>` in HLSL. + // + // The other case is a `storage buffer` which is the catch-all for any + // non-formatted R/W access to a buffer. In GLSL this is a `buffer { ... }` + // declaration, while in HLSL it covers a bunch of different `RW*Buffer` + // cases. In these cases we do *not* need a `VkBufferView`, but in + // order to be compatible with other APIs that require views for any + // potentially writable access, we will have to create one anyway. + // + // We will distinguish the two cases by looking at whether the view + // is being requested with a format or not. + // + + switch(desc.type) + { + default: + assert(!"unhandled"); + return SLANG_FAIL; + + case ResourceView::Type::UnorderedAccess: + // Is this a formatted view? + // + if(desc.format == Format::Unknown) + { + // Buffer usage that doesn't involve formatting doesn't + // require a view in Vulkan. + RefPtr<PlainBufferResourceViewImpl> viewImpl = new PlainBufferResourceViewImpl(); + viewImpl->m_buffer = resourceImpl; + viewImpl->offset = 0; + viewImpl->size = size; + *outView = viewImpl.detach(); + return SLANG_OK; + } + // + // If the view is formatted, then we need to handle + // it just like we would for a "sampled" buffer: + // + // FALLTHROUGH + case ResourceView::Type::ShaderResource: + { + VkBufferViewCreateInfo info = { VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO }; + + info.format = VulkanUtil::getVkFormat(desc.format); + info.buffer = resourceImpl->m_buffer.m_buffer; + info.offset = offset; + info.range = size; + + VkBufferView view; + SLANG_VK_RETURN_ON_FAIL(m_api.vkCreateBufferView(m_device, &info, nullptr, &view)); + + RefPtr<TexelBufferResourceViewImpl> viewImpl = new TexelBufferResourceViewImpl(); + viewImpl->m_buffer = resourceImpl; + viewImpl->m_view = view; + *outView = viewImpl.detach(); + return SLANG_OK; + } + break; + } +} + +Result VKRenderer::createInputLayout(const InputElementDesc* elements, UInt numElements, InputLayout** outLayout) +{ + RefPtr<InputLayoutImpl> layout(new InputLayoutImpl); + + List<VkVertexInputAttributeDescription>& dstVertexDescs = layout->m_vertexDescs; + + size_t vertexSize = 0; + dstVertexDescs.setCount(numElements); + + for (UInt i = 0; i < numElements; ++i) + { + const InputElementDesc& srcDesc = elements[i]; + VkVertexInputAttributeDescription& dstDesc = dstVertexDescs[i]; + + dstDesc.location = uint32_t(i); + dstDesc.binding = 0; + dstDesc.format = VulkanUtil::getVkFormat(srcDesc.format); + if (dstDesc.format == VK_FORMAT_UNDEFINED) + { + return SLANG_FAIL; + } + + dstDesc.offset = uint32_t(srcDesc.offset); + + const size_t elementSize = RendererUtil::getFormatSize(srcDesc.format); + assert(elementSize > 0); + const size_t endElement = srcDesc.offset + elementSize; + + vertexSize = (vertexSize < endElement) ? endElement : vertexSize; + } + + // Work out the overall size + layout->m_vertexSize = int(vertexSize); + *outLayout = layout.detach(); + return SLANG_OK; +} + +void* VKRenderer::map(BufferResource* bufferIn, MapFlavor flavor) +{ + BufferResourceImpl* buffer = static_cast<BufferResourceImpl*>(bufferIn); + assert(buffer->m_mapFlavor == MapFlavor::Unknown); + + // Make sure everything has completed before reading... + m_deviceQueue.flushAndWait(); + + const size_t bufferSize = buffer->getDesc().sizeInBytes; + + switch (flavor) + { + case MapFlavor::WriteDiscard: + case MapFlavor::HostWrite: + { + if (!buffer->m_uploadBuffer.isInitialized()) + { + return nullptr; + } + + void* mappedData = nullptr; + SLANG_VK_CHECK(m_api.vkMapMemory(m_device, buffer->m_uploadBuffer.m_memory, 0, bufferSize, 0, &mappedData)); + buffer->m_mapFlavor = flavor; + return mappedData; + } + case MapFlavor::HostRead: + { + // Make sure there is space in the read buffer + buffer->m_readBuffer.setCount(bufferSize); + + // create staging buffer + Buffer staging; + + SLANG_RETURN_NULL_ON_FAIL(staging.init(m_api, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)); + + // Copy from real buffer to staging buffer + VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); + + VkBufferCopy copyInfo = {}; + copyInfo.size = bufferSize; + m_api.vkCmdCopyBuffer(commandBuffer, buffer->m_buffer.m_buffer, staging.m_buffer, 1, ©Info); + + m_deviceQueue.flushAndWait(); + + // Write out the data from the buffer + void* mappedData = nullptr; + SLANG_VK_CHECK(m_api.vkMapMemory(m_device, staging.m_memory, 0, bufferSize, 0, &mappedData)); + + ::memcpy(buffer->m_readBuffer.getBuffer(), mappedData, bufferSize); + m_api.vkUnmapMemory(m_device, staging.m_memory); + + buffer->m_mapFlavor = flavor; + + return buffer->m_readBuffer.getBuffer(); + } + default: + return nullptr; + } +} + +void VKRenderer::unmap(BufferResource* bufferIn) +{ + BufferResourceImpl* buffer = static_cast<BufferResourceImpl*>(bufferIn); + assert(buffer->m_mapFlavor != MapFlavor::Unknown); + + const size_t bufferSize = buffer->getDesc().sizeInBytes; + + switch (buffer->m_mapFlavor) + { + case MapFlavor::WriteDiscard: + case MapFlavor::HostWrite: + { + m_api.vkUnmapMemory(m_device, buffer->m_uploadBuffer.m_memory); + + // Copy from staging buffer to real buffer + VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); + + VkBufferCopy copyInfo = {}; + copyInfo.size = bufferSize; + m_api.vkCmdCopyBuffer(commandBuffer, buffer->m_uploadBuffer.m_buffer, buffer->m_buffer.m_buffer, 1, ©Info); + + // TODO: is this necessary? + //m_deviceQueue.flushAndWait(); + break; + } + default: break; + } + + // Mark as no longer mapped + buffer->m_mapFlavor = MapFlavor::Unknown; +} + +void VKRenderer::setPrimitiveTopology(PrimitiveTopology topology) +{ + m_primitiveTopology = VulkanUtil::getVkPrimitiveTopology(topology); +} + +void VKRenderer::setVertexBuffers(UInt startSlot, UInt slotCount, BufferResource*const* buffers, const UInt* strides, const UInt* offsets) +{ + { + const Index num = Index(startSlot + slotCount); + if (num > m_boundVertexBuffers.getCount()) + { + m_boundVertexBuffers.setCount(num); + } + } + + for (Index i = 0; i < Index(slotCount); i++) + { + BufferResourceImpl* buffer = static_cast<BufferResourceImpl*>(buffers[i]); + if (buffer) + { + assert(buffer->m_initialUsage == Resource::Usage::VertexBuffer); + } + + BoundVertexBuffer& boundBuffer = m_boundVertexBuffers[startSlot + i]; + boundBuffer.m_buffer = buffer; + boundBuffer.m_stride = int(strides[i]); + boundBuffer.m_offset = int(offsets[i]); + } +} + +void VKRenderer::setIndexBuffer(BufferResource* buffer, Format indexFormat, UInt offset) +{ +} + +void VKRenderer::setDepthStencilTarget(ResourceView* depthStencilView) +{ +} + +void VKRenderer::setViewports(UInt count, Viewport const* viewports) +{ + static const int kMaxViewports = 8; // TODO: base on device caps + assert(count <= kMaxViewports); + + VkViewport vkViewports[kMaxViewports]; + for(UInt ii = 0; ii < count; ++ii) + { + auto& inViewport = viewports[ii]; + auto& vkViewport = vkViewports[ii]; + + vkViewport.x = inViewport.originX; + vkViewport.y = inViewport.originY; + vkViewport.width = inViewport.extentX; + vkViewport.height = inViewport.extentY; + vkViewport.minDepth = inViewport.minZ; + vkViewport.maxDepth = inViewport.maxZ; + } + + VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); + m_api.vkCmdSetViewport(commandBuffer, 0, uint32_t(count), vkViewports); +} + +void VKRenderer::setScissorRects(UInt count, ScissorRect const* rects) +{ + static const int kMaxScissorRects = 8; // TODO: base on device caps + assert(count <= kMaxScissorRects); + + VkRect2D vkRects[kMaxScissorRects]; + for(UInt ii = 0; ii < count; ++ii) + { + auto& inRect = rects[ii]; + auto& vkRect = vkRects[ii]; + + vkRect.offset.x = int32_t(inRect.minX); + vkRect.offset.y = int32_t(inRect.minY); + vkRect.extent.width = uint32_t(inRect.maxX - inRect.minX); + vkRect.extent.height = uint32_t(inRect.maxY - inRect.minY); + } + + VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); + m_api.vkCmdSetScissor(commandBuffer, 0, uint32_t(count), vkRects); +} + +void VKRenderer::setPipelineState(PipelineType pipelineType, PipelineState* state) +{ + m_currentPipeline = (PipelineStateImpl*)state; +} + +void VKRenderer::draw(UInt vertexCount, UInt startVertex = 0) +{ + auto pipeline = m_currentPipeline; + if (!pipeline || pipeline->m_shaderProgram->m_pipelineType != PipelineType::Graphics) + { + assert(!"Invalid render pipeline"); + return; + } + + SLANG_RETURN_VOID_ON_FAIL(_beginPass()); + + // Also create descriptor sets based on the given pipeline layout + VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); + + m_api.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->m_pipeline); + + auto pipelineLayoutImpl = pipeline->m_pipelineLayout.Ptr(); + m_api.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayoutImpl->m_pipelineLayout, + 0, uint32_t(pipelineLayoutImpl->m_descriptorSetCount), + &m_currentDescriptorSets[0], + 0, nullptr); + + // Bind the vertex buffer + if (m_boundVertexBuffers.getCount() > 0 && m_boundVertexBuffers[0].m_buffer) + { + const BoundVertexBuffer& boundVertexBuffer = m_boundVertexBuffers[0]; + + VkBuffer vertexBuffers[] = { boundVertexBuffer.m_buffer->m_buffer.m_buffer }; + VkDeviceSize offsets[] = { VkDeviceSize(boundVertexBuffer.m_offset) }; + + m_api.vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + } + + m_api.vkCmdDraw(commandBuffer, static_cast<uint32_t>(vertexCount), 1, 0, 0); + + _endPass(); +} + +void VKRenderer::drawIndexed(UInt indexCount, UInt startIndex, UInt baseVertex) +{ +} + +void VKRenderer::dispatchCompute(int x, int y, int z) +{ + auto pipeline = m_currentPipeline; + if (!pipeline || pipeline->m_shaderProgram->m_pipelineType != PipelineType::Compute) + { + assert(!"Invalid compute pipeline"); + return; + } + + // Also create descriptor sets based on the given pipeline layout + VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); + + m_api.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->m_pipeline); + + auto pipelineLayoutImpl = pipeline->m_pipelineLayout.Ptr(); + m_api.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayoutImpl->m_pipelineLayout, + 0, uint32_t(pipelineLayoutImpl->m_descriptorSetCount), + &m_currentDescriptorSets[0], + 0, nullptr); + + m_api.vkCmdDispatch(commandBuffer, x, y, z); +} + +static VkImageViewType _calcImageViewType(TextureResource::Type type, const TextureResource::Desc& desc) +{ + switch (type) + { + case Resource::Type::Texture1D: return desc.arraySize > 1 ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D; + case Resource::Type::Texture2D: return desc.arraySize > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; + case Resource::Type::TextureCube: return desc.arraySize > 1 ? VK_IMAGE_VIEW_TYPE_CUBE_ARRAY : VK_IMAGE_VIEW_TYPE_CUBE; + case Resource::Type::Texture3D: + { + // Can't have an array and 3d texture + assert(desc.arraySize <= 1); + if (desc.arraySize <= 1) + { + return VK_IMAGE_VIEW_TYPE_3D; + } + break; + } + default: break; + } + + return VK_IMAGE_VIEW_TYPE_MAX_ENUM; +} + +#if 0 +BindingState* VKRenderer::createBindingState(const BindingState::Desc& bindingStateDesc) +{ + RefPtr<BindingStateImpl> bindingState(new BindingStateImpl(bindingStateDesc, &m_api)); + + const auto& srcBindings = bindingStateDesc.m_bindings; + const int numBindings = int(srcBindings.Count()); + + auto& dstDetails = bindingState->m_bindingDetails; + dstDetails.SetSize(numBindings); + + for (int i = 0; i < numBindings; ++i) + { + auto& dstDetail = dstDetails[i]; + const auto& srcBinding = srcBindings[i]; + + switch (srcBinding.bindingType) + { + case BindingType::Buffer: + { + if (!srcBinding.resource || !srcBinding.resource->isBuffer()) + { + assert(!"Needs to have a buffer resource set"); + return nullptr; + } + + BufferResourceImpl* bufferResource = static_cast<BufferResourceImpl*>(srcBinding.resource.Ptr()); + const BufferResource::Desc& bufferResourceDesc = bufferResource->getDesc(); + + if (bufferResourceDesc.bindFlags & Resource::BindFlag::UnorderedAccess) + { + // VkBufferView uav + + VkBufferViewCreateInfo info = { VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO }; + + info.format = VK_FORMAT_R32_SFLOAT; + // TODO: + // Not sure how to handle typeless? + if (bufferResourceDesc.elementSize == 0) + { + info.format = VK_FORMAT_R32_SFLOAT; // DXGI_FORMAT_R32_TYPELESS ? + } + + info.buffer = bufferResource->m_buffer.m_buffer; + info.offset = 0; + info.range = bufferResourceDesc.sizeInBytes; + + SLANG_VK_RETURN_NULL_ON_FAIL(m_api.vkCreateBufferView(m_device, &info, nullptr, &dstDetail.m_uav)); + } + + // TODO: Setup views. + // VkImageView srv + + + break; + } + case BindingType::Sampler: + { + VkSamplerCreateInfo samplerInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; + + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxAnisotropy = 1; + + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + + SLANG_VK_RETURN_NULL_ON_FAIL(m_api.vkCreateSampler(m_device, &samplerInfo, nullptr, &dstDetail.m_sampler)); + + break; + } + case BindingType::Texture: + { + if (!srcBinding.resource || !srcBinding.resource->isTexture()) + { + assert(!"Needs to have a texture resource set"); + return nullptr; + } + + TextureResourceImpl* textureResource = static_cast<TextureResourceImpl*>(srcBinding.resource.Ptr()); + const TextureResource::Desc& texDesc = textureResource->getDesc(); + + VkImageViewType imageViewType = _calcImageViewType(textureResource->getType(), texDesc); + if (imageViewType == VK_IMAGE_VIEW_TYPE_MAX_ENUM) + { + assert(!"Invalid view type"); + return nullptr; + } + const VkFormat format = VulkanUtil::getVkFormat(texDesc.format); + if (format == VK_FORMAT_UNDEFINED) + { + assert(!"Unhandled image format"); + return nullptr; + } + + // Create the image view + + VkImageViewCreateInfo viewInfo = {}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = textureResource->m_image; + viewInfo.viewType = imageViewType; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + + SLANG_VK_RETURN_NULL_ON_FAIL(m_api.vkCreateImageView(m_device, &viewInfo, nullptr, &dstDetail.m_srv)); + + break; + } + case BindingType::CombinedTextureSampler: + { + assert(!"not implemented"); + return nullptr; + } + } + } + + return bindingState.detach();; +} +#endif + +static VkDescriptorType translateDescriptorType(DescriptorSlotType type) +{ + switch(type) + { + default: + return VK_DESCRIPTOR_TYPE_MAX_ENUM; + +#define CASE(SRC, DST) \ + case DescriptorSlotType::SRC: return VK_DESCRIPTOR_TYPE_##DST + + CASE(Sampler, SAMPLER); + CASE(CombinedImageSampler, COMBINED_IMAGE_SAMPLER); + CASE(SampledImage, SAMPLED_IMAGE); + CASE(StorageImage, STORAGE_IMAGE); + CASE(UniformTexelBuffer, UNIFORM_TEXEL_BUFFER); + CASE(StorageTexelBuffer, STORAGE_TEXEL_BUFFER); + CASE(UniformBuffer, UNIFORM_BUFFER); + CASE(StorageBuffer, STORAGE_BUFFER); + CASE(DynamicUniformBuffer, UNIFORM_BUFFER_DYNAMIC); + CASE(DynamicStorageBuffer, STORAGE_BUFFER_DYNAMIC); + CASE(InputAttachment, INPUT_ATTACHMENT); + +#undef CASE + } +} + +Result VKRenderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc& desc, DescriptorSetLayout** outLayout) +{ + RefPtr<DescriptorSetLayoutImpl> descriptorSetLayoutImpl = new DescriptorSetLayoutImpl(m_api); + + Slang::List<VkDescriptorSetLayoutBinding> dstBindings; + + uint32_t descriptorCountForTypes[VK_DESCRIPTOR_TYPE_RANGE_SIZE] = { 0, }; + + UInt rangeCount = desc.slotRangeCount; + for(UInt rr = 0; rr < rangeCount; ++rr) + { + auto& srcRange = desc.slotRanges[rr]; + + VkDescriptorType dstDescriptorType = translateDescriptorType(srcRange.type); + + VkDescriptorSetLayoutBinding dstBinding; + dstBinding.binding = uint32_t(rr); + dstBinding.descriptorType = dstDescriptorType; + dstBinding.descriptorCount = uint32_t(srcRange.count); + dstBinding.stageFlags = VK_SHADER_STAGE_ALL; + dstBinding.pImmutableSamplers = nullptr; + + descriptorCountForTypes[dstDescriptorType] += uint32_t(srcRange.count); + + dstBindings.add(dstBinding); + + DescriptorSetLayoutImpl::RangeInfo rangeInfo; + rangeInfo.descriptorType = dstDescriptorType; + descriptorSetLayoutImpl->m_ranges.add(rangeInfo); + } + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; + descriptorSetLayoutInfo.bindingCount = uint32_t(dstBindings.getCount()); + descriptorSetLayoutInfo.pBindings = dstBindings.getBuffer(); + + VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; + SLANG_VK_CHECK(m_api.vkCreateDescriptorSetLayout(m_device, &descriptorSetLayoutInfo, nullptr, &descriptorSetLayout)); + + // Create a pool while we are at it, to allocate descriptor sets of this type. + + VkDescriptorPoolSize poolSizes[VK_DESCRIPTOR_TYPE_RANGE_SIZE]; + uint32_t poolSizeCount = 0; + for (int ii = 0; ii < SLANG_COUNT_OF(descriptorCountForTypes); ++ii) + { + auto descriptorCount = descriptorCountForTypes[ii]; + if (descriptorCount > 0) + { + poolSizes[poolSizeCount].type = VkDescriptorType(ii); + poolSizes[poolSizeCount].descriptorCount = descriptorCount; + poolSizeCount++; + } + } + + VkDescriptorPoolCreateInfo descriptorPoolInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; + descriptorPoolInfo.maxSets = 128; // TODO: actually pick a size. + descriptorPoolInfo.poolSizeCount = poolSizeCount; + descriptorPoolInfo.pPoolSizes = &poolSizes[0]; + + VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + SLANG_VK_CHECK(m_api.vkCreateDescriptorPool(m_device, &descriptorPoolInfo, nullptr, &descriptorPool)); + + descriptorSetLayoutImpl->m_descriptorSetLayout = descriptorSetLayout; + descriptorSetLayoutImpl->m_descriptorPool = descriptorPool; + + *outLayout = descriptorSetLayoutImpl.detach(); + return SLANG_OK; +} + +Result VKRenderer::createPipelineLayout(const PipelineLayout::Desc& desc, PipelineLayout** outLayout) +{ + UInt descriptorSetCount = desc.descriptorSetCount; + + VkDescriptorSetLayout descriptorSetLayouts[kMaxDescriptorSets]; + for(UInt ii = 0; ii < descriptorSetCount; ++ii) + { + descriptorSetLayouts[ii] = ((DescriptorSetLayoutImpl*) desc.descriptorSets[ii].layout)->m_descriptorSetLayout; + } + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; + pipelineLayoutInfo.setLayoutCount = uint32_t(desc.descriptorSetCount); + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayouts[0]; + + VkPipelineLayout pipelineLayout; + SLANG_VK_CHECK(m_api.vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &pipelineLayout)); + + RefPtr<PipelineLayoutImpl> pipelineLayoutImpl = new PipelineLayoutImpl(m_api); + pipelineLayoutImpl->m_pipelineLayout = pipelineLayout; + pipelineLayoutImpl->m_descriptorSetCount = descriptorSetCount; + + *outLayout = pipelineLayoutImpl.detach(); + return SLANG_OK; +} + +Result VKRenderer::createDescriptorSet(DescriptorSetLayout* layout, DescriptorSet** outDescriptorSet) +{ + auto layoutImpl = (DescriptorSetLayoutImpl*)layout; + + VkDescriptorSetAllocateInfo descriptorSetAllocInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; + descriptorSetAllocInfo.descriptorPool = layoutImpl->m_descriptorPool; + descriptorSetAllocInfo.descriptorSetCount = 1; + descriptorSetAllocInfo.pSetLayouts = &layoutImpl->m_descriptorSetLayout; + + VkDescriptorSet descriptorSet; + SLANG_VK_CHECK(m_api.vkAllocateDescriptorSets(m_device, &descriptorSetAllocInfo, &descriptorSet)); + + RefPtr<DescriptorSetImpl> descriptorSetImpl = new DescriptorSetImpl(this); + descriptorSetImpl->m_layout = layoutImpl; + descriptorSetImpl->m_descriptorSet = descriptorSet; + *outDescriptorSet = descriptorSetImpl.detach(); + return SLANG_OK; +} + +/* static */VKRenderer::DescriptorSetImpl::Binding::Type VKRenderer::DescriptorSetImpl::_getBindingType(RefObject* ptr) +{ + typedef Binding::Type Type; + + if (ptr) + { + if (dynamic_cast<ResourceView*>(ptr)) + { + return Type::ResourceView; + } + else if (dynamic_cast<BufferResource*>(ptr)) + { + return Type::BufferResource; + } + else if (dynamic_cast<SamplerState*>(ptr)) + { + return Type::SamplerState; + } + } + return Type::Unknown; +} + +void VKRenderer::DescriptorSetImpl::_setBinding(Binding::Type type, UInt range, UInt index, RefObject* ptr) +{ + SLANG_ASSERT(ptr == nullptr || _getBindingType(ptr) == type); + + const Index numBindings = m_bindings.getCount(); + for (Index i = 0; i < numBindings; ++i) + { + Binding& binding = m_bindings[i]; + + if (binding.type == type && binding.range == uint32_t(range) && binding.index == uint32_t(index)) + { + if (ptr) + { + binding.obj = ptr; + } + else + { + m_bindings.removeAt(i); + } + + return; + } + } + + // If an entry is not found, and we have a pointer, create an entry + if (ptr) + { + Binding binding; + binding.type = type; + binding.range = uint32_t(range); + binding.index = uint32_t(index); + binding.obj = ptr; + + m_bindings.add(binding); + } +} + +void VKRenderer::DescriptorSetImpl::setConstantBuffer(UInt range, UInt index, BufferResource* buffer) +{ + auto bufferImpl = (BufferResourceImpl*)buffer; + + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = bufferImpl->m_buffer.m_buffer; + bufferInfo.offset = 0; + bufferInfo.range = bufferImpl->getDesc().sizeInBytes; + + VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; + writeInfo.dstSet = m_descriptorSet; + writeInfo.dstBinding = uint32_t(range); + writeInfo.dstArrayElement = uint32_t(index); + writeInfo.descriptorCount = 1; + writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType; + writeInfo.pBufferInfo = &bufferInfo; + + m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr); + + _setBinding(Binding::Type::BufferResource, range, index, buffer); +} + +void VKRenderer::DescriptorSetImpl::setResource(UInt range, UInt index, ResourceView* view) +{ + auto viewImpl = (ResourceViewImpl*)view; + switch (viewImpl->m_type) + { + case ResourceViewImpl::ViewType::Texture: + { + auto textureViewImpl = (TextureResourceViewImpl*)viewImpl; + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageView = textureViewImpl->m_view; + imageInfo.imageLayout = textureViewImpl->m_layout; + // imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; + writeInfo.dstSet = m_descriptorSet; + writeInfo.dstBinding = uint32_t(range); + writeInfo.dstArrayElement = uint32_t(index); + writeInfo.descriptorCount = 1; + writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType; + writeInfo.pImageInfo = &imageInfo; + + m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr); + } + break; + + case ResourceViewImpl::ViewType::TexelBuffer: + { + auto bufferViewImpl = (TexelBufferResourceViewImpl*)viewImpl; + + VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; + writeInfo.dstSet = m_descriptorSet; + writeInfo.dstBinding = uint32_t(range); + writeInfo.dstArrayElement = uint32_t(index); + writeInfo.descriptorCount = 1; + writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType; + writeInfo.pTexelBufferView = &bufferViewImpl->m_view; + + m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr); + } + break; + + case ResourceViewImpl::ViewType::PlainBuffer: + { + auto bufferViewImpl = (PlainBufferResourceViewImpl*) viewImpl; + + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = bufferViewImpl->m_buffer->m_buffer.m_buffer; + bufferInfo.offset = bufferViewImpl->offset; + bufferInfo.range = bufferViewImpl->size; + + VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; + writeInfo.dstSet = m_descriptorSet; + writeInfo.dstBinding = uint32_t(range); + writeInfo.dstArrayElement = uint32_t(index); + writeInfo.descriptorCount = 1; + writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType; + writeInfo.pBufferInfo = &bufferInfo; + + m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr); + } + break; + + } + + _setBinding(Binding::Type::ResourceView, range, index, view); +} + +void VKRenderer::DescriptorSetImpl::setSampler(UInt range, UInt index, SamplerState* sampler) +{ + + _setBinding(Binding::Type::SamplerState, range, index, sampler); +} + +void VKRenderer::DescriptorSetImpl::setCombinedTextureSampler( + UInt range, + UInt index, + ResourceView* textureView, + SamplerState* sampler) +{ + + _setBinding(Binding::Type::SamplerState, range, index, sampler); + _setBinding(Binding::Type::ResourceView, range, index, textureView); +} + +void VKRenderer::setDescriptorSet(PipelineType pipelineType, PipelineLayout* layout, UInt index, DescriptorSet* descriptorSet) +{ + // Ideally this should eventually be as simple as: + // + // m_api.vkCmdBindDescriptorSets( + // commandBuffer, + // translatePipelineBindPoint(pipelineType), + // layout->m_pipelineLayout, + // index, + // 1, + // ((DescriptorSetImpl*) descriptorSet)->m_descriptorSet, + // 0, + // nullptr); + // + // For now we are lazily flushing state right before drawing, so + // we will hang onto the parameters that were passed in and then + // use them later. + // + + auto descriptorSetImpl = (DescriptorSetImpl*)descriptorSet; + m_currentDescriptorSetImpls[index] = descriptorSetImpl; + m_currentDescriptorSets[index] = descriptorSetImpl->m_descriptorSet; +} + +Result VKRenderer::createProgram(const ShaderProgram::Desc& desc, ShaderProgram** outProgram) +{ + RefPtr<ShaderProgramImpl> impl = new ShaderProgramImpl(desc.pipelineType); + if( desc.pipelineType == PipelineType::Compute) + { + auto computeKernel = desc.findKernel(StageType::Compute); + impl->m_compute = compileEntryPoint(*computeKernel, VK_SHADER_STAGE_COMPUTE_BIT, impl->m_buffers[0]); + } + else + { + auto vertexKernel = desc.findKernel(StageType::Vertex); + auto fragmentKernel = desc.findKernel(StageType::Fragment); + + impl->m_vertex = compileEntryPoint(*vertexKernel, VK_SHADER_STAGE_VERTEX_BIT, impl->m_buffers[0]); + impl->m_fragment = compileEntryPoint(*fragmentKernel, VK_SHADER_STAGE_FRAGMENT_BIT, impl->m_buffers[1]); + } + *outProgram = impl.detach(); + return SLANG_OK; +} + +Result VKRenderer::createGraphicsPipelineState(const GraphicsPipelineStateDesc& desc, PipelineState** outState) +{ + VkPipelineCache pipelineCache = VK_NULL_HANDLE; + + auto programImpl = (ShaderProgramImpl*) desc.program; + auto pipelineLayoutImpl = (PipelineLayoutImpl*) desc.pipelineLayout; + auto inputLayoutImpl = (InputLayoutImpl*) desc.inputLayout; + + const int width = int(desc.framebufferWidth); + const int height = int(desc.framebufferHeight); + + // Shader Stages + // + // Currently only handles vertex/fragment. + + static const uint32_t kMaxShaderStages = 2; + VkPipelineShaderStageCreateInfo shaderStages[kMaxShaderStages]; + + uint32_t shaderStageCount = 0; + shaderStages[shaderStageCount++] = programImpl->m_vertex; + shaderStages[shaderStageCount++] = programImpl->m_fragment; + + // VertexBuffer/s + // Currently only handles one + + VkPipelineVertexInputStateCreateInfo vertexInputInfo = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkVertexInputBindingDescription vertexInputBindingDescription; + + if (inputLayoutImpl) + { + vertexInputBindingDescription.binding = 0; + vertexInputBindingDescription.stride = inputLayoutImpl->m_vertexSize; + vertexInputBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + const auto& srcAttributeDescs = inputLayoutImpl->m_vertexDescs; + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &vertexInputBindingDescription; + + vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(srcAttributeDescs.getCount()); + vertexInputInfo.pVertexAttributeDescriptions = srcAttributeDescs.getBuffer(); + } + + VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)width; + viewport.height = (float)height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = { 0, 0 }; + scissor.extent = { uint32_t(width), uint32_t(height) }; + + VkPipelineViewportStateCreateInfo viewportState = {}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending = {}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + VkGraphicsPipelineCreateInfo pipelineInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.layout = pipelineLayoutImpl->m_pipelineLayout; + pipelineInfo.renderPass = m_renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + VkPipeline pipeline = VK_NULL_HANDLE; + SLANG_VK_CHECK(m_api.vkCreateGraphicsPipelines(m_device, pipelineCache, 1, &pipelineInfo, nullptr, &pipeline)); + + RefPtr<PipelineStateImpl> pipelineStateImpl = new PipelineStateImpl(m_api); + pipelineStateImpl->m_pipeline = pipeline; + pipelineStateImpl->m_pipelineLayout = pipelineLayoutImpl; + pipelineStateImpl->m_shaderProgram = programImpl; + *outState = pipelineStateImpl.detach(); + return SLANG_OK; +} + +Result VKRenderer::createComputePipelineState(const ComputePipelineStateDesc& desc, PipelineState** outState) +{ + VkPipelineCache pipelineCache = VK_NULL_HANDLE; + + auto programImpl = (ShaderProgramImpl*) desc.program; + auto pipelineLayoutImpl = (PipelineLayoutImpl*) desc.pipelineLayout; + + VkComputePipelineCreateInfo computePipelineInfo = { VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO }; + computePipelineInfo.stage = programImpl->m_compute; + computePipelineInfo.layout = pipelineLayoutImpl->m_pipelineLayout; + + VkPipeline pipeline = VK_NULL_HANDLE; + SLANG_VK_CHECK(m_api.vkCreateComputePipelines(m_device, pipelineCache, 1, &computePipelineInfo, nullptr, &pipeline)); + + RefPtr<PipelineStateImpl> pipelineStateImpl = new PipelineStateImpl(m_api); + pipelineStateImpl->m_pipeline = pipeline; + pipelineStateImpl->m_pipelineLayout = pipelineLayoutImpl; + pipelineStateImpl->m_shaderProgram = programImpl; + *outState = pipelineStateImpl.detach(); + return SLANG_OK; +} + + +#if 0 + else if (m_currentProgram->m_pipelineType == PipelineType::Graphics) + { + // Create the graphics pipeline + + const int width = m_swapChain.getWidth(); + const int height = m_swapChain.getHeight(); + + + + + + // + + + } + else + { + assert(!"Unhandled program type"); + return SLANG_FAIL; + } + + pipelineOut = pipeline; + return SLANG_OK; + + +#endif + +} // renderer_test diff --git a/tools/gfx/vulkan/render-vk.h b/tools/gfx/vulkan/render-vk.h new file mode 100644 index 000000000..14a8e403a --- /dev/null +++ b/tools/gfx/vulkan/render-vk.h @@ -0,0 +1,10 @@ +// render-vk.h +#pragma once + +namespace gfx { + +class Renderer; + +Renderer* createVKRenderer(); + +} // gfx diff --git a/tools/gfx/vulkan/vk-api.cpp b/tools/gfx/vulkan/vk-api.cpp new file mode 100644 index 000000000..50f80aa26 --- /dev/null +++ b/tools/gfx/vulkan/vk-api.cpp @@ -0,0 +1,142 @@ +// vk-api.cpp +#include "vk-api.h" + +#include "../../source/core/slang-list.h" + +namespace gfx { +using namespace Slang; + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! VulkanApi !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +#define VK_API_CHECK_FUNCTION(x) && (x != nullptr) +#define VK_API_CHECK_FUNCTIONS(FUNCTION_LIST) true FUNCTION_LIST(VK_API_CHECK_FUNCTION) + +bool VulkanApi::areDefined(ProcType type) const +{ + switch (type) + { + case ProcType::Global: return VK_API_CHECK_FUNCTIONS(VK_API_ALL_GLOBAL_PROCS); + case ProcType::Instance: return VK_API_CHECK_FUNCTIONS(VK_API_ALL_INSTANCE_PROCS); + case ProcType::Device: return VK_API_CHECK_FUNCTIONS(VK_API_ALL_DEVICE_PROCS); + default: + { + assert(!"Unhandled type"); + return false; + } + } +} + +Slang::Result VulkanApi::initGlobalProcs(const VulkanModule& module) +{ +#define VK_API_GET_GLOBAL_PROC(x) x = (PFN_##x)module.getFunction(#x); + + // Initialize all the global functions + VK_API_ALL_GLOBAL_PROCS(VK_API_GET_GLOBAL_PROC) + + if (!areDefined(ProcType::Global)) + { + return SLANG_FAIL; + } + m_module = &module; + return SLANG_OK; +} + +Slang::Result VulkanApi::initInstanceProcs(VkInstance instance) +{ + assert(instance && vkGetInstanceProcAddr != nullptr); + +#define VK_API_GET_INSTANCE_PROC(x) x = (PFN_##x)vkGetInstanceProcAddr(instance, #x); + + VK_API_ALL_INSTANCE_PROCS(VK_API_GET_INSTANCE_PROC) + + // Get optional + VK_API_INSTANCE_PROCS_OPT(VK_API_GET_INSTANCE_PROC) + + if (!areDefined(ProcType::Instance)) + { + return SLANG_FAIL; + } + + + m_instance = instance; + return SLANG_OK; +} + +Slang::Result VulkanApi::initPhysicalDevice(VkPhysicalDevice physicalDevice) +{ + assert(m_physicalDevice == VK_NULL_HANDLE); + m_physicalDevice = physicalDevice; + + vkGetPhysicalDeviceProperties(m_physicalDevice, &m_deviceProperties); + vkGetPhysicalDeviceFeatures(m_physicalDevice, &m_deviceFeatures); + vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &m_deviceMemoryProperties); + + return SLANG_OK; +} + +Slang::Result VulkanApi::initDeviceProcs(VkDevice device) +{ + assert(m_instance && device && vkGetDeviceProcAddr != nullptr); + +#define VK_API_GET_DEVICE_PROC(x) x = (PFN_##x)vkGetDeviceProcAddr(device, #x); + + VK_API_ALL_DEVICE_PROCS(VK_API_GET_DEVICE_PROC) + + if (!areDefined(ProcType::Device)) + { + return SLANG_FAIL; + } + + m_device = device; + return SLANG_OK; +} + +int VulkanApi::findMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) const +{ + assert(typeBits); + + const int numMemoryTypes = int(m_deviceMemoryProperties.memoryTypeCount); + + // bit holds current test bit against typeBits. Ie bit == 1 << typeBits + + uint32_t bit = 1; + for (int i = 0; i < numMemoryTypes; ++i, bit += bit) + { + auto const& memoryType = m_deviceMemoryProperties.memoryTypes[i]; + if ((typeBits & bit) && (memoryType.propertyFlags & properties) == properties) + { + return i; + } + } + + //assert(!"failed to find a usable memory type"); + return -1; +} + +int VulkanApi::findQueue(VkQueueFlags reqFlags) const +{ + assert(m_physicalDevice != VK_NULL_HANDLE); + + uint32_t numQueueFamilies = 0; + vkGetPhysicalDeviceQueueFamilyProperties(m_physicalDevice, &numQueueFamilies, nullptr); + + Slang::List<VkQueueFamilyProperties> queueFamilies; + queueFamilies.setCount(numQueueFamilies); + vkGetPhysicalDeviceQueueFamilyProperties(m_physicalDevice, &numQueueFamilies, queueFamilies.getBuffer()); + + // Find a queue that can service our needs + //VkQueueFlags reqQueueFlags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT; + + int queueFamilyIndex = -1; + for (int i = 0; i < int(numQueueFamilies); ++i) + { + if ((queueFamilies[i].queueFlags & reqFlags) == reqFlags) + { + return i; + } + } + + return -1; +} + +} // renderer_test diff --git a/tools/gfx/vulkan/vk-api.h b/tools/gfx/vulkan/vk-api.h new file mode 100644 index 000000000..001f44d19 --- /dev/null +++ b/tools/gfx/vulkan/vk-api.h @@ -0,0 +1,203 @@ +// vk-api.h +#pragma once + +#include "vk-module.h" + +namespace gfx { + +#define VK_API_GLOBAL_PROCS(x) \ + x(vkGetInstanceProcAddr) \ + x(vkCreateInstance) \ + /* */ + +#define VK_API_INSTANCE_PROCS_OPT(x) \ + x(vkGetPhysicalDeviceFeatures2) \ + x(vkGetPhysicalDeviceProperties2) \ + /* */ + +#define VK_API_INSTANCE_PROCS(x) \ + x(vkCreateDevice) \ + x(vkCreateDebugReportCallbackEXT) \ + x(vkDestroyDebugReportCallbackEXT) \ + x(vkDebugReportMessageEXT) \ + x(vkEnumeratePhysicalDevices) \ + x(vkGetPhysicalDeviceProperties) \ + x(vkGetPhysicalDeviceFeatures) \ + x(vkGetPhysicalDeviceMemoryProperties) \ + x(vkGetPhysicalDeviceQueueFamilyProperties) \ + x(vkGetPhysicalDeviceFormatProperties) \ + x(vkGetDeviceProcAddr) \ + /* */ + +#define VK_API_DEVICE_PROCS(x) \ + x(vkCreateDescriptorPool) \ + x(vkDestroyDescriptorPool) \ + x(vkGetDeviceQueue) \ + x(vkQueueSubmit) \ + x(vkQueueWaitIdle) \ + x(vkCreateBuffer) \ + x(vkAllocateMemory) \ + x(vkMapMemory) \ + x(vkUnmapMemory) \ + x(vkCmdCopyBuffer) \ + x(vkDestroyBuffer) \ + x(vkFreeMemory) \ + x(vkCreateDescriptorSetLayout) \ + x(vkDestroyDescriptorSetLayout) \ + x(vkAllocateDescriptorSets) \ + x(vkUpdateDescriptorSets) \ + x(vkCreatePipelineLayout) \ + x(vkDestroyPipelineLayout) \ + x(vkCreateComputePipelines) \ + x(vkCreateGraphicsPipelines) \ + x(vkDestroyPipeline) \ + x(vkCreateShaderModule) \ + x(vkDestroyShaderModule) \ + x(vkCreateFramebuffer) \ + x(vkDestroyFramebuffer) \ + x(vkCreateImage) \ + x(vkDestroyImage) \ + x(vkCreateImageView) \ + x(vkDestroyImageView) \ + x(vkCreateRenderPass) \ + x(vkDestroyRenderPass) \ + x(vkCreateCommandPool) \ + x(vkDestroyCommandPool) \ + x(vkCreateSampler) \ + x(vkDestroySampler) \ + x(vkCreateBufferView) \ + x(vkDestroyBufferView) \ + \ + x(vkGetBufferMemoryRequirements) \ + x(vkGetImageMemoryRequirements) \ + \ + x(vkCmdBindPipeline) \ + x(vkCmdBindDescriptorSets) \ + x(vkCmdDispatch) \ + x(vkCmdDraw) \ + x(vkCmdSetScissor) \ + x(vkCmdSetViewport) \ + x(vkCmdBindVertexBuffers) \ + x(vkCmdBindIndexBuffer) \ + x(vkCmdBeginRenderPass) \ + x(vkCmdEndRenderPass) \ + x(vkCmdPipelineBarrier) \ + x(vkCmdCopyBufferToImage)\ + \ + x(vkCreateFence) \ + x(vkDestroyFence) \ + x(vkResetFences) \ + x(vkGetFenceStatus) \ + x(vkWaitForFences) \ + \ + x(vkCreateSemaphore) \ + x(vkDestroySemaphore) \ + \ + x(vkCreateEvent) \ + x(vkDestroyEvent) \ + x(vkGetEventStatus) \ + x(vkSetEvent) \ + x(vkResetEvent) \ + \ + x(vkFreeCommandBuffers) \ + x(vkAllocateCommandBuffers) \ + x(vkBeginCommandBuffer) \ + x(vkEndCommandBuffer) \ + x(vkResetCommandBuffer) \ + \ + x(vkBindImageMemory) \ + x(vkBindBufferMemory) \ + /* */ + +#if SLANG_WINDOWS_FAMILY +# define VK_API_INSTANCE_PLATFORM_KHR_PROCS(x) \ + x(vkCreateWin32SurfaceKHR) \ + /* */ +#else +# define VK_API_INSTANCE_PLATFORM_KHR_PROCS(x) \ + x(vkCreateXlibSurfaceKHR) \ + /* */ +#endif + +#define VK_API_INSTANCE_KHR_PROCS(x) \ + VK_API_INSTANCE_PLATFORM_KHR_PROCS(x) \ + x(vkGetPhysicalDeviceSurfaceSupportKHR) \ + x(vkGetPhysicalDeviceSurfaceFormatsKHR) \ + x(vkGetPhysicalDeviceSurfacePresentModesKHR) \ + x(vkGetPhysicalDeviceSurfaceCapabilitiesKHR) \ + x(vkDestroySurfaceKHR) \ + /* */ + +#define VK_API_DEVICE_KHR_PROCS(x) \ + x(vkQueuePresentKHR) \ + x(vkCreateSwapchainKHR) \ + x(vkGetSwapchainImagesKHR) \ + x(vkDestroySwapchainKHR) \ + x(vkAcquireNextImageKHR) \ + /* */ + +#define VK_API_ALL_GLOBAL_PROCS(x) \ + VK_API_GLOBAL_PROCS(x) + +#define VK_API_ALL_INSTANCE_PROCS(x) \ + VK_API_INSTANCE_PROCS(x) \ + VK_API_INSTANCE_KHR_PROCS(x) + +#define VK_API_ALL_DEVICE_PROCS(x) \ + VK_API_DEVICE_PROCS(x) \ + VK_API_DEVICE_KHR_PROCS(x) + +#define VK_API_ALL_PROCS(x) \ + VK_API_ALL_GLOBAL_PROCS(x) \ + VK_API_ALL_INSTANCE_PROCS(x) \ + VK_API_ALL_DEVICE_PROCS(x) \ + \ + VK_API_INSTANCE_PROCS_OPT(x) \ + /* */ + +#define VK_API_DECLARE_PROC(NAME) PFN_##NAME NAME = nullptr; + +struct VulkanApi +{ + VK_API_ALL_PROCS(VK_API_DECLARE_PROC) + + enum class ProcType + { + Global, + Instance, + Device, + }; + + /// Returns true if all the functions in the class are defined + bool areDefined(ProcType type) const; + + /// Sets up global parameters + Slang::Result initGlobalProcs(const VulkanModule& module); + /// Initialize the instance functions + Slang::Result initInstanceProcs(VkInstance instance); + + /// Called before initDevice + Slang::Result initPhysicalDevice(VkPhysicalDevice physicalDevice); + + /// Initialize the device functions + Slang::Result initDeviceProcs(VkDevice device); + + /// Type bits control which indices are tested against bit 0 for testing at index 0 + /// properties - a memory type must have all the bits set as passed in + /// Returns -1 if couldn't find an appropriate memory type index + int findMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) const; + + /// Given queue required flags, finds a queue + int findQueue(VkQueueFlags reqFlags) const; + + const VulkanModule* m_module = nullptr; ///< Module this was all loaded from + VkInstance m_instance = VK_NULL_HANDLE; + VkDevice m_device = VK_NULL_HANDLE; + VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; + + VkPhysicalDeviceProperties m_deviceProperties; + VkPhysicalDeviceFeatures m_deviceFeatures; + VkPhysicalDeviceMemoryProperties m_deviceMemoryProperties; +}; + +} // renderer_test diff --git a/tools/gfx/vulkan/vk-device-queue.cpp b/tools/gfx/vulkan/vk-device-queue.cpp new file mode 100644 index 000000000..10a3d0e3b --- /dev/null +++ b/tools/gfx/vulkan/vk-device-queue.cpp @@ -0,0 +1,199 @@ +// vk-device-queue.cpp +#include "vk-device-queue.h" + +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +namespace gfx { +using namespace Slang; + +VulkanDeviceQueue::~VulkanDeviceQueue() +{ + for (int i = 0; i < int(EventType::CountOf); ++i) + { + m_api->vkDestroySemaphore(m_api->m_device, m_semaphores[i], nullptr); + } + + for (int i = 0; i < m_numCommandBuffers; i++) + { + m_api->vkFreeCommandBuffers(m_api->m_device, m_commandPool, 1, &m_commandBuffers[i]); + m_api->vkDestroyFence(m_api->m_device, m_fences[i].fence, nullptr); + } + m_api->vkDestroyCommandPool(m_api->m_device, m_commandPool, nullptr); +} + +SlangResult VulkanDeviceQueue::init(const VulkanApi& api, VkQueue queue, int queueIndex) +{ + assert(m_api == nullptr); + m_api = &api; + + for (int i = 0; i < int(EventType::CountOf); ++i) + { + m_semaphores[i] = VK_NULL_HANDLE; + m_currentSemaphores[i] = VK_NULL_HANDLE; + } + + m_numCommandBuffers = kMaxCommandBuffers; + m_queueIndex = queueIndex; + + m_queue = queue; + + VkCommandPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + + poolCreateInfo.queueFamilyIndex = queueIndex; + + api.vkCreateCommandPool(api.m_device, &poolCreateInfo, nullptr, &m_commandPool); + + VkCommandBufferAllocateInfo commandInfo = {}; + commandInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + commandInfo.commandPool = m_commandPool; + commandInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + commandInfo.commandBufferCount = 1; + + VkFenceCreateInfo fenceCreateInfo = {}; + fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceCreateInfo.flags = 0; // VK_FENCE_CREATE_SIGNALED_BIT; + + for (int i = 0; i < m_numCommandBuffers; i++) + { + Fence& fence = m_fences[i]; + + api.vkAllocateCommandBuffers(api.m_device, &commandInfo, &m_commandBuffers[i]); + + api.vkCreateFence(api.m_device, &fenceCreateInfo, nullptr, &fence.fence); + fence.active = false; + fence.value = 0; + } + + VkSemaphoreCreateInfo semaphoreCreateInfo = {}; + semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + for (int i = 0; i < int(EventType::CountOf); ++i) + { + api.vkCreateSemaphore(api.m_device, &semaphoreCreateInfo, nullptr, &m_semaphores[i]); + } + + // Second step of flush to prime command buffer + flushStepB(); + + return SLANG_OK; +} + +void VulkanDeviceQueue::flushStepA() +{ + m_api->vkEndCommandBuffer(m_commandBuffer); + + VkPipelineStageFlags stageFlags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + // Wait semaphores + if (isCurrent(EventType::BeginFrame)) + { + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = &m_currentSemaphores[int(EventType::BeginFrame)]; + } + + submitInfo.pWaitDstStageMask = &stageFlags; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &m_commandBuffer; + + // Signal semaphores + if (isCurrent(EventType::EndFrame)) + { + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &m_currentSemaphores[int(EventType::EndFrame)]; + } + + Fence& fence = m_fences[m_commandBufferIndex]; + + m_api->vkQueueSubmit(m_queue, 1, &submitInfo, fence.fence); + + // mark signaled fence value + fence.value = m_nextFenceValue; + fence.active = true; + + // increment fence value + m_nextFenceValue++; + + // No longer waiting on this semaphore + makeCompleted(EventType::BeginFrame); +} + +void VulkanDeviceQueue::_updateFenceAtIndex( int fenceIndex, bool blocking) +{ + Fence& fence = m_fences[fenceIndex]; + + if (fence.active) + { + uint64_t timeout = blocking ? ~uint64_t(0) : 0; + + if (VK_SUCCESS == m_api->vkWaitForFences(m_api->m_device, 1, &fence.fence, VK_TRUE, timeout)) + { + m_api->vkResetFences(m_api->m_device, 1, &fence.fence); + + fence.active = false; + + if (fence.value > m_lastFenceCompleted) + { + m_lastFenceCompleted = fence.value; + } + } + } +} + +void VulkanDeviceQueue::flushStepB() +{ + m_commandBufferIndex = (m_commandBufferIndex + 1) % m_numCommandBuffers; + m_commandBuffer = m_commandBuffers[m_commandBufferIndex]; + + // non-blocking update of fence values + for (int i = 0; i < m_numCommandBuffers; ++i) + { + _updateFenceAtIndex(i, false); + } + + // blocking update of fence values + _updateFenceAtIndex(m_commandBufferIndex, true); + + m_api->vkResetCommandBuffer(m_commandBuffer, 0); + + //m_api.vkResetCommandPool(m_api->m_device, m_commandPool, 0); + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + m_api->vkBeginCommandBuffer(m_commandBuffer, &beginInfo); +} + +void VulkanDeviceQueue::flush() +{ + flushStepA(); + flushStepB(); +} + +void VulkanDeviceQueue::flushAndWait() +{ + flush(); + waitForIdle(); +} + +VkSemaphore VulkanDeviceQueue::makeCurrent(EventType eventType) +{ + assert(!isCurrent(eventType)); + VkSemaphore semaphore = m_semaphores[int(eventType)]; + m_currentSemaphores[int(eventType)] = semaphore; + return semaphore; +} + +void VulkanDeviceQueue::makeCompleted(EventType eventType) +{ + m_currentSemaphores[int(eventType)] = VK_NULL_HANDLE; +} + +} // renderer_test diff --git a/tools/gfx/vulkan/vk-device-queue.h b/tools/gfx/vulkan/vk-device-queue.h new file mode 100644 index 000000000..d57483ec0 --- /dev/null +++ b/tools/gfx/vulkan/vk-device-queue.h @@ -0,0 +1,94 @@ +// vk-swap-chain.h +#pragma once + +#include "vk-api.h" + +namespace gfx { + +struct VulkanDeviceQueue +{ + enum + { + kMaxCommandBuffers = 8, + }; + + enum class EventType + { + BeginFrame, + EndFrame, + CountOf, + }; + + /// Initialize - must be called before anything else can be done + SlangResult init(const VulkanApi& api, VkQueue queue, int queueIndex); + + /// Flushes the current command list, and steps to next (internally this is equivalent to a stepA followed by stepB) + void flush(); + /// Performs a full flush, and then waits for idle. + void flushAndWait(); + + /// Blocks until all work submitted to GPU has completed + void waitForIdle() { m_api->vkQueueWaitIdle(m_queue); } + + /// Get the graphics queue index (as set on init) + int getQueueIndex() const { return m_queueIndex; } + + /// Make the specified event 'current' - meaning it's semaphore must be waited on + VkSemaphore makeCurrent(EventType eventType); + /// Makes the event no longer required to be waited on + void makeCompleted(EventType eventType); + /// Returns true if the event is already current + SLANG_FORCE_INLINE bool isCurrent(EventType eventType) const { return m_currentSemaphores[int(eventType)] != VK_NULL_HANDLE; } + + /// Get the command buffer + VkCommandBuffer getCommandBuffer() const { return m_commandBuffer; } + + /// Get the queue + VkQueue getQueue() const { return m_queue; } + + /// Get the API + const VulkanApi* getApi() const { return m_api; } + + /// Flushes the current command list + void flushStepA(); + /// Steps to next command buffer and opens. May block if command buffer is still in use + void flushStepB(); + + /// Dtor + ~VulkanDeviceQueue(); + + protected: + + struct Fence + { + VkFence fence; + bool active; + uint64_t value; + }; + + void _updateFenceAtIndex(int fenceIndex, bool blocking); + + VkQueue m_queue = VK_NULL_HANDLE; + + VkCommandPool m_commandPool = VK_NULL_HANDLE; + int m_numCommandBuffers = 0; + int m_commandBufferIndex = 0; + // There are the same amount of command buffers as fences + VkCommandBuffer m_commandBuffers[kMaxCommandBuffers] = { VK_NULL_HANDLE }; + + Fence m_fences[kMaxCommandBuffers] = { {VK_NULL_HANDLE, 0, 0u} }; + + VkCommandBuffer m_commandBuffer = VK_NULL_HANDLE; + + VkSemaphore m_semaphores[int(EventType::CountOf)]; + VkSemaphore m_currentSemaphores[int(EventType::CountOf)]; + + uint64_t m_lastFenceCompleted = 1; + uint64_t m_nextFenceValue = 2; + + int m_queueIndex = 0; + + const VulkanApi* m_api = nullptr; +}; + +} // renderer_test diff --git a/tools/gfx/vulkan/vk-module.cpp b/tools/gfx/vulkan/vk-module.cpp new file mode 100644 index 000000000..4e92a3d2c --- /dev/null +++ b/tools/gfx/vulkan/vk-module.cpp @@ -0,0 +1,76 @@ +// module.cpp +#include "vk-module.h" + +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +#if SLANG_WINDOWS_FAMILY +# include <windows.h> +#else +# include <dlfcn.h> +#endif + +namespace gfx { +using namespace Slang; + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! VulkanModule !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +Slang::Result VulkanModule::init() +{ + if (isInitialized()) + { + destroy(); + return SLANG_OK; + } + + const char* dynamicLibraryName = "Unknown"; + +#if SLANG_WINDOWS_FAMILY + dynamicLibraryName = "vulkan-1.dll"; + HMODULE module = ::LoadLibraryA(dynamicLibraryName); + m_module = (void*)module; +#else + dynamicLibraryName = "libvulkan.so.1"; + m_module = dlopen(dynamicLibraryName, RTLD_NOW); +#endif + + if (!m_module) + { + fprintf(stderr, "error: failed load '%s'\n", dynamicLibraryName); + return SLANG_FAIL; + } + + return SLANG_OK; +} + +PFN_vkVoidFunction VulkanModule::getFunction(const char* name) const +{ + assert(m_module); + if (!m_module) + { + return nullptr; + } +#if SLANG_WINDOWS_FAMILY + return (PFN_vkVoidFunction)::GetProcAddress((HMODULE)m_module, name); +#else + return (PFN_vkVoidFunction)dlsym(m_module, name); +#endif +} + +void VulkanModule::destroy() +{ + if (!isInitialized()) + { + return; + } + +#if SLANG_WINDOWS_FAMILY + ::FreeLibrary((HMODULE)m_module); +#else + dlclose(m_module); +#endif + m_module = nullptr; +} + +} // renderer_test diff --git a/tools/gfx/vulkan/vk-module.h b/tools/gfx/vulkan/vk-module.h new file mode 100644 index 000000000..4d18823ca --- /dev/null +++ b/tools/gfx/vulkan/vk-module.h @@ -0,0 +1,40 @@ +// vk-module.h +#pragma once + +#include "../../slang.h" + +#include "../../slang-com-helper.h" + +#if SLANG_WINDOWS_FAMILY +# define VK_USE_PLATFORM_WIN32_KHR 1 +#else +# define VK_USE_PLATFORM_XLIB_KHR 1 +#endif + +#define VK_NO_PROTOTYPES + +#include <vulkan/include/vulkan/vulkan.h> + +namespace gfx { + +struct VulkanModule +{ + /// true if has been initialized + SLANG_FORCE_INLINE bool isInitialized() const { return m_module != nullptr; } + + /// Get a function by name + PFN_vkVoidFunction getFunction(const char* name) const; + + /// Initialize + Slang::Result init(); + /// Destroy + void destroy(); + + /// Dtor + ~VulkanModule() { destroy(); } + + protected: + void* m_module = nullptr; +}; + +} // renderer_test diff --git a/tools/gfx/vulkan/vk-swap-chain.cpp b/tools/gfx/vulkan/vk-swap-chain.cpp new file mode 100644 index 000000000..5cf2e96ae --- /dev/null +++ b/tools/gfx/vulkan/vk-swap-chain.cpp @@ -0,0 +1,421 @@ +// vk-swap-chain.cpp +#include "vk-swap-chain.h" + +#include "vk-util.h" + +#include "../../source/core/slang-list.h" + +#include <stdlib.h> +#include <stdio.h> + +namespace gfx { +using namespace Slang; + +static Index _indexOfFormat(List<VkSurfaceFormatKHR>& formatsIn, VkFormat format) +{ + const Index numFormats = formatsIn.getCount(); + const VkSurfaceFormatKHR* formats = formatsIn.getBuffer(); + + for (Index i = 0; i < numFormats; ++i) + { + if (formats[i].format == format) + { + return i; + } + } + return -1; +} + +SlangResult VulkanSwapChain::init(VulkanDeviceQueue* deviceQueue, const Desc& descIn, const PlatformDesc* platformDescIn) +{ + assert(platformDescIn); + + m_deviceQueue = deviceQueue; + m_api = deviceQueue->getApi(); + + // Make sure it's not set initially + m_format = VK_FORMAT_UNDEFINED; + + Desc desc(descIn); + +#if SLANG_WINDOWS_FAMILY + const WinPlatformDesc* platformDesc = static_cast<const WinPlatformDesc*>(platformDescIn); + _setPlatformDesc(*platformDesc); + + VkWin32SurfaceCreateInfoKHR surfaceCreateInfo = {}; + surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + surfaceCreateInfo.hinstance = platformDesc->m_hinstance; + surfaceCreateInfo.hwnd = platformDesc->m_hwnd; + + SLANG_VK_RETURN_ON_FAIL(m_api->vkCreateWin32SurfaceKHR(m_api->m_instance, &surfaceCreateInfo, nullptr, &m_surface)); +#else + const XPlatformDesc* platformDesc = static_cast<const XPlatformDesc*>(platformDescIn); + _setPlatformDesc(*platformDesc); + + VkXlibSurfaceCreateInfoKHR surfaceCreateInfo = {}; + surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; + surfaceCreateInfo.dpy = platformDesc->m_display; + surfaceCreateInfo.window = platformDesc->m_window; + + SLANG_VK_RETURN_ON_FAIL(m_api->vkCreateXlibSurfaceKHR(m_api->m_instance, &surfaceCreateInfo, nullptr, &m_surface)); +#endif + + VkBool32 supported = false; + m_api->vkGetPhysicalDeviceSurfaceSupportKHR(m_api->m_physicalDevice, deviceQueue->getQueueIndex(), m_surface, &supported); + + uint32_t numSurfaceFormats = 0; + List<VkSurfaceFormatKHR> surfaceFormats; + m_api->vkGetPhysicalDeviceSurfaceFormatsKHR(m_api->m_physicalDevice, m_surface, &numSurfaceFormats, nullptr); + surfaceFormats.setCount(int(numSurfaceFormats)); + m_api->vkGetPhysicalDeviceSurfaceFormatsKHR(m_api->m_physicalDevice, m_surface, &numSurfaceFormats, surfaceFormats.getBuffer()); + + // Look for a suitable format + List<VkFormat> formats; + formats.add(VulkanUtil::getVkFormat(desc.m_format)); + // HACK! To check for a different format if couldn't be found + if (descIn.m_format == Format::RGBA_Unorm_UInt8) + { + formats.add(VK_FORMAT_B8G8R8A8_UNORM); + } + + for(Index i = 0; i < formats.getCount(); ++i) + { + VkFormat format = formats[i]; + if (_indexOfFormat(surfaceFormats, format) >= 0) + { + m_format = format; + } + } + + if (m_format == VK_FORMAT_UNDEFINED) + { + return SLANG_FAIL; + } + + // Save the desc + m_desc = desc; + + SLANG_RETURN_ON_FAIL(_createSwapChain()); + + m_desc = desc; + return SLANG_OK; +} + +void VulkanSwapChain::getWindowSize(int* widthOut, int* heightOut) const +{ +#if SLANG_WINDOWS_FAMILY + auto platformDesc = _getPlatformDesc<WinPlatformDesc>(); + + RECT rc; + ::GetClientRect(platformDesc->m_hwnd, &rc); + *widthOut = rc.right - rc.left; + *heightOut = rc.bottom - rc.top; +#else + auto platformDesc = _getPlatformDesc<XPlatformDesc>(); + + XWindowAttributes winAttr = {}; + XGetWindowAttributes(platformDesc->m_display, platformDesc->m_window, &winAttr); + + *widthOut = winAttr.width; + *heightOut = winAttr.height; +#endif +} + +SlangResult VulkanSwapChain::_createFrameBuffers(VkRenderPass renderPass) +{ + assert(renderPass != VK_NULL_HANDLE); + + for (Index i = 0; i < m_images.getCount(); ++i) + { + Image& image = m_images[i]; + VkImageView attachments[] = + { + image.m_imageView + }; + + VkFramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = m_width; + framebufferInfo.height = m_height; + framebufferInfo.layers = 1; + + SLANG_VK_RETURN_ON_FAIL(m_api->vkCreateFramebuffer(m_api->m_device, &framebufferInfo, nullptr, &image.m_frameBuffer)); + } + + return SLANG_OK; +} + +void VulkanSwapChain::_destroyFrameBuffers() +{ + for (Index i = 0; i < m_images.getCount(); ++i) + { + Image& image = m_images[i]; + if (image.m_frameBuffer != VK_NULL_HANDLE) + { + m_api->vkDestroyFramebuffer(m_api->m_device, image.m_frameBuffer, nullptr); + image.m_frameBuffer = VK_NULL_HANDLE; + } + } +} + +SlangResult VulkanSwapChain::createFrameBuffers(VkRenderPass renderPass) +{ + if (m_renderPass != VK_NULL_HANDLE) + { + _destroyFrameBuffers(); + m_renderPass = VK_NULL_HANDLE; + } + if (renderPass != VK_NULL_HANDLE) + { + SLANG_RETURN_ON_FAIL(_createFrameBuffers(renderPass)); + } + m_renderPass = renderPass; + return SLANG_OK; +} + +SlangResult VulkanSwapChain::_createSwapChain() +{ + if (hasValidSwapChain()) + { + return SLANG_OK; + } + + int width, height; + getWindowSize(&width, &height); + + VkExtent2D imageExtent = {}; + imageExtent.width = width; + imageExtent.height = height; + + m_width = width; + m_height = height; + + // catch this before throwing error + if (m_width == 0 || m_height == 0) + { + return SLANG_FAIL; + } + + // It is necessary to query the caps -> otherwise the LunarG verification layer will issue an error + { + VkSurfaceCapabilitiesKHR surfaceCaps; + + SLANG_VK_RETURN_ON_FAIL(m_api->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_api->m_physicalDevice, m_surface, &surfaceCaps)); + } + + List<VkPresentModeKHR> presentModes; + uint32_t numPresentModes = 0; + m_api->vkGetPhysicalDeviceSurfacePresentModesKHR(m_api->m_physicalDevice, m_surface, &numPresentModes, nullptr); + presentModes.setCount(numPresentModes); + m_api->vkGetPhysicalDeviceSurfacePresentModesKHR(m_api->m_physicalDevice, m_surface, &numPresentModes, presentModes.getBuffer()); + + { + int numCheckPresentOptions = 3; + VkPresentModeKHR presentOptions[] = { VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_KHR }; + if (m_vsync) + { + presentOptions[0] = VK_PRESENT_MODE_FIFO_KHR; + presentOptions[1] = VK_PRESENT_MODE_IMMEDIATE_KHR; + presentOptions[2] = VK_PRESENT_MODE_MAILBOX_KHR; + } + + m_presentMode = VK_PRESENT_MODE_MAX_ENUM_KHR; // Invalid + + // Find the first option that's available on the device + for (int j = 0; j < numCheckPresentOptions; j++) + { + if (presentModes.indexOf(presentOptions[j]) != Index(-1)) + { + m_presentMode = presentOptions[j]; + break; + } + } + + if (m_presentMode == VK_PRESENT_MODE_MAX_ENUM_KHR) + { + return SLANG_FAIL; + } + } + + VkSwapchainKHR oldSwapchain = VK_NULL_HANDLE; + + VkSwapchainCreateInfoKHR swapchainDesc = {}; + swapchainDesc.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchainDesc.surface = m_surface; + swapchainDesc.minImageCount = 3; + swapchainDesc.imageFormat = m_format; + swapchainDesc.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + swapchainDesc.imageExtent = imageExtent; + swapchainDesc.imageArrayLayers = 1; + swapchainDesc.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + swapchainDesc.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapchainDesc.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + swapchainDesc.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapchainDesc.presentMode = m_presentMode; + swapchainDesc.clipped = VK_TRUE; + swapchainDesc.oldSwapchain = oldSwapchain; + + SLANG_VK_RETURN_ON_FAIL(m_api->vkCreateSwapchainKHR(m_api->m_device, &swapchainDesc, nullptr, &m_swapChain)); + + uint32_t numSwapChainImages = 0; + m_api->vkGetSwapchainImagesKHR(m_api->m_device, m_swapChain, &numSwapChainImages, nullptr); + + { + List<VkImage> images; + images.setCount(numSwapChainImages); + + m_api->vkGetSwapchainImagesKHR(m_api->m_device, m_swapChain, &numSwapChainImages, images.getBuffer()); + + m_images.setCount(numSwapChainImages); + for (int i = 0; i < int(numSwapChainImages); ++i) + { + Image& dstImage = m_images[i]; + dstImage.m_image = images[i]; + + } + } + + { + VkImageViewCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = m_format; + + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + for (int i = 0; i < int(numSwapChainImages); ++i) + { + Image& image = m_images[i]; + + createInfo.image = image.m_image; + + SLANG_VK_RETURN_ON_FAIL(m_api->vkCreateImageView(m_api->m_device, &createInfo, nullptr, &image.m_imageView)); + } + } + + if (m_renderPass != VK_NULL_HANDLE) + { + _createFrameBuffers(m_renderPass); + } + + return SLANG_OK; +} + +void VulkanSwapChain::_destroySwapChain() +{ + if (!hasValidSwapChain()) + { + return; + } + + m_deviceQueue->waitForIdle(); + + if (m_renderPass != VK_NULL_HANDLE) + { + _destroyFrameBuffers(); + } + + for (Index i = 0; i < m_images.getCount(); ++i) + { + Image& image = m_images[i]; + + if (image.m_imageView != VK_NULL_HANDLE) + { + m_api->vkDestroyImageView(m_api->m_device, image.m_imageView, nullptr); + } + } + + if (m_swapChain != VK_NULL_HANDLE) + { + m_api->vkDestroySwapchainKHR(m_api->m_device, m_swapChain, nullptr); + m_swapChain = VK_NULL_HANDLE; + } + + // Mark that it is no longer used + m_images.clear(); +} + +VulkanSwapChain::~VulkanSwapChain() +{ + _destroySwapChain(); + + if (m_surface) + { + m_api->vkDestroySurfaceKHR(m_api->m_instance, m_surface, nullptr); + m_surface = VK_NULL_HANDLE; + } +} + +int VulkanSwapChain::nextFrontImageIndex() +{ + if (!hasValidSwapChain()) + { + if (SLANG_FAILED(_createSwapChain())) + { + return -1; + } + } + + VkSemaphore beginFrameSemaphore = m_deviceQueue->makeCurrent(VulkanDeviceQueue::EventType::BeginFrame); + + uint32_t swapChainIndex = 0; + VkResult result = m_api->vkAcquireNextImageKHR(m_api->m_device, m_swapChain, UINT64_MAX, beginFrameSemaphore, VK_NULL_HANDLE, &swapChainIndex); + + if (result != VK_SUCCESS) + { + _destroySwapChain(); + return -1; + } + m_currentSwapChainIndex = int(swapChainIndex); + return swapChainIndex; +} + +void VulkanSwapChain::present(bool vsync) +{ + if (!hasValidSwapChain()) + { + m_deviceQueue->flush(); + return; + } + + VkSemaphore endFrameSemaphore = m_deviceQueue->makeCurrent(VulkanDeviceQueue::EventType::EndFrame); + + m_deviceQueue->flushStepA(); + + uint32_t swapChainIndices[] = { uint32_t(m_currentSwapChainIndex) }; + + VkPresentInfoKHR presentInfo = {}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &m_swapChain; + presentInfo.pImageIndices = swapChainIndices; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &endFrameSemaphore; + + VkResult result = m_api->vkQueuePresentKHR(m_deviceQueue->getQueue(), &presentInfo); + + m_deviceQueue->makeCompleted(VulkanDeviceQueue::EventType::EndFrame); + + m_deviceQueue->flushStepB(); + + if (result != VK_SUCCESS || m_vsync != vsync) + { + m_vsync = vsync; + _destroySwapChain(); + } +} + +} // renderer_test diff --git a/tools/gfx/vulkan/vk-swap-chain.h b/tools/gfx/vulkan/vk-swap-chain.h new file mode 100644 index 000000000..57d1173b8 --- /dev/null +++ b/tools/gfx/vulkan/vk-swap-chain.h @@ -0,0 +1,141 @@ +// vk-swap-chain.h +#pragma once + +#include "vk-api.h" +#include "vk-device-queue.h" + +#include "../render.h" + +#include "../../source/core/slang-list.h" + +namespace gfx { + +struct VulkanSwapChain +{ + /* enum + { + kMaxImages = 8, + }; */ + + /// Base class for platform specific information + struct PlatformDesc + { + }; + +#if SLANG_WINDOWS_FAMILY + struct WinPlatformDesc: public PlatformDesc + { + HINSTANCE m_hinstance; + HWND m_hwnd; + }; +#else + struct XPlatformDesc : public PlatformDesc + { + Display* m_display; + Window m_window; + }; +#endif + + struct Desc + { + void init() + { + m_format = Format::Unknown; + m_depthFormatTypeless = Format::Unknown; + m_depthFormat = Format::Unknown; + m_textureDepthFormat = Format::Unknown; + } + + Format m_format; + //bool m_enableFormat; + Format m_depthFormatTypeless; + Format m_depthFormat; + Format m_textureDepthFormat; + }; + + struct Image + { + VkImage m_image = VK_NULL_HANDLE; + VkImageView m_imageView = VK_NULL_HANDLE; + VkFramebuffer m_frameBuffer = VK_NULL_HANDLE; + }; + + + /// Must be called before the swap chain can be used + SlangResult init(VulkanDeviceQueue* deviceQueue, const Desc& desc, const PlatformDesc* platformDesc); + + /// Create the frame buffers (they must be compatible with the supplied renderPass) + SlangResult createFrameBuffers(VkRenderPass renderPass); + + /// Returned the desc used to construct the swap chain. + /// Is invalid if init hasn't returned with successful result. + const Desc& getDesc() const { return m_desc; } + + /// True if the swap chain is available + bool hasValidSwapChain() const { return m_images.getCount() > 0; } + + /// Present to the display + void present(bool vsync); + + /// Get the current size of the window (in pixels written to widthOut, heightOut) + void getWindowSize(int* widthOut, int* heightOut) const; + + /// Get the VkFormat for the back buffer + VkFormat getVkFormat() const { return m_format; } + + /// Get width of the back buffers + int getWidth() const { return m_width; } + /// Get the height of the back buffer + int getHeight() const { return m_height; } + + /// Get the detail about the images + const Slang::List<Image>& getImages() const { return m_images; } + + /// Get the next front render image index. Returns -1, if image couldn't be found + int nextFrontImageIndex(); + + /// Dtor + ~VulkanSwapChain(); + + protected: + + + template <typename T> + void _setPlatformDesc(const T& desc) + { + const PlatformDesc* check = &desc; + int size = (sizeof(T) + sizeof(void*) - 1) / sizeof(void*); + m_platformDescBuffer.setCount(size); + *(T*)m_platformDescBuffer.getBuffer() = desc; + } + template <typename T> + const T* _getPlatformDesc() const { return static_cast<const T*>((const PlatformDesc*)m_platformDescBuffer.getBuffer()); } + SlangResult _createSwapChain(); + void _destroySwapChain(); + SlangResult _createFrameBuffers(VkRenderPass renderPass); + void _destroyFrameBuffers(); + + bool m_vsync = true; + int m_width = 0; + int m_height = 0; + + VkPresentModeKHR m_presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; + VkFormat m_format = VK_FORMAT_UNDEFINED; ///< The format used for backbuffer. Valid after successful init. + + VkSurfaceKHR m_surface = VK_NULL_HANDLE; + VkSwapchainKHR m_swapChain = VK_NULL_HANDLE; + + VkRenderPass m_renderPass = VK_NULL_HANDLE; //< Not owned + + int m_currentSwapChainIndex = 0; + + Slang::List<Image> m_images; + + VulkanDeviceQueue* m_deviceQueue = nullptr; + const VulkanApi* m_api = nullptr; + + Desc m_desc; ///< The desc used to init this swap chain + Slang::List<void*> m_platformDescBuffer; ///< Buffer to hold the platform specific description parameters (as passed in platformDesc) +}; + +} // renderer_test diff --git a/tools/gfx/vulkan/vk-util.cpp b/tools/gfx/vulkan/vk-util.cpp new file mode 100644 index 000000000..e8940d1b2 --- /dev/null +++ b/tools/gfx/vulkan/vk-util.cpp @@ -0,0 +1,59 @@ +// vk-util.cpp +#include "vk-util.h" + +#include <stdlib.h> +#include <stdio.h> + +namespace gfx { + +/* static */VkFormat VulkanUtil::getVkFormat(Format format) +{ + switch (format) + { + case Format::RGBA_Float32: return VK_FORMAT_R32G32B32A32_SFLOAT; + case Format::RGB_Float32: return VK_FORMAT_R32G32B32_SFLOAT; + case Format::RG_Float32: return VK_FORMAT_R32G32_SFLOAT; + case Format::R_Float32: return VK_FORMAT_R32_SFLOAT; + case Format::RGBA_Unorm_UInt8: return VK_FORMAT_R8G8B8A8_UNORM; + case Format::R_UInt32: return VK_FORMAT_R32_UINT; + + case Format::D_Float32: return VK_FORMAT_D32_SFLOAT; + case Format::D_Unorm24_S8: return VK_FORMAT_D24_UNORM_S8_UINT; + + default: return VK_FORMAT_UNDEFINED; + } +} + +/* static */SlangResult VulkanUtil::toSlangResult(VkResult res) +{ + return (res == VK_SUCCESS) ? SLANG_OK : SLANG_FAIL; +} + +/* static */Slang::Result VulkanUtil::handleFail(VkResult res) +{ + if (res != VK_SUCCESS) + { + assert(!"Vulkan returned a failure"); + } + return toSlangResult(res); +} + +/* static */void VulkanUtil::checkFail(VkResult res) +{ + assert(res != VK_SUCCESS); + assert(!"Vulkan check failed"); + +} + +/* static */VkPrimitiveTopology VulkanUtil::getVkPrimitiveTopology(PrimitiveTopology topology) +{ + switch (topology) + { + case PrimitiveTopology::TriangleList: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + default: break; + } + assert(!"Unknown topology"); + return VK_PRIMITIVE_TOPOLOGY_MAX_ENUM; +} + +} // renderer_test diff --git a/tools/gfx/vulkan/vk-util.h b/tools/gfx/vulkan/vk-util.h new file mode 100644 index 000000000..c8194789d --- /dev/null +++ b/tools/gfx/vulkan/vk-util.h @@ -0,0 +1,41 @@ +// vk-util.h +#pragma once + +#include "vk-api.h" +#include "../render.h" + +// Macros to make testing vulkan return codes simpler + +/// SLANG_VK_RETURN_ON_FAIL can be used in a similar way to SLANG_RETURN_ON_FAIL macro, except it will turn a vulkan failure into Slang::Result in the process +/// Calls handleFail which on debug builds asserts +#define SLANG_VK_RETURN_ON_FAIL(x) { VkResult _res = x; if (_res != VK_SUCCESS) { return VulkanUtil::handleFail(_res); } } + +#define SLANG_VK_RETURN_NULL_ON_FAIL(x) { VkResult _res = x; if (_res != VK_SUCCESS) { VulkanUtil::handleFail(_res); return nullptr; } } + +/// Is similar to SLANG_VK_RETURN_ON_FAIL, but does not return. Will call checkFail on failure - which asserts on debug builds. +#define SLANG_VK_CHECK(x) { VkResult _res = x; if (_res != VK_SUCCESS) { VulkanUtil::checkFail(_res); } } + +namespace gfx { + +// Utility functions for Vulkan +struct VulkanUtil +{ + /// Get the equivalent VkFormat from the format + /// Returns VK_FORMAT_UNDEFINED if a match is not found + static VkFormat getVkFormat(Format format); + + /// Called by SLANG_VK_RETURN_FAIL if a res is a failure. + /// On debug builds this will cause an assertion on failure. + static Slang::Result handleFail(VkResult res); + /// Called when a failure has occurred with SLANG_VK_CHECK - will typically assert. + static void checkFail(VkResult res); + + /// Get the VkPrimitiveTopology for the given topology. + /// Returns VK_PRIMITIVE_TOPOLOGY_MAX_ENUM on failure + static VkPrimitiveTopology getVkPrimitiveTopology(PrimitiveTopology topology); + + /// Returns Slang::Result equivalent of a VkResult + static Slang::Result toSlangResult(VkResult res); +}; + +} // renderer_test |
