feat: add depth buffer, camera and MVP push constants

- Add Camera ECS component with view/projection matrices.
- Add Vulkan depth buffer to Swapchain: image, memory, view, render pass, framebuffers.
- Update VulkanPipeline with depth stencil testing and push-constant layout.
- Update vertex shader to use push-constant MVP matrix.
- MeshRenderer finds Camera, computes MVP per entity and pushes it.
- Program.cs creates a camera entity and keeps the cube rotating.

Build and run verified: window opens and FPS is reported.
This commit is contained in:
emil28092005
2026-06-16 19:20:29 +03:00
parent 05703f820f
commit e1ab3a14be
7 changed files with 300 additions and 35 deletions
+173 -29
View File
@@ -17,6 +17,10 @@ public sealed unsafe class Swapchain : IDisposable
private Image[] _images = null!;
private ImageView[] _imageViews = null!;
private Framebuffer[] _framebuffers = null!;
private Image _depthImage;
private DeviceMemory _depthMemory;
private ImageView _depthImageView;
private Format _depthFormat;
private SurfaceFormatKHR _surfaceFormat;
private PresentModeKHR _presentMode;
private Extent2D _extent;
@@ -31,6 +35,7 @@ public sealed unsafe class Swapchain : IDisposable
{
_context = context;
_surfaceFormat = ChooseSurfaceFormat();
_depthFormat = FindDepthFormat();
CreateRenderPass();
Recreate(1280, 720);
}
@@ -77,6 +82,8 @@ public sealed unsafe class Swapchain : IDisposable
_imageViews = new ImageView[_images.Length];
_framebuffers = new Framebuffer[_images.Length];
CreateDepthResources();
for (var i = 0; i < _images.Length; i++)
{
_imageViews[i] = CreateImageView(_images[i], _surfaceFormat.Format);
@@ -107,6 +114,113 @@ public sealed unsafe class Swapchain : IDisposable
return images;
}
private Format FindDepthFormat()
{
var candidates = new[] { Format.D32Sfloat, Format.D32SfloatS8Uint, Format.D24UnormS8Uint };
foreach (var format in candidates)
{
FormatProperties props;
_context.Vk.GetPhysicalDeviceFormatProperties(_context.PhysicalDevice, format, &props);
if ((props.OptimalTilingFeatures & FormatFeatureFlags.DepthStencilAttachmentBit) != 0)
return format;
}
throw new InvalidOperationException("No supported depth format found.");
}
private uint FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
{
PhysicalDeviceMemoryProperties memoryProperties;
_context.Vk.GetPhysicalDeviceMemoryProperties(_context.PhysicalDevice, &memoryProperties);
for (var i = 0; i < memoryProperties.MemoryTypeCount; i++)
{
if ((typeFilter & (1u << i)) != 0 &&
(memoryProperties.MemoryTypes[i].PropertyFlags & properties) == properties)
{
return (uint)i;
}
}
throw new InvalidOperationException("Failed to find suitable memory type.");
}
private void CreateDepthResources()
{
CreateDepthImage();
CreateDepthImageView();
}
private void CreateDepthImage()
{
var createInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
ImageType = ImageType.Type2D,
Extent = new Extent3D(_extent.Width, _extent.Height, 1),
MipLevels = 1,
ArrayLayers = 1,
Format = _depthFormat,
Tiling = ImageTiling.Optimal,
InitialLayout = ImageLayout.Undefined,
Usage = ImageUsageFlags.DepthStencilAttachmentBit,
Samples = SampleCountFlags.Count1Bit,
SharingMode = SharingMode.Exclusive
};
Image image;
var result = _context.Vk.CreateImage(_context.Device, &createInfo, null, &image);
if (result != Result.Success)
throw new InvalidOperationException($"vkCreateImage failed: {result}");
_depthImage = image;
MemoryRequirements memRequirements;
_context.Vk.GetImageMemoryRequirements(_context.Device, image, &memRequirements);
var memoryTypeIndex = FindMemoryType(memRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit);
var allocInfo = new MemoryAllocateInfo
{
SType = StructureType.MemoryAllocateInfo,
AllocationSize = memRequirements.Size,
MemoryTypeIndex = memoryTypeIndex
};
DeviceMemory memory;
result = _context.Vk.AllocateMemory(_context.Device, &allocInfo, null, &memory);
if (result != Result.Success)
throw new InvalidOperationException($"vkAllocateMemory failed: {result}");
_depthMemory = memory;
result = _context.Vk.BindImageMemory(_context.Device, image, memory, 0);
if (result != Result.Success)
throw new InvalidOperationException($"vkBindImageMemory failed: {result}");
}
private void CreateDepthImageView()
{
var createInfo = new ImageViewCreateInfo
{
SType = StructureType.ImageViewCreateInfo,
Image = _depthImage,
ViewType = ImageViewType.Type2D,
Format = _depthFormat,
SubresourceRange = new ImageSubresourceRange(ImageAspectFlags.DepthBit, 0, 1, 0, 1)
};
ImageView imageView;
var result = _context.Vk.CreateImageView(_context.Device, &createInfo, null, &imageView);
if (result != Result.Success)
throw new InvalidOperationException($"vkCreateImageView failed: {result}");
_depthImageView = imageView;
}
private void CleanupDepthResources()
{
if (_depthImageView.Handle != 0)
_context.Vk.DestroyImageView(_context.Device, _depthImageView, null);
if (_depthImage.Handle != 0)
_context.Vk.DestroyImage(_context.Device, _depthImage, null);
if (_depthMemory.Handle != 0)
_context.Vk.FreeMemory(_context.Device, _depthMemory, null);
}
private void CreateRenderPass()
{
var colorAttachment = new AttachmentDescription
@@ -121,17 +235,38 @@ public sealed unsafe class Swapchain : IDisposable
FinalLayout = ImageLayout.PresentSrcKhr
};
var depthAttachment = new AttachmentDescription
{
Format = _depthFormat,
Samples = SampleCountFlags.Count1Bit,
LoadOp = AttachmentLoadOp.Clear,
StoreOp = AttachmentStoreOp.DontCare,
StencilLoadOp = AttachmentLoadOp.DontCare,
StencilStoreOp = AttachmentStoreOp.DontCare,
InitialLayout = ImageLayout.Undefined,
FinalLayout = ImageLayout.DepthStencilAttachmentOptimal
};
var attachments = new[] { colorAttachment, depthAttachment };
var colorAttachmentRef = new AttachmentReference
{
Attachment = 0,
Layout = ImageLayout.ColorAttachmentOptimal
};
var depthAttachmentRef = new AttachmentReference
{
Attachment = 1,
Layout = ImageLayout.DepthStencilAttachmentOptimal
};
var subpass = new SubpassDescription
{
PipelineBindPoint = PipelineBindPoint.Graphics,
ColorAttachmentCount = 1,
PColorAttachments = &colorAttachmentRef
PColorAttachments = &colorAttachmentRef,
PDepthStencilAttachment = &depthAttachmentRef
};
var dependency = new SubpassDependency
@@ -144,22 +279,25 @@ public sealed unsafe class Swapchain : IDisposable
DstAccessMask = AccessFlags.ColorAttachmentWriteBit
};
var createInfo = new RenderPassCreateInfo
fixed (AttachmentDescription* pAttachments = attachments)
{
SType = StructureType.RenderPassCreateInfo,
AttachmentCount = 1,
PAttachments = &colorAttachment,
SubpassCount = 1,
PSubpasses = &subpass,
DependencyCount = 1,
PDependencies = &dependency
};
var createInfo = new RenderPassCreateInfo
{
SType = StructureType.RenderPassCreateInfo,
AttachmentCount = (uint)attachments.Length,
PAttachments = pAttachments,
SubpassCount = 1,
PSubpasses = &subpass,
DependencyCount = 1,
PDependencies = &dependency
};
RenderPass renderPass;
var result = _context.Vk.CreateRenderPass(_context.Device, &createInfo, null, &renderPass);
if (result != Result.Success)
throw new InvalidOperationException($"vkCreateRenderPass failed: {result}");
_renderPass = renderPass;
RenderPass renderPass;
var result = _context.Vk.CreateRenderPass(_context.Device, &createInfo, null, &renderPass);
if (result != Result.Success)
throw new InvalidOperationException($"vkCreateRenderPass failed: {result}");
_renderPass = renderPass;
}
}
private ImageView CreateImageView(Image image, Format format)
@@ -183,22 +321,26 @@ public sealed unsafe class Swapchain : IDisposable
private Framebuffer CreateFramebuffer(ImageView imageView)
{
var createInfo = new FramebufferCreateInfo
var attachments = new[] { imageView, _depthImageView };
fixed (ImageView* pAttachments = attachments)
{
SType = StructureType.FramebufferCreateInfo,
RenderPass = _renderPass,
AttachmentCount = 1,
PAttachments = &imageView,
Width = _extent.Width,
Height = _extent.Height,
Layers = 1
};
var createInfo = new FramebufferCreateInfo
{
SType = StructureType.FramebufferCreateInfo,
RenderPass = _renderPass,
AttachmentCount = (uint)attachments.Length,
PAttachments = pAttachments,
Width = _extent.Width,
Height = _extent.Height,
Layers = 1
};
Framebuffer framebuffer;
var result = _context.Vk.CreateFramebuffer(_context.Device, &createInfo, null, &framebuffer);
if (result != Result.Success)
throw new InvalidOperationException($"vkCreateFramebuffer failed: {result}");
return framebuffer;
Framebuffer framebuffer;
var result = _context.Vk.CreateFramebuffer(_context.Device, &createInfo, null, &framebuffer);
if (result != Result.Success)
throw new InvalidOperationException($"vkCreateFramebuffer failed: {result}");
return framebuffer;
}
}
private SurfaceFormatKHR ChooseSurfaceFormat()
@@ -275,6 +417,8 @@ public sealed unsafe class Swapchain : IDisposable
}
}
CleanupDepthResources();
if (_imageViews != null)
{
foreach (var view in _imageViews)