commit 0d469e0538bb4025d4b68975dabb5dc1ede1e8ae Author: Rune Date: Fri May 29 19:12:18 2026 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0dd3b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +recovery +BeefSpace_Lock.toml +BeefSpace_User.toml diff --git a/BeefProj.toml b/BeefProj.toml new file mode 100644 index 0000000..43d7126 --- /dev/null +++ b/BeefProj.toml @@ -0,0 +1,15 @@ +FileVersion = 1 + +[Project] +Name = "Vulkan.Example" +TargetType = "BeefGUIApplication" +StartupObject = "Vulkan.Example.Program" + +[Dependencies] +corlib = "*" +Vulkan = "*" +"Glfw.git" = {Git = "https://git.unicon-gmbh.de/BeefBindings/Glfw.git"} +VulkanMemoryAllocator = "*" + +[Configs.Debug.Win64] +PreBuildCmds = ["glslangvalidator -V $(ProjectDir)/src/shader.vert.glsl -o $(ProjectDir)/src/shader.vert.spv", "glslangvalidator -V $(ProjectDir)/src/shader.frag.glsl -o $(ProjectDir)/src/shader.frag.spv"] diff --git a/BeefSpace.toml b/BeefSpace.toml new file mode 100644 index 0000000..dfb4629 --- /dev/null +++ b/BeefSpace.toml @@ -0,0 +1,10 @@ +FileVersion = 1 + +[Workspace] +StartupProject = "Vulkan.Example" + +[Projects] +"Vulkan.Example" = {Path = "."} +Vulkan = {Path = "../BeefBindings_Vulkan"} +"Glfw.git" = {Git = "https://git.unicon-gmbh.de/BeefBindings/Glfw.git"} +VulkanMemoryAllocator = {Path = "../../RandomStuff/Cpp2Beef/Bindings/VulkanMemoryAllocator"} diff --git a/src/Program.bf b/src/Program.bf new file mode 100644 index 0000000..eb4d0ba --- /dev/null +++ b/src/Program.bf @@ -0,0 +1,918 @@ +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 = Vma.CreateBuffer(allocator, bufferCI, scope .() + { + flags = .Mapped | .HostAccessSequentialWrite, + usage = .Auto, + }, let transferBuffer, let transferBufferAlloc, let transferBufferAllocInfo); + CheckResult(result); + defer Vma.DestroyBuffer(allocator, 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 = Vma.CreateBuffer(allocator, 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 = Vma.CreateAllocator(scope .() + { + physicalDevice = physicalDevice.device, + device = device, + pAllocationCallbacks = vkAlloc, + pVulkanFunctions = &vulkanPfns, + instance = instance + }, out allocator); + CheckResult(result); + defer:: Vma.DestroyAllocator(allocator); + } + + { + var vertices = vertices; + CreateAndPushBuffer(out vertexBuffer, let vertexBufferAllocation, .VertexBuffer, &vertices, sizeof(decltype(vertices))); + defer:: Vma.DestroyBuffer(allocator, vertexBuffer, vertexBufferAllocation); + + var indices = indices; + CreateAndPushBuffer(out indexBuffer, let indexBufferAllocation, .IndexBuffer, &indices, sizeof(decltype(indices))); + defer:: Vma.DestroyBuffer(allocator, 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; + } +} \ No newline at end of file diff --git a/src/shader.frag.glsl b/src/shader.frag.glsl new file mode 100644 index 0000000..e948fd6 --- /dev/null +++ b/src/shader.frag.glsl @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} \ No newline at end of file diff --git a/src/shader.frag.spv b/src/shader.frag.spv new file mode 100644 index 0000000..828f1e5 Binary files /dev/null and b/src/shader.frag.spv differ diff --git a/src/shader.vert.glsl b/src/shader.vert.glsl new file mode 100644 index 0000000..66e5e1b --- /dev/null +++ b/src/shader.vert.glsl @@ -0,0 +1,22 @@ +#version 450 + +layout(push_constant) uniform PushConstants { + mat4 view; +} pushConsts; + +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 color; + +layout(location = 0) out vec3 fragColor; + +const mat4 projection = mat4( + 2, 0, 0, 0, + 0, 2, 0, 0, + 0, 0, -1, -0.2f, + 0, 0, -1, 0 +); + +void main() { + gl_Position = vec4(position, 1.0) * pushConsts.view * projection; + fragColor = color; +} \ No newline at end of file diff --git a/src/shader.vert.spv b/src/shader.vert.spv new file mode 100644 index 0000000..bc98f0a Binary files /dev/null and b/src/shader.vert.spv differ