Skip to content

Instantly share code, notes, and snippets.

@Lightnet
Last active February 27, 2025 16:24
Show Gist options
  • Save Lightnet/4b9e8a2da29c0e79cfbb4f79c0769268 to your computer and use it in GitHub Desktop.
Save Lightnet/4b9e8a2da29c0e79cfbb4f79c0769268 to your computer and use it in GitHub Desktop.
SDL3 Vulkan CMake Build Test

PROJECT:

assets
-frag.glsl
-vert.glsl
src
-main.c
CMakeLists.txt

required:

Information:

  • W,A,S,D = Camera control
  • Mouse = Rotate camera

Notes:

  • this is test.
  • move forward as cube is not visiable yet. It render inside out.
@echo off
cmake -S . -B build
cmake --build build
cmake_minimum_required(VERSION 3.16)
project(VulkanSDL3Project C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
Vulkan-Headers
GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Headers.git
GIT_TAG main
)
FetchContent_MakeAvailable(Vulkan-Headers)
FetchContent_Declare(
Vulkan-Loader
GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Loader.git
GIT_TAG main
)
FetchContent_MakeAvailable(Vulkan-Loader)
FetchContent_Declare(
SDL3
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG main
)
FetchContent_MakeAvailable(SDL3)
add_executable(${PROJECT_NAME} src/main.c)
target_link_libraries(${PROJECT_NAME} PRIVATE
Vulkan::Headers
Vulkan::Loader
SDL3::SDL3
)
target_include_directories(${PROJECT_NAME} PRIVATE
${vulkan-headers_SOURCE_DIR}/include
${vulkan-loader_SOURCE_DIR}/include
${sdl3_SOURCE_DIR}/include
)
set(ASSETS_SRC_DIR ${CMAKE_SOURCE_DIR}/assets)
set(ASSETS_DEST_DIR ${CMAKE_BINARY_DIR}/Debug)
file(GLOB SHADER_FILES "${ASSETS_SRC_DIR}/*.spv")
foreach(SHADER_FILE ${SHADER_FILES})
get_filename_component(SHADER_FILE_NAME ${SHADER_FILE} NAME)
configure_file(${SHADER_FILE} ${ASSETS_DEST_DIR}/${SHADER_FILE_NAME} COPYONLY)
endforeach()
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${ASSETS_DEST_DIR}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SHADER_FILES} ${ASSETS_DEST_DIR}
COMMENT "Copying shader files to Debug output directory"
)
#version 450
layout(location = 0) in vec3 frag_color;
layout(location = 0) out vec4 out_color;
void main() {
out_color = vec4(frag_color, 1.0);
}
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
#include <vulkan/vulkan.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <string.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
typedef struct {
float pos[3];
float yaw;
float pitch;
float speed;
} Camera;
typedef struct {
float pos[3];
float color[3];
} Vertex;
static const Vertex cube_vertices[] = {
// Back face (z=-0.5)
{{0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
{{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{-0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
// Front face (z=0.5)
{{0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, -0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, {{-0.5f, -0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
{{-0.5f, -0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, {{-0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
// Left face (x=-0.5)
{{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, {{-0.5f, -0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, {{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}},
{{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}}, {{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}}, {{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}},
// Right face (x=0.5)
{{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 0.0f}}, {{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 0.0f}}, {{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 0.0f}},
{{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 0.0f}},
// Top face (y=0.5)
{{0.5f, 0.5f, -0.5f}, {0.0f, 1.0f, 1.0f}}, {{-0.5f, 0.5f, -0.5f}, {0.0f, 1.0f, 1.0f}}, {{-0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 1.0f}},
{{-0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 1.0f}}, {{0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 1.0f}}, {{0.5f, 0.5f, -0.5f}, {0.0f, 1.0f, 1.0f}},
// Bottom face (y=-0.5)
{{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 1.0f}}, {{0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 1.0f}}, {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 1.0f}},
{{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 1.0f}}, {{-0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 1.0f}}, {{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 1.0f}},
};
typedef struct {
float mvp[16];
float light_pos[3];
} UBO;
typedef struct {
VkInstance instance;
VkPhysicalDevice physical_device;
VkDevice device;
VkQueue graphics_queue;
VkSurfaceKHR surface;
VkSwapchainKHR swapchain;
VkImage* swapchain_images;
uint32_t swapchain_image_count;
VkImageView* swapchain_image_views;
VkRenderPass render_pass;
VkPipelineLayout pipeline_layout;
VkPipeline graphics_pipeline;
VkFramebuffer* framebuffers;
VkCommandPool command_pool;
VkCommandBuffer command_buffer;
VkSemaphore image_available_semaphore;
VkSemaphore render_finished_semaphore;
VkFence in_flight_fence;
VkBuffer vertex_buffer;
VkDeviceMemory vertex_buffer_memory;
VkBuffer uniform_buffer;
VkDeviceMemory uniform_buffer_memory;
VkDescriptorSetLayout descriptor_set_layout;
VkDescriptorPool descriptor_pool;
VkDescriptorSet descriptor_set;
uint32_t graphics_queue_family_index;
uint32_t present_queue_family_index;
VkQueue present_queue;
PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
PFN_vkQueuePresentKHR vkQueuePresentKHR;
VkImage depth_image;
VkDeviceMemory depth_image_memory;
VkImageView depth_image_view;
} VulkanContext;
static void* read_spirv_file(const char* path, size_t* size) {
FILE* file = fopen(path, "rb");
if (!file) {
printf("Failed to open shader file: %s\n", path);
return NULL;
}
fseek(file, 0, SEEK_END);
*size = ftell(file);
fseek(file, 0, SEEK_SET);
void* data = malloc(*size);
fread(data, 1, *size, file);
fclose(file);
return data;
}
static void mat4_identity(float* m) {
memset(m, 0, 16 * sizeof(float));
m[0] = m[5] = m[10] = m[15] = 1.0f;
}
static void mat4_perspective(float* m, float fov, float aspect, float near, float far) {
float tan_half_fov = tanf(fov / 2.0f);
mat4_identity(m);
m[0] = 1.0f / (aspect * tan_half_fov);
m[5] = 1.0f / tan_half_fov;
m[10] = -(far + near) / (far - near);
m[11] = -1.0f;
m[14] = -(2.0f * far * near) / (far - near);
}
static void mat4_rotate_y(float* m, float angle) {
float c = cosf(angle), s = sinf(angle);
float temp[16];
memcpy(temp, m, 16 * sizeof(float));
m[0] = temp[0] * c + temp[8] * s;
m[8] = temp[0] * -s + temp[8] * c;
m[2] = temp[2] * c + temp[10] * s;
m[10] = temp[2] * -s + temp[10] * c;
}
static void mat4_rotate_x(float* m, float angle) {
float c = cosf(angle), s = sinf(angle);
float temp[16];
memcpy(temp, m, 16 * sizeof(float));
m[4] = temp[4] * c + temp[8] * s;
m[8] = temp[4] * -s + temp[8] * c;
m[6] = temp[6] * c + temp[10] * s;
m[10] = temp[6] * -s + temp[10] * c;
}
static void mat4_translate(float* m, float x, float y, float z) {
m[12] += x;
m[13] += y;
m[14] += z;
}
static bool create_instance(VulkanContext* ctx) {
printf("Creating Vulkan instance...\n");
VkApplicationInfo app_info = {0};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pApplicationName = "Vulkan SDL3 Demo";
app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
app_info.pEngineName = "No Engine";
app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
app_info.apiVersion = VK_API_VERSION_1_0;
Uint32 ext_count;
const char *const *ext_names = SDL_Vulkan_GetInstanceExtensions(&ext_count);
if (!ext_names) {
printf("Failed to get Vulkan extensions: %s\n", SDL_GetError());
return false;
}
printf("Instance extensions requested (%u):\n", ext_count);
for (uint32_t i = 0; i < ext_count; i++) {
printf(" %s\n", ext_names[i]);
}
VkInstanceCreateInfo instance_info = {0};
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_info.pApplicationInfo = &app_info;
instance_info.enabledExtensionCount = ext_count;
instance_info.ppEnabledExtensionNames = ext_names;
VkResult result = vkCreateInstance(&instance_info, NULL, &ctx->instance);
if (result != VK_SUCCESS) {
printf("Failed to create Vulkan instance: %d\n", result);
return false;
}
printf("Vulkan instance created successfully\n");
return true;
}
static bool check_device_extension_support(VkPhysicalDevice device) {
uint32_t extension_count = 0;
vkEnumerateDeviceExtensionProperties(device, NULL, &extension_count, NULL);
VkExtensionProperties* extensions = malloc(extension_count * sizeof(VkExtensionProperties));
vkEnumerateDeviceExtensionProperties(device, NULL, &extension_count, extensions);
printf("Available device extensions (%u):\n", extension_count);
bool found_swapchain = false;
for (uint32_t i = 0; i < extension_count; i++) {
printf(" %s\n", extensions[i].extensionName);
if (strcmp(extensions[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) {
found_swapchain = true;
}
}
free(extensions);
if (!found_swapchain) {
printf("VK_KHR_swapchain extension not supported by this device\n");
return false;
}
return true;
}
static bool pick_physical_device(VulkanContext* ctx, VkSurfaceKHR surface) {
printf("Picking physical device...\n");
uint32_t device_count = 0;
vkEnumeratePhysicalDevices(ctx->instance, &device_count, NULL);
if (device_count == 0) {
printf("No Vulkan physical devices found\n");
return false;
}
printf("Found %u physical devices\n", device_count);
VkPhysicalDevice* devices = malloc(device_count * sizeof(VkPhysicalDevice));
vkEnumeratePhysicalDevices(ctx->instance, &device_count, devices);
for (uint32_t i = 0; i < device_count; i++) {
VkPhysicalDeviceProperties device_props;
vkGetPhysicalDeviceProperties(devices[i], &device_props);
printf("Device %u: %s (Type: %d)\n", i, device_props.deviceName, device_props.deviceType);
uint32_t queue_family_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(devices[i], &queue_family_count, NULL);
VkQueueFamilyProperties* queue_families = malloc(queue_family_count * sizeof(VkQueueFamilyProperties));
vkGetPhysicalDeviceQueueFamilyProperties(devices[i], &queue_family_count, queue_families);
printf(" Queue families (%u):\n", queue_family_count);
uint32_t graphics_family = UINT32_MAX;
uint32_t present_family = UINT32_MAX;
for (uint32_t j = 0; j < queue_family_count; j++) {
printf(" Family %u: Queue count = %u, Flags = 0x%x\n", j, queue_families[j].queueCount, queue_families[j].queueFlags);
if (queue_families[j].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
graphics_family = j;
}
VkBool32 present_support = VK_FALSE;
vkGetPhysicalDeviceSurfaceSupportKHR(devices[i], j, surface, &present_support);
printf(" Family %u: Present support = %s\n", j, present_support ? "Yes" : "No");
if (present_support) {
present_family = j;
}
if (graphics_family != UINT32_MAX && present_family != UINT32_MAX) break;
}
free(queue_families);
if (graphics_family != UINT32_MAX && present_family != UINT32_MAX &&
check_device_extension_support(devices[i])) {
ctx->physical_device = devices[i];
ctx->graphics_queue_family_index = graphics_family;
ctx->present_queue_family_index = present_family;
printf("Selected device: %s\n", device_props.deviceName);
printf(" Graphics queue family: %u\n", graphics_family);
printf(" Present queue family: %u\n", present_family);
free(devices);
return true;
}
}
free(devices);
printf("No suitable Vulkan physical device found with graphics and present support\n");
return false;
}
static bool create_device(VulkanContext* ctx) {
printf("Creating Vulkan device...\n");
float queue_priority = 1.0f;
VkDeviceQueueCreateInfo queue_infos[2] = {{0}, {0}};
uint32_t queue_info_count = 1;
queue_infos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_infos[0].queueFamilyIndex = ctx->graphics_queue_family_index;
queue_infos[0].queueCount = 1;
queue_infos[0].pQueuePriorities = &queue_priority;
if (ctx->graphics_queue_family_index != ctx->present_queue_family_index) {
queue_infos[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_infos[1].queueFamilyIndex = ctx->present_queue_family_index;
queue_infos[1].queueCount = 1;
queue_infos[1].pQueuePriorities = &queue_priority;
queue_info_count = 2;
printf("Using separate graphics and present queues\n");
} else {
printf("Using same queue for graphics and present\n");
}
const char* device_extensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
VkDeviceCreateInfo device_info = {0};
device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_info.queueCreateInfoCount = queue_info_count;
device_info.pQueueCreateInfos = queue_infos;
device_info.enabledExtensionCount = 1;
device_info.ppEnabledExtensionNames = device_extensions;
VkResult result = vkCreateDevice(ctx->physical_device, &device_info, NULL, &ctx->device);
if (result != VK_SUCCESS) {
printf("Failed to create Vulkan device: %d\n", result);
return false;
}
printf("Vulkan device created successfully\n");
vkGetDeviceQueue(ctx->device, ctx->graphics_queue_family_index, 0, &ctx->graphics_queue);
vkGetDeviceQueue(ctx->device, ctx->present_queue_family_index, 0, &ctx->present_queue);
ctx->vkCreateSwapchainKHR = (PFN_vkCreateSwapchainKHR)vkGetDeviceProcAddr(ctx->device, "vkCreateSwapchainKHR");
ctx->vkGetSwapchainImagesKHR = (PFN_vkGetSwapchainImagesKHR)vkGetDeviceProcAddr(ctx->device, "vkGetSwapchainImagesKHR");
ctx->vkAcquireNextImageKHR = (PFN_vkAcquireNextImageKHR)vkGetDeviceProcAddr(ctx->device, "vkAcquireNextImageKHR");
ctx->vkQueuePresentKHR = (PFN_vkQueuePresentKHR)vkGetDeviceProcAddr(ctx->device, "vkQueuePresentKHR");
printf("Swapchain function pointers loaded:\n");
printf(" vkCreateSwapchainKHR: %p\n", (void*)ctx->vkCreateSwapchainKHR);
printf(" vkGetSwapchainImagesKHR: %p\n", (void*)ctx->vkGetSwapchainImagesKHR);
printf(" vkAcquireNextImageKHR: %p\n", (void*)ctx->vkAcquireNextImageKHR);
printf(" vkQueuePresentKHR: %p\n", (void*)ctx->vkQueuePresentKHR);
if (!ctx->vkCreateSwapchainKHR || !ctx->vkGetSwapchainImagesKHR || !ctx->vkAcquireNextImageKHR || !ctx->vkQueuePresentKHR) {
printf("Failed to load one or more swapchain function pointers\n");
return false;
}
return true;
}
static bool create_swapchain(VulkanContext* ctx, SDL_Window* window) {
printf("Creating swapchain...\n");
VkSurfaceCapabilitiesKHR surface_caps;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(ctx->physical_device, ctx->surface, &surface_caps);
printf("Surface capabilities: minImageCount=%u, maxImageCount=%u\n", surface_caps.minImageCount, surface_caps.maxImageCount);
VkSwapchainCreateInfoKHR swapchain_info = {0};
swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_info.surface = ctx->surface;
swapchain_info.minImageCount = 2;
swapchain_info.imageFormat = VK_FORMAT_B8G8R8A8_UNORM;
swapchain_info.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
swapchain_info.imageExtent.width = WINDOW_WIDTH;
swapchain_info.imageExtent.height = WINDOW_HEIGHT;
swapchain_info.imageArrayLayers = 1;
swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
uint32_t queue_family_indices[] = {ctx->graphics_queue_family_index, ctx->present_queue_family_index};
if (ctx->graphics_queue_family_index != ctx->present_queue_family_index) {
swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchain_info.queueFamilyIndexCount = 2;
swapchain_info.pQueueFamilyIndices = queue_family_indices;
} else {
swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchain_info.queueFamilyIndexCount = 0;
swapchain_info.pQueueFamilyIndices = NULL;
}
swapchain_info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
swapchain_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
swapchain_info.presentMode = VK_PRESENT_MODE_FIFO_KHR;
swapchain_info.clipped = VK_TRUE;
VkResult result = ctx->vkCreateSwapchainKHR(ctx->device, &swapchain_info, NULL, &ctx->swapchain);
if (result != VK_SUCCESS) {
printf("Failed to create swapchain: %d\n", result);
return false;
}
printf("Swapchain created successfully\n");
ctx->vkGetSwapchainImagesKHR(ctx->device, ctx->swapchain, &ctx->swapchain_image_count, NULL);
printf("Swapchain image count: %u\n", ctx->swapchain_image_count);
ctx->swapchain_images = malloc(ctx->swapchain_image_count * sizeof(VkImage));
ctx->vkGetSwapchainImagesKHR(ctx->device, ctx->swapchain, &ctx->swapchain_image_count, ctx->swapchain_images);
ctx->swapchain_image_views = malloc(ctx->swapchain_image_count * sizeof(VkImageView));
for (uint32_t i = 0; i < ctx->swapchain_image_count; i++) {
VkImageViewCreateInfo view_info = {0};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.image = ctx->swapchain_images[i];
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = VK_FORMAT_B8G8R8A8_UNORM;
view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
vkCreateImageView(ctx->device, &view_info, NULL, &ctx->swapchain_image_views[i]);
}
return true;
}
static bool create_depth_resources(VulkanContext* ctx) {
VkImageCreateInfo image_info = {0};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.extent.width = WINDOW_WIDTH;
image_info.extent.height = WINDOW_HEIGHT;
image_info.extent.depth = 1;
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.format = VK_FORMAT_D32_SFLOAT;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
if (vkCreateImage(ctx->device, &image_info, NULL, &ctx->depth_image) != VK_SUCCESS) {
printf("Failed to create depth image\n");
return false;
}
VkMemoryRequirements mem_reqs;
vkGetImageMemoryRequirements(ctx->device, ctx->depth_image, &mem_reqs);
VkPhysicalDeviceMemoryProperties mem_props;
vkGetPhysicalDeviceMemoryProperties(ctx->physical_device, &mem_props);
uint32_t memory_type_index = 0;
for (; memory_type_index < mem_props.memoryTypeCount; memory_type_index++) {
if ((mem_reqs.memoryTypeBits & (1 << memory_type_index)) &&
(mem_props.memoryTypes[memory_type_index].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) break;
}
VkMemoryAllocateInfo alloc = {0};
alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc.allocationSize = mem_reqs.size;
alloc.memoryTypeIndex = memory_type_index;
if (vkAllocateMemory(ctx->device, &alloc, NULL, &ctx->depth_image_memory) != VK_SUCCESS) {
printf("Failed to allocate depth image memory\n");
return false;
}
vkBindImageMemory(ctx->device, ctx->depth_image, ctx->depth_image_memory, 0);
VkImageViewCreateInfo view_info = {0};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.image = ctx->depth_image;
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = VK_FORMAT_D32_SFLOAT;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
if (vkCreateImageView(ctx->device, &view_info, NULL, &ctx->depth_image_view) != VK_SUCCESS) {
printf("Failed to create depth image view\n");
return false;
}
return true;
}
static bool init_vulkan(SDL_Window* window, VulkanContext* ctx) {
if (!create_instance(ctx)) return false;
if (!SDL_Vulkan_CreateSurface(window, ctx->instance, NULL, &ctx->surface)) {
printf("Failed to create Vulkan surface: %s\n", SDL_GetError());
return false;
}
if (!pick_physical_device(ctx, ctx->surface)) return false;
if (!create_device(ctx)) return false;
if (!create_swapchain(ctx, window)) return false;
if (!create_depth_resources(ctx)) return false;
printf("Initializing render pass...\n");
VkAttachmentDescription attachments[2] = {{0}, {0}};
attachments[0].format = VK_FORMAT_B8G8R8A8_UNORM;
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
attachments[1].format = VK_FORMAT_D32_SFLOAT;
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference color_ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentReference depth_ref = {1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
VkSubpassDescription subpass = {0};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_ref;
subpass.pDepthStencilAttachment = &depth_ref;
VkRenderPassCreateInfo render_pass_info = {0};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_info.attachmentCount = 2;
render_pass_info.pAttachments = attachments;
render_pass_info.subpassCount = 1;
render_pass_info.pSubpasses = &subpass;
if (vkCreateRenderPass(ctx->device, &render_pass_info, NULL, &ctx->render_pass) != VK_SUCCESS) {
printf("Failed to create render pass\n");
return false;
}
printf("Initializing pipeline layout...\n");
VkDescriptorSetLayoutBinding ubo_binding = {0};
ubo_binding.binding = 0;
ubo_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
ubo_binding.descriptorCount = 1;
ubo_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
VkDescriptorSetLayoutCreateInfo layout_info = {0};
layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layout_info.bindingCount = 1;
layout_info.pBindings = &ubo_binding;
if (vkCreateDescriptorSetLayout(ctx->device, &layout_info, NULL, &ctx->descriptor_set_layout) != VK_SUCCESS) {
printf("Failed to create descriptor set layout\n");
return false;
}
VkPipelineLayoutCreateInfo pipeline_layout_info = {0};
pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &ctx->descriptor_set_layout;
if (vkCreatePipelineLayout(ctx->device, &pipeline_layout_info, NULL, &ctx->pipeline_layout) != VK_SUCCESS) {
printf("Failed to create pipeline layout\n");
return false;
}
printf("Loading shaders...\n");
size_t vert_shader_size, frag_shader_size;
void* vert_shader_code = read_spirv_file("vert.spv", &vert_shader_size);
void* frag_shader_code = read_spirv_file("frag.spv", &frag_shader_size);
if (!vert_shader_code || !frag_shader_code) {
printf("Failed to load shaders\n");
free(vert_shader_code);
free(frag_shader_code);
return false;
}
VkShaderModuleCreateInfo vert_info = {0};
vert_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
vert_info.codeSize = vert_shader_size;
vert_info.pCode = vert_shader_code;
VkShaderModuleCreateInfo frag_info = {0};
frag_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
frag_info.codeSize = frag_shader_size;
frag_info.pCode = frag_shader_code;
VkShaderModule vert_module, frag_module;
if (vkCreateShaderModule(ctx->device, &vert_info, NULL, &vert_module) != VK_SUCCESS ||
vkCreateShaderModule(ctx->device, &frag_info, NULL, &frag_module) != VK_SUCCESS) {
printf("Failed to create shader modules\n");
free(vert_shader_code);
free(frag_shader_code);
return false;
}
free(vert_shader_code);
free(frag_shader_code);
VkPipelineShaderStageCreateInfo shader_stages[] = {
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_VERTEX_BIT, vert_module, "main", NULL},
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_FRAGMENT_BIT, frag_module, "main", NULL}
};
VkVertexInputBindingDescription binding_desc = {0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX};
VkVertexInputAttributeDescription attr_desc[] = {
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)},
{1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, color)}
};
VkPipelineVertexInputStateCreateInfo vertex_input_info = {0};
vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertex_input_info.vertexBindingDescriptionCount = 1;
vertex_input_info.pVertexBindingDescriptions = &binding_desc;
vertex_input_info.vertexAttributeDescriptionCount = 2;
vertex_input_info.pVertexAttributeDescriptions = attr_desc;
VkPipelineInputAssemblyStateCreateInfo input_assembly = {0};
input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
input_assembly.primitiveRestartEnable = VK_FALSE;
VkViewport viewport = {0.0f, 0.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 0.0f, 1.0f};
VkRect2D scissor = {{0, 0}, {WINDOW_WIDTH, WINDOW_HEIGHT}};
VkPipelineViewportStateCreateInfo viewport_state = {0};
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewport_state.viewportCount = 1;
viewport_state.pViewports = &viewport;
viewport_state.scissorCount = 1;
viewport_state.pScissors = &scissor;
// In init_vulkan(), adjust rasterizer state
VkPipelineRasterizationStateCreateInfo rasterizer = {0};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
// rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; // Keep backface culling
rasterizer.cullMode = VK_CULL_MODE_NONE; // Disable culling
//
// rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; // Change to counterclockwise
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; // Match current winding
rasterizer.lineWidth = 1.0f;
VkPipelineMultisampleStateCreateInfo multisampling = {0};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineDepthStencilStateCreateInfo depth_stencil = {0};
depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depth_stencil.depthTestEnable = VK_TRUE;
depth_stencil.depthWriteEnable = VK_TRUE;
depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS;
depth_stencil.depthBoundsTestEnable = VK_FALSE;
depth_stencil.stencilTestEnable = VK_FALSE;
VkPipelineColorBlendAttachmentState color_blend_attachment = {0};
color_blend_attachment.blendEnable = VK_FALSE;
color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
VkPipelineColorBlendStateCreateInfo color_blending = {0};
color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
color_blending.attachmentCount = 1;
color_blending.pAttachments = &color_blend_attachment;
VkGraphicsPipelineCreateInfo pipeline_info = {0};
pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipeline_info.stageCount = 2;
pipeline_info.pStages = shader_stages;
pipeline_info.pVertexInputState = &vertex_input_info;
pipeline_info.pInputAssemblyState = &input_assembly;
pipeline_info.pViewportState = &viewport_state;
pipeline_info.pRasterizationState = &rasterizer;
pipeline_info.pMultisampleState = &multisampling;
pipeline_info.pDepthStencilState = &depth_stencil;
pipeline_info.pColorBlendState = &color_blending;
pipeline_info.layout = ctx->pipeline_layout;
pipeline_info.renderPass = ctx->render_pass;
pipeline_info.subpass = 0;
if (vkCreateGraphicsPipelines(ctx->device, VK_NULL_HANDLE, 1, &pipeline_info, NULL, &ctx->graphics_pipeline) != VK_SUCCESS) {
printf("Failed to create graphics pipeline\n");
return false;
}
ctx->framebuffers = malloc(ctx->swapchain_image_count * sizeof(VkFramebuffer));
for (uint32_t i = 0; i < ctx->swapchain_image_count; i++) {
VkImageView attachments[] = {ctx->swapchain_image_views[i], ctx->depth_image_view};
VkFramebufferCreateInfo fb_info = {0};
fb_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fb_info.renderPass = ctx->render_pass;
fb_info.attachmentCount = 2;
fb_info.pAttachments = attachments;
fb_info.width = WINDOW_WIDTH;
fb_info.height = WINDOW_HEIGHT;
fb_info.layers = 1;
vkCreateFramebuffer(ctx->device, &fb_info, NULL, &ctx->framebuffers[i]);
}
VkCommandPoolCreateInfo pool_info = {0};
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
pool_info.queueFamilyIndex = ctx->graphics_queue_family_index;
if (vkCreateCommandPool(ctx->device, &pool_info, NULL, &ctx->command_pool) != VK_SUCCESS) {
printf("Failed to create command pool\n");
return false;
}
VkCommandBufferAllocateInfo alloc_info = {0};
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
alloc_info.commandPool = ctx->command_pool;
alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
alloc_info.commandBufferCount = 1;
if (vkAllocateCommandBuffers(ctx->device, &alloc_info, &ctx->command_buffer) != VK_SUCCESS) {
printf("Failed to allocate command buffers\n");
return false;
}
VkSemaphoreCreateInfo semaphore_info = {0};
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fence_info = {0};
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
vkCreateSemaphore(ctx->device, &semaphore_info, NULL, &ctx->image_available_semaphore);
vkCreateSemaphore(ctx->device, &semaphore_info, NULL, &ctx->render_finished_semaphore);
vkCreateFence(ctx->device, &fence_info, NULL, &ctx->in_flight_fence);
VkBufferCreateInfo buffer_info = {0};
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_info.size = sizeof(cube_vertices);
buffer_info.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(ctx->device, &buffer_info, NULL, &ctx->vertex_buffer) != VK_SUCCESS) {
printf("Failed to create vertex buffer\n");
return false;
}
VkMemoryRequirements mem_reqs;
vkGetBufferMemoryRequirements(ctx->device, ctx->vertex_buffer, &mem_reqs);
VkPhysicalDeviceMemoryProperties mem_props;
vkGetPhysicalDeviceMemoryProperties(ctx->physical_device, &mem_props);
uint32_t memory_type_index = 0;
for (; memory_type_index < mem_props.memoryTypeCount; memory_type_index++) {
if ((mem_reqs.memoryTypeBits & (1 << memory_type_index)) && (mem_props.memoryTypes[memory_type_index].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) break;
}
VkMemoryAllocateInfo alloc = {0};
alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc.allocationSize = mem_reqs.size;
alloc.memoryTypeIndex = memory_type_index;
if (vkAllocateMemory(ctx->device, &alloc, NULL, &ctx->vertex_buffer_memory) != VK_SUCCESS) {
printf("Failed to allocate vertex buffer memory\n");
return false;
}
vkBindBufferMemory(ctx->device, ctx->vertex_buffer, ctx->vertex_buffer_memory, 0);
void* data;
vkMapMemory(ctx->device, ctx->vertex_buffer_memory, 0, sizeof(cube_vertices), 0, &data);
memcpy(data, cube_vertices, sizeof(cube_vertices));
vkUnmapMemory(ctx->device, ctx->vertex_buffer_memory);
buffer_info.size = sizeof(UBO);
buffer_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
if (vkCreateBuffer(ctx->device, &buffer_info, NULL, &ctx->uniform_buffer) != VK_SUCCESS) {
printf("Failed to create uniform buffer\n");
return false;
}
vkGetBufferMemoryRequirements(ctx->device, ctx->uniform_buffer, &mem_reqs);
alloc.allocationSize = mem_reqs.size;
if (vkAllocateMemory(ctx->device, &alloc, NULL, &ctx->uniform_buffer_memory) != VK_SUCCESS) {
printf("Failed to allocate uniform buffer memory\n");
return false;
}
vkBindBufferMemory(ctx->device, ctx->uniform_buffer, ctx->uniform_buffer_memory, 0);
VkDescriptorPoolSize pool_size = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1};
VkDescriptorPoolCreateInfo pool_create_info = {0};
pool_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool_create_info.maxSets = 1;
pool_create_info.poolSizeCount = 1;
pool_create_info.pPoolSizes = &pool_size;
if (vkCreateDescriptorPool(ctx->device, &pool_create_info, NULL, &ctx->descriptor_pool) != VK_SUCCESS) {
printf("Failed to create descriptor pool\n");
return false;
}
VkDescriptorSetAllocateInfo desc_alloc_info = {0};
desc_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
desc_alloc_info.descriptorPool = ctx->descriptor_pool;
desc_alloc_info.descriptorSetCount = 1;
desc_alloc_info.pSetLayouts = &ctx->descriptor_set_layout;
if (vkAllocateDescriptorSets(ctx->device, &desc_alloc_info, &ctx->descriptor_set) != VK_SUCCESS) {
printf("Failed to allocate descriptor sets\n");
return false;
}
VkDescriptorBufferInfo buffer_desc_info = {ctx->uniform_buffer, 0, sizeof(UBO)};
VkWriteDescriptorSet desc_write = {0};
desc_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
desc_write.dstSet = ctx->descriptor_set;
desc_write.dstBinding = 0;
desc_write.descriptorCount = 1;
desc_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
desc_write.pBufferInfo = &buffer_desc_info;
vkUpdateDescriptorSets(ctx->device, 1, &desc_write, 0, NULL);
printf("Vulkan initialization completed successfully\n");
return true;
}
static void render(VulkanContext* ctx, Camera* camera) {
vkWaitForFences(ctx->device, 1, &ctx->in_flight_fence, VK_TRUE, UINT64_MAX);
vkResetFences(ctx->device, 1, &ctx->in_flight_fence);
uint32_t image_index;
VkResult result = ctx->vkAcquireNextImageKHR(ctx->device, ctx->swapchain, UINT64_MAX, ctx->image_available_semaphore, VK_NULL_HANDLE, &image_index);
if (result != VK_SUCCESS) {
printf("Failed to acquire next image: %d\n", result);
return;
}
vkResetCommandBuffer(ctx->command_buffer, 0);
VkCommandBufferBeginInfo begin_info = {0};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(ctx->command_buffer, &begin_info);
VkClearValue clear_values[2] = {{{{0.2f, 0.3f, 0.5f, 1.0f}}}, {{{1.0f, 0}}}};
VkRenderPassBeginInfo render_pass_info = {0};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_info.renderPass = ctx->render_pass;
render_pass_info.framebuffer = ctx->framebuffers[image_index];
render_pass_info.renderArea.offset = (VkOffset2D){0, 0};
render_pass_info.renderArea.extent = (VkExtent2D){WINDOW_WIDTH, WINDOW_HEIGHT};
render_pass_info.clearValueCount = 2;
render_pass_info.pClearValues = clear_values;
vkCmdBeginRenderPass(ctx->command_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(ctx->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->graphics_pipeline);
VkDeviceSize offsets[] = {0};
printf("Binding vertex buffer: %p\n", (void*)ctx->vertex_buffer);
vkCmdBindVertexBuffers(ctx->command_buffer, 0, 1, &ctx->vertex_buffer, offsets);
vkCmdBindDescriptorSets(ctx->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->pipeline_layout, 0, 1, &ctx->descriptor_set, 0, NULL);
UBO ubo = {};
mat4_perspective(ubo.mvp, M_PI / 2.0f, (float)WINDOW_WIDTH / WINDOW_HEIGHT, 0.01f, 100.0f);
mat4_rotate_y(ubo.mvp, camera->yaw * M_PI / 180.0f);
mat4_rotate_x(ubo.mvp, camera->pitch * M_PI / 180.0f);
mat4_translate(ubo.mvp, -camera->pos[0], -camera->pos[1], -camera->pos[2]);
ubo.light_pos[0] = 2.0f; ubo.light_pos[1] = 2.0f; ubo.light_pos[2] = -2.0f;
void* data;
vkMapMemory(ctx->device, ctx->uniform_buffer_memory, 0, sizeof(UBO), 0, &data);
memcpy(data, &ubo, sizeof(UBO));
vkUnmapMemory(ctx->device, ctx->uniform_buffer_memory);
printf("Camera state - Pos: (%.2f, %.2f, %.2f), Yaw: %.2f, Pitch: %.2f\n",
camera->pos[0], camera->pos[1], camera->pos[2], camera->yaw, camera->pitch);
printf("MVP Matrix:\n");
for (int i = 0; i < 4; i++) {
printf(" [%.2f, %.2f, %.2f, %.2f]\n", ubo.mvp[i*4], ubo.mvp[i*4+1], ubo.mvp[i*4+2], ubo.mvp[i*4+3]);
}
printf("Drawing 36 vertices...\n");
vkCmdDraw(ctx->command_buffer, 36, 1, 0, 0);
vkCmdEndRenderPass(ctx->command_buffer);
vkEndCommandBuffer(ctx->command_buffer);
VkSubmitInfo submit_info = {0};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &ctx->image_available_semaphore;
submit_info.pWaitDstStageMask = (VkPipelineStageFlags[]){VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &ctx->command_buffer;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &ctx->render_finished_semaphore;
vkQueueSubmit(ctx->graphics_queue, 1, &submit_info, ctx->in_flight_fence);
VkPresentInfoKHR present_info = {0};
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = &ctx->render_finished_semaphore;
present_info.swapchainCount = 1;
present_info.pSwapchains = &ctx->swapchain;
present_info.pImageIndices = &image_index;
result = ctx->vkQueuePresentKHR(ctx->present_queue, &present_info);
if (result != VK_SUCCESS) {
printf("Failed to present image: %d\n", result);
}
}
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL could not initialize: %s\n", SDL_GetError());
return 1;
}
SDL_Window* window = SDL_CreateWindow("Vulkan SDL3 3D Demo", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
if (!window) {
printf("Window could not be created: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
VulkanContext ctx = {0};
if (!init_vulkan(window, &ctx)) {
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
Camera camera = {{0.0f, 0.0f, 3.0f}, 0.0f, 0.0f, 5.0f}; // Start farther back
SDL_SetWindowRelativeMouseMode(window, true);
bool running = true;
SDL_Event event;
const Uint8* keyboard_state = SDL_GetKeyboardState(NULL);
Uint32 last_time = SDL_GetTicks();
while (running) {
Uint32 current_time = SDL_GetTicks();
float delta_time = (current_time - last_time) / 1000.0f;
last_time = current_time;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) running = false;
else if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE) SDL_SetWindowRelativeMouseMode(window, false);
else if (event.type == SDL_EVENT_MOUSE_MOTION && SDL_GetWindowRelativeMouseMode(window)) {
camera.yaw -= event.motion.xrel * 0.1f;
camera.pitch -= event.motion.yrel * 0.1f;
camera.pitch = SDL_clamp(camera.pitch, -89.0f, 89.0f);
}
}
float forward = sinf(camera.yaw * M_PI / 180.0f);
float right = cosf(camera.yaw * M_PI / 180.0f);
if (keyboard_state[SDL_SCANCODE_W]) { camera.pos[0] += forward * camera.speed * delta_time; camera.pos[2] -= right * camera.speed * delta_time; }
if (keyboard_state[SDL_SCANCODE_S]) { camera.pos[0] -= forward * camera.speed * delta_time; camera.pos[2] += right * camera.speed * delta_time; }
if (keyboard_state[SDL_SCANCODE_A]) { camera.pos[0] -= right * camera.speed * delta_time; camera.pos[2] -= forward * camera.speed * delta_time; }
if (keyboard_state[SDL_SCANCODE_D]) { camera.pos[0] += right * camera.speed * delta_time; camera.pos[2] += forward * camera.speed * delta_time; }
render(&ctx, &camera);
}
vkDeviceWaitIdle(ctx.device);
vkDestroyBuffer(ctx.device, ctx.vertex_buffer, NULL);
vkFreeMemory(ctx.device, ctx.vertex_buffer_memory, NULL);
vkDestroyBuffer(ctx.device, ctx.uniform_buffer, NULL);
vkFreeMemory(ctx.device, ctx.uniform_buffer_memory, NULL);
vkDestroyDescriptorPool(ctx.device, ctx.descriptor_pool, NULL);
vkDestroyDescriptorSetLayout(ctx.device, ctx.descriptor_set_layout, NULL);
vkDestroyPipeline(ctx.device, ctx.graphics_pipeline, NULL);
vkDestroyPipelineLayout(ctx.device, ctx.pipeline_layout, NULL);
vkDestroyRenderPass(ctx.device, ctx.render_pass, NULL);
for (uint32_t i = 0; i < ctx.swapchain_image_count; i++)
vkDestroyFramebuffer(ctx.device, ctx.framebuffers[i], NULL);
free(ctx.framebuffers);
vkDestroyImageView(ctx.device, ctx.depth_image_view, NULL);
vkDestroyImage(ctx.device, ctx.depth_image, NULL);
vkFreeMemory(ctx.device, ctx.depth_image_memory, NULL);
for (uint32_t i = 0; i < ctx.swapchain_image_count; i++)
vkDestroyImageView(ctx.device, ctx.swapchain_image_views[i], NULL);
free(ctx.swapchain_image_views);
free(ctx.swapchain_images);
vkDestroySwapchainKHR(ctx.device, ctx.swapchain, NULL);
vkDestroySurfaceKHR(ctx.instance, ctx.surface, NULL);
vkDestroyCommandPool(ctx.device, ctx.command_pool, NULL);
vkDestroySemaphore(ctx.device, ctx.image_available_semaphore, NULL);
vkDestroySemaphore(ctx.device, ctx.render_finished_semaphore, NULL);
vkDestroyFence(ctx.device, ctx.in_flight_fence, NULL);
vkDestroyDevice(ctx.device, NULL);
vkDestroyInstance(ctx.instance, NULL);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
"./build/Debug/VulkanSDL3Project.exe"
cd assets
glslc -fshader-stage=vertex vert.glsl -o vert.spv
glslc -fshader-stage=fragment frag.glsl -o frag.spv
#version 450
layout(location = 0) in vec3 in_pos;
layout(location = 1) in vec3 in_color;
layout(binding = 0) uniform UBO {
mat4 mvp;
vec3 light_pos;
} ubo;
layout(location = 0) out vec3 frag_color;
void main() {
gl_Position = ubo.mvp * vec4(in_pos * 2.0, 1.0); // Scale cube by 2x
frag_color = in_color;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment