Mercurial > games > semicongine
diff src/zamikongine/engine.nim @ 19:b55d6ecde79d
did: introduce scene graph, meshs and generic vertex buffers
author | Sam <sam@basx.dev> |
---|---|
date | Mon, 09 Jan 2023 11:04:19 +0700 |
parents | src/engine.nim@90e117952f74 |
children | beb86492b178 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/zamikongine/engine.nim Mon Jan 09 11:04:19 2023 +0700 @@ -0,0 +1,676 @@ +import std/sequtils +import std/typetraits +import std/strformat +import std/enumerate +import std/logging + + +import ./vulkan +import ./vulkan_helpers +import ./window +import ./events +import ./shader +import ./vertex +import ./buffer +import ./thing +import ./mesh + +import ./glslang/glslang + +const MAX_FRAMES_IN_FLIGHT = 2 +const DEBUG_LOG = not defined(release) + +var logger = newConsoleLogger() +addHandler(logger) + + +const VULKAN_VERSION = VK_MAKE_API_VERSION(0'u32, 1'u32, 2'u32, 0'u32) + +type + Device = object + device: VkDevice + physicalDevice: PhysicalDevice + graphicsQueueFamily: uint32 + presentationQueueFamily: uint32 + graphicsQueue: VkQueue + presentationQueue: VkQueue + Swapchain = object + swapchain: VkSwapchainKHR + images: seq[VkImage] + imageviews: seq[VkImageView] + RenderPipeline = object + shaders*: seq[ShaderProgram] + layout*: VkPipelineLayout + pipeline*: VkPipeline + QueueFamily = object + properties*: VkQueueFamilyProperties + hasSurfaceSupport*: bool + PhysicalDevice = object + device*: VkPhysicalDevice + extensions*: seq[string] + properties*: VkPhysicalDeviceProperties + features*: VkPhysicalDeviceFeatures + queueFamilies*: seq[QueueFamily] + formats: seq[VkSurfaceFormatKHR] + presentModes: seq[VkPresentModeKHR] + Vulkan* = object + debugMessenger: VkDebugUtilsMessengerEXT + instance*: VkInstance + deviceList*: seq[PhysicalDevice] + device*: Device + surface*: VkSurfaceKHR + surfaceFormat: VkSurfaceFormatKHR + frameDimension: VkExtent2D + swapchain: Swapchain + framebuffers: seq[VkFramebuffer] + renderPass*: VkRenderPass + pipeline*: RenderPipeline + commandPool*: VkCommandPool + commandBuffers*: array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer] + imageAvailableSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore] + renderFinishedSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore] + inFlightFences*: array[MAX_FRAMES_IN_FLIGHT, VkFence] + vertexBuffers: seq[(seq[Buffer], uint32)] + indexedVertexBuffers: seq[(seq[Buffer], Buffer, uint32, VkIndexType)] + Engine* = object + vulkan: Vulkan + window: NativeWindow + currentscenedata: ref Thing + +proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[PhysicalDevice] = + for vulkanPhysicalDevice in getVulkanPhysicalDevices(instance): + var device = PhysicalDevice(device: vulkanPhysicalDevice, extensions: getDeviceExtensions(vulkanPhysicalDevice)) + vkGetPhysicalDeviceProperties(vulkanPhysicalDevice, addr(device.properties)) + vkGetPhysicalDeviceFeatures(vulkanPhysicalDevice, addr(device.features)) + device.formats = vulkanPhysicalDevice.getDeviceSurfaceFormats(surface) + device.presentModes = vulkanPhysicalDevice.getDeviceSurfacePresentModes(surface) + + debug(&"Physical device nr {int(vulkanPhysicalDevice)} {cleanString(device.properties.deviceName)}") + for i, queueFamilyProperty in enumerate(getQueueFamilies(vulkanPhysicalDevice)): + var hasSurfaceSupport: VkBool32 = VK_FALSE + checkVkResult vkGetPhysicalDeviceSurfaceSupportKHR(vulkanPhysicalDevice, uint32(i), surface, addr(hasSurfaceSupport)) + device.queueFamilies.add(QueueFamily(properties: queueFamilyProperty, hasSurfaceSupport: bool(hasSurfaceSupport))) + debug(&" Queue family {i} {queueFamilyProperty}") + + result.add(device) + +proc filterForDevice(devices: seq[PhysicalDevice]): seq[(PhysicalDevice, uint32, uint32)] = + for device in devices: + if not (device.formats.len > 0 and device.presentModes.len > 0 and "VK_KHR_swapchain" in device.extensions): + continue + var graphicsQueueFamily = high(uint32) + var presentationQueueFamily = high(uint32) + for i, queueFamily in enumerate(device.queueFamilies): + if queueFamily.hasSurfaceSupport: + presentationQueueFamily = uint32(i) + if bool(uint32(queueFamily.properties.queueFlags) and ord(VK_QUEUE_GRAPHICS_BIT)): + graphicsQueueFamily = uint32(i) + if graphicsQueueFamily != high(uint32) and presentationQueueFamily != high(uint32): + result.add((device, graphicsQueueFamily, presentationQueueFamily)) + + for (device, graphicsQueueFamily, presentationQueueFamily) in result: + debug(&"Viable device: {cleanString(device.properties.deviceName)} (graphics queue family {graphicsQueueFamily}, presentation queue family {presentationQueueFamily})") + + +proc getFrameDimension(window: NativeWindow, device: VkPhysicalDevice, surface: VkSurfaceKHR): VkExtent2D = + let capabilities = device.getSurfaceCapabilities(surface) + if capabilities.currentExtent.width != high(uint32): + return capabilities.currentExtent + else: + let (width, height) = window.size() + return VkExtent2D( + width: min(max(uint32(width), capabilities.minImageExtent.width), capabilities.maxImageExtent.width), + height: min(max(uint32(height), capabilities.minImageExtent.height), capabilities.maxImageExtent.height), + ) + +when DEBUG_LOG: + proc setupDebugLog(instance: VkInstance): VkDebugUtilsMessengerEXT = + var createInfo = VkDebugUtilsMessengerCreateInfoEXT( + sType: VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + messageSeverity: VkDebugUtilsMessageSeverityFlagsEXT( + ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) or + ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) or + ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + ), + messageType: VkDebugUtilsMessageTypeFlagsEXT( + ord(VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) or + ord(VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) or + ord(VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) + ), + pfnUserCallback: debugCallback, + pUserData: nil, + ) + checkVkResult instance.vkCreateDebugUtilsMessengerEXT(addr(createInfo), nil, addr(result)) + +proc setupVulkanDeviceAndQueues(instance: VkInstance, surface: VkSurfaceKHR): Device = + let usableDevices = instance.getAllPhysicalDevices(surface).filterForDevice() + if len(usableDevices) == 0: + raise newException(Exception, "No suitable graphics device found") + result.physicalDevice = usableDevices[0][0] + result.graphicsQueueFamily = usableDevices[0][1] + result.presentationQueueFamily = usableDevices[0][2] + + debug(&"Chose device {cleanString(result.physicalDevice.properties.deviceName)}") + + (result.device, result.graphicsQueue, result.presentationQueue) = getVulcanDevice( + result.physicalDevice.device, + result.physicalDevice.features, + result.graphicsQueueFamily, + result.presentationQueueFamily, + ) + +proc setupSwapChain(device: VkDevice, physicalDevice: PhysicalDevice, surface: VkSurfaceKHR, dimension: VkExtent2D, surfaceFormat: VkSurfaceFormatKHR): Swapchain = + + let capabilities = physicalDevice.device.getSurfaceCapabilities(surface) + var selectedPresentationMode = getPresentMode(physicalDevice.presentModes) + # setup swapchain + var swapchainCreateInfo = VkSwapchainCreateInfoKHR( + sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + surface: surface, + minImageCount: max(capabilities.minImageCount + 1, capabilities.maxImageCount), + imageFormat: surfaceFormat.format, + imageColorSpace: surfaceFormat.colorSpace, + imageExtent: dimension, + imageArrayLayers: 1, + imageUsage: VkImageUsageFlags(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT), + # VK_SHARING_MODE_CONCURRENT no supported (i.e cannot use different queue families for drawing to swap surface?) + imageSharingMode: VK_SHARING_MODE_EXCLUSIVE, + preTransform: capabilities.currentTransform, + compositeAlpha: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + presentMode: selectedPresentationMode, + clipped: VK_TRUE, + oldSwapchain: VkSwapchainKHR(0), + ) + checkVkResult device.vkCreateSwapchainKHR(addr(swapchainCreateInfo), nil, addr(result.swapchain)) + result.images = device.getSwapChainImages(result.swapchain) + + # setup swapchian image views + + result.imageviews = newSeq[VkImageView](result.images.len) + for i, image in enumerate(result.images): + var imageViewCreateInfo = VkImageViewCreateInfo( + sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + image: image, + viewType: VK_IMAGE_VIEW_TYPE_2D, + format: surfaceFormat.format, + components: VkComponentMapping( + r: VK_COMPONENT_SWIZZLE_IDENTITY, + g: VK_COMPONENT_SWIZZLE_IDENTITY, + b: VK_COMPONENT_SWIZZLE_IDENTITY, + a: VK_COMPONENT_SWIZZLE_IDENTITY, + ), + subresourceRange: VkImageSubresourceRange( + aspectMask: VkImageAspectFlags(VK_IMAGE_ASPECT_COLOR_BIT), + baseMipLevel: 0, + levelCount: 1, + baseArrayLayer: 0, + layerCount: 1, + ), + ) + checkVkResult device.vkCreateImageView(addr(imageViewCreateInfo), nil, addr(result.imageviews[i])) + +proc setupRenderPass(device: VkDevice, format: VkFormat): VkRenderPass = + var + colorAttachment = VkAttachmentDescription( + format: format, + samples: VK_SAMPLE_COUNT_1_BIT, + loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR, + storeOp: VK_ATTACHMENT_STORE_OP_STORE, + stencilLoadOp: VK_ATTACHMENT_LOAD_OP_DONT_CARE, + stencilStoreOp: VK_ATTACHMENT_STORE_OP_DONT_CARE, + initialLayout: VK_IMAGE_LAYOUT_UNDEFINED, + finalLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + ) + colorAttachmentRef = VkAttachmentReference( + attachment: 0, + layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + ) + subpass = VkSubpassDescription( + pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, + colorAttachmentCount: 1, + pColorAttachments: addr(colorAttachmentRef) + ) + dependency = VkSubpassDependency( + srcSubpass: VK_SUBPASS_EXTERNAL, + dstSubpass: 0, + srcStageMask: VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT), + srcAccessMask: VkAccessFlags(0), + dstStageMask: VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT), + dstAccessMask: VkAccessFlags(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT), + ) + renderPassCreateInfo = VkRenderPassCreateInfo( + sType: VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + attachmentCount: 1, + pAttachments: addr(colorAttachment), + subpassCount: 1, + pSubpasses: addr(subpass), + dependencyCount: 1, + pDependencies: addr(dependency), + ) + checkVkResult device.vkCreateRenderPass(addr(renderPassCreateInfo), nil, addr(result)) + +proc setupRenderPipeline[T](device: VkDevice, frameDimension: VkExtent2D, renderPass: VkRenderPass, vertexShader, fragmentShader: string): RenderPipeline = + # load shaders + result.shaders.add(device.initShaderProgram(VK_SHADER_STAGE_VERTEX_BIT, vertexShader)) + result.shaders.add(device.initShaderProgram(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader)) + + var + # define which parts can be dynamic (pipeline is fixed after setup) + dynamicStates = [VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR] + dynamicState = VkPipelineDynamicStateCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + dynamicStateCount: uint32(dynamicStates.len), + pDynamicStates: addr(dynamicStates[0]), + ) + vertexbindings = generateInputVertexBinding[T]() + attributebindings = generateInputAttributeBinding[T]() + + # define input data format + vertexInputInfo = VkPipelineVertexInputStateCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + vertexBindingDescriptionCount: uint32(vertexbindings.len), + pVertexBindingDescriptions: addr(vertexbindings[0]), + vertexAttributeDescriptionCount: uint32(attributebindings.len), + pVertexAttributeDescriptions: addr(attributebindings[0]), + ) + inputAssembly = VkPipelineInputAssemblyStateCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + primitiveRestartEnable: VK_FALSE, + ) + + # setup viewport + var viewportState = VkPipelineViewportStateCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + viewportCount: 1, + scissorCount: 1, + ) + + # rasterizerization config + var + rasterizer = VkPipelineRasterizationStateCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + depthClampEnable: VK_FALSE, + rasterizerDiscardEnable: VK_FALSE, + polygonMode: VK_POLYGON_MODE_FILL, + lineWidth: 1.0, + cullMode: VkCullModeFlags(VK_CULL_MODE_BACK_BIT), + frontFace: VK_FRONT_FACE_CLOCKWISE, + depthBiasEnable: VK_FALSE, + depthBiasConstantFactor: 0.0, + depthBiasClamp: 0.0, + depthBiasSlopeFactor: 0.0, + ) + multisampling = VkPipelineMultisampleStateCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + sampleShadingEnable: VK_FALSE, + rasterizationSamples: VK_SAMPLE_COUNT_1_BIT, + minSampleShading: 1.0, + pSampleMask: nil, + alphaToCoverageEnable: VK_FALSE, + alphaToOneEnable: VK_FALSE, + ) + colorBlendAttachment = VkPipelineColorBlendAttachmentState( + colorWriteMask: VkColorComponentFlags( + ord(VK_COLOR_COMPONENT_R_BIT) or + ord(VK_COLOR_COMPONENT_G_BIT) or + ord(VK_COLOR_COMPONENT_B_BIT) or + ord(VK_COLOR_COMPONENT_A_BIT) + ), + blendEnable: VK_TRUE, + srcColorBlendFactor: VK_BLEND_FACTOR_SRC_ALPHA, + dstColorBlendFactor: VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + colorBlendOp: VK_BLEND_OP_ADD, + srcAlphaBlendFactor: VK_BLEND_FACTOR_ONE, + dstAlphaBlendFactor: VK_BLEND_FACTOR_ZERO, + alphaBlendOp: VK_BLEND_OP_ADD, + ) + colorBlending = VkPipelineColorBlendStateCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + logicOpEnable: VK_TRUE, + logicOp: VK_LOGIC_OP_COPY, + attachmentCount: 1, + pAttachments: addr(colorBlendAttachment), + blendConstants: [0.0'f, 0.0'f, 0.0'f, 0.0'f], + ) + + # create pipeline + pipelineLayoutInfo = VkPipelineLayoutCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + setLayoutCount: 0, + pSetLayouts: nil, + pushConstantRangeCount: 0, + pPushConstantRanges: nil, + ) + checkVkResult vkCreatePipelineLayout(device, addr(pipelineLayoutInfo), nil, addr(result.layout)) + + var stages: seq[VkPipelineShaderStageCreateInfo] + for shader in result.shaders: + stages.add(shader.shader) + var pipelineInfo = VkGraphicsPipelineCreateInfo( + sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + stageCount: uint32(stages.len), + pStages: addr(stages[0]), + pVertexInputState: addr(vertexInputInfo), + pInputAssemblyState: addr(inputAssembly), + pViewportState: addr(viewportState), + pRasterizationState: addr(rasterizer), + pMultisampleState: addr(multisampling), + pDepthStencilState: nil, + pColorBlendState: addr(colorBlending), + pDynamicState: addr(dynamicState), + layout: result.layout, + renderPass: renderPass, + subpass: 0, + basePipelineHandle: VkPipeline(0), + basePipelineIndex: -1, + ) + checkVkResult vkCreateGraphicsPipelines( + device, + VkPipelineCache(0), + 1, + addr(pipelineInfo), + nil, + addr(result.pipeline) + ) + +proc setupFramebuffers(device: VkDevice, swapchain: var Swapchain, renderPass: VkRenderPass, dimension: VkExtent2D): seq[VkFramebuffer] = + result = newSeq[VkFramebuffer](swapchain.images.len) + for i, imageview in enumerate(swapchain.imageviews): + var framebufferInfo = VkFramebufferCreateInfo( + sType: VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + renderPass: renderPass, + attachmentCount: 1, + pAttachments: addr(swapchain.imageviews[i]), + width: dimension.width, + height: dimension.height, + layers: 1, + ) + checkVkResult device.vkCreateFramebuffer(addr(framebufferInfo), nil, addr(result[i])) + +proc trash(device: VkDevice, swapchain: Swapchain, framebuffers: seq[VkFramebuffer]) = + for framebuffer in framebuffers: + device.vkDestroyFramebuffer(framebuffer, nil) + for imageview in swapchain.imageviews: + device.vkDestroyImageView(imageview, nil) + device.vkDestroySwapchainKHR(swapchain.swapchain, nil) + +proc recreateSwapchain(vulkan: Vulkan): (Swapchain, seq[VkFramebuffer]) = + debug(&"Recreate swapchain with dimension {vulkan.frameDimension}") + checkVkResult vulkan.device.device.vkDeviceWaitIdle() + + vulkan.device.device.trash(vulkan.swapchain, vulkan.framebuffers) + + result[0] = vulkan.device.device.setupSwapChain( + vulkan.device.physicalDevice, + vulkan.surface, + vulkan.frameDimension, + vulkan.surfaceFormat + ) + result[1] = vulkan.device.device.setupFramebuffers( + result[0], + vulkan.renderPass, + vulkan.frameDimension + ) + + +proc setupCommandBuffers(device: VkDevice, graphicsQueueFamily: uint32): (VkCommandPool, array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer]) = + # set up command buffer + var poolInfo = VkCommandPoolCreateInfo( + sType: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + flags: VkCommandPoolCreateFlags(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT), + queueFamilyIndex: graphicsQueueFamily, + ) + checkVkResult device.vkCreateCommandPool(addr(poolInfo), nil, addr(result[0])) + + var allocInfo = VkCommandBufferAllocateInfo( + sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + commandPool: result[0], + level: VK_COMMAND_BUFFER_LEVEL_PRIMARY, + commandBufferCount: result[1].len.uint32, + ) + checkVkResult device.vkAllocateCommandBuffers(addr(allocInfo), addr(result[1][0])) + +proc setupSyncPrimitives(device: VkDevice): ( + array[MAX_FRAMES_IN_FLIGHT, VkSemaphore], + array[MAX_FRAMES_IN_FLIGHT, VkSemaphore], + array[MAX_FRAMES_IN_FLIGHT, VkFence], +) = + var semaphoreInfo = VkSemaphoreCreateInfo(sType: VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO) + var fenceInfo = VkFenceCreateInfo( + sType: VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + flags: VkFenceCreateFlags(VK_FENCE_CREATE_SIGNALED_BIT) + ) + for i in 0 ..< MAX_FRAMES_IN_FLIGHT: + checkVkResult device.vkCreateSemaphore(addr(semaphoreInfo), nil, addr(result[0][i])) + checkVkResult device.vkCreateSemaphore(addr(semaphoreInfo), nil, addr(result[1][i])) + checkVkResult device.vkCreateFence(addr(fenceInfo), nil, addr(result[2][i])) + +proc igniteEngine*(): Engine = + + result.window = createWindow("Hello triangle") + + # setup vulkan functions + vkLoad1_0() + vkLoad1_1() + vkLoad1_2() + + checkGlslangResult glslang_initialize_process() + + # create vulkan instance + result.vulkan.instance = createVulkanInstance(VULKAN_VERSION) + when DEBUG_LOG: + result.vulkan.debugMessenger = result.vulkan.instance.setupDebugLog() + result.vulkan.surface = result.vulkan.instance.createVulkanSurface(result.window) + result.vulkan.device = result.vulkan.instance.setupVulkanDeviceAndQueues(result.vulkan.surface) + + # get basic frame information + result.vulkan.surfaceFormat = result.vulkan.device.physicalDevice.formats.getSuitableSurfaceFormat() + result.vulkan.frameDimension = result.window.getFrameDimension(result.vulkan.device.physicalDevice.device, result.vulkan.surface) + + # setup swapchain and render pipeline + result.vulkan.swapchain = result.vulkan.device.device.setupSwapChain( + result.vulkan.device.physicalDevice, + result.vulkan.surface, + result.vulkan.frameDimension, + result.vulkan.surfaceFormat + ) + result.vulkan.renderPass = result.vulkan.device.device.setupRenderPass(result.vulkan.surfaceFormat.format) + result.vulkan.framebuffers = result.vulkan.device.device.setupFramebuffers( + result.vulkan.swapchain, + result.vulkan.renderPass, + result.vulkan.frameDimension + ) + ( + result.vulkan.commandPool, + result.vulkan.commandBuffers, + ) = result.vulkan.device.device.setupCommandBuffers(result.vulkan.device.graphicsQueueFamily) + + ( + result.vulkan.imageAvailableSemaphores, + result.vulkan.renderFinishedSemaphores, + result.vulkan.inFlightFences, + ) = result.vulkan.device.device.setupSyncPrimitives() + + +proc setupPipeline*[T: object, U: uint16|uint32](engine: var Engine, scenedata: ref Thing, vertexShader, fragmentShader: string) = + engine.currentscenedata = scenedata + engine.vulkan.pipeline = setupRenderPipeline[T]( + engine.vulkan.device.device, + engine.vulkan.frameDimension, + engine.vulkan.renderPass, + vertexShader, + fragmentShader, + ) + for mesh in partsOfType[ref Mesh[T]](engine.currentscenedata): + engine.vulkan.vertexBuffers.add createVertexBuffers(mesh[], engine.vulkan.device.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue) + for mesh in partsOfType[ref IndexedMesh[T, U]](engine.currentscenedata): + engine.vulkan.indexedVertexBuffers.add createIndexedVertexBuffers(mesh[], engine.vulkan.device.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue) + +proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: VkPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, frameDimension: VkExtent2D, engine: var Engine) = + var + beginInfo = VkCommandBufferBeginInfo( + sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + pInheritanceInfo: nil, + ) + clearColor = VkClearValue(color: VkClearColorValue(float32: [0.2'f, 0.2'f, 0.2'f, 1.0'f])) + renderPassInfo = VkRenderPassBeginInfo( + sType: VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + renderPass: renderPass, + framebuffer: framebuffer, + renderArea: VkRect2D( + offset: VkOffset2D(x: 0, y: 0), + extent: frameDimension, + ), + clearValueCount: 1, + pClearValues: addr(clearColor), + ) + viewport = VkViewport( + x: 0.0, + y: 0.0, + width: (float) frameDimension.width, + height: (float) frameDimension.height, + minDepth: 0.0, + maxDepth: 1.0, + ) + scissor = VkRect2D( + offset: VkOffset2D(x: 0, y: 0), + extent: frameDimension + ) + checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo)) + commandBuffer.vkCmdBeginRenderPass(addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE) + commandBuffer.vkCmdSetViewport(firstViewport=0, viewportCount=1, addr(viewport)) + commandBuffer.vkCmdSetScissor(firstScissor=0, scissorCount=1, addr(scissor)) + commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline) + + for (vertexBufferSet, vertexCount) in engine.vulkan.vertexBuffers: + var + vertexBuffers: seq[VkBuffer] + offsets: seq[VkDeviceSize] + for buffer in vertexBufferSet: + vertexBuffers.add buffer.vkBuffer + offsets.add VkDeviceSize(0) + + commandBuffer.vkCmdBindVertexBuffers(firstBinding=0'u32, bindingCount=2'u32, pBuffers=addr(vertexBuffers[0]), pOffsets=addr(offsets[0])) + commandBuffer.vkCmdDraw(vertexCount=vertexCount, instanceCount=1'u32, firstVertex=0'u32, firstInstance=0'u32) + + for (vertexBufferSet, indexBuffer, indicesCount, indexType) in engine.vulkan.indexedVertexBuffers: + var + vertexBuffers: seq[VkBuffer] + offsets: seq[VkDeviceSize] + for buffer in vertexBufferSet: + vertexBuffers.add buffer.vkBuffer + offsets.add VkDeviceSize(0) + + commandBuffer.vkCmdBindVertexBuffers(firstBinding=0'u32, bindingCount=2'u32, pBuffers=addr(vertexBuffers[0]), pOffsets=addr(offsets[0])) + commandBuffer.vkCmdBindIndexBuffer(indexBuffer.vkBuffer, VkDeviceSize(0), indexType); + commandBuffer.vkCmdDrawIndexed(indicesCount, 1, 0, 0, 0); + commandBuffer.vkCmdEndRenderPass() + checkVkResult commandBuffer.vkEndCommandBuffer() + +proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, engine: var Engine) = + checkVkResult vulkan.device.device.vkWaitForFences(1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64)) + var bufferImageIndex: uint32 + let nextImageResult = vulkan.device.device.vkAcquireNextImageKHR( + vulkan.swapchain.swapchain, + high(uint64), + vulkan.imageAvailableSemaphores[currentFrame], + VkFence(0), + addr(bufferImageIndex) + ) + if nextImageResult == VK_ERROR_OUT_OF_DATE_KHR: + vulkan.frameDimension = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface) + (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain() + elif not (nextImageResult in [VK_SUCCESS, VK_SUBOPTIMAL_KHR]): + raise newException(Exception, "Vulkan error: vkAcquireNextImageKHR returned " & $nextImageResult) + checkVkResult vulkan.device.device.vkResetFences(1, addr(vulkan.inFlightFences[currentFrame])) + + checkVkResult vulkan.commandBuffers[currentFrame].vkResetCommandBuffer(VkCommandBufferResetFlags(0)) + vulkan.renderPass.recordCommandBuffer(vulkan.pipeline.pipeline, vulkan.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex], vulkan.frameDimension, engine) + var + waitSemaphores = [vulkan.imageAvailableSemaphores[currentFrame]] + waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)] + signalSemaphores = [vulkan.renderFinishedSemaphores[currentFrame]] + submitInfo = VkSubmitInfo( + sType: VK_STRUCTURE_TYPE_SUBMIT_INFO, + waitSemaphoreCount: 1, + pWaitSemaphores: addr(waitSemaphores[0]), + pWaitDstStageMask: addr(waitStages[0]), + commandBufferCount: 1, + pCommandBuffers: addr(vulkan.commandBuffers[currentFrame]), + signalSemaphoreCount: 1, + pSignalSemaphores: addr(signalSemaphores[0]), + ) + checkVkResult vkQueueSubmit(vulkan.device.graphicsQueue, 1, addr(submitInfo), vulkan.inFlightFences[currentFrame]) + + var presentInfo = VkPresentInfoKHR( + sType: VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + waitSemaphoreCount: 1, + pWaitSemaphores: addr(signalSemaphores[0]), + swapchainCount: 1, + pSwapchains: addr(vulkan.swapchain.swapchain), + pImageIndices: addr(bufferImageIndex), + pResults: nil, + ) + let presentResult = vkQueuePresentKHR(vulkan.device.presentationQueue, addr(presentInfo)) + + if presentResult == VK_ERROR_OUT_OF_DATE_KHR or presentResult == VK_SUBOPTIMAL_KHR or resized: + vulkan.frameDimension = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface) + (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain() + + +proc fullThrottle*(engine: var Engine) = + var + killed = false + currentFrame = 0 + resized = false + + while not killed: + for event in engine.window.pendingEvents(): + case event.eventType: + of Quit: + killed = true + of ResizedWindow: + resized = true + of KeyDown: + echo event + if event.key == Escape: + killed = true + else: + discard + engine.window.drawFrame(engine.vulkan, currentFrame, resized, engine) + resized = false + currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT; + checkVkResult engine.vulkan.device.device.vkDeviceWaitIdle() + +proc trash*(engine: var Engine) = + for (bufferset, cnt) in engine.vulkan.vertexBuffers.mitems: + for buffer in bufferset.mitems: + buffer.trash() + for (bufferset, indexbuffer, cnt, t) in engine.vulkan.indexedVertexBuffers.mitems: + indexbuffer.trash() + for buffer in bufferset.mitems: + buffer.trash() + engine.vulkan.device.device.trash(engine.vulkan.swapchain, engine.vulkan.framebuffers) + checkVkResult engine.vulkan.device.device.vkDeviceWaitIdle() + + for i in 0 ..< MAX_FRAMES_IN_FLIGHT: + engine.vulkan.device.device.vkDestroySemaphore(engine.vulkan.imageAvailableSemaphores[i], nil) + engine.vulkan.device.device.vkDestroySemaphore(engine.vulkan.renderFinishedSemaphores[i], nil) + engine.vulkan.device.device.vkDestroyFence(engine.vulkan.inFlightFences[i], nil) + + engine.vulkan.device.device.vkDestroyCommandPool(engine.vulkan.commandPool, nil) + engine.vulkan.device.device.vkDestroyPipeline(engine.vulkan.pipeline.pipeline, nil) + engine.vulkan.device.device.vkDestroyPipelineLayout(engine.vulkan.pipeline.layout, nil) + engine.vulkan.device.device.vkDestroyRenderPass(engine.vulkan.renderPass, nil) + + for shader in engine.vulkan.pipeline.shaders: + engine.vulkan.device.device.vkDestroyShaderModule(shader.shader.module, nil) + + engine.vulkan.instance.vkDestroySurfaceKHR(engine.vulkan.surface, nil) + engine.vulkan.device.device.vkDestroyDevice(nil) + when DEBUG_LOG: + engine.vulkan.instance.vkDestroyDebugUtilsMessengerEXT(engine.vulkan.debugMessenger, nil) + glslang_finalize_process() + engine.window.trash() + engine.vulkan.instance.vkDestroyInstance(nil)