From a996440c612509925eccfc48c6f2520d48133251 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Tue, 2 Dec 2025 13:14:48 -0500 Subject: [PATCH] Next part of the tutorial where we have a couple compute shaders controlled by ImGUI. --- Makefile | 2 + gradient_color.comp | 31 +++++++++++++ gradient_color.comp.spv | Bin 0 -> 1980 bytes sky.comp | 92 +++++++++++++++++++++++++++++++++++++++ sky.comp.spv | Bin 0 -> 6132 bytes vk_engine.cpp | 94 +++++++++++++++++++++++++++++++--------- vk_engine.h | 21 +++++++++ 7 files changed, 219 insertions(+), 21 deletions(-) create mode 100644 gradient_color.comp create mode 100644 gradient_color.comp.spv create mode 100644 sky.comp create mode 100644 sky.comp.spv diff --git a/Makefile b/Makefile index 3b0e75c..73487f8 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ endif shaders: glslangValidator -V gradient.comp -o gradient.comp.spv + glslangValidator -V sky.comp -o sky.comp.spv + glslangValidator -V gradient_color.comp -o gradient_color.comp.spv %.cpp : %.rl ragel -I $(ROOT_DIR) -G1 -o $@ $< diff --git a/gradient_color.comp b/gradient_color.comp new file mode 100644 index 0000000..4627cb4 --- /dev/null +++ b/gradient_color.comp @@ -0,0 +1,31 @@ +#version 460 + +layout (local_size_x = 16, local_size_y = 16) in; + +layout(rgba16f,set = 0, binding = 0) uniform image2D image; + +//push constants block +layout( push_constant ) uniform constants +{ + vec4 data1; + vec4 data2; + vec4 data3; + vec4 data4; +} PushConstants; + +void main() +{ + ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy); + + ivec2 size = imageSize(image); + + vec4 topColor = PushConstants.data1; + vec4 bottomColor = PushConstants.data2; + + if(texelCoord.x < size.x && texelCoord.y < size.y) + { + float blend = float(texelCoord.y)/(size.y); + + imageStore(image, texelCoord, mix(topColor,bottomColor, blend)); + } +} diff --git a/gradient_color.comp.spv b/gradient_color.comp.spv new file mode 100644 index 0000000000000000000000000000000000000000..56c176ae293ad584d97d22b78612fa3a8ecf8bc1 GIT binary patch literal 1980 zcmZ9Mi%t_k6owZ{5d}fyCSD7us36_|Q3S=J1tF2d2QUR1Xi~a{uErQ2z}N9Td>!K@ zG4cC$JIRJg{+@IG`RB};IkPQvPMwOPj;J^4iMFF`3`U(WQKWr{?TxzAxU&6jd#;u2 z&&@B~SJ@vGH0j&`nj(G|II7m0@@K(0c)7vUH_r40)^_6mV51_6{JJ8{Y37pJ=USs2 z$H)6>deVFcjrWyCyjyK-Hb2FC)ubLbH`mdc<2<%i|5C%G@hG-_R6UR|=Ot_se<;U| z_!u|kE@SuNW-F;SlNP?GkaO%;lj;L(;Jgz#?<8=ZLGDqG^H${Mu|AGyxnTp3FkxLaMW-jodlh#}PQsA!jcF z=Nh?Ejw`e|o9$j(SZf&vLv+tI6YQx@e*{f8wglW`jF`uja?Y2Cd$4js{h5^KhoRp5 za^_Uuhc%b{ay#};LjHby_2wUC)eyD>#(6=*SYx&Mp*GYr(w0 z+MHTzG+yH*CF}&SC-%Yh-u)zySMM2&gZ>7-dixUeH}U1wdq!jR_T>(+KKrFU>ruuB z%`9`)mDk)Qmj6zkY3v-B0rJ1t$M4bcmz>Vs19J9AKjij*%KhzW*x&yt`JnMX%Kh`2 z1!DQI|02HY8Il zt3WQSd5(Xw_7`ySVa*!8+&3yS$4g)yd3)wNl=FUkKV{(iG4`#k1M4#OeY^s`ix9uY wx9`U0uK;uIuv+~_o0AXd;|p?b6LbLY&AjGPzeg_XaBnrn+a literal 0 HcmV?d00001 diff --git a/sky.comp b/sky.comp new file mode 100644 index 0000000..0503f9a --- /dev/null +++ b/sky.comp @@ -0,0 +1,92 @@ +#version 450 +layout (local_size_x = 16, local_size_y = 16) in; +layout(rgba8,set = 0, binding = 0) uniform image2D image; + +// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. + +//push constants block +layout( push_constant ) uniform constants +{ + vec4 data1; + vec4 data2; + vec4 data3; + vec4 data4; +} PushConstants; + +// Return random noise in the range [0.0, 1.0], as a function of x. +float Noise2d( in vec2 x ) +{ + float xhash = cos( x.x * 37.0 ); + float yhash = cos( x.y * 57.0 ); + return fract( 415.92653 * ( xhash + yhash ) ); +} + +// Convert Noise2d() into a "star field" by stomping everthing below fThreshhold to zero. +float NoisyStarField( in vec2 vSamplePos, float fThreshhold ) +{ + float StarVal = Noise2d( vSamplePos ); + if ( StarVal >= fThreshhold ) + StarVal = pow( (StarVal - fThreshhold)/(1.0 - fThreshhold), 6.0 ); + else + StarVal = 0.0; + return StarVal; +} + +// Stabilize NoisyStarField() by only sampling at integer values. +float StableStarField( in vec2 vSamplePos, float fThreshhold ) +{ + // Linear interpolation between four samples. + // Note: This approach has some visual artifacts. + // There must be a better way to "anti alias" the star field. + float fractX = fract( vSamplePos.x ); + float fractY = fract( vSamplePos.y ); + vec2 floorSample = floor( vSamplePos ); + float v1 = NoisyStarField( floorSample, fThreshhold ); + float v2 = NoisyStarField( floorSample + vec2( 0.0, 1.0 ), fThreshhold ); + float v3 = NoisyStarField( floorSample + vec2( 1.0, 0.0 ), fThreshhold ); + float v4 = NoisyStarField( floorSample + vec2( 1.0, 1.0 ), fThreshhold ); + + float StarVal = v1 * ( 1.0 - fractX ) * ( 1.0 - fractY ) + + v2 * ( 1.0 - fractX ) * fractY + + v3 * fractX * ( 1.0 - fractY ) + + v4 * fractX * fractY; + return StarVal; +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 iResolution = imageSize(image); + // Sky Background Color + //vec3 vColor = vec3( 0.1, 0.2, 0.4 ) * fragCoord.y / iResolution.y; + vec3 vColor = PushConstants.data1.xyz * fragCoord.y / iResolution.y; + + // Note: Choose fThreshhold in the range [0.99, 0.9999]. + // Higher values (i.e., closer to one) yield a sparser starfield. + float StarFieldThreshhold = PushConstants.data1.w;//0.97; + + // Stars with a slow crawl. + float xRate = 0.2; + float yRate = -0.06; + vec2 vSamplePos = fragCoord.xy + vec2( xRate * float( 1 ), yRate * float( 1 ) ); + float StarVal = StableStarField( vSamplePos, StarFieldThreshhold ); + vColor += vec3( StarVal ); + + fragColor = vec4(vColor, 1.0); +} + + + +void main() +{ + vec4 value = vec4(0.0, 0.0, 0.0, 1.0); + ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = imageSize(image); + if(texelCoord.x < size.x && texelCoord.y < size.y) + { + vec4 color; + mainImage(color,texelCoord); + + imageStore(image, texelCoord, color); + } +} + diff --git a/sky.comp.spv b/sky.comp.spv new file mode 100644 index 0000000000000000000000000000000000000000..b4003abd5791ab7e9329909ce9379bcfd218393c GIT binary patch literal 6132 zcmZvf36zy(8OPt*7!*(mkVT_GN=%V}03{Mx45R}xfSP8hmzjYXz09S#!vto9nPpm9 zougrvj-l;~SZ#}-|yb<8Q&TGIL~>W|NmLv<@@f?)Ht&>rG_*n zjZROc`ZGQ?!lb00j~$_IJ)%uyC%ChomM|&37mr8l-IuoB!r!^f3=i1k7D{bv!>IF;clUrk&@qMMOD=XznAGX{PLtLe=v&J>2>FAx^ zrD}J9k4Epx@ItUto>?kWp*bxAZ!h(gwiWT~(Ddo(Kg%A?X+z+%>pjm9mn)UN`o0j; zln6}&^Xl=04Xby~Fx-^RHI_?Z?ajk{&8cG;Zc1B>eQ`bZf?>Yqbm1`El)Nwl3+l1% zPrfFOZWtZ_KfMPn=^Aj)#;$6myraLT(p#HtkDTX#cGf}kK(YDm{Y~JGN^iBl)Z1@; zZ?BwBXQ{u$b_{X83v#{-5$D~NV|hcIZ-X4mpxUUk7u~+2+PyOC(~?@#t?)xPZP;CG zPPgORxv|tAbKeQxHH2Hz-QeMOx;fof)QdZD0G#jN{Uh+h)+m*C)O)m~?|}Qec6OCl zaxb`uz7xK?Tg&IIE>|{}%4>QDDjg;FcFig9?&nX?)t-wiQNw>jcMRR_=JZc+KEuB< zyqbK^X9nUE_^3JrH`UIy?8O+l@9BUN14Vg?lAIuuo*9IPqmQ!Yc4?-I+uYVjma%QleLo&ZUYfdh> zX0$m|(B{aS<81bmx9%ijo`-Hje2#k->N?;QXeE<%8Q^aPm{xxogqR;f(py z2#CFTPvJ0j39Q)r354!=8cXqMwft>L_KpXEKS?}j)>$#?w{RwQ}wb@K; z9p4){WfroCvp3&!^0D_5FfC|%PC-A6SXb=KetUB4cO|g#ephnrcO|gzf!`JJQo32& z{W*!+&i6yaHLfSZ_5G;e|@s9lz*@JW!u|F#~W1UyLoK?DK zzd+1yL(F&o9zoTie7LC6X8^&TFKxW2Jy@Af$Kbi{Q9 zKDxl3gZ>$ad2NU@S^FqN{<5Oi^9yb!dMpvfyZ~uH{2kHn+U+GDwU5EJcAT+!FGS>{ z_HhMwEIMk>#*W(ht?k_MG2l|KQohHKL&UR=z6BDUu%uisj7 zvDU@da)Doh?F{#l?>q5Q#29(sg{5d8-vxaqA*UkFtQ~bu#?I?516%jfVy@-b#whu0Pw=+6x_|}Z@856U-9NYOl zL+$(wCxLzPGn@d9Gn|NRufx%ChU>873_a%!8TL%i$guviGOYjX4C_B<1U@&z#&6EB z@f{=ZmJAzzL57XLa0Kqnu$ux)!u;qaKXv@G$8Js zHJs-oi2F1Pi9P>l!Cys;H9m$|M?UgCj_o{o-Y3BF?R+K8`y?V4;%~nErx0uEcU^;M zx#i^DhW<1X>)MAMbA6`pnd3Q$wLPOd3fuF!3o)k^{aM8IMUBs48z1-o^Vo8}7ZGz0 zwsj)rUTisI+F65jzkryl&$}$QAF-c4=kjjsM`D&QV$13CUWlXTm#|%vKJSP)zK>tV zo`jgA&lvBSeB^%xTQ2tgtJrcO#<}-jLk1CN7JnU)Gv2y`Xt{|02DaP=;zyw$K;(Ra kdk|aByA|9w3$78}&hB_OA41GGUcdWkPv2R6@&B0r13pK2p#T5? literal 0 HcmV?d00001 diff --git a/vk_engine.cpp b/vk_engine.cpp index bf61633..da914ab 100644 --- a/vk_engine.cpp +++ b/vk_engine.cpp @@ -19,7 +19,7 @@ #define VMA_IMPLEMENTATION #include "vk_mem_alloc.h" -constexpr bool bUseValidationLayers = false; +constexpr bool bUseValidationLayers = true; VulkanEngine* loadedEngine = nullptr; @@ -202,17 +202,35 @@ void VulkanEngine::run() continue; } - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplSDL2_NewFrame(); - ImGui::NewFrame(); - - ImGui::ShowDemoWindow(); - ImGui::Render(); - + render_imgui(); draw(); } } +void VulkanEngine::render_imgui() { + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + + ImGui::NewFrame(); + + if (ImGui::Begin("background")) { + + ComputeEffect& selected = backgroundEffects[currentBackgroundEffect]; + + ImGui::Text("Selected effect: %s", selected.name); + + ImGui::SliderInt("Effect Index", ¤tBackgroundEffect,0, backgroundEffects.size() - 1); + + ImGui::InputFloat4("data1",(float*)& selected.data.data1); + ImGui::InputFloat4("data2",(float*)& selected.data.data2); + ImGui::InputFloat4("data3",(float*)& selected.data.data3); + ImGui::InputFloat4("data4",(float*)& selected.data.data4); + } + ImGui::End(); + + ImGui::Render(); +} + void VulkanEngine::init_vulkan() { vkb::InstanceBuilder builder; @@ -394,13 +412,14 @@ void VulkanEngine::init_sync_structures() { void VulkanEngine::draw_background(VkCommandBuffer cmd) { -// bind the gradient drawing compute pipeline - vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _gradientPipeline); + ComputeEffect &effect = backgroundEffects[currentBackgroundEffect]; + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, effect.pipeline); - // bind the descriptor set containing the draw image for the compute pipeline vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _gradientPipelineLayout, 0, 1, &_drawImageDescriptors, 0, nullptr); - // execute the compute pipeline dispatch. We are using 16x16 workgroup size so we need to divide by it + vkCmdPushConstants(cmd, _gradientPipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(ComputePushConstants), &effect.data); + vkCmdDispatch(cmd, std::ceil(_drawExtent.width / 16.0), std::ceil(_drawExtent.height / 16.0), 1); } @@ -461,19 +480,28 @@ void VulkanEngine::init_background_pipelines() computeLayout.pSetLayouts = &_drawImageDescriptorLayout; computeLayout.setLayoutCount = 1; + VkPushConstantRange pushConstant{}; + pushConstant.offset = 0; + pushConstant.size = sizeof(ComputePushConstants) ; + pushConstant.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + + computeLayout.pPushConstantRanges = &pushConstant; + computeLayout.pushConstantRangeCount = 1; + VK_CHECK(vkCreatePipelineLayout(_device, &computeLayout, nullptr, &_gradientPipelineLayout)); - VkShaderModule computeDrawShader; + VkShaderModule gradientShader; + bool good = vkutil::load_shader_module("gradient_color.comp.spv", _device, &gradientShader); + assert(good && "failed to load gradient_color.comp.spv"); - if(!vkutil::load_shader_module("gradient.comp.spv", _device, &computeDrawShader)) { - std::println("Error when building compute shader"); - } + VkShaderModule skyShader; + good = vkutil::load_shader_module("sky.comp.spv", _device, &skyShader); VkPipelineShaderStageCreateInfo stageinfo{}; stageinfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stageinfo.pNext = nullptr; stageinfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; - stageinfo.module = computeDrawShader; + stageinfo.module = gradientShader; stageinfo.pName = "main"; VkComputePipelineCreateInfo computePipelineCreateInfo{}; @@ -482,13 +510,37 @@ void VulkanEngine::init_background_pipelines() computePipelineCreateInfo.layout = _gradientPipelineLayout; computePipelineCreateInfo.stage = stageinfo; - VK_CHECK(vkCreateComputePipelines(_device,VK_NULL_HANDLE,1, &computePipelineCreateInfo, nullptr, &_gradientPipeline)); + ComputeEffect gradient; + gradient.layout = _gradientPipelineLayout; + gradient.name = "gradient"; + gradient.data = {}; + gradient.data.data1 = glm::vec4(1, 0, 0, 1); + gradient.data.data2 = glm::vec4(0, 0, 1, 1); - vkDestroyShaderModule(_device, computeDrawShader, nullptr); + VK_CHECK(vkCreateComputePipelines(_device, VK_NULL_HANDLE, 1, &computePipelineCreateInfo, nullptr, &gradient.pipeline)); - _mainDeletionQueue.push_function([&]() { + // change the shader module only to create the sky + computePipelineCreateInfo.stage.module = skyShader; + ComputeEffect sky; + sky.layout = _gradientPipelineLayout; + sky.name = "sky"; + sky.data = {}; + // default sky + sky.data.data1 = glm::vec4(0.1, 0.2, 0.4, 0.97); + + VK_CHECK(vkCreateComputePipelines(_device, VK_NULL_HANDLE, 1, &computePipelineCreateInfo, nullptr, &sky.pipeline)); + + + backgroundEffects.push_back(gradient); + backgroundEffects.push_back(sky); + + vkDestroyShaderModule(_device, gradientShader, nullptr); + vkDestroyShaderModule(_device, skyShader, nullptr); + + _mainDeletionQueue.push_function([=,this]() { vkDestroyPipelineLayout(_device, _gradientPipelineLayout, nullptr); - vkDestroyPipeline(_device, _gradientPipeline, nullptr); + vkDestroyPipeline(_device, sky.pipeline, nullptr); + vkDestroyPipeline(_device, sky.pipeline, nullptr); }); } diff --git a/vk_engine.h b/vk_engine.h index 712beac..5a7903b 100644 --- a/vk_engine.h +++ b/vk_engine.h @@ -6,6 +6,22 @@ #include #include +struct ComputePushConstants { + glm::vec4 data1; + glm::vec4 data2; + glm::vec4 data3; + glm::vec4 data4; +}; + +struct ComputeEffect { + const char *name; + VkPipeline pipeline; + VkPipelineLayout layout; + + ComputePushConstants data; +}; + + struct DeletionQueue { std::deque> deletors; @@ -85,6 +101,10 @@ public: struct SDL_Window* _window{ nullptr }; DeletionQueue _mainDeletionQueue; + // imgui shader stuff + std::vector backgroundEffects; + int currentBackgroundEffect{0}; + static VulkanEngine& Get(); //initializes everything in the engine @@ -115,4 +135,5 @@ private: void destroy_swapchain(); void draw_background(VkCommandBuffer cmd); void draw_imgui(VkCommandBuffer cmd, VkImageView targetImageView); + void render_imgui(); };