first commit

This commit is contained in:
2026-05-29 19:12:18 +02:00
commit 0d469e0538
8 changed files with 977 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
build
recovery
BeefSpace_Lock.toml
BeefSpace_User.toml

15
BeefProj.toml Normal file
View File

@@ -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"]

10
BeefSpace.toml Normal file
View File

@@ -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"}

918
src/Program.bf Normal file
View File

@@ -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<char8*> 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<VkStructureType, VkBaseInStructure*> 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<VkDeviceQueueCreateInfo> 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<VkStructureType, VkBaseInStructure*> 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<uint32>((.)width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
Math.Clamp<uint32>((.)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<VkStructureType, VkBaseInStructure*> 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<VulkanExtension> 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<char8*> extensions = scope .(Span<char8*>(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<PhysicalDevice> 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<uint8> 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;
}
}

8
src/shader.frag.glsl Normal file
View File

@@ -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);
}

BIN
src/shader.frag.spv Normal file

Binary file not shown.

22
src/shader.vert.glsl Normal file
View File

@@ -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;
}

BIN
src/shader.vert.spv Normal file

Binary file not shown.