Skip to content

Instantly share code, notes, and snippets.

@Lightnet
Last active February 28, 2025 00:05
Show Gist options
  • Save Lightnet/b640fe7b72a860c9296c3c73a29fc3e6 to your computer and use it in GitHub Desktop.
Save Lightnet/b640fe7b72a860c9296c3c73a29fc3e6 to your computer and use it in GitHub Desktop.
Grok3 SDL3 Vulkan CMake build. Most simple triangle test. Pure C Language. VS2022

For building pure C language SDL3 and Vulkan Render Graphic.

Most simple triangle test in pure C Language. Use Grok3 AI agent to debug how it works.

Required:

  • Required Vulkan SDK for shader compiler to work. As using the cmake to build the shader files.
  • VS2022 (tested)
  • Python 3.13
  • nasm
  • VulkanSDK 1.4.304.1
  • it will required github download those repo to build application.

Notes:

  • there are tools need to build for SDL3 and other Vulkan to build those binary and dlls.
  • VulkanSDK version got to match or update to reflect the builds version from and to CMake repo of Vulkan Libs.
  • There will be bugs since it simple triangle if change it will break. Not sure where to change some code...
project_root/
├── CMakeLists.txt
├── src/
│   └── main.c
├── assets/
│   ├── frag.glsl  (Fragment shader)
│   └── vert.glsl  (Vertex shader)
├── build.bat
└── build/         (Generated build directory)

Show there no error on SDL_vulkan setup for now.

cmake_minimum_required(VERSION 3.20)
project(VulkanSDL3Project C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_COMPILER "")
set(CMAKE_CXX_FOUND FALSE)
include(FetchContent)
# SDL3
FetchContent_Declare(
SDL3
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG main
)
set(SDL_VULKAN ON CACHE BOOL "Enable Vulkan support" FORCE)
set(SDL_DIRECTX OFF CACHE BOOL "Disable DirectX" FORCE)
set(SDL_OPENGL OFF CACHE BOOL "Disable OpenGL" FORCE)
set(SDL_OPENGLES OFF CACHE BOOL "Disable OpenGLES" FORCE)
FetchContent_MakeAvailable(SDL3)
# cglm
FetchContent_Declare(
cglm
GIT_REPOSITORY https://github.com/recp/cglm.git
GIT_TAG v0.9.6
)
FetchContent_MakeAvailable(cglm)
# Vulkan Headers
FetchContent_Declare(
Vulkan-Headers
GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Headers.git
GIT_TAG vulkan-sdk-1.4.304.1
)
FetchContent_MakeAvailable(Vulkan-Headers)
# Vulkan Loader
FetchContent_Declare(
Vulkan-Loader
GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Loader.git
GIT_TAG vulkan-sdk-1.4.304.1
)
FetchContent_MakeAvailable(Vulkan-Loader)
find_package(Vulkan REQUIRED)
add_executable(${PROJECT_NAME} src/main.c)
set_source_files_properties(src/main.c PROPERTIES LANGUAGE C)
target_include_directories(${PROJECT_NAME} PRIVATE
${Vulkan_INCLUDE_DIRS}
)
target_link_libraries(${PROJECT_NAME} PRIVATE
SDL3::SDL3
cglm
Vulkan::Vulkan
)
# Shader compilation
find_program(GLSLC glslc REQUIRED HINTS ENV VULKAN_SDK PATH_SUFFIXES bin)
set(SHADER_SRC_DIR ${CMAKE_SOURCE_DIR}/assets)
set(SHADER_OUT_DIR ${CMAKE_BINARY_DIR}/shaders)
file(MAKE_DIRECTORY ${SHADER_OUT_DIR})
add_custom_command(
OUTPUT ${SHADER_OUT_DIR}/frag.spv
COMMAND ${GLSLC} -fshader-stage=frag ${SHADER_SRC_DIR}/frag.glsl -o ${SHADER_OUT_DIR}/frag.spv
DEPENDS ${SHADER_SRC_DIR}/frag.glsl
COMMENT "Compiling frag.glsl to SPIR-V (fragment shader)"
)
add_custom_command(
OUTPUT ${SHADER_OUT_DIR}/vert.spv
COMMAND ${GLSLC} -fshader-stage=vert ${SHADER_SRC_DIR}/vert.glsl -o ${SHADER_OUT_DIR}/vert.spv
DEPENDS ${SHADER_SRC_DIR}/vert.glsl
COMMENT "Compiling vert.glsl to SPIR-V (vertex shader)"
)
add_custom_target(
Shaders ALL
DEPENDS ${SHADER_OUT_DIR}/frag.spv ${SHADER_OUT_DIR}/vert.spv
)
add_dependencies(${PROJECT_NAME} Shaders)
# Copy DLLs to output directory
if(WIN32)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:SDL3::SDL3>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMENT "Copying SDL3.dll to output directory"
)
if(DEFINED ENV{VULKAN_SDK})
set(VULKAN_DLL "$ENV{VULKAN_SDK}/Bin/vulkan-1.dll")
if(EXISTS ${VULKAN_DLL})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${VULKAN_DLL}
$<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMENT "Copying vulkan-1.dll from Vulkan SDK to output directory"
)
endif()
endif()
endif()
# Copy shaders to output directory
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${SHADER_OUT_DIR}/vert.spv
$<TARGET_FILE_DIR:${PROJECT_NAME}>/vert.spv
COMMENT "Copying vert.spv to output directory"
)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${SHADER_OUT_DIR}/frag.spv
$<TARGET_FILE_DIR:${PROJECT_NAME}>/frag.spv
COMMENT "Copying frag.spv to output directory"
)
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
#include <vulkan/vulkan.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WIDTH 800
#define HEIGHT 600
struct VulkanContext {
VkInstance instance;
VkPhysicalDevice physicalDevice;
VkDevice device;
VkQueue graphicsQueue;
VkSurfaceKHR surface;
VkSwapchainKHR swapchain;
VkRenderPass renderPass;
VkPipelineLayout pipelineLayout;
VkPipeline graphicsPipeline;
VkCommandPool commandPool;
VkCommandBuffer commandBuffer;
VkBuffer vertexBuffer;
VkDeviceMemory vertexMemory;
VkSemaphore imageAvailableSemaphore;
VkSemaphore renderFinishedSemaphore;
VkFence inFlightFence;
uint32_t imageCount;
VkImage* swapchainImages;
VkImageView* swapchainImageViews;
VkFramebuffer* swapchainFramebuffers;
} vkCtx = {0};
uint32_t find_memory_type(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(vkCtx.physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
printf("Failed to find suitable memory type!\n");
exit(1);
}
void init_vulkan(SDL_Window* window) {
VkApplicationInfo appInfo = {VK_STRUCTURE_TYPE_APPLICATION_INFO};
appInfo.pApplicationName = "Vulkan SDL3";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_3;
const char* extensions[] = {VK_KHR_SURFACE_EXTENSION_NAME, "VK_KHR_win32_surface"};
VkInstanceCreateInfo createInfo = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
createInfo.pApplicationInfo = &appInfo;
createInfo.enabledExtensionCount = 2;
createInfo.ppEnabledExtensionNames = extensions;
createInfo.enabledLayerCount = 0;
if (vkCreateInstance(&createInfo, NULL, &vkCtx.instance) != VK_SUCCESS) {
printf("Failed to create Vulkan instance\n");
exit(1);
}
if (!SDL_Vulkan_CreateSurface(window, vkCtx.instance, NULL, &vkCtx.surface)) {
printf("Failed to create Vulkan surface: %s\n", SDL_GetError());
exit(1);
}
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(vkCtx.instance, &deviceCount, NULL);
VkPhysicalDevice* devices = malloc(deviceCount * sizeof(VkPhysicalDevice));
vkEnumeratePhysicalDevices(vkCtx.instance, &deviceCount, devices);
vkCtx.physicalDevice = devices[0];
free(devices);
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(vkCtx.physicalDevice, &queueFamilyCount, NULL);
VkQueueFamilyProperties* queueFamilies = malloc(queueFamilyCount * sizeof(VkQueueFamilyProperties));
vkGetPhysicalDeviceQueueFamilyProperties(vkCtx.physicalDevice, &queueFamilyCount, queueFamilies);
uint32_t graphicsFamily = -1;
for (uint32_t i = 0; i < queueFamilyCount; i++) {
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
graphicsFamily = i;
break;
}
}
free(queueFamilies);
float queuePriority = 1.0f;
VkDeviceQueueCreateInfo queueCreateInfo = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
queueCreateInfo.queueFamilyIndex = graphicsFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
const char* deviceExtensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
VkDeviceCreateInfo deviceCreateInfo = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
deviceCreateInfo.queueCreateInfoCount = 1;
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
deviceCreateInfo.enabledExtensionCount = 1;
deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions;
if (vkCreateDevice(vkCtx.physicalDevice, &deviceCreateInfo, NULL, &vkCtx.device) != VK_SUCCESS) {
printf("Failed to create logical device\n");
exit(1);
}
vkGetDeviceQueue(vkCtx.device, graphicsFamily, 0, &vkCtx.graphicsQueue);
VkSwapchainCreateInfoKHR swapchainInfo = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
swapchainInfo.surface = vkCtx.surface;
swapchainInfo.minImageCount = 2;
swapchainInfo.imageFormat = VK_FORMAT_B8G8R8A8_UNORM;
swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
swapchainInfo.imageExtent.width = WIDTH;
swapchainInfo.imageExtent.height = HEIGHT;
swapchainInfo.imageArrayLayers = 1;
swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchainInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
swapchainInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;
swapchainInfo.clipped = VK_TRUE;
if (vkCreateSwapchainKHR(vkCtx.device, &swapchainInfo, NULL, &vkCtx.swapchain) != VK_SUCCESS) {
printf("Failed to create swapchain\n");
exit(1);
}
vkGetSwapchainImagesKHR(vkCtx.device, vkCtx.swapchain, &vkCtx.imageCount, NULL);
vkCtx.swapchainImages = malloc(vkCtx.imageCount * sizeof(VkImage));
vkGetSwapchainImagesKHR(vkCtx.device, vkCtx.swapchain, &vkCtx.imageCount, vkCtx.swapchainImages);
vkCtx.swapchainImageViews = malloc(vkCtx.imageCount * sizeof(VkImageView));
for (uint32_t i = 0; i < vkCtx.imageCount; i++) {
VkImageViewCreateInfo viewInfo = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
viewInfo.image = vkCtx.swapchainImages[i];
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = VK_FORMAT_B8G8R8A8_UNORM;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(vkCtx.device, &viewInfo, NULL, &vkCtx.swapchainImageViews[i]) != VK_SUCCESS) {
printf("Failed to create image views\n");
exit(1);
}
}
VkAttachmentDescription colorAttachment = {};
colorAttachment.format = VK_FORMAT_B8G8R8A8_UNORM;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference colorAttachmentRef = {};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
VkRenderPassCreateInfo renderPassInfo = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
if (vkCreateRenderPass(vkCtx.device, &renderPassInfo, NULL, &vkCtx.renderPass) != VK_SUCCESS) {
printf("Failed to create render pass\n");
exit(1);
}
vkCtx.swapchainFramebuffers = malloc(vkCtx.imageCount * sizeof(VkFramebuffer));
for (uint32_t i = 0; i < vkCtx.imageCount; i++) {
VkFramebufferCreateInfo framebufferInfo = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
framebufferInfo.renderPass = vkCtx.renderPass;
framebufferInfo.attachmentCount = 1;
framebufferInfo.pAttachments = &vkCtx.swapchainImageViews[i];
framebufferInfo.width = WIDTH;
framebufferInfo.height = HEIGHT;
framebufferInfo.layers = 1;
if (vkCreateFramebuffer(vkCtx.device, &framebufferInfo, NULL, &vkCtx.swapchainFramebuffers[i]) != VK_SUCCESS) {
printf("Failed to create framebuffer\n");
exit(1);
}
}
VkCommandPoolCreateInfo poolInfo = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
poolInfo.queueFamilyIndex = graphicsFamily;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; // Fix for reset
if (vkCreateCommandPool(vkCtx.device, &poolInfo, NULL, &vkCtx.commandPool) != VK_SUCCESS) {
printf("Failed to create command pool\n");
exit(1);
}
VkCommandBufferAllocateInfo allocInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
allocInfo.commandPool = vkCtx.commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = 1;
if (vkAllocateCommandBuffers(vkCtx.device, &allocInfo, &vkCtx.commandBuffer) != VK_SUCCESS) {
printf("Failed to allocate command buffer\n");
exit(1);
}
VkSemaphoreCreateInfo semaphoreInfo = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
VkFenceCreateInfo fenceInfo = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
if (vkCreateSemaphore(vkCtx.device, &semaphoreInfo, NULL, &vkCtx.imageAvailableSemaphore) != VK_SUCCESS ||
vkCreateSemaphore(vkCtx.device, &semaphoreInfo, NULL, &vkCtx.renderFinishedSemaphore) != VK_SUCCESS ||
vkCreateFence(vkCtx.device, &fenceInfo, NULL, &vkCtx.inFlightFence) != VK_SUCCESS) {
printf("Failed to create synchronization objects\n");
exit(1);
}
}
void create_triangle() {
float vertices[] = {
0.0f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // Top, red
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // Bottom-left, green
0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // Bottom-right, blue
};
VkBufferCreateInfo bufferInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
bufferInfo.size = sizeof(vertices);
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(vkCtx.device, &bufferInfo, NULL, &vkCtx.vertexBuffer) != VK_SUCCESS) {
printf("Failed to create vertex buffer\n");
exit(1);
}
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(vkCtx.device, vkCtx.vertexBuffer, &memRequirements);
VkMemoryAllocateInfo allocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = find_memory_type(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (vkAllocateMemory(vkCtx.device, &allocInfo, NULL, &vkCtx.vertexMemory) != VK_SUCCESS) {
printf("Failed to allocate vertex buffer memory\n");
exit(1);
}
vkBindBufferMemory(vkCtx.device, vkCtx.vertexBuffer, vkCtx.vertexMemory, 0);
void* data;
vkMapMemory(vkCtx.device, vkCtx.vertexMemory, 0, bufferInfo.size, 0, &data);
memcpy(data, vertices, sizeof(vertices));
vkUnmapMemory(vkCtx.device, vkCtx.vertexMemory);
}
void create_pipeline() {
FILE* vertFile = fopen("vert.spv", "rb");
if (!vertFile) {
printf("Failed to open vert.spv\n");
exit(1);
}
fseek(vertFile, 0, SEEK_END);
long vertSize = ftell(vertFile);
fseek(vertFile, 0, SEEK_SET);
char* vertShaderCode = malloc(vertSize);
fread(vertShaderCode, 1, vertSize, vertFile);
fclose(vertFile);
FILE* fragFile = fopen("frag.spv", "rb");
if (!fragFile) {
printf("Failed to open frag.spv\n");
exit(1);
}
fseek(fragFile, 0, SEEK_END);
long fragSize = ftell(fragFile);
fseek(fragFile, 0, SEEK_SET);
char* fragShaderCode = malloc(fragSize);
fread(fragShaderCode, 1, fragSize, fragFile);
fclose(fragFile);
VkShaderModuleCreateInfo vertShaderInfo = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
vertShaderInfo.codeSize = vertSize;
vertShaderInfo.pCode = (uint32_t*)vertShaderCode;
VkShaderModule vertModule;
if (vkCreateShaderModule(vkCtx.device, &vertShaderInfo, NULL, &vertModule) != VK_SUCCESS) {
printf("Failed to create vertex shader module\n");
exit(1);
}
VkShaderModuleCreateInfo fragShaderInfo = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
fragShaderInfo.codeSize = fragSize;
fragShaderInfo.pCode = (uint32_t*)fragShaderCode;
VkShaderModule fragModule;
if (vkCreateShaderModule(vkCtx.device, &fragShaderInfo, NULL, &fragModule) != VK_SUCCESS) {
printf("Failed to create fragment shader module\n");
exit(1);
}
VkPipelineShaderStageCreateInfo shaderStages[] = {
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_VERTEX_BIT, vertModule, "main", 0},
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_FRAGMENT_BIT, fragModule, "main", 0}
};
VkVertexInputBindingDescription bindingDesc = {0, 6 * sizeof(float), VK_VERTEX_INPUT_RATE_VERTEX};
VkVertexInputAttributeDescription attrDesc[] = {
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // Position
{1, 0, VK_FORMAT_R32G32B32_SFLOAT, 3 * sizeof(float)} // Color
};
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDesc;
vertexInputInfo.vertexAttributeDescriptionCount = 2;
vertexInputInfo.pVertexAttributeDescriptions = attrDesc;
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
VkViewport viewport = {0.0f, 0.0f, (float)WIDTH, (float)HEIGHT, 0.0f, 1.0f};
VkRect2D scissor = {{0, 0}, {WIDTH, HEIGHT}};
VkPipelineViewportStateCreateInfo viewportState = {VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
VkPipelineRasterizationStateCreateInfo rasterizer = {VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
rasterizer.lineWidth = 1.0f;
VkPipelineMultisampleStateCreateInfo multisampling = {VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_FALSE;
VkPipelineColorBlendStateCreateInfo colorBlending = {VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
if (vkCreatePipelineLayout(vkCtx.device, &pipelineLayoutInfo, NULL, &vkCtx.pipelineLayout) != VK_SUCCESS) {
printf("Failed to create pipeline layout\n");
exit(1);
}
VkGraphicsPipelineCreateInfo pipelineInfo = {VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.layout = vkCtx.pipelineLayout;
pipelineInfo.renderPass = vkCtx.renderPass;
pipelineInfo.subpass = 0;
if (vkCreateGraphicsPipelines(vkCtx.device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &vkCtx.graphicsPipeline) != VK_SUCCESS) {
printf("Failed to create graphics pipeline\n");
exit(1);
}
vkDestroyShaderModule(vkCtx.device, fragModule, NULL);
vkDestroyShaderModule(vkCtx.device, vertModule, NULL);
free(vertShaderCode);
free(fragShaderCode);
}
void record_command_buffer(uint32_t imageIndex) {
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
if (vkBeginCommandBuffer(vkCtx.commandBuffer, &beginInfo) != VK_SUCCESS) {
printf("Failed to begin command buffer\n");
exit(1);
}
VkRenderPassBeginInfo renderPassInfo = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
renderPassInfo.renderPass = vkCtx.renderPass;
renderPassInfo.framebuffer = vkCtx.swapchainFramebuffers[imageIndex];
renderPassInfo.renderArea.offset = (VkOffset2D){0, 0};
renderPassInfo.renderArea.extent = (VkExtent2D){WIDTH, HEIGHT};
VkClearValue clearColor = {{{0.5f, 0.5f, 0.5f, 1.0f}}}; // Gray background
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
vkCmdBeginRenderPass(vkCtx.commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(vkCtx.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkCtx.graphicsPipeline);
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(vkCtx.commandBuffer, 0, 1, &vkCtx.vertexBuffer, offsets);
vkCmdDraw(vkCtx.commandBuffer, 3, 1, 0, 0);
vkCmdEndRenderPass(vkCtx.commandBuffer);
if (vkEndCommandBuffer(vkCtx.commandBuffer) != VK_SUCCESS) {
printf("Failed to end command buffer\n");
exit(1);
}
}
int main(int argc, char* argv[]) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = SDL_CreateWindow("Vulkan SDL3", WIDTH, HEIGHT, SDL_WINDOW_VULKAN);
if (!window) {
printf("Window creation failed: %s\n", SDL_GetError());
return 1;
}
init_vulkan(window);
create_triangle();
create_pipeline();
bool running = true;
SDL_Event event;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) running = false;
}
vkWaitForFences(vkCtx.device, 1, &vkCtx.inFlightFence, VK_TRUE, UINT64_MAX);
vkResetFences(vkCtx.device, 1, &vkCtx.inFlightFence);
uint32_t imageIndex;
VkResult result = vkAcquireNextImageKHR(vkCtx.device, vkCtx.swapchain, UINT64_MAX, vkCtx.imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
if (result != VK_SUCCESS) {
printf("Failed to acquire next image: %d\n", result);
exit(1);
}
vkResetCommandBuffer(vkCtx.commandBuffer, 0);
record_command_buffer(imageIndex);
VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
VkSemaphore waitSemaphores[] = {vkCtx.imageAvailableSemaphore};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &vkCtx.commandBuffer;
VkSemaphore signalSemaphores[] = {vkCtx.renderFinishedSemaphore};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
if (vkQueueSubmit(vkCtx.graphicsQueue, 1, &submitInfo, vkCtx.inFlightFence) != VK_SUCCESS) {
printf("Failed to submit draw command buffer\n");
exit(1);
}
VkPresentInfoKHR presentInfo = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &vkCtx.swapchain;
presentInfo.pImageIndices = &imageIndex;
if (vkQueuePresentKHR(vkCtx.graphicsQueue, &presentInfo) != VK_SUCCESS) {
printf("Failed to present image\n");
exit(1);
}
}
vkDeviceWaitIdle(vkCtx.device);
vkDestroySemaphore(vkCtx.device, vkCtx.renderFinishedSemaphore, NULL);
vkDestroySemaphore(vkCtx.device, vkCtx.imageAvailableSemaphore, NULL);
vkDestroyFence(vkCtx.device, vkCtx.inFlightFence, NULL);
vkDestroyCommandPool(vkCtx.device, vkCtx.commandPool, NULL);
vkDestroyPipeline(vkCtx.device, vkCtx.graphicsPipeline, NULL);
vkDestroyPipelineLayout(vkCtx.device, vkCtx.pipelineLayout, NULL);
for (uint32_t i = 0; i < vkCtx.imageCount; i++) {
vkDestroyFramebuffer(vkCtx.device, vkCtx.swapchainFramebuffers[i], NULL);
vkDestroyImageView(vkCtx.device, vkCtx.swapchainImageViews[i], NULL);
}
free(vkCtx.swapchainFramebuffers);
free(vkCtx.swapchainImages);
free(vkCtx.swapchainImageViews);
vkDestroyRenderPass(vkCtx.device, vkCtx.renderPass, NULL);
vkDestroySwapchainKHR(vkCtx.device, vkCtx.swapchain, NULL);
vkDestroyBuffer(vkCtx.device, vkCtx.vertexBuffer, NULL);
vkFreeMemory(vkCtx.device, vkCtx.vertexMemory, NULL);
vkDestroyDevice(vkCtx.device, NULL);
vkDestroySurfaceKHR(vkCtx.instance, vkCtx.surface, NULL);
vkDestroyInstance(vkCtx.instance, NULL);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
#version 450
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 0) out vec3 fragColor;
void main() {
gl_Position = vec4(inPosition, 1.0); // No transformations, just raw positions
fragColor = inColor;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment