diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a22edab --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.o +*.so +examples/example +examples/image +examples/opengl +examples/rotate +examples/vulkan diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..261e265 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,3 @@ +-D_MILSKO +-DUSE_X11 +-Iinclude diff --git a/examples/triangle.frag.spv b/examples/triangle.frag.spv new file mode 100644 index 0000000..a664e83 Binary files /dev/null and b/examples/triangle.frag.spv differ diff --git a/examples/triangle.vert.spv b/examples/triangle.vert.spv new file mode 100644 index 0000000..0855f65 Binary files /dev/null and b/examples/triangle.vert.spv differ diff --git a/examples/vulkan.c b/examples/vulkan.c new file mode 100644 index 0000000..e039eea --- /dev/null +++ b/examples/vulkan.c @@ -0,0 +1,610 @@ +/* $Id$ */ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +MwWidget window, vulkan; +int ow = 300; +int oh = 250; + +PFN_vkGetInstanceProcAddr _vkGetInstanceProcAddr; +VkInstance instance; +VkDevice device; +VkPhysicalDevice physicalDevice; +VkSurfaceKHR surface; +VkQueue graphicsQueue; +VkQueue presentQueue; + +VkSwapchainKHR swapchain; +VkImage* swapchainImages; +VkImageView* swapchainImageView; +uint32_t swapchainImageViewCount; +uint32_t currentImageIndex; +uint32_t frameNumber; + +VkFence* fences; +VkSemaphore* renderFinishedSemaphores; +VkSemaphore* imageAvaliableSemaphores; + +VkFramebuffer* framebuffers; + +VkPipeline pipeline; +VkCommandBuffer* cmdBuffers; +VkRenderPass renderPass; + +VkResult res; + +VkSwapchainCreateInfoKHR swapchainCreateInfo = {}; + +#define MAX_FRAMES_IN_FLIGHT swapchainImageViewCount + +// convienence macro for loading a vulkan function pointer into memory +#define LOAD_VK_FUNCTION(name) \ + PFN_##name _##name = (PFN_##name)_vkGetInstanceProcAddr(instance, #name); \ + assert(_##name); + +void tick(MwWidget handle, void* user_data, void* call_data) { + (void)handle; + (void)user_data; + (void)call_data; + + VkCommandBufferBeginInfo beginInfo = {}; + VkRenderPassBeginInfo renderPassInfo = {}; + VkSubmitInfo submitInfo = {}; + VkPresentInfoKHR presentInfo = {}; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + uint32_t vertexCount = 3; + uint32_t instanceCount = 1; + + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT}; + + LOAD_VK_FUNCTION(vkCreateFence); + LOAD_VK_FUNCTION(vkWaitForFences); + LOAD_VK_FUNCTION(vkResetFences); + LOAD_VK_FUNCTION(vkResetCommandBuffer); + LOAD_VK_FUNCTION(vkBeginCommandBuffer); + LOAD_VK_FUNCTION(vkEndCommandBuffer); + LOAD_VK_FUNCTION(vkQueueSubmit); + LOAD_VK_FUNCTION(vkAcquireNextImageKHR); + LOAD_VK_FUNCTION(vkQueuePresentKHR); + LOAD_VK_FUNCTION(vkCreateSwapchainKHR); + + LOAD_VK_FUNCTION(vkCmdBeginRenderPass); + LOAD_VK_FUNCTION(vkCmdEndRenderPass); + LOAD_VK_FUNCTION(vkCmdDraw); + LOAD_VK_FUNCTION(vkCmdBindPipeline); + + if((res = _vkWaitForFences(device, 1, &fences[frameNumber], VK_TRUE, UINT64_MAX)) != VK_SUCCESS) { + printf("error waiting on fence: %s\n", string_VkResult(res)); + exit(0); + } + + if((res = _vkResetFences(device, 1, &fences[frameNumber])) != VK_SUCCESS) { + printf("error resetting fence: %s\n", string_VkResult(res)); + exit(0); + } + +swapchainRetry: + if((res = _vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, imageAvaliableSemaphores[frameNumber], NULL, ¤tImageIndex)) != VK_SUCCESS) { + if(res == VK_ERROR_OUT_OF_DATE_KHR) { + if(_vkCreateSwapchainKHR(device, &swapchainCreateInfo, NULL, &swapchain) != VK_SUCCESS) { + printf("failed to create swapchain: %s\n", string_VkResult(res)); + exit(0); + }; + goto swapchainRetry; + } + }; + + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.pNext = NULL; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + beginInfo.pInheritanceInfo = NULL; + + if((res = _vkResetCommandBuffer(cmdBuffers[frameNumber], 0)) != VK_SUCCESS) { + printf("error beginning command buffer record: %s\n", string_VkResult(res)); + exit(0); + } + + if((res = _vkBeginCommandBuffer(cmdBuffers[frameNumber], &beginInfo)) != VK_SUCCESS) { + printf("error beginning command buffer record: %s\n", string_VkResult(res)); + exit(0); + } + + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.pNext = NULL; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = framebuffers[frameNumber]; + renderPassInfo.renderArea.offset = (VkOffset2D){0, 0}; + renderPassInfo.renderArea.extent = (VkExtent2D){(uint32_t)ow, (uint32_t)oh}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + _vkCmdBeginRenderPass(cmdBuffers[frameNumber], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + _vkCmdBindPipeline(cmdBuffers[frameNumber], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + + _vkCmdDraw(cmdBuffers[frameNumber], vertexCount, instanceCount, 0, 0); + + _vkCmdEndRenderPass(cmdBuffers[frameNumber]); + + if((res = _vkEndCommandBuffer(cmdBuffers[frameNumber])) != VK_SUCCESS) { + printf("error recording command buffer: %s\n", string_VkResult(res)); + exit(0); + } + + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.pNext = NULL; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = &imageAvaliableSemaphores[frameNumber]; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &cmdBuffers[frameNumber]; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &renderFinishedSemaphores[frameNumber]; + + if((res = _vkQueueSubmit(graphicsQueue, 1, &submitInfo, fences[frameNumber])) != VK_SUCCESS) { + printf("error submitting command buffer: %s\n", string_VkResult(res)); + exit(0); + } + + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[frameNumber]; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapchain; + presentInfo.pImageIndices = ¤tImageIndex; + + presentInfo.pResults = NULL; // Optional + + _vkQueuePresentKHR(presentQueue, &presentInfo); + + // frameNumber = currentImageIndex; + frameNumber = (frameNumber + 1) % (MAX_FRAMES_IN_FLIGHT); +} + +void vulkan_setup(MwWidget handle) { + FILE* vertFile; + FILE* fragFile; + void* vertBuf; + void* fragBuf; + size_t vertFileSize; + size_t fragFileSize; + size_t amountRead; + + uint32_t i; + VkViewport viewport = {}; + VkRect2D scissor = {}; + VkCommandPool cmdPool = {}; + VkResult res = 0; + VkShaderModule fragShaderModule; + VkShaderModule vertShaderModule; + VkPipelineLayout pipelineLayout = {}; + VkAttachmentDescription colorAttachment = {}; + VkAttachmentReference colorAttachmentRef = {}; + VkSubpassDescription subpass = {}; + + VkRenderPassCreateInfo renderPassInfo = {}; + VkShaderModuleCreateInfo vertInfo = {}; + VkShaderModuleCreateInfo fragInfo = {}; + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; + VkPipelineShaderStageCreateInfo shaderStages[2] = {}; + VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + VkPipelineViewportStateCreateInfo viewportState = {}; + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + VkPipelineColorBlendStateCreateInfo colorBlending = {}; + VkGraphicsPipelineCreateInfo pipelineInfo = {}; + VkFramebufferCreateInfo framebufferInfo = {}; + VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandPoolCreateInfo poolInfo = {}; + VkImageViewCreateInfo imageViewCreateInfo = {}; + VkSemaphoreCreateInfo semaphoreInfo = {}; + VkFenceCreateInfo fenceInfo = {}; + + _vkGetInstanceProcAddr = MwVulkanGetInstanceProcAddr(handle); + instance = MwVulkanGetInstance(handle); + device = MwVulkanGetLogicalDevice(handle); + physicalDevice = MwVulkanGetPhysicalDevice(handle); + graphicsQueue = MwVulkanGetGraphicsQueue(handle); + presentQueue = MwVulkanGetPresentQueue(handle); + surface = MwVulkanGetSurface(handle); + + LOAD_VK_FUNCTION(vkCreateShaderModule); + LOAD_VK_FUNCTION(vkCreatePipelineLayout); + LOAD_VK_FUNCTION(vkCreateGraphicsPipelines); + LOAD_VK_FUNCTION(vkCreateCommandPool); + LOAD_VK_FUNCTION(vkCreateFramebuffer); + LOAD_VK_FUNCTION(vkAllocateCommandBuffers); + LOAD_VK_FUNCTION(vkCreateFence); + LOAD_VK_FUNCTION(vkCreateSemaphore); + LOAD_VK_FUNCTION(vkCreateSwapchainKHR); + LOAD_VK_FUNCTION(vkGetSwapchainImagesKHR); + LOAD_VK_FUNCTION(vkCreateImageView); + LOAD_VK_FUNCTION(vkCreateRenderPass); + + // create a swapchain + swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchainCreateInfo.surface = surface; + swapchainCreateInfo.minImageCount = 3; + swapchainCreateInfo.imageFormat = VK_FORMAT_B8G8R8A8_SRGB; + swapchainCreateInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + swapchainCreateInfo.imageExtent = (VkExtent2D){.width = ow, .height = oh}, + swapchainCreateInfo.imageArrayLayers = 1, + swapchainCreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT; + // th is how we specify no transformation. + swapchainCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapchainCreateInfo.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; + // we don't care about the color of pixels that are obscured. + swapchainCreateInfo.clipped = VK_TRUE; + swapchainCreateInfo.oldSwapchain = NULL; + + if(MwVulkanGetGraphicsQueueIndex(handle) != MwVulkanGetPresentQueueIndex(handle)) { + swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + swapchainCreateInfo.queueFamilyIndexCount = 2; + + uint32_t indices[] = { + MwVulkanGetGraphicsQueueIndex(handle), + MwVulkanGetPresentQueueIndex(handle), + }; + swapchainCreateInfo.pQueueFamilyIndices = indices; + } else { + swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapchainCreateInfo.queueFamilyIndexCount = 0; + swapchainCreateInfo.pQueueFamilyIndices = NULL; + } + + if(_vkCreateSwapchainKHR(device, &swapchainCreateInfo, NULL, &swapchain) != VK_SUCCESS) { + printf("failed to create swapchain: %s\n", string_VkResult(res)); + exit(0); + }; + + imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = swapchainCreateInfo.imageFormat; + imageViewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_R; + imageViewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_G; + imageViewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_B; + imageViewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_A; + imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCreateInfo.subresourceRange.baseMipLevel = 0; + imageViewCreateInfo.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; + imageViewCreateInfo.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + + if(_vkGetSwapchainImagesKHR(device, swapchain, &swapchainImageViewCount, NULL) != VK_SUCCESS) { + printf("failed to get swapchain images: %s\n", string_VkResult(res)); + exit(0); + } + swapchainImages = malloc(sizeof(VkImage) * MAX_FRAMES_IN_FLIGHT); + swapchainImageView = malloc(sizeof(VkImageView) * MAX_FRAMES_IN_FLIGHT); + _vkGetSwapchainImagesKHR(device, swapchain, &swapchainImageViewCount, swapchainImages); + for(i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + imageViewCreateInfo.image = swapchainImages[i]; + if(_vkCreateImageView(device, &imageViewCreateInfo, NULL, &swapchainImageView[i]) != VK_SUCCESS) { + printf("failed to get swapchain images: %s\n", string_VkResult(res)); + exit(0); + } + } + + // Create a Render Pass. + colorAttachment.flags = 0; + colorAttachment.format = swapchainCreateInfo.imageFormat; + 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; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + subpass.flags = 0; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.inputAttachmentCount = 0; + subpass.pInputAttachments = NULL; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pResolveAttachments = NULL; + subpass.pDepthStencilAttachment = NULL; + subpass.preserveAttachmentCount = 0; + subpass.pPreserveAttachments = NULL; + + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.pNext = NULL; + renderPassInfo.flags = 0; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 0; + renderPassInfo.pDependencies = NULL; + + if((res = _vkCreateRenderPass(device, &renderPassInfo, NULL, &renderPass)) != VK_SUCCESS) { + printf("error creating the render pass: %s\n", string_VkResult(res)); + exit(0); + } + + // Create the Vertex Shader Module. + vertFile = fopen("triangle.vert.spv", "rb"); + fragFile = fopen("triangle.frag.spv", "rb"); + + fseek(vertFile, 0L, SEEK_END); + vertFileSize = ftell(vertFile); + rewind(vertFile); + vertBuf = malloc(vertFileSize); + memset(vertBuf, 0, vertFileSize); + amountRead = fread(vertBuf, 1, vertFileSize, vertFile); + printf("triangle.vert.spv: read %ld bytes\n", amountRead); + + vertInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + vertInfo.pNext = NULL; + vertInfo.flags = 0; + vertInfo.codeSize = amountRead; + vertInfo.pCode = vertBuf; + + if(_vkCreateShaderModule(device, &vertInfo, NULL, &vertShaderModule) != VK_SUCCESS) { + printf("failed to create the shader module: %s\n", string_VkResult(res)); + exit(0); + } + + // Create the Fragment Shader Module. + fseek(fragFile, 0L, SEEK_END); + fragFileSize = ftell(fragFile); + rewind(fragFile); + fragBuf = malloc(fragFileSize); + memset(fragBuf, 0, fragFileSize); + amountRead = fread(fragBuf, 1, fragFileSize, fragFile); + printf("triangle.frag.spv: read %ld bytes\n", amountRead); + + fragInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + fragInfo.pNext = NULL; + fragInfo.flags = 0; + fragInfo.codeSize = amountRead; + fragInfo.pCode = fragBuf; + + if(_vkCreateShaderModule(device, &fragInfo, NULL, &fragShaderModule) != VK_SUCCESS) { + printf("error creating the shader module: %s\n", string_VkResult(res)); + exit(0); + } + + // Create Pipeline Layout. + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.pNext = NULL; + pipelineLayoutInfo.flags = 0; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pSetLayouts = NULL; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if((res = _vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout)) != VK_SUCCESS) { + printf("error creating the pipeline layout: %s\n", string_VkResult(res)); + exit(0); + } + + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.pNext = NULL; + vertShaderStageInfo.flags = 0; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + vertShaderStageInfo.pSpecializationInfo = NULL; + + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.pNext = NULL; + fragShaderStageInfo.flags = 0; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + fragShaderStageInfo.pSpecializationInfo = NULL; + + shaderStages[0] = vertShaderStageInfo; + shaderStages[1] = fragShaderStageInfo; + + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.pNext = NULL; + vertexInputInfo.flags = 0; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.pVertexBindingDescriptions = NULL; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + vertexInputInfo.pVertexAttributeDescriptions = NULL; + + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.pNext = NULL; + inputAssembly.flags = 0; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)ow; + viewport.height = (float)oh; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + scissor.offset = (VkOffset2D){0, 0}; + scissor.extent = (VkExtent2D){(uint32_t)viewport.width, (uint32_t)viewport.height}; + + viewportState = (VkPipelineViewportStateCreateInfo){}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.pNext = NULL; + rasterizer.flags = 0; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0; + rasterizer.depthBiasClamp = 0.0; + rasterizer.depthBiasSlopeFactor = 0.0; + rasterizer.lineWidth = 1.0f; + + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.pNext = NULL; + multisampling.flags = 0; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.minSampleShading = 0.0; + multisampling.pSampleMask = NULL; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + colorBlendAttachment.blendEnable = VK_FALSE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.pNext = NULL; + colorBlending.flags = 0; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.pNext = NULL; + pipelineInfo.flags = 0; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pTessellationState = NULL; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = NULL; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = NULL; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineInfo.basePipelineIndex = 0; + + if((res = _vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &pipeline)) != VK_SUCCESS) { + printf("failed to create graphics pipeline: %s\n", string_VkResult(res)); + exit(0); + } + + framebuffers = malloc(sizeof(VkFramebuffer) * MAX_FRAMES_IN_FLIGHT); + + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.pNext = NULL; + framebufferInfo.flags = 0; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.width = ow; + framebufferInfo.height = oh; + framebufferInfo.layers = 1; + for(i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + framebufferInfo.pAttachments = &swapchainImageView[i]; + if((res = _vkCreateFramebuffer(device, &framebufferInfo, NULL, &framebuffers[i])) != VK_SUCCESS) { + printf("error creating the frame buffer: %s\n", string_VkResult(res)); + exit(0); + } + } + + // Create Command Pool. + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.pNext = NULL; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = MwVulkanGetGraphicsQueueIndex(handle); + + if((res = _vkCreateCommandPool(device, &poolInfo, NULL, &cmdPool)) != VK_SUCCESS) { + printf("error creating the command pool: %s\n", string_VkResult(res)); + exit(0); + } + + // Create Command Buffer to record draw commands. + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.pNext = NULL; + allocInfo.commandPool = cmdPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + cmdBuffers = malloc(sizeof(VkCommandBuffer) * MAX_FRAMES_IN_FLIGHT); + + for(i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if((res = _vkAllocateCommandBuffers(device, &allocInfo, &cmdBuffers[i])) != VK_SUCCESS) { + printf("error allocating the command buffers: %s\n", string_VkResult(res)); + exit(0); + } + } + + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.pNext = 0; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphoreInfo.pNext = 0; + semaphoreInfo.flags = VK_SEMAPHORE_TYPE_BINARY; + + imageAvaliableSemaphores = malloc(sizeof(VkSemaphore) * MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores = malloc(sizeof(VkSemaphore) * MAX_FRAMES_IN_FLIGHT); + fences = malloc(sizeof(VkFence) * MAX_FRAMES_IN_FLIGHT); + + for(i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if(_vkCreateSemaphore(device, &semaphoreInfo, NULL, &imageAvaliableSemaphores[i]) != VK_SUCCESS) { + printf("error creating fence: %s\n", string_VkResult(res)); + exit(0); + } + if(_vkCreateSemaphore(device, &semaphoreInfo, NULL, &renderFinishedSemaphores[i]) != VK_SUCCESS) { + printf("error creating fence: %s\n", string_VkResult(res)); + exit(0); + } + if(_vkCreateFence(device, &fenceInfo, NULL, &fences[i]) != VK_SUCCESS) { + printf("error creating fence: %s\n", string_VkResult(res)); + exit(0); + } + } +} + +int main() { + window = MwVaCreateWidget(MwWindowClass, "main", NULL, 0, 0, 400, 450, + MwNtitle, "hello world", + NULL); + + MwVulkanEnableExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + vulkan = MwCreateWidget(MwVulkanClass, "vulkan", window, 50, 50, ow, oh); + + MwAddUserHandler(window, MwNtickHandler, tick, NULL); + + vulkan_setup(vulkan); + + MwLoop(window); +} diff --git a/include/Mw/Vulkan.h b/include/Mw/Vulkan.h new file mode 100644 index 0000000..3afc572 --- /dev/null +++ b/include/Mw/Vulkan.h @@ -0,0 +1,39 @@ +/* $Id$ */ +#ifndef __MW_VULKAN_H__ +#define __MW_VULKAN_H__ + +#if !defined(_WIN32) && !defined(__linux__) && !defined(__FreeBSD__) +#error Vulkan is unsupported on the requested platform. +#endif + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +MWDECL MwClass MwVulkanClass; + +// Add an extension to the list of extensions to enable prior to initialization. +// This must be called before MwCreateWidget. +MWDECL void MwVulkanEnableExtension(const char* ext_name); + +MWDECL PFN_vkGetInstanceProcAddr MwVulkanGetInstanceProcAddr(MwWidget handle); +MWDECL VkInstance MwVulkanGetInstance(MwWidget handle); +MWDECL VkSurfaceKHR MwVulkanGetSurface(MwWidget handle); +MWDECL VkPhysicalDevice MwVulkanGetPhysicalDevice(MwWidget handle); +MWDECL VkDevice MwVulkanGetLogicalDevice(MwWidget handle); +MWDECL int MwVulkanGetGraphicsQueueIndex(MwWidget handle); +MWDECL int MwVulkanGetPresentQueueIndex(MwWidget handle); +MWDECL VkQueue MwVulkanGetGraphicsQueue(MwWidget handle); +MWDECL VkQueue MwVulkanGetPresentQueue(MwWidget handle); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/vulkan.c b/src/vulkan.c new file mode 100644 index 0000000..650cb45 --- /dev/null +++ b/src/vulkan.c @@ -0,0 +1,385 @@ +/* $Id$ */ +#include "Mw/Vulkan.h" +#include "Mw/TypeDefs.h" +#include +#include + +#include +#include +#include + +#include + +#ifdef _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR 1 +#endif +#ifdef __linux__ +#define VK_USE_PLATFORM_XLIB_KHR 1 +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif + +#include +#include +#include + +#include "stb_ds.h" + +// convienence macro for handling vulkan errors +#define VK_CMD(func) \ + vk_res = func; \ + if(vk_res != VK_SUCCESS) { \ + printf("VULKAN ERROR AT %s:%d: %s\n", __FILE__, __LINE__, string_VkResult(vk_res)); \ + exit(0); \ + } + +// convienence macro for loading a vulkan function pointer into memory +#define LOAD_VK_FUNCTION(name) \ + PFN_##name _##name = (PFN_##name)o->vkGetInstanceProcAddr(o->vkInstance, #name); \ + assert(_##name); + +bool enableValidationLayers = true; + +const char** enabledExtensions; +unsigned int enabledExtensionCount = 0; + +typedef struct vulkan { + void* vulkanLibrary; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + VkInstance vkInstance; + VkSurfaceKHR vkSurface; + + VkPhysicalDevice vkPhysicalDevice; + VkDevice vkLogicalDevice; + VkQueue vkGraphicsQueue; + VkQueue vkPresentQueue; + uint32_t vkGraphicsFamilyIDX; + uint32_t vkPresentFamilyIDX; + + const char** vkInstanceExtensions; + const char** vkDeviceExtensions; + const char** vkLayers; + int vkInstanceExtensionCount; + int vkDeviceExtensionCount; + int vkLayerCount; +} vulkan_t; + +static void vulkan_instance_setup(MwWidget handle, vulkan_t* o); +static void vulkan_surface_setup(MwWidget handle, vulkan_t* o); +static void vulkan_devices_setup(MwWidget handle, vulkan_t* o); + +static void create(MwWidget handle) { + vulkan_t* o = malloc(sizeof(*o)); + + // !! important to call it in this order + vulkan_instance_setup(handle, o); + vulkan_surface_setup(handle, o); + vulkan_devices_setup(handle, o); + + handle->internal = o; + MwSetDefault(handle); +} + +static void destroy(MwWidget handle) { + vulkan_t* o = (vulkan_t*)handle->internal; + free(o); +} + +static void vulkan_instance_setup(MwWidget handle, vulkan_t* o) { + // todo: Some sort of function for being able to set the vulkan version? + uint32_t vulkan_version = VK_VERSION_1_0; + uint32_t api_version = VK_API_VERSION_1_0; + uint32_t extension_count = 0; + uint32_t layer_count = 0; + unsigned long i, n = 0; + + PFN_vkEnumerateInstanceExtensionProperties + _vkEnumerateInstanceExtensionProperties; + PFN_vkEnumerateInstanceLayerProperties _vkEnumerateInstanceLayerProperties; + PFN_vkCreateInstance _vkCreateInstance; + + VkApplicationInfo app_info; + VkInstanceCreateInfo instance_create_info; + + VkExtensionProperties* ext_props; + VkLayerProperties* layer_props; + + VkResult vk_res; + + // TODO: support for whatever win32's equivalants to dlopen/dlsym are + o->vulkanLibrary = dlopen("libvulkan.so", RTLD_LAZY | RTLD_GLOBAL); + o->vkGetInstanceProcAddr = dlsym(o->vulkanLibrary, "vkGetInstanceProcAddr"); + assert(o->vkGetInstanceProcAddr); + + // Load in any other function pointers we need. + _vkEnumerateInstanceExtensionProperties = dlsym(o->vulkanLibrary, "vkEnumerateInstanceExtensionProperties"); + assert(_vkEnumerateInstanceExtensionProperties); + _vkEnumerateInstanceLayerProperties = dlsym(o->vulkanLibrary, "vkEnumerateInstanceLayerProperties"); + assert(_vkEnumerateInstanceLayerProperties); + _vkCreateInstance = dlsym(o->vulkanLibrary, "vkCreateInstance"); + assert(_vkCreateInstance); + + // setup enabled extensions + arrput(enabledExtensions, VK_KHR_SURFACE_EXTENSION_NAME); +#if defined(_WIN32) + arrput(enabledExtensions, VK_KHR_WIN32_SURFACE_EXTENSION_NAME); +#elif defined(__linux__) || defined(__FreeBSD__) + arrput(enabledExtensions, VK_KHR_XLIB_SURFACE_EXTENSION_NAME); +#endif + + // passing null gives us all the extensions provided by the current vulkan implementation + VK_CMD(_vkEnumerateInstanceExtensionProperties(NULL, &extension_count, NULL)); + ext_props = malloc(sizeof(VkExtensionProperties) * extension_count); + VK_CMD(_vkEnumerateInstanceExtensionProperties(NULL, &extension_count, ext_props)); + o->vkInstanceExtensions = malloc(sizeof(const char*) * (arrlen(enabledExtensions) + 1)); + + for(i = 0; i < extension_count; i++) { + for(n = 0; n < arrlen(enabledExtensions); n++) { + if(strcmp(ext_props[i].extensionName, enabledExtensions[n]) == 0) { + o->vkInstanceExtensions[o->vkInstanceExtensionCount] = ext_props[i].extensionName; + o->vkInstanceExtensionCount++; + break; + } + } + } + printf("enabled %d instance extensions\n", o->vkInstanceExtensionCount); + + app_info = (VkApplicationInfo){ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pNext = NULL, + .pApplicationName = "", + .applicationVersion = vulkan_version, + .pEngineName = "", + .engineVersion = vulkan_version, + .apiVersion = api_version, + }; + + VK_CMD(_vkEnumerateInstanceLayerProperties(&layer_count, NULL)); + layer_props = malloc(sizeof(VkLayerProperties) * layer_count); + VK_CMD(_vkEnumerateInstanceLayerProperties(&layer_count, layer_props)); + o->vkLayers = malloc(256 * (layer_count + 2)); + for(i = 0; i < layer_count; i++) { + if(enableValidationLayers) { + if(strcmp(layer_props[i].layerName, "VK_LAYER_KHRONOS_validation") == 0) { + printf("layer: %s\n", layer_props[i].layerName); + memset(&o->vkLayers[i], 0, 255); + memcpy(&o->vkLayers[i], layer_props[i].layerName, 254); + o->vkLayerCount++; + break; + } else { + continue; + } + } + } + o->vkLayers[i++] = NULL; + + instance_create_info = (VkInstanceCreateInfo){ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .flags = 0, + .pApplicationInfo = &app_info, + .enabledExtensionCount = o->vkInstanceExtensionCount, + .enabledLayerCount = 0, + .ppEnabledExtensionNames = o->vkInstanceExtensions, + .ppEnabledLayerNames = o->vkLayers, + }; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + + VK_CMD(_vkCreateInstance(&instance_create_info, NULL, &o->vkInstance)); +} + +static void vulkan_surface_setup(MwWidget handle, vulkan_t* o) { + int vk_res; +#ifdef _WIN32 + LOAD_VK_FUNCTION(vkCreateWin32SurfaceKHR); + + VkWin32SurfaceCreateInfoKHR createInfo = { + .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, + .pNext = NULL, + .flags = 0, + .hinstance = handle->lowlevel->hInstance, + .hwnd = handle->lowlevel->hWnd, + }; + + VK_CMD(_vkCreateWin32SurfaceKHR(o->vkInstance, &createInfo, NULL, + &o->vkSurface)); +#endif +#ifdef __linux__ + LOAD_VK_FUNCTION(vkCreateXlibSurfaceKHR); + + VkXlibSurfaceCreateInfoKHR createInfo = { + .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, + .pNext = NULL, + .flags = 0, + .dpy = handle->lowlevel->display, + .window = handle->lowlevel->window, + }; + VK_CMD(_vkCreateXlibSurfaceKHR(o->vkInstance, &createInfo, NULL, &o->vkSurface)); +#endif +} + +static void vulkan_devices_setup(MwWidget handle, vulkan_t* o) { + int vk_res; + unsigned long i, n; + uint32_t deviceCount; + VkPhysicalDevice* devices; + uint32_t queueFamilyCount; + VkQueueFamilyProperties* family_props; + float queuePriority = 1.0f; + VkDeviceQueueCreateInfo queueCreateInfos[2]; + VkDeviceCreateInfo createInfo; + VkExtensionProperties* ext_props; + uint32_t extension_count; + VkBool32 has_graphics = false; + VkBool32 has_present = false; + int queueCreateCount = 0; + + LOAD_VK_FUNCTION(vkEnumeratePhysicalDevices); + LOAD_VK_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties); + LOAD_VK_FUNCTION(vkCreateDevice); + LOAD_VK_FUNCTION(vkGetDeviceQueue); + LOAD_VK_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR); + + PFN_vkEnumerateDeviceExtensionProperties _vkEnumerateDeviceExtensionProperties = (PFN_vkEnumerateDeviceExtensionProperties) + o->vkGetInstanceProcAddr(o->vkInstance, "vkEnumerateDeviceExtensionProperties"); + assert(_vkEnumerateDeviceExtensionProperties); + + // create the physical device + VK_CMD(_vkEnumeratePhysicalDevices(o->vkInstance, &deviceCount, NULL)); + devices = malloc(sizeof(VkPhysicalDevice) * deviceCount); + VK_CMD(_vkEnumeratePhysicalDevices(o->vkInstance, &deviceCount, devices)); + + for(i = 0; i < deviceCount; i++) { + _vkGetPhysicalDeviceQueueFamilyProperties(devices[i], &queueFamilyCount, NULL); + family_props = malloc(sizeof(VkQueueFamilyProperties) * queueFamilyCount); + _vkGetPhysicalDeviceQueueFamilyProperties(devices[i], &queueFamilyCount, family_props); + o->vkGraphicsFamilyIDX = 0; + o->vkPresentFamilyIDX = 0; + for(n = 0; n < queueFamilyCount; n++) { + if(family_props[n].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + has_graphics = true; + o->vkGraphicsFamilyIDX = n; + }; + + _vkGetPhysicalDeviceSurfaceSupportKHR(devices[i], n, o->vkSurface, + &has_present); + if(has_present) { + o->vkPresentFamilyIDX = n; + } + if(has_graphics && has_present) { + o->vkPhysicalDevice = devices[i]; + break; + } + } + free(family_props); + } + if(!has_graphics && !has_present) { + // rare, yes, but idk maybe some shitty drivers will present this dillema idk. + printf("There were no devices with either a graphics or presentation queue. Exiting!\n"); + exit(1); + } + + // create the logical device + queueCreateInfos[queueCreateCount] = (VkDeviceQueueCreateInfo){ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .queueFamilyIndex = o->vkGraphicsFamilyIDX, + .queueCount = 1, + .pQueuePriorities = &queuePriority, + }; + if(o->vkGraphicsFamilyIDX == o->vkPresentFamilyIDX) { + queueCreateInfos[queueCreateCount++] = (VkDeviceQueueCreateInfo){ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .queueFamilyIndex = o->vkPresentFamilyIDX, + .queueCount = 1, + .pQueuePriorities = &queuePriority, + }; + } + + VK_CMD(_vkEnumerateDeviceExtensionProperties(o->vkPhysicalDevice, NULL, &extension_count, NULL)); + ext_props = malloc(sizeof(VkExtensionProperties) * extension_count); + VK_CMD(_vkEnumerateDeviceExtensionProperties(o->vkPhysicalDevice, NULL, &extension_count, ext_props)); + o->vkDeviceExtensions = malloc(sizeof(const char*) * (arrlen(enabledExtensions) + 1)); + + for(i = 0; i < extension_count; i++) { + for(n = 0; n < arrlen(enabledExtensions); n++) { + if(strcmp(ext_props[i].extensionName, enabledExtensions[n]) == 0) { + o->vkDeviceExtensions[o->vkDeviceExtensionCount] = ext_props[i].extensionName; + o->vkDeviceExtensionCount++; + break; + } + } + } + printf("enabled %d device extensions\n", o->vkDeviceExtensionCount); + + createInfo = (VkDeviceCreateInfo){ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .queueCreateInfoCount = queueCreateCount, + .pQueueCreateInfos = queueCreateInfos, + .pEnabledFeatures = NULL, + .enabledExtensionCount = o->vkDeviceExtensionCount, + .ppEnabledExtensionNames = o->vkDeviceExtensions, + .enabledLayerCount = 0, + .ppEnabledLayerNames = NULL, + }; + + VK_CMD(_vkCreateDevice(o->vkPhysicalDevice, &createInfo, NULL, &o->vkLogicalDevice) != VK_SUCCESS); + + _vkGetDeviceQueue(o->vkLogicalDevice, o->vkGraphicsFamilyIDX, 0, &o->vkGraphicsQueue); + if(o->vkGraphicsFamilyIDX == o->vkPresentFamilyIDX) { + o->vkPresentQueue = o->vkGraphicsQueue; + } else { + _vkGetDeviceQueue(o->vkLogicalDevice, o->vkPresentFamilyIDX, 0, &o->vkPresentQueue); + } + // free(devices); +} + +void MwVulkanEnableExtension(const char* name) { + arrput(enabledExtensions, name); +} + +PFN_vkGetInstanceProcAddr MwVulkanGetInstanceProcAddr(MwWidget handle) { + return ((vulkan_t*)handle->internal)->vkGetInstanceProcAddr; +}; +VkInstance MwVulkanGetInstance(MwWidget handle) { + return ((vulkan_t*)handle->internal)->vkInstance; +}; +VkSurfaceKHR MwVulkanGetSurface(MwWidget handle) { + return ((vulkan_t*)handle->internal)->vkSurface; +}; +VkPhysicalDevice MwVulkanGetPhysicalDevice(MwWidget handle) { + return ((vulkan_t*)handle->internal)->vkPhysicalDevice; +}; +VkDevice MwVulkanGetLogicalDevice(MwWidget handle) { + return ((vulkan_t*)handle->internal)->vkLogicalDevice; +}; +VkQueue MwVulkanGetGraphicsQueue(MwWidget handle) { + return ((vulkan_t*)handle->internal)->vkGraphicsQueue; +}; +VkQueue MwVulkanGetPresentQueue(MwWidget handle) { + return ((vulkan_t*)handle->internal)->vkPresentQueue; +}; + +int MwVulkanGetGraphicsQueueIndex(MwWidget handle) { + return ((vulkan_t*)handle->internal)->vkGraphicsFamilyIDX; +} + +int MwVulkanGetPresentQueueIndex(MwWidget handle) { + return ((vulkan_t*)handle->internal)->vkPresentFamilyIDX; +} + +MwClassRec MwVulkanClassRec = { + create, /* create */ + destroy, /* destroy */ + NULL, /* draw */ + NULL /* click */ +}; +MwClass MwVulkanClass = &MwVulkanClassRec;