feat: rebuild Vulkan renderer from scratch — pure P/Invoke triangle (Vulkan 1.3)
- Complete rewrite of Engine.Graphics.Vulkan with pure P/Invoke (no wrapper libs) - Vulkan 1.3: dynamic rendering (vkCmdBeginRendering/vkCmdEndRendering), synchronization2 (vkQueueSubmit2, vkCmdPipelineBarrier2) - Split types into VulkanHandles.cs, VulkanEnums.cs, VulkanStructs.cs - Staging buffer → device-local vertex buffer pattern - Correct swapchain semaphore indexing (per-image, not per-frame-in-flight) - VK_EXT_debug_utils debug messenger with validation layer fallback - Dynamic viewport/scissor (no pipeline recreation on resize) - Simplified Program.cs to triangle-only rendering - Removed old Silk.NET renderer, ImGui, PBR shaders, screenshot code - Updated VULKAN_IMPLEMENTATION_PLAN.md with full architecture decisions
This commit is contained in:
@@ -1,279 +1,207 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Engine.Core;
|
||||
|
||||
namespace Engine.Graphics.Vulkan;
|
||||
|
||||
public sealed unsafe class VulkanPipeline : IDisposable
|
||||
internal sealed unsafe class VulkanPipeline : IDisposable
|
||||
{
|
||||
public VkPipelineLayout PipelineLayout;
|
||||
public VkPipeline Pipeline;
|
||||
public VkDescriptorSetLayout DescriptorSetLayout;
|
||||
public VkShaderModule VertexShader;
|
||||
public VkShaderModule FragmentShader;
|
||||
public VkShaderModule VertModule;
|
||||
public VkShaderModule FragModule;
|
||||
|
||||
private readonly VulkanContext _ctx;
|
||||
private readonly VkDevice _device;
|
||||
private bool _disposed;
|
||||
|
||||
public const int PushConstantSize = 144;
|
||||
public const int FrameUboSize = 16 + 16 + 16 * 4 * 8;
|
||||
|
||||
public VulkanPipeline(VulkanContext ctx, VkRenderPass renderPass)
|
||||
public VulkanPipeline(VkDevice device, VkFormat colorFormat, byte[] vertSpv, byte[] fragSpv)
|
||||
{
|
||||
_ctx = ctx;
|
||||
Create(renderPass);
|
||||
}
|
||||
_device = device;
|
||||
VertModule = CreateShaderModule(vertSpv);
|
||||
FragModule = CreateShaderModule(fragSpv);
|
||||
|
||||
private unsafe void Create(VkRenderPass renderPass)
|
||||
{
|
||||
Console.WriteLine("[Vulkan] Loading shaders...");
|
||||
VertexShader = CreateShaderModule("Shaders/vertex.spv");
|
||||
FragmentShader = CreateShaderModule("Shaders/fragment.spv");
|
||||
Console.WriteLine("[Vulkan] Shaders loaded.");
|
||||
|
||||
var mainName = Vk.AllocUtf8("main");
|
||||
|
||||
var stages = new VkPipelineShaderStageCreateInfo[2];
|
||||
stages[0] = new VkPipelineShaderStageCreateInfo
|
||||
fixed (byte* pName = "main\0"u8)
|
||||
{
|
||||
sType = VkStructureType.PipelineShaderStageCreateInfo,
|
||||
pNext = null,
|
||||
flags = 0,
|
||||
stage = VkShaderStageFlags.Vertex,
|
||||
module = VertexShader,
|
||||
pName = mainName,
|
||||
pSpecializationInfo = null
|
||||
};
|
||||
stages[1] = new VkPipelineShaderStageCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineShaderStageCreateInfo,
|
||||
pNext = null,
|
||||
flags = 0,
|
||||
stage = VkShaderStageFlags.Fragment,
|
||||
module = FragmentShader,
|
||||
pName = mainName,
|
||||
pSpecializationInfo = null
|
||||
};
|
||||
var stages = stackalloc VkPipelineShaderStageCreateInfo[2];
|
||||
stages[0] = new VkPipelineShaderStageCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineShaderStageCreateInfo,
|
||||
stage = VkShaderStageFlags.Vertex,
|
||||
module = VertModule,
|
||||
pName = pName,
|
||||
};
|
||||
stages[1] = new VkPipelineShaderStageCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineShaderStageCreateInfo,
|
||||
stage = VkShaderStageFlags.Fragment,
|
||||
module = FragModule,
|
||||
pName = pName,
|
||||
};
|
||||
|
||||
var bindingDesc = new VkVertexInputBindingDescription
|
||||
{
|
||||
binding = 0,
|
||||
stride = 36,
|
||||
inputRate = 0
|
||||
};
|
||||
var bindings = stackalloc VkVertexInputBindingDescription[1];
|
||||
bindings[0] = new VkVertexInputBindingDescription
|
||||
{
|
||||
binding = 0,
|
||||
stride = (uint)sizeof(Vertex),
|
||||
inputRate = VkVertexInputRate.Vertex,
|
||||
};
|
||||
|
||||
var attrDescs = stackalloc VkVertexInputAttributeDescription[3];
|
||||
attrDescs[0] = new VkVertexInputAttributeDescription { location = 0, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 0 };
|
||||
attrDescs[1] = new VkVertexInputAttributeDescription { location = 1, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 12 };
|
||||
attrDescs[2] = new VkVertexInputAttributeDescription { location = 2, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 24 };
|
||||
var attributes = stackalloc VkVertexInputAttributeDescription[3];
|
||||
attributes[0] = new VkVertexInputAttributeDescription
|
||||
{
|
||||
location = 0,
|
||||
binding = 0,
|
||||
format = VkFormat.R32G32B32Sfloat,
|
||||
offset = 0,
|
||||
};
|
||||
attributes[1] = new VkVertexInputAttributeDescription
|
||||
{
|
||||
location = 1,
|
||||
binding = 0,
|
||||
format = VkFormat.R32G32B32Sfloat,
|
||||
offset = 12,
|
||||
};
|
||||
attributes[2] = new VkVertexInputAttributeDescription
|
||||
{
|
||||
location = 2,
|
||||
binding = 0,
|
||||
format = VkFormat.R32G32B32Sfloat,
|
||||
offset = 24,
|
||||
};
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertexInputState;
|
||||
vertexInputState.sType = VkStructureType.PipelineVertexInputStateCreateInfo;
|
||||
vertexInputState.pNext = null;
|
||||
vertexInputState.flags = 0;
|
||||
vertexInputState.vertexBindingDescriptionCount = 1;
|
||||
vertexInputState.pVertexBindingDescriptions = &bindingDesc;
|
||||
vertexInputState.vertexAttributeDescriptionCount = 3;
|
||||
vertexInputState.pVertexAttributeDescriptions = attrDescs;
|
||||
var vertexInputState = new VkPipelineVertexInputStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineVertexInputStateCreateInfo,
|
||||
vertexBindingDescriptionCount = 1,
|
||||
pVertexBindingDescriptions = bindings,
|
||||
vertexAttributeDescriptionCount = 3,
|
||||
pVertexAttributeDescriptions = attributes,
|
||||
};
|
||||
|
||||
var inputAssemblyState = new VkPipelineInputAssemblyStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineInputAssemblyStateCreateInfo,
|
||||
pNext = null,
|
||||
flags = 0,
|
||||
topology = VkPrimitiveTopology.TriangleList,
|
||||
primitiveRestartEnable = 0
|
||||
};
|
||||
var inputAssemblyState = new VkPipelineInputAssemblyStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineInputAssemblyStateCreateInfo,
|
||||
topology = VkPrimitiveTopology.TriangleList,
|
||||
primitiveRestartEnable = VkBool32.False,
|
||||
};
|
||||
|
||||
var viewport = new VkViewport { x = 0, y = 0, width = 1280, height = 720, minDepth = 0, maxDepth = 1 };
|
||||
var scissor = new VkRect2D { offset = new VkOffset2D { x = 0, y = 0 }, extent = new VkExtent2D { width = 1280, height = 720 } };
|
||||
var viewportState = new VkPipelineViewportStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineViewportStateCreateInfo,
|
||||
viewportCount = 1,
|
||||
pViewports = null,
|
||||
scissorCount = 1,
|
||||
pScissors = null,
|
||||
};
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewportState;
|
||||
viewportState.sType = VkStructureType.PipelineViewportStateCreateInfo;
|
||||
viewportState.pNext = null;
|
||||
viewportState.flags = 0;
|
||||
viewportState.viewportCount = 1;
|
||||
viewportState.pViewports = &viewport;
|
||||
viewportState.scissorCount = 1;
|
||||
viewportState.pScissors = &scissor;
|
||||
var rasterizationState = new VkPipelineRasterizationStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineRasterizationStateCreateInfo,
|
||||
depthClampEnable = VkBool32.False,
|
||||
rasterizerDiscardEnable = VkBool32.False,
|
||||
polygonMode = VkPolygonMode.Fill,
|
||||
cullMode = VkCullModeFlags.None,
|
||||
frontFace = VkFrontFace.CounterClockwise,
|
||||
depthBiasEnable = VkBool32.False,
|
||||
lineWidth = 1.0f,
|
||||
};
|
||||
|
||||
var rasterizationState = new VkPipelineRasterizationStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineRasterizationStateCreateInfo,
|
||||
pNext = null,
|
||||
flags = 0,
|
||||
depthClampEnable = 0,
|
||||
rasterizerDiscardEnable = 0,
|
||||
polygonMode = VkPolygonMode.Fill,
|
||||
cullMode = VkCullModeFlags.None,
|
||||
frontFace = VkFrontFace.Clockwise,
|
||||
depthBiasEnable = 0,
|
||||
depthBiasConstantFactor = 0,
|
||||
depthBiasClamp = 0,
|
||||
depthBiasSlopeFactor = 0,
|
||||
lineWidth = 1.0f
|
||||
};
|
||||
var multisampleState = new VkPipelineMultisampleStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineMultisampleStateCreateInfo,
|
||||
rasterizationSamples = VkSampleCountFlags.Count1,
|
||||
sampleShadingEnable = VkBool32.False,
|
||||
};
|
||||
|
||||
var multisampleState = new VkPipelineMultisampleStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineMultisampleStateCreateInfo,
|
||||
pNext = null,
|
||||
flags = 0,
|
||||
rasterizationSamples = VkSampleCountFlags.One,
|
||||
sampleShadingEnable = 0,
|
||||
minSampleShading = 0,
|
||||
pSampleMask = null,
|
||||
alphaToCoverageEnable = 0,
|
||||
alphaToOneEnable = 0
|
||||
};
|
||||
var blendAttachment = new VkPipelineColorBlendAttachmentState
|
||||
{
|
||||
blendEnable = VkBool32.False,
|
||||
colorWriteMask = VkColorComponentFlags.R | VkColorComponentFlags.G | VkColorComponentFlags.B | VkColorComponentFlags.A,
|
||||
};
|
||||
|
||||
var depthStencilState = new VkPipelineDepthStencilStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineDepthStencilStateCreateInfo,
|
||||
pNext = null,
|
||||
flags = 0,
|
||||
depthTestEnable = 1,
|
||||
depthWriteEnable = 1,
|
||||
depthCompareOp = VkCompareOp.Less,
|
||||
depthBoundsTestEnable = 0,
|
||||
stencilTestEnable = 0,
|
||||
front = new VkStencilOpState(),
|
||||
back = new VkStencilOpState(),
|
||||
minDepthBounds = 0,
|
||||
maxDepthBounds = 1
|
||||
};
|
||||
var colorBlendState = new VkPipelineColorBlendStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineColorBlendStateCreateInfo,
|
||||
logicOpEnable = VkBool32.False,
|
||||
attachmentCount = 1,
|
||||
pAttachments = &blendAttachment,
|
||||
};
|
||||
|
||||
var blendAttachment = new VkPipelineColorBlendAttachmentState
|
||||
{
|
||||
blendEnable = 0,
|
||||
srcColorBlendFactor = VkBlendFactor.One,
|
||||
dstColorBlendFactor = VkBlendFactor.Zero,
|
||||
colorBlendOp = VkBlendOp.Add,
|
||||
srcAlphaBlendFactor = VkBlendFactor.One,
|
||||
dstAlphaBlendFactor = VkBlendFactor.Zero,
|
||||
alphaBlendOp = VkBlendOp.Add,
|
||||
colorWriteMask = VkColorComponentFlags.R | VkColorComponentFlags.G | VkColorComponentFlags.B | VkColorComponentFlags.A
|
||||
};
|
||||
var dynamicStates = stackalloc VkDynamicState[2];
|
||||
dynamicStates[0] = VkDynamicState.Viewport;
|
||||
dynamicStates[1] = VkDynamicState.Scissor;
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo colorBlendState = default;
|
||||
colorBlendState.sType = VkStructureType.PipelineColorBlendStateCreateInfo;
|
||||
colorBlendState.pNext = null;
|
||||
colorBlendState.flags = 0;
|
||||
colorBlendState.logicOpEnable = 0;
|
||||
colorBlendState.logicOp = 0;
|
||||
colorBlendState.attachmentCount = 1;
|
||||
colorBlendState.pAttachments = &blendAttachment;
|
||||
var dynamicState = new VkPipelineDynamicStateCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineDynamicStateCreateInfo,
|
||||
dynamicStateCount = 2,
|
||||
pDynamicStates = dynamicStates,
|
||||
};
|
||||
|
||||
var dynamicStates = stackalloc VkDynamicState[2];
|
||||
dynamicStates[0] = VkDynamicState.Viewport;
|
||||
dynamicStates[1] = VkDynamicState.Scissor;
|
||||
var layoutInfo = new VkPipelineLayoutCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineLayoutCreateInfo,
|
||||
setLayoutCount = 0,
|
||||
pushConstantRangeCount = 0,
|
||||
};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamicState = default;
|
||||
dynamicState.sType = VkStructureType.PipelineDynamicStateCreateInfo;
|
||||
dynamicState.dynamicStateCount = 2;
|
||||
dynamicState.pDynamicStates = dynamicStates;
|
||||
fixed (VkPipelineLayout* layoutPtr = &PipelineLayout)
|
||||
{
|
||||
var result = Vk.vkCreatePipelineLayout(_device, &layoutInfo, 0, layoutPtr);
|
||||
if (result != VkResult.Success)
|
||||
throw new InvalidOperationException($"vkCreatePipelineLayout failed: {result}");
|
||||
}
|
||||
|
||||
var uboBinding = new VkDescriptorSetLayoutBinding
|
||||
{
|
||||
binding = 0,
|
||||
descriptorType = VkDescriptorType.UniformBuffer,
|
||||
descriptorCount = 1,
|
||||
stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment,
|
||||
pImmutableSamplers = null
|
||||
};
|
||||
var renderingInfo = new VkPipelineRenderingCreateInfo
|
||||
{
|
||||
sType = VkStructureType.PipelineRenderingCreateInfo,
|
||||
colorAttachmentCount = 1,
|
||||
pColorAttachmentFormats = &colorFormat,
|
||||
};
|
||||
|
||||
Console.WriteLine("[Vulkan] Creating descriptor set layout...");
|
||||
VkDescriptorSetLayoutCreateInfo dsLayoutInfo;
|
||||
dsLayoutInfo.sType = VkStructureType.DescriptorSetLayoutCreateInfo;
|
||||
dsLayoutInfo.pNext = null;
|
||||
dsLayoutInfo.flags = 0;
|
||||
dsLayoutInfo.bindingCount = 1;
|
||||
dsLayoutInfo.pBindings = &uboBinding;
|
||||
var pipelineInfo = new VkGraphicsPipelineCreateInfo
|
||||
{
|
||||
sType = VkStructureType.GraphicsPipelineCreateInfo,
|
||||
pNext = (nint)(&renderingInfo),
|
||||
stageCount = 2,
|
||||
pStages = stages,
|
||||
pVertexInputState = &vertexInputState,
|
||||
pInputAssemblyState = &inputAssemblyState,
|
||||
pViewportState = &viewportState,
|
||||
pRasterizationState = &rasterizationState,
|
||||
pMultisampleState = &multisampleState,
|
||||
pColorBlendState = &colorBlendState,
|
||||
pDynamicState = &dynamicState,
|
||||
layout = PipelineLayout,
|
||||
renderPass = new VkRenderPass { Handle = 0 },
|
||||
subpass = 0,
|
||||
};
|
||||
|
||||
VkDescriptorSetLayout dsLayout;
|
||||
VkResult result = Vk.vkCreateDescriptorSetLayout(_ctx.Device, &dsLayoutInfo, null, &dsLayout);
|
||||
Vk.CheckResult(result, "vkCreateDescriptorSetLayout");
|
||||
DescriptorSetLayout = dsLayout;
|
||||
Console.WriteLine("[Vulkan] Descriptor set layout created.");
|
||||
|
||||
var pushConstantRange = new VkPushConstantRange
|
||||
{
|
||||
stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment,
|
||||
offset = 0,
|
||||
size = PushConstantSize
|
||||
};
|
||||
|
||||
Console.WriteLine("[Vulkan] Creating pipeline layout...");
|
||||
VkPipelineLayoutCreateInfo layoutInfo;
|
||||
layoutInfo.sType = VkStructureType.PipelineLayoutCreateInfo;
|
||||
layoutInfo.pNext = null;
|
||||
layoutInfo.flags = 0;
|
||||
layoutInfo.setLayoutCount = 1;
|
||||
layoutInfo.pSetLayouts = &dsLayout;
|
||||
layoutInfo.pushConstantRangeCount = 1;
|
||||
layoutInfo.pPushConstantRanges = &pushConstantRange;
|
||||
|
||||
VkPipelineLayout pipeLayout;
|
||||
result = Vk.vkCreatePipelineLayout(_ctx.Device, &layoutInfo, null, &pipeLayout);
|
||||
Vk.CheckResult(result, "vkCreatePipelineLayout");
|
||||
PipelineLayout = pipeLayout;
|
||||
Console.WriteLine("[Vulkan] Pipeline layout created.");
|
||||
|
||||
Console.WriteLine("[Vulkan] Creating graphics pipeline...");
|
||||
fixed (VkPipelineShaderStageCreateInfo* pStages = stages)
|
||||
{
|
||||
VkGraphicsPipelineCreateInfo pipelineInfo;
|
||||
pipelineInfo.sType = VkStructureType.GraphicsPipelineCreateInfo;
|
||||
pipelineInfo.pNext = null;
|
||||
pipelineInfo.flags = 0;
|
||||
pipelineInfo.stageCount = 2;
|
||||
pipelineInfo.pStages = pStages;
|
||||
pipelineInfo.pVertexInputState = &vertexInputState;
|
||||
pipelineInfo.pInputAssemblyState = &inputAssemblyState;
|
||||
pipelineInfo.pTessellationState = null;
|
||||
pipelineInfo.pViewportState = &viewportState;
|
||||
pipelineInfo.pRasterizationState = &rasterizationState;
|
||||
pipelineInfo.pMultisampleState = &multisampleState;
|
||||
pipelineInfo.pDepthStencilState = &depthStencilState;
|
||||
pipelineInfo.pColorBlendState = &colorBlendState;
|
||||
pipelineInfo.pDynamicState = &dynamicState;
|
||||
pipelineInfo.layout = pipeLayout;
|
||||
pipelineInfo.renderPass = renderPass;
|
||||
pipelineInfo.subpass = 0;
|
||||
pipelineInfo.basePipelineHandle = default;
|
||||
pipelineInfo.basePipelineIndex = -1;
|
||||
|
||||
VkPipeline pipe;
|
||||
result = Vk.vkCreateGraphicsPipelines(_ctx.Device, 0, 1, &pipelineInfo, null, &pipe);
|
||||
Vk.CheckResult(result, "vkCreateGraphicsPipelines");
|
||||
Pipeline = pipe;
|
||||
fixed (VkPipeline* pipePtr = &Pipeline)
|
||||
{
|
||||
var result = Vk.vkCreateGraphicsPipelines(_device, 0, 1, &pipelineInfo, 0, pipePtr);
|
||||
if (result != VkResult.Success)
|
||||
throw new InvalidOperationException($"vkCreateGraphicsPipelines failed: {result}");
|
||||
}
|
||||
}
|
||||
|
||||
Vk.FreeUtf8(mainName);
|
||||
|
||||
Console.WriteLine("[Vulkan] Graphics pipeline created.");
|
||||
Console.WriteLine("[Vulkan] Graphics pipeline created (dynamic rendering)");
|
||||
}
|
||||
|
||||
private VkShaderModule CreateShaderModule(string path)
|
||||
private VkShaderModule CreateShaderModule(byte[] spv)
|
||||
{
|
||||
var fullPath = Path.Combine(AppContext.BaseDirectory, path);
|
||||
if (!File.Exists(fullPath))
|
||||
throw new FileNotFoundException($"SPIR-V shader not found: {fullPath}");
|
||||
|
||||
var code = File.ReadAllBytes(fullPath);
|
||||
var codeSize = (ulong)code.Length;
|
||||
|
||||
fixed (byte* pCode = code)
|
||||
fixed (byte* pCode = spv)
|
||||
{
|
||||
VkShaderModuleCreateInfo createInfo;
|
||||
createInfo.sType = VkStructureType.ShaderModuleCreateInfo;
|
||||
createInfo.pNext = null;
|
||||
createInfo.flags = 0;
|
||||
createInfo.codeSize = codeSize;
|
||||
createInfo.pCode = (uint*)pCode;
|
||||
var info = new VkShaderModuleCreateInfo
|
||||
{
|
||||
sType = VkStructureType.ShaderModuleCreateInfo,
|
||||
codeSize = (nuint)spv.Length,
|
||||
pCode = (uint*)pCode,
|
||||
};
|
||||
|
||||
VkShaderModule module;
|
||||
var result = Vk.vkCreateShaderModule(_ctx.Device, &createInfo, null, &module);
|
||||
Vk.CheckResult(result, $"vkCreateShaderModule ({path})");
|
||||
var module = VkShaderModule.Null;
|
||||
var result = Vk.vkCreateShaderModule(_device, &info, 0, &module);
|
||||
if (result != VkResult.Success)
|
||||
throw new InvalidOperationException($"vkCreateShaderModule failed: {result}");
|
||||
return module;
|
||||
}
|
||||
}
|
||||
@@ -283,10 +211,9 @@ public sealed unsafe class VulkanPipeline : IDisposable
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
if (Pipeline.Value != 0) Vk.vkDestroyPipeline(_ctx.Device, Pipeline, null);
|
||||
if (PipelineLayout.Value != 0) Vk.vkDestroyPipelineLayout(_ctx.Device, PipelineLayout, null);
|
||||
if (DescriptorSetLayout.Value != 0) Vk.vkDestroyDescriptorSetLayout(_ctx.Device, DescriptorSetLayout, null);
|
||||
if (VertexShader.Value != 0) Vk.vkDestroyShaderModule(_ctx.Device, VertexShader, null);
|
||||
if (FragmentShader.Value != 0) Vk.vkDestroyShaderModule(_ctx.Device, FragmentShader, null);
|
||||
if (Pipeline.Handle != 0) Vk.vkDestroyPipeline(_device, Pipeline, 0);
|
||||
if (PipelineLayout.Handle != 0) Vk.vkDestroyPipelineLayout(_device, PipelineLayout, 0);
|
||||
if (FragModule.Handle != 0) Vk.vkDestroyShaderModule(_device, FragModule, 0);
|
||||
if (VertModule.Handle != 0) Vk.vkDestroyShaderModule(_device, VertModule, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user