Mercurial > games > semicongine
changeset 108:70f92081d2c1
did: reorganize code
line wrap: on
line diff
--- a/src/semicongine/buffer.nim Mon Mar 27 21:01:32 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -import std/typetraits - -import ./vulkan -import ./vulkan_helpers - -type - BufferType* = enum - None = 0 - TransferSrc = VK_BUFFER_USAGE_TRANSFER_SRC_BIT - TransferDst = VK_BUFFER_USAGE_TRANSFER_DST_BIT - UniformBuffer = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT - IndexBuffer = VK_BUFFER_USAGE_INDEX_BUFFER_BIT - VertexBuffer = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT - MemoryProperty* = enum - DeviceLocal = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT - HostVisible = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT - HostCoherent = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT - MemoryProperties* = set[MemoryProperty] - Buffer* = object - device*: VkDevice - vkBuffer*: VkBuffer - size*: uint64 - memoryRequirements*: VkMemoryRequirements - memoryProperties*: MemoryProperties - memory*: VkDeviceMemory - bufferTypes*: set[BufferType] - data*: pointer - -proc trash*(buffer: var Buffer) = - if int64(buffer.vkBuffer) != 0 and int64(buffer.memory) != 0: - vkUnmapMemory(buffer.device, buffer.memory) - if int64(buffer.vkBuffer) != 0: - vkDestroyBuffer(buffer.device, buffer.vkBuffer, nil) - buffer.vkBuffer = VkBuffer(0) - if int64(buffer.memory) != 0: - vkFreeMemory(buffer.device, buffer.memory, nil) - buffer.memory = VkDeviceMemory(0) - -proc findMemoryType*(memoryRequirements: VkMemoryRequirements, - physicalDevice: VkPhysicalDevice, properties: MemoryProperties): uint32 = - var physicalProperties: VkPhysicalDeviceMemoryProperties - vkGetPhysicalDeviceMemoryProperties(physicalDevice, addr(physicalProperties)) - - for i in 0'u32 ..< physicalProperties.memoryTypeCount: - if bool(memoryRequirements.memoryTypeBits and (1'u32 shl i)): - if (uint32(physicalProperties.memoryTypes[i].propertyFlags) and cast[ - uint32](properties)) == cast[uint32](properties): - return i - -proc InitBuffer*( - device: VkDevice, - physicalDevice: VkPhysicalDevice, - size: uint64, - bufferTypes: set[BufferType], - properties: MemoryProperties, -): Buffer = - result = Buffer(device: device, size: size, bufferTypes: bufferTypes, - memoryProperties: properties) - var usageFlags = 0 - for usage in bufferTypes: - usageFlags = ord(usageFlags) or ord(usage) - var bufferInfo = VkBufferCreateInfo( - sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - size: VkDeviceSize(result.size), - usage: VkBufferUsageFlags(usageFlags), - sharingMode: VK_SHARING_MODE_EXCLUSIVE, - ) - checkVkResult vkCreateBuffer(result.device, addr(bufferInfo), nil, addr( - result.vkBuffer)) - vkGetBufferMemoryRequirements(result.device, result.vkBuffer, addr( - result.memoryRequirements)) - - var allocInfo = VkMemoryAllocateInfo( - sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - allocationSize: result.memoryRequirements.size, - memoryTypeIndex: result.memoryRequirements.findMemoryType(physicalDevice, properties) - ) - if result.size > 0: - checkVkResult result.device.vkAllocateMemory(addr(allocInfo), nil, addr(result.memory)) - checkVkResult result.device.vkBindBufferMemory(result.vkBuffer, result.memory, - VkDeviceSize(0)) - checkVkResult vkMapMemory( - result.device, - result.memory, - offset = VkDeviceSize(0), - VkDeviceSize(result.size), - VkMemoryMapFlags(0), - addr(result.data) - ) - - -proc transferBuffer*(commandPool: VkCommandPool, queue: VkQueue, src, - dst: Buffer, size: uint64) = - assert uint64(src.device) == uint64(dst.device) - assert TransferSrc in src.bufferTypes - assert TransferDst in dst.bufferTypes - var - allocInfo = VkCommandBufferAllocateInfo( - sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - level: VK_COMMAND_BUFFER_LEVEL_PRIMARY, - commandPool: commandPool, - commandBufferCount: 1, - ) - commandBuffer: VkCommandBuffer - checkVkResult vkAllocateCommandBuffers(src.device, addr(allocInfo), addr(commandBuffer)) - - var beginInfo = VkCommandBufferBeginInfo( - sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT), - ) - checkVkResult vkBeginCommandBuffer(commandBuffer, addr(beginInfo)) - var copyRegion = VkBufferCopy(size: VkDeviceSize(size)) - vkCmdCopyBuffer(commandBuffer, src.vkBuffer, dst.vkBuffer, 1, addr(copyRegion)) - checkVkResult vkEndCommandBuffer(commandBuffer) - - var submitInfo = VkSubmitInfo( - sType: VK_STRUCTURE_TYPE_SUBMIT_INFO, - commandBufferCount: 1, - pCommandBuffers: addr(commandBuffer), - ) - - checkVkResult vkQueueSubmit(queue, 1, addr(submitInfo), VkFence(0)) - checkVkResult vkQueueWaitIdle(queue) - vkFreeCommandBuffers(src.device, commandPool, 1, addr(commandBuffer)) -
--- a/src/semicongine/descriptor.nim Mon Mar 27 21:01:32 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -import std/strutils -import std/unicode -import std/strformat -import std/typetraits - -import ./vulkan -import ./vulkan_helpers -import ./math/vector -import ./math/matrix -import ./buffer -import ./glsl_helpers - -# TODO: check for alignment in uniform blocks -# -type - DescriptorType = SomeNumber|TVec|TMat - Descriptor*[T: DescriptorType] = object - value*: T - ViewProjectionTransform* = Descriptor[Mat44] - -proc createUniformDescriptorLayout*(device: VkDevice, - shaderStage: VkShaderStageFlags, binding: uint32): VkDescriptorSetLayout = - var - layoutbinding = VkDescriptorSetLayoutBinding( - binding: binding, - descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - descriptorCount: 1, - stageFlags: shaderStage, - pImmutableSamplers: nil, - ) - layoutInfo = VkDescriptorSetLayoutCreateInfo( - sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - bindingCount: 1, - pBindings: addr(layoutbinding) - ) - checkVkResult device.vkCreateDescriptorSetLayout(addr(layoutInfo), nil, addr(result)) - -proc createUniformBuffers*[nBuffers: static int, Uniforms](device: VkDevice, - physicalDevice: VkPhysicalDevice): array[nBuffers, Buffer] = - let size = sizeof(Uniforms) - for i in 0 ..< nBuffers: - var buffer = InitBuffer( - device, - physicalDevice, - uint64(size), - {UniformBuffer}, - {HostVisible, HostCoherent}, - ) - result[i] = buffer - -template getDescriptorType*(v: Descriptor): auto = get(genericParams(typeof(v)), 0) - -func generateGLSLUniformDeclarations*[Uniforms](binding: int = 0): string {.compileTime.} = - var stmtList: seq[string] - - when not (Uniforms is void): - let uniformTypeName = name(Uniforms).toUpper() - let uniformInstanceName = name(Uniforms).toLower() - stmtList.add(&"layout(binding = {binding}) uniform {uniformTypeName} {{") - for fieldname, value in Uniforms().fieldPairs: - when typeof(value) is Descriptor: - let glsltype = getGLSLType[getDescriptorType(value)]() - let n = fieldname - stmtList.add(&" {glsltype} {n};") - stmtList.add(&"}} {uniformInstanceName};") - - return stmtList.join("\n")
--- a/src/semicongine/engine.nim Mon Mar 27 21:01:32 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,870 +0,0 @@ -import std/options -import std/os -import std/times -import std/typetraits -import std/strformat -import std/enumerate -import std/logging - - -import ./math/vector -import ./vulkan -import ./vulkan_helpers -import ./platform/window -import ./events -import ./shader -import ./vertex -import ./buffer -import ./thing -import ./descriptor -import ./mesh - -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) -const ENGINE_NAME = "zamkongine" -const ENGINE_VERSION = "0.1" -const BUILD_VERSION = ENGINE_VERSION & '-' & gorge("git log -1 --format=format:'%H'") -echo "Engine: " & ENGINE_NAME & " " & BUILD_VERSION - -type - Device = object - device*: VkDevice - physicalDevice*: PhysicalDevice - graphicsQueueFamily*: uint32 - presentationQueueFamily*: uint32 - graphicsQueue*: VkQueue - presentationQueue*: VkQueue - commandPool*: VkCommandPool - commandBuffers*: array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer] - Swapchain = object - swapchain: VkSwapchainKHR - images: seq[VkImage] - imageviews: seq[VkImageView] - RenderPipeline*[VertexType, Uniforms] = object - device*: VkDevice - shaders*: seq[ShaderProgram[VertexType, Uniforms]] - layout*: VkPipelineLayout - pipeline*: VkPipeline - vertexBuffers*: seq[(seq[Buffer], bool, Buffer, uint32, VkIndexType)] - descriptorSetLayout*: VkDescriptorSetLayout - uniformBuffers*: array[MAX_FRAMES_IN_FLIGHT, Buffer] - descriptorPool*: VkDescriptorPool - descriptors: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSet] - clearColor*: Vec4 - 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 - frameSize*: TVec2[uint32] - swapchain*: Swapchain - framebuffers*: seq[VkFramebuffer] - renderPass*: VkRenderPass - imageAvailableSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore] - renderFinishedSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore] - inFlightFences*: array[MAX_FRAMES_IN_FLIGHT, VkFence] - Input* = object - keysDown*: set[Key] - keysPressed*: set[Key] - keysReleased*: set[Key] - mouseDown*: set[MouseButton] - mousePressed*: set[MouseButton] - mouseReleased*: set[MouseButton] - mousePos*: Vec2 - Engine* = object - vulkan*: Vulkan - window*: NativeWindow - currentscenedata*: Thing - input*: Input - maxFPS*: uint - - -method update*(thing: Thing, engine: Engine, t, dt: float32) {.base.} = discard -method update*(part: Part, engine: Engine, t, dt: float32) {.base.} = discard - -method update*[T, U](mesh: Mesh[T, U], engine: Engine, t, dt: float32) = - let transform = @[mesh.thing.getModelTransform().transposed()] - for name, value in mesh.vertexData.fieldPairs: - when value is ModelTransformAttribute: - value.data = transform - engine.vulkan.device.updateVertexData(value) - -proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[ - PhysicalDevice] = - for vulkanPhysicalDevice in getVulkanPhysicalDevices(instance): - var device = PhysicalDevice(device: vulkanPhysicalDevice, extensions: vulkan.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): TVec2[uint32] = - let capabilities = device.getSurfaceCapabilities(surface) - if capabilities.currentExtent.width != high(uint32): - return TVec2[uint32]([capabilities.currentExtent.width, - capabilities.currentExtent.height]) - else: - let (width, height) = window.size() - return TVec2[uint32]([ - min(max(uint32(width), capabilities.minImageExtent.width), - capabilities.maxImageExtent.width), - 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: TVec2[uint32], surfaceFormat: VkSurfaceFormatKHR): Swapchain = - - let capabilities = physicalDevice.device.getSurfaceCapabilities(surface) - var selectedPresentationMode = getPresentMode(physicalDevice.presentModes) - var imageCount = capabilities.minImageCount + 1 - if capabilities.maxImageCount > 0: - imageCount = min(capabilities.maxImageCount, imageCount) - # TODO: something not working on window..., likely the extent - var extent = VkExtent2D( - width: if dimension[0] > 0: dimension[0] else: 1, - height: if dimension[1] > 0: dimension[1] else: 1, - ) - # setup swapchain - var swapchainCreateInfo = VkSwapchainCreateInfoKHR( - sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, - surface: surface, - minImageCount: imageCount, - imageFormat: surfaceFormat.format, - imageColorSpace: surfaceFormat.colorSpace, - imageExtent: extent, - 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 initRenderPipeline[VertexType, Uniforms](device: VkDevice, frameSize: TVec2[uint32], renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, Uniforms] = - # load shaders - result.device = device - result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_VERTEX_BIT, vertexShader)) - result.shaders.add(initShaderProgram[VertexType, Uniforms](device, 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[VertexType]() - attributebindings = generateInputAttributeBinding[VertexType]() - - # 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], - ) - - result.descriptorSetLayout = device.createUniformDescriptorLayout( - VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), 0) - var - # "globals" that go into the shader, uniforms etc. - pipelineLayoutInfo = VkPipelineLayoutCreateInfo( - sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - setLayoutCount: 1, - pSetLayouts: addr(result.descriptorSetLayout), - 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: TVec2[uint32]): 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[0], - height: dimension[1], - 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]) = - if vulkan.frameSize.x == 0 or vulkan.frameSize.y == 0: - return (vulkan.swapchain, vulkan.framebuffers) - debug(&"Recreate swapchain with dimension {vulkan.frameSize}") - 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.frameSize, - vulkan.surfaceFormat - ) - result[1] = vulkan.device.device.setupFramebuffers( - result[0], - vulkan.renderPass, - vulkan.frameSize - ) - - -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*(windowTitle: string): Engine = - - result.window = createWindow(windowTitle) - let mousepos = result.window.getMousePosition() - if mousepos.isSome(): - result.input.mousePos = mousePos.get() - - - # create vulkan instance - result.vulkan.instance = createVulkanInstance(VULKAN_VERSION) - - # setup vulkan functions - loadVulkan(result.vulkan.instance) - - 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.frameSize = 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.frameSize, - 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.frameSize - ) - ( - result.vulkan.device.commandPool, - result.vulkan.device.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*[VertexType; UniformType; IndexType: uint16|uint32](engine: var Engine, scenedata: Thing, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, UniformType] = - engine.currentscenedata = scenedata - result = initRenderPipeline[VertexType, UniformType]( - engine.vulkan.device.device, - engine.vulkan.frameSize, - engine.vulkan.renderPass, - vertexShader, - fragmentShader, - ) - - for mesh in allPartsOfType[Mesh[VertexType, IndexType]]( - engine.currentscenedata): - result.vertexBuffers.add createIndexedVertexBuffers(mesh, - result.device, engine.vulkan.device.physicalDevice.device, - engine.vulkan.device.commandPool, engine.vulkan.device.graphicsQueue) - - # uniform buffers - when not (UniformType is void): - result.uniformBuffers = createUniformBuffers[MAX_FRAMES_IN_FLIGHT, UniformType]( - result.device, - engine.vulkan.device.physicalDevice.device - ) - - var - poolSize = VkDescriptorPoolSize( - thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - descriptorCount: uint32(MAX_FRAMES_IN_FLIGHT), - ) - poolInfo = VkDescriptorPoolCreateInfo( - sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - poolSizeCount: 1, - pPoolSizes: addr(poolSize), - maxSets: uint32(MAX_FRAMES_IN_FLIGHT), - ) - checkVkResult vkCreateDescriptorPool(result.device, addr(poolInfo), nil, addr(result.descriptorPool)) - - var layouts: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSetLayout] - for i in 0 ..< MAX_FRAMES_IN_FLIGHT: - layouts[i] = result.descriptorSetLayout - var allocInfo = VkDescriptorSetAllocateInfo( - sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - descriptorPool: result.descriptorPool, - descriptorSetCount: uint32(MAX_FRAMES_IN_FLIGHT), - pSetLayouts: addr(layouts[0]), - ) - - checkVkResult vkAllocateDescriptorSets(result.device, addr(allocInfo), addr(result.descriptors[0])) - - when not (UniformType is void): - var bufferInfos: array[MAX_FRAMES_IN_FLIGHT, array[1, VkDescriptorBufferInfo]] # because we use only one Uniform atm - for i in 0 ..< MAX_FRAMES_IN_FLIGHT: - bufferInfos[i][0] = VkDescriptorBufferInfo( - buffer: result.uniformBuffers[i].vkBuffer, - offset: VkDeviceSize(0), - range: VkDeviceSize(sizeof(UniformType)), - ) - var descriptorWrite = VkWriteDescriptorSet( - sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - dstSet: result.descriptors[i], - dstBinding: 0, - dstArrayElement: 0, - descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - descriptorCount: 1, - pBufferInfo: addr(bufferInfos[i][0]), - ) - vkUpdateDescriptorSets(result.device, 1, addr(descriptorWrite), 0, nil) - -proc updateBufferData*[T](device: Device, buffer: Buffer, data: var T) = - when stripGenericParams(T) is seq: # seq needs special treatment for automated data uploading - assert data.len > 0 - let size = data.len * sizeof(get(genericParams(typeof(data)), 0)) - let dataptr = addr(data[0]) - else: - let size = sizeof(data) - let dataptr = addr(data) - if not (HostVisible in buffer.memoryProperties): - if not (TransferDst in buffer.bufferTypes): - raise newException(Exception, "Buffer cannot be updated") - var stagingBuffer = device.device.InitBuffer(device.physicalDevice.device, - uint64(size), {TransferSrc}, {HostVisible, HostCoherent}) - copyMem(stagingBuffer.data, dataptr, size) - transferBuffer(device.commandPool, device.graphicsQueue, stagingBuffer, - buffer, uint64(size)) - stagingBuffer.trash() - else: - copyMem(buffer.data, dataptr, size) - -proc updateVertexData*[T: VertexAttribute](device: Device, - vertexAttribute: var T) = - device.updateBufferData(vertexAttribute.buffer, vertexAttribute.data) - -proc updateUniformData*[VertexType, Uniforms](device: Device, - pipeline: RenderPipeline[VertexType, Uniforms], data: var Uniforms) = - for buffer in pipeline.uniformBuffers: - device.updateBufferData(buffer, data) - - -proc runPipeline[VertexType; Uniforms](commandBuffer: VkCommandBuffer, pipeline: var RenderPipeline[VertexType, Uniforms], currentFrame: int) = - vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline) - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, addr(pipeline.descriptors[currentFrame]), 0, nil) - - for (vertexBufferSet, indexed, indexBuffer, count, indexType) in pipeline.vertexBuffers: - var - vertexBuffers: seq[VkBuffer] - offsets: seq[VkDeviceSize] - for buffer in vertexBufferSet: - vertexBuffers.add buffer.vkBuffer - offsets.add VkDeviceSize(0) - - vkCmdBindVertexBuffers(commandBuffer, firstBinding = 0'u32, bindingCount = uint32(vertexBuffers.len), pBuffers = addr(vertexBuffers[ 0]), pOffsets = addr(offsets[0])) - if indexed: - vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType) - vkCmdDrawIndexed(commandBuffer, count, 1, 0, 0, 0) - else: - vkCmdDraw(commandBuffer, vertexCount = count, instanceCount = 1, firstVertex = 0, firstInstance = 0) - -proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: var RenderPipeline, - commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, - frameSize: TVec2[uint32], currentFrame: int) = - var - beginInfo = VkCommandBufferBeginInfo( - sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - pInheritanceInfo: nil, - ) - clearColor = VkClearValue(color: VkClearColorValue(float32: pipeline.clearColor)) - renderPassInfo = VkRenderPassBeginInfo( - sType: VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, - renderPass: renderPass, - framebuffer: framebuffer, - renderArea: VkRect2D( - offset: VkOffset2D(x: 0, y: 0), - extent: VkExtent2D(width: frameSize.x, height: frameSize.y), - ), - clearValueCount: 1, - pClearValues: addr(clearColor), - ) - viewport = VkViewport( - x: 0.0, - y: 0.0, - width: (float)frameSize.x, - height: (float)frameSize.y, - minDepth: 0.0, - maxDepth: 1.0, - ) - scissor = VkRect2D( - offset: VkOffset2D(x: 0, y: 0), - extent: VkExtent2D(width: frameSize.x, height: frameSize.y) - ) - checkVkResult vkBeginCommandBuffer(commandBuffer, addr(beginInfo)) - block: - vkCmdBeginRenderPass(commandBuffer, addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE) - vkCmdSetViewport(commandBuffer, firstViewport = 0, viewportCount = 1, addr(viewport)) - vkCmdSetScissor(commandBuffer, firstScissor = 0, scissorCount = 1, addr(scissor)) - runPipeline(commandBuffer, pipeline, currentFrame) - vkCmdEndRenderPass(commandBuffer) - checkVkResult vkEndCommandBuffer(commandBuffer) - -proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, pipeline: var RenderPipeline) = - - checkVkResult vkWaitForFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64)) - - var bufferImageIndex: uint32 - let nextImageResult = vkAcquireNextImageKHR( - vulkan.device.device, - vulkan.swapchain.swapchain, - high(uint64), - vulkan.imageAvailableSemaphores[currentFrame], - VkFence(0), - addr(bufferImageIndex) - ) - if nextImageResult == VK_ERROR_OUT_OF_DATE_KHR: - vulkan.frameSize = 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 vkResetFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame])) - - checkVkResult vkResetCommandBuffer(vulkan.device.commandBuffers[currentFrame], VkCommandBufferResetFlags(0)) - vulkan.renderPass.recordCommandBuffer( - pipeline, - vulkan.device.commandBuffers[currentFrame], - vulkan.framebuffers[bufferImageIndex], vulkan.frameSize, - currentFrame - ) - 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.device.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.frameSize = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface) - (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain() - - -func frametime(engine: Engine): auto = - if engine.maxFPS == 0: 0'f - else: 1'f / float32(engine.maxFPS) - -proc run*(engine: var Engine, pipeline: var RenderPipeline, globalUpdate: proc( - engine: var Engine, t, dt: float32)) = - var - currentFrame = 0 - resized = false - lastUpdate = cpuTime() - lastframe = 0'f - - while true: - # process input - engine.input.keysPressed = {} - engine.input.keysReleased = {} - engine.input.mousePressed = {} - engine.input.mouseReleased = {} - var killed = false - for event in engine.window.pendingEvents(): - case event.eventType: - of Quit: - killed = true - of ResizedWindow: - resized = true - of KeyPressed: - engine.input.keysPressed.incl event.key - engine.input.keysDown.incl event.key - of KeyReleased: - engine.input.keysReleased.incl event.key - engine.input.keysDown.excl event.key - of MousePressed: - engine.input.mousePressed.incl event.button - engine.input.mouseDown.incl event.button - of MouseReleased: - engine.input.mouseReleased.incl event.button - engine.input.mouseDown.excl event.button - of MouseMoved: - engine.input.mousePos = Vec2([float32(event.x), float32(event.y)]) - if killed: # at least on windows we should return immediately as swapchain recreation will fail after kill - break - - # game logic update - let - now = cpuTime() - dt = now - lastUpdate - lastUpdate = now - engine.globalUpdate(now, dt) - for thing in allThings(engine.currentscenedata): - for part in thing.parts: - update(part, engine, now, dt) - update(thing, engine, now, dt) - - # submit frame for drawing - if engine.maxFPS == 0 or (now - lastframe >= engine.frametime): # framerate limit - engine.window.drawFrame(engine.vulkan, currentFrame, resized, pipeline) - lastframe = now - currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT - resized = false - - checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device) - -proc trash*(pipeline: var RenderPipeline) = - vkDestroyDescriptorPool(pipeline.device, pipeline.descriptorPool, nil) - vkDestroyDescriptorSetLayout(pipeline.device, pipeline.descriptorSetLayout, nil) - vkDestroyPipeline(pipeline.device, pipeline.pipeline, nil) - vkDestroyPipelineLayout(pipeline.device, pipeline.layout, nil) - for shader in pipeline.shaders: - vkDestroyShaderModule(pipeline.device, shader.shader.module, nil) - - for (bufferset, indexed, indexbuffer, cnt, t) in - pipeline.vertexBuffers.mitems: - if indexed: - indexbuffer.trash() - for buffer in bufferset.mitems: - buffer.trash() - for buffer in pipeline.uniformBuffers.mitems: - buffer.trash() - -proc trash*(engine: var Engine) = - checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device) - engine.vulkan.device.device.trash(engine.vulkan.swapchain, - engine.vulkan.framebuffers) - - 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.vkDestroyRenderPass(engine.vulkan.renderPass, nil) - engine.vulkan.device.device.vkDestroyCommandPool(engine.vulkan.device.commandPool, 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) - engine.window.trash() - # needs to happen after window is trashed as the driver might have a hook registered for the window destruction - engine.vulkan.instance.vkDestroyInstance(nil)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/entity.nim Tue Mar 28 00:20:49 2023 +0700 @@ -0,0 +1,131 @@ +import std/strformat +import std/typetraits + +import ./math/matrix + +type + Component* = ref object of RootObj + thing*: Entity + + Entity* = ref object of RootObj + name*: string + transform*: Mat44 # todo: cache transform + only update VBO when transform changed + parent*: Entity + children*: seq[Entity] + parts*: seq[Component] + + +func `$`*(thing: Entity): string = thing.name +method `$`*(part: Component): string {.base.} = + if part.thing != nil: + &"{part.thing} -> Component" + else: + &"Standalone Component" + +proc add*(thing: Entity, child: Entity) = + child.parent = thing + thing.children.add child +proc add*(thing: Entity, part: Component) = + part.thing = thing + thing.parts.add part +proc add*(thing: Entity, children: seq[Entity]) = + for child in children: + child.parent = thing + thing.children.add child +proc add*(thing: Entity, parts: seq[Component]) = + for part in parts: + part.thing = thing + thing.parts.add part + +func newThing*(name: string = ""): Entity = + result = new Entity + result.name = name + result.transform = Unit44 + if result.name == "": + result.name = &"Entity[{$(cast[ByteAddress](result))}]" + +func newThing*(name: string, firstChild: Entity, children: varargs[ + Entity]): Entity = + result = new Entity + result.add firstChild + for child in children: + result.add child + result.name = name + result.transform = Unit44 + if result.name == "": + result.name = &"Entity[{$(cast[ByteAddress](result))}]" + +proc newThing*(name: string, firstPart: Component, parts: varargs[Component]): Entity = + result = new Entity + result.name = name + result.add firstPart + for part in parts: + result.add part + if result.name == "": + result.name = &"Entity[{$(cast[ByteAddress](result))}]" + result.transform = Unit44 + +func getModelTransform*(thing: Entity): Mat44 = + result = Unit44 + var currentThing = thing + while currentThing != nil: + result = currentThing.transform * result + currentThing = currentThing.parent + +iterator allPartsOfType*[T: Component](root: Entity): T = + var queue = @[root] + while queue.len > 0: + let thing = queue.pop + for part in thing.parts: + if part of T: + yield T(part) + for i in countdown(thing.children.len - 1, 0): + queue.add thing.children[i] + +func firstWithName*(root: Entity, name: string): Entity = + var queue = @[root] + while queue.len > 0: + let next = queue.pop + for child in next.children: + if child.name == name: + return child + queue.add child + +func firstPartWithName*[T: Component](root: Entity, name: string): T = + var queue = @[root] + while queue.len > 0: + let next = queue.pop + for child in next.children: + if child.name == name: + for part in child.parts: + if part of T: + return T(part) + queue.add child + +func allWithName*(root: Entity, name: string): seq[Entity] = + var queue = @[root] + while queue.len > 0: + let next = queue.pop + for child in next.children: + if child.name == name: + result.add child + queue.add child + +func allPartsWithName*[T: Component](root: Entity, name: string): seq[T] = + var queue = @[root] + while queue.len > 0: + let next = queue.pop + for child in next.children: + if child.name == name: + for part in child.parts: + if part of T: + result.add T(part) + queue.add child + +iterator allThings*(root: Entity): Entity = + var queue = @[root] + while queue.len > 0: + let next = queue.pop + for child in next.children: + queue.add child + yield next
--- a/src/semicongine/glsl_helpers.nim Mon Mar 27 21:01:32 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -import ./math/vector -import ./math/matrix - -func getGLSLType*[T](): string = - # todo: likely not correct as we would need to enable some - # extensions somewhere (Vulkan/GLSL compiler?) to have - # everything work as intended. Or maybe the GPU driver does - # some automagic conversion stuf.. - when T is uint8: "uint" - elif T is int8: "int" - elif T is uint16: "uint" - elif T is int16: "int" - elif T is uint32: "uint" - elif T is int32: "int" - elif T is uint64: "uint" - elif T is int64: "int" - elif T is float32: "float" - elif T is float64: "double" - - elif T is TVec2[uint8]: "uvec2" - elif T is TVec2[int8]: "ivec2" - elif T is TVec2[uint16]: "uvec2" - elif T is TVec2[int16]: "ivec2" - elif T is TVec2[uint32]: "uvec2" - elif T is TVec2[int32]: "ivec2" - elif T is TVec2[uint64]: "uvec2" - elif T is TVec2[int64]: "ivec2" - elif T is TVec2[float32]: "vec2" - elif T is TVec2[float64]: "dvec2" - - elif T is TVec3[uint8]: "uvec3" - elif T is TVec3[int8]: "ivec3" - elif T is TVec3[uint16]: "uvec3" - elif T is TVec3[int16]: "ivec3" - elif T is TVec3[uint32]: "uvec3" - elif T is TVec3[int32]: "ivec3" - elif T is TVec3[uint64]: "uvec3" - elif T is TVec3[int64]: "ivec3" - elif T is TVec3[float32]: "vec3" - elif T is TVec3[float64]: "dvec3" - - elif T is TVec4[uint8]: "uvec4" - elif T is TVec4[int8]: "ivec4" - elif T is TVec4[uint16]: "uvec4" - elif T is TVec4[int16]: "ivec4" - elif T is TVec4[uint32]: "uvec4" - elif T is TVec4[int32]: "ivec4" - elif T is TVec4[uint64]: "uvec4" - elif T is TVec4[int64]: "ivec4" - elif T is TVec4[float32]: "vec4" - elif T is TVec4[float64]: "dvec4" - - elif T is TMat22[float32]: "mat2" - elif T is TMat23[float32]: "mat32" - elif T is TMat32[float32]: "mat23" - elif T is TMat33[float32]: "mat3" - elif T is TMat34[float32]: "mat43" - elif T is TMat43[float32]: "mat34" - elif T is TMat44[float32]: "mat4" - - elif T is TMat22[float64]: "dmat2" - elif T is TMat23[float64]: "dmat32" - elif T is TMat32[float64]: "dmat23" - elif T is TMat33[float64]: "dmat3" - elif T is TMat34[float64]: "dmat43" - elif T is TMat43[float64]: "dmat34" - elif T is TMat44[float64]: "dmat4"
--- a/src/semicongine/image.nim Mon Mar 27 21:01:32 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -import ./buffer -import ./math/vector -import ./vulkan -import ./vulkan_helpers - -type - ImageUsage* = enum - TransferDst = VK_IMAGE_USAGE_TRANSFER_DST_BIT - SampledBit = VK_IMAGE_USAGE_SAMPLED_BIT - ImageUsages = set[ImageUsage] - Image = object - buffer: Buffer - image: VkImage - memory: VkDeviceMemory - -proc InitImage(data: var seq[byte], size: TVec2[uint32], format: VkFormat, - tiling: VkImageTiling, usage: ImageUsages, properties: MemoryProperties, - device: VkDevice, - physicalDevice: VkPhysicalDevice): Image = - result.buffer = InitBuffer(device, physicalDevice, uint64(data.len), { - TransferSrc}, {HostVisible, HostCoherent}) - copyMem(result.buffer.data, addr(data[0]), data.len) - - var imageInfo = VkImageCreateInfo( - sType: VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - imageType: VK_IMAGE_TYPE_2D, - extent: VkExtent3D(width: size.x, height: size.y, depth: 1), - mipLevels: 1, - arrayLayers: 1, - format: format, - tiling: tiling, - initialLayout: VK_IMAGE_LAYOUT_UNDEFINED, - usage: cast[VkImageUsageFlags](usage), - sharingMode: VK_SHARING_MODE_EXCLUSIVE, - samples: VK_SAMPLE_COUNT_1_BIT, - ) - checkVkResult vkCreateImage(device, addr(imageInfo), nil, addr(result.image)) - - var memRequirements: VkMemoryRequirements - vkGetImageMemoryRequirements(device, result.image, addr(memRequirements)) - - var allocInfo = VkMemoryAllocateInfo( - sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - allocationSize: memRequirements.size, - memoryTypeIndex: memRequirements.findMemoryType(physicalDevice, properties) - ) - - checkVkResult vkAllocateMemory(device, addr(allocInfo), nil, addr(result.memory)) - checkVkResult vkBindImageMemory(device, result.image, result.memory, - VkDeviceSize(0))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/legacy/buffer.nim Tue Mar 28 00:20:49 2023 +0700 @@ -0,0 +1,125 @@ +import std/typetraits + +import ./vulkan +import ./vulkan_helpers + +type + BufferType* = enum + None = 0 + TransferSrc = VK_BUFFER_USAGE_TRANSFER_SRC_BIT + TransferDst = VK_BUFFER_USAGE_TRANSFER_DST_BIT + UniformBuffer = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT + IndexBuffer = VK_BUFFER_USAGE_INDEX_BUFFER_BIT + VertexBuffer = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT + MemoryProperty* = enum + DeviceLocal = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT + HostVisible = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + HostCoherent = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT + MemoryProperties* = set[MemoryProperty] + Buffer* = object + device*: VkDevice + vkBuffer*: VkBuffer + size*: uint64 + memoryRequirements*: VkMemoryRequirements + memoryProperties*: MemoryProperties + memory*: VkDeviceMemory + bufferTypes*: set[BufferType] + data*: pointer + +proc trash*(buffer: var Buffer) = + if int64(buffer.vkBuffer) != 0 and int64(buffer.memory) != 0: + vkUnmapMemory(buffer.device, buffer.memory) + if int64(buffer.vkBuffer) != 0: + vkDestroyBuffer(buffer.device, buffer.vkBuffer, nil) + buffer.vkBuffer = VkBuffer(0) + if int64(buffer.memory) != 0: + vkFreeMemory(buffer.device, buffer.memory, nil) + buffer.memory = VkDeviceMemory(0) + +proc findMemoryType*(memoryRequirements: VkMemoryRequirements, + physicalDevice: VkPhysicalDevice, properties: MemoryProperties): uint32 = + var physicalProperties: VkPhysicalDeviceMemoryProperties + vkGetPhysicalDeviceMemoryProperties(physicalDevice, addr(physicalProperties)) + + for i in 0'u32 ..< physicalProperties.memoryTypeCount: + if bool(memoryRequirements.memoryTypeBits and (1'u32 shl i)): + if (uint32(physicalProperties.memoryTypes[i].propertyFlags) and cast[ + uint32](properties)) == cast[uint32](properties): + return i + +proc InitBuffer*( + device: VkDevice, + physicalDevice: VkPhysicalDevice, + size: uint64, + bufferTypes: set[BufferType], + properties: MemoryProperties, +): Buffer = + result = Buffer(device: device, size: size, bufferTypes: bufferTypes, + memoryProperties: properties) + var usageFlags = 0 + for usage in bufferTypes: + usageFlags = ord(usageFlags) or ord(usage) + var bufferInfo = VkBufferCreateInfo( + sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + size: VkDeviceSize(result.size), + usage: VkBufferUsageFlags(usageFlags), + sharingMode: VK_SHARING_MODE_EXCLUSIVE, + ) + checkVkResult vkCreateBuffer(result.device, addr(bufferInfo), nil, addr( + result.vkBuffer)) + vkGetBufferMemoryRequirements(result.device, result.vkBuffer, addr( + result.memoryRequirements)) + + var allocInfo = VkMemoryAllocateInfo( + sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + allocationSize: result.memoryRequirements.size, + memoryTypeIndex: result.memoryRequirements.findMemoryType(physicalDevice, properties) + ) + if result.size > 0: + checkVkResult result.device.vkAllocateMemory(addr(allocInfo), nil, addr(result.memory)) + checkVkResult result.device.vkBindBufferMemory(result.vkBuffer, result.memory, + VkDeviceSize(0)) + checkVkResult vkMapMemory( + result.device, + result.memory, + offset = VkDeviceSize(0), + VkDeviceSize(result.size), + VkMemoryMapFlags(0), + addr(result.data) + ) + + +proc transferBuffer*(commandPool: VkCommandPool, queue: VkQueue, src, + dst: Buffer, size: uint64) = + assert uint64(src.device) == uint64(dst.device) + assert TransferSrc in src.bufferTypes + assert TransferDst in dst.bufferTypes + var + allocInfo = VkCommandBufferAllocateInfo( + sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + level: VK_COMMAND_BUFFER_LEVEL_PRIMARY, + commandPool: commandPool, + commandBufferCount: 1, + ) + commandBuffer: VkCommandBuffer + checkVkResult vkAllocateCommandBuffers(src.device, addr(allocInfo), addr(commandBuffer)) + + var beginInfo = VkCommandBufferBeginInfo( + sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT), + ) + checkVkResult vkBeginCommandBuffer(commandBuffer, addr(beginInfo)) + var copyRegion = VkBufferCopy(size: VkDeviceSize(size)) + vkCmdCopyBuffer(commandBuffer, src.vkBuffer, dst.vkBuffer, 1, addr(copyRegion)) + checkVkResult vkEndCommandBuffer(commandBuffer) + + var submitInfo = VkSubmitInfo( + sType: VK_STRUCTURE_TYPE_SUBMIT_INFO, + commandBufferCount: 1, + pCommandBuffers: addr(commandBuffer), + ) + + checkVkResult vkQueueSubmit(queue, 1, addr(submitInfo), VkFence(0)) + checkVkResult vkQueueWaitIdle(queue) + vkFreeCommandBuffers(src.device, commandPool, 1, addr(commandBuffer)) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/legacy/descriptor.nim Tue Mar 28 00:20:49 2023 +0700 @@ -0,0 +1,67 @@ +import std/strutils +import std/unicode +import std/strformat +import std/typetraits + +import ./vulkan +import ./vulkan_helpers +import ./math/vector +import ./math/matrix +import ./buffer +import ./glsl_helpers + +# TODO: check for alignment in uniform blocks +# +type + DescriptorType = SomeNumber|TVec|TMat + Descriptor*[T: DescriptorType] = object + value*: T + ViewProjectionTransform* = Descriptor[Mat44] + +proc createUniformDescriptorLayout*(device: VkDevice, + shaderStage: VkShaderStageFlags, binding: uint32): VkDescriptorSetLayout = + var + layoutbinding = VkDescriptorSetLayoutBinding( + binding: binding, + descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + descriptorCount: 1, + stageFlags: shaderStage, + pImmutableSamplers: nil, + ) + layoutInfo = VkDescriptorSetLayoutCreateInfo( + sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + bindingCount: 1, + pBindings: addr(layoutbinding) + ) + checkVkResult device.vkCreateDescriptorSetLayout(addr(layoutInfo), nil, addr(result)) + +proc createUniformBuffers*[nBuffers: static int, Uniforms](device: VkDevice, + physicalDevice: VkPhysicalDevice): array[nBuffers, Buffer] = + let size = sizeof(Uniforms) + for i in 0 ..< nBuffers: + var buffer = InitBuffer( + device, + physicalDevice, + uint64(size), + {UniformBuffer}, + {HostVisible, HostCoherent}, + ) + result[i] = buffer + +template getDescriptorType*(v: Descriptor): auto = get(genericParams(typeof(v)), 0) + +func generateGLSLUniformDeclarations*[Uniforms](binding: int = 0): string {.compileTime.} = + var stmtList: seq[string] + + when not (Uniforms is void): + let uniformTypeName = name(Uniforms).toUpper() + let uniformInstanceName = name(Uniforms).toLower() + stmtList.add(&"layout(binding = {binding}) uniform {uniformTypeName} {{") + for fieldname, value in Uniforms().fieldPairs: + when typeof(value) is Descriptor: + let glsltype = getGLSLType[getDescriptorType(value)]() + let n = fieldname + stmtList.add(&" {glsltype} {n};") + stmtList.add(&"}} {uniformInstanceName};") + + return stmtList.join("\n")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/legacy/engine.nim Tue Mar 28 00:20:49 2023 +0700 @@ -0,0 +1,870 @@ +import std/options +import std/os +import std/times +import std/typetraits +import std/strformat +import std/enumerate +import std/logging + + +import ./math/vector +import ./vulkan +import ./vulkan_helpers +import ./platform/window +import ./events +import ./shader +import ./vertex +import ./buffer +import ./thing +import ./descriptor +import ./mesh + +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) +const ENGINE_NAME = "zamkongine" +const ENGINE_VERSION = "0.1" +const BUILD_VERSION = ENGINE_VERSION & '-' & gorge("git log -1 --format=format:'%H'") +echo "Engine: " & ENGINE_NAME & " " & BUILD_VERSION + +type + Device = object + device*: VkDevice + physicalDevice*: PhysicalDevice + graphicsQueueFamily*: uint32 + presentationQueueFamily*: uint32 + graphicsQueue*: VkQueue + presentationQueue*: VkQueue + commandPool*: VkCommandPool + commandBuffers*: array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer] + Swapchain = object + swapchain: VkSwapchainKHR + images: seq[VkImage] + imageviews: seq[VkImageView] + RenderPipeline*[VertexType, Uniforms] = object + device*: VkDevice + shaders*: seq[ShaderProgram[VertexType, Uniforms]] + layout*: VkPipelineLayout + pipeline*: VkPipeline + vertexBuffers*: seq[(seq[Buffer], bool, Buffer, uint32, VkIndexType)] + descriptorSetLayout*: VkDescriptorSetLayout + uniformBuffers*: array[MAX_FRAMES_IN_FLIGHT, Buffer] + descriptorPool*: VkDescriptorPool + descriptors: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSet] + clearColor*: Vec4 + 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 + frameSize*: TVec2[uint32] + swapchain*: Swapchain + framebuffers*: seq[VkFramebuffer] + renderPass*: VkRenderPass + imageAvailableSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore] + renderFinishedSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore] + inFlightFences*: array[MAX_FRAMES_IN_FLIGHT, VkFence] + Input* = object + keysDown*: set[Key] + keysPressed*: set[Key] + keysReleased*: set[Key] + mouseDown*: set[MouseButton] + mousePressed*: set[MouseButton] + mouseReleased*: set[MouseButton] + mousePos*: Vec2 + Engine* = object + vulkan*: Vulkan + window*: NativeWindow + currentscenedata*: Thing + input*: Input + maxFPS*: uint + + +method update*(thing: Thing, engine: Engine, t, dt: float32) {.base.} = discard +method update*(part: Part, engine: Engine, t, dt: float32) {.base.} = discard + +method update*[T, U](mesh: Mesh[T, U], engine: Engine, t, dt: float32) = + let transform = @[mesh.thing.getModelTransform().transposed()] + for name, value in mesh.vertexData.fieldPairs: + when value is ModelTransformAttribute: + value.data = transform + engine.vulkan.device.updateVertexData(value) + +proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[ + PhysicalDevice] = + for vulkanPhysicalDevice in getVulkanPhysicalDevices(instance): + var device = PhysicalDevice(device: vulkanPhysicalDevice, extensions: vulkan.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): TVec2[uint32] = + let capabilities = device.getSurfaceCapabilities(surface) + if capabilities.currentExtent.width != high(uint32): + return TVec2[uint32]([capabilities.currentExtent.width, + capabilities.currentExtent.height]) + else: + let (width, height) = window.size() + return TVec2[uint32]([ + min(max(uint32(width), capabilities.minImageExtent.width), + capabilities.maxImageExtent.width), + 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: TVec2[uint32], surfaceFormat: VkSurfaceFormatKHR): Swapchain = + + let capabilities = physicalDevice.device.getSurfaceCapabilities(surface) + var selectedPresentationMode = getPresentMode(physicalDevice.presentModes) + var imageCount = capabilities.minImageCount + 1 + if capabilities.maxImageCount > 0: + imageCount = min(capabilities.maxImageCount, imageCount) + # TODO: something not working on window..., likely the extent + var extent = VkExtent2D( + width: if dimension[0] > 0: dimension[0] else: 1, + height: if dimension[1] > 0: dimension[1] else: 1, + ) + # setup swapchain + var swapchainCreateInfo = VkSwapchainCreateInfoKHR( + sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + surface: surface, + minImageCount: imageCount, + imageFormat: surfaceFormat.format, + imageColorSpace: surfaceFormat.colorSpace, + imageExtent: extent, + 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 initRenderPipeline[VertexType, Uniforms](device: VkDevice, frameSize: TVec2[uint32], renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, Uniforms] = + # load shaders + result.device = device + result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_VERTEX_BIT, vertexShader)) + result.shaders.add(initShaderProgram[VertexType, Uniforms](device, 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[VertexType]() + attributebindings = generateInputAttributeBinding[VertexType]() + + # 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], + ) + + result.descriptorSetLayout = device.createUniformDescriptorLayout( + VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), 0) + var + # "globals" that go into the shader, uniforms etc. + pipelineLayoutInfo = VkPipelineLayoutCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + setLayoutCount: 1, + pSetLayouts: addr(result.descriptorSetLayout), + 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: TVec2[uint32]): 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[0], + height: dimension[1], + 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]) = + if vulkan.frameSize.x == 0 or vulkan.frameSize.y == 0: + return (vulkan.swapchain, vulkan.framebuffers) + debug(&"Recreate swapchain with dimension {vulkan.frameSize}") + 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.frameSize, + vulkan.surfaceFormat + ) + result[1] = vulkan.device.device.setupFramebuffers( + result[0], + vulkan.renderPass, + vulkan.frameSize + ) + + +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*(windowTitle: string): Engine = + + result.window = createWindow(windowTitle) + let mousepos = result.window.getMousePosition() + if mousepos.isSome(): + result.input.mousePos = mousePos.get() + + + # create vulkan instance + result.vulkan.instance = createVulkanInstance(VULKAN_VERSION) + + # setup vulkan functions + loadVulkan(result.vulkan.instance) + + 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.frameSize = 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.frameSize, + 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.frameSize + ) + ( + result.vulkan.device.commandPool, + result.vulkan.device.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*[VertexType; UniformType; IndexType: uint16|uint32](engine: var Engine, scenedata: Thing, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, UniformType] = + engine.currentscenedata = scenedata + result = initRenderPipeline[VertexType, UniformType]( + engine.vulkan.device.device, + engine.vulkan.frameSize, + engine.vulkan.renderPass, + vertexShader, + fragmentShader, + ) + + for mesh in allPartsOfType[Mesh[VertexType, IndexType]]( + engine.currentscenedata): + result.vertexBuffers.add createIndexedVertexBuffers(mesh, + result.device, engine.vulkan.device.physicalDevice.device, + engine.vulkan.device.commandPool, engine.vulkan.device.graphicsQueue) + + # uniform buffers + when not (UniformType is void): + result.uniformBuffers = createUniformBuffers[MAX_FRAMES_IN_FLIGHT, UniformType]( + result.device, + engine.vulkan.device.physicalDevice.device + ) + + var + poolSize = VkDescriptorPoolSize( + thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + descriptorCount: uint32(MAX_FRAMES_IN_FLIGHT), + ) + poolInfo = VkDescriptorPoolCreateInfo( + sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + poolSizeCount: 1, + pPoolSizes: addr(poolSize), + maxSets: uint32(MAX_FRAMES_IN_FLIGHT), + ) + checkVkResult vkCreateDescriptorPool(result.device, addr(poolInfo), nil, addr(result.descriptorPool)) + + var layouts: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSetLayout] + for i in 0 ..< MAX_FRAMES_IN_FLIGHT: + layouts[i] = result.descriptorSetLayout + var allocInfo = VkDescriptorSetAllocateInfo( + sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + descriptorPool: result.descriptorPool, + descriptorSetCount: uint32(MAX_FRAMES_IN_FLIGHT), + pSetLayouts: addr(layouts[0]), + ) + + checkVkResult vkAllocateDescriptorSets(result.device, addr(allocInfo), addr(result.descriptors[0])) + + when not (UniformType is void): + var bufferInfos: array[MAX_FRAMES_IN_FLIGHT, array[1, VkDescriptorBufferInfo]] # because we use only one Uniform atm + for i in 0 ..< MAX_FRAMES_IN_FLIGHT: + bufferInfos[i][0] = VkDescriptorBufferInfo( + buffer: result.uniformBuffers[i].vkBuffer, + offset: VkDeviceSize(0), + range: VkDeviceSize(sizeof(UniformType)), + ) + var descriptorWrite = VkWriteDescriptorSet( + sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + dstSet: result.descriptors[i], + dstBinding: 0, + dstArrayElement: 0, + descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + descriptorCount: 1, + pBufferInfo: addr(bufferInfos[i][0]), + ) + vkUpdateDescriptorSets(result.device, 1, addr(descriptorWrite), 0, nil) + +proc updateBufferData*[T](device: Device, buffer: Buffer, data: var T) = + when stripGenericParams(T) is seq: # seq needs special treatment for automated data uploading + assert data.len > 0 + let size = data.len * sizeof(get(genericParams(typeof(data)), 0)) + let dataptr = addr(data[0]) + else: + let size = sizeof(data) + let dataptr = addr(data) + if not (HostVisible in buffer.memoryProperties): + if not (TransferDst in buffer.bufferTypes): + raise newException(Exception, "Buffer cannot be updated") + var stagingBuffer = device.device.InitBuffer(device.physicalDevice.device, + uint64(size), {TransferSrc}, {HostVisible, HostCoherent}) + copyMem(stagingBuffer.data, dataptr, size) + transferBuffer(device.commandPool, device.graphicsQueue, stagingBuffer, + buffer, uint64(size)) + stagingBuffer.trash() + else: + copyMem(buffer.data, dataptr, size) + +proc updateVertexData*[T: VertexAttribute](device: Device, + vertexAttribute: var T) = + device.updateBufferData(vertexAttribute.buffer, vertexAttribute.data) + +proc updateUniformData*[VertexType, Uniforms](device: Device, + pipeline: RenderPipeline[VertexType, Uniforms], data: var Uniforms) = + for buffer in pipeline.uniformBuffers: + device.updateBufferData(buffer, data) + + +proc runPipeline[VertexType; Uniforms](commandBuffer: VkCommandBuffer, pipeline: var RenderPipeline[VertexType, Uniforms], currentFrame: int) = + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline) + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, addr(pipeline.descriptors[currentFrame]), 0, nil) + + for (vertexBufferSet, indexed, indexBuffer, count, indexType) in pipeline.vertexBuffers: + var + vertexBuffers: seq[VkBuffer] + offsets: seq[VkDeviceSize] + for buffer in vertexBufferSet: + vertexBuffers.add buffer.vkBuffer + offsets.add VkDeviceSize(0) + + vkCmdBindVertexBuffers(commandBuffer, firstBinding = 0'u32, bindingCount = uint32(vertexBuffers.len), pBuffers = addr(vertexBuffers[ 0]), pOffsets = addr(offsets[0])) + if indexed: + vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType) + vkCmdDrawIndexed(commandBuffer, count, 1, 0, 0, 0) + else: + vkCmdDraw(commandBuffer, vertexCount = count, instanceCount = 1, firstVertex = 0, firstInstance = 0) + +proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: var RenderPipeline, + commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, + frameSize: TVec2[uint32], currentFrame: int) = + var + beginInfo = VkCommandBufferBeginInfo( + sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + pInheritanceInfo: nil, + ) + clearColor = VkClearValue(color: VkClearColorValue(float32: pipeline.clearColor)) + renderPassInfo = VkRenderPassBeginInfo( + sType: VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + renderPass: renderPass, + framebuffer: framebuffer, + renderArea: VkRect2D( + offset: VkOffset2D(x: 0, y: 0), + extent: VkExtent2D(width: frameSize.x, height: frameSize.y), + ), + clearValueCount: 1, + pClearValues: addr(clearColor), + ) + viewport = VkViewport( + x: 0.0, + y: 0.0, + width: (float)frameSize.x, + height: (float)frameSize.y, + minDepth: 0.0, + maxDepth: 1.0, + ) + scissor = VkRect2D( + offset: VkOffset2D(x: 0, y: 0), + extent: VkExtent2D(width: frameSize.x, height: frameSize.y) + ) + checkVkResult vkBeginCommandBuffer(commandBuffer, addr(beginInfo)) + block: + vkCmdBeginRenderPass(commandBuffer, addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE) + vkCmdSetViewport(commandBuffer, firstViewport = 0, viewportCount = 1, addr(viewport)) + vkCmdSetScissor(commandBuffer, firstScissor = 0, scissorCount = 1, addr(scissor)) + runPipeline(commandBuffer, pipeline, currentFrame) + vkCmdEndRenderPass(commandBuffer) + checkVkResult vkEndCommandBuffer(commandBuffer) + +proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, pipeline: var RenderPipeline) = + + checkVkResult vkWaitForFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64)) + + var bufferImageIndex: uint32 + let nextImageResult = vkAcquireNextImageKHR( + vulkan.device.device, + vulkan.swapchain.swapchain, + high(uint64), + vulkan.imageAvailableSemaphores[currentFrame], + VkFence(0), + addr(bufferImageIndex) + ) + if nextImageResult == VK_ERROR_OUT_OF_DATE_KHR: + vulkan.frameSize = 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 vkResetFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame])) + + checkVkResult vkResetCommandBuffer(vulkan.device.commandBuffers[currentFrame], VkCommandBufferResetFlags(0)) + vulkan.renderPass.recordCommandBuffer( + pipeline, + vulkan.device.commandBuffers[currentFrame], + vulkan.framebuffers[bufferImageIndex], vulkan.frameSize, + currentFrame + ) + 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.device.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.frameSize = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface) + (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain() + + +func frametime(engine: Engine): auto = + if engine.maxFPS == 0: 0'f + else: 1'f / float32(engine.maxFPS) + +proc run*(engine: var Engine, pipeline: var RenderPipeline, globalUpdate: proc( + engine: var Engine, t, dt: float32)) = + var + currentFrame = 0 + resized = false + lastUpdate = cpuTime() + lastframe = 0'f + + while true: + # process input + engine.input.keysPressed = {} + engine.input.keysReleased = {} + engine.input.mousePressed = {} + engine.input.mouseReleased = {} + var killed = false + for event in engine.window.pendingEvents(): + case event.eventType: + of Quit: + killed = true + of ResizedWindow: + resized = true + of KeyPressed: + engine.input.keysPressed.incl event.key + engine.input.keysDown.incl event.key + of KeyReleased: + engine.input.keysReleased.incl event.key + engine.input.keysDown.excl event.key + of MousePressed: + engine.input.mousePressed.incl event.button + engine.input.mouseDown.incl event.button + of MouseReleased: + engine.input.mouseReleased.incl event.button + engine.input.mouseDown.excl event.button + of MouseMoved: + engine.input.mousePos = Vec2([float32(event.x), float32(event.y)]) + if killed: # at least on windows we should return immediately as swapchain recreation will fail after kill + break + + # game logic update + let + now = cpuTime() + dt = now - lastUpdate + lastUpdate = now + engine.globalUpdate(now, dt) + for thing in allThings(engine.currentscenedata): + for part in thing.parts: + update(part, engine, now, dt) + update(thing, engine, now, dt) + + # submit frame for drawing + if engine.maxFPS == 0 or (now - lastframe >= engine.frametime): # framerate limit + engine.window.drawFrame(engine.vulkan, currentFrame, resized, pipeline) + lastframe = now + currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT + resized = false + + checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device) + +proc trash*(pipeline: var RenderPipeline) = + vkDestroyDescriptorPool(pipeline.device, pipeline.descriptorPool, nil) + vkDestroyDescriptorSetLayout(pipeline.device, pipeline.descriptorSetLayout, nil) + vkDestroyPipeline(pipeline.device, pipeline.pipeline, nil) + vkDestroyPipelineLayout(pipeline.device, pipeline.layout, nil) + for shader in pipeline.shaders: + vkDestroyShaderModule(pipeline.device, shader.shader.module, nil) + + for (bufferset, indexed, indexbuffer, cnt, t) in + pipeline.vertexBuffers.mitems: + if indexed: + indexbuffer.trash() + for buffer in bufferset.mitems: + buffer.trash() + for buffer in pipeline.uniformBuffers.mitems: + buffer.trash() + +proc trash*(engine: var Engine) = + checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device) + engine.vulkan.device.device.trash(engine.vulkan.swapchain, + engine.vulkan.framebuffers) + + 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.vkDestroyRenderPass(engine.vulkan.renderPass, nil) + engine.vulkan.device.device.vkDestroyCommandPool(engine.vulkan.device.commandPool, 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) + engine.window.trash() + # needs to happen after window is trashed as the driver might have a hook registered for the window destruction + engine.vulkan.instance.vkDestroyInstance(nil)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/legacy/glsl_helpers.nim Tue Mar 28 00:20:49 2023 +0700 @@ -0,0 +1,67 @@ +import ./math/vector +import ./math/matrix + +func getGLSLType*[T](): string = + # todo: likely not correct as we would need to enable some + # extensions somewhere (Vulkan/GLSL compiler?) to have + # everything work as intended. Or maybe the GPU driver does + # some automagic conversion stuf.. + when T is uint8: "uint" + elif T is int8: "int" + elif T is uint16: "uint" + elif T is int16: "int" + elif T is uint32: "uint" + elif T is int32: "int" + elif T is uint64: "uint" + elif T is int64: "int" + elif T is float32: "float" + elif T is float64: "double" + + elif T is TVec2[uint8]: "uvec2" + elif T is TVec2[int8]: "ivec2" + elif T is TVec2[uint16]: "uvec2" + elif T is TVec2[int16]: "ivec2" + elif T is TVec2[uint32]: "uvec2" + elif T is TVec2[int32]: "ivec2" + elif T is TVec2[uint64]: "uvec2" + elif T is TVec2[int64]: "ivec2" + elif T is TVec2[float32]: "vec2" + elif T is TVec2[float64]: "dvec2" + + elif T is TVec3[uint8]: "uvec3" + elif T is TVec3[int8]: "ivec3" + elif T is TVec3[uint16]: "uvec3" + elif T is TVec3[int16]: "ivec3" + elif T is TVec3[uint32]: "uvec3" + elif T is TVec3[int32]: "ivec3" + elif T is TVec3[uint64]: "uvec3" + elif T is TVec3[int64]: "ivec3" + elif T is TVec3[float32]: "vec3" + elif T is TVec3[float64]: "dvec3" + + elif T is TVec4[uint8]: "uvec4" + elif T is TVec4[int8]: "ivec4" + elif T is TVec4[uint16]: "uvec4" + elif T is TVec4[int16]: "ivec4" + elif T is TVec4[uint32]: "uvec4" + elif T is TVec4[int32]: "ivec4" + elif T is TVec4[uint64]: "uvec4" + elif T is TVec4[int64]: "ivec4" + elif T is TVec4[float32]: "vec4" + elif T is TVec4[float64]: "dvec4" + + elif T is TMat22[float32]: "mat2" + elif T is TMat23[float32]: "mat32" + elif T is TMat32[float32]: "mat23" + elif T is TMat33[float32]: "mat3" + elif T is TMat34[float32]: "mat43" + elif T is TMat43[float32]: "mat34" + elif T is TMat44[float32]: "mat4" + + elif T is TMat22[float64]: "dmat2" + elif T is TMat23[float64]: "dmat32" + elif T is TMat32[float64]: "dmat23" + elif T is TMat33[float64]: "dmat3" + elif T is TMat34[float64]: "dmat43" + elif T is TMat43[float64]: "dmat34" + elif T is TMat44[float64]: "dmat4"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/legacy/image.nim Tue Mar 28 00:20:49 2023 +0700 @@ -0,0 +1,50 @@ +import ./buffer +import ./math/vector +import ./vulkan +import ./vulkan_helpers + +type + ImageUsage* = enum + TransferDst = VK_IMAGE_USAGE_TRANSFER_DST_BIT + SampledBit = VK_IMAGE_USAGE_SAMPLED_BIT + ImageUsages = set[ImageUsage] + Image = object + buffer: Buffer + image: VkImage + memory: VkDeviceMemory + +proc InitImage(data: var seq[byte], size: TVec2[uint32], format: VkFormat, + tiling: VkImageTiling, usage: ImageUsages, properties: MemoryProperties, + device: VkDevice, + physicalDevice: VkPhysicalDevice): Image = + result.buffer = InitBuffer(device, physicalDevice, uint64(data.len), { + TransferSrc}, {HostVisible, HostCoherent}) + copyMem(result.buffer.data, addr(data[0]), data.len) + + var imageInfo = VkImageCreateInfo( + sType: VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + imageType: VK_IMAGE_TYPE_2D, + extent: VkExtent3D(width: size.x, height: size.y, depth: 1), + mipLevels: 1, + arrayLayers: 1, + format: format, + tiling: tiling, + initialLayout: VK_IMAGE_LAYOUT_UNDEFINED, + usage: cast[VkImageUsageFlags](usage), + sharingMode: VK_SHARING_MODE_EXCLUSIVE, + samples: VK_SAMPLE_COUNT_1_BIT, + ) + checkVkResult vkCreateImage(device, addr(imageInfo), nil, addr(result.image)) + + var memRequirements: VkMemoryRequirements + vkGetImageMemoryRequirements(device, result.image, addr(memRequirements)) + + var allocInfo = VkMemoryAllocateInfo( + sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + allocationSize: memRequirements.size, + memoryTypeIndex: memRequirements.findMemoryType(physicalDevice, properties) + ) + + checkVkResult vkAllocateMemory(device, addr(allocInfo), nil, addr(result.memory)) + checkVkResult vkBindImageMemory(device, result.image, result.memory, + VkDeviceSize(0))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/legacy/shader.nim Tue Mar 28 00:20:49 2023 +0700 @@ -0,0 +1,172 @@ +import std/os +import std/typetraits +import std/hashes +import std/strformat +import std/strutils +import std/tables +import std/compilesettings + +import ./vulkan_helpers +import ./glsl_helpers +import ./vulkan +import ./vertex +import ./descriptor +import ./math/vector + +type + AllowedUniformType = SomeNumber|TVec + UniformSlot *[T: AllowedUniformType] = object + ShaderProgram*[VertexType, Uniforms] = object + entryPoint*: string + programType*: VkShaderStageFlagBits + shader*: VkPipelineShaderStageCreateInfo + uniforms*: Uniforms + +proc staticExecChecked(command: string, input = ""): string {.compileTime.} = + let (output, exitcode) = gorgeEx( + command = command, + input = input) + if exitcode != 0: + raise newException(Exception, &"Running '{command}' produced exit code: {exitcode}" & output) + return output + + +func stage2string(stage: VkShaderStageFlagBits): string {.compileTime.} = + case stage + of VK_SHADER_STAGE_VERTEX_BIT: "vert" + of VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT: "tesc" + of VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT: "tese" + of VK_SHADER_STAGE_GEOMETRY_BIT: "geom" + of VK_SHADER_STAGE_FRAGMENT_BIT: "frag" + of VK_SHADER_STAGE_COMPUTE_BIT: "comp" + else: "" + +proc compileGLSLToSPIRV(stage: static VkShaderStageFlagBits, shaderSource: static string, entrypoint: string): seq[uint32] {.compileTime.} = + when defined(nimcheck): # will not run if nimcheck is running + return result + const + stagename = stage2string(stage) + shaderHash = hash(shaderSource) + # cross compilation for windows workaround, sorry computer + shaderfile = getTempDir() / fmt"shader_{shaderHash}.{stagename}" + projectPath = querySetting(projectPath) + + discard staticExecChecked( + command = fmt"{projectPath}/glslangValidator --entry-point {entrypoint} -V --stdin -S {stagename} -o {shaderfile}", + input = shaderSource + ) + + when defined(mingw) and defined(linux): # required for crosscompilation, path separators get messed up + let shaderbinary = staticRead shaderfile.replace("\\", "/") + else: + let shaderbinary = staticRead shaderfile + when defined(linux): + discard staticExecChecked(command = fmt"rm {shaderfile}") + elif defined(windows): + discard staticExecChecked(command = fmt"cmd.exe /c del {shaderfile}") + else: + raise newException(Exception, "Unsupported operating system") + + var i = 0 + while i < shaderbinary.len: + result.add( + (uint32(shaderbinary[i + 0]) shl 0) or + (uint32(shaderbinary[i + 1]) shl 8) or + (uint32(shaderbinary[i + 2]) shl 16) or + (uint32(shaderbinary[i + 3]) shl 24) + ) + i += 4 + +proc initShaderProgram*[VertexType, Uniforms](device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string = "main"): ShaderProgram[VertexType, Uniforms] = + result.entryPoint = entryPoint + result.programType = programType + + const constcode = compileGLSLToSPIRV(programType, shader, entryPoint) + var code = constcode + var createInfo = VkShaderModuleCreateInfo( + sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + codeSize: uint(code.len * sizeof(uint32)), + pCode: if code.len > 0: addr(code[0]) else: nil, + ) + var shaderModule: VkShaderModule + checkVkResult vkCreateShaderModule(device, addr(createInfo), nil, addr(shaderModule)) + + result.shader = VkPipelineShaderStageCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + stage: programType, + module: shaderModule, + pName: cstring(result.entryPoint), # entry point for shader + ) + +func generateVertexShaderCode*[VertexType, Uniforms]( + shaderBody: static string = "", + entryPoint: static string = "main", + glslVersion: static string = "450" +): string {.compileTime.} = + var lines: seq[string] + lines.add "#version " & glslVersion + lines.add "layout(row_major) uniform;" + lines.add generateGLSLUniformDeclarations[Uniforms]() + lines.add generateGLSLVertexDeclarations[VertexType]() + lines.add "layout(location = 0) out vec4 fragColor;" + lines.add "void " & entryPoint & "() {" + + var viewprojection = "" + + var hasPosition = 0 + var hasColor = 0 + for attrname, value in VertexType().fieldPairs: + when typeof(value) is PositionAttribute: + let glsltype = getGLSLType[getAttributeType(value)]() + lines.add &" {glsltype} in_position = " & attrname & ";" + if getAttributeType(value) is TVec2: + lines.add " vec4 out_position = vec4(in_position, 0.0, 1.0);" + elif getAttributeType(value) is TVec3: + lines.add " vec4 out_position = vec4(in_position, 1.0);" + elif getAttributeType(value) is TVec4: + lines.add " vec4 out_position = in_position;" + hasPosition += 1 + when typeof(value) is ModelTransformAttribute: + lines.add &" out_position = " & attrname & " * out_position;" + when typeof(value) is ColorAttribute: + let glsltype = getGLSLType[getAttributeType(value)]() + lines.add &" {glsltype} in_color = " & attrname & ";" + if getAttributeType(value) is TVec3: + lines.add &" vec4 out_color = vec4(in_color, 1);"; + elif getAttributeType(value) is TVec4: + lines.add &" vec4 out_color = in_color;"; + hasColor += 1 + when not (Uniforms is void): + let uniformBlockName = name(Uniforms).toLower() + for attrname, value in Uniforms().fieldPairs: + when typeof(value) is ViewProjectionTransform: + lines.add "out_position = " & uniformBlockName & "." & attrname & " * out_position;" + + lines.add shaderBody + lines.add " gl_Position = out_position;" + lines.add " fragColor = out_color;" + lines.add "}" + if hasPosition != 1: + raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type PositionAttribute (has {hasPosition})") + if hasColor != 1: + raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type ColorAttribute (has {hasColor})") + return lines.join("\n") + +func generateFragmentShaderCode*[VertexType]( + shaderBody: static string = "", + entryPoint: static string = "main", + glslVersion: static string = "450" +): string {.compileTime.} = + var lines: seq[string] + lines.add "#version " & glslVersion + lines.add "layout(row_major) uniform;" + lines.add "layout(location = 0) in vec4 fragColor;" + lines.add "layout(location = 0) out vec4 outColor;" + lines.add "void " & entryPoint & "() {" + lines.add " vec4 in_color = fragColor;" + lines.add " vec4 out_color = in_color;" + lines.add shaderBody + lines.add " outColor = out_color;" + lines.add "}" + + return lines.join("\n")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/legacy/thing.nim Tue Mar 28 00:20:49 2023 +0700 @@ -0,0 +1,131 @@ +import std/strformat +import std/typetraits + +import ./math/matrix + +type + Part* = ref object of RootObj + thing*: Thing + + Thing* = ref object of RootObj + name*: string + transform*: Mat44 # todo: cache transform + only update VBO when transform changed + parent*: Thing + children*: seq[Thing] + parts*: seq[Part] + + +func `$`*(thing: Thing): string = thing.name +method `$`*(part: Part): string {.base.} = + if part.thing != nil: + &"{part.thing} -> Part" + else: + &"Standalone Part" + +proc add*(thing: Thing, child: Thing) = + child.parent = thing + thing.children.add child +proc add*(thing: Thing, part: Part) = + part.thing = thing + thing.parts.add part +proc add*(thing: Thing, children: seq[Thing]) = + for child in children: + child.parent = thing + thing.children.add child +proc add*(thing: Thing, parts: seq[Part]) = + for part in parts: + part.thing = thing + thing.parts.add part + +func newThing*(name: string = ""): Thing = + result = new Thing + result.name = name + result.transform = Unit44 + if result.name == "": + result.name = &"Thing[{$(cast[ByteAddress](result))}]" + +func newThing*(name: string, firstChild: Thing, children: varargs[ + Thing]): Thing = + result = new Thing + result.add firstChild + for child in children: + result.add child + result.name = name + result.transform = Unit44 + if result.name == "": + result.name = &"Thing[{$(cast[ByteAddress](result))}]" + +proc newThing*(name: string, firstPart: Part, parts: varargs[Part]): Thing = + result = new Thing + result.name = name + result.add firstPart + for part in parts: + result.add part + if result.name == "": + result.name = &"Thing[{$(cast[ByteAddress](result))}]" + result.transform = Unit44 + +func getModelTransform*(thing: Thing): Mat44 = + result = Unit44 + var currentThing = thing + while currentThing != nil: + result = currentThing.transform * result + currentThing = currentThing.parent + +iterator allPartsOfType*[T: Part](root: Thing): T = + var queue = @[root] + while queue.len > 0: + let thing = queue.pop + for part in thing.parts: + if part of T: + yield T(part) + for i in countdown(thing.children.len - 1, 0): + queue.add thing.children[i] + +func firstWithName*(root: Thing, name: string): Thing = + var queue = @[root] + while queue.len > 0: + let next = queue.pop + for child in next.children: + if child.name == name: + return child + queue.add child + +func firstPartWithName*[T: Part](root: Thing, name: string): T = + var queue = @[root] + while queue.len > 0: + let next = queue.pop + for child in next.children: + if child.name == name: + for part in child.parts: + if part of T: + return T(part) + queue.add child + +func allWithName*(root: Thing, name: string): seq[Thing] = + var queue = @[root] + while queue.len > 0: + let next = queue.pop + for child in next.children: + if child.name == name: + result.add child + queue.add child + +func allPartsWithName*[T: Part](root: Thing, name: string): seq[T] = + var queue = @[root] + while queue.len > 0: + let next = queue.pop + for child in next.children: + if child.name == name: + for part in child.parts: + if part of T: + result.add T(part) + queue.add child + +iterator allThings*(root: Thing): Thing = + var queue = @[root] + while queue.len > 0: + let next = queue.pop + for child in next.children: + queue.add child + yield next
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/legacy/vertex.nim Tue Mar 28 00:20:49 2023 +0700 @@ -0,0 +1,181 @@ +import std/options +import std/macros +import std/strutils +import std/strformat +import std/typetraits + +import ./math/vector +import ./math/matrix +import ./vulkan +import ./buffer +import ./glsl_helpers + +type + VertexAttributeType = SomeNumber|TVec|TMat + # useOnDeviceMemory can be used to make sure the attribute buffer memory will + # be on the device. Data will be faster to access but much slower to update + GenericAttribute*[T: VertexAttributeType] = object + data*: seq[T] + buffer*: Buffer + useOnDeviceMemory*: bool + PositionAttribute*[T: TVec] = object + data*: seq[T] + buffer*: Buffer + useOnDeviceMemory*: bool + ColorAttribute*[T: TVec] = object + data*: seq[T] + buffer*: Buffer + useOnDeviceMemory*: bool + GenericInstanceAttribute*[T: VertexAttributeType] = object + data*: seq[T] + buffer*: Buffer + useOnDeviceMemory*: bool + ModelTransformAttribute* = GenericInstanceAttribute[Mat44] + InstanceAttribute* = GenericInstanceAttribute|ModelTransformAttribute + VertexAttribute* = GenericAttribute|PositionAttribute|ColorAttribute|InstanceAttribute + +template getAttributeType*(v: VertexAttribute): auto = get(genericParams(typeof(v)), 0) + +func datasize*(attribute: VertexAttribute): uint64 = + uint64(sizeof(getAttributeType(attribute))) * uint64(attribute.data.len) + +# from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html +func nLocationSlots[T: VertexAttributeType](): int = + when (T is TVec3[float64] or T is TVec3[uint64] or T is TVec4[float64] or + T is TVec4[float64]): + 2 + elif T is SomeNumber or T is TVec: + 1 + else: + {.error: "Unsupported vertex attribute type".} + +# numbers +func getVkFormat[T: VertexAttributeType](): VkFormat = + when T is uint8: VK_FORMAT_R8_UINT + elif T is int8: VK_FORMAT_R8_SINT + elif T is uint16: VK_FORMAT_R16_UINT + elif T is int16: VK_FORMAT_R16_SINT + elif T is uint32: VK_FORMAT_R32_UINT + elif T is int32: VK_FORMAT_R32_SINT + elif T is uint64: VK_FORMAT_R64_UINT + elif T is int64: VK_FORMAT_R64_SINT + elif T is float32: VK_FORMAT_R32_SFLOAT + elif T is float64: VK_FORMAT_R64_SFLOAT + elif T is TVec2[uint8]: VK_FORMAT_R8G8_UINT + elif T is TVec2[int8]: VK_FORMAT_R8G8_SINT + elif T is TVec2[uint16]: VK_FORMAT_R16G16_UINT + elif T is TVec2[int16]: VK_FORMAT_R16G16_SINT + elif T is TVec2[uint32]: VK_FORMAT_R32G32_UINT + elif T is TVec2[int32]: VK_FORMAT_R32G32_SINT + elif T is TVec2[uint64]: VK_FORMAT_R64G64_UINT + elif T is TVec2[int64]: VK_FORMAT_R64G64_SINT + elif T is TVec2[float32]: VK_FORMAT_R32G32_SFLOAT + elif T is TVec2[float64]: VK_FORMAT_R64G64_SFLOAT + elif T is TVec3[uint8]: VK_FORMAT_R8G8B8_UINT + elif T is TVec3[int8]: VK_FORMAT_R8G8B8_SINT + elif T is TVec3[uint16]: VK_FORMAT_R16G16B16_UINT + elif T is TVec3[int16]: VK_FORMAT_R16G16B16_SINT + elif T is TVec3[uint32]: VK_FORMAT_R32G32B32_UINT + elif T is TVec3[int32]: VK_FORMAT_R32G32B32_SINT + elif T is TVec3[uint64]: VK_FORMAT_R64G64B64_UINT + elif T is TVec3[int64]: VK_FORMAT_R64G64B64_SINT + elif T is TVec3[float32]: VK_FORMAT_R32G32B32_SFLOAT + elif T is TVec3[float64]: VK_FORMAT_R64G64B64_SFLOAT + elif T is TVec4[uint8]: VK_FORMAT_R8G8B8A8_UINT + elif T is TVec4[int8]: VK_FORMAT_R8G8B8A8_SINT + elif T is TVec4[uint16]: VK_FORMAT_R16G16B16A16_UINT + elif T is TVec4[int16]: VK_FORMAT_R16G16B16A16_SINT + elif T is TVec4[uint32]: VK_FORMAT_R32G32B32A32_UINT + elif T is TVec4[int32]: VK_FORMAT_R32G32B32A32_SINT + elif T is TVec4[uint64]: VK_FORMAT_R64G64B64A64_UINT + elif T is TVec4[int64]: VK_FORMAT_R64G64B64A64_SINT + elif T is TVec4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT + elif T is TVec4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT + else: {.error: "Unsupported vertex attribute type".} + + +func VertexCount*[T](t: T): uint32 = + for name, value in t.fieldPairs: + when typeof(value) is VertexAttribute and not (typeof( + value) is InstanceAttribute): + if result == 0: + result = uint32(value.data.len) + else: + assert result == uint32(value.data.len) + + +func hasAttributeType*[T, AT](t: T): uint32 = + for name, value in t.fieldPairs: + when typeof(value) is AT: + return true + return false + +# helper function to make sure matrices are correctly +# divided into multiple vector attributes +template compositeAttributeType[T]() = + when T is TMat33[float32]: + Vec3 + elif T is TMat44[float32]: + Vec4 + else: + T +func compositeAttributesNumber[T](): int = + when T is TMat33[float32]: + 3 + elif T is TMat44[float32]: + 4 + else: + 1 + + +func generateGLSLVertexDeclarations*[T](): string = + var stmtList: seq[string] + var i = 0 + for name, value in T().fieldPairs: + when typeof(value) is VertexAttribute: + let glsltype = getGLSLType[getAttributeType(value)]() + let n = name + stmtList.add(&"layout(location = {i}) in {glsltype} {n};") + for j in 0 ..< compositeAttributesNumber[getAttributeType(value)](): + i += nLocationSlots[compositeAttributeType(getAttributeType(value))]() + + return stmtList.join("\n") + + +func generateInputVertexBinding*[T](bindingoffset: int = 0, locationoffset: int = 0): seq[VkVertexInputBindingDescription] = + # packed attribute data, not interleaved (aks "struct of arrays") + var binding = bindingoffset + for name, value in T().fieldPairs: + when typeof(value) is InstanceAttribute: + let inputRate = VK_VERTEX_INPUT_RATE_INSTANCE + elif typeof(value) is VertexAttribute: + let inputRate = VK_VERTEX_INPUT_RATE_VERTEX + else: + {.error: "Unsupported vertex attribute type".} + result.add( + VkVertexInputBindingDescription( + binding: uint32(binding), + stride: uint32(sizeof(getAttributeType(value))), + inputRate: inputRate, + ) + ) + binding += 1 + + +func generateInputAttributeBinding*[T](bindingoffset: int = 0, locationoffset: int = 0): seq[VkVertexInputAttributeDescription] = + # TODO: + var location = 0 + var binding = bindingoffset + for name, value in T().fieldPairs: + when typeof(value) is VertexAttribute: + for i in 0 ..< compositeAttributesNumber[getAttributeType(value)](): + result.add( + VkVertexInputAttributeDescription( + binding: uint32(binding), + location: uint32(location), + format: getVkFormat[compositeAttributeType(getAttributeType(value))](), + offset: uint32(i * sizeof(compositeAttributeType(getAttributeType(value)))), + ) + ) + location += nLocationSlots[compositeAttributeType(getAttributeType(value))]() + binding += 1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/legacy/vulkan_helpers.nim Tue Mar 28 00:20:49 2023 +0700 @@ -0,0 +1,215 @@ +import std/tables +import std/strutils +import std/strformat +import std/logging +import std/macros + +import ./vulkan +import ./window + +# the included code need checkVkResult, therefore having the template above +when defined(linux): + include ./platform/linux/vulkan +when defined(windows): + include ./platform/windows/vulkan + +const ENABLEVULKANVALIDATIONLAYERS* = not defined(release) + +func addrOrNil[T](obj: var openArray[T]): ptr T = + if obj.len > 0: addr(obj[0]) else: nil + +func filterForSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): seq[VkSurfaceFormatKHR] = + for format in formats: + if format.format == VK_FORMAT_B8G8R8A8_SRGB and format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: + result.add(format) + +func getSuitableSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): VkSurfaceFormatKHR = + let usableSurfaceFormats = filterForSurfaceFormat(formats) + if len(usableSurfaceFormats) == 0: + raise newException(Exception, "No suitable surface formats found") + return usableSurfaceFormats[0] + + +func cleanString*(str: openArray[char]): string = + for i in 0 ..< len(str): + if str[i] == char(0): + result = join(str[0 ..< i]) + break + +proc getInstanceExtensions*(): seq[string] = + var extensionCount: uint32 + checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr( extensionCount), nil) + var extensions = newSeq[VkExtensionProperties](extensionCount) + checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr( extensionCount), addrOrNil(extensions)) + + for extension in extensions: + result.add(cleanString(extension.extensionName)) + + +proc getDeviceExtensions*(device: VkPhysicalDevice): seq[string] = + var extensionCount: uint32 + checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr( + extensionCount), nil) + var extensions = newSeq[VkExtensionProperties](extensionCount) + checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr( + extensionCount), addrOrNil(extensions)) + + for extension in extensions: + result.add(cleanString(extension.extensionName)) + + +proc getValidationLayers*(): seq[string] = + var n_layers: uint32 + checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), nil) + var layers = newSeq[VkLayerProperties](n_layers) + checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), addrOrNil(layers)) + + for layer in layers: + result.add(cleanString(layer.layerName)) + + +proc getVulkanPhysicalDevices*(instance: VkInstance): seq[VkPhysicalDevice] = + var n_devices: uint32 + checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), nil) + result = newSeq[VkPhysicalDevice](n_devices) + checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), addrOrNil(result)) + + +proc getQueueFamilies*(device: VkPhysicalDevice): seq[VkQueueFamilyProperties] = + var n_queuefamilies: uint32 + vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), nil) + result = newSeq[VkQueueFamilyProperties](n_queuefamilies) + vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), addrOrNil(result)) + + +proc getDeviceSurfaceFormats*(device: VkPhysicalDevice, surface: VkSurfaceKHR): seq[VkSurfaceFormatKHR] = + var n_formats: uint32 + checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), nil) + result = newSeq[VkSurfaceFormatKHR](n_formats) + checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), addrOrNil(result)) + + +proc getDeviceSurfacePresentModes*(device: VkPhysicalDevice, + surface: VkSurfaceKHR): seq[VkPresentModeKHR] = + var n_modes: uint32 + checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr( + n_modes), nil) + result = newSeq[VkPresentModeKHR](n_modes) + checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr( + n_modes), addrOrNil(result)) + + +proc getSwapChainImages*(device: VkDevice, swapChain: VkSwapchainKHR): seq[VkImage] = + var n_images: uint32 + checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), nil) + result = newSeq[VkImage](n_images) + checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), addr( + result[0])) + + +func getPresentMode*(modes: seq[VkPresentModeKHR]): VkPresentModeKHR = + let preferredModes = [ + VK_PRESENT_MODE_MAILBOX_KHR, # triple buffering + VK_PRESENT_MODE_FIFO_RELAXED_KHR, # double duffering + VK_PRESENT_MODE_FIFO_KHR, # double duffering + VK_PRESENT_MODE_IMMEDIATE_KHR, # single buffering + ] + for preferredMode in preferredModes: + for mode in modes: + if preferredMode == mode: + return mode + # should never be reached, but seems to be garuanteed by vulkan specs to always be available + return VK_PRESENT_MODE_FIFO_KHR + + +proc createVulkanInstance*(vulkanVersion: uint32): VkInstance = + + var requiredExtensions = @["VK_KHR_surface".cstring] & REQUIRED_PLATFORM_EXTENSIONS + when ENABLEVULKANVALIDATIONLAYERS: + requiredExtensions.add("VK_EXT_debug_utils".cstring) + + let availableExtensions = getInstanceExtensions() + for extension in requiredExtensions: + assert $extension in availableExtensions, $extension + + let availableLayers = getValidationLayers() + var usableLayers = newSeq[cstring]() + + when ENABLEVULKANVALIDATIONLAYERS: + const desiredLayers = ["VK_LAYER_KHRONOS_validation".cstring, "VK_LAYER_MESA_overlay".cstring] + else: + const desiredLayers: array[0, string] = [] + for layer in desiredLayers: + if $layer in availableLayers: + usableLayers.add(layer) + + echo "Available validation layers: ", availableLayers + echo "Using validation layers: ", usableLayers + echo "Available extensions: ", availableExtensions + echo "Using instance extensions: ", requiredExtensions + + var appinfo = VkApplicationInfo( + sType: VK_STRUCTURE_TYPE_APPLICATION_INFO, + pApplicationName: "Hello Triangle", + pEngineName: "Custom engine", + apiVersion: vulkanVersion, + ) + var createinfo = VkInstanceCreateInfo( + sType: VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + pApplicationInfo: addr(appinfo), + enabledLayerCount: usableLayers.len.uint32, + ppEnabledLayerNames: cast[ptr UncheckedArray[cstring]](addrOrNil( + usableLayers)), + enabledExtensionCount: requiredExtensions.len.uint32, + ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr( + requiredExtensions[0])) + ) + checkVkResult vkCreateInstance(addr(createinfo), nil, addr(result)) + let other_extensions = @["VK_KHR_swapchain".cstring] + for extension in requiredExtensions & other_extensions: + result.loadExtension($extension) + +proc getVulcanDevice*( + physicalDevice: var VkPhysicalDevice, + features: var VkPhysicalDeviceFeatures, + graphicsQueueFamily: uint32, + presentationQueueFamily: uint32, +): (VkDevice, VkQueue, VkQueue) = + # setup queue and device + # TODO: need check this, possibly wrong logic, see Vulkan tutorial + var priority = 1.0'f32 + var queueCreateInfo = [ + VkDeviceQueueCreateInfo( + sType: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + queueFamilyIndex: graphicsQueueFamily, + queueCount: 1, + pQueuePriorities: addr(priority), + ), + ] + + var requiredExtensions = ["VK_KHR_swapchain".cstring] + var deviceCreateInfo = VkDeviceCreateInfo( + sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + queueCreateInfoCount: uint32(queueCreateInfo.len), + pQueueCreateInfos: addrOrNil(queueCreateInfo), + pEnabledFeatures: addr(features), + enabledExtensionCount: requiredExtensions.len.uint32, + ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(requiredExtensions)) + ) + checkVkResult vkCreateDevice(physicalDevice, addr(deviceCreateInfo), nil, + addr(result[0])) + vkGetDeviceQueue(result[0], graphicsQueueFamily, 0'u32, addr(result[1])) + vkGetDeviceQueue(result[0], presentationQueueFamily, 0'u32, addr(result[2])) + +proc debugCallback*( + messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT, + messageTypes: VkDebugUtilsMessageTypeFlagsEXT, + pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT, + userData: pointer +): VkBool32 {.cdecl.} = + echo &"{messageSeverity}: {VkDebugUtilsMessageTypeFlagBitsEXT(messageTypes)}: {pCallbackData.pMessage}" + return false + +proc getSurfaceCapabilities*(device: VkPhysicalDevice, + surface: VkSurfaceKHR): VkSurfaceCapabilitiesKHR = + checkVkResult device.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(surface, addr(result))
--- a/src/semicongine/mesh.nim Mon Mar 27 21:01:32 2023 +0700 +++ b/src/semicongine/mesh.nim Tue Mar 28 00:20:49 2023 +0700 @@ -5,7 +5,6 @@ import ./vulkan import ./thing import ./buffer -import ./vertex import ./math/vector type @@ -23,10 +22,9 @@ var indexoffset = U(0) for mesh in meshes: for srcname, srcvalue in mesh.vertexData.fieldPairs: - when typeof(srcvalue) is VertexAttribute: - for dstname, dstvalue in result.vertexData.fieldPairs: - when srcname == dstname: - dstvalue.data.add srcvalue.data + for dstname, dstvalue in result.vertexData.fieldPairs: + when srcname == dstname: + dstvalue.data.add srcvalue.data var indexdata: seq[array[3, U]] for i in mesh.indices: indexdata.add [i[0] + indexoffset, i[1] + indexoffset, i[2] + indexoffset] @@ -47,24 +45,23 @@ ): (seq[Buffer], uint32) = result[1] = mesh.vertexData.VertexCount for name, value in mesh.vertexData.fieldPairs: - when typeof(value) is VertexAttribute: - assert value.data.len > 0 - var flags = if value.useOnDeviceMemory: {TransferSrc} else: {VertexBuffer} - var stagingBuffer = device.InitBuffer(physicalDevice, value.datasize, - flags, {HostVisible, HostCoherent}) - copyMem(stagingBuffer.data, addr(value.data[0]), value.datasize) + assert value.data.len > 0 + var flags = if value.useOnDeviceMemory: {TransferSrc} else: {VertexBuffer} + var stagingBuffer = device.InitBuffer(physicalDevice, value.datasize, + flags, {HostVisible, HostCoherent}) + copyMem(stagingBuffer.data, addr(value.data[0]), value.datasize) - if value.useOnDeviceMemory: - var finalBuffer = device.InitBuffer(physicalDevice, value.datasize, { - TransferDst, VertexBuffer}, {DeviceLocal}) - transferBuffer(commandPool, queue, stagingBuffer, finalBuffer, - value.datasize) - stagingBuffer.trash() - result[0].add(finalBuffer) - value.buffer = finalBuffer - else: - result[0].add(stagingBuffer) - value.buffer = stagingBuffer + if value.useOnDeviceMemory: + var finalBuffer = device.InitBuffer(physicalDevice, value.datasize, { + TransferDst, VertexBuffer}, {DeviceLocal}) + transferBuffer(commandPool, queue, stagingBuffer, finalBuffer, + value.datasize) + stagingBuffer.trash() + result[0].add(finalBuffer) + value.buffer = finalBuffer + else: + result[0].add(stagingBuffer) + value.buffer = stagingBuffer proc createIndexBuffer*( mesh: Mesh,
--- a/src/semicongine/shader.nim Mon Mar 27 21:01:32 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,172 +0,0 @@ -import std/os -import std/typetraits -import std/hashes -import std/strformat -import std/strutils -import std/tables -import std/compilesettings - -import ./vulkan_helpers -import ./glsl_helpers -import ./vulkan -import ./vertex -import ./descriptor -import ./math/vector - -type - AllowedUniformType = SomeNumber|TVec - UniformSlot *[T: AllowedUniformType] = object - ShaderProgram*[VertexType, Uniforms] = object - entryPoint*: string - programType*: VkShaderStageFlagBits - shader*: VkPipelineShaderStageCreateInfo - uniforms*: Uniforms - -proc staticExecChecked(command: string, input = ""): string {.compileTime.} = - let (output, exitcode) = gorgeEx( - command = command, - input = input) - if exitcode != 0: - raise newException(Exception, &"Running '{command}' produced exit code: {exitcode}" & output) - return output - - -func stage2string(stage: VkShaderStageFlagBits): string {.compileTime.} = - case stage - of VK_SHADER_STAGE_VERTEX_BIT: "vert" - of VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT: "tesc" - of VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT: "tese" - of VK_SHADER_STAGE_GEOMETRY_BIT: "geom" - of VK_SHADER_STAGE_FRAGMENT_BIT: "frag" - of VK_SHADER_STAGE_COMPUTE_BIT: "comp" - else: "" - -proc compileGLSLToSPIRV(stage: static VkShaderStageFlagBits, shaderSource: static string, entrypoint: string): seq[uint32] {.compileTime.} = - when defined(nimcheck): # will not run if nimcheck is running - return result - const - stagename = stage2string(stage) - shaderHash = hash(shaderSource) - # cross compilation for windows workaround, sorry computer - shaderfile = getTempDir() / fmt"shader_{shaderHash}.{stagename}" - projectPath = querySetting(projectPath) - - discard staticExecChecked( - command = fmt"{projectPath}/glslangValidator --entry-point {entrypoint} -V --stdin -S {stagename} -o {shaderfile}", - input = shaderSource - ) - - when defined(mingw) and defined(linux): # required for crosscompilation, path separators get messed up - let shaderbinary = staticRead shaderfile.replace("\\", "/") - else: - let shaderbinary = staticRead shaderfile - when defined(linux): - discard staticExecChecked(command = fmt"rm {shaderfile}") - elif defined(windows): - discard staticExecChecked(command = fmt"cmd.exe /c del {shaderfile}") - else: - raise newException(Exception, "Unsupported operating system") - - var i = 0 - while i < shaderbinary.len: - result.add( - (uint32(shaderbinary[i + 0]) shl 0) or - (uint32(shaderbinary[i + 1]) shl 8) or - (uint32(shaderbinary[i + 2]) shl 16) or - (uint32(shaderbinary[i + 3]) shl 24) - ) - i += 4 - -proc initShaderProgram*[VertexType, Uniforms](device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string = "main"): ShaderProgram[VertexType, Uniforms] = - result.entryPoint = entryPoint - result.programType = programType - - const constcode = compileGLSLToSPIRV(programType, shader, entryPoint) - var code = constcode - var createInfo = VkShaderModuleCreateInfo( - sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - codeSize: uint(code.len * sizeof(uint32)), - pCode: if code.len > 0: addr(code[0]) else: nil, - ) - var shaderModule: VkShaderModule - checkVkResult vkCreateShaderModule(device, addr(createInfo), nil, addr(shaderModule)) - - result.shader = VkPipelineShaderStageCreateInfo( - sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - stage: programType, - module: shaderModule, - pName: cstring(result.entryPoint), # entry point for shader - ) - -func generateVertexShaderCode*[VertexType, Uniforms]( - shaderBody: static string = "", - entryPoint: static string = "main", - glslVersion: static string = "450" -): string {.compileTime.} = - var lines: seq[string] - lines.add "#version " & glslVersion - lines.add "layout(row_major) uniform;" - lines.add generateGLSLUniformDeclarations[Uniforms]() - lines.add generateGLSLVertexDeclarations[VertexType]() - lines.add "layout(location = 0) out vec4 fragColor;" - lines.add "void " & entryPoint & "() {" - - var viewprojection = "" - - var hasPosition = 0 - var hasColor = 0 - for attrname, value in VertexType().fieldPairs: - when typeof(value) is PositionAttribute: - let glsltype = getGLSLType[getAttributeType(value)]() - lines.add &" {glsltype} in_position = " & attrname & ";" - if getAttributeType(value) is TVec2: - lines.add " vec4 out_position = vec4(in_position, 0.0, 1.0);" - elif getAttributeType(value) is TVec3: - lines.add " vec4 out_position = vec4(in_position, 1.0);" - elif getAttributeType(value) is TVec4: - lines.add " vec4 out_position = in_position;" - hasPosition += 1 - when typeof(value) is ModelTransformAttribute: - lines.add &" out_position = " & attrname & " * out_position;" - when typeof(value) is ColorAttribute: - let glsltype = getGLSLType[getAttributeType(value)]() - lines.add &" {glsltype} in_color = " & attrname & ";" - if getAttributeType(value) is TVec3: - lines.add &" vec4 out_color = vec4(in_color, 1);"; - elif getAttributeType(value) is TVec4: - lines.add &" vec4 out_color = in_color;"; - hasColor += 1 - when not (Uniforms is void): - let uniformBlockName = name(Uniforms).toLower() - for attrname, value in Uniforms().fieldPairs: - when typeof(value) is ViewProjectionTransform: - lines.add "out_position = " & uniformBlockName & "." & attrname & " * out_position;" - - lines.add shaderBody - lines.add " gl_Position = out_position;" - lines.add " fragColor = out_color;" - lines.add "}" - if hasPosition != 1: - raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type PositionAttribute (has {hasPosition})") - if hasColor != 1: - raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type ColorAttribute (has {hasColor})") - return lines.join("\n") - -func generateFragmentShaderCode*[VertexType]( - shaderBody: static string = "", - entryPoint: static string = "main", - glslVersion: static string = "450" -): string {.compileTime.} = - var lines: seq[string] - lines.add "#version " & glslVersion - lines.add "layout(row_major) uniform;" - lines.add "layout(location = 0) in vec4 fragColor;" - lines.add "layout(location = 0) out vec4 outColor;" - lines.add "void " & entryPoint & "() {" - lines.add " vec4 in_color = fragColor;" - lines.add " vec4 out_color = in_color;" - lines.add shaderBody - lines.add " outColor = out_color;" - lines.add "}" - - return lines.join("\n")
--- a/src/semicongine/thing.nim Mon Mar 27 21:01:32 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -import std/strformat -import std/typetraits - -import ./math/matrix - -type - Part* = ref object of RootObj - thing*: Thing - - Thing* = ref object of RootObj - name*: string - transform*: Mat44 # todo: cache transform + only update VBO when transform changed - parent*: Thing - children*: seq[Thing] - parts*: seq[Part] - - -func `$`*(thing: Thing): string = thing.name -method `$`*(part: Part): string {.base.} = - if part.thing != nil: - &"{part.thing} -> Part" - else: - &"Standalone Part" - -proc add*(thing: Thing, child: Thing) = - child.parent = thing - thing.children.add child -proc add*(thing: Thing, part: Part) = - part.thing = thing - thing.parts.add part -proc add*(thing: Thing, children: seq[Thing]) = - for child in children: - child.parent = thing - thing.children.add child -proc add*(thing: Thing, parts: seq[Part]) = - for part in parts: - part.thing = thing - thing.parts.add part - -func newThing*(name: string = ""): Thing = - result = new Thing - result.name = name - result.transform = Unit44 - if result.name == "": - result.name = &"Thing[{$(cast[ByteAddress](result))}]" -func newThing*(name: string, firstChild: Thing, children: varargs[ - Thing]): Thing = - result = new Thing - result.add firstChild - for child in children: - result.add child - result.name = name - result.transform = Unit44 - if result.name == "": - result.name = &"Thing[{$(cast[ByteAddress](result))}]" -proc newThing*(name: string, firstPart: Part, parts: varargs[Part]): Thing = - result = new Thing - result.name = name - result.add firstPart - for part in parts: - result.add part - if result.name == "": - result.name = &"Thing[{$(cast[ByteAddress](result))}]" - result.transform = Unit44 - -func getModelTransform*(thing: Thing): Mat44 = - result = Unit44 - var currentThing = thing - while currentThing != nil: - result = currentThing.transform * result - currentThing = currentThing.parent - -iterator allPartsOfType*[T: Part](root: Thing): T = - var queue = @[root] - while queue.len > 0: - let thing = queue.pop - for part in thing.parts: - if part of T: - yield T(part) - for i in countdown(thing.children.len - 1, 0): - queue.add thing.children[i] - -func firstWithName*(root: Thing, name: string): Thing = - var queue = @[root] - while queue.len > 0: - let next = queue.pop - for child in next.children: - if child.name == name: - return child - queue.add child - -func firstPartWithName*[T: Part](root: Thing, name: string): T = - var queue = @[root] - while queue.len > 0: - let next = queue.pop - for child in next.children: - if child.name == name: - for part in child.parts: - if part of T: - return T(part) - queue.add child - -func allWithName*(root: Thing, name: string): seq[Thing] = - var queue = @[root] - while queue.len > 0: - let next = queue.pop - for child in next.children: - if child.name == name: - result.add child - queue.add child - -func allPartsWithName*[T: Part](root: Thing, name: string): seq[T] = - var queue = @[root] - while queue.len > 0: - let next = queue.pop - for child in next.children: - if child.name == name: - for part in child.parts: - if part of T: - result.add T(part) - queue.add child - -iterator allThings*(root: Thing): Thing = - var queue = @[root] - while queue.len > 0: - let next = queue.pop - for child in next.children: - queue.add child - yield next
--- a/src/semicongine/vertex.nim Mon Mar 27 21:01:32 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,181 +0,0 @@ -import std/options -import std/macros -import std/strutils -import std/strformat -import std/typetraits - -import ./math/vector -import ./math/matrix -import ./vulkan -import ./buffer -import ./glsl_helpers - -type - VertexAttributeType = SomeNumber|TVec|TMat - # useOnDeviceMemory can be used to make sure the attribute buffer memory will - # be on the device. Data will be faster to access but much slower to update - GenericAttribute*[T: VertexAttributeType] = object - data*: seq[T] - buffer*: Buffer - useOnDeviceMemory*: bool - PositionAttribute*[T: TVec] = object - data*: seq[T] - buffer*: Buffer - useOnDeviceMemory*: bool - ColorAttribute*[T: TVec] = object - data*: seq[T] - buffer*: Buffer - useOnDeviceMemory*: bool - GenericInstanceAttribute*[T: VertexAttributeType] = object - data*: seq[T] - buffer*: Buffer - useOnDeviceMemory*: bool - ModelTransformAttribute* = GenericInstanceAttribute[Mat44] - InstanceAttribute* = GenericInstanceAttribute|ModelTransformAttribute - VertexAttribute* = GenericAttribute|PositionAttribute|ColorAttribute|InstanceAttribute - -template getAttributeType*(v: VertexAttribute): auto = get(genericParams(typeof(v)), 0) - -func datasize*(attribute: VertexAttribute): uint64 = - uint64(sizeof(getAttributeType(attribute))) * uint64(attribute.data.len) - -# from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html -func nLocationSlots[T: VertexAttributeType](): int = - when (T is TVec3[float64] or T is TVec3[uint64] or T is TVec4[float64] or - T is TVec4[float64]): - 2 - elif T is SomeNumber or T is TVec: - 1 - else: - {.error: "Unsupported vertex attribute type".} - -# numbers -func getVkFormat[T: VertexAttributeType](): VkFormat = - when T is uint8: VK_FORMAT_R8_UINT - elif T is int8: VK_FORMAT_R8_SINT - elif T is uint16: VK_FORMAT_R16_UINT - elif T is int16: VK_FORMAT_R16_SINT - elif T is uint32: VK_FORMAT_R32_UINT - elif T is int32: VK_FORMAT_R32_SINT - elif T is uint64: VK_FORMAT_R64_UINT - elif T is int64: VK_FORMAT_R64_SINT - elif T is float32: VK_FORMAT_R32_SFLOAT - elif T is float64: VK_FORMAT_R64_SFLOAT - elif T is TVec2[uint8]: VK_FORMAT_R8G8_UINT - elif T is TVec2[int8]: VK_FORMAT_R8G8_SINT - elif T is TVec2[uint16]: VK_FORMAT_R16G16_UINT - elif T is TVec2[int16]: VK_FORMAT_R16G16_SINT - elif T is TVec2[uint32]: VK_FORMAT_R32G32_UINT - elif T is TVec2[int32]: VK_FORMAT_R32G32_SINT - elif T is TVec2[uint64]: VK_FORMAT_R64G64_UINT - elif T is TVec2[int64]: VK_FORMAT_R64G64_SINT - elif T is TVec2[float32]: VK_FORMAT_R32G32_SFLOAT - elif T is TVec2[float64]: VK_FORMAT_R64G64_SFLOAT - elif T is TVec3[uint8]: VK_FORMAT_R8G8B8_UINT - elif T is TVec3[int8]: VK_FORMAT_R8G8B8_SINT - elif T is TVec3[uint16]: VK_FORMAT_R16G16B16_UINT - elif T is TVec3[int16]: VK_FORMAT_R16G16B16_SINT - elif T is TVec3[uint32]: VK_FORMAT_R32G32B32_UINT - elif T is TVec3[int32]: VK_FORMAT_R32G32B32_SINT - elif T is TVec3[uint64]: VK_FORMAT_R64G64B64_UINT - elif T is TVec3[int64]: VK_FORMAT_R64G64B64_SINT - elif T is TVec3[float32]: VK_FORMAT_R32G32B32_SFLOAT - elif T is TVec3[float64]: VK_FORMAT_R64G64B64_SFLOAT - elif T is TVec4[uint8]: VK_FORMAT_R8G8B8A8_UINT - elif T is TVec4[int8]: VK_FORMAT_R8G8B8A8_SINT - elif T is TVec4[uint16]: VK_FORMAT_R16G16B16A16_UINT - elif T is TVec4[int16]: VK_FORMAT_R16G16B16A16_SINT - elif T is TVec4[uint32]: VK_FORMAT_R32G32B32A32_UINT - elif T is TVec4[int32]: VK_FORMAT_R32G32B32A32_SINT - elif T is TVec4[uint64]: VK_FORMAT_R64G64B64A64_UINT - elif T is TVec4[int64]: VK_FORMAT_R64G64B64A64_SINT - elif T is TVec4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT - elif T is TVec4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT - else: {.error: "Unsupported vertex attribute type".} - - -func VertexCount*[T](t: T): uint32 = - for name, value in t.fieldPairs: - when typeof(value) is VertexAttribute and not (typeof( - value) is InstanceAttribute): - if result == 0: - result = uint32(value.data.len) - else: - assert result == uint32(value.data.len) - - -func hasAttributeType*[T, AT](t: T): uint32 = - for name, value in t.fieldPairs: - when typeof(value) is AT: - return true - return false - -# helper function to make sure matrices are correctly -# divided into multiple vector attributes -template compositeAttributeType[T]() = - when T is TMat33[float32]: - Vec3 - elif T is TMat44[float32]: - Vec4 - else: - T -func compositeAttributesNumber[T](): int = - when T is TMat33[float32]: - 3 - elif T is TMat44[float32]: - 4 - else: - 1 - - -func generateGLSLVertexDeclarations*[T](): string = - var stmtList: seq[string] - var i = 0 - for name, value in T().fieldPairs: - when typeof(value) is VertexAttribute: - let glsltype = getGLSLType[getAttributeType(value)]() - let n = name - stmtList.add(&"layout(location = {i}) in {glsltype} {n};") - for j in 0 ..< compositeAttributesNumber[getAttributeType(value)](): - i += nLocationSlots[compositeAttributeType(getAttributeType(value))]() - - return stmtList.join("\n") - - -func generateInputVertexBinding*[T](bindingoffset: int = 0, locationoffset: int = 0): seq[VkVertexInputBindingDescription] = - # packed attribute data, not interleaved (aks "struct of arrays") - var binding = bindingoffset - for name, value in T().fieldPairs: - when typeof(value) is InstanceAttribute: - let inputRate = VK_VERTEX_INPUT_RATE_INSTANCE - elif typeof(value) is VertexAttribute: - let inputRate = VK_VERTEX_INPUT_RATE_VERTEX - else: - {.error: "Unsupported vertex attribute type".} - result.add( - VkVertexInputBindingDescription( - binding: uint32(binding), - stride: uint32(sizeof(getAttributeType(value))), - inputRate: inputRate, - ) - ) - binding += 1 - - -func generateInputAttributeBinding*[T](bindingoffset: int = 0, locationoffset: int = 0): seq[VkVertexInputAttributeDescription] = - # TODO: - var location = 0 - var binding = bindingoffset - for name, value in T().fieldPairs: - when typeof(value) is VertexAttribute: - for i in 0 ..< compositeAttributesNumber[getAttributeType(value)](): - result.add( - VkVertexInputAttributeDescription( - binding: uint32(binding), - location: uint32(location), - format: getVkFormat[compositeAttributeType(getAttributeType(value))](), - offset: uint32(i * sizeof(compositeAttributeType(getAttributeType(value)))), - ) - ) - location += nLocationSlots[compositeAttributeType(getAttributeType(value))]() - binding += 1
--- a/src/semicongine/vulkan/api.nim Mon Mar 27 21:01:32 2023 +0700 +++ b/src/semicongine/vulkan/api.nim Tue Mar 28 00:20:49 2023 +0700 @@ -25,7 +25,7 @@ var callstr = astToStr(call).replace("\n", "") while callstr.find(" ") >= 0: callstr = callstr.replace(" ", " ") - debug "CALLING vulkan: ", callstr + debug "Calling vulkan: ", callstr let value = call if value != VK_SUCCESS: error "Vulkan error: ", astToStr(call), " returned ", $value @@ -12065,4 +12065,4 @@ converter VkBool2NimBool*(a: VkBool32): bool = a > 0 converter NimBool2VkBool*(a: bool): VkBool32 = VkBool32(a) -proc `$`*(x: uint32): string {.raises: [].} = addInt(result, x) \ No newline at end of file +proc `$`*(x: uint32): string {.raises: [].} = addInt(result, x)
--- a/src/semicongine/vulkan/commandbuffer.nim Mon Mar 27 21:01:32 2023 +0700 +++ b/src/semicongine/vulkan/commandbuffer.nim Tue Mar 28 00:20:49 2023 +0700 @@ -14,7 +14,7 @@ buffers*: seq[VkCommandBuffer] device: Device -proc createCommandBufferPool*(device: Device, family: QueueFamily, nBuffers: uint32): CommandBufferPool = +proc createCommandBufferPool*(device: Device, family: QueueFamily, nBuffers: int): CommandBufferPool = assert device.vk.valid var createInfo = VkCommandPoolCreateInfo( sType: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, @@ -29,7 +29,7 @@ sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, commandPool: result.vk, level: VK_COMMAND_BUFFER_LEVEL_PRIMARY, - commandBufferCount: nBuffers, + commandBufferCount: uint32(nBuffers), ) result.buffers = newSeq[VkCommandBuffer](nBuffers) checkVkResult device.vk.vkAllocateCommandBuffers(addr(allocInfo), result.buffers.toCPointer)
--- a/src/semicongine/vulkan/descriptor.nim Mon Mar 27 21:01:32 2023 +0700 +++ b/src/semicongine/vulkan/descriptor.nim Tue Mar 28 00:20:49 2023 +0700 @@ -83,7 +83,7 @@ pool.device.vk.vkDestroyDescriptorPool(pool.vk, nil) pool.vk.reset -proc allocateDescriptorSet*(pool: DescriptorPool, layout: DescriptorSetLayout, nframes: uint32): seq[DescriptorSet] = +proc allocateDescriptorSet*(pool: DescriptorPool, layout: DescriptorSetLayout, nframes: int): seq[DescriptorSet] = assert pool.device.vk.valid assert pool.vk.valid assert layout.device.vk.valid
--- a/src/semicongine/vulkan/pipeline.nim Mon Mar 27 21:01:32 2023 +0700 +++ b/src/semicongine/vulkan/pipeline.nim Tue Mar 28 00:20:49 2023 +0700 @@ -1,152 +1,30 @@ import ./api import ./utils -import ./renderpass import ./device import ./shader import ./descriptor type Pipeline* = object - device: Device + device*: Device vk*: VkPipeline - layout: VkPipelineLayout + layout*: VkPipelineLayout descriptorSetLayout*: DescriptorSetLayout - - -proc createPipeline*[VertexShader: Shader, FragmentShader: Shader](renderPass: RenderPass, vertexShader: VertexShader, fragmentShader: FragmentShader): Pipeline = - assert renderPass.vk.valid - assert renderPass.device.vk.valid - assert vertexShader.stage == VK_SHADER_STAGE_VERTEX_BIT - assert fragmentShader.stage == VK_SHADER_STAGE_FRAGMENT_BIT - - result.device = renderPass.device - - var descriptors = @[Descriptor( - thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - count: 1, - stages: @[VK_SHADER_STAGE_VERTEX_BIT], - itemsize: uint32(sizeof(shaderUniforms(vertexShader))) - )] - when shaderUniforms(vertexShader) is shaderUniforms(fragmentShader): - descriptors[0].stages.add VK_SHADER_STAGE_FRAGMENT_BIT - else: - descriptors.add Descriptor( - thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - count: 1, - stages: @[VK_SHADER_STAGE_FRAGMENT_BIT], - itemsize: uint32(sizeof(shaderUniforms(fragmentShader))) - ) - result.descriptorSetLayout = renderPass.device.createDescriptorSetLayout(descriptors) - - # TODO: Push constants - # var pushConstant = VkPushConstantRange( - # stageFlags: toBits shaderStage, - # offset: 0, - # size: 0, - # ) - var descriptorSetLayouts: seq[VkDescriptorSetLayout] = @[result.descriptorSetLayout.vk] - # var pushConstants: seq[VkPushConstantRange] = @[pushConstant] - var pipelineLayoutInfo = VkPipelineLayoutCreateInfo( - sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - setLayoutCount: uint32(descriptorSetLayouts.len), - pSetLayouts: descriptorSetLayouts.toCPointer, - # pushConstantRangeCount: uint32(pushConstants.len), - # pPushConstantRanges: pushConstants.toCPointer, - ) - checkVkResult vkCreatePipelineLayout(renderPass.device.vk, addr(pipelineLayoutInfo), nil, addr(result.layout)) + descriptorPool*: DescriptorPool + descriptorSets*: seq[DescriptorSet] - var - bindings: seq[VkVertexInputBindingDescription] - attributes: seq[VkVertexInputAttributeDescription] - vertexInputInfo = vertexShader.getVertexInputInfo(bindings, attributes) - inputAssembly = VkPipelineInputAssemblyStateCreateInfo( - sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, - topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - primitiveRestartEnable: VK_FALSE, - ) - viewportState = VkPipelineViewportStateCreateInfo( - sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, - viewportCount: 1, - scissorCount: 1, - ) - 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: toBits [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: toBits [VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT, 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: false, - attachmentCount: 1, - pAttachments: addr(colorBlendAttachment), - ) - 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: dynamicStates.toCPointer, - ) - stages = @[vertexShader.getPipelineInfo(), fragmentShader.getPipelineInfo()] - createInfo = VkGraphicsPipelineCreateInfo( - sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, - stageCount: uint32(stages.len), - pStages: stages.toCPointer, - 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.vk, - subpass: 0, - basePipelineHandle: VkPipeline(0), - basePipelineIndex: -1, - ) - checkVkResult vkCreateGraphicsPipelines( - renderPass.device.vk, - VkPipelineCache(0), - 1, - addr(createInfo), - nil, - addr(result.vk) - ) +proc run*(pipeline: Pipeline, commandBuffer: VkCommandBuffer) = + # pipeline an descriptors bound + echo "Running pipeline" proc destroy*(pipeline: var Pipeline) = assert pipeline.device.vk.valid assert pipeline.vk.valid assert pipeline.layout.valid assert pipeline.descriptorSetLayout.vk.valid - + + if pipeline.descriptorPool.vk.valid: + pipeline.descriptorPool.destroy() pipeline.descriptorSetLayout.destroy() pipeline.device.vk.vkDestroyPipelineLayout(pipeline.layout, nil) pipeline.device.vk.vkDestroyPipeline(pipeline.vk, nil)
--- a/src/semicongine/vulkan/renderpass.nim Mon Mar 27 21:01:32 2023 +0700 +++ b/src/semicongine/vulkan/renderpass.nim Tue Mar 28 00:20:49 2023 +0700 @@ -3,6 +3,9 @@ import ./api import ./utils import ./device +import ./pipeline +import ./shader +import ./descriptor import ../math type @@ -18,13 +21,16 @@ RenderPass* = object vk*: VkRenderPass device*: Device + inFlightFrames*: int subpasses*: seq[Subpass] + pipelines*: seq[Pipeline] proc createRenderPass*( device: Device, attachments: seq[VkAttachmentDescription], subpasses: seq[Subpass], - dependencies: seq[VkSubpassDependency] + dependencies: seq[VkSubpassDependency], + inFlightFrames: int, ): RenderPass = assert device.vk.valid var pAttachments = attachments @@ -56,10 +62,143 @@ pDependencies: pDependencies.toCPointer, ) result.device = device + result.inFlightFrames = inFlightFrames result.subpasses = pSubpasses + result.pipelines = newSeq[Pipeline](pSubpasses.len) checkVkResult device.vk.vkCreateRenderPass(addr(createInfo), nil, addr(result.vk)) -proc simpleForwardRenderPass*(device: Device, format: VkFormat, clearColor=Vec4([0.5'f32, 0.5'f32, 0.5'f32, 1'f32])): RenderPass = +proc attachPipeline[VertexShader: Shader, FragmentShader: Shader](renderPass: var RenderPass, vertexShader: VertexShader, fragmentShader: FragmentShader, subpass = 0'u32) = + assert renderPass.vk.valid + assert renderPass.device.vk.valid + assert vertexShader.stage == VK_SHADER_STAGE_VERTEX_BIT + assert fragmentShader.stage == VK_SHADER_STAGE_FRAGMENT_BIT + + var pipeline = Pipeline(device: renderPass.device) + + var descriptors = @[Descriptor( + thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + count: 1, + stages: @[VK_SHADER_STAGE_VERTEX_BIT], + itemsize: uint32(sizeof(shaderUniforms(vertexShader))) + )] + when shaderUniforms(vertexShader) is shaderUniforms(fragmentShader): + descriptors[0].stages.add VK_SHADER_STAGE_FRAGMENT_BIT + else: + descriptors.add Descriptor( + thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + count: 1, + stages: @[VK_SHADER_STAGE_FRAGMENT_BIT], + itemsize: uint32(sizeof(shaderUniforms(fragmentShader))) + ) + pipeline.descriptorSetLayout = renderPass.device.createDescriptorSetLayout(descriptors) + + # TODO: Push constants + # var pushConstant = VkPushConstantRange( + # stageFlags: toBits shaderStage, + # offset: 0, + # size: 0, + # ) + var descriptorSetLayouts: seq[VkDescriptorSetLayout] = @[pipeline.descriptorSetLayout.vk] + # var pushConstants: seq[VkPushConstantRange] = @[pushConstant] + var pipelineLayoutInfo = VkPipelineLayoutCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + setLayoutCount: uint32(descriptorSetLayouts.len), + pSetLayouts: descriptorSetLayouts.toCPointer, + # pushConstantRangeCount: uint32(pushConstants.len), + # pPushConstantRanges: pushConstants.toCPointer, + ) + checkVkResult vkCreatePipelineLayout(renderPass.device.vk, addr(pipelineLayoutInfo), nil, addr(pipeline.layout)) + + var + bindings: seq[VkVertexInputBindingDescription] + attributes: seq[VkVertexInputAttributeDescription] + vertexInputInfo = vertexShader.getVertexInputInfo(bindings, attributes) + inputAssembly = VkPipelineInputAssemblyStateCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + primitiveRestartEnable: VK_FALSE, + ) + viewportState = VkPipelineViewportStateCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + viewportCount: 1, + scissorCount: 1, + ) + 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: toBits [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: toBits [VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT, 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: false, + attachmentCount: 1, + pAttachments: addr(colorBlendAttachment), + ) + 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: dynamicStates.toCPointer, + ) + stages = @[vertexShader.getPipelineInfo(), fragmentShader.getPipelineInfo()] + createInfo = VkGraphicsPipelineCreateInfo( + sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + stageCount: uint32(stages.len), + pStages: stages.toCPointer, + pVertexInputState: addr(vertexInputInfo), + pInputAssemblyState: addr(inputAssembly), + pViewportState: addr(viewportState), + pRasterizationState: addr(rasterizer), + pMultisampleState: addr(multisampling), + pDepthStencilState: nil, + pColorBlendState: addr(colorBlending), + pDynamicState: addr(dynamicState), + layout: pipeline.layout, + renderPass: renderPass.vk, + subpass: subpass, + basePipelineHandle: VkPipeline(0), + basePipelineIndex: -1, + ) + checkVkResult vkCreateGraphicsPipelines( + renderPass.device.vk, + VkPipelineCache(0), + 1, + addr(createInfo), + nil, + addr(pipeline.vk) + ) + pipeline.descriptorPool = pipeline.device.createDescriptorSetPool(@[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1'u32)]) + pipeline.descriptorSets = pipeline.descriptorPool.allocateDescriptorSet(pipeline.descriptorSetLayout, renderPass.inFlightFrames) + renderPass.pipelines[subpass] = pipeline + +proc simpleForwardRenderPass*[VertexShader: Shader, FragmentShader: Shader](device: Device, format: VkFormat, vertexShader: VertexShader, fragmentShader: FragmentShader, inFlightFrames: int, clearColor=Vec4([0.5'f32, 0.5'f32, 0.5'f32, 1'f32])): RenderPass = assert device.vk.valid var attachments = @[VkAttachmentDescription( @@ -80,10 +219,14 @@ ) ] dependencies: seq[VkSubpassDependency] - result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies) + result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies, inFlightFrames=inFlightFrames) + result.attachPipeline(vertexShader, fragmentShader, 0) proc destroy*(renderpass: var RenderPass) = assert renderpass.device.vk.valid assert renderpass.vk.valid renderpass.device.vk.vkDestroyRenderPass(renderpass.vk, nil) renderpass.vk.reset + for pipeline in renderpass.pipelines.mitems: + pipeline.destroy() + renderpass.pipelines = @[]
--- a/src/semicongine/vulkan/swapchain.nim Mon Mar 27 21:01:32 2023 +0700 +++ b/src/semicongine/vulkan/swapchain.nim Tue Mar 28 00:20:49 2023 +0700 @@ -23,8 +23,8 @@ nImages*: uint32 imageviews*: seq[ImageView] framebuffers*: seq[Framebuffer] - nInFlight*: uint32 - currentInFlight*: uint32 + currentInFlight*: int + framesRendered*: int queueFinishedFence: seq[Fence] imageAvailableSemaphore*: seq[Semaphore] renderFinishedSemaphore*: seq[Semaphore] @@ -37,13 +37,12 @@ surfaceFormat: VkSurfaceFormatKHR, queueFamily: QueueFamily, desiredNumberOfImages=3'u32, - framesInFlight=2'u32, presentationMode: VkPresentModeKHR=VK_PRESENT_MODE_MAILBOX_KHR ): (Swapchain, VkResult) = assert device.vk.valid assert device.physicalDevice.vk.valid assert renderPass.vk.valid - assert framesInFlight > 0 + assert renderPass.inFlightFrames > 0 var capabilities = device.physicalDevice.getSurfaceCapabilities() @@ -76,7 +75,6 @@ swapchain = Swapchain( device: device, format: surfaceFormat.format, - nInFlight: framesInFlight, dimension: TVec2[uint32]([capabilities.currentExtent.width, capabilities.currentExtent.height]), renderPass: renderPass, ) @@ -93,21 +91,21 @@ let imageview = image.createImageView() swapChain.imageviews.add imageview swapChain.framebuffers.add swapchain.device.createFramebuffer(renderPass, [imageview], swapchain.dimension) - for i in 0 ..< swapchain.nInFlight: + for i in 0 ..< swapchain.renderPass.inFlightFrames: swapchain.queueFinishedFence.add device.createFence() swapchain.imageAvailableSemaphore.add device.createSemaphore() swapchain.renderFinishedSemaphore.add device.createSemaphore() - swapchain.commandBufferPool = device.createCommandBufferPool(queueFamily, swapchain.nInFlight) + swapchain.commandBufferPool = device.createCommandBufferPool(queueFamily, swapchain.renderPass.inFlightFrames) return (swapchain, createResult) -proc drawNextFrame*(swapchain: var Swapchain, pipeline: Pipeline): bool = +proc drawNextFrame*(swapchain: var Swapchain): bool = assert swapchain.device.vk.valid assert swapchain.vk.valid assert swapchain.device.firstGraphicsQueue().isSome assert swapchain.device.firstPresentationQueue().isSome - swapchain.currentInFlight = (swapchain.currentInFlight + 1) mod swapchain.nInFlight + swapchain.currentInFlight = (swapchain.currentInFlight + 1) mod swapchain.renderPass.inFlightFrames swapchain.queueFinishedFence[swapchain.currentInFlight].wait() var currentFramebufferIndex: uint32 @@ -124,13 +122,22 @@ swapchain.queueFinishedFence[swapchain.currentInFlight].reset() + var commandBuffer = swapchain.commandBufferPool.buffers[swapchain.currentInFlight] + renderCommands( - swapchain.commandBufferPool.buffers[swapchain.currentInFlight], + commandBuffer, swapchain.renderpass, swapchain.framebuffers[currentFramebufferIndex] ): - swapchain.commandBufferPool.buffers[swapchain.currentInFlight].vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.vk) - echo "TODO: Draw calls here" + for i in 0 ..< swapchain.renderpass.subpasses.len: + var pipeline = swapchain.renderpass.pipelines[i] + commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.vk) + commandBuffer.vkCmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, addr(pipeline.descriptorSets[swapchain.currentInFlight].vk), 0, nil) + pipeline.run(commandBuffer) + + swapchain.renderpass.pipelines[i].run(commandBuffer) + if i < swapchain.renderpass.subpasses.len - 1: + commandBuffer.vkCmdNextSubpass(VK_SUBPASS_CONTENTS_INLINE) var waitSemaphores = [swapchain.imageAvailableSemaphore[swapchain.currentInFlight].vk] @@ -141,7 +148,7 @@ pWaitSemaphores: addr(waitSemaphores[0]), pWaitDstStageMask: addr(waitStages[0]), commandBufferCount: 1, - pCommandBuffers: addr(swapchain.commandBufferPool.buffers[swapchain.currentInFlight]), + pCommandBuffers: addr(commandBuffer), signalSemaphoreCount: 1, pSignalSemaphores: addr(swapchain.renderFinishedSemaphore[swapchain.currentInFlight].vk), ) @@ -165,6 +172,8 @@ if not (presentResult in [VK_SUCCESS, VK_SUBOPTIMAL_KHR]): return false + inc swapchain.framesRendered + return true @@ -179,7 +188,7 @@ assert framebuffer.vk.valid framebuffer.destroy() swapchain.commandBufferPool.destroy() - for i in 0 ..< swapchain.nInFlight: + for i in 0 ..< swapchain.renderPass.inFlightFrames: assert swapchain.queueFinishedFence[i].vk.valid assert swapchain.imageAvailableSemaphore[i].vk.valid assert swapchain.renderFinishedSemaphore[i].vk.valid
--- a/src/semicongine/vulkan_helpers.nim Mon Mar 27 21:01:32 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,215 +0,0 @@ -import std/tables -import std/strutils -import std/strformat -import std/logging -import std/macros - -import ./vulkan -import ./window - -# the included code need checkVkResult, therefore having the template above -when defined(linux): - include ./platform/linux/vulkan -when defined(windows): - include ./platform/windows/vulkan - -const ENABLEVULKANVALIDATIONLAYERS* = not defined(release) - -func addrOrNil[T](obj: var openArray[T]): ptr T = - if obj.len > 0: addr(obj[0]) else: nil - -func filterForSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): seq[VkSurfaceFormatKHR] = - for format in formats: - if format.format == VK_FORMAT_B8G8R8A8_SRGB and format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: - result.add(format) - -func getSuitableSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): VkSurfaceFormatKHR = - let usableSurfaceFormats = filterForSurfaceFormat(formats) - if len(usableSurfaceFormats) == 0: - raise newException(Exception, "No suitable surface formats found") - return usableSurfaceFormats[0] - - -func cleanString*(str: openArray[char]): string = - for i in 0 ..< len(str): - if str[i] == char(0): - result = join(str[0 ..< i]) - break - -proc getInstanceExtensions*(): seq[string] = - var extensionCount: uint32 - checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr( extensionCount), nil) - var extensions = newSeq[VkExtensionProperties](extensionCount) - checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr( extensionCount), addrOrNil(extensions)) - - for extension in extensions: - result.add(cleanString(extension.extensionName)) - - -proc getDeviceExtensions*(device: VkPhysicalDevice): seq[string] = - var extensionCount: uint32 - checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr( - extensionCount), nil) - var extensions = newSeq[VkExtensionProperties](extensionCount) - checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr( - extensionCount), addrOrNil(extensions)) - - for extension in extensions: - result.add(cleanString(extension.extensionName)) - - -proc getValidationLayers*(): seq[string] = - var n_layers: uint32 - checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), nil) - var layers = newSeq[VkLayerProperties](n_layers) - checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), addrOrNil(layers)) - - for layer in layers: - result.add(cleanString(layer.layerName)) - - -proc getVulkanPhysicalDevices*(instance: VkInstance): seq[VkPhysicalDevice] = - var n_devices: uint32 - checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), nil) - result = newSeq[VkPhysicalDevice](n_devices) - checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), addrOrNil(result)) - - -proc getQueueFamilies*(device: VkPhysicalDevice): seq[VkQueueFamilyProperties] = - var n_queuefamilies: uint32 - vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), nil) - result = newSeq[VkQueueFamilyProperties](n_queuefamilies) - vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), addrOrNil(result)) - - -proc getDeviceSurfaceFormats*(device: VkPhysicalDevice, surface: VkSurfaceKHR): seq[VkSurfaceFormatKHR] = - var n_formats: uint32 - checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), nil) - result = newSeq[VkSurfaceFormatKHR](n_formats) - checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), addrOrNil(result)) - - -proc getDeviceSurfacePresentModes*(device: VkPhysicalDevice, - surface: VkSurfaceKHR): seq[VkPresentModeKHR] = - var n_modes: uint32 - checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr( - n_modes), nil) - result = newSeq[VkPresentModeKHR](n_modes) - checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr( - n_modes), addrOrNil(result)) - - -proc getSwapChainImages*(device: VkDevice, swapChain: VkSwapchainKHR): seq[VkImage] = - var n_images: uint32 - checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), nil) - result = newSeq[VkImage](n_images) - checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), addr( - result[0])) - - -func getPresentMode*(modes: seq[VkPresentModeKHR]): VkPresentModeKHR = - let preferredModes = [ - VK_PRESENT_MODE_MAILBOX_KHR, # triple buffering - VK_PRESENT_MODE_FIFO_RELAXED_KHR, # double duffering - VK_PRESENT_MODE_FIFO_KHR, # double duffering - VK_PRESENT_MODE_IMMEDIATE_KHR, # single buffering - ] - for preferredMode in preferredModes: - for mode in modes: - if preferredMode == mode: - return mode - # should never be reached, but seems to be garuanteed by vulkan specs to always be available - return VK_PRESENT_MODE_FIFO_KHR - - -proc createVulkanInstance*(vulkanVersion: uint32): VkInstance = - - var requiredExtensions = @["VK_KHR_surface".cstring] & REQUIRED_PLATFORM_EXTENSIONS - when ENABLEVULKANVALIDATIONLAYERS: - requiredExtensions.add("VK_EXT_debug_utils".cstring) - - let availableExtensions = getInstanceExtensions() - for extension in requiredExtensions: - assert $extension in availableExtensions, $extension - - let availableLayers = getValidationLayers() - var usableLayers = newSeq[cstring]() - - when ENABLEVULKANVALIDATIONLAYERS: - const desiredLayers = ["VK_LAYER_KHRONOS_validation".cstring, "VK_LAYER_MESA_overlay".cstring] - else: - const desiredLayers: array[0, string] = [] - for layer in desiredLayers: - if $layer in availableLayers: - usableLayers.add(layer) - - echo "Available validation layers: ", availableLayers - echo "Using validation layers: ", usableLayers - echo "Available extensions: ", availableExtensions - echo "Using instance extensions: ", requiredExtensions - - var appinfo = VkApplicationInfo( - sType: VK_STRUCTURE_TYPE_APPLICATION_INFO, - pApplicationName: "Hello Triangle", - pEngineName: "Custom engine", - apiVersion: vulkanVersion, - ) - var createinfo = VkInstanceCreateInfo( - sType: VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - pApplicationInfo: addr(appinfo), - enabledLayerCount: usableLayers.len.uint32, - ppEnabledLayerNames: cast[ptr UncheckedArray[cstring]](addrOrNil( - usableLayers)), - enabledExtensionCount: requiredExtensions.len.uint32, - ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr( - requiredExtensions[0])) - ) - checkVkResult vkCreateInstance(addr(createinfo), nil, addr(result)) - let other_extensions = @["VK_KHR_swapchain".cstring] - for extension in requiredExtensions & other_extensions: - result.loadExtension($extension) - -proc getVulcanDevice*( - physicalDevice: var VkPhysicalDevice, - features: var VkPhysicalDeviceFeatures, - graphicsQueueFamily: uint32, - presentationQueueFamily: uint32, -): (VkDevice, VkQueue, VkQueue) = - # setup queue and device - # TODO: need check this, possibly wrong logic, see Vulkan tutorial - var priority = 1.0'f32 - var queueCreateInfo = [ - VkDeviceQueueCreateInfo( - sType: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - queueFamilyIndex: graphicsQueueFamily, - queueCount: 1, - pQueuePriorities: addr(priority), - ), - ] - - var requiredExtensions = ["VK_KHR_swapchain".cstring] - var deviceCreateInfo = VkDeviceCreateInfo( - sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - queueCreateInfoCount: uint32(queueCreateInfo.len), - pQueueCreateInfos: addrOrNil(queueCreateInfo), - pEnabledFeatures: addr(features), - enabledExtensionCount: requiredExtensions.len.uint32, - ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(requiredExtensions)) - ) - checkVkResult vkCreateDevice(physicalDevice, addr(deviceCreateInfo), nil, - addr(result[0])) - vkGetDeviceQueue(result[0], graphicsQueueFamily, 0'u32, addr(result[1])) - vkGetDeviceQueue(result[0], presentationQueueFamily, 0'u32, addr(result[2])) - -proc debugCallback*( - messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT, - messageTypes: VkDebugUtilsMessageTypeFlagsEXT, - pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT, - userData: pointer -): VkBool32 {.cdecl.} = - echo &"{messageSeverity}: {VkDebugUtilsMessageTypeFlagBitsEXT(messageTypes)}: {pCallbackData.pMessage}" - return false - -proc getSurfaceCapabilities*(device: VkPhysicalDevice, - surface: VkSurfaceKHR): VkSurfaceCapabilitiesKHR = - checkVkResult device.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(surface, addr(result))
--- a/tests/test_vulkan_wrapper.nim Mon Mar 27 21:01:32 2023 +0700 +++ b/tests/test_vulkan_wrapper.nim Tue Mar 28 00:20:49 2023 +0700 @@ -64,32 +64,28 @@ selectedPhysicalDevice.filterForGraphicsPresentationQueues() ) - var surfaceFormat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat() - var renderpass = device.simpleForwardRenderPass(surfaceFormat.format) - echo renderpass - var (swapchain, res) = device.createSwapchain(renderpass, surfaceFormat, device.firstGraphicsQueue().get().family) - if res != VK_SUCCESS: - raise newException(Exception, "Unable to create swapchain") - const vertexBinary = shaderCode[Vertex, Uniforms, FragmentInput](stage=VK_SHADER_STAGE_VERTEX_BIT, version=450, entrypoint="main", "fragpos = pos;") const fragmentBinary = shaderCode[FragmentInput, void, Pixel](stage=VK_SHADER_STAGE_FRAGMENT_BIT, version=450, entrypoint="main", "color = vec4(1, 1, 1, 0);") var vertexshader = createShader[Vertex, Uniforms, FragmentInput](device, VK_SHADER_STAGE_VERTEX_BIT, "main", vertexBinary) fragmentshader = createShader[FragmentInput, void, Pixel](device, VK_SHADER_STAGE_FRAGMENT_BIT, "main", fragmentBinary) - pipeline = renderpass.createPipeline(vertexshader, fragmentshader) - descriptorPool = device.createDescriptorSetPool(@[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1'u32)]) - descriptorSet = descriptorPool.allocateDescriptorSet(pipeline.descriptorSetLayout, 1) + surfaceFormat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat() + renderpass = device.simpleForwardRenderPass(surfaceFormat.format, vertexshader, fragmentshader, 2) + var (swapchain, res) = device.createSwapchain(renderpass, surfaceFormat, device.firstGraphicsQueue().get().family, 2) + if res != VK_SUCCESS: + raise newException(Exception, "Unable to create swapchain") echo "All successfull" - discard swapchain.drawNextFrame(pipeline) + for i in 0 ..< 2: + discard swapchain.drawNextFrame() + echo "Rendered ", swapchain.framesRendered, " frames" echo "Start cleanup" + # cleanup checkVkResult device.vk.vkDeviceWaitIdle() - descriptorPool.destroy() vertexshader.destroy() fragmentshader.destroy() - pipeline.destroy() renderpass.destroy() swapchain.destroy() device.destroy()