using System; using System.IO; using System.Collections; using System.Diagnostics; using Vulkan; using Vulkan.Loader; using Vulkan.Metadata; using Glfw; using VulkanMemoryAllocator; namespace Vulkan.Example; class Program { public static void CheckResult(VkResult result) { if (result != .VkSuccess) Internal.FatalError(scope $"{result}"); } public static Glfw.Window* window; public static VkSurfaceKHR surface; public static VulkanLoadedFunctions vkPfns; public static VkAllocationCallbacks* vkAlloc; public static VkInstance instance; public static PhysicalDevice physicalDevice ~ delete _; public static VkDevice device; public static VkQueue graphicsQueue; public static VkQueue transferQueue; public static VkQueue presentQueue; public static VmaAllocator allocator; public static VkBuffer vertexBuffer; public static VkBuffer indexBuffer; public static VkSwapchainKHR swapchain; public static VkImage[] images; public static VkImageView[] imageViews; public static VkPipelineLayout pipelineLayout; public static VkPipeline pipeline; public const int MaxFramesInFlight = 3; public const uint64 Timeout = uint64.MaxValue; public static VkCommandPool commandPool; public static VkCommandBuffer[MaxFramesInFlight] commandBuffers; public static VkSemaphore[MaxFramesInFlight] imageAvalible; public static VkSemaphore[MaxFramesInFlight] renderFinished; public static VkFence[MaxFramesInFlight] renderFinishedFence; typealias Vector = (float x, float y, float z); typealias Vertex = (Vector pos, Vector color); public const Vertex[8] vertices = . ( ((-1, -1, -1), (1, 0, 0)), // Bottom-left-back (Red) (( 1, -1, -1), (0, 1, 0)), // Bottom-right-back (Green) (( 1, 1, -1), (0, 0, 1)), // Top-right-back (Blue) ((-1, 1, -1), (1, 1, 0)), // Top-left-back (Yellow) ((-1, -1, 1), (1, 0, 1)), // Bottom-left-front (Cyan) (( 1, -1, 1), (0, 1, 1)), // Bottom-right-front (Magenta) (( 1, 1, 1), (1, 0.5f, 0)), // Top-right-front (Orange) ((-1, 1, 1), (0.5f, 0, 0.5f)) // Top-left-front (Purple) ); public const uint32[36] indices = . ( 0, 1, 2, 0, 2, 3, // Back face 4, 7, 6, 4, 6, 5, // Front face 0, 3, 7, 0, 7, 4, // Left face 1, 2, 6, 1, 6, 5, // Right face 0, 1, 5, 0, 5, 4, // Bottom face 2, 3, 7, 2, 7, 6 // Top face ); public struct PushConstants : this(float[16] view); public class PhysicalDevice { public const VulkanExtension[?] requiredExtensions = .(.VK_KHR_swapchain); public const VulkanFeature[?] requiredFeatures = .(.dynamicRendering, .fillModeNonSolid); public VkPhysicalDevice device; public VulkanApiVersion apiVersion; public VkPhysicalDeviceType kind; public StringView name; public VkSurfaceFormatKHR swapchainFormat; public int graphicsFamilyIndex = -1; public int transferFamilyIndex = -1; public int presentFamilyIndex = -1; append List enabledExtensionNames = .(extensionsFinal.Count); public bool Init(VkPhysicalDevice device) { this.device = device; device.GetProperties(var properties); apiVersion = (.)properties.apiVersion; name = .(&properties.deviceName); device.GetQueueFamilyProperties_Scope!(let queueFamilies); for (let family in queueFamilies) { if (family.queueFlags.HasFlag(.Transfer)) transferFamilyIndex = @family; if (family.queueFlags.HasFlag(.Graphics)) graphicsFamilyIndex = @family; if (Glfw.GetPhysicalDevicePresentationSupport((.)instance, (.)device, (.)@family)) presentFamilyIndex = @family; } if (graphicsFamilyIndex < 0 || transferFamilyIndex < 0 || presentFamilyIndex < 0) { Debug.WriteLine($"[Vulkan.Example] {name}: Missing queues"); return false; } VkResult result = device.EnumerateDeviceExtensionProperties_Scope!(null, let extensions); CheckResult(result); findExts: for (let final in extensionsFinal) { let promotedTo = final.PromotedTo; if (promotedTo != 0 && apiVersion >= promotedTo) continue; char8* cstr = final.Name; for (var ext in extensions) if (String.Equals(cstr, &ext.extensionName)) { enabledExtensionNames.Add(cstr); continue findExts; } Debug.WriteLine($"[Vulkan.Example] {name}: Missing extension {final}"); return false; } BumpAllocator featureAlloc = scope .(); Dictionary featureChainBuffer = new:featureAlloc .(8); var featureChainHead = BuildFeatureChain(let featureBools, featureChainBuffer, featureAlloc); device.GetFeatures2KHR(out *featureChainHead); for (let featureBool in featureBools) { if (*featureBool) continue; Debug.WriteLine($"[Vulkan.Example] {name}: Missing feature {requiredFeatures[@featureBool]}"); return false; } device.GetSurfaceFormatsKHR_Scope!(surface, let surfaceFormats); for (let format in surfaceFormats) { if (format.colorSpace != .SrgbNonlinearKHR) continue; if (swapchainFormat.format == .UNDEFINED) swapchainFormat = format; else if (swapchainFormat.format.Size > format.format.Size) swapchainFormat = format; } if (swapchainFormat.format == .UNDEFINED) swapchainFormat = surfaceFormats[0]; return true; } public VkResult CreateDevice(params VkQueue*[3] outQueues) { uint32[outQueues.Count] queueIndices = .(); List queueCIs = scope .(queueIndices.Count); queueCIs: for (var index in ref queueIndices) { index = (.)-1; switch (@index) { case 0: index = (.)graphicsFamilyIndex; case 1: index = (.)transferFamilyIndex; case 2: index = (.)presentFamilyIndex; } Runtime.Assert(index != (.)-1); for (let queueCI in queueCIs) if (index == queueCI.queueFamilyIndex) continue queueCIs; queueCIs.Add(.(null, 0, index, float[1](1.0f))); } BumpAllocator featureAlloc = scope .(); Dictionary featureChainBuffer = new:featureAlloc .(8); void* pNext = BuildFeatureChain(let featureBools, featureChainBuffer, featureAlloc); for (let featureBool in featureBools) *featureBool = true; VkResult result = device.CreateDevice(scope .(pNext, 0, queueCreateInfos: queueCIs, enabledExtensionNames: enabledExtensionNames ), vkAlloc, out Program.device); if (result != .VkSuccess) return result; vkPfns.LoadDevice(Program.device); for (let outQueue in outQueues) Program.device.GetQueue(queueIndices[@outQueue], 0, out *outQueue); return .VkSuccess; } public void CreateSwapchain() { VkResult result = device.GetSurfaceCapabilitiesKHR(surface, let capabilities); CheckResult(result); uint32 minImageCount = capabilities.minImageCount + 1; if (capabilities.maxImageCount != 0 && minImageCount > capabilities.maxImageCount) minImageCount = capabilities.maxImageCount; VkExtent2D imageExtent; if (capabilities.currentExtent.width != uint32.MaxValue) imageExtent = capabilities.currentExtent; else { Glfw.GetFramebufferSize(window, let width, let height); imageExtent = .( Math.Clamp((.)width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), Math.Clamp((.)height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) ); } bool sharingExclusive = graphicsFamilyIndex == presentFamilyIndex; VkSwapchainKHR oldSwapchain = swapchain; defer oldSwapchain.DestroyKHR(Program.device, vkAlloc); result = Program.device.CreateSwapchainKHR(scope .() { surface = surface, minImageCount = minImageCount, imageFormat = swapchainFormat.format, imageColorSpace = swapchainFormat.colorSpace, imageExtent = imageExtent, imageArrayLayers = 1, imageUsage = .ColorAttachment, imageSharingMode = sharingExclusive ? .Exclusive : .Concurrent, queueFamilyIndices = sharingExclusive ? .() : uint32[?]((.)graphicsFamilyIndex, (.)presentFamilyIndex), preTransform = capabilities.currentTransform, compositeAlpha = .OpaqueKHR, presentMode = .FifoKHR, clipped = true, oldSwapchain = oldSwapchain, }, vkAlloc, out swapchain); CheckResult(result); delete images; swapchain.GetImagesKHR_New!(Program.device, out images); if (imageViews != null) { for (let view in imageViews) view.Destroy(Program.device, vkAlloc); if (images.Count != imageViews.Count) { delete imageViews; imageViews = new .[images.Count]; } } else imageViews = new .[images.Count]; for (let image in images) { result = Program.device.CreateImageView(scope .(null, 0, image, ._2d, swapchainFormat.format, components: default, subresourceRange: .(.Color, 0, 1, 0, 1) ), vkAlloc, out imageViews[@image]); CheckResult(result); } } public static int operator<=>(Self lhs, Self rhs) { return (lhs.kind == .DiscreteGpu) <=> (rhs.kind == .DiscreteGpu); } public VkPhysicalDeviceFeatures2* BuildFeatureChain(out VkBool32*[requiredFeatures.Count] outFeatureBools, Dictionary chainBuffer, BumpAllocator alloc) { outFeatureBools = default; VkPhysicalDeviceFeatures2* head = new:alloc .(); VkBaseInStructure* tail = (.)head; chainBuffer.Add(VkPhysicalDeviceFeatures2.SType, tail); for (let feature in requiredFeatures) { VkStructureType sType; do { let apiVersion = feature.ApiVersion; if (apiVersion != 0 && apiVersion <= this.apiVersion) { sType = apiVersion.FeatureStruct; break; } let ext = feature.Extension; if (ext != 0) { sType = ext.FeatureStruct; break; } Runtime.FatalError(); } VkBaseInStructure** valuePtr; switch (chainBuffer.TryAdd(sType)) { case .Added(?, out valuePtr): *valuePtr = (.)(new:alloc uint8[sType.Type.Size]).Ptr; (*valuePtr).sType = sType; tail.pNext = *valuePtr; tail = *valuePtr; fallthrough; case .Exists(?, out valuePtr): outFeatureBools[@feature] = GetFeatureBool(feature, *valuePtr); } } return head; } [Comptime, OnCompile(.TypeInit)] static void OnTypeInit() { HashSet extensions = scope .(16); for (let ext in requiredExtensions) { Runtime.Assert(ext.Kind == .Device); extensions.Add(ext); } for (let feature in requiredFeatures) { let ext = feature.Extension; if (ext == 0) continue; Runtime.Assert(ext.Kind == .Device); extensions.Add(ext); } bool added; repeat { added = false; for (let ext in extensions) for (let dep in ext.Dependencies) if (dep.Kind == .Device) added |= extensions.Add(dep); } while (added); String emit = scope .(1024); emit.Append("public const VulkanExtension[?] extensionsFinal = .("); bool comma = false; for (let ext in extensions) { if (comma) emit.Append(", "); comma = true; emit.Append('.'); ext.ToString(emit); } emit.Append(""" ); public static VkBool32* GetFeatureBool(VulkanFeature feature, VkBaseInStructure* pStruct) { switch (feature) { """); for (let feature in requiredFeatures) { emit.Append("\tcase ."); feature.ToString(emit); emit.Append(""" : switch (pStruct.sType) { """); mixin FeatureStruct(var holder) { if (holder != 0) { VkStructureType featureStruct = holder.FeatureStruct; Runtime.Assert(featureStruct != 0); emit.Append("\t\tcase ."); featureStruct.ToString(emit); emit.Append(": return &(("); featureStruct.Type.ToString(emit); emit.Append("*)pStruct)."); feature.ToString(emit); emit.Append(";\n"); } } FeatureStruct!(feature.ApiVersion); FeatureStruct!(feature.Extension); emit.Append(""" default: Runtime.FatalError("Invalid struct for feature"); } """); } emit.Append(""" default: Runtime.FatalError("Unhandled feature"); } } """); Compiler.EmitTypeBody(typeof(Self), emit); } } public static void CreateAndPushBuffer(out VkBuffer buffer, out VmaAllocation allocation, VkBufferUsageFlags usage, void* ptr, int size) { VkBufferCreateInfo* bufferCI = scope .(null, 0, (.)size, .TransferSrc, .Exclusive ); VkResult result = allocator.CreateBuffer(bufferCI, scope .() { flags = .Mapped | .HostAccessSequentialWrite, usage = .Auto, }, let transferBuffer, let transferBufferAlloc, let transferBufferAllocInfo); CheckResult(result); defer allocator.DestroyBuffer(transferBuffer, transferBufferAlloc); Internal.MemCpy(transferBufferAllocInfo.pMappedData, ptr, size); if (physicalDevice.transferFamilyIndex != physicalDevice.graphicsFamilyIndex) { bufferCI.sharingMode = .Concurrent; bufferCI.queueFamilyIndices = uint32[?]( (.)physicalDevice.transferFamilyIndex, (.)physicalDevice.graphicsFamilyIndex, ); } bufferCI.usage = usage | .TransferDst; result = allocator.CreateBuffer(bufferCI, scope .() { usage = .Auto, }, out buffer, out allocation, ?); CheckResult(result); result = device.CreateCommandPool(scope .(null, .Transient, (.)physicalDevice.transferFamilyIndex ), vkAlloc, let cmdPool); CheckResult(result); defer cmdPool.Destroy(device, vkAlloc); result = device.AllocateCommandBuffers(scope .(null, cmdPool, .Primary, 1 ), var cmd); CheckResult(result); defer cmdPool.FreeCommandBuffers(device, .(&cmd, 1)); result = cmd.Begin(scope .(null, .OneTimeSubmit, null)); CheckResult(result); cmd.CopyBuffer(transferBuffer, buffer, VkBufferCopy[?]( .(0, 0, (.)size) )); result = cmd.End(); CheckResult(result); result = transferQueue.Submit(VkSubmitInfo[?](.() { commandBufferCount = 1, pCommandBuffers = &cmd, })); result = transferQueue.WaitIdle(); CheckResult(result); } public static int Main(String[] args) { VkResult result; { Glfw.Init(); defer:: Glfw.Terminate(); Glfw.SetErrorCallback((error_code, description) => Runtime.FatalError(scope $"Glfw.{(Glfw.Error)error_code}: {StringView(description)}")); Glfw.WindowHint(.ClientApi, Glfw.NoApi); Glfw.WindowHint(.Resizable, Glfw.True); window = Glfw.CreateWindow(1024, 800, "Vulkan-Beef Example", null, null); defer:: Glfw.DestroyWindow(window); Runtime.Assert(Glfw.VulkanSupported()); Glfw.InitVulkanLoader(null); void* getProc = Glfw.GetInstanceProcAddress(0, "vkGetInstanceProcAddr"); vkPfns.Load((.)getProc); VulkanLoadedFunctions.current = &vkPfns; result = vkEnumerateInstanceVersion(let instanceFullVersion); CheckResult(result); VulkanApiVersion instanceApiVersion = (.)VK_MAKE_API_VERSION(0, VK_API_VERSION_MAJOR(instanceFullVersion), VK_API_VERSION_MINOR(instanceFullVersion), 0); char8** glfwExts = Glfw.GetRequiredInstanceExtensions(let glfwExtCount); List extensions = scope .(Span(glfwExts, glfwExtCount)); extensions.Add(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); #if DEBUG extensions.Add(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); #endif { result = vkEnumerateInstanceExtensionProperties_Scope!(null, let properties); CheckResult(result); findExt: for (let ext in extensions) { for (var property in properties) { if (String.Equals(ext, &property.extensionName)) continue findExt; } Runtime.FatalError(scope $"Missing instance extension {StringView(ext)}"); } } #if DEBUG VkDebugUtilsMessengerCreateInfoEXT* debugCI = scope .(null, 0, .VerboseEXT | .ErrorEXT | .WarningEXT, .GeneralEXT | .ValidationEXT | .PerformanceEXT, (messageSeverity, messageTypes, pCallbackData, pUserData) => { Debug.WriteLine($"[Vulkan] {StringView(pCallbackData.pMessage)}"); return false; }); #endif result = vkCreateInstance(scope .() { #if DEBUG pNext = debugCI, #endif flags = 0, pApplicationInfo = scope .() { pApplicationName = "Vulkan-Beef Example", applicationVersion = VK_MAKE_VERSION(0, 0, 1), apiVersion = (.)instanceApiVersion, }, enabledLayerNames = char8*[?]( #if DEBUG "VK_LAYER_KHRONOS_validation" #endif ), enabledExtensionNames = extensions, }, vkAlloc, out instance); CheckResult(result); vkPfns.LoadInstance(instance); defer:: instance.Destroy(vkAlloc); #if DEBUG result = instance.CreateDebugUtilsMessengerEXT(debugCI, vkAlloc, let messegner); CheckResult(result); defer:: messegner.DestroyEXT(instance, vkAlloc); #endif result = (.)Glfw.CreateWindowSurface((.)instance, window, (.)vkAlloc, out *(Glfw.VkSurfaceKHR*)&surface); CheckResult(result); defer:: surface.DestroyKHR(instance, vkAlloc); } { result = instance.EnumeratePhysicalDevices_Scope!(let devices); CheckResult(result); List options = new .(devices.Count); for (let device in devices) { PhysicalDevice info = new .(); if (!info.Init(device)) { delete info; continue; } options.Add(info); } Runtime.Assert(!options.IsEmpty, "No suitable GPUs found"); options.Sort(); physicalDevice = options.PopFront(); Debug.WriteLine($"[Vulkan.Example] Selected GPU: {physicalDevice.name}"); result = physicalDevice.CreateDevice(&graphicsQueue, &transferQueue, &presentQueue); CheckResult(result); defer:: device.Destroy(vkAlloc); DeleteContainerAndItems!(options); physicalDevice.CreateSwapchain(); defer:: { for (let view in imageViews) view.Destroy(Program.device, vkAlloc); delete imageViews; delete images; swapchain.DestroyKHR(device, vkAlloc); } } { VmaVulkanFunctions vulkanPfns = default; [Comptime] static void EmitFill() { String emit = scope .(1024); for (let field in typeof(VmaVulkanFunctions).GetFields()) emit.Append("vulkanPfns.", field.Name, " = vkPfns.", field.Name, ";\n"); Compiler.MixinRoot(emit); } EmitFill(); result = vmaCreateAllocator(scope .() { physicalDevice = physicalDevice.device, device = device, pAllocationCallbacks = vkAlloc, pVulkanFunctions = &vulkanPfns, instance = instance }, out allocator); CheckResult(result); defer:: allocator.Destroy(); } { var vertices = vertices; CreateAndPushBuffer(out vertexBuffer, let vertexBufferAllocation, .VertexBuffer, &vertices, sizeof(decltype(vertices))); defer:: allocator.DestroyBuffer(vertexBuffer, vertexBufferAllocation); var indices = indices; CreateAndPushBuffer(out indexBuffer, let indexBufferAllocation, .IndexBuffer, &indices, sizeof(decltype(indices))); defer:: allocator.DestroyBuffer(indexBuffer, indexBufferAllocation); } { mixin ShaderModule(String path) { let spirvBuf = Compiler.ReadBinary(path); Span spirv = spirvBuf; result = device.CreateShaderModule(scope .() { codeSize = (.)spirv.Length, pCode = (.)spirv.Ptr, }, vkAlloc, let module); CheckResult(result); defer:: module.Destroy(device, vkAlloc); module } let vertModule = ShaderModule!(Compiler.ProjectDir + "/src/shader.vert.spv"); let fragModule = ShaderModule!(Compiler.ProjectDir + "/src/shader.frag.spv"); result = device.CreatePipelineLayout(scope .( pushConstantRanges: VkPushConstantRange[?](.(.Vertex, 0, sizeof(PushConstants))) ), vkAlloc, out pipelineLayout); CheckResult(result); defer:: pipelineLayout.Destroy(device, vkAlloc); result = default(VkPipelineCache).CreateGraphicsPipelines(device, 1, scope .() { pNext = scope VkPipelineRenderingCreateInfo() { colorAttachmentFormats = VkFormat[?](physicalDevice.swapchainFormat.format), }, stages = VkPipelineShaderStageCreateInfo[?]( .(null, 0, .Vertex, vertModule, "main"), .(null, 0, .Fragment, fragModule, "main"), ), pVertexInputState = scope .(null, 0, VkVertexInputBindingDescription[?]( .(0, strideof(Vertex), .Vertex) ), VkVertexInputAttributeDescription[?]( .(0, 0, .R32G32B32_SFLOAT, offsetof(Vertex, pos)), .(1, 0, .R32G32B32_SFLOAT, offsetof(Vertex, color)), ) ), pInputAssemblyState = scope .(null, 0, .TriangleList, false), pViewportState = scope .() { viewportCount = 1, scissorCount = 1, }, pRasterizationState = scope .() { depthClampEnable = false, rasterizerDiscardEnable = false, polygonMode = .Line, cullMode = .None, depthBiasEnable = false, lineWidth = 1.0f, }, pMultisampleState = scope .() { rasterizationSamples = ._1, sampleShadingEnable = false, alphaToCoverageEnable = false, alphaToOneEnable = false, }, pColorBlendState = scope .() { logicOpEnable = false, logicOp = .Copy, attachmentCount = 1, pAttachments = scope .() { blendEnable = false, srcColorBlendFactor = .One, dstColorBlendFactor = .Zero, colorBlendOp = .Add, srcAlphaBlendFactor = .One, dstAlphaBlendFactor = .Zero, alphaBlendOp = .Add, colorWriteMask = .R | .G | .B | .A, }, blendConstants = .(0.0f, 0.0f, 0.0f, 0.0f), }, pDynamicState = scope .(null, 0, VkDynamicState[?]( .Viewport, .Scissor, )), layout = pipelineLayout, }, vkAlloc, out pipeline); CheckResult(result); defer:: pipeline.Destroy(device, vkAlloc); } { result = device.CreateCommandPool(scope .(null, .ResetCommandBuffer, (.)physicalDevice.graphicsFamilyIndex ), vkAlloc, out commandPool); CheckResult(result); defer:: commandPool.Destroy(device, vkAlloc); for (int i < MaxFramesInFlight) { result = device.CreateSemaphore(scope .(null, 0), vkAlloc, out imageAvalible[i]); CheckResult(result); result = device.CreateSemaphore(scope .(null, 0), vkAlloc, out renderFinished[i]); CheckResult(result); result = device.CreateFence(scope .(null, .Signaled), vkAlloc, out renderFinishedFence[i]); CheckResult(result); } defer:: { for (let semaphore in imageAvalible) semaphore.Destroy(device, vkAlloc); for (let semaphore in renderFinished) semaphore.Destroy(device, vkAlloc); for (let fence in renderFinishedFence) fence.Destroy(device, vkAlloc); } result = device.AllocateCommandBuffers(scope .(null, commandPool, .Primary, commandBuffers.Count ), out commandBuffers[0]); CheckResult(result); defer:: commandPool.FreeCommandBuffers(device, commandBuffers); } Glfw.GetFramebufferSize(window, var framebufferWidth, var framebufferHeight); int frameIndex = 0; float rotationAngle = 0; Stopwatch deltaWatch = scope .()..Start(); while (Glfw.WindowShouldClose(window) == 0) { result = device.WaitForFences(.(&renderFinishedFence[frameIndex], 1), false, Timeout); CheckResult(result); result = device.ResetFences(.(&renderFinishedFence[frameIndex], 1)); CheckResult(result); Glfw.GetFramebufferSize(window, let newWidth, let newHeight); if (framebufferWidth != newWidth || framebufferHeight != newHeight) { framebufferWidth = newWidth; framebufferHeight = newHeight; physicalDevice.CreateSwapchain(); } uint32 imageIndex; while (true) { result = swapchain.AcquireNextImageKHR(device, Timeout, imageAvalible[frameIndex], null, out imageIndex); switch (result) { case .VkSuboptimalKHR: physicalDevice.CreateSwapchain(); continue; case .VkSuccess: default: CheckResult(result); } break; } PushConstants consts = default; rotationAngle += (.)deltaWatch.Elapsed.TotalSeconds; consts.view = . ( Math.Cos(rotationAngle), 0, Math.Sin(rotationAngle), 0, 0, 1, 0, 0, -Math.Sin(rotationAngle), 0, Math.Cos(rotationAngle), -5, 0, 0, 0, 1 ); deltaWatch.Restart(); VkCommandBuffer cmd = commandBuffers[frameIndex]; result = cmd.Reset(); CheckResult(result); result = cmd.Begin(scope .(null, .OneTimeSubmit, null)); CheckResult(result); { cmd.PipelineBarrier(.TopOfPipe, .ColorAttachmentOutput, 0, .(), .(), VkImageMemoryBarrier[?](.() { oldLayout = .Undefined, newLayout = .ColorAttachmentOptimal, srcAccessMask = 0, dstAccessMask = .ColorAttachmentWrite, srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, image = images[imageIndex], subresourceRange = .() { aspectMask = .Color, baseMipLevel = 0, levelCount = 1, baseArrayLayer = 0, layerCount = 1, }, })); VkRect2D renderArea = .(.(0, 0), .((.)framebufferWidth, (.)framebufferHeight)); cmd.BeginRendering(scope .(null, 0, renderArea: renderArea, layerCount: 1, viewMask: 0, colorAttachments: VkRenderingAttachmentInfo[?](.() { imageView = imageViews[imageIndex], imageLayout = .ColorAttachmentOptimal, resolveMode = .None, resolveImageView = imageViews[imageIndex], resolveImageLayout = .PresentSrcKHR, loadOp = .Clear, storeOp = .Store, clearValue = .( color: .() { float32 = .(0, 0, 0, 1f) }, depthStencil: default ) }) )); cmd.BindPipeline(.Graphics, pipeline); cmd.SetViewport(0, VkViewport[?](.(0, 0, framebufferWidth, framebufferHeight, 0f, 1f))); cmd.SetScissor(0, .(&renderArea, 1)); cmd.BindVertexBuffers(0, 1, &vertexBuffer, scope uint64()); cmd.BindIndexBuffer(indexBuffer, 0, .Uint32); cmd.PushConstants(pipelineLayout, .Vertex, 0, .(&consts, sizeof(PushConstants))); cmd.DrawIndexed(indices.Count, 1, 0, 0, 0); cmd.EndRendering(); cmd.PipelineBarrier(.ColorAttachmentOutput, .BottomOfPipe, 0, .(), .(), VkImageMemoryBarrier[?](.() { oldLayout = .ColorAttachmentOptimal, newLayout = .PresentSrcKHR, srcAccessMask = .ColorAttachmentWrite, dstAccessMask = 0, srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, image = images[imageIndex], subresourceRange = .() { aspectMask = .Color, baseMipLevel = 0, levelCount = 1, baseArrayLayer = 0, layerCount = 1, }, })); } result = cmd.End(); CheckResult(result); VkPipelineStageFlags waitDstStageMaks = .ColorAttachmentOutput; result = graphicsQueue.Submit(VkSubmitInfo[?](.() { pNext = scope VkTimelineSemaphoreSubmitInfo(null, waitSemaphoreValues: uint64[?](1), signalSemaphoreValues: uint64[?](2) ), waitSemaphoreCount = 1, pWaitSemaphores = &imageAvalible[frameIndex], pWaitDstStageMask = &waitDstStageMaks, commandBufferCount = 1, pCommandBuffers = &cmd, signalSemaphoreCount = 1, pSignalSemaphores = &renderFinished[frameIndex], } ), renderFinishedFence[frameIndex]); CheckResult(result); result = presentQueue.PresentKHR(scope .() { waitSemaphoreCount = 1, pWaitSemaphores = &renderFinished[frameIndex], swapchainCount = 1, pSwapchains = &swapchain, pImageIndices = &imageIndex, }); CheckResult(result); frameIndex = (frameIndex + 1) % MaxFramesInFlight; Glfw.PollEvents(); } result = device.WaitIdle(); CheckResult(result); return 0; } }