|
#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; |
|
} |