feat: depth buffer + 3D model matrix — rotating cube now looks correct

- Depth image (D32_SFLOAT) + image view created in VulkanSwapchain
- VkPipelineDepthStencilStateCreateInfo: depth test + write enabled, CompareOp=Less
- Push constant changed from float angle (4B) to mat4 model (64B)
- 3D rotation: RotateY * RotateX in renderer, applied via push constant
- Vertex shader: gl_Position = vp * model * vec4(pos, 1.0)
- Depth attachment in VkRenderingInfo + depth clear (1.0)
- Depth image layout transition (Undefined → DepthStencilAttachmentOptimal)
- New Vulkan functions: vkCreateImage, vkDestroyImage, vkGetImageMemoryRequirements, vkBindImageMemory
- New structs: VkPipelineDepthStencilStateCreateInfo, VkStencilOpState, VkImageCreateInfo
- New enums: VkCompareOp, VkImageType, VkImageTiling
- 0 validation errors
This commit is contained in:
emil28092005
2026-06-18 02:14:54 +03:00
parent fde8b0f63a
commit d6bd1807cf
9 changed files with 265 additions and 18 deletions
@@ -10,18 +10,11 @@ layout(row_major, set = 0, binding = 0) uniform CameraUBO {
mat4 vp;
};
layout(push_constant) uniform PC {
float angle;
layout(row_major, push_constant) uniform PC {
mat4 model;
} pc;
void main() {
float c = cos(pc.angle);
float s = sin(pc.angle);
vec3 rotated = vec3(
inPosition.x * c - inPosition.y * s,
inPosition.x * s + inPosition.y * c,
inPosition.z
);
gl_Position = vp * vec4(rotated, 1.0);
gl_Position = vp * pc.model * vec4(inPosition, 1.0);
fragColor = inColor;
}
Binary file not shown.
+12
View File
@@ -47,6 +47,10 @@ internal static unsafe class Vk
public delegate VkResult VkGetFenceStatus(VkDevice device, VkFence fence);
public delegate VkResult VkCreateBuffer(VkDevice device, VkBufferCreateInfo* pCreateInfo, nint pAllocator, VkBuffer* pBuffer);
public delegate void VkDestroyBuffer(VkDevice device, VkBuffer buffer, nint pAllocator);
public delegate VkResult VkCreateImage(VkDevice device, VkImageCreateInfo* pCreateInfo, nint pAllocator, VkImage* pImage);
public delegate void VkDestroyImage(VkDevice device, VkImage image, nint pAllocator);
public delegate void VkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements);
public delegate VkResult VkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, ulong memoryOffset);
public delegate VkResult VkAllocateMemory(VkDevice device, VkMemoryAllocateInfo* pAllocateInfo, nint pAllocator, VkDeviceMemory* pMemory);
public delegate void VkFreeMemory(VkDevice device, VkDeviceMemory memory, nint pAllocator);
public delegate VkResult VkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, ulong memoryOffset);
@@ -124,6 +128,10 @@ internal static unsafe class Vk
public static VkGetFenceStatus vkGetFenceStatus;
public static VkCreateBuffer vkCreateBuffer;
public static VkDestroyBuffer vkDestroyBuffer;
public static VkCreateImage vkCreateImage;
public static VkDestroyImage vkDestroyImage;
public static VkGetImageMemoryRequirements vkGetImageMemoryRequirements;
public static VkBindImageMemory vkBindImageMemory;
public static VkAllocateMemory vkAllocateMemory;
public static VkFreeMemory vkFreeMemory;
public static VkBindBufferMemory vkBindBufferMemory;
@@ -208,6 +216,10 @@ internal static unsafe class Vk
vkGetFenceStatus = LoadDev<VkGetFenceStatus>(p, "vkGetFenceStatus");
vkCreateBuffer = LoadDev<VkCreateBuffer>(p, "vkCreateBuffer");
vkDestroyBuffer = LoadDev<VkDestroyBuffer>(p, "vkDestroyBuffer");
vkCreateImage = LoadDev<VkCreateImage>(p, "vkCreateImage");
vkDestroyImage = LoadDev<VkDestroyImage>(p, "vkDestroyImage");
vkGetImageMemoryRequirements = LoadDev<VkGetImageMemoryRequirements>(p, "vkGetImageMemoryRequirements");
vkBindImageMemory = LoadDev<VkBindImageMemory>(p, "vkBindImageMemory");
vkAllocateMemory = LoadDev<VkAllocateMemory>(p, "vkAllocateMemory");
vkFreeMemory = LoadDev<VkFreeMemory>(p, "vkFreeMemory");
vkBindBufferMemory = LoadDev<VkBindBufferMemory>(p, "vkBindBufferMemory");
+28
View File
@@ -61,6 +61,7 @@ public enum VkStructureType : int
CommandBufferBeginInfo = 42,
RenderPassBeginInfo = 43,
ImageViewCreateInfo = 15,
ImageCreateInfo = 14,
SemaphoreCreateInfo = 9,
FenceCreateInfo = 8,
SwapchainCreateInfoKHR = 1000001000,
@@ -207,6 +208,31 @@ public enum VkFrontFace : int
Clockwise = 1,
}
public enum VkCompareOp : int
{
Never = 0,
Less = 1,
Equal = 2,
LessOrEqual = 3,
Greater = 4,
NotEqual = 5,
GreaterOrEqual = 6,
Always = 7,
}
public enum VkImageType : int
{
Type1D = 0,
Type2D = 1,
Type3D = 2,
}
public enum VkImageTiling : int
{
Optimal = 0,
Linear = 1,
}
public enum VkBlendFactor : int
{
Zero = 0,
@@ -283,6 +309,8 @@ public enum VkPipelineStageFlags2 : ulong
public enum VkAccessFlags2 : ulong
{
None = 0,
DepthStencilAttachmentWrite = 0x00000400,
DepthStencilAttachmentRead = 0x00000200,
ColorAttachmentRead = 0x00000080,
ColorAttachmentWrite = 0x00000100,
TransferRead = 0x00000800,
+14 -2
View File
@@ -14,7 +14,7 @@ internal sealed unsafe class VulkanPipeline : IDisposable
private readonly VkDevice _device;
private bool _disposed;
public VulkanPipeline(VkDevice device, VkFormat colorFormat, byte[] vertSpv, byte[] fragSpv)
public VulkanPipeline(VkDevice device, VkFormat colorFormat, VkFormat depthFormat, byte[] vertSpv, byte[] fragSpv)
{
_device = device;
VertModule = CreateShaderModule(vertSpv);
@@ -115,6 +115,16 @@ internal sealed unsafe class VulkanPipeline : IDisposable
sampleShadingEnable = VkBool32.False,
};
var depthStencilState = new VkPipelineDepthStencilStateCreateInfo
{
sType = VkStructureType.PipelineDepthStencilStateCreateInfo,
depthTestEnable = VkBool32.True,
depthWriteEnable = VkBool32.True,
depthCompareOp = VkCompareOp.Less,
depthBoundsTestEnable = VkBool32.False,
stencilTestEnable = VkBool32.False,
};
var blendAttachment = new VkPipelineColorBlendAttachmentState
{
blendEnable = VkBool32.False,
@@ -144,7 +154,7 @@ internal sealed unsafe class VulkanPipeline : IDisposable
{
stageFlags = VkShaderStageFlags.Vertex,
offset = 0,
size = 4,
size = 64,
};
var descLayout = DescriptorSetLayout;
@@ -169,6 +179,7 @@ internal sealed unsafe class VulkanPipeline : IDisposable
sType = VkStructureType.PipelineRenderingCreateInfo,
colorAttachmentCount = 1,
pColorAttachmentFormats = &colorFormat,
depthAttachmentFormat = depthFormat,
};
var pipelineInfo = new VkGraphicsPipelineCreateInfo
@@ -182,6 +193,7 @@ internal sealed unsafe class VulkanPipeline : IDisposable
pViewportState = &viewportState,
pRasterizationState = &rasterizationState,
pMultisampleState = &multisampleState,
pDepthStencilState = &depthStencilState,
pColorBlendState = &colorBlendState,
pDynamicState = &dynamicState,
layout = PipelineLayout,
@@ -23,7 +23,7 @@ internal sealed class VulkanRenderContext : IRenderContext
};
_swapchain = new VulkanSwapchain(_ctx.Device, _ctx.PhysicalDevice, _ctx.Surface,
surfaceFormat, window.Width, window.Height);
surfaceFormat, window.Width, window.Height, _ctx);
}
public IRenderer CreateRenderer()
+57 -2
View File
@@ -34,7 +34,7 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
var vertSpv = LoadShader("Shaders/triangle.vert.spv");
var fragSpv = LoadShader("Shaders/triangle.frag.spv");
_pipeline = new VulkanPipeline(ctx.Device, swapchain.Format, vertSpv, fragSpv);
_pipeline = new VulkanPipeline(ctx.Device, swapchain.Format, swapchain.DepthFormat, vertSpv, fragSpv);
_frameResources = new VulkanFrameResources(ctx.Device, ctx.GraphicsQueueFamilyIndex,
swapchain.ImageCount, ctx, _pipeline.DescriptorSetLayout);
@@ -109,11 +109,21 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
0, 0,
0x400, 0x100);
TransitionImageLayoutDepth(cmd, _swapchain.DepthImage,
VkImageLayout.Undefined, VkImageLayout.DepthStencilAttachmentOptimal,
0, 0,
0x100, 0x200);
var clearValue = new VkClearValue
{
Color = new VkClearColorValue { Float0 = 0.02f, Float1 = 0.02f, Float2 = 0.02f, Float3 = 1.0f },
};
var depthClearValue = new VkClearValue
{
DepthStencil = new VkClearDepthStencilValue { Depth = 1.0f, Stencil = 0 },
};
var colorAttachment = new VkRenderingAttachmentInfo
{
sType = VkStructureType.RenderingAttachmentInfo,
@@ -124,6 +134,16 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
clearValue = clearValue,
};
var depthAttachment = new VkRenderingAttachmentInfo
{
sType = VkStructureType.RenderingAttachmentInfo,
imageView = _swapchain.DepthImageView,
imageLayout = VkImageLayout.DepthStencilAttachmentOptimal,
loadOp = VkAttachmentLoadOp.Clear,
storeOp = VkAttachmentStoreOp.Store,
clearValue = depthClearValue,
};
var renderingInfo = new VkRenderingInfo
{
sType = VkStructureType.RenderingInfo,
@@ -135,6 +155,7 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
layerCount = 1,
colorAttachmentCount = 1,
pColorAttachments = &colorAttachment,
pDepthAttachment = &depthAttachment,
};
Vk.vkCmdBeginRendering(cmd, &renderingInfo);
@@ -168,7 +189,8 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
Vk.vkCmdBindIndexBuffer(cmd, _indexBuffer.Buffer, 0, 0);
var angle = _totalTime;
Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Vertex, 0, 4, &angle);
var model = Matrix4x4.CreateRotationY(angle) * Matrix4x4.CreateRotationX(angle * 0.5f);
Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Vertex, 0, 64, &model);
Vk.vkCmdDrawIndexed(cmd, _indexCount, 1, 0, 0, 0);
@@ -282,6 +304,39 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
Vk.vkCmdPipelineBarrier2(cmd, &depInfo);
}
private static void TransitionImageLayoutDepth(VkCommandBuffer cmd, VkImage image,
VkImageLayout oldLayout, VkImageLayout newLayout,
ulong srcStage, ulong srcAccess,
ulong dstStage, ulong dstAccess)
{
var barrier = new VkImageMemoryBarrier2
{
sType = VkStructureType.ImageMemoryBarrier2,
srcStageMask = srcStage,
srcAccessMask = srcAccess,
dstStageMask = dstStage,
dstAccessMask = dstAccess,
oldLayout = oldLayout,
newLayout = newLayout,
image = image,
subresourceRange = new VkImageSubresourceRange
{
AspectMask = VkImageAspectFlags.Depth,
LevelCount = 1,
LayerCount = 1,
},
};
var depInfo = new VkDependencyInfo
{
sType = VkStructureType.DependencyInfo,
imageMemoryBarrierCount = 1,
pImageMemoryBarriers = &barrier,
};
Vk.vkCmdPipelineBarrier2(cmd, &depInfo);
}
private static byte[] LoadShader(string path)
{
if (!File.Exists(path))
+51 -2
View File
@@ -671,7 +671,7 @@ public unsafe struct VkGraphicsPipelineCreateInfo
public VkPipelineViewportStateCreateInfo* pViewportState;
public VkPipelineRasterizationStateCreateInfo* pRasterizationState;
public VkPipelineMultisampleStateCreateInfo* pMultisampleState;
public nint pDepthStencilState;
public VkPipelineDepthStencilStateCreateInfo* pDepthStencilState;
public VkPipelineColorBlendStateCreateInfo* pColorBlendState;
public VkPipelineDynamicStateCreateInfo* pDynamicState;
public VkPipelineLayout layout;
@@ -792,7 +792,7 @@ public unsafe struct VkRenderingInfo
public uint viewMask;
public uint colorAttachmentCount;
public VkRenderingAttachmentInfo* pColorAttachments;
public nint pDepthAttachment;
public VkRenderingAttachmentInfo* pDepthAttachment;
public nint pStencilAttachment;
}
@@ -908,6 +908,55 @@ public unsafe struct VkDebugUtilsMessengerCallbackDataEXT
public nint pObjects;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkStencilOpState
{
public int failOp;
public int passOp;
public int depthFailOp;
public int compareOp;
public uint compareMask;
public uint writeMask;
public uint reference;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPipelineDepthStencilStateCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public VkBool32 depthTestEnable;
public VkBool32 depthWriteEnable;
public VkCompareOp depthCompareOp;
public VkBool32 depthBoundsTestEnable;
public VkBool32 stencilTestEnable;
public VkStencilOpState front;
public VkStencilOpState back;
public float minDepthBounds;
public float maxDepthBounds;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkImageCreateInfo
{
public VkStructureType sType;
public nint pNext;
public uint flags;
public VkImageType imageType;
public VkFormat format;
public VkExtent3D extent;
public uint mipLevels;
public uint arrayLayers;
public VkSampleCountFlags samples;
public VkImageTiling tiling;
public VkImageUsageFlags usage;
public VkSharingMode sharingMode;
public uint queueFamilyIndexCount;
public uint* pQueueFamilyIndices;
public VkImageLayout initialLayout;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct VkPushConstantRange
{
+99 -1
View File
@@ -11,19 +11,26 @@ internal sealed unsafe class VulkanSwapchain : IDisposable
public VkExtent2D Extent;
public uint ImageCount;
public VkImage DepthImage;
public VkDeviceMemory DepthImageMemory;
public VkImageView DepthImageView;
public VkFormat DepthFormat = VkFormat.D32Sfloat;
private readonly VkDevice _device;
private readonly VkPhysicalDevice _physicalDevice;
private readonly VkSurfaceKHR _surface;
private readonly VkSurfaceFormatKHR _surfaceFormat;
private readonly VulkanContext _ctx;
private bool _disposed;
public VulkanSwapchain(VkDevice device, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
VkSurfaceFormatKHR surfaceFormat, int width, int height)
VkSurfaceFormatKHR surfaceFormat, int width, int height, VulkanContext ctx)
{
_device = device;
_physicalDevice = physicalDevice;
_surface = surface;
_surfaceFormat = surfaceFormat;
_ctx = ctx;
Create(width, height);
}
@@ -132,6 +139,81 @@ internal sealed unsafe class VulkanSwapchain : IDisposable
}
Console.WriteLine($"[Vulkan] Swapchain: {actualCount} images, {Extent.Width}x{Extent.Height}, format={Format}");
CreateDepthResources();
}
private void CreateDepthResources()
{
var imageInfo = new VkImageCreateInfo
{
sType = VkStructureType.ImageCreateInfo,
imageType = VkImageType.Type2D,
format = DepthFormat,
extent = new VkExtent3D { Width = Extent.Width, Height = Extent.Height, Depth = 1 },
mipLevels = 1,
arrayLayers = 1,
samples = VkSampleCountFlags.Count1,
tiling = VkImageTiling.Optimal,
usage = VkImageUsageFlags.DepthStencilAttachment,
sharingMode = VkSharingMode.Exclusive,
initialLayout = VkImageLayout.Undefined,
};
var depthImg = VkImage.Null;
var result = Vk.vkCreateImage(_device, &imageInfo, 0, &depthImg);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateImage (depth) failed: {result}");
DepthImage = depthImg;
var reqs = new VkMemoryRequirements();
Vk.vkGetImageMemoryRequirements(_device, DepthImage, &reqs);
var memTypeIndex = _ctx.FindMemoryType(reqs.memoryTypeBits, VkMemoryPropertyFlags.DeviceLocal);
var allocInfo = new VkMemoryAllocateInfo
{
sType = VkStructureType.MemoryAllocateInfo,
allocationSize = reqs.size,
memoryTypeIndex = memTypeIndex,
};
var depthMem = VkDeviceMemory.Null;
result = Vk.vkAllocateMemory(_device, &allocInfo, 0, &depthMem);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkAllocateMemory (depth) failed: {result}");
DepthImageMemory = depthMem;
Vk.vkBindImageMemory(_device, DepthImage, DepthImageMemory, 0);
var viewInfo = new VkImageViewCreateInfo
{
sType = VkStructureType.ImageViewCreateInfo,
image = DepthImage,
viewType = VkImageViewType.Type2D,
format = DepthFormat,
components = new VkComponentMapping
{
R = VkComponentSwizzle.Identity,
G = VkComponentSwizzle.Identity,
B = VkComponentSwizzle.Identity,
A = VkComponentSwizzle.Identity,
},
subresourceRange = new VkImageSubresourceRange
{
AspectMask = VkImageAspectFlags.Depth,
BaseMipLevel = 0,
LevelCount = 1,
BaseArrayLayer = 0,
LayerCount = 1,
},
};
var depthView = VkImageView.Null;
result = Vk.vkCreateImageView(_device, &viewInfo, 0, &depthView);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateImageView (depth) failed: {result}");
DepthImageView = depthView;
}
public void Recreate(int width, int height)
@@ -143,6 +225,22 @@ internal sealed unsafe class VulkanSwapchain : IDisposable
private void Cleanup()
{
if (DepthImageView.Handle != 0)
{
Vk.vkDestroyImageView(_device, DepthImageView, 0);
DepthImageView = VkImageView.Null;
}
if (DepthImage.Handle != 0)
{
Vk.vkDestroyImage(_device, DepthImage, 0);
DepthImage = VkImage.Null;
}
if (DepthImageMemory.Handle != 0)
{
Vk.vkFreeMemory(_device, DepthImageMemory, 0);
DepthImageMemory = VkDeviceMemory.Null;
}
for (int i = 0; i < ImageViews.Length; i++)
{
if (ImageViews[i].Handle != 0)