918 lines
27 KiB
Beef
918 lines
27 KiB
Beef
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 = 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<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 = 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<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;
|
|
}
|
|
} |