From 139b60b8af465baf2366d4397b3a5bf2fa98b7d1 Mon Sep 17 00:00:00 2001 From: Emil Date: Sun, 21 Jun 2026 00:41:34 +0300 Subject: [PATCH] fix: flip Y in vertex shader, dynamic viewport/scissor, clean debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix: Y coordinate was inverted (1.0 - 2.0*y → 2.0*y - 1.0) Vulkan clip space Y is already down-up, no need to flip - Fix: dynamic viewport/scissor with viewport_count(1) + scissor_count(1) Required by validation layer even with dynamic state - Clean: removed debug eprintlns, restored default clear color - Shaders recompiled to SPIR-V --- assets/shaders/cell.vert | 8 +++---- assets/shaders/cell_vert.spv | Bin 2260 -> 2084 bytes src/render/vulkan.rs | 42 ++++++++++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/assets/shaders/cell.vert b/assets/shaders/cell.vert index 846d180..086845e 100644 --- a/assets/shaders/cell.vert +++ b/assets/shaders/cell.vert @@ -16,12 +16,12 @@ layout(location = 1) out vec4 out_fg; layout(location = 2) out vec4 out_bg; void main() { - vec2 pixel = in_grid * pc.cell_size + in_pos * pc.cell_size; - vec2 clip = vec2( + vec2 pixel = (in_grid + in_pos) * pc.cell_size; + gl_Position = vec4( 2.0 * pixel.x / pc.screen_size.x - 1.0, - 1.0 - 2.0 * pixel.y / pc.screen_size.y + 2.0 * pixel.y / pc.screen_size.y - 1.0, + 0.0, 1.0 ); - gl_Position = vec4(clip, 0.0, 1.0); out_uv = in_atlas.xy + in_pos * in_atlas.zw; out_fg = in_fg; out_bg = in_bg; diff --git a/assets/shaders/cell_vert.spv b/assets/shaders/cell_vert.spv index 25b916c888e69531f1e597f68185b7a8e78d1629..5c19606196599c4935c4f8327d4e401c7a760370 100644 GIT binary patch literal 2084 zcmZ9MYflqV5QYyF3W|z|fER?ts;DTU0;0&xBE^J65)!{QwX9^5wQbtP5I<>*iNDKV zmV4}~n$5Q4y=NS`aDx(Zn3v_)?07>Cb>PT>LmNj99J4VF z8;%{JKW1{)U!R`|m|Xnj;J5oQ5B$`@&pf*0nFpBqqpCuk8ATJ1Jua Z4n>H6RVf z7YTD`I0SrNns0+w1Gak_X2!%!$?qur@ThboA%8C5s{!-{7RX=RbcX_?NZg><2UFP~(r7eram4GxkPn!t{7S!k!rBpOroN zt}Oh+5@O)D#PQDK5_W_4x4enK{|tH8gvo_pS0{WIjv6KDI}$kFf*Q>Gt^}SM;Cqr8 z3H+9rS?T){z8y)ra~}xf`xSB@$|fKFN8ldGhUY!4kH@m<@$8*{6^s%ogvkOZS`oqV5S&!_)3kf?ONth*=c={#xwS*md zuUfp>THxS0JraATb=kbHSULuP~g+D#$pA_jMyPPlHif=vt)M%o`*34T= zs~h<{a3$Lbu2Z<5KiZ!5%0{EnT59K?8-ICwTxm3$UDjF^dZzYDx2}CscYUqh#UsWy z^K}%l{n-?HwYgMpY`ke~bQ+)Jy;I(!Z>^no^0gN1K*4)w!RM{cYn$PO&u_*z!xfM7 zc4xV@Vz1pir>bwW+5GFXU+3s?4$gCJv$M4MQQTL|mj0JJ&1Jrz8{@2vdB57l&Yc)x zQNuUT|2Fx35B$PxHqCkz+wYi7v$pR!vSZd`U{7$y$_*mcaIV(ySi^ZmjhLNp>am8L zHRRO6nIq?Hg433BXS}P1{&!bzF%z-5`sb71x?<;{-#TLFQI2;WV(agtigiv9ZM^mY ztbTMAIY`t1TCA@Mb_Q~acpKWg#di{p?;Hg?({lVSu=_7>yx5(U8wT$|R}pKDCp?s} z_wB_twtS!O5%KnV4B;=WTQ|7mRR3%~k&;s5d=>WzcF z-+A3Y8{>RO_$ywT48OZ%PWauWvfo|OKgEu_AAYmPm|rk`Xmj0}@|%hMx5!@a z;aBfK+?yI%zVSZ9z3}ZLW`E+p6JyRWVxD}&9K?1XqRt`g|LYuvlaD$_u+5YInra`# zw!Xgme3AXjS+|NlfylW_-tGIGMC7e29z)zk`5KJ7bOx~>cdVRyHu-*}+;d>_<-aBF z0=B&S7kymBwh#9!`nZHGAAL+<+lPGgaTz=MDChd0puXtiDz^FZ(Z@Axd(c;>H{aqq zVt@Lam+vbV?{W)U?p6AhJJ@ob;O=6}xl?tjx~umPWA(ZJ@$K&G1H|8mv-D1}@!o0f zV`K(dq?SECNu0dLd!oOm*zQf_&SIBy=fHA`yIw(Jf1Y8RZ%pjZb8PR^XKyvMT-1Mo Yy^wtV?!@7%V=pG3zd7-5x}8S;0H*AYDgXcg diff --git a/src/render/vulkan.rs b/src/render/vulkan.rs index 52c2c46..22f6d61 100644 --- a/src/render/vulkan.rs +++ b/src/render/vulkan.rs @@ -88,6 +88,7 @@ pub struct VulkanRenderer { descriptor_pool: vk::DescriptorPool, descriptor_set: vk::DescriptorSet, descriptor_set_layout: vk::DescriptorSetLayout, + tick_count: u64, } impl VulkanRenderer { @@ -152,6 +153,7 @@ impl VulkanRenderer { atlas_image, atlas_memory, atlas_view, atlas_sampler, atlas_map, instance_buffer, instance_memory, instance_ptr, instance_count, descriptor_pool, descriptor_set, descriptor_set_layout, + tick_count: 0, }) } @@ -252,6 +254,20 @@ impl VulkanRenderer { device.cmd_begin_render_pass(cmd, &rp_info, vk::SubpassContents::INLINE); device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline); + + let viewport = vk::Viewport { + x: 0.0, y: 0.0, + width: self.swapchain_extent.width as f32, + height: self.swapchain_extent.height as f32, + min_depth: 0.0, max_depth: 1.0, + }; + let scissor = vk::Rect2D { + offset: vk::Offset2D::default(), + extent: self.swapchain_extent, + }; + device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&viewport)); + device.cmd_set_scissor(cmd, 0, std::slice::from_ref(&scissor)); + device.cmd_bind_vertex_buffers(cmd, 0, &[self.vertex_buffer, self.instance_buffer], &[0, 0]); device.cmd_bind_index_buffer(cmd, self.index_buffer, 0, vk::IndexType::UINT16); device.cmd_bind_descriptor_sets(cmd, vk::PipelineBindPoint::GRAPHICS, @@ -285,6 +301,7 @@ impl VulkanRenderer { } self.frame_index = (self.frame_index + 1) % MAX_FRAMES; + self.tick_count += 1; } pub fn grid_w(&self) -> usize { self.grid_w } @@ -330,11 +347,23 @@ fn create_instance(entry: &ash::Entry) -> Result { let app_info = vk::ApplicationInfo::default() .application_name(&app_name) .api_version(vk::API_VERSION_1_2); - let ext_names: Vec = vec![ + + let mut ext_names: Vec = vec![ CString::new("VK_KHR_surface").unwrap(), CString::new("VK_KHR_xlib_surface").unwrap(), CString::new("VK_KHR_wayland_surface").unwrap(), ]; + + // Add debug utils extension if available + let avail_exts = unsafe { entry.enumerate_instance_extension_properties(None) }.unwrap_or_default(); + let has_debug_utils = avail_exts.iter().any(|e| { + let name = unsafe { std::ffi::CStr::from_ptr(e.extension_name.as_ptr() as *const i8) }; + name.to_str().unwrap_or("") == "VK_EXT_debug_utils" + }); + if has_debug_utils { + ext_names.push(CString::new("VK_EXT_debug_utils").unwrap()); + } + let ext_ptrs: Vec<*const i8> = ext_names.iter().map(|n| n.as_ptr()).collect(); let create_info = vk::InstanceCreateInfo::default() .application_info(&app_info) @@ -497,9 +526,11 @@ fn create_pipeline(device: &ash::Device, rp: vk::RenderPass, ds_layout: vk::Desc let vi = vk::PipelineVertexInputStateCreateInfo::default() .vertex_binding_descriptions(&bindings).vertex_attribute_descriptions(&attrs); let ia = vk::PipelineInputAssemblyStateCreateInfo::default().topology(vk::PrimitiveTopology::TRIANGLE_LIST); - let vp = vk::Viewport { x: 0.0, y: 0.0, width: 1.0, height: 1.0, min_depth: 0.0, max_depth: 1.0 }; - let sc = vk::Rect2D { offset: vk::Offset2D::default(), extent: vk::Extent2D { width: 4096, height: 4096 } }; - let vs_state = vk::PipelineViewportStateCreateInfo::default().viewports(std::slice::from_ref(&vp)).scissors(std::slice::from_ref(&sc)); + let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]; + let dynamic_state = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states); + let vs_state = vk::PipelineViewportStateCreateInfo::default() + .viewport_count(1) + .scissor_count(1); let rs = vk::PipelineRasterizationStateCreateInfo::default().line_width(1.0).cull_mode(vk::CullModeFlags::NONE); let ms = vk::PipelineMultisampleStateCreateInfo::default().rasterization_samples(vk::SampleCountFlags::TYPE_1); let cba = vk::PipelineColorBlendAttachmentState::default().color_write_mask(vk::ColorComponentFlags::RGBA); @@ -513,7 +544,8 @@ fn create_pipeline(device: &ash::Device, rp: vk::RenderPass, ds_layout: vk::Desc let pi = vk::GraphicsPipelineCreateInfo::default() .stages(&stages).vertex_input_state(&vi).input_assembly_state(&ia) .viewport_state(&vs_state).rasterization_state(&rs).multisample_state(&ms) - .color_blend_state(&cb).layout(layout).render_pass(rp).subpass(0); + .color_blend_state(&cb).dynamic_state(&dynamic_state) + .layout(layout).render_pass(rp).subpass(0); let pipes = unsafe { device.create_graphics_pipelines(vk::PipelineCache::null(), std::slice::from_ref(&pi), None) } .map_err(|(_, e)| format!("pipeline: {e:?}"))?; unsafe { device.destroy_shader_module(vm, None); device.destroy_shader_module(fm, None); }