From ec2955436dfa6e96ae03d1b5f9fede585f71d374 Mon Sep 17 00:00:00 2001 From: Alexandre Date: Thu, 23 Jul 2026 20:48:42 +0200 Subject: [PATCH] Added new shaders, modified logs messages, continued the tutorial. --- shaders/gradient_color.comp | 29 ++++ shaders/sky.comp | 91 +++++++++++ src/Engine/engine/callbacks.h | 2 +- src/Engine/engine/engine.h | 4 +- src/Engine/engine/initializers.h | 24 +++ src/Engine/engine/pipelines.h | 269 +++++++++++++++++++++++++------ 6 files changed, 368 insertions(+), 51 deletions(-) create mode 100644 shaders/gradient_color.comp create mode 100644 shaders/sky.comp diff --git a/shaders/gradient_color.comp b/shaders/gradient_color.comp new file mode 100644 index 0000000..b467d86 --- /dev/null +++ b/shaders/gradient_color.comp @@ -0,0 +1,29 @@ +#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/shaders/sky.comp b/shaders/sky.comp new file mode 100644 index 0000000..c78cf64 --- /dev/null +++ b/shaders/sky.comp @@ -0,0 +1,91 @@ +#version 450 +layout (local_size_x = 16, local_size_y = 16) in; +layout(rgba16f,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/src/Engine/engine/callbacks.h b/src/Engine/engine/callbacks.h index 6d4e726..93b5986 100644 --- a/src/Engine/engine/callbacks.h +++ b/src/Engine/engine/callbacks.h @@ -17,7 +17,7 @@ class Callback { void Callback::keyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { #ifdef DEBUG - fmt::println("Key pressed, key {}", key); + fmt::println("[Engine:Info] Key pressed, key {}", key); #endif } diff --git a/src/Engine/engine/engine.h b/src/Engine/engine/engine.h index 8f8f8e8..9c02978 100644 --- a/src/Engine/engine/engine.h +++ b/src/Engine/engine/engine.h @@ -3,7 +3,7 @@ // Normal header // Current progress -// 3 - The render Pipeline, not started it yet +// 3 - Graphics Pipeline - Setting up the render pipeline #include "descriptors.h" // Required for public interface custom allocator #include "Common/Types.h" @@ -1104,7 +1104,7 @@ void Engine::buildImgui() { // Main ImGui things ImGui::ShowDemoWindow(); - if(ImGui::Begin("background")) { + if(ImGui::Begin("Background Shader control")) { ComputeEffect& selected = backgroundEffects[currentBackgroundEffect]; ImGui::Text( diff --git a/src/Engine/engine/initializers.h b/src/Engine/engine/initializers.h index 6114e80..e0ea423 100644 --- a/src/Engine/engine/initializers.h +++ b/src/Engine/engine/initializers.h @@ -69,6 +69,12 @@ namespace vkinit { VkRenderingAttachmentInfo* colorAttachment, VkRenderingAttachmentInfo* depthAttachment ); + + VkPipelineShaderStageCreateInfo pipelineShaderStageCreateInfo( + VkShaderStageFlagBits stage, + VkShaderModule shaderModule, + const char * entry = "main" + ); } @@ -303,6 +309,24 @@ namespace vkinit { }; return renderInfo; } + + VkPipelineShaderStageCreateInfo pipelineShaderStageCreateInfo( + VkShaderStageFlagBits stage, + VkShaderModule shaderModule, + const char * entry + ) { + VkPipelineShaderStageCreateInfo info { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + + .stage = stage, + .module = shaderModule, + + .pName = entry + }; + + return info; + } } #endif #endif diff --git a/src/Engine/engine/pipelines.h b/src/Engine/engine/pipelines.h index 33566f6..fe0e901 100644 --- a/src/Engine/engine/pipelines.h +++ b/src/Engine/engine/pipelines.h @@ -17,6 +17,36 @@ namespace vkutil { ); }; +class PipelineBuilder { + public: + std::vector _shaderStages; + + VkPipelineInputAssemblyStateCreateInfo _inputAssembly; + VkPipelineRasterizationStateCreateInfo _rasterizer; + VkPipelineColorBlendAttachmentState _colorBlendAttachment; + VkPipelineMultisampleStateCreateInfo _multisampling; + VkPipelineLayout _pipelineLayout; + VkPipelineDepthStencilStateCreateInfo _depthStencil; + VkPipelineRenderingCreateInfo _renderInfo; + + PipelineBuilder() { clear(); } + + void clear(); + + VkPipeline buildPipeline( + VkDevice device + ); + + void setShaders( + VkShaderModule vertexShader, + VkShaderModule fragmentShader + ); + + void setInputTopology( + VkPrimitiveTopology topology + ); +}; + #endif #ifdef PIPELINES_IMPL @@ -29,68 +59,211 @@ namespace vkutil { // Custom imports #include "initializers.h" -namespace vkutil { - bool loadShaderModule( - const char* filePath, - VkDevice device, - VkShaderModule* outShaderModule - ) { - #ifdef DEBUG - fmt::println("[Engine:Utils] Loading shader located at {}...", filePath); - #endif - // Open the file, with the cursor at the end - std::ifstream file( - filePath, - std::ios::ate | std::ios::binary - ); +bool vkutil::loadShaderModule( + const char* filePath, + VkDevice device, + VkShaderModule* outShaderModule +) { + #ifdef DEBUG + fmt::println("[Engine:Utils] Loading shader located at {}...", filePath); + #endif + // Open the file, with the cursor at the end + std::ifstream file( + filePath, + std::ios::ate | std::ios::binary + ); - if (!file.is_open()) { - return false; - } + if (!file.is_open()) { + return false; + } - // Find the size of the file by looking - // at the cursor - size_t fileSize = (size_t)file.tellg(); + // Find the size of the file by looking + // at the cursor + size_t fileSize = (size_t)file.tellg(); - // Allocate a big enough vector to suit SPIR-V - std::vector buffer(fileSize/ sizeof(uint32_t)); + // Allocate a big enough vector to suit SPIR-V + std::vector buffer(fileSize/ sizeof(uint32_t)); - file.seekg(0); // Put the cursor at the begining + file.seekg(0); // Put the cursor at the begining - // Load the file in the buffer - file.read((char*)buffer.data(), fileSize); + // Load the file in the buffer + file.read((char*)buffer.data(), fileSize); - file.close(); + file.close(); - // Create the shader module - VkShaderModuleCreateInfo createInfo { - .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - .pNext = nullptr, - .codeSize = buffer.size() * sizeof(uint32_t), // Convert to bytes - .pCode = buffer.data() - }; + // Create the shader module + VkShaderModuleCreateInfo createInfo { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .pNext = nullptr, + .codeSize = buffer.size() * sizeof(uint32_t), // Convert to bytes + .pCode = buffer.data() + }; - // Check that everything went well - VkShaderModule shaderModule; + // Check that everything went well + VkShaderModule shaderModule; - if (vkCreateShaderModule( + if (vkCreateShaderModule( + device, + &createInfo, + nullptr, + &shaderModule + ) != VK_SUCCESS){ + return false; + } + *outShaderModule = shaderModule; + + #ifdef DEBUG + fmt::println("[Engine:Utils] Shader loaded from {} and size {}", filePath, fileSize); + #endif + + return true; +} + +void PipelineBuilder::clear() { + #ifdef DEBUG + fmt::println("[PipelineBuilder] Clearing pipeline..."); + #endif + // Clear structs + _inputAssembly = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO + }; + + _rasterizer = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO + }; + + _colorBlendAttachment = {}; + + _multisampling = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO + }; + + _pipelineLayout = {}; + + _depthStencil = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO + }; + + _renderInfo = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO + }; + + _shaderStages.clear(); + + #ifdef DEBUG + fmt::println("[PipelineBuilder] Pipeline cleared !"); + #endif +} + +VkPipeline PipelineBuilder::buildPipeline ( + VkDevice device +) { + #ifdef DEBUG + fmt::println("[PipelineBuilder] Building rasterisation pipeline..."); + #endif + // Make the viewport state from our stored viewport and scissor + // This makes it that it won't support multiple viewports or scissors + VkPipelineViewportStateCreateInfo viewportState { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pNext = nullptr, + + .viewportCount = 1, + .scissorCount = 1 + }; + + // Setup placeholder color blending. + // Need to be changed when using transparent objects, which we aren't rn + // The blending is "no blend", but we write it to the color attachment + VkPipelineColorBlendStateCreateInfo colorBlending { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = nullptr, + + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_COPY, + .attachmentCount = 1, + .pAttachments = &_colorBlendAttachment + }; + // Completely clear VertexInputStateCreateInfo, as we have no need for it + VkPipelineVertexInputStateCreateInfo _vertexInputInfo { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO + }; + + // Dynamic Viewport + VkDynamicState state[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + + VkPipelineDynamicStateCreateInfo dynamicInfo { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = 2, + .pDynamicStates = &state[0] + }; + + // Build the pipeline + // Uses all the class members info structs + VkGraphicsPipelineCreateInfo pipelineInfo { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = &_renderInfo, + + .stageCount = (uint32_t)_shaderStages.size(), + .pStages = _shaderStages.data(), + + .pVertexInputState = &_vertexInputInfo, + .pInputAssemblyState = &_inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &_rasterizer, + .pMultisampleState = &_multisampling, + .pDepthStencilState = &_depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicInfo, + + .layout = _pipelineLayout + }; + + // Check the creation of the pipeline + // I don't use the VK_CHECK() + // 'cause there might be pipeline-specific options + VkPipeline newPipeline; + + if( + vkCreateGraphicsPipelines( device, - &createInfo, + VK_NULL_HANDLE, + 1, + &pipelineInfo, nullptr, - &shaderModule - ) != VK_SUCCESS){ - return false; - } - *outShaderModule = shaderModule; + &newPipeline + ) != VK_SUCCESS + ) { + fmt::println("[PipelineBuilder] Failed to create pipeline !"); + return VK_NULL_HANDLE; // Graphics pipeline failed to be created + } else { + return newPipeline; + } - #ifdef DEBUG - fmt::println("[Engine:Utils] Shader loaded from {} and size {}", filePath, fileSize); - #endif +} - return true; - } -}; +void PipelineBuilder::setShaders( + VkShaderModule vertexShader, + VkShaderModule fragmentShader +) { + _shaderStages.clear(); + _shaderStages.push_back( + vkinit::pipelineShaderStageCreateInfo( + VK_SHADER_STAGE_VERTEX_BIT, + vertexShader + ) + ); + + _shaderStages.push_back( + vkinit::pipelineShaderStageCreateInfo( + VK_SHADER_STAGE_FRAGMENT_BIT, + fragmentShader + ) + ); +} #endif #endif