#include "RenderAPI.h" #include "PlatformBase.h" #if SUPPORT_VULKAN #include #include #include #include // This plugin does not link to the Vulkan loader, easier to support multiple APIs and systems that don't have Vulkan support #define VK_NO_PROTOTYPES #include "Unity/IUnityGraphicsVulkan.h" #define UNITY_USED_VULKAN_API_FUNCTIONS(apply) \ apply(vkCreateInstance); \ apply(vkCmdBeginRenderPass); \ apply(vkCreateBuffer); \ apply(vkGetPhysicalDeviceMemoryProperties); \ apply(vkGetBufferMemoryRequirements); \ apply(vkMapMemory); \ apply(vkBindBufferMemory); \ apply(vkAllocateMemory); \ apply(vkDestroyBuffer); \ apply(vkFreeMemory); \ apply(vkUnmapMemory); \ apply(vkQueueWaitIdle); \ apply(vkDeviceWaitIdle); \ apply(vkCmdCopyBufferToImage); \ apply(vkFlushMappedMemoryRanges); \ apply(vkCreatePipelineLayout); \ apply(vkCreateShaderModule); \ apply(vkDestroyShaderModule); \ apply(vkCreateGraphicsPipelines); \ apply(vkCmdBindPipeline); \ apply(vkCmdDraw); \ apply(vkCmdPushConstants); \ apply(vkCmdBindVertexBuffers); \ apply(vkDestroyPipeline); \ apply(vkCmdSetFragmentShadingRateKHR); \ apply(vkCmdBindDescriptorSets); \ apply(vkCreateDevice); \ apply(vkGetPhysicalDeviceFeatures2); \ apply(vkGetPhysicalDeviceProperties2); \ apply(vkDestroyPipelineLayout); #define VULKAN_DEFINE_API_FUNCPTR(func) static PFN_##func func VULKAN_DEFINE_API_FUNCPTR(vkGetInstanceProcAddr); UNITY_USED_VULKAN_API_FUNCTIONS(VULKAN_DEFINE_API_FUNCPTR); #undef VULKAN_DEFINE_API_FUNCPTR extern void UnityLog(const char* msg); enum VRSPluginShadingRate { X1_PER_PIXEL, X1_PER_2X1_PIXELS, X1_PER_1X2_PIXELS, X1_PER_2X2_PIXELS, X1_PER_4X2_PIXELS, X1_PER_2X4_PIXELS, X1_PER_4X4_PIXELS, }; static VkExtent2D vrs_ragment_size_table[] = { VkExtent2D(1,1), VkExtent2D(2,1), VkExtent2D(1,2), VkExtent2D(2,2), VkExtent2D(4,2), VkExtent2D(2,4), VkExtent2D(4,4), }; static VkExtent2D vrs_fragment_size = vrs_ragment_size_table[0]; static bool vrs_enable; static int index = 0; static void LoadVulkanAPI(PFN_vkGetInstanceProcAddr getInstanceProcAddr, VkInstance instance) { UnityLog("LoadVulkanAPI called."); if (!vkGetInstanceProcAddr && getInstanceProcAddr) vkGetInstanceProcAddr = getInstanceProcAddr; if (!vkCreateInstance) vkCreateInstance = (PFN_vkCreateInstance)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"); #define LOAD_VULKAN_FUNC(fn) if (!fn) fn = (PFN_##fn)vkGetInstanceProcAddr(instance, #fn) UNITY_USED_VULKAN_API_FUNCTIONS(LOAD_VULKAN_FUNC); #undef LOAD_VULKAN_FUNC } static VKAPI_ATTR void VKAPI_CALL Hook_vkCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin, VkSubpassContents contents) { // Change this to 'true' to override the clear color with green const bool allowOverrideClearColor = false; if (pRenderPassBegin->clearValueCount <= 16 && pRenderPassBegin->clearValueCount > 0 && allowOverrideClearColor) { VkClearValue clearValues[16] = {}; memcpy(clearValues, pRenderPassBegin->pClearValues, pRenderPassBegin->clearValueCount * sizeof(VkClearValue)); VkRenderPassBeginInfo patchedBeginInfo = *pRenderPassBegin; patchedBeginInfo.pClearValues = clearValues; for (unsigned int i = 0; i < pRenderPassBegin->clearValueCount - 1; ++i) { clearValues[i].color.float32[0] = 0.0; clearValues[i].color.float32[1] = 1.0; clearValues[i].color.float32[2] = 0.0; clearValues[i].color.float32[3] = 1.0; } vkCmdBeginRenderPass(commandBuffer, &patchedBeginInfo, contents); } else { vkCmdBeginRenderPass(commandBuffer, pRenderPassBegin, contents); } } static VKAPI_ATTR VkResult VKAPI_CALL Hook_vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { UnityLog("Hook_vkCreateInstance called."); if (pCreateInfo->ppEnabledExtensionNames) { for (size_t i = 0; i < pCreateInfo->enabledExtensionCount; i++) { UnityLog(pCreateInfo->ppEnabledExtensionNames[i]); } } vkCreateInstance = (PFN_vkCreateInstance)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"); VkResult result = vkCreateInstance(pCreateInfo, pAllocator, pInstance); if (result == VK_SUCCESS) LoadVulkanAPI(vkGetInstanceProcAddr, *pInstance); return result; } static VKAPI_ATTR VkResult VKAPI_CALL Hook_vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { if (pCreateInfo->pNext) { UnityLog("pCreateInfo->pNext"); } return vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); } static VKAPI_ATTR void VKAPI_CALL Hook_vkCmdBindDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t firstSet, uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets, uint32_t dynamicOffsetCount, const uint32_t* pDynamicOffsets) { if (vrs_enable) { VkExtent2D fragment_size = vrs_ragment_size_table[index]; VkFragmentShadingRateCombinerOpKHR combiner_ops[2]; // If shading rate from attachment is enabled, we set the combiner, so that the values from the attachment are used // Combiner for pipeline (A) and primitive (B) - Not used in this sample combiner_ops[0] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_KHR; // Combiner for pipeline (A) and attachment (B), replace the pipeline default value (fragment_size) with the fragment sizes stored in the attachment combiner_ops[1] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_KHR; vkCmdSetFragmentShadingRateKHR(commandBuffer, &fragment_size, combiner_ops); } vkCmdBindDescriptorSets(commandBuffer, pipelineBindPoint, layout, firstSet, descriptorSetCount, pDescriptorSets, dynamicOffsetCount, pDynamicOffsets); } static VKAPI_ATTR void VKAPI_CALL Hook_vkCmdBindPipeline(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline) { //if (vrs_enable) //{ // VkExtent2D fragment_size = vrs_ragment_size_table[index]; // VkFragmentShadingRateCombinerOpKHR combiner_ops[2]; // // If shading rate from attachment is enabled, we set the combiner, so that the values from the attachment are used // // Combiner for pipeline (A) and primitive (B) - Not used in this sample // combiner_ops[0] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_KHR; // // Combiner for pipeline (A) and attachment (B), replace the pipeline default value (fragment_size) with the fragment sizes stored in the attachment // combiner_ops[1] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_KHR; // vkCmdSetFragmentShadingRateKHR(commandBuffer, &fragment_size, combiner_ops); //} vkCmdBindPipeline(commandBuffer, pipelineBindPoint, pipeline); } static int FindMemoryTypeIndex(VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties, VkMemoryRequirements const & memoryRequirements, VkMemoryPropertyFlags memoryPropertyFlags) { uint32_t memoryTypeBits = memoryRequirements.memoryTypeBits; // Search memtypes to find first index with those properties for (uint32_t memoryTypeIndex = 0; memoryTypeIndex < VK_MAX_MEMORY_TYPES; ++memoryTypeIndex) { if ((memoryTypeBits & 1) == 1) { // Type is available, does it match user properties? if ((physicalDeviceMemoryProperties.memoryTypes[memoryTypeIndex].propertyFlags & memoryPropertyFlags) == memoryPropertyFlags) return memoryTypeIndex; } memoryTypeBits >>= 1; } return -1; } static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL Hook_vkGetInstanceProcAddr(VkInstance device, const char* funcName) { if (!funcName) return NULL; UnityLog(funcName); #define INTERCEPT(fn) if (strcmp(funcName, #fn) == 0) return (PFN_vkVoidFunction)&Hook_##fn INTERCEPT(vkCreateInstance); INTERCEPT(vkCreateDevice); INTERCEPT(vkCmdBindPipeline); INTERCEPT(vkCmdBindDescriptorSets); #undef INTERCEPT return (PFN_vkVoidFunction)vkGetInstanceProcAddr(device, funcName); } static PFN_vkGetInstanceProcAddr UNITY_INTERFACE_API InterceptVulkanInitialization(PFN_vkGetInstanceProcAddr getInstanceProcAddr, void*) { vkGetInstanceProcAddr = getInstanceProcAddr; return Hook_vkGetInstanceProcAddr; } extern "C" void RenderAPI_Vulkan_OnPluginLoad(IUnityInterfaces* interfaces) { if (IUnityGraphicsVulkanV2* vulkanInterface = interfaces->Get()) { vulkanInterface->AddInterceptInitialization(InterceptVulkanInitialization, NULL, 0); } else if (IUnityGraphicsVulkan* vulkanInterface = interfaces->Get()) { vulkanInterface->InterceptInitialization(InterceptVulkanInitialization, NULL); } } struct VulkanBuffer { VkBuffer buffer; VkDeviceMemory deviceMemory; void* mapped; VkDeviceSize sizeInBytes; VkDeviceSize deviceMemorySize; VkMemoryPropertyFlags deviceMemoryFlags; }; static VkPipelineLayout CreateTrianglePipelineLayout(VkDevice device) { VkPushConstantRange pushConstantRange; pushConstantRange.offset = 0; pushConstantRange.size = 64; // single matrix pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {}; pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; pipelineLayoutCreateInfo.pushConstantRangeCount = 1; VkPipelineLayout pipelineLayout; return vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, NULL, &pipelineLayout) == VK_SUCCESS ? pipelineLayout : VK_NULL_HANDLE; } namespace Shader { // Source of vertex shader (filename: shader.vert) /* #version 310 es layout(location = 0) in highp vec3 vpos; layout(location = 1) in highp vec4 vcol; layout(location = 0) out highp vec4 color; layout(push_constant) uniform PushConstants { mat4 matrix; }; void main() { gl_Position = matrix * vec4(vpos, 1.0); color = vcol; } */ // Source of fragment shader (filename: shader.frag) /* #version 310 es layout(location = 0) out highp vec4 fragColor; layout(location = 0) in highp vec4 color; void main() { fragColor = color; } */ // compiled to SPIR-V using: // %VULKAN_SDK%\bin\glslc -mfmt=num shader.frag shader.vert -c const uint32_t vertexShaderSpirv[] = { 0x07230203,0x00010000,0x000d0007,0x00000024, 0x00000000,0x00020011,0x00000001,0x0006000b, 0x00000001,0x4c534c47,0x6474732e,0x3035342e, 0x00000000,0x0003000e,0x00000000,0x00000001, 0x0009000f,0x00000000,0x00000004,0x6e69616d, 0x00000000,0x0000000a,0x00000016,0x00000020, 0x00000022,0x00030003,0x00000001,0x00000136, 0x000a0004,0x475f4c47,0x4c474f4f,0x70635f45, 0x74735f70,0x5f656c79,0x656e696c,0x7269645f, 0x69746365,0x00006576,0x00080004,0x475f4c47, 0x4c474f4f,0x6e695f45,0x64756c63,0x69645f65, 0x74636572,0x00657669,0x00040005,0x00000004, 0x6e69616d,0x00000000,0x00060005,0x00000008, 0x505f6c67,0x65567265,0x78657472,0x00000000, 0x00060006,0x00000008,0x00000000,0x505f6c67, 0x7469736f,0x006e6f69,0x00070006,0x00000008, 0x00000001,0x505f6c67,0x746e696f,0x657a6953, 0x00000000,0x00030005,0x0000000a,0x00000000, 0x00060005,0x0000000e,0x68737550,0x736e6f43, 0x746e6174,0x00000073,0x00050006,0x0000000e, 0x00000000,0x7274616d,0x00007869,0x00030005, 0x00000010,0x00000000,0x00040005,0x00000016, 0x736f7076,0x00000000,0x00040005,0x00000020, 0x6f6c6f63,0x00000072,0x00040005,0x00000022, 0x6c6f6376,0x00000000,0x00050048,0x00000008, 0x00000000,0x0000000b,0x00000000,0x00050048, 0x00000008,0x00000001,0x0000000b,0x00000001, 0x00030047,0x00000008,0x00000002,0x00040048, 0x0000000e,0x00000000,0x00000005,0x00050048, 0x0000000e,0x00000000,0x00000023,0x00000000, 0x00050048,0x0000000e,0x00000000,0x00000007, 0x00000010,0x00030047,0x0000000e,0x00000002, 0x00040047,0x00000016,0x0000001e,0x00000000, 0x00040047,0x00000020,0x0000001e,0x00000000, 0x00040047,0x00000022,0x0000001e,0x00000001, 0x00020013,0x00000002,0x00030021,0x00000003, 0x00000002,0x00030016,0x00000006,0x00000020, 0x00040017,0x00000007,0x00000006,0x00000004, 0x0004001e,0x00000008,0x00000007,0x00000006, 0x00040020,0x00000009,0x00000003,0x00000008, 0x0004003b,0x00000009,0x0000000a,0x00000003, 0x00040015,0x0000000b,0x00000020,0x00000001, 0x0004002b,0x0000000b,0x0000000c,0x00000000, 0x00040018,0x0000000d,0x00000007,0x00000004, 0x0003001e,0x0000000e,0x0000000d,0x00040020, 0x0000000f,0x00000009,0x0000000e,0x0004003b, 0x0000000f,0x00000010,0x00000009,0x00040020, 0x00000011,0x00000009,0x0000000d,0x00040017, 0x00000014,0x00000006,0x00000003,0x00040020, 0x00000015,0x00000001,0x00000014,0x0004003b, 0x00000015,0x00000016,0x00000001,0x0004002b, 0x00000006,0x00000018,0x3f800000,0x00040020, 0x0000001e,0x00000003,0x00000007,0x0004003b, 0x0000001e,0x00000020,0x00000003,0x00040020, 0x00000021,0x00000001,0x00000007,0x0004003b, 0x00000021,0x00000022,0x00000001,0x00050036, 0x00000002,0x00000004,0x00000000,0x00000003, 0x000200f8,0x00000005,0x00050041,0x00000011, 0x00000012,0x00000010,0x0000000c,0x0004003d, 0x0000000d,0x00000013,0x00000012,0x0004003d, 0x00000014,0x00000017,0x00000016,0x00050051, 0x00000006,0x00000019,0x00000017,0x00000000, 0x00050051,0x00000006,0x0000001a,0x00000017, 0x00000001,0x00050051,0x00000006,0x0000001b, 0x00000017,0x00000002,0x00070050,0x00000007, 0x0000001c,0x00000019,0x0000001a,0x0000001b, 0x00000018,0x00050091,0x00000007,0x0000001d, 0x00000013,0x0000001c,0x00050041,0x0000001e, 0x0000001f,0x0000000a,0x0000000c,0x0003003e, 0x0000001f,0x0000001d,0x0004003d,0x00000007, 0x00000023,0x00000022,0x0003003e,0x00000020, 0x00000023,0x000100fd,0x00010038 }; const uint32_t fragmentShaderSpirv[] = { 0x07230203,0x00010000,0x000d0006,0x0000000d, 0x00000000,0x00020011,0x00000001,0x0006000b, 0x00000001,0x4c534c47,0x6474732e,0x3035342e, 0x00000000,0x0003000e,0x00000000,0x00000001, 0x0007000f,0x00000004,0x00000004,0x6e69616d, 0x00000000,0x00000009,0x0000000b,0x00030010, 0x00000004,0x00000007,0x00030003,0x00000001, 0x00000136,0x000a0004,0x475f4c47,0x4c474f4f, 0x70635f45,0x74735f70,0x5f656c79,0x656e696c, 0x7269645f,0x69746365,0x00006576,0x00080004, 0x475f4c47,0x4c474f4f,0x6e695f45,0x64756c63, 0x69645f65,0x74636572,0x00657669,0x00040005, 0x00000004,0x6e69616d,0x00000000,0x00050005, 0x00000009,0x67617266,0x6f6c6f43,0x00000072, 0x00040005,0x0000000b,0x6f6c6f63,0x00000072, 0x00040047,0x00000009,0x0000001e,0x00000000, 0x00040047,0x0000000b,0x0000001e,0x00000000, 0x00020013,0x00000002,0x00030021,0x00000003, 0x00000002,0x00030016,0x00000006,0x00000020, 0x00040017,0x00000007,0x00000006,0x00000004, 0x00040020,0x00000008,0x00000003,0x00000007, 0x0004003b,0x00000008,0x00000009,0x00000003, 0x00040020,0x0000000a,0x00000001,0x00000007, 0x0004003b,0x0000000a,0x0000000b,0x00000001, 0x00050036,0x00000002,0x00000004,0x00000000, 0x00000003,0x000200f8,0x00000005,0x0004003d, 0x00000007,0x0000000c,0x0000000b,0x0003003e, 0x00000009,0x0000000c,0x000100fd,0x00010038 }; } // namespace Shader static VkPipeline CreateTrianglePipeline(VkDevice device, VkPipelineLayout pipelineLayout, VkRenderPass renderPass, VkPipelineCache pipelineCache) { if (pipelineLayout == VK_NULL_HANDLE) return VK_NULL_HANDLE; if (device == VK_NULL_HANDLE) return VK_NULL_HANDLE; if (renderPass == VK_NULL_HANDLE) return VK_NULL_HANDLE; bool success = true; VkGraphicsPipelineCreateInfo pipelineCreateInfo = {}; VkPipelineShaderStageCreateInfo shaderStages[2] = {}; shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; shaderStages[0].pName = "main"; shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shaderStages[1].pName = "main"; if (success) { VkShaderModuleCreateInfo moduleCreateInfo = {}; moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; moduleCreateInfo.codeSize = sizeof(Shader::vertexShaderSpirv); moduleCreateInfo.pCode = Shader::vertexShaderSpirv; success = vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderStages[0].module) == VK_SUCCESS; } if (success) { VkShaderModuleCreateInfo moduleCreateInfo = {}; moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; moduleCreateInfo.codeSize = sizeof(Shader::fragmentShaderSpirv); moduleCreateInfo.pCode = Shader::fragmentShaderSpirv; success = vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderStages[1].module) == VK_SUCCESS; } VkPipeline pipeline; if (success) { pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineCreateInfo.layout = pipelineLayout; pipelineCreateInfo.renderPass = renderPass; VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {}; inputAssemblyState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; VkPipelineRasterizationStateCreateInfo rasterizationState = {}; rasterizationState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizationState.polygonMode = VK_POLYGON_MODE_FILL; rasterizationState.cullMode = VK_CULL_MODE_NONE; rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizationState.depthClampEnable = VK_FALSE; rasterizationState.rasterizerDiscardEnable = VK_FALSE; rasterizationState.depthBiasEnable = VK_FALSE; rasterizationState.lineWidth = 1.0f; VkPipelineColorBlendAttachmentState blendAttachmentState[1] = {}; blendAttachmentState[0].colorWriteMask = 0xf; blendAttachmentState[0].blendEnable = VK_FALSE; VkPipelineColorBlendStateCreateInfo colorBlendState = {}; colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlendState.attachmentCount = 1; colorBlendState.pAttachments = blendAttachmentState; VkPipelineViewportStateCreateInfo viewportState = {}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.scissorCount = 1; const VkDynamicState dynamicStateEnables[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR }; VkPipelineDynamicStateCreateInfo dynamicState = {}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.pDynamicStates = dynamicStateEnables; dynamicState.dynamicStateCount = sizeof(dynamicStateEnables) / sizeof(*dynamicStateEnables); VkPipelineDepthStencilStateCreateInfo depthStencilState = {}; depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencilState.depthTestEnable = VK_TRUE; depthStencilState.depthWriteEnable = VK_TRUE; depthStencilState.depthBoundsTestEnable = VK_FALSE; depthStencilState.stencilTestEnable = VK_FALSE; depthStencilState.depthCompareOp = VK_COMPARE_OP_GREATER_OR_EQUAL; // Unity/Vulkan uses reverse Z depthStencilState.back.failOp = VK_STENCIL_OP_KEEP; depthStencilState.back.passOp = VK_STENCIL_OP_KEEP; depthStencilState.back.compareOp = VK_COMPARE_OP_ALWAYS; depthStencilState.front = depthStencilState.back; VkPipelineMultisampleStateCreateInfo multisampleState = {}; multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; multisampleState.pSampleMask = NULL; // Vertex: // float3 vpos; // byte4 vcol; VkVertexInputBindingDescription vertexInputBinding = {}; vertexInputBinding.binding = 0; vertexInputBinding.stride = 16; vertexInputBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; VkVertexInputAttributeDescription vertexInputAttributes[2]; vertexInputAttributes[0].binding = 0; vertexInputAttributes[0].location = 0; vertexInputAttributes[0].format = VK_FORMAT_R32G32B32_SFLOAT; vertexInputAttributes[0].offset = 0; vertexInputAttributes[1].binding = 0; vertexInputAttributes[1].location = 1; vertexInputAttributes[1].format = VK_FORMAT_R8G8B8A8_UNORM; vertexInputAttributes[1].offset = 12; VkPipelineVertexInputStateCreateInfo vertexInputState = {}; vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputState.vertexBindingDescriptionCount = 1; vertexInputState.pVertexBindingDescriptions = &vertexInputBinding; vertexInputState.vertexAttributeDescriptionCount = 2; vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes; pipelineCreateInfo.stageCount = sizeof(shaderStages) / sizeof(*shaderStages); pipelineCreateInfo.pStages = shaderStages; pipelineCreateInfo.pVertexInputState = &vertexInputState; pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; pipelineCreateInfo.pRasterizationState = &rasterizationState; pipelineCreateInfo.pColorBlendState = &colorBlendState; pipelineCreateInfo.pMultisampleState = &multisampleState; pipelineCreateInfo.pViewportState = &viewportState; pipelineCreateInfo.pDepthStencilState = &depthStencilState; pipelineCreateInfo.pDynamicState = &dynamicState; success = vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, NULL, &pipeline) == VK_SUCCESS; } if (shaderStages[0].module != VK_NULL_HANDLE) vkDestroyShaderModule(device, shaderStages[0].module, NULL); if (shaderStages[1].module != VK_NULL_HANDLE) vkDestroyShaderModule(device, shaderStages[1].module, NULL); return success ? pipeline : VK_NULL_HANDLE; } class RenderAPI_Vulkan : public RenderAPI { public: RenderAPI_Vulkan(); virtual ~RenderAPI_Vulkan() { } virtual void ProcessDeviceEvent(UnityGfxDeviceEventType type, IUnityInterfaces* interfaces); virtual bool GetUsesReverseZ() { return true; } virtual void DrawSimpleTriangles(const float worldMatrix[16], int triangleCount, const void* verticesFloat3Byte4); virtual void* BeginModifyTexture(void* textureHandle, int textureWidth, int textureHeight, int* outRowPitch); virtual void EndModifyTexture(void* textureHandle, int textureWidth, int textureHeight, int rowPitch, void* dataPtr); virtual void* BeginModifyVertexBuffer(void* bufferHandle, size_t* outBufferSize); virtual void EndModifyVertexBuffer(void* bufferHandle); virtual void enableVRS(int vrsEnum); virtual void disableVRS(); private: typedef std::vector VulkanBuffers; typedef std::map DeleteQueue; private: bool CreateVulkanBuffer(size_t bytes, VulkanBuffer* buffer, VkBufferUsageFlags usage); void ImmediateDestroyVulkanBuffer(const VulkanBuffer& buffer); void SafeDestroy(unsigned long long frameNumber, const VulkanBuffer& buffer); void GarbageCollect(bool force = false); private: IUnityGraphicsVulkan* m_UnityVulkan; UnityVulkanInstance m_Instance; VulkanBuffer m_TextureStagingBuffer; VulkanBuffer m_VertexStagingBuffer; std::map m_DeleteQueue; VkPipelineLayout m_TrianglePipelineLayout; VkPipeline m_TrianglePipeline; VkRenderPass m_TrianglePipelineRenderPass; }; RenderAPI* CreateRenderAPI_Vulkan() { return new RenderAPI_Vulkan(); } RenderAPI_Vulkan::RenderAPI_Vulkan() : m_UnityVulkan(NULL) , m_TextureStagingBuffer() , m_VertexStagingBuffer() , m_TrianglePipelineLayout(VK_NULL_HANDLE) , m_TrianglePipeline(VK_NULL_HANDLE) , m_TrianglePipelineRenderPass(VK_NULL_HANDLE) { } void RenderAPI_Vulkan::ProcessDeviceEvent(UnityGfxDeviceEventType type, IUnityInterfaces* interfaces) { switch (type) { case kUnityGfxDeviceEventInitialize: UnityLog("ProcessDeviceEvent called. kUnityGfxDeviceEventInitialize"); m_UnityVulkan = interfaces->Get(); m_Instance = m_UnityVulkan->Instance(); // Make sure Vulkan API functions are loaded LoadVulkanAPI(m_Instance.getInstanceProcAddr, m_Instance.instance); UnityVulkanPluginEventConfig config_1; config_1.graphicsQueueAccess = kUnityVulkanGraphicsQueueAccess_DontCare; config_1.renderPassPrecondition = kUnityVulkanRenderPass_EnsureInside; config_1.flags = kUnityVulkanEventConfigFlag_EnsurePreviousFrameSubmission | kUnityVulkanEventConfigFlag_ModifiesCommandBuffersState; m_UnityVulkan->ConfigureEvent(1, &config_1); // alternative way to intercept API m_UnityVulkan->InterceptVulkanAPI("vkCmdBeginRenderPass", (PFN_vkVoidFunction)Hook_vkCmdBeginRenderPass); break; case kUnityGfxDeviceEventShutdown: UnityLog("ProcessDeviceEvent called. kUnityGfxDeviceEventShutdown"); if (m_Instance.device != VK_NULL_HANDLE) { GarbageCollect(true); if (m_TrianglePipeline != VK_NULL_HANDLE) { vkDestroyPipeline(m_Instance.device, m_TrianglePipeline, NULL); m_TrianglePipeline = VK_NULL_HANDLE; } if (m_TrianglePipelineLayout != VK_NULL_HANDLE) { vkDestroyPipelineLayout(m_Instance.device, m_TrianglePipelineLayout, NULL); m_TrianglePipelineLayout = VK_NULL_HANDLE; } } m_UnityVulkan = NULL; m_TrianglePipelineRenderPass = VK_NULL_HANDLE; m_Instance = UnityVulkanInstance(); break; } } bool RenderAPI_Vulkan::CreateVulkanBuffer(size_t sizeInBytes, VulkanBuffer* buffer, VkBufferUsageFlags usage) { if (sizeInBytes == 0) return false; VkBufferCreateInfo bufferCreateInfo; bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferCreateInfo.pNext = NULL; bufferCreateInfo.pQueueFamilyIndices = &m_Instance.queueFamilyIndex; bufferCreateInfo.queueFamilyIndexCount = 1; bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; bufferCreateInfo.usage = usage; bufferCreateInfo.flags = 0; bufferCreateInfo.size = sizeInBytes; *buffer = VulkanBuffer(); if (vkCreateBuffer(m_Instance.device, &bufferCreateInfo, NULL, &buffer->buffer) != VK_SUCCESS) return false; VkPhysicalDeviceMemoryProperties physicalDeviceProperties; vkGetPhysicalDeviceMemoryProperties(m_Instance.physicalDevice, &physicalDeviceProperties); VkMemoryRequirements memoryRequirements; vkGetBufferMemoryRequirements(m_Instance.device, buffer->buffer, &memoryRequirements); const int memoryTypeIndex = FindMemoryTypeIndex(physicalDeviceProperties, memoryRequirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); if (memoryTypeIndex < 0) { ImmediateDestroyVulkanBuffer(*buffer); return false; } VkMemoryAllocateInfo memoryAllocateInfo; memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memoryAllocateInfo.pNext = NULL; memoryAllocateInfo.memoryTypeIndex = memoryTypeIndex; memoryAllocateInfo.allocationSize = memoryRequirements.size; if (vkAllocateMemory(m_Instance.device, &memoryAllocateInfo, NULL, &buffer->deviceMemory) != VK_SUCCESS) { ImmediateDestroyVulkanBuffer(*buffer); return false; } if (vkMapMemory(m_Instance.device, buffer->deviceMemory, 0, VK_WHOLE_SIZE, 0, &buffer->mapped) != VK_SUCCESS) { ImmediateDestroyVulkanBuffer(*buffer); return false; } if (vkBindBufferMemory(m_Instance.device, buffer->buffer, buffer->deviceMemory, 0) != VK_SUCCESS) { ImmediateDestroyVulkanBuffer(*buffer); return false; } buffer->sizeInBytes = sizeInBytes; buffer->deviceMemoryFlags = physicalDeviceProperties.memoryTypes[memoryTypeIndex].propertyFlags; buffer->deviceMemorySize = memoryAllocateInfo.allocationSize; return true; } void RenderAPI_Vulkan::ImmediateDestroyVulkanBuffer(const VulkanBuffer& buffer) { if (buffer.buffer != VK_NULL_HANDLE) vkDestroyBuffer(m_Instance.device, buffer.buffer, NULL); if (buffer.mapped && buffer.deviceMemory != VK_NULL_HANDLE) vkUnmapMemory(m_Instance.device, buffer.deviceMemory); if (buffer.deviceMemory != VK_NULL_HANDLE) vkFreeMemory(m_Instance.device, buffer.deviceMemory, NULL); } void RenderAPI_Vulkan::SafeDestroy(unsigned long long frameNumber, const VulkanBuffer& buffer) { m_DeleteQueue[frameNumber].push_back(buffer); } void RenderAPI_Vulkan::GarbageCollect(bool force /*= false*/) { UnityVulkanRecordingState recordingState; if (force) recordingState.safeFrameNumber = ~0ull; else if (!m_UnityVulkan->CommandRecordingState(&recordingState, kUnityVulkanGraphicsQueueAccess_DontCare)) return; DeleteQueue::iterator it = m_DeleteQueue.begin(); while (it != m_DeleteQueue.end()) { if (it->first <= recordingState.safeFrameNumber) { for (size_t i = 0; i < it->second.size(); ++i) ImmediateDestroyVulkanBuffer(it->second[i]); m_DeleteQueue.erase(it++); } else ++it; } } void RenderAPI_Vulkan::enableVRS(int vrsEnum) { char msg[20]; sprintf(msg, "vrs:%d\0", vrsEnum); UnityLog(msg); index = vrsEnum; vrs_fragment_size = vrs_ragment_size_table[vrsEnum]; vrs_enable = true; } void RenderAPI_Vulkan::disableVRS() { vrs_enable = false; } void RenderAPI_Vulkan::DrawSimpleTriangles(const float worldMatrix[16], int triangleCount, const void* verticesFloat3Byte4) { // not needed, we already configured the event to be inside a render pass // m_UnityVulkan->EnsureInsideRenderPass(); UnityVulkanRecordingState recordingState; if (!m_UnityVulkan->CommandRecordingState(&recordingState, kUnityVulkanGraphicsQueueAccess_DontCare)) return; // Unity does not destroy render passes, so this is safe regarding ABA-problem if (recordingState.renderPass != m_TrianglePipelineRenderPass) { if (m_TrianglePipelineLayout == VK_NULL_HANDLE) m_TrianglePipelineLayout = CreateTrianglePipelineLayout(m_Instance.device); m_TrianglePipeline = CreateTrianglePipeline(m_Instance.device, m_TrianglePipelineLayout, recordingState.renderPass, VK_NULL_HANDLE); m_TrianglePipelineRenderPass = recordingState.renderPass; } VkExtent2D fragment_size = { 2, 2 }; VkFragmentShadingRateCombinerOpKHR combiner_ops[2]; // If shading rate from attachment is enabled, we set the combiner, so that the values from the attachment are used // Combiner for pipeline (A) and primitive (B) - Not used in this sample combiner_ops[0] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR; // Combiner for pipeline (A) and attachment (B), replace the pipeline default value (fragment_size) with the fragment sizes stored in the attachment combiner_ops[1] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR; vkCmdSetFragmentShadingRateKHR(recordingState.commandBuffer, &fragment_size, combiner_ops); if (m_TrianglePipeline != VK_NULL_HANDLE && m_TrianglePipelineLayout != VK_NULL_HANDLE) { VulkanBuffer buffer; if (!CreateVulkanBuffer(16 * 3 * triangleCount, &buffer, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)) return; memcpy(buffer.mapped, verticesFloat3Byte4, static_cast(buffer.sizeInBytes)); if (!(buffer.deviceMemoryFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { VkMappedMemoryRange range; range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; range.pNext = NULL; range.memory = buffer.deviceMemory; range.offset = 0; range.size = buffer.deviceMemorySize; vkFlushMappedMemoryRanges(m_Instance.device, 1, &range); } const VkDeviceSize offset = 0; vkCmdBindVertexBuffers(recordingState.commandBuffer, 0, 1, &buffer.buffer, &offset); vkCmdPushConstants(recordingState.commandBuffer, m_TrianglePipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, 64, (const void*)worldMatrix); vkCmdBindPipeline(recordingState.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_TrianglePipeline); vkCmdDraw(recordingState.commandBuffer, triangleCount * 3, 1, 0, 0); SafeDestroy(recordingState.currentFrameNumber, buffer); } GarbageCollect(); } void* RenderAPI_Vulkan::BeginModifyTexture(void* textureHandle, int textureWidth, int textureHeight, int* outRowPitch) { *outRowPitch = textureWidth * 4; const size_t stagingBufferSizeRequirements = *outRowPitch * textureHeight; UnityVulkanRecordingState recordingState; if (!m_UnityVulkan->CommandRecordingState(&recordingState, kUnityVulkanGraphicsQueueAccess_DontCare)) return NULL; SafeDestroy(recordingState.currentFrameNumber, m_TextureStagingBuffer); m_TextureStagingBuffer = VulkanBuffer(); if (!CreateVulkanBuffer(stagingBufferSizeRequirements, &m_TextureStagingBuffer, VK_BUFFER_USAGE_TRANSFER_SRC_BIT)) return NULL; return m_TextureStagingBuffer.mapped; } void RenderAPI_Vulkan::EndModifyTexture(void* textureHandle, int textureWidth, int textureHeight, int rowPitch, void* dataPtr) { // cannot do resource uploads inside renderpass m_UnityVulkan->EnsureOutsideRenderPass(); UnityVulkanImage image; if (!m_UnityVulkan->AccessTexture(textureHandle, UnityVulkanWholeImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, kUnityVulkanResourceAccess_PipelineBarrier, &image)) return; UnityVulkanRecordingState recordingState; if (!m_UnityVulkan->CommandRecordingState(&recordingState, kUnityVulkanGraphicsQueueAccess_DontCare)) return; VkBufferImageCopy region; region.bufferImageHeight = 0; region.bufferRowLength = 0; region.bufferOffset = 0; region.imageOffset.x = 0; region.imageOffset.y = 0; region.imageOffset.z = 0; region.imageExtent.width = textureWidth; region.imageExtent.height = textureHeight; region.imageExtent.depth = 1; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageSubresource.mipLevel = 0; vkCmdCopyBufferToImage(recordingState.commandBuffer, m_TextureStagingBuffer.buffer, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); } void* RenderAPI_Vulkan::BeginModifyVertexBuffer(void* bufferHandle, size_t* outBufferSize) { UnityVulkanRecordingState recordingState; if (!m_UnityVulkan->CommandRecordingState(&recordingState, kUnityVulkanGraphicsQueueAccess_DontCare)) return NULL; UnityVulkanBuffer bufferInfo; if (!m_UnityVulkan->AccessBuffer(bufferHandle, 0, 0, kUnityVulkanResourceAccess_ObserveOnly, &bufferInfo)) return NULL; *outBufferSize = bufferInfo.sizeInBytes; if (!bufferInfo.memory.mapped) return NULL; // We don't want to start modifying a resource that might still be used by the GPU, // so we can use kUnityVulkanResourceAccess_Recreate to recreate it while still keeping the old one alive if it's in use. UnityVulkanBuffer recreatedBuffer; if (!m_UnityVulkan->AccessBuffer(bufferHandle, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_HOST_WRITE_BIT, kUnityVulkanResourceAccess_Recreate, &recreatedBuffer)) return NULL; // We don't care about the previous contents of this vertex buffer so we can return the mapped pointer to the new resource memory return recreatedBuffer.memory.mapped; } void RenderAPI_Vulkan::EndModifyVertexBuffer(void* bufferHandle) { // cannot do resource uploads inside renderpass, but we know that the texture modification is done first and that already ends the renderpass // m_UnityVulkan->EnsureOutsideRenderPass(); UnityVulkanBuffer buffer; if (!m_UnityVulkan->AccessBuffer(bufferHandle, 0, 0, kUnityVulkanResourceAccess_ObserveOnly, &buffer)) return; if (!(buffer.memory.flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { VkMappedMemoryRange range; range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; range.pNext = NULL; range.memory = buffer.memory.memory; range.offset = buffer.memory.offset; // size and offset also must be multiple of nonCoherentAtomSize range.size = buffer.memory.size; vkFlushMappedMemoryRanges(m_Instance.device, 1, &range); } } #endif // #if SUPPORT_VULKAN