912 lines
37 KiB
C++
Raw Normal View History

2024-11-01 16:55:46 +08:00
#include "RenderAPI.h"
#include "PlatformBase.h"
#if SUPPORT_VULKAN
#include <string.h>
#include <map>
#include <vector>
#include <math.h>
// 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<IUnityGraphicsVulkanV2>())
{
vulkanInterface->AddInterceptInitialization(InterceptVulkanInitialization, NULL, 0);
}
else if (IUnityGraphicsVulkan* vulkanInterface = interfaces->Get<IUnityGraphicsVulkan>())
{
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<VulkanBuffer> VulkanBuffers;
typedef std::map<unsigned long long, VulkanBuffers> 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<unsigned long long, VulkanBuffers> 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<IUnityGraphicsVulkan>();
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<size_t>(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, &region);
}
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