Created
September 5, 2025 06:45
-
-
Save harrisonturton/adbedabd2568d9fda2a3026c1e6701cf to your computer and use it in GitHub Desktop.
Vulkan triangle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "vulkan.hpp" | |
#include <fcntl.h> | |
#include <cassert> | |
#include <cstdint> | |
#include <fstream> | |
#include <iostream> | |
#include <stdexcept> | |
// Must come before other vulkan includes | |
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS 1 | |
#include "vulkan/vulkan_raii.hpp" | |
// Separate from raii import | |
#include "vulkan/vulkan.hpp" | |
namespace vulkan { | |
// This assumption holds for both iOS and MacOS apps, and is coupled to the | |
// additional_contents attribute on the ios_application and macos_application | |
// Bazel rules. | |
// TODO: Figure out cross-platform mechanism for resource resolution | |
// const std::string kShaderPath = "/Contents/Shaders/simple.spv"; | |
const std::string kShaderPath = "/simple.spv"; | |
std::vector<uint8_t> readBinaryFile(const std::string &path); | |
vk::raii::ShaderModule createShaderModule( | |
vk::raii::Device &device, const std::vector<uint8_t> &spv); | |
Vulkan::Vulkan(std::string resourceRoot, void *layer) { | |
resourceRoot_ = resourceRoot; | |
layer_ = layer; | |
createInstance(); | |
createSurface(); | |
pickPhysicalDevice(); | |
createLogicalDevice(); | |
createSwapchain(); | |
createImageViews(); | |
createRenderPass(); | |
createGraphicsPipeline(); | |
createFramebuffers(); | |
createCommandPool(); | |
createCommandBuffer(); | |
createSyncObjects(); | |
} | |
void Vulkan::createInstance() { | |
vk::raii::Context context; | |
auto appInfo = vk::ApplicationInfo{ | |
.pApplicationName = "Triangle", | |
.applicationVersion = VK_MAKE_VERSION(1, 0, 0), | |
.pEngineName = "No Engine", | |
.engineVersion = VK_MAKE_VERSION(1, 0, 0), | |
.apiVersion = vk::ApiVersion14, | |
}; | |
// TODO: Validate that required extensions exist before requesting them | |
std::vector<const char *> extensions = { | |
vk::KHRSurfaceExtensionName, | |
vk::EXTMetalSurfaceExtensionName, | |
}; | |
// TODO: Build //third_party/vulkan_validaton_layers and use them here | |
std::vector<const char *> layers = {}; | |
auto createInfo = vk::InstanceCreateInfo{ | |
.pApplicationInfo = &appInfo, | |
.enabledExtensionCount = uint32_t(extensions.size()), | |
.ppEnabledExtensionNames = extensions.data(), | |
.ppEnabledLayerNames = layers.data(), | |
.enabledLayerCount = uint32_t(layers.size()), | |
}; | |
instance_ = vk::raii::Instance(context, createInfo); | |
} | |
void Vulkan::createSurface() { | |
auto createInfo = vk::MetalSurfaceCreateInfoEXT{ | |
.pLayer = layer_, | |
}; | |
surface_ = instance_.createMetalSurfaceEXT(createInfo); | |
} | |
void Vulkan::pickPhysicalDevice() { | |
physicalDevice_ = instance_.enumeratePhysicalDevices().front(); | |
} | |
void Vulkan::createLogicalDevice() { | |
// Locate the graphics queue | |
auto queueFamilyProperties = physicalDevice_.getQueueFamilyProperties(); | |
auto graphicsQueueFamilyIndex = -1; | |
for (auto i = 0; i < queueFamilyProperties.size(); i++) { | |
auto queue = queueFamilyProperties[i]; | |
if (queue.queueFlags & vk::QueueFlagBits::eGraphics) { | |
graphicsQueueFamilyIndex = i; | |
break; | |
} | |
} | |
if (graphicsQueueFamilyIndex < 0) { | |
std::cerr << "failed to find graphics queue\n"; | |
return; | |
} | |
graphicsQueueFamilyIndex_ = graphicsQueueFamilyIndex; | |
// Making a simplifying assumption that the graphics queue family also | |
// supports presentation. This is often true, but not always. | |
// TODO: Support selecting distinct graphics and presentation queues | |
auto presentSupport = physicalDevice_.getSurfaceSupportKHR( | |
uint32_t(graphicsQueueFamilyIndex), *surface_); | |
if (!presentSupport) { | |
std::cerr | |
<< "error: presentation not supported by the graphics queue family\n"; | |
return; | |
} | |
auto queuePriorities = std::vector<float>{ | |
1.0, | |
}; | |
auto queueCreateInfos = std::vector<vk::DeviceQueueCreateInfo>{ | |
vk::DeviceQueueCreateInfo{ | |
.queueFamilyIndex = uint32_t(graphicsQueueFamilyIndex), | |
.pQueuePriorities = queuePriorities.data(), | |
.queueCount = 1, | |
}, | |
}; | |
auto featureChain = vk::StructureChain< | |
vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan13Features, | |
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>{ | |
{}, | |
{.dynamicRendering = true}, | |
{.extendedDynamicState = true}, | |
}; | |
auto extensions = std::vector<const char *>{ | |
vk::KHRSwapchainExtensionName, | |
vk::KHRSpirv14ExtensionName, | |
vk::KHRSynchronization2ExtensionName, | |
vk::KHRCreateRenderpass2ExtensionName, | |
}; | |
auto deviceCreateInfo = vk::DeviceCreateInfo{ | |
.pQueueCreateInfos = queueCreateInfos.data(), | |
.queueCreateInfoCount = uint32_t(queueCreateInfos.size()), | |
.ppEnabledExtensionNames = extensions.data(), | |
.enabledExtensionCount = uint32_t(extensions.size()), | |
.pNext = &featureChain.get<vk::PhysicalDeviceFeatures2>(), | |
}; | |
device_ = physicalDevice_.createDevice(deviceCreateInfo); | |
graphicsQueue_ = device_.getQueue(uint32_t(graphicsQueueFamilyIndex), 0); | |
presentQueue_ = device_.getQueue(uint32_t(graphicsQueueFamilyIndex), 0); | |
} | |
void Vulkan::createSwapchain() { | |
vk::SurfaceCapabilitiesKHR surfaceCapabilities | |
= physicalDevice_.getSurfaceCapabilitiesKHR(*surface_); | |
std::vector<vk::SurfaceFormatKHR> availableFormats | |
= physicalDevice_.getSurfaceFormatsKHR(*surface_); | |
std::vector<vk::PresentModeKHR> availablePresentModes | |
= physicalDevice_.getSurfacePresentModesKHR(*surface_); | |
// TODO: Make this selection smarter | |
vk::SurfaceFormatKHR surfaceFormat = availableFormats[0]; | |
vk::PresentModeKHR presentMode = vk::PresentModeKHR::eFifo; | |
vk::Extent2D swapchainExtent = { | |
.height = uint32_t(height_), | |
.width = uint32_t(width_), | |
}; | |
swapchainImageFormat_ = surfaceFormat.format; | |
swapchainExtent_ = swapchainExtent; | |
auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); | |
minImageCount = (surfaceCapabilities.maxImageCount > 0 | |
&& minImageCount > surfaceCapabilities.maxImageCount) | |
? surfaceCapabilities.maxImageCount | |
: minImageCount; | |
uint32_t imageCount = surfaceCapabilities.minImageCount + 1; | |
if (surfaceCapabilities.maxImageCount > 0 | |
&& imageCount > surfaceCapabilities.maxImageCount) { | |
imageCount = surfaceCapabilities.maxImageCount; | |
} | |
auto swapchainCreateInfo = vk::SwapchainCreateInfoKHR{ | |
.flags = vk::SwapchainCreateFlagsKHR(), | |
.surface = *surface_, | |
.minImageCount = minImageCount, | |
.imageFormat = surfaceFormat.format, | |
.imageColorSpace = surfaceFormat.colorSpace, | |
.imageExtent = swapchainExtent, | |
.imageArrayLayers = 1, | |
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment, | |
.imageSharingMode = vk::SharingMode::eExclusive, | |
.preTransform = surfaceCapabilities.currentTransform, | |
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, | |
.presentMode = presentMode, | |
.clipped = true, | |
.oldSwapchain = nullptr, | |
// Assuming graphics and present queues are the same | |
.queueFamilyIndexCount = 0, | |
.pQueueFamilyIndices = nullptr, | |
}; | |
swapchain_ = device_.createSwapchainKHR(swapchainCreateInfo); | |
swapchainImages_ = swapchain_.getImages(); | |
} | |
void Vulkan::createImageViews() { | |
swapchainImageViews_.clear(); | |
auto imageViewCreateInfo = vk::ImageViewCreateInfo{ | |
.viewType = vk::ImageViewType::e2D, | |
.format = swapchainImageFormat_, | |
.subresourceRange = vk::ImageSubresourceRange{ | |
.aspectMask = vk::ImageAspectFlagBits::eColor, | |
.baseMipLevel = 0, | |
.levelCount = 1, | |
.baseArrayLayer = 0, | |
.layerCount = 1, | |
}, | |
}; | |
for (auto &image : swapchainImages_) { | |
imageViewCreateInfo.image = image; | |
swapchainImageViews_.emplace_back( | |
device_.createImageView(imageViewCreateInfo)); | |
} | |
} | |
void Vulkan::createRenderPass() { | |
auto colorAttachment = vk::AttachmentDescription{ | |
.format = swapchainImageFormat_, | |
.samples = vk::SampleCountFlagBits::e1, | |
.loadOp = vk::AttachmentLoadOp::eClear, | |
.storeOp = vk::AttachmentStoreOp::eStore, | |
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare, | |
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare, | |
.initialLayout = vk::ImageLayout::eUndefined, | |
.finalLayout = vk::ImageLayout::ePresentSrcKHR, | |
}; | |
auto colorAttachmentRef = vk::AttachmentReference{ | |
.attachment = 0, | |
.layout = vk::ImageLayout::eColorAttachmentOptimal, | |
}; | |
auto subpass = vk::SubpassDescription{ | |
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics, | |
.colorAttachmentCount = 1, | |
.pColorAttachments = &colorAttachmentRef, | |
}; | |
auto renderPassInfo = vk::RenderPassCreateInfo{ | |
.attachmentCount = 1, | |
.pAttachments = &colorAttachment, | |
.subpassCount = 1, | |
.pSubpasses = &subpass, | |
}; | |
renderPass_ = device_.createRenderPass(renderPassInfo); | |
} | |
void Vulkan::createGraphicsPipeline() { | |
auto root = std::filesystem::path(resourceRoot_); | |
auto shaderPath = root.concat(kShaderPath); | |
auto shaderBytecode = readBinaryFile(shaderPath); | |
vk::raii::ShaderModule shaderModule | |
= createShaderModule(device_, shaderBytecode); | |
auto vertShaderStageInfo = vk::PipelineShaderStageCreateInfo{ | |
.stage = vk::ShaderStageFlagBits::eVertex, | |
.module = shaderModule, | |
.pName = "vertMain", | |
}; | |
auto fragShaderStageInfo = vk::PipelineShaderStageCreateInfo{ | |
.stage = vk::ShaderStageFlagBits::eFragment, | |
.module = shaderModule, | |
.pName = "fragMain", | |
}; | |
vk::PipelineShaderStageCreateInfo shaderStages[] = { | |
vertShaderStageInfo, | |
fragShaderStageInfo, | |
}; | |
auto vertexInputInfo = vk::PipelineVertexInputStateCreateInfo{}; | |
auto inputAssemblyInfo = vk::PipelineInputAssemblyStateCreateInfo{ | |
.topology = vk::PrimitiveTopology::eTriangleList, | |
}; | |
std::vector dynamicStates | |
= {vk::DynamicState::eViewport, vk::DynamicState::eScissor}; | |
auto dynamicState = vk::PipelineDynamicStateCreateInfo{ | |
.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size()), | |
.pDynamicStates = dynamicStates.data(), | |
}; | |
auto viewportCreateInfo = vk::PipelineViewportStateCreateInfo{ | |
.viewportCount = 1, | |
// Dynamic state, set at draw time | |
.pScissors = nullptr, | |
// Dynamic state, set at draw time | |
.pViewports = nullptr, | |
}; | |
auto rasteriser = vk::PipelineRasterizationStateCreateInfo{ | |
.depthClampEnable = vk::False, | |
.rasterizerDiscardEnable = vk::False, | |
.polygonMode = vk::PolygonMode::eFill, | |
.cullMode = vk::CullModeFlagBits::eBack, | |
.frontFace = vk::FrontFace::eClockwise, | |
.depthBiasEnable = vk::False, | |
.depthBiasSlopeFactor = 1.0f, | |
.lineWidth = 1.0f, | |
}; | |
auto multisampling = vk::PipelineMultisampleStateCreateInfo{ | |
.rasterizationSamples = vk::SampleCountFlagBits::e1, | |
.sampleShadingEnable = vk::False, | |
}; | |
auto rgbaWriteMask | |
= vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | |
| vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; | |
auto colorBlendAttachment = vk::PipelineColorBlendAttachmentState{ | |
.colorWriteMask = rgbaWriteMask, | |
.blendEnable = vk::False, | |
}; | |
auto colorBlending = vk::PipelineColorBlendStateCreateInfo{ | |
.logicOpEnable = vk::False, | |
.logicOp = vk::LogicOp::eCopy, | |
.attachmentCount = 1, | |
.pAttachments = &colorBlendAttachment, | |
}; | |
auto pipelineLayoutCreateInfo = vk::PipelineLayoutCreateInfo{ | |
.setLayoutCount = 0, | |
.pushConstantRangeCount = 0, | |
}; | |
pipelineLayout_ = device_.createPipelineLayout(pipelineLayoutCreateInfo); | |
// TODO: Check whether we need to specify the | |
// vk::PipelineRenderingCreateInfo here, or whether that's only when dynamic | |
// rendering is enabled. | |
auto pipelineInfo = vk::GraphicsPipelineCreateInfo{ | |
.stageCount = 2, | |
.pStages = shaderStages, | |
.pVertexInputState = &vertexInputInfo, | |
.pInputAssemblyState = &inputAssemblyInfo, | |
.pViewportState = &viewportCreateInfo, | |
.pRasterizationState = &rasteriser, | |
.pMultisampleState = &multisampling, | |
.pColorBlendState = &colorBlending, | |
.layout = *pipelineLayout_, | |
// MoltenVK does not support the dynamic state extension | |
.pDynamicState = &dynamicState, | |
.renderPass = *renderPass_, | |
}; | |
graphicsPipeline_ = device_.createGraphicsPipeline(nullptr, pipelineInfo); | |
} | |
void Vulkan::createFramebuffers() { | |
for (auto i = 0; i < swapchainImageViews_.size(); i++) { | |
vk::ImageView attachments[] = { | |
swapchainImageViews_[i], | |
}; | |
auto framebufferInfo = vk::FramebufferCreateInfo{ | |
.renderPass = *renderPass_, | |
.attachmentCount = 1, | |
.pAttachments = attachments, | |
.width = swapchainExtent_.width, | |
.height = swapchainExtent_.height, | |
.layers = 1, | |
}; | |
swapchainFramebuffers_.emplace_back( | |
device_.createFramebuffer(framebufferInfo)); | |
} | |
} | |
void Vulkan::createCommandPool() { | |
auto poolInfo = vk::CommandPoolCreateInfo{ | |
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, | |
.queueFamilyIndex = uint32_t(graphicsQueueFamilyIndex_), | |
}; | |
commandPool_ = device_.createCommandPool(poolInfo); | |
} | |
void Vulkan::createCommandBuffer() { | |
auto allocInfo = vk::CommandBufferAllocateInfo{ | |
.commandPool = *commandPool_, | |
.level = vk::CommandBufferLevel::ePrimary, | |
.commandBufferCount = 1, | |
}; | |
auto buffers = device_.allocateCommandBuffers(allocInfo); | |
commandBuffer_ = std::move(buffers[0]); | |
} | |
void Vulkan::recordCommandBuffer(uint32_t imageIndex) { | |
commandBuffer_.begin({}); | |
// Begin render pass | |
transitionImageLayout( | |
imageIndex, vk::ImageLayout::eUndefined, | |
vk::ImageLayout::eColorAttachmentOptimal, {}, // srcAccessMask | |
vk::AccessFlagBits2::eColorAttachmentWrite, | |
vk::PipelineStageFlagBits2::eTopOfPipe, | |
vk::PipelineStageFlagBits2::eColorAttachmentOutput); | |
vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); | |
auto renderPassInfo = vk::RenderPassBeginInfo{ | |
.renderPass = *renderPass_, | |
.framebuffer = swapchainFramebuffers_[imageIndex], | |
.renderArea = vk::Rect2D{ | |
.offset = vk::Offset2D{0, 0}, | |
.extent = swapchainExtent_, | |
}, | |
.clearValueCount = 1, | |
.pClearValues = &clearColor, | |
}; | |
commandBuffer_.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); | |
// Draw with pipeline | |
commandBuffer_.bindPipeline( | |
vk::PipelineBindPoint::eGraphics, *graphicsPipeline_); | |
vk::Viewport viewport = vk::Viewport{ | |
.x = 0.0, | |
.y = 0.0, | |
.width = static_cast<float>(swapchainExtent_.width), | |
.height = static_cast<float>(swapchainExtent_.height), | |
.minDepth = 0.0f, | |
.maxDepth = 1.0f, | |
}; | |
commandBuffer_.setViewport(0, viewport); | |
auto scissor = vk::Rect2D{ | |
.extent = swapchainExtent_, | |
.offset = vk::Offset2D{0, 0}, | |
}; | |
commandBuffer_.setScissor(0, scissor); | |
commandBuffer_.draw( | |
3, // Vertex count | |
1, // Instance count | |
0, // First vertex | |
0 // First instance | |
); | |
commandBuffer_.endRenderPass(); | |
transitionImageLayout( | |
imageIndex, vk::ImageLayout::eColorAttachmentOptimal, | |
vk::ImageLayout::ePresentSrcKHR, | |
vk::AccessFlagBits2::eColorAttachmentWrite, {}, | |
vk::PipelineStageFlagBits2::eColorAttachmentOutput, | |
vk::PipelineStageFlagBits2::eBottomOfPipe); | |
commandBuffer_.end(); | |
} | |
void Vulkan::transitionImageLayout( | |
uint32_t imageIndex, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, | |
vk::AccessFlags2 srcAccessMask, vk::AccessFlags2 dstAccessMask, | |
vk::PipelineStageFlags2 srcStageMask, | |
vk::PipelineStageFlags2 dstStageMask) { | |
auto barrier = vk::ImageMemoryBarrier2{ | |
.srcStageMask = srcStageMask, | |
.srcAccessMask = srcAccessMask, | |
.dstStageMask = dstStageMask, | |
.dstAccessMask = dstAccessMask, | |
.oldLayout = oldLayout, | |
.newLayout = newLayout, | |
.srcQueueFamilyIndex = vk::QueueFamilyIgnored, | |
.dstQueueFamilyIndex = vk::QueueFamilyIgnored, | |
.image = swapchainImages_[imageIndex], | |
.subresourceRange = { | |
.aspectMask = vk::ImageAspectFlagBits::eColor, | |
.baseMipLevel = 0, | |
.levelCount = 1, | |
.baseArrayLayer = 0, | |
.layerCount = 1, | |
}, | |
}; | |
auto dependencyInfo = vk::DependencyInfo{ | |
.dependencyFlags = {}, | |
.imageMemoryBarrierCount = 1, | |
.pImageMemoryBarriers = &barrier, | |
}; | |
commandBuffer_.pipelineBarrier2(dependencyInfo); | |
} | |
void Vulkan::createSyncObjects() { | |
presentCompleteSemaphore_ = device_.createSemaphore({}); | |
renderFinishedSemaphore_ = device_.createSemaphore({}); | |
auto drawFenceInfo = vk::FenceCreateInfo{ | |
// Create as already signalled to unblock rendering of the first frame | |
.flags = vk::FenceCreateFlagBits::eSignaled, | |
}; | |
drawFence_ = device_.createFence(drawFenceInfo); | |
} | |
void Vulkan::drawFrame() { | |
if (framebufferResized_) { | |
framebufferResized_ = false; | |
recreateSwapchain(); | |
return; | |
} | |
while (vk::Result::eTimeout | |
== device_.waitForFences(*drawFence_, vk::True, UINT64_MAX)) { | |
} | |
auto [result, imageIndex] = swapchain_.acquireNextImage( | |
UINT64_MAX, *presentCompleteSemaphore_, nullptr); | |
if (result == vk::Result::eErrorOutOfDateKHR) { | |
recreateSwapchain(); | |
return; | |
} else if ( | |
result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { | |
std::cerr << "failed to acquire swapchain image\n"; | |
throw std::runtime_error("failed to acquire swapchain image"); | |
} | |
device_.resetFences(*drawFence_); | |
commandBuffer_.reset(); | |
recordCommandBuffer(imageIndex); | |
vk::PipelineStageFlags waitDestinationStageMask | |
= vk::PipelineStageFlagBits::eColorAttachmentOutput; | |
auto submitInfo = vk::SubmitInfo{ | |
.waitSemaphoreCount = 1, | |
.pWaitSemaphores = &*presentCompleteSemaphore_, | |
.pWaitDstStageMask = &waitDestinationStageMask, | |
.commandBufferCount = 1, | |
.pCommandBuffers = &*commandBuffer_, | |
.signalSemaphoreCount = 1, | |
.pSignalSemaphores = &*renderFinishedSemaphore_, | |
}; | |
try { | |
graphicsQueue_.submit(submitInfo, *drawFence_); | |
} catch (const std::exception &ex) { | |
std::cerr << "failed to submit: " << ex.what() << std::endl; | |
throw ex; | |
} | |
auto presentInfo = vk::PresentInfoKHR{ | |
.pWaitSemaphores = &*renderFinishedSemaphore_, | |
.waitSemaphoreCount = 1, | |
.swapchainCount = 1, | |
.pSwapchains = &*swapchain_, | |
.pImageIndices = &imageIndex, | |
.pResults = nullptr, | |
}; | |
auto presentResult = presentQueue_.presentKHR(presentInfo); | |
if (presentResult == vk::Result::eErrorOutOfDateKHR | |
|| presentResult == vk::Result::eSuboptimalKHR || framebufferResized_) { | |
framebufferResized_ = false; | |
recreateSwapchain(); | |
} else if (presentResult != vk::Result::eSuccess) { | |
std::cerr << "failed to present swapchain image: " << presentResult | |
<< std::endl; | |
throw new std::runtime_error("failed to present swapchain image"); | |
} | |
} | |
void Vulkan::resize(int width, int height) { | |
width_ = width; | |
height_ = height; | |
framebufferResized_ = true; | |
} | |
void Vulkan::recreateSwapchain() { | |
device_.waitIdle(); | |
// Cleanup | |
swapchainFramebuffers_.clear(); | |
swapchainImageViews_.clear(); | |
swapchain_ = nullptr; | |
// Recreate | |
createSwapchain(); | |
createImageViews(); | |
createFramebuffers(); | |
} | |
vk::raii::ShaderModule createShaderModule( | |
vk::raii::Device &device, const std::vector<uint8_t> &spv) { | |
auto createInfo = vk::ShaderModuleCreateInfo{ | |
.codeSize = spv.size(), | |
.pCode = reinterpret_cast<const uint32_t *>(spv.data()), | |
}; | |
return device.createShaderModule(createInfo); | |
} | |
std::vector<std::uint8_t> readBinaryFile(const std::string &path) { | |
std::ifstream file = std::ifstream(path, std::ios::binary | std::ios::ate); | |
assert(!file.fail() && "failed to open file"); | |
std::ifstream::pos_type size = file.tellg(); | |
file.seekg(0, std::ios::beg); | |
std::vector<uint8_t> buf(size); | |
file.read(reinterpret_cast<char *>(buf.data()), size); | |
return buf; | |
} | |
} // namespace vulkan |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <cstddef> | |
#include <memory> | |
// Must come before other vulkan includes | |
#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS 1 | |
#include "vulkan/vulkan_raii.hpp" | |
namespace vulkan { | |
class Vulkan { | |
public: | |
/** | |
* Create a Vulkan instance from a CAMetalLayer. | |
*/ | |
explicit Vulkan(std::string resourceRoot, void* layer); | |
void drawFrame(); | |
void resize(int width, int height); | |
private: | |
void createInstance(); | |
void createSurface(); | |
void pickPhysicalDevice(); | |
void createLogicalDevice(); | |
void createSwapchain(); | |
void createImageViews(); | |
void createRenderPass(); | |
void createGraphicsPipeline(); | |
void createFramebuffers(); | |
void createCommandPool(); | |
void createCommandBuffer(); | |
void createSyncObjects(); | |
void recreateSwapchain(); | |
void recordCommandBuffer(uint32_t imageIndex); | |
void transitionImageLayout( | |
uint32_t imageIndex, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, | |
vk::AccessFlags2 srcAccessMask, vk::AccessFlags2 dstAccessMask, | |
vk::PipelineStageFlags2 srcStageMask, | |
vk::PipelineStageFlags2 dstStageMask); | |
void* layer_; | |
std::string resourceRoot_; | |
// Account for device pixel ratio | |
int width_ = 1200 * 2; | |
int height_ = 800 * 2; | |
bool framebufferResized_ = false; | |
vk::raii::Instance instance_ = nullptr; | |
vk::raii::SurfaceKHR surface_ = nullptr; | |
vk::raii::PhysicalDevice physicalDevice_ = nullptr; | |
vk::raii::Device device_ = nullptr; | |
vk::raii::Queue graphicsQueue_ = nullptr; | |
vk::raii::Queue presentQueue_ = nullptr; | |
vk::raii::SwapchainKHR swapchain_ = nullptr; | |
std::vector<vk::Image> swapchainImages_; | |
std::vector<vk::raii::ImageView> swapchainImageViews_; | |
vk::raii::RenderPass renderPass_ = nullptr; | |
vk::raii::PipelineLayout pipelineLayout_ = nullptr; | |
vk::raii::Pipeline graphicsPipeline_ = nullptr; | |
std::vector<vk::raii::Framebuffer> swapchainFramebuffers_; | |
vk::raii::CommandPool commandPool_ = nullptr; | |
vk::raii::CommandBuffer commandBuffer_ = nullptr; | |
vk::raii::Semaphore presentCompleteSemaphore_ = nullptr; | |
vk::raii::Semaphore renderFinishedSemaphore_ = nullptr; | |
vk::raii::Fence drawFence_ = nullptr; | |
vk::Format swapchainImageFormat_; | |
vk::Extent2D swapchainExtent_; | |
int graphicsQueueFamilyIndex_ = -1; | |
}; | |
} // namespace vulkan |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment