Next part of the tutorial where we have a couple compute shaders controlled by ImGUI.

This commit is contained in:
Zed A. Shaw 2025-12-02 13:14:48 -05:00
parent 14f307b1b3
commit a996440c61
7 changed files with 219 additions and 21 deletions

View file

@ -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 $@ $<

31
gradient_color.comp Normal file
View file

@ -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));
}
}

BIN
gradient_color.comp.spv Normal file

Binary file not shown.

92
sky.comp Normal file
View file

@ -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);
}
}

BIN
sky.comp.spv Normal file

Binary file not shown.

View file

@ -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", &currentBackgroundEffect,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);
});
}

View file

@ -6,6 +6,22 @@
#include <vk_types.h>
#include <vk_descriptors.h>
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<std::function<void()>> deletors;
@ -85,6 +101,10 @@ public:
struct SDL_Window* _window{ nullptr };
DeletionQueue _mainDeletionQueue;
// imgui shader stuff
std::vector<ComputeEffect> 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();
};