From 46a04850e3f8ec80e87590e386c3faca06a98569 Mon Sep 17 00:00:00 2001 From: emil28092005 Date: Thu, 18 Jun 2026 00:14:40 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20rendering=20now=20works=20=E2=80=94=20th?= =?UTF-8?q?ree=20critical=20bugs=20fixed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. VkColorComponentFlags: wrong bit values (0x10-0x80 instead of 0x01-0x08) → colorWriteMask was 0xF0 instead of 0x0F → no color channels written 2. VkClearValue: was Sequential layout instead of Explicit (union) → depthStencil clear value written at wrong offset → depth buffer cleared to 0.0 instead of 1.0 → all fragments failed depth test 3. Vulkan clip space correction in vertex shader: gl_Position.y = -gl_Position.y (Vulkan Y-down vs OpenGL Y-up) gl_Position.z = (gl_Position.z + gl_Position.w) * 0.5 (Z [0,1] vs [-1,1]) Also: - PushConstantSize fixed from 128 to 144 (two mat4 + vec4) - Dynamic viewport/scissor state added to pipeline - Push constants use column_major (default) to match System.Numerics row-major layout - Camera.GetProjectionMatrix reverted to pure OpenGL-style (correction in shader) - Screenshots show actual 3D geometry (calibration cubes visible) - 66/66 tests pass --- .../Shaders/fragment.frag | 5 +-- .../Shaders/fragment.spv | Bin 6224 -> 6100 bytes src/Engine.Graphics.Vulkan/Shaders/vertex.spv | Bin 2760 -> 3072 bytes .../Shaders/vertex.vert | 6 +++- src/Engine.Graphics.Vulkan/VulkanPipeline.cs | 17 +++++++--- src/Engine.Graphics.Vulkan/VulkanRenderer.cs | 4 +-- src/Engine.Graphics.Vulkan/VulkanTypes.cs | 32 +++++++++++++----- 7 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/Engine.Graphics.Vulkan/Shaders/fragment.frag b/src/Engine.Graphics.Vulkan/Shaders/fragment.frag index 7fdeaba..5a99f7b 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/fragment.frag +++ b/src/Engine.Graphics.Vulkan/Shaders/fragment.frag @@ -5,7 +5,7 @@ layout(location = 1) in vec3 fragNormal; layout(location = 2) in vec3 fragWorldPos; layout(location = 3) in vec3 fragViewDir; -layout(row_major, set = 0, binding = 0) uniform FrameUBO { +layout(set = 0, binding = 0) uniform FrameUBO { vec3 cameraPosition; uint lightCount; vec3 ambientColor; @@ -47,11 +47,9 @@ void main() { float attenuation; if (dirIntensity.w < 0.0) { - // Directional light: direction stored as xyz, w = intensity (negative marks directional) lightDir = normalize(-dirIntensity.xyz); attenuation = abs(dirIntensity.w); } else { - // Point light: position stored as xyz, w = intensity (positive marks point) vec3 toLight = dirIntensity.xyz - fragWorldPos; float dist = length(toLight); lightDir = toLight / max(dist, 0.001); @@ -63,7 +61,6 @@ void main() { vec3 H = normalize(V + lightDir); float NdotL = max(dot(N, lightDir), 0.0); float NdotH = max(dot(N, H), 0.0); - float VdotH = max(dot(V, H), 0.0); float specPower = mix(128.0, 4.0, roughness); float specIntensity = pow(NdotH, specPower); diff --git a/src/Engine.Graphics.Vulkan/Shaders/fragment.spv b/src/Engine.Graphics.Vulkan/Shaders/fragment.spv index 369001b9aa036e6ee99b92ba9630d768bab9cec6..1517e3d3cb83999aeb3bdff69394bb22f821b3db 100644 GIT binary patch delta 1044 zcmYk4%}bO~6vof{zKss1nYQ%-Lia}q)g=UT+t;T5AB9Qq@ z;#~xh3r#IeOKbZ6nm)8QYie7&e$Q}kIKwmdInO!gp6A}V>nAF?mMA>fXckzO@2{Db zfgrsd*TP)J=FF@(9uMX(PsJ1Pj5XMj(|X4yn)Oui@XlP`3_-Zkx zdY4s+#|smMQCF2Fi_wiBNR!@7dvswQl~4B(DjOj8>*4enAAcpz-A%iWlEtBI0%;!m z>H9_;fevuW#c48|JsTX#BCP0K8G{R zH`!w+u9Y`h+eV)dsa2}R=*-g8uCs1{lP;FMr{E5_#b2cSGoTvjeq;r{W28MNmR8Zk z(hHcnq-vsWRhpmp5-!chui$cZ-A#NASB-Sl$kiKg6L?o|;i{AF_jw2JH!J<$=ski& z70U=ollIp_8)E(AR&CHj`~h49(-=|w(P@`h)vug1^%q#xKgjA0e1i8gzg}D=kkIK) z^%=e!RDm}m%{PC6%b=c#?tKgRXQG^*x9?NV_kD+}ua7ypsqORpNYuxRKj6}|q1S#o bEo99zQg5jSwt?!EKL#{3WvwN%d&}WJKm(uM delta 1123 zcmZ9L%WD%+7{%{onkH(qsg&d)_Fmo8)ql5T@` zu!~Z~cfeI}iJNKLyFec4bvUHm10L7_ly3^R);Crjz}i3^E6DW_$h8@a0Nq=jR&FxP zsl2Aj`xxA;il4ycaTA}y)oT@yS9$f(#g(Ja{-n_ufy5$_t}_m#IcT6B;snr3ZD(2e zo>^@o9ItQenIuxBRE^Q|l;$4N3-~r0YyFqtI=H|?Q@2+@9_eo46uf%kYhr0rnpk-Q zQb%-^1mRE|0eQ0L}xq)ihi_>8{U5_+evK|7$dZAaNRK z5t3#mT+)zV{|R;i3^H;b=PZzR3dpJ4Y*=W>S3bj3CsqFq#VvNxFYskRy6Z2^Ei?~T zt=@cw^E+^FUOD|cT%B^R?k8MzhfMl-a{C1WiTWTW+Ap{?HFVlFJXU>D1Xi=RMaxeR?V*!>ujO4Y{rQd*!ON#|;Z{Zij37eRjS#Ke<{i zO+kNEuQT-0$G($Kbnh;HirbTVhyr?VciAMA{to=U)Xt+`RZTL2X zY?J&ZFZxT}d{wNFrfQM@+Uo00o~@Q~R-$Oi6Pz{pZAEg!%hWZDi zYdUoFp`R|lE<5>JR&MyvgLv>unq<;c$B;+E`_O}xc*)P0#bGvloSC?c#WOFDwX4Pf z&7G^wK*P(`42X3tezfM~WpkoocOQGKnjPw_`yu0ZLu7tn8-7(R&Vk)q(X8%?_+YcD zhxs29v4|0Qj`vh8!-xyUZWuAZ=!IeMVDy4qk#@_)Ojs^*^WKn&iQL{KGYja=pE;<@ zyia6u;{Q;4Q&jb=x+jSr^w#d1e(V%cHyAm<>b}GUJ1R0e&IJ~UIG_2FpSzwAu}mJX zM7K{F$T2P-;=&&kN4=5gko>tfWO79M-Krf7IxKm6;L(qTIRP^qAI`)#i95I!WWIV} z2PE&(9*KrTXM(&t$oO-9Y>7!t`?OONHKX6J9ba(bBQrx558Fc`=iW|}@0hpwuZxdC7G@VuN94 zHWzC)7sOlaOXAU)-IbahZ^rDdibwZXxZ`ic;|u;zz^;i0=WSWM@5Ebe%OL#nY<+ehgjU@u*iI7#p45RK6B#1A=YM} z+rsc6j``dX4-T=+r>mReyYPdET8NFUo%ybKz7fne_1+U<3%*mnVEmIqJkEfg{)Dl@;&jN1of0kU2|6DQ{#IpE*hjY9T YMhxui94~_%=Ri*_J?-G{6yd7qJ<%=9Hvj+t literal 2760 zcmZ9M*=`d{5QaO8&A#u;$(bbVOW5}ViLp-{AP^%2T%sWv!btW^G?@V63W+D+m3S&{ zkodozu4G7C<*xc`uP%FJcyfz#LvE`+FI{!^xnUvB?Ql(hcjsTtPpy_qQ#Ws2#bUP` z@rKy!al1S}s_#LZW(bEw6QV039IlHVidv$MXhff3-9Hte;YRh@@NEd$Ci!z-^p|@1 zs#qaS)#6-lZS`Y2&sNJgD^WD%3CzF9IwSIqHM~%#pLygSyLlG~x^L}1&FXXea&E;MZrb9J_hNU4jpsor^_G9 zcD|OC8$Qe+9=uPJOq!|kBK!s5Mjf6dn3n|bUMQ8ySl!0Nul1v@D+JNg2PMD%CA!aHE72WL26Bwchq&;E z#Zhl0IwF7G4VfH~zPIWIgN{nx9(eR)VNbve$A_NyPvQ+81et$5utSn}>yAW2qKiS^ z6J-49A6sHl(*fPoM9t_A>c$tG_{i*##l!Z9$hmLRypFC4~PCeAy}9m)tF()U!{-j<5$0Wy9O=7l9)a_ppd}ekmVnI~p)u>)5e? zF%z>p9x!U^DJywTi15cAJ^S)QanbQxBR4jWR6VtVS*@qUQ|qM2YCSC;+-f}|zFzBD z$zU9->zsJ7dadUJR?StL>V2_6!~Shitvw z5Wo2yZVCgZZsv1a1jbp=Yw|XCMBosIp6J1R?uy3;+B0EBXWQv9~7x diff --git a/src/Engine.Graphics.Vulkan/Shaders/vertex.vert b/src/Engine.Graphics.Vulkan/Shaders/vertex.vert index 2524b71..ae10582 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/vertex.vert +++ b/src/Engine.Graphics.Vulkan/Shaders/vertex.vert @@ -4,7 +4,7 @@ layout(location = 0) in vec3 inPosition; layout(location = 1) in vec3 inColor; layout(location = 2) in vec3 inNormal; -layout(row_major, set = 0, binding = 0) uniform FrameUBO { +layout(set = 0, binding = 0) uniform FrameUBO { vec3 cameraPosition; uint lightCount; vec3 ambientColor; @@ -27,6 +27,10 @@ void main() { vec4 worldPos = pc.model * vec4(inPosition, 1.0); gl_Position = pc.mvp * vec4(inPosition, 1.0); + // Vulkan clip space: Y-down, Z [0,1] — convert from OpenGL Y-up, Z [-1,1] + gl_Position.y = -gl_Position.y; + gl_Position.z = (gl_Position.z + gl_Position.w) * 0.5; + fragColor = inColor; fragNormal = normalize(mat3(pc.model) * inNormal); fragWorldPos = worldPos.xyz; diff --git a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs index b69bfaa..333b2ad 100644 --- a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs +++ b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs @@ -14,7 +14,7 @@ public sealed unsafe class VulkanPipeline : IDisposable private readonly VulkanContext _ctx; private bool _disposed; - public const int PushConstantSize = 128; + public const int PushConstantSize = 144; public const int FrameUboSize = 16 + 16 + 16 * 4 * 8; public VulkanPipeline(VulkanContext ctx, VkRenderPass renderPass) @@ -84,8 +84,8 @@ public sealed unsafe class VulkanPipeline : IDisposable primitiveRestartEnable = 0 }; - var viewport = new VkViewport { x = 0, y = 0, width = 0, height = 0, minDepth = 0, maxDepth = 1 }; - var scissor = new VkRect2D { offset = new VkOffset2D { x = 0, y = 0 }, extent = new VkExtent2D { width = 0, height = 0 } }; + 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 } }; VkPipelineViewportStateCreateInfo viewportState; viewportState.sType = VkStructureType.PipelineViewportStateCreateInfo; @@ -163,6 +163,15 @@ public sealed unsafe class VulkanPipeline : IDisposable colorBlendState.attachmentCount = 1; colorBlendState.pAttachments = &blendAttachment; + var dynamicStates = stackalloc VkDynamicState[2]; + dynamicStates[0] = VkDynamicState.Viewport; + dynamicStates[1] = VkDynamicState.Scissor; + + VkPipelineDynamicStateCreateInfo dynamicState = default; + dynamicState.sType = VkStructureType.PipelineDynamicStateCreateInfo; + dynamicState.dynamicStateCount = 2; + dynamicState.pDynamicStates = dynamicStates; + var uboBinding = new VkDescriptorSetLayoutBinding { binding = 0, @@ -226,7 +235,7 @@ public sealed unsafe class VulkanPipeline : IDisposable pipelineInfo.pMultisampleState = &multisampleState; pipelineInfo.pDepthStencilState = &depthStencilState; pipelineInfo.pColorBlendState = &colorBlendState; - pipelineInfo.pDynamicState = null; + pipelineInfo.pDynamicState = &dynamicState; pipelineInfo.layout = pipeLayout; pipelineInfo.renderPass = renderPass; pipelineInfo.subpass = 0; diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs index 556abf7..66209a8 100644 --- a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs @@ -475,7 +475,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, IScreenshotProvider }; Vk.vkCmdSetScissor(cmd, 0, 1, &scissor); - VkDescriptorSet ds = _descriptorSets[_currentFrame]; + var ds = _descriptorSets[_currentFrame]; Vk.vkCmdBindDescriptorSets(cmd, 0, _pipeline.PipelineLayout, 0, 1, &ds, 0, null); world.Each((Entity e, ref Transform transform, ref Mesh mesh, ref Material material) => @@ -502,7 +502,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, IScreenshotProvider proj = cam.GetProjectionMatrix(); }); - var mvp = model * view * proj; + var mvp = proj * view * model; var pushData = new byte[VulkanPipeline.PushConstantSize]; fixed (byte* pPush = pushData) diff --git a/src/Engine.Graphics.Vulkan/VulkanTypes.cs b/src/Engine.Graphics.Vulkan/VulkanTypes.cs index 4bdb763..e2faaa2 100644 --- a/src/Engine.Graphics.Vulkan/VulkanTypes.cs +++ b/src/Engine.Graphics.Vulkan/VulkanTypes.cs @@ -212,6 +212,12 @@ public enum VkCullModeFlags : uint FrontAndBack = 0x00000003, } +public enum VkDynamicState : int +{ + Viewport = 0, + Scissor = 1, +} + public enum VkFrontFace : int { CounterClockwise = 0, @@ -241,10 +247,10 @@ public enum VkBlendOp : int public enum VkColorComponentFlags : uint { - R = 0x00000010, - G = 0x00000020, - B = 0x00000040, - A = 0x00000080, + R = 0x00000001, + G = 0x00000002, + B = 0x00000004, + A = 0x00000008, } public enum VkCompareOp : int @@ -1169,6 +1175,16 @@ unsafe public struct VkPipelineColorBlendStateCreateInfo public fixed float blendConstants[4]; } +[StructLayout(LayoutKind.Sequential)] +unsafe public struct VkPipelineDynamicStateCreateInfo +{ + public VkStructureType sType; + public void* pNext; + public uint flags; + public uint dynamicStateCount; + public VkDynamicState* pDynamicStates; +} + [StructLayout(LayoutKind.Sequential)] unsafe public struct VkGraphicsPipelineCreateInfo { @@ -1310,11 +1326,11 @@ unsafe public struct VkRenderPassBeginInfo public VkClearValue* pClearValues; } -[StructLayout(LayoutKind.Sequential)] -unsafe public struct VkClearValue +[StructLayout(LayoutKind.Explicit)] +public struct VkClearValue { - public VkClearColorValue color; - public VkClearDepthStencilValue depthStencil; + [FieldOffset(0)] public VkClearColorValue color; + [FieldOffset(0)] public VkClearDepthStencilValue depthStencil; } [StructLayout(LayoutKind.Sequential)]