diff --git a/src/Engine.Graphics.Vulkan/Shaders/shadow.frag b/src/Engine.Graphics.Vulkan/Shaders/shadow.frag new file mode 100644 index 0000000..f0bcb49 --- /dev/null +++ b/src/Engine.Graphics.Vulkan/Shaders/shadow.frag @@ -0,0 +1,4 @@ +#version 450 + +void main() { +} diff --git a/src/Engine.Graphics.Vulkan/Shaders/shadow.frag.spv b/src/Engine.Graphics.Vulkan/Shaders/shadow.frag.spv new file mode 100644 index 0000000..757dd51 Binary files /dev/null and b/src/Engine.Graphics.Vulkan/Shaders/shadow.frag.spv differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/shadow.vert b/src/Engine.Graphics.Vulkan/Shaders/shadow.vert new file mode 100644 index 0000000..1f7ca77 --- /dev/null +++ b/src/Engine.Graphics.Vulkan/Shaders/shadow.vert @@ -0,0 +1,14 @@ +#version 450 + +layout(location = 0) in vec3 inPosition; + +layout(push_constant) uniform PC { + mat4 model; + vec4 lightPos; + vec4 lightColor; + mat4 lightViewProj; +} pc; + +void main() { + gl_Position = pc.lightViewProj * pc.model * vec4(inPosition, 1.0); +} diff --git a/src/Engine.Graphics.Vulkan/Shaders/shadow.vert.spv b/src/Engine.Graphics.Vulkan/Shaders/shadow.vert.spv new file mode 100644 index 0000000..15e1809 Binary files /dev/null and b/src/Engine.Graphics.Vulkan/Shaders/shadow.vert.spv differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag index 164e00c..a604fd7 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag @@ -3,6 +3,7 @@ layout(location = 0) in vec3 fragWorldPos; layout(location = 1) in vec3 fragNormal; layout(location = 2) in vec3 fragAlbedo; +layout(location = 3) in vec4 fragLightSpacePos; layout(location = 0) out vec4 outColor; @@ -10,10 +11,13 @@ layout(set = 0, binding = 0) uniform CameraUBO { mat4 vp; }; +layout(set = 0, binding = 1) uniform sampler2D shadowMap; + layout(push_constant) uniform PC { mat4 model; vec4 lightPos; vec4 lightColor; + mat4 lightViewProj; } pc; const vec3 AMBIENT = vec3(0.01, 0.01, 0.02); @@ -60,6 +64,33 @@ vec3 acesTonemap(vec3 color) return clamp((color * (a * color + b)) / (color * (c * color + d) + e), 0.0, 1.0); } +float calcShadow(vec4 lightSpacePos, vec3 N, vec3 L) +{ + vec3 projCoords = lightSpacePos.xyz / lightSpacePos.w; + projCoords = projCoords * 0.5 + 0.5; + + if (projCoords.x < 0.0 || projCoords.x > 1.0) return 1.0; + if (projCoords.y < 0.0 || projCoords.y > 1.0) return 1.0; + if (projCoords.z > 1.0) return 1.0; + + float bias = max(0.005 * (1.0 - dot(N, L)), 0.0005); + float currentDepth = projCoords.z; + + // PCF 3x3 + float shadow = 0.0; + vec2 texelSize = 1.0 / vec2(2048.0, 2048.0); + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + float closestDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; + shadow += currentDepth - bias > closestDepth ? 0.0 : 1.0; + } + } + shadow /= 9.0; + return shadow; +} + void main() { vec3 N = normalize(fragNormal); @@ -68,8 +99,6 @@ void main() float roughness = 0.5; float metallic = 0.1; - vec3 color = AMBIENT * albedo; - vec3 lightPos = pc.lightPos.xyz; float lightIntensity = pc.lightPos.w; vec3 lightColor = pc.lightColor.xyz; @@ -82,6 +111,9 @@ void main() float attenuation = pow(clamp(1.0 - dist / max(lightRange, 0.001), 0.0, 1.0), 2.0); vec3 radiance = lightColor * lightIntensity * attenuation; + // Shadow + float shadow = calcShadow(fragLightSpacePos, N, L); + vec3 H = normalize(V + L); vec3 F0 = mix(vec3(0.04), albedo, metallic); @@ -97,7 +129,9 @@ void main() vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic); float NdotL = max(dot(N, L), 0.0); - color += (kD * albedo / PI + specular) * radiance * NdotL; + vec3 lighting = (kD * albedo / PI + specular) * radiance * NdotL * shadow; + + vec3 color = AMBIENT * albedo + lighting; color = acesTonemap(color); color = pow(color, vec3(1.0 / 2.2)); diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv index bd86092..c15a826 100644 Binary files a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv and b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert index 0182991..64e9d7a 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert @@ -7,6 +7,7 @@ layout(location = 2) in vec3 inNormal; layout(location = 0) out vec3 fragWorldPos; layout(location = 1) out vec3 fragNormal; layout(location = 2) out vec3 fragAlbedo; +layout(location = 3) out vec4 fragLightSpacePos; layout(set = 0, binding = 0) uniform CameraUBO { mat4 vp; @@ -16,13 +17,14 @@ layout(push_constant) uniform PC { mat4 model; vec4 lightPos; vec4 lightColor; + mat4 lightViewProj; } pc; void main() { vec4 worldPos = pc.model * vec4(inPosition, 1.0); - gl_Position = pc.model * vec4(inPosition, 1.0); gl_Position = vp * worldPos; fragWorldPos = worldPos.xyz; fragNormal = mat3(pc.model) * inNormal; fragAlbedo = inColor; + fragLightSpacePos = pc.lightViewProj * worldPos; } diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv index f3fc4bd..a1803be 100644 Binary files a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv and b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv differ diff --git a/src/Engine.Graphics.Vulkan/Vk.cs b/src/Engine.Graphics.Vulkan/Vk.cs index 3e893a1..2882df2 100644 --- a/src/Engine.Graphics.Vulkan/Vk.cs +++ b/src/Engine.Graphics.Vulkan/Vk.cs @@ -64,6 +64,7 @@ internal static unsafe class Vk public delegate void VkCmdDraw(VkCommandBuffer commandBuffer, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance); public delegate void VkCmdDrawIndexed(VkCommandBuffer commandBuffer, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance); public delegate void VkCmdBindIndexBuffer(VkCommandBuffer commandBuffer, VkBuffer buffer, ulong offset, int indexType); + public delegate void VkCmdSetDepthBias(VkCommandBuffer commandBuffer, float depthBiasConstantFactor, float depthBiasClamp, float depthBiasSlopeFactor); public delegate void VkCmdBeginRendering(VkCommandBuffer commandBuffer, VkRenderingInfo* pRenderingInfo); public delegate void VkCmdEndRendering(VkCommandBuffer commandBuffer); public delegate void VkCmdPipelineBarrier2(VkCommandBuffer commandBuffer, VkDependencyInfo* pDependencyInfo); @@ -148,6 +149,7 @@ internal static unsafe class Vk public static VkCmdDraw vkCmdDraw; public static VkCmdDrawIndexed vkCmdDrawIndexed; public static VkCmdBindIndexBuffer vkCmdBindIndexBuffer; + public static VkCmdSetDepthBias vkCmdSetDepthBias; public static VkCmdBeginRendering vkCmdBeginRendering; public static VkCmdEndRendering vkCmdEndRendering; public static VkCmdPipelineBarrier2 vkCmdPipelineBarrier2; @@ -239,6 +241,7 @@ internal static unsafe class Vk vkCmdDraw = LoadDev(p, "vkCmdDraw"); vkCmdDrawIndexed = LoadDev(p, "vkCmdDrawIndexed"); vkCmdBindIndexBuffer = LoadDev(p, "vkCmdBindIndexBuffer"); + vkCmdSetDepthBias = LoadDev(p, "vkCmdSetDepthBias"); vkCmdBeginRendering = LoadDev(p, "vkCmdBeginRendering"); vkCmdEndRendering = LoadDev(p, "vkCmdEndRendering"); vkCmdPipelineBarrier2 = LoadDev(p, "vkCmdPipelineBarrier2"); diff --git a/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs b/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs index 229f830..698f58b 100644 --- a/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs +++ b/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs @@ -140,19 +140,25 @@ internal sealed unsafe class VulkanFrameResources : IDisposable private void CreateDescriptorPoolAndSets(VkDescriptorSetLayout layout) { - var poolSize = new VkDescriptorPoolSize + var poolSizes = stackalloc VkDescriptorPoolSize[2]; + poolSizes[0] = new VkDescriptorPoolSize { type = VkDescriptorType.UniformBuffer, descriptorCount = MaxFramesInFlight, }; + poolSizes[1] = new VkDescriptorPoolSize + { + type = VkDescriptorType.CombinedImageSampler, + descriptorCount = MaxFramesInFlight, + }; var poolInfo = new VkDescriptorPoolCreateInfo { sType = VkStructureType.DescriptorPoolCreateInfo, flags = VkDescriptorPoolCreateFlags.FreeDescriptorSet, maxSets = MaxFramesInFlight, - poolSizeCount = 1, - pPoolSizes = &poolSize, + poolSizeCount = 2, + pPoolSizes = poolSizes, }; var descPool = VkDescriptorPool.Null; @@ -204,6 +210,29 @@ internal sealed unsafe class VulkanFrameResources : IDisposable } } + public void UpdateShadowDescriptor(int frameIndex, VkSampler sampler, VkImageView shadowView) + { + var imageInfo = new VkDescriptorImageInfo + { + sampler = sampler, + imageView = shadowView, + imageLayout = VkImageLayout.ShaderReadOnlyOptimal, + }; + + var write = new VkWriteDescriptorSet + { + sType = VkStructureType.WriteDescriptorSet, + dstSet = DescriptorSets[frameIndex], + dstBinding = 1, + dstArrayElement = 0, + descriptorCount = 1, + descriptorType = VkDescriptorType.CombinedImageSampler, + pImageInfo = (nint)(&imageInfo), + }; + + Vk.vkUpdateDescriptorSets(_device, 1, &write, 0, 0); + } + public void UpdateUbo(int frameIndex, void* data, ulong size) { void* pData = null; diff --git a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs index 6cfe5af..5de566b 100644 --- a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs +++ b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs @@ -150,7 +150,7 @@ internal sealed unsafe class VulkanPipeline : IDisposable pDynamicStates = dynamicStates, }; - var pushConstantRanges = stackalloc VkPushConstantRange[2]; + var pushConstantRanges = stackalloc VkPushConstantRange[3]; pushConstantRanges[0] = new VkPushConstantRange { stageFlags = VkShaderStageFlags.Vertex, @@ -163,6 +163,12 @@ internal sealed unsafe class VulkanPipeline : IDisposable offset = 64, size = 32, }; + pushConstantRanges[2] = new VkPushConstantRange + { + stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, + offset = 96, + size = 64, + }; var descLayout = DescriptorSetLayout; var layoutInfo = new VkPipelineLayoutCreateInfo @@ -170,7 +176,7 @@ internal sealed unsafe class VulkanPipeline : IDisposable sType = VkStructureType.PipelineLayoutCreateInfo, setLayoutCount = 1, pSetLayouts = &descLayout, - pushConstantRangeCount = 2, + pushConstantRangeCount = 3, pPushConstantRanges = (nint)pushConstantRanges, }; @@ -221,19 +227,27 @@ internal sealed unsafe class VulkanPipeline : IDisposable private void CreateDescriptorSetLayout() { - var binding = new VkDescriptorSetLayoutBinding + var bindings = stackalloc VkDescriptorSetLayoutBinding[2]; + bindings[0] = new VkDescriptorSetLayoutBinding { binding = 0, descriptorType = VkDescriptorType.UniformBuffer, descriptorCount = 1, stageFlags = VkShaderStageFlags.Vertex, }; + bindings[1] = new VkDescriptorSetLayoutBinding + { + binding = 1, + descriptorType = VkDescriptorType.CombinedImageSampler, + descriptorCount = 1, + stageFlags = VkShaderStageFlags.Fragment, + }; var info = new VkDescriptorSetLayoutCreateInfo { sType = VkStructureType.DescriptorSetLayoutCreateInfo, - bindingCount = 1, - pBindings = &binding, + bindingCount = 2, + pBindings = bindings, }; var descLayout = VkDescriptorSetLayout.Null; diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs index 659bee2..cffad2f 100644 --- a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs @@ -13,6 +13,7 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen private readonly VulkanSwapchain _swapchain; private readonly VulkanPipeline _pipeline; private readonly VulkanFrameResources _frameResources; + private readonly VulkanShadowMap _shadowMap; internal readonly VulkanImGui? _imGui; private readonly Dictionary _meshCache = new(); @@ -39,6 +40,11 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen _frameResources = new VulkanFrameResources(ctx.Device, ctx.GraphicsQueueFamilyIndex, swapchain.ImageCount, ctx, _pipeline.DescriptorSetLayout); + _shadowMap = new VulkanShadowMap(ctx.Device, ctx, _pipeline.DescriptorSetLayout); + + for (int i = 0; i < VulkanFrameResources.MaxFramesInFlight; i++) + _frameResources.UpdateShadowDescriptor(i, _shadowMap.ShadowSampler, _shadowMap.DepthImageView); + _imGui = new VulkanImGui(ctx, _frameResources.CommandPool, swapchain.Format, swapchain.DepthFormat); } @@ -105,6 +111,13 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen var vpCopy = vp; System.Buffer.MemoryCopy(&vpCopy, uboData, 64, 64); + // Compute light view-projection matrix for shadow mapping + var lightPosVec = new Vector3(lightPos.X, lightPos.Y, lightPos.Z); + var lightView = Matrix4x4.CreateLookAt(lightPosVec, Vector3.Zero, Vector3.UnitY); + var lightProj = Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 1.0f, 0.1f, 60f); + lightProj.M22 *= -1; + var lightViewProj = lightView * lightProj; + var drawCalls = new List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model)>(); world.Each((Entity e, ref Transform t, ref Mesh m) => @@ -123,10 +136,10 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen drawCalls.Add((entry.vb.Buffer, entry.ib.Buffer, entry.indexCount, t.GetMatrix())); }); - Render(vp, drawCalls, uboData, lightPos, lightColor); + Render(vp, drawCalls, uboData, lightPos, lightColor, lightViewProj); } - private void Render(Matrix4x4 vp, List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model)> drawCalls, byte* uboData, Vector4 lightPos, Vector4 lightColor) + private void Render(Matrix4x4 vp, List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model)> drawCalls, byte* uboData, Vector4 lightPos, Vector4 lightColor, Matrix4x4 lightViewProj) { _frameResources.WaitFrame(_frameIndex); @@ -138,7 +151,7 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen { _swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width, _ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height); - Render(vp, drawCalls, uboData, lightPos, lightColor); + Render(vp, drawCalls, uboData, lightPos, lightColor, lightViewProj); return; } @@ -159,6 +172,86 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen }; Vk.vkBeginCommandBuffer(cmd, &beginInfo); + // === SHADOW PASS === + TransitionImageLayoutDepth(cmd, _shadowMap.DepthImage, + VkImageLayout.Undefined, VkImageLayout.DepthStencilAttachmentOptimal, + 0, 0, 0x100, 0x200); + + var shadowDepthClear = new VkClearValue + { + DepthStencil = new VkClearDepthStencilValue { Depth = 1.0f, Stencil = 0 }, + }; + + var shadowDepthAttachment = new VkRenderingAttachmentInfo + { + sType = VkStructureType.RenderingAttachmentInfo, + imageView = _shadowMap.DepthImageView, + imageLayout = VkImageLayout.DepthStencilAttachmentOptimal, + loadOp = VkAttachmentLoadOp.Clear, + storeOp = VkAttachmentStoreOp.Store, + clearValue = shadowDepthClear, + }; + + var shadowRenderingInfo = new VkRenderingInfo + { + sType = VkStructureType.RenderingInfo, + renderArea = new VkRect2D + { + Offset = new VkOffset2D { X = 0, Y = 0 }, + Extent = new VkExtent2D { Width = VulkanShadowMap.ShadowMapSize, Height = VulkanShadowMap.ShadowMapSize }, + }, + layerCount = 1, + colorAttachmentCount = 0, + pColorAttachments = null, + pDepthAttachment = &shadowDepthAttachment, + }; + + Vk.vkCmdBeginRendering(cmd, &shadowRenderingInfo); + + Vk.vkCmdBindPipeline(cmd, VkPipelineBindPoint.Graphics, _shadowMap.Pipeline); + + var shadowViewport = new VkViewport + { + X = 0, Y = 0, + Width = VulkanShadowMap.ShadowMapSize, + Height = VulkanShadowMap.ShadowMapSize, + MinDepth = 0, MaxDepth = 1, + }; + Vk.vkCmdSetViewport(cmd, 0, 1, &shadowViewport); + + var shadowScissor = new VkRect2D + { + Offset = new VkOffset2D { X = 0, Y = 0 }, + Extent = new VkExtent2D { Width = VulkanShadowMap.ShadowMapSize, Height = VulkanShadowMap.ShadowMapSize }, + }; + Vk.vkCmdSetScissor(cmd, 0, 1, &shadowScissor); + + Vk.vkCmdSetDepthBias(cmd, 1.25f, 0.0f, 1.75f); + + foreach (var dc in drawCalls) + { + var vertexBuf = dc.vertexBuf; + ulong offset = 0; + Vk.vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuf, &offset); + Vk.vkCmdBindIndexBuffer(cmd, dc.indexBuf, 0, 1); + + var model = dc.model; + Vk.vkCmdPushConstants(cmd, _shadowMap.PipelineLayout, VkShaderStageFlags.Vertex, 0, 64, &model); + + var lvpCopy = lightViewProj; + Vk.vkCmdPushConstants(cmd, _shadowMap.PipelineLayout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 96, 64, &lvpCopy); + + Vk.vkCmdDrawIndexed(cmd, dc.indexCount, 1, 0, 0, 0); + } + + Vk.vkCmdEndRendering(cmd); + + // Transition shadow map: depth attachment → shader read + TransitionImageLayoutDepth(cmd, _shadowMap.DepthImage, + VkImageLayout.DepthStencilAttachmentOptimal, VkImageLayout.ShaderReadOnlyOptimal, + 0x100, 0x200, 0x8, 0x20); + + // === MAIN PASS === TransitionImageLayout(cmd, _swapchain.Images[imageIndex], VkImageLayout.Undefined, VkImageLayout.ColorAttachmentOptimal, 0, 0, @@ -253,6 +346,10 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen var lcCopy = lightColor; Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Fragment, 80, 16, &lcCopy); + // Light view-proj at offset 96 (vertex + fragment) + var lvpCopy = lightViewProj; + Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 96, 64, &lvpCopy); + Vk.vkCmdDrawIndexed(cmd, dc.indexCount, 1, 0, 0, 0); } @@ -442,6 +539,7 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen } _meshCache.Clear(); + _shadowMap?.Dispose(); _imGui?.Dispose(); _frameResources?.Dispose(); _pipeline?.Dispose(); diff --git a/src/Engine.Graphics.Vulkan/VulkanShadowMap.cs b/src/Engine.Graphics.Vulkan/VulkanShadowMap.cs new file mode 100644 index 0000000..a87c40e --- /dev/null +++ b/src/Engine.Graphics.Vulkan/VulkanShadowMap.cs @@ -0,0 +1,305 @@ +using System.Runtime.InteropServices; + +namespace Engine.Graphics.Vulkan; + +internal sealed unsafe class VulkanShadowMap : IDisposable +{ + public const uint ShadowMapSize = 2048; + + public VkImage DepthImage; + public VkDeviceMemory DepthImageMemory; + public VkImageView DepthImageView; + public VkSampler ShadowSampler; + public VkPipeline Pipeline; + public VkPipelineLayout PipelineLayout; + public VkShaderModule VertModule; + + private readonly VkDevice _device; + private readonly VulkanContext _ctx; + private bool _disposed; + + public VulkanShadowMap(VkDevice device, VulkanContext ctx, VkDescriptorSetLayout descLayout) + { + _device = device; + _ctx = ctx; + + CreateShadowImage(); + CreateShadowSampler(); + CreateShadowPipeline(descLayout); + + Console.WriteLine("[Vulkan] Shadow map created (2048x2048 D32_SFLOAT)"); + } + + private void CreateShadowImage() + { + var imageInfo = new VkImageCreateInfo + { + sType = VkStructureType.ImageCreateInfo, + imageType = VkImageType.Type2D, + format = VkFormat.D32Sfloat, + extent = new VkExtent3D { Width = ShadowMapSize, Height = ShadowMapSize, Depth = 1 }, + mipLevels = 1, + arrayLayers = 1, + samples = VkSampleCountFlags.Count1, + tiling = VkImageTiling.Optimal, + usage = VkImageUsageFlags.DepthStencilAttachment | VkImageUsageFlags.Sampled, + sharingMode = VkSharingMode.Exclusive, + initialLayout = VkImageLayout.Undefined, + }; + + var img = VkImage.Null; + Vk.vkCreateImage(_device, &imageInfo, 0, &img); + DepthImage = img; + + 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 mem = VkDeviceMemory.Null; + Vk.vkAllocateMemory(_device, &allocInfo, 0, &mem); + DepthImageMemory = mem; + Vk.vkBindImageMemory(_device, DepthImage, DepthImageMemory, 0); + + var viewInfo = new VkImageViewCreateInfo + { + sType = VkStructureType.ImageViewCreateInfo, + image = DepthImage, + viewType = VkImageViewType.Type2D, + format = VkFormat.D32Sfloat, + 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 view = VkImageView.Null; + Vk.vkCreateImageView(_device, &viewInfo, 0, &view); + DepthImageView = view; + } + + private void CreateShadowSampler() + { + var samplerInfo = new VkSamplerCreateInfo + { + sType = VkStructureType.SamplerCreateInfo, + magFilter = VkFilter.Linear, + minFilter = VkFilter.Linear, + mipmapMode = VkSamplerMipmapMode.Linear, + addressModeU = VkSamplerAddressMode.ClampToBorder, + addressModeV = VkSamplerAddressMode.ClampToBorder, + addressModeW = VkSamplerAddressMode.ClampToBorder, + minLod = 0, + maxLod = 1, + borderColor = 1, // VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE + }; + + var samp = VkSampler.Null; + Vk.vkCreateSampler(_device, &samplerInfo, 0, &samp); + ShadowSampler = samp; + } + + private void CreateShadowPipeline(VkDescriptorSetLayout descLayout) + { + var vertSpv = LoadShader("Shaders/shadow.vert.spv"); + var fragSpv = LoadShader("Shaders/shadow.frag.spv"); + VertModule = CreateShaderModule(vertSpv); + var fragModule = CreateShaderModule(fragSpv); + + fixed (byte* pName = "main\0"u8) + { + 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 bindings = stackalloc VkVertexInputBindingDescription[1]; + bindings[0] = new VkVertexInputBindingDescription { binding = 0, stride = (uint)sizeof(Engine.Core.Vertex), inputRate = VkVertexInputRate.Vertex }; + + var attributes = stackalloc VkVertexInputAttributeDescription[1]; + attributes[0] = new VkVertexInputAttributeDescription { location = 0, binding = 0, format = VkFormat.R32G32B32Sfloat, offset = 0 }; + + var vertexInputState = new VkPipelineVertexInputStateCreateInfo + { + sType = VkStructureType.PipelineVertexInputStateCreateInfo, + vertexBindingDescriptionCount = 1, + pVertexBindingDescriptions = bindings, + vertexAttributeDescriptionCount = 1, + pVertexAttributeDescriptions = attributes, + }; + + var inputAssemblyState = new VkPipelineInputAssemblyStateCreateInfo + { + sType = VkStructureType.PipelineInputAssemblyStateCreateInfo, + topology = VkPrimitiveTopology.TriangleList, + primitiveRestartEnable = VkBool32.False, + }; + + var viewportState = new VkPipelineViewportStateCreateInfo + { + sType = VkStructureType.PipelineViewportStateCreateInfo, + viewportCount = 1, + scissorCount = 1, + }; + + var rasterizationState = new VkPipelineRasterizationStateCreateInfo + { + sType = VkStructureType.PipelineRasterizationStateCreateInfo, + depthClampEnable = VkBool32.False, + rasterizerDiscardEnable = VkBool32.False, + polygonMode = VkPolygonMode.Fill, + cullMode = VkCullModeFlags.None, + frontFace = VkFrontFace.CounterClockwise, + depthBiasEnable = VkBool32.True, + lineWidth = 1.0f, + }; + + var multisampleState = new VkPipelineMultisampleStateCreateInfo + { + sType = VkStructureType.PipelineMultisampleStateCreateInfo, + rasterizationSamples = VkSampleCountFlags.Count1, + }; + + var depthStencilState = new VkPipelineDepthStencilStateCreateInfo + { + sType = VkStructureType.PipelineDepthStencilStateCreateInfo, + depthTestEnable = VkBool32.True, + depthWriteEnable = VkBool32.True, + depthCompareOp = VkCompareOp.LessOrEqual, + depthBoundsTestEnable = VkBool32.False, + stencilTestEnable = VkBool32.False, + }; + + var colorBlendState = new VkPipelineColorBlendStateCreateInfo + { + sType = VkStructureType.PipelineColorBlendStateCreateInfo, + logicOpEnable = VkBool32.False, + attachmentCount = 0, + pAttachments = null, + }; + + var dynamicStates = stackalloc VkDynamicState[3]; + dynamicStates[0] = VkDynamicState.Viewport; + dynamicStates[1] = VkDynamicState.Scissor; + dynamicStates[2] = VkDynamicState.DepthBias; + + var dynamicState = new VkPipelineDynamicStateCreateInfo + { + sType = VkStructureType.PipelineDynamicStateCreateInfo, + dynamicStateCount = 3, + pDynamicStates = dynamicStates, + }; + + var pushConstantRanges = stackalloc VkPushConstantRange[2]; + pushConstantRanges[0] = new VkPushConstantRange { stageFlags = VkShaderStageFlags.Vertex, offset = 0, size = 64 }; + pushConstantRanges[1] = new VkPushConstantRange { stageFlags = VkShaderStageFlags.Fragment, offset = 64, size = 96 }; + + var layoutInfo = new VkPipelineLayoutCreateInfo + { + sType = VkStructureType.PipelineLayoutCreateInfo, + setLayoutCount = 0, + pushConstantRangeCount = 2, + pPushConstantRanges = (nint)pushConstantRanges, + }; + + var pl = VkPipelineLayout.Null; + Vk.vkCreatePipelineLayout(_device, &layoutInfo, 0, &pl); + PipelineLayout = pl; + + var depthFormat = VkFormat.D32Sfloat; + var renderingInfo = new VkPipelineRenderingCreateInfo + { + sType = VkStructureType.PipelineRenderingCreateInfo, + colorAttachmentCount = 0, + pColorAttachmentFormats = null, + depthAttachmentFormat = depthFormat, + }; + + var pipelineInfo = new VkGraphicsPipelineCreateInfo + { + sType = VkStructureType.GraphicsPipelineCreateInfo, + pNext = (nint)(&renderingInfo), + stageCount = 2, + pStages = stages, + pVertexInputState = &vertexInputState, + pInputAssemblyState = &inputAssemblyState, + pViewportState = &viewportState, + pRasterizationState = &rasterizationState, + pMultisampleState = &multisampleState, + pDepthStencilState = &depthStencilState, + pColorBlendState = &colorBlendState, + pDynamicState = &dynamicState, + layout = PipelineLayout, + renderPass = new VkRenderPass { Handle = 0 }, + subpass = 0, + }; + + var pp = VkPipeline.Null; + Vk.vkCreateGraphicsPipelines(_device, 0, 1, &pipelineInfo, 0, &pp); + Pipeline = pp; + + Vk.vkDestroyShaderModule(_device, fragModule, 0); + } + } + + private VkShaderModule CreateShaderModule(byte[] spv) + { + fixed (byte* pCode = spv) + { + var info = new VkShaderModuleCreateInfo + { + sType = VkStructureType.ShaderModuleCreateInfo, + codeSize = (nuint)spv.Length, + pCode = (uint*)pCode, + }; + var module = VkShaderModule.Null; + Vk.vkCreateShaderModule(_device, &info, 0, &module); + return module; + } + } + + private static byte[] LoadShader(string path) + { + if (!File.Exists(path)) + { + var altPath = Path.Combine(AppContext.BaseDirectory, path); + if (!File.Exists(altPath)) + { + altPath = Path.Combine(AppContext.BaseDirectory, "Shaders", Path.GetFileName(path)); + if (!File.Exists(altPath)) + throw new FileNotFoundException($"Shader file not found: {path}"); + } + return File.ReadAllBytes(altPath); + } + return File.ReadAllBytes(path); + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + + if (Pipeline.Handle != 0) Vk.vkDestroyPipeline(_device, Pipeline, 0); + if (PipelineLayout.Handle != 0) Vk.vkDestroyPipelineLayout(_device, PipelineLayout, 0); + if (VertModule.Handle != 0) Vk.vkDestroyShaderModule(_device, VertModule, 0); + if (ShadowSampler.Handle != 0) Vk.vkDestroySampler(_device, ShadowSampler, 0); + if (DepthImageView.Handle != 0) Vk.vkDestroyImageView(_device, DepthImageView, 0); + if (DepthImage.Handle != 0) Vk.vkDestroyImage(_device, DepthImage, 0); + if (DepthImageMemory.Handle != 0) Vk.vkFreeMemory(_device, DepthImageMemory, 0); + } +}