diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index d4ed3503f..267618272 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1343,9 +1343,9 @@ static void DeallocateWrapper(void* block, void* user) // Initialize platform: graphics, inputs and more #if defined(GRAPHICS_API_VULKAN) -// Initialize platform for Vulkan (GLFW STUB) +// Initialize platform for Vulkan static int InitPlatformVulkan(void) { - TRACELOG(LOG_INFO, "PLATFORM: Initializing platform for Vulkan (GLFW STUB)"); + TRACELOG(LOG_INFO, "PLATFORM: Initializing platform for Vulkan"); glfwSetErrorCallback(ErrorCallback); @@ -1361,18 +1361,93 @@ static int InitPlatformVulkan(void) { glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE); #endif - if (!glfwInit()) { + if (glfwInit() == GLFW_FALSE) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize GLFW for Vulkan"); return RL_FALSE; } - if (!glfwVulkanSupported()) { + if (glfwVulkanSupported() == GLFW_FALSE) { TRACELOG(LOG_FATAL, "PLATFORM: Vulkan not supported by GLFW or system drivers"); glfwTerminate(); return RL_FALSE; } TRACELOG(LOG_INFO, "PLATFORM: GLFW Vulkan support detected."); + // Get required instance extensions + uint32_t requiredExtensionsCount = 0; + const char **requiredExtensions = glfwGetRequiredInstanceExtensions(&requiredExtensionsCount); + if (requiredExtensions == NULL) { + TRACELOG(LOG_FATAL, "PLATFORM: Could not get required Vulkan instance extensions"); + glfwTerminate(); + return RL_FALSE; + } + + TRACELOG(LOG_INFO, "PLATFORM: Required Vulkan instance extensions (%u):", requiredExtensionsCount); + for (uint32_t i = 0; i < requiredExtensionsCount; i++) TRACELOG(LOG_INFO, " %s", requiredExtensions[i]); + + // Define desired validation layers + const char *validationLayers[] = { "VK_LAYER_KHRONOS_validation" }; + uint32_t enabledLayerCount = 0; + const char *enabledLayers[1]; // Max 1 layer for now + +#if defined(RLGL_ENABLE_VULKAN_DEBUG) // Or a custom define for enabling validation layers + uint32_t layerCount = 0; + vkEnumerateInstanceLayerProperties(&layerCount, NULL); + if (layerCount > 0) { + VkLayerProperties *availableLayers = (VkLayerProperties *)RL_MALLOC(layerCount * sizeof(VkLayerProperties)); + if (availableLayers == NULL) { + TRACELOG(LOG_WARNING, "PLATFORM: Failed to allocate memory for Vulkan layer properties"); + } else { + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers); + bool layerFound = false; + for (uint32_t i = 0; i < layerCount; i++) { + if (strcmp(validationLayers[0], availableLayers[i].layerName) == 0) { + layerFound = true; + break; + } + } + RL_FREE(availableLayers); + + if (layerFound) { + enabledLayers[0] = validationLayers[0]; + enabledLayerCount = 1; + TRACELOG(LOG_INFO, "PLATFORM: Enabled validation layer: %s", enabledLayers[0]); + } else { + TRACELOG(LOG_WARNING, "PLATFORM: Validation layer VK_LAYER_KHRONOS_validation not available"); + } + } + } else { + TRACELOG(LOG_INFO, "PLATFORM: No Vulkan instance layers found."); + } +#endif + + VkApplicationInfo appInfo = {0}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = (CORE.Window.title != NULL) ? CORE.Window.title : "raylib Vulkan App"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "raylib"; + appInfo.engineVersion = VK_MAKE_VERSION(RAYLIB_VERSION_MAJOR, RAYLIB_VERSION_MINOR, RAYLIB_VERSION_PATCH); + appInfo.apiVersion = VK_API_VERSION_1_1; + + VkInstanceCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + createInfo.enabledExtensionCount = requiredExtensionsCount; + createInfo.ppEnabledExtensionNames = requiredExtensions; + createInfo.enabledLayerCount = enabledLayerCount; + createInfo.ppEnabledLayerNames = (enabledLayerCount > 0) ? enabledLayers : NULL; + + // Initialize global vkInstanceHandle + vkInstanceHandle = VK_NULL_HANDLE; // Ensure it's NULL before creation + VkResult result = vkCreateInstance(&createInfo, NULL, &vkInstanceHandle); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to create Vulkan instance (Error: %i)", result); + glfwTerminate(); + return RL_FALSE; + } + TRACELOG(LOG_INFO, "PLATFORM: Vulkan instance created successfully"); + + // Window hints should be set *before* window creation glfwDefaultWindowHints(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // Critical for Vulkan @@ -1384,8 +1459,8 @@ static int InitPlatformVulkan(void) { else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // Cannot be set on creation - if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; // Cannot be set on creation + if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // Cannot be set on creation, will be handled later if set + if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; // Cannot be set on creation, will be handled later if set if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); @@ -1396,7 +1471,7 @@ static int InitPlatformVulkan(void) { if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) { glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); #if defined(__APPLE__) - glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); + // glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); // This hint might be managed differently or implicitly with Vulkan/MoltenVK #endif } else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); if ((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); @@ -1427,17 +1502,30 @@ static int InitPlatformVulkan(void) { int creationHeight = (CORE.Window.screen.height > 0) ? CORE.Window.screen.height : 480; platform.handle = glfwCreateWindow(creationWidth, creationHeight, (CORE.Window.title != 0)? CORE.Window.title : " ", CORE.Window.fullscreen ? monitor : NULL, NULL); - CORE.Window.handle = platform.handle; - if (!CORE.Window.handle) { + if (!platform.handle) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to create GLFW window for Vulkan"); + vkDestroyInstance(vkInstanceHandle, NULL); // Clean up created instance + vkInstanceHandle = VK_NULL_HANDLE; glfwTerminate(); return RL_FALSE; } TRACELOG(LOG_INFO, "PLATFORM: GLFW window created for Vulkan"); - - vkInstanceHandle = (VkInstance)0x1; - vkSurfaceHandle = (VkSurfaceKHR)0x1; - TRACELOG(LOG_INFO, "PLATFORM: VkInstance and VkSurfaceKHR STUBBED as non-null for rlvkInit testing."); + CORE.Window.handle = platform.handle; // Assign global window handle after successful creation + + // Create Vulkan surface + vkSurfaceHandle = VK_NULL_HANDLE; // Ensure it's NULL before creation + VkResult surfaceResult = glfwCreateWindowSurface(vkInstanceHandle, platform.handle, NULL, &vkSurfaceHandle); + if (surfaceResult != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to create Vulkan window surface (Error: %i)", surfaceResult); + vkDestroyInstance(vkInstanceHandle, NULL); + vkInstanceHandle = VK_NULL_HANDLE; + glfwDestroyWindow(platform.handle); + platform.handle = NULL; + CORE.Window.handle = NULL; + glfwTerminate(); + return RL_FALSE; + } + TRACELOG(LOG_INFO, "PLATFORM: Vulkan window surface created successfully"); // Setup GLFW callbacks glfwSetWindowSizeCallback(platform.handle, WindowSizeCallback); @@ -1491,7 +1579,7 @@ static int InitPlatformVulkan(void) { } CORE.Window.ready = RL_TRUE; - TRACELOG(LOG_INFO, "PLATFORM: Vulkan platform initialized successfully (GLFW STUBBED VkInstance/Surface)"); + TRACELOG(LOG_INFO, "PLATFORM: Vulkan platform initialized successfully"); InitTimer(); CORE.Storage.basePath = GetWorkingDirectory(); @@ -1500,20 +1588,50 @@ static int InitPlatformVulkan(void) { } static void ClosePlatformVulkan(void) { - TRACELOG(LOG_INFO, "PLATFORM: Closing Vulkan platform (GLFW STUB)"); - #if defined(GRAPHICS_API_VULKAN) - vkSurfaceHandle = VK_NULL_HANDLE; - vkInstanceHandle = VK_NULL_HANDLE; - TRACELOG(LOG_INFO, "PLATFORM: VkInstance and VkSurfaceKHR STUBBED as NULL."); - #endif - - if (CORE.Window.handle != NULL) { - glfwDestroyWindow(CORE.Window.handle); - CORE.Window.handle = NULL; - platform.handle = NULL; - } + TRACELOG(LOG_INFO, "PLATFORM: Closing Vulkan platform"); + + // Destroy Vulkan surface + // Note: vkGetInstanceProcAddr is not strictly needed for vkDestroySurfaceKHR if Vulkan headers are recent enough + // and vkDestroySurfaceKHR was linked directly or via a loader. However, explicitly getting the function pointer + // is safer if there's any doubt about the linking process or for older setups. + // For simplicity and modern Vulkan loaders (like the one GLFW might use internally or if linked with Vulkan SDK), + // direct call is often fine. If issues arise, use vkGetInstanceProcAddr. + if (vkSurfaceHandle != VK_NULL_HANDLE && vkInstanceHandle != VK_NULL_HANDLE) { + // PFN_vkDestroySurfaceKHR pfnDestroySurfaceKHR = (PFN_vkDestroySurfaceKHR)vkGetInstanceProcAddr(vkInstanceHandle, "vkDestroySurfaceKHR"); + // if (pfnDestroySurfaceKHR) pfnDestroySurfaceKHR(vkInstanceHandle, vkSurfaceHandle, NULL); + // else TRACELOG(LOG_WARNING, "PLATFORM: Failed to get vkDestroySurfaceKHR proc address"); + // Assuming vkDestroySurfaceKHR is available directly through linking/loader: + vkDestroySurfaceKHR(vkInstanceHandle, vkSurfaceHandle, NULL); + vkSurfaceHandle = VK_NULL_HANDLE; // Set to NULL after destruction + TRACELOG(LOG_INFO, "PLATFORM: Vulkan surface destroyed"); + } else if (vkInstanceHandle == VK_NULL_HANDLE && vkSurfaceHandle != VK_NULL_HANDLE) { + TRACELOG(LOG_WARNING, "PLATFORM: vkInstanceHandle is NULL, cannot destroy vkSurfaceHandle. Surface might be leaked if instance was lost prematurely."); + vkSurfaceHandle = VK_NULL_HANDLE; // Still nullify to prevent reuse attempts + } + + + // Destroy Vulkan instance + if (vkInstanceHandle != VK_NULL_HANDLE) { + vkDestroyInstance(vkInstanceHandle, NULL); + vkInstanceHandle = VK_NULL_HANDLE; // Set to NULL after destruction + TRACELOG(LOG_INFO, "PLATFORM: Vulkan instance destroyed"); + } + + // Destroy GLFW window + // Use platform.handle as it's the direct reference to the created window. + // CORE.Window.handle should mirror platform.handle but platform.handle is the source of truth here. + if (platform.handle != NULL) { + glfwDestroyWindow(platform.handle); + platform.handle = NULL; // Set to NULL after destruction + CORE.Window.handle = NULL; // Ensure CORE's copy is also NULL + TRACELOG(LOG_INFO, "PLATFORM: GLFW window destroyed"); + } + + // Terminate GLFW glfwTerminate(); - TRACELOG(LOG_INFO, "PLATFORM: Vulkan platform resources closed (GLFW STUBBED VkInstance/Surface)"); + TRACELOG(LOG_INFO, "PLATFORM: GLFW terminated"); + + TRACELOG(LOG_INFO, "PLATFORM: Vulkan platform resources closed successfully"); } #endif // GRAPHICS_API_VULKAN diff --git a/src/rlvk.c b/src/rlvk.c index 0d7b8044f..1c5bae4f8 100644 --- a/src/rlvk.c +++ b/src/rlvk.c @@ -1,27 +1,784 @@ #include "rlvk.h" -#include "utils.h" // For TRACELOG if needed -#include // For printf in stubs -#include // For RL_MALLOC, RL_FREE if used +#include "utils.h" // For TRACELOG +#include // For TRACELOG / printf +#include // For RL_MALLOC, RL_FREE, NULL +#include // For strcmp, memset +#include // For bool type + +// Core Vulkan Handles +static VkInstance vkInstance = VK_NULL_HANDLE; +static VkSurfaceKHR vkSurface = VK_NULL_HANDLE; +static VkPhysicalDevice vkPhysicalDevice = VK_NULL_HANDLE; +static VkDevice vkDevice = VK_NULL_HANDLE; +static VkQueue vkGraphicsQueue = VK_NULL_HANDLE; +static VkQueue vkPresentQueue = VK_NULL_HANDLE; + +// Queue Family Indices +typedef struct { + uint32_t graphicsFamily; + uint32_t presentFamily; + bool graphicsFamilyHasValue; + bool presentFamilyHasValue; +} QueueFamilyIndices; +static QueueFamilyIndices queueFamilyIndices; + +// Swapchain related +static VkSwapchainKHR vkSwapchain = VK_NULL_HANDLE; +static VkFormat vkSwapchainImageFormat; +static VkExtent2D vkSwapchainExtent; +static VkImage* vkSwapchainImages = NULL; +static uint32_t vkSwapchainImageCount = 0; +static VkImageView* vkSwapchainImageViews = NULL; + +// Render Pass and Framebuffers +static VkRenderPass vkRenderPass = VK_NULL_HANDLE; +static VkFramebuffer* vkFramebuffers = NULL; // One per swapchain image view + +// Depth Buffer +static VkImage vkDepthImage = VK_NULL_HANDLE; +static VkDeviceMemory vkDepthImageMemory = VK_NULL_HANDLE; +static VkImageView vkDepthImageView = VK_NULL_HANDLE; +static VkFormat vkDepthFormat; + +// Command Pool and Command Buffers +static VkCommandPool vkCommandPool = VK_NULL_HANDLE; +static VkCommandBuffer* vkCommandBuffers = NULL; // One per framebuffer + +// Synchronization Primitives +static VkSemaphore vkImageAvailableSemaphore = VK_NULL_HANDLE; +static VkSemaphore vkRenderFinishedSemaphore = VK_NULL_HANDLE; +static VkFence* vkInFlightFences = NULL; // One per frame in flight (usually same as swapchain image count) +// static VkFence* imagesInFlight; // Maps swapchain images to fences -// Global or static variables for Vulkan state (minimal for stubs) static bool rlvkReady = false; +static int screenWidth = 0; +static int screenHeight = 0; + +// Drawing/Frame state +static uint32_t currentFrame = 0; +#define MAX_FRAMES_IN_FLIGHT 2 // Default to 2, will be set to vkSwapchainImageCount if different + // This define might become a static variable if vkSwapchainImageCount can change (e.g. recreation) +static uint32_t acquiredImageIndex = 0; // To store the image index from vkAcquireNextImageKHR + +// Clear values for the render pass +static VkClearColorValue currentClearColor = {{0.0f, 0.0f, 0.0f, 1.0f}}; // Default to black +static VkClearDepthStencilValue defaultDepthStencilClear = {1.0f, 0}; // Default depth/stencil clear + + +// Helper function to find suitable queue families +static QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device, VkSurfaceKHR surface) { + QueueFamilyIndices indices = {0, 0, false, false}; + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, NULL); + + VkQueueFamilyProperties* queueFamilies = (VkQueueFamilyProperties*)RL_MALLOC(queueFamilyCount * sizeof(VkQueueFamilyProperties)); + if (!queueFamilies) { + TRACELOG(LOG_ERROR, "RLVK: Failed to allocate memory for queue families"); + return indices; + } + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies); + + for (uint32_t i = 0; i < queueFamilyCount; i++) { + if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + indices.graphicsFamilyHasValue = true; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + if (presentSupport) { + indices.presentFamily = i; + indices.presentFamilyHasValue = true; + } + + if (indices.graphicsFamilyHasValue && indices.presentFamilyHasValue) { + break; + } + } + RL_FREE(queueFamilies); + return indices; +} + +// Helper function to check device suitability +static bool isDeviceSuitable(VkPhysicalDevice device, VkSurfaceKHR surface) { + QueueFamilyIndices indices = findQueueFamilies(device, surface); + // TODO: Check for required device extensions (e.g. swapchain) + // TODO: Query and check surface formats and present modes + return indices.graphicsFamilyHasValue && indices.presentFamilyHasValue; +} + void rlvkInit(VkInstance instance, VkSurfaceKHR surface, int width, int height) { - printf("rlvkInit called (STUB)\n"); - // Minimal check or setup - if (instance != VK_NULL_HANDLE && surface != VK_NULL_HANDLE) { - rlvkReady = true; - TRACELOG(LOG_INFO, "RLVK: Vulkan backend initialized (stubbed)."); + TRACELOG(LOG_INFO, "RLVK: Initializing Vulkan backend."); + if (rlvkReady) { + TRACELOG(LOG_WARNING, "RLVK: Vulkan backend already initialized."); + return; + } + + vkInstance = instance; + vkSurface = surface; + screenWidth = width; + screenHeight = height; + + if (vkInstance == VK_NULL_HANDLE) { + TRACELOG(LOG_FATAL, "RLVK: Provided VkInstance is NULL."); + return; + } + if (vkSurface == VK_NULL_HANDLE) { + TRACELOG(LOG_FATAL, "RLVK: Provided VkSurfaceKHR is NULL."); + return; + } + + // --- Physical Device Selection --- + uint32_t deviceCount = 0; + VkResult result = vkEnumeratePhysicalDevices(vkInstance, &deviceCount, NULL); + if (result != VK_SUCCESS || deviceCount == 0) { + TRACELOG(LOG_FATAL, "RLVK: Failed to find GPUs with Vulkan support!"); + return; + } + + VkPhysicalDevice* devices = (VkPhysicalDevice*)RL_MALLOC(deviceCount * sizeof(VkPhysicalDevice)); + if (!devices) { + TRACELOG(LOG_FATAL, "RLVK: Failed to allocate memory for physical devices list."); + return; + } + result = vkEnumeratePhysicalDevices(vkInstance, &deviceCount, devices); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to enumerate physical devices."); + RL_FREE(devices); + return; + } + + TRACELOG(LOG_INFO, "RLVK: Found %d physical device(s).", deviceCount); + VkPhysicalDeviceProperties chosenDeviceProperties; // To store properties of the chosen device for logging or other uses + + for (uint32_t i = 0; i < deviceCount; i++) { + VkPhysicalDeviceProperties deviceProperties; + vkGetPhysicalDeviceProperties(devices[i], &deviceProperties); + TRACELOG(LOG_DEBUG, "RLVK: Evaluating device: %s (ID: %u, Type: %u)", deviceProperties.deviceName, deviceProperties.deviceID, deviceProperties.deviceType); + + if (isDeviceSuitable(devices[i], vkSurface)) { + // Prefer discrete GPU if available + if (vkPhysicalDevice == VK_NULL_HANDLE || deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + vkPhysicalDevice = devices[i]; + chosenDeviceProperties = deviceProperties; // Store its properties + // If it's a discrete GPU, we might want to break early, or continue to see if there are others. + // For now, take the first suitable discrete GPU or the first suitable integrated/other if no discrete. + if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + TRACELOG(LOG_INFO, "RLVK: Selected discrete GPU: %s", deviceProperties.deviceName); + break; + } + } + } + } + RL_FREE(devices); + + if (vkPhysicalDevice == VK_NULL_HANDLE) { + TRACELOG(LOG_FATAL, "RLVK: Failed to find a suitable GPU!"); + return; + } + TRACELOG(LOG_INFO, "RLVK: Selected physical device: %s", chosenDeviceProperties.deviceName); + + + queueFamilyIndices = findQueueFamilies(vkPhysicalDevice, vkSurface); + if (!queueFamilyIndices.graphicsFamilyHasValue || !queueFamilyIndices.presentFamilyHasValue) { + TRACELOG(LOG_FATAL, "RLVK: Could not find required queue families on selected physical device."); + vkPhysicalDevice = VK_NULL_HANDLE; // Reset since it's not fully suitable + return; + } + TRACELOG(LOG_INFO, "RLVK: Graphics Queue Family Index: %u", queueFamilyIndices.graphicsFamily); + TRACELOG(LOG_INFO, "RLVK: Present Queue Family Index: %u", queueFamilyIndices.presentFamily); + + + // Placeholder for further initialization steps + // For now, if we reached here with a physical device, consider it partially ready for this phase. + // rlvkReady = true; // This will be set at the very end of the full Init function. + TRACELOG(LOG_INFO, "RLVK: Physical device selected successfully. Further initialization pending."); + + // TODO: Implement steps 3-11 as per the plan. + // For now, stubbing the rest of the function. + // This is just Phase 1. + if (vkPhysicalDevice != VK_NULL_HANDLE) { + // rlvkReady = true; // This will be at the end of the full function + TRACELOG(LOG_INFO, "RLVK: Stub: Phase 1 (Device Selection) complete."); + } else { + TRACELOG(LOG_ERROR, "RLVK: Stub: Phase 1 (Device Selection) failed."); + rlvkReady = false; + return; + } + // --- Logical Device Creation --- + float queuePriority = 1.0f; + VkDeviceQueueCreateInfo queueCreateInfos[2]; // Max 2: one for graphics, one for present (if different) + uint32_t uniqueQueueFamilyCount = 0; + + // Graphics Queue + queueCreateInfos[uniqueQueueFamilyCount].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfos[uniqueQueueFamilyCount].queueFamilyIndex = queueFamilyIndices.graphicsFamily; + queueCreateInfos[uniqueQueueFamilyCount].queueCount = 1; + queueCreateInfos[uniqueQueueFamilyCount].pQueuePriorities = &queuePriority; + queueCreateInfos[uniqueQueueFamilyCount].pNext = NULL; + queueCreateInfos[uniqueQueueFamilyCount].flags = 0; + uniqueQueueFamilyCount++; + + // Present Queue (if different from graphics) + if (queueFamilyIndices.presentFamily != queueFamilyIndices.graphicsFamily) { + queueCreateInfos[uniqueQueueFamilyCount].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfos[uniqueQueueFamilyCount].queueFamilyIndex = queueFamilyIndices.presentFamily; + queueCreateInfos[uniqueQueueFamilyCount].queueCount = 1; + queueCreateInfos[uniqueQueueFamilyCount].pQueuePriorities = &queuePriority; + queueCreateInfos[uniqueQueueFamilyCount].pNext = NULL; + queueCreateInfos[uniqueQueueFamilyCount].flags = 0; + uniqueQueueFamilyCount++; + } + + VkPhysicalDeviceFeatures deviceFeatures = {0}; // Initialize all features to VK_FALSE + // Enable specific features if needed, e.g. deviceFeatures.samplerAnisotropy = VK_TRUE; + + const char* deviceExtensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME + }; + + VkDeviceCreateInfo deviceCreateInfo = {0}; + deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + deviceCreateInfo.queueCreateInfoCount = uniqueQueueFamilyCount; + deviceCreateInfo.pQueueCreateInfos = queueCreateInfos; + deviceCreateInfo.pEnabledFeatures = &deviceFeatures; + deviceCreateInfo.enabledExtensionCount = sizeof(deviceExtensions) / sizeof(deviceExtensions[0]); + deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions; + // deviceCreateInfo.enabledLayerCount is deprecated and ignored for vkCreateDevice. Validation layers are instance-level. + + result = vkCreateDevice(vkPhysicalDevice, &deviceCreateInfo, NULL, &vkDevice); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to create logical device (Error: %i)", result); + // Potentially reset vkPhysicalDevice here if cleanup is needed + return; + } + TRACELOG(LOG_INFO, "RLVK: Logical device created successfully."); + + vkGetDeviceQueue(vkDevice, queueFamilyIndices.graphicsFamily, 0, &vkGraphicsQueue); + vkGetDeviceQueue(vkDevice, queueFamilyIndices.presentFamily, 0, &vkPresentQueue); + TRACELOG(LOG_INFO, "RLVK: Graphics and Present queues obtained."); + + // --- Swapchain Creation --- + VkSurfaceCapabilitiesKHR capabilities; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vkPhysicalDevice, vkSurface, &capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(vkPhysicalDevice, vkSurface, &formatCount, NULL); + VkSurfaceFormatKHR* formats = NULL; + if (formatCount != 0) { + formats = (VkSurfaceFormatKHR*)RL_MALLOC(formatCount * sizeof(VkSurfaceFormatKHR)); + vkGetPhysicalDeviceSurfaceFormatsKHR(vkPhysicalDevice, vkSurface, &formatCount, formats); + } else { + TRACELOG(LOG_FATAL, "RLVK: No surface formats found for swapchain creation."); + vkDestroyDevice(vkDevice, NULL); vkDevice = VK_NULL_HANDLE; + return; + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(vkPhysicalDevice, vkSurface, &presentModeCount, NULL); + VkPresentModeKHR* presentModes = NULL; + if (presentModeCount != 0) { + presentModes = (VkPresentModeKHR*)RL_MALLOC(presentModeCount * sizeof(VkPresentModeKHR)); + vkGetPhysicalDeviceSurfacePresentModesKHR(vkPhysicalDevice, vkSurface, &presentModeCount, presentModes); + } else { + TRACELOG(LOG_FATAL, "RLVK: No present modes found for swapchain creation."); + RL_FREE(formats); + vkDestroyDevice(vkDevice, NULL); vkDevice = VK_NULL_HANDLE; + return; + } + + // Choose swap surface format + VkSurfaceFormatKHR surfaceFormat = formats[0]; // Default to first available + for (uint32_t i = 0; i < formatCount; i++) { + if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB && formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + surfaceFormat = formats[i]; + break; + } + } + vkSwapchainImageFormat = surfaceFormat.format; + TRACELOG(LOG_INFO, "RLVK: Chosen swapchain format: %d, color space: %d", surfaceFormat.format, surfaceFormat.colorSpace); + + // Choose swap present mode + VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; // Guaranteed to be available + for (uint32_t i = 0; i < presentModeCount; i++) { + if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { + presentMode = presentModes[i]; + break; + } + } + TRACELOG(LOG_INFO, "RLVK: Chosen present mode: %d", presentMode); + + RL_FREE(formats); + RL_FREE(presentModes); + + // Choose swap extent + if (capabilities.currentExtent.width != UINT32_MAX) { + vkSwapchainExtent = capabilities.currentExtent; + } else { + vkSwapchainExtent.width = (uint32_t)screenWidth; + vkSwapchainExtent.height = (uint32_t)screenHeight; + vkSwapchainExtent.width = MAX(capabilities.minImageExtent.width, MIN(capabilities.maxImageExtent.width, vkSwapchainExtent.width)); + vkSwapchainExtent.height = MAX(capabilities.minImageExtent.height, MIN(capabilities.maxImageExtent.height, vkSwapchainExtent.height)); + } + TRACELOG(LOG_INFO, "RLVK: Swapchain extent: %u x %u", vkSwapchainExtent.width, vkSwapchainExtent.height); + + vkSwapchainImageCount = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 && vkSwapchainImageCount > capabilities.maxImageCount) { + vkSwapchainImageCount = capabilities.maxImageCount; + } + TRACELOG(LOG_INFO, "RLVK: Swapchain image count: %u", vkSwapchainImageCount); + + VkSwapchainCreateInfoKHR swapchainCreateInfo = {0}; + swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchainCreateInfo.surface = vkSurface; + swapchainCreateInfo.minImageCount = vkSwapchainImageCount; + swapchainCreateInfo.imageFormat = surfaceFormat.format; + swapchainCreateInfo.imageColorSpace = surfaceFormat.colorSpace; + swapchainCreateInfo.imageExtent = vkSwapchainExtent; + swapchainCreateInfo.imageArrayLayers = 1; + swapchainCreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; // For rendering directly to swapchain images + + uint32_t qFamilyIndices[] = {queueFamilyIndices.graphicsFamily, queueFamilyIndices.presentFamily}; + if (queueFamilyIndices.graphicsFamily != queueFamilyIndices.presentFamily) { + swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + swapchainCreateInfo.queueFamilyIndexCount = 2; + swapchainCreateInfo.pQueueFamilyIndices = qFamilyIndices; } else { - TRACELOG(LOG_ERROR, "RLVK: Failed to initialize Vulkan backend due to null instance or surface (stubbed)."); - rlvkReady = false; + swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapchainCreateInfo.queueFamilyIndexCount = 0; // Optional + swapchainCreateInfo.pQueueFamilyIndices = NULL; // Optional + } + + swapchainCreateInfo.preTransform = capabilities.currentTransform; + swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; // No alpha blending with window system + swapchainCreateInfo.presentMode = presentMode; + swapchainCreateInfo.clipped = VK_TRUE; // Allow clipping if other windows obscure parts of the surface + swapchainCreateInfo.oldSwapchain = VK_NULL_HANDLE; // For resizing, not used now + + result = vkCreateSwapchainKHR(vkDevice, &swapchainCreateInfo, NULL, &vkSwapchain); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to create swapchain (Error: %i)", result); + vkDestroyDevice(vkDevice, NULL); vkDevice = VK_NULL_HANDLE; + return; + } + TRACELOG(LOG_INFO, "RLVK: Swapchain created successfully."); + + // Get swapchain images + // vkSwapchainImageCount was requested, now query actual count (can be higher) + vkGetSwapchainImagesKHR(vkDevice, vkSwapchain, &vkSwapchainImageCount, NULL); + vkSwapchainImages = (VkImage*)RL_MALLOC(vkSwapchainImageCount * sizeof(VkImage)); + if (!vkSwapchainImages) { + TRACELOG(LOG_FATAL, "RLVK: Failed to allocate memory for swapchain images."); + vkDestroySwapchainKHR(vkDevice, vkSwapchain, NULL); vkSwapchain = VK_NULL_HANDLE; + vkDestroyDevice(vkDevice, NULL); vkDevice = VK_NULL_HANDLE; + return; + } + vkGetSwapchainImagesKHR(vkDevice, vkSwapchain, &vkSwapchainImageCount, vkSwapchainImages); + TRACELOG(LOG_INFO, "RLVK: Retrieved %u swapchain images.", vkSwapchainImageCount); + + // --- Image View Creation (Step 5) --- + vkSwapchainImageViews = (VkImageView*)RL_MALLOC(vkSwapchainImageCount * sizeof(VkImageView)); + if (!vkSwapchainImageViews) { + TRACELOG(LOG_FATAL, "RLVK: Failed to allocate memory for swapchain image views."); + // Perform necessary cleanup from previous steps + RL_FREE(vkSwapchainImages); vkSwapchainImages = NULL; + vkDestroySwapchainKHR(vkDevice, vkSwapchain, NULL); vkSwapchain = VK_NULL_HANDLE; + vkDestroyDevice(vkDevice, NULL); vkDevice = VK_NULL_HANDLE; + return; + } + + for (uint32_t i = 0; i < vkSwapchainImageCount; i++) { + VkImageViewCreateInfo viewInfo = {0}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = vkSwapchainImages[i]; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = vkSwapchainImageFormat; + viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + result = vkCreateImageView(vkDevice, &viewInfo, NULL, &vkSwapchainImageViews[i]); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to create image view %u (Error: %i)", i, result); + // Perform cleanup + for (uint32_t j = 0; j < i; j++) vkDestroyImageView(vkDevice, vkSwapchainImageViews[j], NULL); + RL_FREE(vkSwapchainImageViews); vkSwapchainImageViews = NULL; + RL_FREE(vkSwapchainImages); vkSwapchainImages = NULL; + vkDestroySwapchainKHR(vkDevice, vkSwapchain, NULL); vkSwapchain = VK_NULL_HANDLE; + vkDestroyDevice(vkDevice, NULL); vkDevice = VK_NULL_HANDLE; + return; + } + } + TRACELOG(LOG_INFO, "RLVK: Swapchain image views created successfully."); + + // --- Render Pass Creation (Step 6) --- + // Find supported depth format (helper function recommended) + // VkFormat findSupportedFormat(const VkFormat* candidates, uint32_t candidateCount, VkImageTiling tiling, VkFormatFeatureFlags features); + // For now, assume VK_FORMAT_D32_SFLOAT is supported. A real implementation needs findSupportedFormat. + vkDepthFormat = VK_FORMAT_D32_SFLOAT; // Placeholder - must be queried + + VkAttachmentDescription colorAttachment = {0}; + colorAttachment.format = vkSwapchainImageFormat; + 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; + + VkAttachmentDescription depthAttachment = {0}; + depthAttachment.format = vkDepthFormat; // Must be a supported depth format + depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef = {0}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef = {0}; + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {0}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + VkSubpassDependency dependency = {0}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + VkAttachmentDescription attachments[] = {colorAttachment, depthAttachment}; + VkRenderPassCreateInfo renderPassInfo = {0}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = sizeof(attachments) / sizeof(VkAttachmentDescription); + renderPassInfo.pAttachments = attachments; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + result = vkCreateRenderPass(vkDevice, &renderPassInfo, NULL, &vkRenderPass); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to create render pass (Error: %i)", result); + // Perform cleanup... (image views, swapchain, device etc.) + return; // Simplified cleanup for now + } + TRACELOG(LOG_INFO, "RLVK: Render pass created successfully."); + + // --- Depth Resources Creation (Step 7) --- + VkImageCreateInfo imageInfo = {0}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = vkSwapchainExtent.width; + imageInfo.extent.height = vkSwapchainExtent.height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = vkDepthFormat; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // For best performance + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + result = vkCreateImage(vkDevice, &imageInfo, NULL, &vkDepthImage); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to create depth image (Error: %i)", result); + // Cleanup... + return; + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(vkDevice, vkDepthImage, &memRequirements); + + VkMemoryAllocateInfo allocInfo = {0}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + // allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); // Helper needed + // For now, assume memoryTypeIndex 0 is valid (highly unlikely in real scenario without querying) + // This needs a proper findMemoryType implementation. + uint32_t memoryTypeIndex = 0; // Placeholder - THIS IS A BUG without proper findMemoryType + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(vkPhysicalDevice, &memProperties); + bool memoryTypeFound = false; + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((memRequirements.memoryTypeBits & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + memoryTypeIndex = i; + memoryTypeFound = true; + break; + } + } + if (!memoryTypeFound) { + TRACELOG(LOG_FATAL, "RLVK: Failed to find suitable memory type for depth image!"); + // Cleanup... + return; + } + allocInfo.memoryTypeIndex = memoryTypeIndex; + + + result = vkAllocateMemory(vkDevice, &allocInfo, NULL, &vkDepthImageMemory); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to allocate depth image memory (Error: %i)", result); + // Cleanup... + return; + } + vkBindImageMemory(vkDevice, vkDepthImage, vkDepthImageMemory, 0); + + VkImageViewCreateInfo depthViewInfo = {0}; + depthViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + depthViewInfo.image = vkDepthImage; + depthViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + depthViewInfo.format = vkDepthFormat; + depthViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + depthViewInfo.subresourceRange.baseMipLevel = 0; + depthViewInfo.subresourceRange.levelCount = 1; + depthViewInfo.subresourceRange.baseArrayLayer = 0; + depthViewInfo.subresourceRange.layerCount = 1; + + result = vkCreateImageView(vkDevice, &depthViewInfo, NULL, &vkDepthImageView); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to create depth image view (Error: %i)", result); + // Cleanup... + return; + } + TRACELOG(LOG_INFO, "RLVK: Depth resources created successfully."); + + // --- Framebuffer Creation (Step 8) --- + vkFramebuffers = (VkFramebuffer*)RL_MALLOC(vkSwapchainImageCount * sizeof(VkFramebuffer)); + if (!vkFramebuffers) { + TRACELOG(LOG_FATAL, "RLVK: Failed to allocate memory for framebuffers."); + // Perform cleanup... (this is getting repetitive, a helper might be good) + return; + } + for (uint32_t i = 0; i < vkSwapchainImageCount; i++) { + VkImageView attachments[] = { + vkSwapchainImageViews[i], + vkDepthImageView + }; + + VkFramebufferCreateInfo framebufferInfo = {0}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = vkRenderPass; + framebufferInfo.attachmentCount = sizeof(attachments) / sizeof(VkImageView); + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = vkSwapchainExtent.width; + framebufferInfo.height = vkSwapchainExtent.height; + framebufferInfo.layers = 1; + + result = vkCreateFramebuffer(vkDevice, &framebufferInfo, NULL, &vkFramebuffers[i]); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to create framebuffer %u (Error: %i)", i, result); + // Perform cleanup... + return; + } + } + TRACELOG(LOG_INFO, "RLVK: Framebuffers created successfully."); + + // --- Command Pool and Command Buffers (Step 9) --- + VkCommandPoolCreateInfo poolInfo = {0}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + + result = vkCreateCommandPool(vkDevice, &poolInfo, NULL, &vkCommandPool); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to create command pool (Error: %i)", result); + // Perform cleanup... + return; + } + TRACELOG(LOG_INFO, "RLVK: Command pool created successfully."); + + vkCommandBuffers = (VkCommandBuffer*)RL_MALLOC(vkSwapchainImageCount * sizeof(VkCommandBuffer)); + if(!vkCommandBuffers) { + TRACELOG(LOG_FATAL, "RLVK: Failed to allocate memory for command buffers."); + // Perform cleanup... + return; + } + + VkCommandBufferAllocateInfo cmdAllocInfo = {0}; + cmdAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + cmdAllocInfo.commandPool = vkCommandPool; + cmdAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + cmdAllocInfo.commandBufferCount = vkSwapchainImageCount; // Allocate all at once + + result = vkAllocateCommandBuffers(vkDevice, &cmdAllocInfo, vkCommandBuffers); + if (result != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to allocate command buffers (Error: %i)", result); + // Perform cleanup... + return; + } + TRACELOG(LOG_INFO, "RLVK: Command buffers allocated successfully."); + + // --- Synchronization Primitives (Step 10) --- + VkSemaphoreCreateInfo semaphoreInfo = {0}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo = {0}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; // Create fences in signaled state + + vkInFlightFences = (VkFence*)RL_MALLOC(vkSwapchainImageCount * sizeof(VkFence)); + if (!vkInFlightFences) { + TRACELOG(LOG_FATAL, "RLVK: Failed to allocate memory for in-flight fences."); + // Perform cleanup... + return; + } + + if (vkCreateSemaphore(vkDevice, &semaphoreInfo, NULL, &vkImageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(vkDevice, &semaphoreInfo, NULL, &vkRenderFinishedSemaphore) != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to create semaphores."); + // Perform cleanup... + if (vkImageAvailableSemaphore != VK_NULL_HANDLE) vkDestroySemaphore(vkDevice, vkImageAvailableSemaphore, NULL); + if (vkRenderFinishedSemaphore != VK_NULL_HANDLE) vkDestroySemaphore(vkDevice, vkRenderFinishedSemaphore, NULL); + RL_FREE(vkInFlightFences); vkInFlightFences = NULL; + return; + } + + for (uint32_t i = 0; i < vkSwapchainImageCount; i++) { + if (vkCreateFence(vkDevice, &fenceInfo, NULL, &vkInFlightFences[i]) != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to create fence %u.", i); + // Perform cleanup for already created fences and semaphores + for(uint32_t j=0; j < i; ++j) vkDestroyFence(vkDevice, vkInFlightFences[j], NULL); + RL_FREE(vkInFlightFences); vkInFlightFences = NULL; + vkDestroySemaphore(vkDevice, vkImageAvailableSemaphore, NULL); + vkDestroySemaphore(vkDevice, vkRenderFinishedSemaphore, NULL); + return; + } } + TRACELOG(LOG_INFO, "RLVK: Synchronization primitives created successfully."); + + rlvkReady = true; // All initialization steps completed successfully + TRACELOG(LOG_INFO, "RLVK: Vulkan backend initialized successfully."); } void rlvkClose(void) { - printf("rlvkClose called (STUB)\n"); + TRACELOG(LOG_INFO, "RLVK: Closing Vulkan backend."); + + if (vkDevice != VK_NULL_HANDLE) { + vkDeviceWaitIdle(vkDevice); // Ensure device is idle before destroying resources + } + + if (vkImageAvailableSemaphore != VK_NULL_HANDLE) { + vkDestroySemaphore(vkDevice, vkImageAvailableSemaphore, NULL); + vkImageAvailableSemaphore = VK_NULL_HANDLE; + } + if (vkRenderFinishedSemaphore != VK_NULL_HANDLE) { + vkDestroySemaphore(vkDevice, vkRenderFinishedSemaphore, NULL); + vkRenderFinishedSemaphore = VK_NULL_HANDLE; + } + if (vkInFlightFences != NULL) { + // Use MAX_FRAMES_IN_FLIGHT or actual count used for fences if different from vkSwapchainImageCount + uint32_t fenceCount = vkSwapchainImageCount; // Assuming fences per swapchain image for now + for (uint32_t i = 0; i < fenceCount; i++) { + if (vkInFlightFences[i] != VK_NULL_HANDLE) { + vkDestroyFence(vkDevice, vkInFlightFences[i], NULL); + } + } + RL_FREE(vkInFlightFences); + vkInFlightFences = NULL; + TRACELOG(LOG_DEBUG, "RLVK: Fences destroyed."); + } + + if (vkCommandPool != VK_NULL_HANDLE) { + vkDestroyCommandPool(vkDevice, vkCommandPool, NULL); + vkCommandPool = VK_NULL_HANDLE; + TRACELOG(LOG_DEBUG, "RLVK: Command pool destroyed."); + } + if (vkCommandBuffers != NULL) { // Command buffers are freed with the pool + RL_FREE(vkCommandBuffers); + vkCommandBuffers = NULL; + } + + if (vkFramebuffers != NULL) { + for (uint32_t i = 0; i < vkSwapchainImageCount; i++) { + if (vkFramebuffers[i] != VK_NULL_HANDLE) { + vkDestroyFramebuffer(vkDevice, vkFramebuffers[i], NULL); + } + } + RL_FREE(vkFramebuffers); + vkFramebuffers = NULL; + TRACELOG(LOG_DEBUG, "RLVK: Framebuffers destroyed."); + } + + if (vkDepthImageView != VK_NULL_HANDLE) { + vkDestroyImageView(vkDevice, vkDepthImageView, NULL); + vkDepthImageView = VK_NULL_HANDLE; + } + if (vkDepthImage != VK_NULL_HANDLE) { + vkDestroyImage(vkDevice, vkDepthImage, NULL); + vkDepthImage = VK_NULL_HANDLE; + } + if (vkDepthImageMemory != VK_NULL_HANDLE) { + vkFreeMemory(vkDevice, vkDepthImageMemory, NULL); + vkDepthImageMemory = VK_NULL_HANDLE; + } + TRACELOG(LOG_DEBUG, "RLVK: Depth resources destroyed."); + + if (vkRenderPass != VK_NULL_HANDLE) { + vkDestroyRenderPass(vkDevice, vkRenderPass, NULL); + vkRenderPass = VK_NULL_HANDLE; + TRACELOG(LOG_DEBUG, "RLVK: Render pass destroyed."); + } + + if (vkSwapchainImageViews != NULL) { + for (uint32_t i = 0; i < vkSwapchainImageCount; i++) { + if (vkSwapchainImageViews[i] != VK_NULL_HANDLE) { + vkDestroyImageView(vkDevice, vkSwapchainImageViews[i], NULL); + } + } + RL_FREE(vkSwapchainImageViews); + vkSwapchainImageViews = NULL; + TRACELOG(LOG_DEBUG, "RLVK: Swapchain image views destroyed."); + } + + if (vkSwapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(vkDevice, vkSwapchain, NULL); + vkSwapchain = VK_NULL_HANDLE; + TRACELOG(LOG_DEBUG, "RLVK: Swapchain destroyed."); + } + if (vkSwapchainImages != NULL) { + RL_FREE(vkSwapchainImages); + vkSwapchainImages = NULL; + } + + vkGraphicsQueue = VK_NULL_HANDLE; + vkPresentQueue = VK_NULL_HANDLE; + if (vkDevice != VK_NULL_HANDLE) { + vkDestroyDevice(vkDevice, NULL); + TRACELOG(LOG_DEBUG,"RLVK: Logical device destroyed."); + } + vkDevice = VK_NULL_HANDLE; + vkPhysicalDevice = VK_NULL_HANDLE; + + vkSurface = VK_NULL_HANDLE; + vkInstance = VK_NULL_HANDLE; + + memset(&queueFamilyIndices, 0, sizeof(QueueFamilyIndices)); + screenWidth = 0; + screenHeight = 0; + currentFrame = 0; // Reset frame counter + rlvkReady = false; - TRACELOG(LOG_INFO, "RLVK: Vulkan backend closed (stubbed)."); + TRACELOG(LOG_INFO, "RLVK: Vulkan backend resources reset."); } bool rlvkIsReady(void) { @@ -29,15 +786,144 @@ bool rlvkIsReady(void) { } void rlvkBeginDrawing(void) { - // printf("rlvkBeginDrawing called (STUB)\n"); + if (!rlvkReady) return; + + // Wait for the fence of the current frame to ensure the command buffer is free to be reused + // MAX_FRAMES_IN_FLIGHT should be used here instead of vkSwapchainImageCount if they can differ. + // For this implementation, we assume they are the same (fences per swapchain image). + VkResult fenceResult = vkWaitForFences(vkDevice, 1, &vkInFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + if (fenceResult != VK_SUCCESS) { + TRACELOG(LOG_ERROR, "RLVK: Failed to wait for fence (Error: %i)", fenceResult); + // Handle error, possibly by trying to recreate resources or exiting + return; + } + + // Acquire next image from swapchain + VkResult acquireResult = vkAcquireNextImageKHR(vkDevice, vkSwapchain, UINT64_MAX, vkImageAvailableSemaphore, VK_NULL_HANDLE, &acquiredImageIndex); + + if (acquireResult == VK_ERROR_OUT_OF_DATE_KHR) { + TRACELOG(LOG_WARNING, "RLVK: Swapchain out of date during vkAcquireNextImageKHR. TODO: Recreate swapchain."); + // rlRecreateSwapchain(); // Placeholder for swapchain recreation logic + return; // Skip rendering this frame + } else if (acquireResult != VK_SUCCESS && acquireResult != VK_SUBOPTIMAL_KHR) { + TRACELOG(LOG_FATAL, "RLVK: Failed to acquire swapchain image (Error: %i)", acquireResult); + return; // Skip rendering this frame + } + + // Only reset the fence if we are sure we will submit work with it. + // This happens after successfully acquiring an image. + vkResetFences(vkDevice, 1, &vkInFlightFences[currentFrame]); + + + // Begin Command Buffer + VkCommandBuffer currentCmdBuffer = vkCommandBuffers[acquiredImageIndex]; // Use command buffer corresponding to acquired image + // Or use vkCommandBuffers[currentFrame] if MAX_FRAMES_IN_FLIGHT is less than swapchain image count. + // For now, assume one command buffer per swapchain image. + + // vkResetCommandBuffer(currentCmdBuffer, 0); // Not needed if VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT is set on pool + + VkCommandBufferBeginInfo beginInfo = {0}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + if (vkBeginCommandBuffer(currentCmdBuffer, &beginInfo) != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to begin command buffer."); + return; + } + + // Begin Render Pass + VkClearValue clearValues[2]; + clearValues[0].color = currentClearColor; // Use the color set by rlvkClearBackground + clearValues[1].depthStencil = defaultDepthStencilClear; + + VkRenderPassBeginInfo renderPassInfo = {0}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = vkRenderPass; + renderPassInfo.framebuffer = vkFramebuffers[acquiredImageIndex]; + renderPassInfo.renderArea.offset = (VkOffset2D){0, 0}; + renderPassInfo.renderArea.extent = vkSwapchainExtent; + renderPassInfo.clearValueCount = sizeof(clearValues) / sizeof(VkClearValue); + renderPassInfo.pClearValues = clearValues; + + vkCmdBeginRenderPass(currentCmdBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + // Set Dynamic Viewport and Scissor + VkViewport viewport = {0}; + viewport.x = 0.0f; + viewport.y = 0.0f; // Or (float)vkSwapchainExtent.height and negative height if flipping + viewport.width = (float)vkSwapchainExtent.width; + viewport.height = (float)vkSwapchainExtent.height; // Or -(float)vkSwapchainExtent.height if flipping + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(currentCmdBuffer, 0, 1, &viewport); + + VkRect2D scissor = {0}; + scissor.offset = (VkOffset2D){0, 0}; + scissor.extent = vkSwapchainExtent; + vkCmdSetScissor(currentCmdBuffer, 0, 1, &scissor); } void rlvkEndDrawing(void) { - // printf("rlvkEndDrawing called (STUB)\n"); + if (!rlvkReady) return; + + VkCommandBuffer currentCmdBuffer = vkCommandBuffers[acquiredImageIndex]; // Or vkCommandBuffers[currentFrame] + + vkCmdEndRenderPass(currentCmdBuffer); + if (vkEndCommandBuffer(currentCmdBuffer) != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to end command buffer."); + return; + } + + // Submit Command Buffer + VkSubmitInfo submitInfo = {0}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {vkImageAvailableSemaphore}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = ¤tCmdBuffer; + VkSemaphore signalSemaphores[] = {vkRenderFinishedSemaphore}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + VkResult submitResult = vkQueueSubmit(vkGraphicsQueue, 1, &submitInfo, vkInFlightFences[currentFrame]); + if (submitResult != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to submit draw command buffer (Error: %i)", submitResult); + return; + } + + // Present Image + VkPresentInfoKHR presentInfo = {0}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + VkSwapchainKHR swapchains[] = {vkSwapchain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapchains; + presentInfo.pImageIndices = &acquiredImageIndex; + + VkResult presentResult = vkQueuePresentKHR(vkPresentQueue, &presentInfo); + + if (presentResult == VK_ERROR_OUT_OF_DATE_KHR || presentResult == VK_SUBOPTIMAL_KHR) { + TRACELOG(LOG_WARNING, "RLVK: Swapchain out of date or suboptimal during vkQueuePresentKHR. TODO: Recreate swapchain."); + // rlRecreateSwapchain(); + } else if (presentResult != VK_SUCCESS) { + TRACELOG(LOG_FATAL, "RLVK: Failed to present swapchain image (Error: %i)", presentResult); + } + + // MAX_FRAMES_IN_FLIGHT needs to be well defined. Assuming it's vkSwapchainImageCount for now for simplicity of fence management. + // If MAX_FRAMES_IN_FLIGHT is less than vkSwapchainImageCount, the logic for fences and command buffers needs adjustment. + currentFrame = (currentFrame + 1) % vkSwapchainImageCount; } void rlvkClearBackground(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { - // printf("rlvkClearBackground called (STUB) with color: %u, %u, %u, %u\n", r, g, b, a); + currentClearColor.float32[0] = (float)r / 255.0f; + currentClearColor.float32[1] = (float)g / 255.0f; + currentClearColor.float32[2] = (float)b / 255.0f; + currentClearColor.float32[3] = (float)a / 255.0f; } unsigned int rlvkLoadTexture(const void *data, int width, int height, int format, int mipmaps) {